İçeriğe geç
13/3030 bölümden 13. bölüm

Inference’ı Ucuzlatmak: KV cache, Batching ve Quantization

Aynı model aynı soruyu 8,8 ve 78,9 saniyede, byte-identical yanıtlıyor. Sonra INT4: iddia değil, üç ölçüm.

Bu sayfada

Aynı model, aynı makinede, aynı soruyu aynı 48 token ile yanıtlıyor. İki çıktı token token aynı — varsayılmadı, kontrol edildi.

TEXT
with a key-value cache:     8.85 s   ( 6.01 tokens/second)
without a key-value cache: 78.95 s   ( 0.60 tokens/second)

Tek bir argüman değişti: use_cache=False. Model, prompt, sampling ya da aritmetik açısından hiçbir şey farklı değil; ikinci çalışma da çektiği zahmete karşılık daha doğru değil. Hiçbir kazanç olmadan dokuz kat daha yavaş.

Bu bölümün şekli bu. İçindeki her şey — cache, batch, quantized ağırlıklar — yanıtı değiştirmeyen iş için ödeme yapmayı durdurma ya da daha ucuz bir yanıtın neye mal olduğunu öğrenme girişimi. Chapter 10 training için fiyat listesini kurmuştu. Bu, sonsuza kadar ödeyeceğin tarafın fiyat listesi: deployed bir model, ömrünün geri kalanı boyunca her istekte, ürettiği her token için kabaca 2N2N FLOPs harcar.

Bir token üretmek için decoder-only transformer, o ana kadarki tüm sequence’ı alır, her layer’dan geçirir ve son position’dan probability distribution’ı okur. Sonra seçilen token’ı ekler ve aynı şeyi tekrar yapar. Bu açıklama doğru; yavaş çalışmanın yaptığı da budur.

Ama aynı zamanda muazzam derecede savurgandır; nedeni de Chapter 9’daki causal mask’tir. Position 7’nin key ve value vectors, position 7’nin input’undan ve ondan önceki position’lardan hesaplanır. Position 8 geldiğinde, position 7 onu göremez — causal tam da bu demektir — dolayısıyla position 7’nin key ve value değerleri öncekiyle birebir aynı sayılardır. Yavaş çalışma yine de her adımda bunları yeniden hesaplar.

Öyleyse sakla. Bu saklama, language model serving’deki tek başına en sonuç belirleyici optimizasyon olan key-value cache’tir:

generate.pyPYTHON
out = model(prompt_ids, use_cache=True)          # prefill: the whole prompt
past = out.past_key_values                        
nxt = out.logits[:, -1].argmax(-1, keepdim=True)

for _ in range(n - 1):
    out = model(nxt, past_key_values=past, use_cache=True)   
    past = out.past_key_values                                
    nxt = out.logits[:, -1].argmax(-1, keepdim=True)

Loop içinde modele ne verildiğine bak: nxt, tek token. Sequence değil. Yeni token’ın query’si cached her key’e attention uygular; cached key’ler zaten asla değişmeyecekti. Bu bir approximation değildir — yukarıdaki identical-output kontrolünün amacı bu. Cache kaliteyi hızla takas etmez; gereksiz aritmetiği siler.

Ölçeklenmeyi temiz görmek için transformer’ı çıkarıp d=64d = 64 ile tek bir attention head’i zamanla; generation’ın tek adımı iki yolla hesaplanır:

context içindeki tokenher şeyi yeniden hesaplamacache ileoranscore matrix
1280,59 ms0,062 ms10x65.536 B vs 512 B
2561,20 ms0,163 ms7x262.144 B vs 1.024 B
5127,03 ms0,078 ms90x1.048.576 B vs 2.048 B
102417,31 ms0,114 ms152x4.194.304 B vs 4.096 B
204859,83 ms0,214 ms279x16.777.216 B vs 8.192 B
4096236,18 ms0,284 ms832x67.108.864 B vs 16.384 B

Sağdaki sütun nedendir. Yeniden hesaplama her adımda tam n×nn \times n attention matrix’i kurar — Chapter 9’daki asymptotic-notation kutusundaki O(n2)O(n^2), token başına bir kez ödenir. Cache ile bunun yerine bir 1×n1 \times n satırı kurarsın: 4.096 token’da 67 MB score’a karşı 16 KB.

Milisaniye yerine multiply-accumulate saymak makineyi argümandan çıkarır. Cold start’tan TT token üretmek için:

üretilen tokencache ileyeniden hesaplamaoran
1282,6 M192,0 M73x
51223,1 M7,36 G318x
2048293,7 M392,6 G1.336x

Adım başına cached sürüm context’e göre lineer, uncached sürüm quadratic’tir; bir generation boyunca toplandığında O(T2)O(T^2), O(T3)O(T^3)’a karşıdır ve oran sınırsız büyür. Girişteki dokuz kat fark 48 token üzerinden ölçüldü — bu tablonun ilk satırından bile kısa.

Cache ayrıca bellekte neyin bulunması gerektiğini de değiştirir. 8 GB laptop GPU’da fp16 ile 256 token üretilirken, allocator peak alınıp resident weights çıkarıldığında:

peak working memory
cache ile21,8 MB
yeniden hesaplama181,7 MB

8,3 kat daha fazla memory, aynı token’ları daha yavaş üretmek için harcandı. Bu, Chapter 5’te verilen sözün beklenmedik bir yönden gelişi: orada reverse-mode autodiff, backward pass için her intermediate’ı canlı tutmak zorundaydı ve training memory’sini activations domine ediyordu. Inference sırasında backward pass yoktur ve onun için tutulacak bir şey de yoktur — bu yüzden memory’yi onun yerine domine eden şey cache olur; bu, kaçınılmaz bir maliyet değil bilinçli bir tercihtir.

Hızlı çalışmaya tekrar bak: ilk token, diğer kırk yediden farklı davrandı.

TEXT
prefill, 40 prompt tokens : 1.0224 s   ->  25.6 ms per token
decode,  47 steps         : 0.1665 s mean per step

Prompt token başına 25,6 ms’ye mal oldu; her generated token 166 ms’ye. Aynı model, aynı hardware, aynı weights; token başına altı kat fark — üstelik çoğu insanın beklemediği yönde. Prompt ucuz kısımdır. Generation, gerçekten farklı fiziğe sahip iki phase’e ayrılır:

Tüm prompt üzerinde tek forward pass. Her token paralel işlenir, bu yüzden her weight matrix memory’den bir kez yüklenir ve yüzlerce token vector’ünden oluşan bir matrix’e karşı çarpılır — matrix-matrix product; taşınan byte başına çok aritmetik vardır, GPU da tam bunun için yapılmıştır. Prefill compute-bound’dır ve maliyeti prompt uzunluğuna kabaca lineer gider.

Token başına tek forward pass, batch one ve sequence one. Her weight matrix hâlâ memory’den tam olarak yüklenir ve tek bir vector ile çarpılır — matrix-vector product; taşınan byte başına neredeyse hiç aritmetik yoktur. Decode memory-bandwidth-bound’dır ve token başına maliyeti context uzunluğuna çok az bağlıdır.

İki yarı da ölçülebilir. Prefill, PP token üzerinde tek pass:

prompt tokensaniyetoken başına ms
160,351521,97
320,525416,42
641,049116,39
1281,655212,93
2563,096512,10

Decode, CC cache’e karşı tek token:

cached tokentek token için ms
16110,05
6497,57
256108,53
1024103,86

İkinci tabloyu iki kez oku. 16 token context’ten 1.024’e çıkmak — attention uygulanacak history’nin altmış dört kat artması — bir adımın maliyetini ölçülebilir biçimde değiştirmedi. Cache’e karşı attention gerçek bir iştir, ama tek vector üretmek için yarım milyar weight’i memory bus üzerinden sürüklemenin sabit maliyetinin yanında sönük kalır. Bir sonraki bölümdeki her şeyin nedeni bu sabit maliyettir.

Bu iki phase, her serving system’in raporladığı iki sayının kökenidir. Time to first token esasen prefill’dir ve prompt ile büyür; uzun bir konuşmanın başlamakta yavaş hissettirmesi bu yüzdendir. Tokens per second 1/decode step1/\text{decode step}’tir ve kabaca sabittir; yanıtın sonra düzenli akmasının nedeni budur. Yavaş başlayıp sonra pürüzsüz stream eden bir chat, rendering hilesi değildir. Bu iki tablodur.

Cache aritmetiği memory ile takas eder; istediği memory de küçük değildir. Context’teki her token için, her layer key-value head başına bir key vector ve bir value vector tutar:

bytes per token=2×L×Hkv×dhead×bytes per element\text{bytes per token} = 2 \times L \times H_{kv} \times d_{\text{head}} \times \text{bytes per element}

2, keys ve values içindir; geri kalanı architecture’dır. Bu bölüm boyunca ölçülen model için — 24 layers, 14 query heads, 2 key-value heads, head dimension 64 — fp16’da bu, token başına 2×24×2×64×2=12,2882 \times 24 \times 2 \times 64 \times 2 = 12{,}288 byte eder.

Bu alandaki formüller iki kat şaşırmaya meraklıdır; bu yüzden inanmak yerine allocator’a karşı kontrol et:

TEXT
KV cache tensors per layer: (1, 2, 295, 64) float16
measured: 3,624,960 bytes for 295 tokens = 12,288 bytes/token
formula : 2 * 24 * 2 * 64 * 2                = 12,288 bytes/token

Tam doğru; denenen her shape’te tam doğru kalıyor:

batchcontextmeasured cachepredictedpeak working memory
15126,0 MB6,0 MB15,4 MB
116.384192,0 MB192,0 MB207,3 MB
165.536768,0 MB768,0 MB793,7 MB
84.096384,0 MB384,0 MB401,5 MB
322.048768,0 MB768,0 MB794,2 MB
641.024768,0 MB768,0 MB797,0 MB
128512768,0 MB768,0 MB816,4 MB

Son üç satır ikinci kez bakmayı hak ediyor. Her biri 2.048 token tutan otuz iki user, 1.024 tutan altmış dört user, 512 tutan yüz yirmi sekiz user — cache her durumda 768 MB, çünkü üçü de toplam 65.536 token tutuyor. Cache yalnızca resident toplam token sayısına bağlıdır; bunların user’lar arasında nasıl dağıldığına değil. Bu gerçek batching bölümünün temelidir.

Chapter 9 multi-query ve grouped-query attention’ı tanıtmış, nedenini bu bölüme ertelemişti. Neden o formül ve özellikle içindeki HkvH_{kv}’tir.

Standard multi-head attention, her query head’e kendi key ve value heads’ini verir. Buradaki modelde 14 query heads var; full multi-head attention ile cache’i token başına 2×24×14×64×2=86,0162 \times 24 \times 14 \times 64 \times 2 = 86{,}016 byte olurdu — 12 KB yerine 84 KB, tam olarak yedi kat fazla; query heads’in key-value heads’e oranı.

Multi-query attention1 bunu sınıra götürür: tüm query heads tek bir key-value head’i paylaşır. Grouped-query attention2 kazanan uzlaşmadır — birkaç key-value head, her biri bir query heads grubunca paylaşılır — çünkü MQA’nın kalite kaybı gerçekti, GQA’nınki ise değil. İkisi de aritmetik satın almaz. Var olma sebepleri o formülü bir tam sayıya bölmektir; uzun context’ler cache’i bağlayıcı kısıt yaptığı anda sektöre yayılmaları da bu yüzden oldu.

Ve bunu hızla yapar. 32 layers ve dimension 128 olan 8 key-value heads’e sahip 7B sınıfı bir model için cache fp16’da token başına 128 KB’tır:

context tokenbir user8 user64 user
4.0000,49 GB3,91 GB31,2 GB
32.0003,91 GB31,25 GB250,0 GB
128.00015,62 GB125,00 GB1.000,0 GB
1.000.000122,07 GB976,56 GB7.812,5 GB

Bu modelin kendi weights’i fp16’da 13,0 GB; bu bölümün sonundaki tablodaki rakam. Dolayısıyla 128.000 token context’te tek bir user’ın cache’i modelden büyüktür. Bu, Chapter 16’nın paraya çevirdiği aritmetiktir ve uzun bir konuşmanın yalnızca yavaş olmamasının nedeni budur — request canlı kaldığı sürece makinenin sabit bir dilimini işgal eder.

Batching: yukarı çıkan sayı ve aşağı inen sayı

Bölüme bağlantı: Batching: yukarı çıkan sayı ve aşağı inen sayı

Decode memory-bound’dır: weights bus üzerinden sürüklenerek tek token üretir ve arithmetic units boşa bekler. Öyleyse aynı adıma daha fazla iş koy. Birkaç request’i aynı anda çalıştır; bir kez okunan weights hepsine hizmet etsin. Aynı modelde ölçüldü: her request 64 token cache tutuyor ve bir token decode ediyor:

batchadım başına latencythroughputlatency vs B=1
10,1286 s7,78 tok/s1,00x
20,1839 s10,88 tok/s1,43x
40,1909 s20,95 tok/s1,49x
80,2781 s28,76 tok/s2,16x
160,3430 s46,64 tok/s2,67x
320,6302 s50,78 tok/s4,90x

Sağdaki iki sütunu birbirine karşı oku; bütün mesele onlar. Bir request’ten on altıya çıkmak throughput’u 6,0 ile çarpar ve herhangi bir tekil request’in bekleme süresini 2,67 ile çarpar. Batch server’ı daha iyi, her user’ı daha kötü yaptı.

Bu ayarla yok edilecek bir bug değil; takasın kendisi ve iki tarafında birer adı var. Latency, yanıt bekleyen kişinin yaşadığı şeydir. Throughput, faturanın bölündüğü şeydir. Hiçbir ayar ikisini birden iyileştirmez.

Nerede durduğuna da dikkat et. 16’dan 32’ye throughput %9 kazanırken latency neredeyse ikiye katlanır: adım memory-bound olmaktan çıkıp compute-bound olmuştur; bu dizden sonra batch hiçbir şey satın almaz. Her deployment’ta böyle bir diz vardır; konumunu kendi sisteminde ölçmek gerekir, ama varlığı ölçüme bağlı değildir.

Static batching kazandığının çoğunu boşa harcar

Bölüme bağlantı: Static batching kazandığının çoğunu boşa harcar

Batch yapmanın naif yolu BB request toplamak, birlikte çalıştırmak ve hepsi bittiğinde dönmektir. Ama birlikte bitmezler: bazı yanıtlar yirmi token, bazıları beş yüz token’dır. Fixed batch en uzun üyesi bitene kadar çalışır; bitmiş her request de o zamana kadar slot’unu işgal etmeyi ve padding katkısı yapmayı sürdürür.

Gerçekçi output length çarpıklığına sahip 64 request al — median 18 token, en uzun 231, toplam 1.874 — ve sekiz slot için ölçülen per-step cost ile iki policy’yi simüle et:

policywall clockthroughputrequest başına mean latencywasted slot-steps
8’li static batches176,9 s10,6 tok/s83,2 s3.214
continuous, 8 slot109,0 s17,2 tok/s8,1 s0

Throughput 1,6x iyileşir. Mean latency on kattan fazla iyileşir; çünkü static batching altında dört adımda biten bir request bile, kimse onu duymadan önce 231 token’lık komşusunu bekler.

Continuous batching3 çözümüdür ve kulağa geldiği kadar basittir: batch bir grup değil, bir slot kümesidir; boşalan bir slot bir sonraki adımda kuyruktaki sonraki request’i kabul eder. Scheduler bir request değil, bir token granularity’sinde çalışır. Production’daki her serving stack artık bunu yapıyor.

İkinci yarısı da cache’tir. Gelip giden slot’lar cache memory’yi fragmented bırakır; her slot için olası maksimum context’i reserve etmek ise reservation’ın çoğunu boşa harcar. PagedAttention4 yanıtı operating systems’den ödünç alır: cache’i sequence başına bir block table ile fixed-size blocks içinde sakla; böylece bir sequence’ın cache’i fiziksel olarak dağınık ama mantıksal olarak contiguous kalabilir — bu ayrıca shared prefix’i olan iki sequence’ın onu tutan blocks’u paylaşmasını sağlar. vLLM bunun üzerine kuruludur; bir serving engine’in transformer takılmış bir memory allocator olmasının nedeni de budur.

Faturanın diğer yarısı weights’in kendisidir. Dört byte’lık yarım milyar parameters 1,98 GB eder; iki byte’ta 0,99 GB; bir byte’ta 0,49 GB. Weight başına daha az bit, modeli disk üzerinde küçültür, memory’de küçültür ve — decode bandwidth-bound olduğu için — her adımı hızlandırır, çünkü taşınacak byte daha azdır.

En basit scheme symmetric absolute-maximum quantization’dır ve üç satıra sığar:

quantize.pyPYTHON
qmax  = 2 ** (bits - 1) - 1
scale = W.abs().max() / qmax                        
Wq    = torch.round(W / scale).clamp(-qmax - 1, qmax)
W_hat = Wq * scale                                  # dequantized

En büyük weight en büyük integer’a denk gelecek şekilde bir scale seç, böl, yuvarla, integer’ları ve scale’i sakla. Geri kurmak için tekrar çarp. Bunda zekice bir şey yoktur ve çalışır — çalışmadığı ana kadar.

Modelin gerçek weights’i üzerinde ölçüldü: 168 projection matrix’in tamamı, 357,8 milyon parameters, relative error WW^/W\lVert W - \hat{W}\rVert / \lVert W \rVert:

schememean relative errorworst matrix
INT8, tüm matrix için tek scale0,04000,1487
INT8, output row başına tek scale0,01000,0149
INT4, tüm matrix için tek scale0,60260,9931
INT4, output row başına tek scale0,17900,2589
INT4, 128’lik grup başına tek scale0,13230,1992
NF4, 64’lük block başına tek scale0,09520,1205
INT3, 128’lik grup başına tek scale0,30440,4123
INT2, 128’lik grup başına tek scale0,77900,8076

Çöküş dördüncü satırdır. En kötü matrix’te 0,99 relative error, reconstruction’ın original’dan özünde hiçbir şey tutmadığı anlamına gelir — matrix, yaklaşık doğru magnitude’da noise ile değiştirilmiştir. Nedeni aynı experiment’ın tek matrix üzerindeki halinde görünür:

TEXT
model.layers.12.mlp.down_proj.weight   (896 x 4864)
mean |w| 0.01386   std 0.01822   max |w| 0.43945   max/std 24.1
weights beyond 6 sigma: 692 of 4,358,144   (0.016 %)

Altı bin weight’ten biri altı standard deviation’ın ötesindedir; en büyüğü 24 uzaktadır. Tüm matrix için tek scale ile o tek weight, 4,3 milyon weight’in tamamı için step size’ı belirler. 8 bit’te 256 step vardır ve tipik weight yine anlamlı birine düşer. 4 bit’te 16 vardır; en dıştaki, neredeyse hiçbir weight’in sahip olmadığı bir değer için ayrılmıştır ve sıradan weights — yani hepsi — iki ya da üç distinct level’a yuvarlanır.

O satırdan sonraki her şey aynı onarımın farklı granularity’lerdeki halidir: scale’e daha küçük bir bölge ver. Output row başına yapmak error’ı 3,4’e böler; ardışık 128 weight’lik grup başına yapmak bir kez daha böler. Maliyeti bookkeeping’dir — 128’lik grup başına 16-bit scale, 4 yerine weight başına 4+16/128=4.1254 + 16/128 = 4.125 bit demektir — ve gap’in çoğunu geri satın alır.

NF4 buna diğer taraftan yaklaşır.5 Level’ların eşit aralıklı olması gerekmez. Bir block içindeki weights yaklaşık normal dağılır; o halde on altı level’ı normal distribution quantile’ları olarak seç: weights’in gerçekten bulunduğu zero yakınında yoğun, bulunmadığı tails’te seyrek. Aynı dört bit, aynı block scaling, daha küçük block — group-128’in 4,125’ine karşı weight başına 4,25 bit — ve ölçülen error 0,1323’ten 0,0952’ye düşer, %28 daha düşük. Bunun bir kısmı daha ince block’tan, kalanı level’ları kütlenin olduğu yere koymaktan gelir; ikisini ayırmak için üçüncü bir satır gerekir.

Chapter 2’nin floating-point kutusu bir sözle bitmişti: bu bölüm weights’i 8 ve 4 bit’e quantize edecek ve sıkıştırılmayı reddeden bir avuç outlier features bulacaktı. İşte buradalar; activations üzerinde “sayıları yuvarla geç”in neden asla çalışmayacağını açıklıyorlar.

Yukarıdaki weights kötü huyluydu. Activations bambaşka bir ligde. Sıradan 84-token prompt al, her layer’da residual stream’i yakala ve 896 dimension’ın her birinin ulaştığı en büyük magnitude’u ölç:

layeren büyük |h|median dimension’ın en büyük |h|oranmedian’ın 6x üstündeki dimensions
16,190,33918x2
41543,481,550996x34
81571,631,4981049x36
121575,031,5461019x34
161579,601,617977x32
201577,982,361668x24
24204,4410,76019x12

Dimension 62, median dimension 1,6’yı hiç aşmazken 1.579,6’ya ulaşır. Bu tek token’ın ya da tek layer’ın rastlantısı değildir: aynı dimension layer 4’te oradadır ve layer 20’de neredeyse aynı değerle hâlâ oradadır. Bunlar outlier features’dır,6 ve sistematiktir — input’un değil, trained model’in bir özelliği.

Layer 16’da bu 896 per-dimension maxima’nın histogramı şekli tartışmasız yapar:

TEXT
     0 -      1 | ######################################## 254
     1 -      2 | ######################################## 283
     2 -      4 | ######################################## 226
     4 -      8 | ######################################## 93
     8 -     16 | ##################                       18
    16 -     32 | #########                                9
    32 -     64 | #######                                  7
    64 -    128 | #####                                    5
   128 -    256 |                                          0
   256 -    512 |                                          0
   512 -   1024 |                                          0
  1024 -   4096 | #                                        1

Sekiz’in altında düzenli bir yığın halinde dokuz yüz dimension, üç octave boyunca hiçbir şey yok, sonra en uçta tek başına bir dimension. Şimdi o tensor’ı INT8’e quantize et ve ne olduğunu say:

schemerelative errortüm tensor’da kullanılan distinct integer levels
tüm tensor için tek scale0,1083256’dan 14
token başına tek scale (row başına)0,0433158
tüm tensor, 1 outlier dimension fp32 tutuldu0,044248
tüm tensor, 4 outlier dimensions fp32 tutuldu0,027957
tüm tensor, 16 outlier dimensions fp32 tutuldu0,0085102

256 level’dan on dört. Scale 1.579,6 tarafından belirlendi, bu yüzden her step 12,44 genişliğinde; tipik activation — median magnitude 0,26, doksan dokuzuncu percentile 2,51 — inecek yer bulamıyor. Dimension başına daha da sert:

TEXT
single tensor-wide scale = 12.4378
  dim 826 (max |h| = 4.77):  1 distinct level out of 256
  dim 336 (max |h| = 1.62):  1 distinct level out of 256
  dim  96 (max |h| = 0.69):  1 distinct level out of 256

after excluding the top 4 dimensions, scale = 0.5749  (22x smaller)
  dim 826: 8 levels    dim 336: 4 levels    dim  96: 3 levels

Tek level. Tüm dimension, her token, aynı sayıya quantize edildi. Sekiz bit ayrıldı ve kabaca sıfırı kullanıldı; bu activations’ı okuyan modele bir constant verildi.

Bu ölçüm, insanların gerçekten kullandığı her tekniğin gerekçesidir:

Outlier’ları bunun dışında tut. LLM.int8()6 matrix multiply’ı ayrıştırır: extreme magnitude’a sahip dimensions 16 bit’te hesaplanır, geri kalan her şey INT8’de; sonra yarılar toplanır. Yukarıdaki tablo makbuzdur — dört dimension’ı çıkarmak error’ı neredeyse dört kat azaltır. SmoothQuant7 ise zorluğu taşır: activations’ı per-channel factor ile böl ve eşleşen weight column’u onunla çarp; product değişmeden kalır ve outlier, onu absorbe edemeyen tensor’dan edebilen tensor’a taşınır.

Yuvarlamayı seç, sadece yuvarlama. Yukarıdakilerin hiçbiri matrix’in ne için olduğunu sormaz. GPTQ8 column by column quantize eder ve her column’dan sonra kalan full-precision columns’u, işlenmiş error’ı telafi edecek şekilde ayarlar — weights’in değil, layer’ın gerçek inputs üzerindeki output error’ını minimize eder. AWQ9, weight channels’ın küçük bir bölümünün diğerlerinden çok daha önemli olduğunu not eder, bunları activation statistics’ten bulur ve quantize etmeden önce büyütür; böylece daha ince level’lara düşerler. İkisi de calibration set ister; hiçbiri gradients istemez.

Ayrıntıları göster

GGUF ve file format’ın bununla ne ilgisi var.

GGUF bir quantization yöntemi değildir; llama.cpp’in kullandığı container’dır ve gguf vs gptq karşılaştırmalarındaki kafa karışıklığı ikisine aynı tür şey muamelesi yapılmasından gelir. GGUF tensors, tokenizer, architecture metadata ve chat template’i tek memory-mappable file içinde tutar; içinde de bir family block scheme taşır — Q4_K_M gibi adlar weight başına bit’i, block size’ı ve bazı tensors’ın daha yüksek precision’da tutulup tutulmadığını encode eder.

Önemli engineering farkı: GPTQ ve AWQ bir GPU kernel için optimize edilmiş weights üretirken, GGUF’un scheme’leri file yüklü değil mapped iken CPU üzerinde ucuzca decode edilir. Aynı nominal “4-bit 7B model”in iki dünyada farklı size ve farklı quality ile var olmasının nedeni budur; dürüst karşılaştırma da asla format değildir — aşağıdaki ölçümdür, kendi task’ında çalıştırılır.

Quantization gerçekte neye mal olur, ölçüldü

Bölüme bağlantı: Quantization gerçekte neye mal olur, ölçüldü

Quantization hakkındaki neredeyse her article önceki bölümde durur: yöntemi açıklar, compression ratio alıntılar ve quality’nin “büyük ölçüde korunduğunu” iddia eder. Chapter 4 kendini kandırmamak hakkındaydı; o halde öğrenelim.

Aynı model, weights her scheme ile yerinde quantized; sonra üç ölçüm: held-out English prose’un 2.048 token’ı üzerinde perplexity — burada, bu course’un draft’ı; repository’nin fixed public-domain book koyup aynı şekilli ama farklı sayılı bir tablo basmasının nedeni bu — greedy decoding altında known answer’lı 16 kısa factual questions battery’si ve quantized model’in identical context verildiğinde full-precision model ile aynı token’ı seçtiği oran.

schememean weight errorperplexityquestion batteryfp32 ile agree
fp32 (reference)0,000023,0813/16100,0 %
INT8 per tensor0,040023,5813/16
INT8 per row0,010022,9613/1698,6 %
INT4 per tensor0,6026365.416.0000/16
INT4 per row0,179046,186/1658,3 %
INT4 group 1280,132331,0810/1671,5 %
NF4 block 640,095224,5511/1684,7 %
INT3 group 1280,3044213,090/165,6 %
INT2 group 1280,779026.325.4360/160,0 %

Bu tabloda dört şey açıkça söylenmeye değer.

INT8 düzgün yapıldığında bedavadır. Per-row INT8, reference’ın 23,08’ine karşı 22,96 skor alır — iki yüzde bir parça kadar fark; bu noise’dur ve “identical” okunmalıdır. Noise’un hangi yöne baktığı stable değildir: repository’nin public-domain corpus’unda aynı iki scheme 22,18’e karşı 22,24 çıkar; bu mesafenin yarısı ve ters yöne. 144 generated token’dan 142’sinde full-precision model ile agree eder. fp32 reference’a karşı memory’nin dörtte biri, gerçekten deploy edeceğin fp16’ya karşı yarısı ve detectable cost yok. INT8 dikkatsizce yapıldığında da neredeyse bedavadır: matrix başına tek scale 0,5 perplexity point ve battery’de sıfır answer kaybettirir. Sekiz bit yeterince affedicidir; granularity neredeyse fark etmez. İnsanların INT8’den INT4’e genelleyip zarar görmesinin nedeni tam da budur.

Tensor başına tek scale ile INT4 modeli yok eder. Perplexity 365 milyon: bozulmuş değil, annihilated. Sonra tüm oyun granularity olur — per-tensor 365.416.000, per-row 46,18, per-group-of-128 31,08, NF4 24,55. Weight başına aynı dört bit, en kötü ile en iyi arasında on beş milyon kat fark.

Perplexity kaba bir alettir, battery daha da kabadır. NF4 ile group-128 INT4 arasında perplexity gap 6,5 point ve battery bir question farklı — Chapter 4’ün confidence interval’ı ise on altıda bir question’ın hiçbir şeyi ayırt etmediğini söyler. Interval’dan daha keskin bir demonstration var: aynı battery’yi modelin stock repetition penalty’si kapalıyken çalıştır; greedy decoding’in gerçekten anlamı budur, ve o iki satır yer değiştirir. On altıda bir question küçük etki değil, hiç etki değildir. Chapter 8’in uyarısı da geçerlidir: perplexity yalnızca tokenizer paylaşan modeller arasında karşılaştırılabilir; başka birinin write-up’ındaki sayı seninkiyle karşılaştırılamaz.

Agreement sütunu üçünün en keskinidir ve neredeyse bedava: full-precision model’i greedily çalıştır, sonra quantized model’e her position’da aynı prefix verildiğinde neyi seçeceğini sor. 16 yerine 144 independent observation vardır, ground truth istemez ve battery’nin sıçrayarak bozulduğu yerde pürüzsüz bozulur. Aynı zamanda bir sonraki bölümün ihtiyaç duyduğu miktarın ta kendisidir.

Bu, Chapter 1’in bu bölüm hakkında verdiği sözün vaktinde gelişi: mathematics 4-bit model’in mümkün olduğunu söyler; engineering kullanılabilir olup olmadığına karar verir.

Chapter 12 bunu duyurmuş ve faturayı buraya bırakmıştı.

Fikir doğrudan prefill/decode ayrımından gelir. γ\gamma token’lık önerilmiş bir sequence’ı doğrulamak, γ\gamma position üzerinde tek forward pass’e mal olur — matrix-matrix product; tek position pass’inden ancak biraz daha pahalı. O halde:

Küçük, ucuz bir model autoregressively γ\gamma candidate token üretir.

Büyük model tüm γ\gamma candidate üzerinde bir kerede tek forward pass çalıştırır ve her position’da ne söyleyeceğini üretir.

İkisinin agree ettiği en uzun prefix’i, ayrıca ilk disagreement’ta büyük model’in bedava verdiği token’ı tut. Gerisini at ve yeniden başla.

Output distribution değişmez. Greedy decoding ile bu açıktır — bir token yalnızca target onu üretecekse kabul edilir. Sampling ile modified acceptance rule gerekir; Leviathan et al. resulting distribution’ın target’ınkiyle tam aynı olduğunu kanıtlar.10 Bu bölümdeki ikinci exact optimisation budur.

Bu yüzden her şey acceptance rate α\alpha’e bağlıdır; ölçülebilir — yukarıdaki agreement sütunudur, bu yüzden orada hesaplandı. Her quantized model’i full-precision target için draft olarak kullanıp 144 generated position üzerinde:

draft modelacceptanceen uzun accepted runtarget pass başına expected tokens, γ=4\gamma = 4
fp32 (target’ın kendisi)100,0 %485,00
INT8 per row98,6 %484,86
NF4 block 6484,7 %203,69
INT4 group 12871,5 %132,85
INT4 per row58,3 %72,24
INT3 group 1285,6 %21,06
INT2 group 1280,0 %01,00

Draft length γ\gamma için verification pass başına expected accepted tokens:

E[tokens]=1αγ+11α\mathbb{E}[\text{tokens}] = \frac{1 - \alpha^{\gamma+1}}{1 - \alpha}

ve net speedup, bunu draft’ın kendi maliyetine böler; target’ın token başına maliyetinin cc fraction’ı:

acceptancec=0.05c=0.05, γ=4\gamma=4c=0.1c=0.1, γ=4\gamma=4c=0.2c=0.2, γ=4\gamma=4c=0.1c=0.1, γ=8\gamma=8
30 %1,19x1,02x0,79x0,79x
50 %1,61x1,38x1,08x1,11x
70 %2,31x1,98x1,54x1,78x
90 %3,41x2,93x2,28x3,40x

Hatırlanacak entry bold olan: speculative decoding generation’ı yavaşlatabilir. Target’ın beşte biri maliyetindeki draft ile %30 acceptance’ta, beş forward pass öder ve 1,4 token tutarsın. Son sütun diğer tuzaktır — daha uzun draft yalnızca acceptance yüksekken yardım eder; çünkü γ\gamma-token guess’in tail’ine neredeyse hiç ulaşılmaz. %90 acceptance’ta γ=8\gamma = 8 3,40x eder; %30’da 0,79x eder: aynı configuration, traffic’in üzerinde ölçülen bir sayıya bağlı olarak kazanç ya da kayıp.

Distillation ve soft label’ın taşıdığı şey

Bölüme bağlantı: Distillation ve soft label’ın taşıdığı şey

Quantization aynı function’ı daha az bit’te saklayarak modeli küçültür. Distillation, daha küçük bir modeli daha büyük olanı taklit edecek şekilde train ederek küçültür11 — deep learning’den neredeyse on yıl eski bir fikir.12

İnce kısım student’ın neyden öğrendiğidir. Correct answer’dan değil: doğrudan onun üzerinde train edilebilirdi. Teacher’ın eklediği şey tüm distribution’dır. Model’e bir phrase’den sonra ne geldiğini sor ve argmax’in ötesine bak:

TEXT
"She poured the milk into the"
  ' jug' 0.1355   ' cup' 0.1051   ' bowl' 0.0605   ' large' 0.0380   ' milk' 0.0360

Hard label jug der ve başka hiçbir şey söylemez. Soft label jug der; ayrıca cup’nin neredeyse aynı kadar iyi olduğunu, bowl’nin plausible olduğunu ve large’nin — bir adjective, tamamen farklı grammatical continuation — hâlâ canlı olduğunu söyler. Original argüman budur: bu bir 7, ama 1’e oldukça benziyor; benzerlik, hard label’ın çöpe attığı bilgidir.

Distillation’ın temperature kullanmasının nedeni de budur. softmax’ten önce logits’i TT ile bölmek distribution’ı düzleştirir ve runners-up’ın relative weight’ini yükseltir: bu phrase’de top token ile üçüncü arasındaki oran T=1T = 1’da 2,24’ten T=2T = 2’ta 1,50’ye düşer — ilkinin karekökü; logits’i ikiye bölmenin bir orana yaptığı şey de budur. Aynı ordering, loss’un attention’ının daha büyük kısmı near misses üzerinde. Student’ın gradient’i teacher’ın verdict’ini değil yalnızca, uncertainty’sini de taşır.

Bu bölümdeki her şey artık tek bir toplamdır:

memory=N×bytes per weightfixed+T×2LHkvdhead×bytesgrows with every token+runtime overheadcall it 1.5 GB\text{memory} = \underbrace{N \times \text{bytes per weight}}_{\text{fixed}} + \underbrace{T \times 2 L H_{kv} d_{\text{head}} \times \text{bytes}}_{\text{grows with every token}} + \underbrace{\text{runtime overhead}}_{\text{call it 1.5 GB}}

Burada TT, tüm concurrent requests üzerindeki toplam resident token’dır. Uygulanışı: 7B ve 70B satırları dimension 128 olan 8 key-value heads varsayar; 13B satırı 40 heads ile full multi-head attention varsayar, çünkü o nesil modeller böyle inşa edilmişti — ve bu belli oluyor.

8 GB

modelprecisionweightsoverhead sonrası freesığan context token
7Bfp1613,0 GBsığmaz
7Bint86,5 GBsığmaz
7Bint4 (g128)3,4 GB3,1 GB25.710
13Bint4 (g128)6,2 GB0,3 GB337
70Bint4 (g128)33,6 GBsığmaz

16 GB

modelprecisionweightsoverhead sonrası freesığan context token
7Bfp1613,0 GB1,5 GB11.972
7Bint86,5 GB8,0 GB65.378
7Bint4 (g128)3,4 GB11,1 GB91.246
13Bint812,1 GB2,4 GB3.136
13Bint4 (g128)6,2 GB8,3 GB10.822

24 GB

modelprecisionweightsoverhead sonrası freesığan context token
7Bfp1613,0 GB9,5 GB77.508
7Bint86,5 GB16,0 GB130.914
7Bint4 (g128)3,4 GB19,1 GB156.782
13Bint812,1 GB10,4 GB13.622
13Bint4 (g128)6,2 GB16,3 GB21.308
70Bint4 (g128)33,6 GBsığmaz

8 GB tablosundaki 13B satırına bak. Weights sığıyor — 8’in 6,2 GB’ı — bu yüzden alışıldık konuşma biçimiyle 13B model “8 GB card üzerinde çalışır”. 337 token context’i var; bu bir conversation değil, ancak bir prompt. “Sığıyor mu” yanlış soru. Doğru soru “ne kadar context ile ve aynı anda kaç user için”dir.

İki 16 GB int8 satırına da bak. 7B 65.378 token alırken 13B 3.136 alır — 5,6 GB extra weights’ten doğan yirmi kat fark; çünkü buradaki 13B’de multi-head attention vardır ve cache’i token başına 800 KB’tır, 7B’nin 128 KB’ına karşı. Benzer size’da iki model; biri uzun context için kullanışsız, hem de hiçbir model card headline’ında görünmeyen bir nedenle.

On üç bölüm önce bu, iki weight ve bir bias’a sahip bir perceptron’dı. Şimdi tasarlanmış, trained, aligned, zor sorular için compute harcamayı öğrenmiş ve token başına ölçülü maliyetle served edilen bir transformer — içinde açılmamış kutu kalmadan.

Bu burada biter ve bilerek biter.

Chapter 14 model başka bir yerdeyken başlar. Senin process’inde değil, memory’inde değil, print edebileceğin bir variable’da değil: administer etmediğin bir makinede, bir API key’in, bir port’un ve bir bill’in arkasında. Burada ölçülen her şey hâlâ oluyor — prefill hâlâ ilk token’dan önce çalışıyor, cache hâlâ conversation ile büyüyor, içinde bulunduğun batch hâlâ başka birine ait ve hâlâ latency’ni belirliyor — ama bundan sonra onu Server-Sent Events stream’i, bir finish_reason ve Retry-After header’lı HTTP 429 üzerinden gözlemlersin. Sorular vantage point ile değişir: bu gradient nasıl hesaplanıyor değil, faturam neden üçe katlandı. Dil de değişir; Chapter 14 bu kuralı duyurmak yerine açıklar — buraya kadar code weights, gradients, logits ve tokenizer bytes tutuyordu; oradan sonra connection, retry, cancellation ve accumulated state tutar. Arkandaki on üç bölüm bu geçişle atılmaz. Port’un öte tarafında çalışan şeyin description’ıdır.


İki omission bilinçli. FlashAttention (Dao et al., arXiv:2205.14135) farklı bir attention değildir — aynı function’ı operation’ı tile ederek hesaplar; böylece n×nn \times n score matrix asla memory’ye yazılmaz. Bu yüzden bu bölümün ikinci tablosundaki 67 MB pratikte aritmetiğin düşündürdüğünden küçüktür. Kernels ise devredilmiştir: Stanford CS336’nın lecture 10’u inference systems’i burada amaçlanandan daha derin işler; CPU tarafı için primary sources llama.cpp repository ve GGUF specification’dır.

  1. Shazeer, N. Fast Transformer Decoding: One Write-Head is All You Need. arXiv:1911.02150 (2019). Paper büyük ölçüde bir memory-bandwidth argümanıdır ve öyle okunur.

  2. Ainslie, J. et al. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. arXiv:2305.13245 (2023). Existing multi-head checkpoint’i dönüştüren uptraining recipe’yi içerir; GQA’nın bu kadar hızlı yayılmasının nedeni budur.

  3. Yu, G.-I., Jeong, J. S., Kim, G.-W., Kim, S. and Chun, B.-G. Orca: A Distributed Serving System for Transformer-Based Generative Models. OSDI 2022. Iteration-level scheduling’i — continuous batching — ve selective batching’i tanıtır.

  4. Kwon, W. et al. Efficient Memory Management for Large Language Model Serving with PagedAttention. arXiv:2309.06180 (2023), SOSP 2023. vLLM’in üzerine kurulduğu paper; §3 operating-systems benzetmesini tam olarak verir.

  5. Dettmers, T., Pagnoni, A., Holtzman, A. and Zettlemoyer, L. QLoRA: Efficient Finetuning of Quantized LLMs. arXiv:2305.14314 (2023). NF4 §3’te tanımlanır; yukarıdaki ölçümde kullanılan on altı level value, bu paper’ın türettikleridir.

  6. Dettmers, T., Lewis, M., Belkada, Y. and Zettlemoyer, L. LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale. arXiv:2208.07339 (2022). §4’teki outlier-feature analysis, yukarıda ölçülen phenomenon’ın kaynağıdır; outlier’ların scale ile sistematik olarak ortaya çıktığı bulgusu dahil. 2

  7. Xiao, G., Lin, J., Seznec, M., Wu, H., Demouth, J. and Han, S. SmoothQuant: Accurate and Efficient Post-Training Quantization for Large Language Models. arXiv:2211.10438 (2022).

  8. Frantar, E., Ashkboos, S., Hoefler, T. and Alistarh, D. GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers. arXiv:2210.17323 (2022).

  9. Lin, J. et al. AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration. arXiv:2306.00978 (2023).

  10. Leviathan, Y., Kalman, M. and Matias, Y. Fast Inference from Transformers via Speculative Decoding. arXiv:2211.17192 (2022). Theorem 1, output distribution’ın değişmediğinin kanıtıdır; Chen et al. (arXiv:2302.01318) aynı fikri independent olarak yayımladı.

  11. Hinton, G., Vinyals, O. and Dean, J. Distilling the Knowledge in a Neural Network. arXiv:1503.02531 (2015). Temperature ve “dark knowledge” argümanı.

  12. Buciluă, C., Caruana, R. and Niculescu-Mizil, A. Model Compression. KDD 2006. Distillation, dokuz yıl önce; transformers yerine ensembles için.

Seçimi LIA'ya bırakmaya hazır mısın?

Tüm yapay zeka modelleriyle tek yerde üret — bugün ücretsiz başla.