Lewati ke konten
13/30Bab 13 dari 30

Membuat Inference Murah: KV cache, Batching, dan Kuantisasi

Model sama menjawab pertanyaan sama dalam 8,8 dan 78,9 detik, output identik byte. Lalu INT4, diukur tiga cara.

Di halaman ini

Model yang sama, di mesin yang sama, menjawab pertanyaan yang sama dengan 48 token yang sama. Kedua output identik token demi token — dicek, bukan diasumsikan.

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)

Satu argumen berubah: use_cache=False. Tidak ada yang berbeda pada model, prompt, sampling, atau aritmetika, dan run kedua tidak menjadi lebih akurat meski sudah membayar mahal. Ia sembilan kali lebih lambat tanpa alasan.

Itulah bentuk bab ini. Semua yang ada di dalamnya — cache, batch, bobot terkuantisasi — adalah upaya untuk berhenti membayar pekerjaan yang tidak mengubah jawaban, atau untuk mencari tahu berapa biaya jawaban yang lebih murah. Bab 10 menetapkan daftar harga untuk training. Ini adalah daftar harga untuk sisi yang kamu bayar selamanya: model yang sudah dideploy menghabiskan kira-kira 2N2N FLOPs untuk setiap token yang ia keluarkan, pada setiap request, sepanjang hidupnya.

Untuk menghasilkan sebuah token, decoder-only transformer mengambil seluruh sequence sejauh ini, menjalankannya melalui setiap layer, lalu membaca distribusi probabilitas dari posisi terakhir. Kemudian ia menambahkan token yang dipilih dan melakukannya lagi. Deskripsi itu benar, dan itulah yang dilakukan run lambat.

Itu juga sangat boros, dan penyebabnya adalah causal mask dari Bab 9. Vektor key dan value posisi 7 dihitung dari input posisi 7 dan posisi-posisi sebelumnya. Saat posisi 8 datang, posisi 7 tidak bisa melihatnya — itulah arti causal — jadi key dan value posisi 7 adalah angka yang persis sama seperti sebelumnya. Run lambat tetap menghitung ulang semuanya, di setiap langkah.

Jadi simpan saja. Penyimpanan itu adalah key-value cache, optimisasi paling konsekuensial dalam serving model bahasa:

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)

Lihat apa yang diberikan ke model di dalam loop: nxt, satu token. Bukan sequence. Query token baru melakukan attention terhadap setiap key yang sudah di-cache, dan key yang di-cache itu memang tidak akan pernah berubah. Ini bukan aproksimasi — pemeriksaan output identik di atas adalah intinya. Cache tidak menukar kualitas dengan kecepatan; ia menghapus aritmetika redundan.

Untuk melihat scaling dengan bersih, singkirkan transformer dan ukur waktu satu attention head dengan d=64d = 64, satu langkah generation dihitung dengan dua cara:

token dalam contexthitung ulang semuanyadengan cacherasiomatriks skor
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

Kolom kanan adalah penyebabnya. Menghitung ulang membangun matriks attention n×nn \times n penuh di setiap langkah — O(n2)O(n^2) dari kotak notasi asimtotik Bab 9, dibayar sekali per token. Dengan cache kamu hanya membangun satu baris 1×n1 \times n: pada 4.096 token, 67 MB skor dibanding 16 KB.

Menghitung multiply-accumulates alih-alih milidetik mengeluarkan mesin dari argumen. Untuk menghasilkan TT token dari cold start:

token yang dihasilkandengan cachemenghitung ulangrasio
1282,6 M192,0 M73x
51223,1 M7,36 G318x
2048293,7 M392,6 G1.336x

Per langkah, versi dengan cache linear terhadap context dan versi tanpa cache kuadratik; dijumlahkan selama sebuah generation, O(T2)O(T^2) melawan O(T3)O(T^3), dengan rasio yang tumbuh tanpa batas. Selisih sembilan kali pada pembuka diukur atas 48 token — lebih pendek dari baris pertama tabel itu.

Cache juga mengubah apa yang harus ada di memori. Pada GPU laptop 8 GB yang menghasilkan 256 token dalam fp16, mengambil puncak allocator lalu mengurangkan bobot resident:

memori kerja puncak
dengan cache21,8 MB
menghitung ulang181,7 MB

Memori 8,3 kali lebih besar, dihabiskan untuk menghasilkan token yang sama dengan lebih lambat. Inilah janji yang dibuat di Bab 5, datang dari arah yang tak terduga: di sana, reverse-mode autodiff harus mempertahankan setiap intermediate untuk backward pass, dan activation mendominasi memori training. Pada inference tidak ada backward pass dan tidak ada yang harus dipertahankan untuk itu — jadi yang mendominasi memori adalah cache, dan itu pilihan sengaja, bukan biaya yang tak terhindarkan.

Prefill dan decode adalah dua mesin berbeda

Tautan ke bagian: Prefill dan decode adalah dua mesin berbeda

Lihat lagi run cepat: token pertamanya berperilaku berbeda dari empat puluh tujuh token lainnya.

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

Prompt memakan biaya 25,6 ms per token dan setiap token yang dihasilkan memakan biaya 166 ms. Model yang sama, hardware yang sama, bobot yang sama, perbedaan enam kali per token — dan arahnya berlawanan dengan dugaan kebanyakan orang. Prompt adalah bagian yang murah. Generation terbagi menjadi dua fase dengan fisika yang benar-benar berbeda:

Satu forward pass atas seluruh prompt. Setiap token diproses paralel, jadi setiap matriks bobot dimuat dari memori sekali dan dikalikan dengan matriks berisi ratusan vektor token — produk matriks-matriks, dengan banyak aritmetika per byte yang dipindahkan, tepat seperti yang dirancang untuk GPU. Prefill bersifat compute-bound, dan biayanya kira-kira linear terhadap panjang prompt.

Satu forward pass per token, batch satu dan sequence satu. Setiap matriks bobot tetap dimuat penuh dari memori, dan dikalikan dengan satu vektor — produk matriks-vektor, dengan hampir tidak ada aritmetika per byte yang dipindahkan. Decode bersifat memory-bandwidth-bound, dan biaya per tokennya nyaris tidak bergantung pada panjang context.

Kedua bagian bisa diukur. Prefill, satu pass atas PP token:

token promptdetikms per token
160,351521,97
320,525416,42
641,049116,39
1281,655212,93
2563,096512,10

Decode, satu token terhadap cache sebesar CC:

token yang di-cachems untuk satu token
16110,05
6497,57
256108,53
1024103,86

Baca tabel kedua dua kali. Berpindah dari context 16 token ke 1.024 — riwayat enam puluh empat kali lebih banyak untuk di-attend — tidak mengubah biaya satu langkah secara terukur. Attention terhadap cache adalah pekerjaan nyata, tetapi ia tertutup oleh biaya tetap menyeret setengah miliar bobot melalui bus memori untuk menghasilkan satu vektor. Biaya tetap itulah alasan semua hal di bagian berikutnya.

Dua fase ini adalah asal dari dua angka yang dilaporkan setiap sistem serving. Time to first token pada dasarnya adalah prefill, dan ia tumbuh bersama prompt, sehingga percakapan panjang terasa lambat untuk mulai. Tokens per second adalah 1/decode step1/\text{decode step}, dan kira-kira konstan, sehingga balasan kemudian mengalir merata. Chat yang mulai lambat lalu streaming mulus bukan trik rendering. Itu adalah dua tabel ini.

Cache menukar aritmetika dengan memori, dan memori yang ia minta tidak kecil. Untuk setiap token dalam context, setiap layer menyimpan satu vektor key dan satu vektor value per key-value head:

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}

Angka 2 adalah untuk key dan value; sisanya adalah arsitektur. Untuk model yang diukur sepanjang bab ini — 24 layer, 14 query head, 2 key-value head, head dimension 64 — dalam fp16 itu adalah 2×24×2×64×2=12,2882 \times 24 \times 2 \times 64 \times 2 = 12{,}288 byte per token.

Formula di bidang ini punya kebiasaan meleset faktor dua, jadi cek terhadap allocator alih-alih mempercayainya:

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

Tepat, dan tetap tepat di setiap bentuk yang dicoba:

batchcontextcache terukurprediksimemori kerja puncak
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

Tiga baris terakhir pantas dilihat lagi. Tiga puluh dua user dengan masing-masing 2.048 token, enam puluh empat dengan 1.024, seratus dua puluh delapan dengan 512 — cache-nya 768 MB di setiap kasus, karena ketiganya menahan 65.536 token. Cache hanya bergantung pada jumlah total token resident, bukan pada bagaimana token itu dibagi di antara user. Fakta itu adalah fondasi bagian batching.

Bab 9 memperkenalkan multi-query dan grouped-query attention lalu menunda alasannya ke bab ini. Alasannya adalah formula itu, khususnya HkvH_{kv} di dalamnya.

Standard multi-head attention memberi setiap query head key dan value head-nya sendiri. Model di sini punya 14 query head; dengan full multi-head attention, cache-nya akan menjadi 2×24×14×64×2=86,0162 \times 24 \times 14 \times 64 \times 2 = 86{,}016 byte per token — 84 KB alih-alih 12 KB, persis tujuh kali lebih besar, rasio query head terhadap key-value head.

Multi-query attention1 membawa ini ke batasnya: semua query head berbagi satu key-value head. Grouped-query attention2 adalah kompromi yang menang — beberapa key-value head, masing-masing dibagi oleh sekelompok query head — karena penurunan kualitas MQA nyata dan GQA tidak. Keduanya tidak membeli aritmetika apa pun. Mereka ada untuk membagi formula itu dengan bilangan bulat, dan menyebar ke seluruh industri begitu long context menjadikan cache sebagai kendala pengikat.

Dan itu terjadi cepat. Untuk model kelas 7B dengan 32 layer dan 8 key-value head berdimensi 128, cache-nya 128 KB per token dalam fp16:

token contextsatu 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

Bobot model itu sendiri 13,0 GB dalam fp16, angka di tabel akhir bab ini. Jadi pada context 128.000 token, cache satu user lebih besar daripada model. Inilah aritmetika yang Bab 16 ubah menjadi uang, dan inilah mengapa percakapan panjang bukan sekadar lambat — ia menempati irisan tetap dari sebuah mesin selama request masih hidup.

Batching: angka yang naik dan angka yang turun

Tautan ke bagian: Batching: angka yang naik dan angka yang turun

Decode bersifat memory-bound: bobot diseret melalui bus untuk menghasilkan satu token, dan unit aritmetika menganggur. Jadi masukkan lebih banyak pekerjaan dalam langkah yang sama. Jalankan beberapa request sekaligus, dan bobot, yang dibaca sekali, melayani semuanya. Diukur pada model yang sama, tiap request menahan cache 64-token dan mendecode satu token:

batchlatensi per langkahthroughputlatensi 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

Baca dua kolom kanan bersama-sama, karena itulah seluruh intinya. Berpindah dari satu request ke enam belas mengalikan throughput dengan 6,0 dan mengalikan waktu tunggu request individual mana pun dengan 2,67. Batch membuat server lebih baik dan setiap user lebih buruk.

Itu bukan bug yang bisa dituning hilang; itulah trade-off-nya sendiri, dan punya nama di masing-masing sisi. Latensi adalah yang dialami seseorang yang menunggu balasan. Throughput adalah pembagi invoice. Tidak ada setting yang memperbaiki keduanya.

Perhatikan juga di mana ia berhenti. Dari 16 ke 32, throughput naik 9 % sementara latensi hampir dua kali lipat: langkahnya berhenti memory-bound dan menjadi compute-bound, dan setelah titik lutut itu batch tidak membeli apa-apa. Setiap deployment punya lutut seperti itu; lokasinya harus diukur pada milikmu, tetapi keberadaannya tidak.

Static batching menyia-nyiakan sebagian besar kemenangannya

Tautan ke bagian: Static batching menyia-nyiakan sebagian besar kemenangannya

Cara naif untuk batching adalah mengumpulkan BB request, menjalankannya bersama, dan mengembalikan saat semuanya selesai. Tetapi mereka tidak selesai bersama: sebagian balasan dua puluh token dan sebagian lima ratus. Batch tetap berjalan sampai anggota terpanjangnya selesai, dan setiap request yang sudah selesai tetap menempati slotnya, menyumbang padding, sampai saat itu.

Ambil 64 request dengan skew panjang output yang realistis — median 18 token, terpanjang 231, total 1.874 — dan simulasikan kedua kebijakan pada biaya per langkah terukur untuk delapan slot:

kebijakanwall clockthroughputlatensi rata-rata per requestslot-step terbuang
batch statis berisi 8176,9 s10,6 tok/s83,2 s3.214
kontinu, 8 slot109,0 s17,2 tok/s8,1 s0

Throughput meningkat 1,6x. Latensi rata-rata meningkat lebih dari sepuluh kali, karena dalam static batching request yang selesai dalam empat langkah tetap menunggu tetangga 231-token sebelum siapa pun mendengarnya.

Continuous batching3 adalah perbaikannya, dan sesederhana kedengarannya: batch bukan sebuah grup, melainkan sekumpulan slot, dan slot yang kosong menerima request antrean berikutnya pada langkah berikutnya juga. Scheduler bekerja pada granularitas satu token, bukan satu request. Setiap stack serving di production sekarang melakukan ini.

Ia punya separuh kedua, yaitu cache. Slot yang datang dan pergi meninggalkan memori cache terfragmentasi, dan mencadangkan setiap slot untuk context maksimum yang mungkin menyia-nyiakan sebagian besar reservasi. PagedAttention4 meminjam jawaban dari operating system: simpan cache dalam blok berukuran tetap dengan tabel blok per sequence, sehingga cache sebuah sequence bisa tersebar secara fisik sambil tetap kontigu secara logis — yang juga memungkinkan dua sequence dengan prefix bersama berbagi blok yang menahannya. Itulah fondasi vLLM, dan mengapa serving engine adalah memory allocator dengan transformer terpasang.

Kuantisasi, dan hal pertama yang rusak

Tautan ke bagian: Kuantisasi, dan hal pertama yang rusak

Separuh lain tagihannya adalah bobot itu sendiri. Setengah miliar parameter pada empat byte masing-masing adalah 1,98 GB; pada dua byte, 0,99 GB; pada satu byte, 0,49 GB. Bit per bobot yang lebih sedikit mengecilkan model di disk, mengecilkannya di memori, dan — karena decode bersifat bandwidth-bound — membuat setiap langkah lebih cepat, karena byte yang dipindahkan lebih sedikit.

Skema paling sederhana adalah kuantisasi symmetric absolute-maximum, dan ia muat dalam tiga baris:

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

Pilih scale agar bobot terbesar memetakan ke integer terbesar, bagi, bulatkan, simpan integer dan scale. Rekonstruksi dengan mengalikan kembali. Tidak ada yang pintar di dalamnya, dan ia bekerja — sampai tiba-tiba tidak.

Diukur pada bobot nyata model: seluruh 168 matriks proyeksi, 357,8 juta parameter, relative error WW^/W\lVert W - \hat{W}\rVert / \lVert W \rVert:

skemamean relative errormatriks terburuk
INT8, satu scale untuk seluruh matriks0,04000,1487
INT8, satu scale per baris output0,01000,0149
INT4, satu scale untuk seluruh matriks0,60260,9931
INT4, satu scale per baris output0,17900,2589
INT4, satu scale per grup 1280,13230,1992
NF4, satu scale per blok 640,09520,1205
INT3, satu scale per grup 1280,30440,4123
INT2, satu scale per grup 1280,77900,8076

Baris keempat adalah runtuhnya. Relative error 0,99 pada matriks terburuk berarti rekonstruksi pada dasarnya tidak mempertahankan apa pun dari yang asli — matriks telah diganti oleh noise dengan besar kira-kira benar. Penyebabnya terlihat dalam eksperimen yang sama pada satu matriks:

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 %)

Satu bobot dari enam ribu berada melewati enam standar deviasi, dan yang terbesar 24 jauhnya. Dengan satu scale untuk seluruh matriks, satu bobot itu menetapkan ukuran langkah untuk semua 4,3 juta bobot. Pada 8 bit ada 256 langkah dan bobot tipikal masih mendarat pada langkah yang bermakna. Pada 4 bit hanya ada 16, yang terluar dicadangkan untuk nilai yang hampir tidak dimiliki siapa pun, dan bobot biasa — yaitu semuanya — dibulatkan ke dua atau tiga level berbeda.

Semua setelah baris itu adalah perbaikan yang sama pada granularitas berbeda: beri scale wilayah yang lebih kecil. Per baris output membagi error dengan 3,4; per grup 128 bobot berurutan membaginya lagi. Biayanya adalah pembukuan — scale 16-bit per grup 128 adalah 4+16/128=4.1254 + 16/128 = 4.125 bit per bobot, bukan 4 — dan itu membeli kembali sebagian besar jarak.

NF4 menyerangnya dari sisi lain.5 Level tidak harus berjarak sama. Bobot dalam satu blok kira-kira terdistribusi normal, jadi pilih enam belas level sebagai kuantil distribusi normal: rapat di dekat nol tempat bobot memang berada, jarang di ekor tempat bobot tidak berada. Empat bit yang sama, block scaling yang sama, pada blok yang lebih kecil — 4,25 bit per bobot melawan 4,125 milik group-128 — dan error terukur turun dari 0,1323 menjadi 0,0952, 28 % lebih rendah. Sebagian karena blok yang lebih halus dan sisanya karena menaruh level di tempat massanya berada, dan memisahkan keduanya akan butuh baris ketiga.

Kotak floating-point Bab 2 berakhir dengan janji: bahwa bab ini akan mengkuantisasi bobot ke 8 dan 4 bit dan menemukan segelintir outlier features yang menolak diperas. Inilah mereka, dan mereka menjelaskan mengapa “cukup bulatkan angkanya” tidak pernah akan berhasil pada activation.

Bobot di atas berperilaku buruk. Activation berada di liga berbeda. Ambil prompt biasa berisi 84 token, tangkap residual stream pada setiap layer, dan ukur magnitudo terbesar yang dicapai masing-masing dari 896 dimensi:

layer|h| terbesar|h| terbesar dimensi medianrasiodimensi di atas 6x median
16,190,33918x2
41543,481,550996x34
81571,631,4981049x36
121575,031,5461019x34
161579,601,617977x32
201577,982,361668x24
24204,4410,76019x12

Dimensi 62 mencapai 1.579,6 sementara dimensi median tidak pernah melebihi 1,6. Ini bukan kebetulan satu token atau satu layer: dimensi yang sama ada di layer 4 dan masih ada di layer 20, dengan nilai hampir sama. Inilah outlier features,6 dan mereka sistematis — properti model yang sudah trained, bukan input.

Histogram dari 896 maksimum per dimensi itu pada layer 16 membuat bentuknya tidak salah lagi:

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

Sembilan ratus dimensi dalam tumpukan rapi di bawah 8, tidak ada sama sekali selama tiga oktaf, lalu satu dimensi sendirian di ujung jauh. Sekarang kuantisasi tensor itu ke INT8 dan hitung apa yang terjadi:

skemarelative errorlevel integer berbeda yang dipakai, seluruh tensor
satu scale untuk seluruh tensor0,108314 dari 256
satu scale per token (per baris)0,0433158
seluruh tensor, 1 dimensi outlier disimpan dalam fp320,044248
seluruh tensor, 4 dimensi outlier disimpan dalam fp320,027957
seluruh tensor, 16 dimensi outlier disimpan dalam fp320,0085102

Empat belas level dari 256. Scale ditetapkan oleh 1.579,6, sehingga setiap langkah lebarnya 12,44, dan activation tipikal — magnitudo median 0,26, persentil kesembilan puluh sembilan 2,51 — tidak punya tempat untuk mendarat. Per dimensi, ini lebih mencolok:

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

Satu level. Seluruh dimensi, setiap token, terkuantisasi ke angka yang sama. Delapan bit dialokasikan dan kira-kira nol digunakan, dan model yang membaca activation itu diberi konstanta.

Pengukuran itu adalah justifikasi untuk setiap teknik yang benar-benar dipakai orang:

Jauhkan outlier dari situ. LLM.int8()6 mendekomposisi perkalian matriks: dimensi dengan magnitudo ekstrem dihitung dalam 16 bit, sisanya dalam INT8, lalu kedua bagian dijumlahkan. Tabel di atas adalah kuitansinya — menghapus empat dimensi memotong error hampir empat kali. SmoothQuant7 sebaliknya memindahkan kesulitan: bagi activation dengan faktor per-channel dan kalikan kolom bobot yang sesuai dengannya, sehingga produk tidak berubah dan outlier berpindah dari tensor yang tidak bisa menyerapnya ke tensor yang bisa.

Pilih pembulatannya, jangan sekadar membulatkan. Tidak ada di atas yang bertanya untuk apa matriks itu. GPTQ8 mengkuantisasi kolom demi kolom dan, setelah tiap kolom, menyesuaikan kolom full-precision yang tersisa untuk mengompensasi error yang sudah dibuat — meminimalkan error output layer pada input nyata, bukan error bobotnya. AWQ9 mencatat bahwa sebagian kecil channel bobot jauh lebih penting daripada sisanya, menemukannya dari statistik activation, lalu menskalakannya ke atas sebelum kuantisasi agar mereka mendarat pada level yang lebih halus. Keduanya membutuhkan calibration set; keduanya tidak membutuhkan gradient.

Tampilkan detail

GGUF, dan apa hubungan format file dengan semua ini.

GGUF bukan metode kuantisasi; ia adalah container yang dipakai llama.cpp, dan kebingungan dalam perbandingan gguf vs gptq muncul karena memperlakukan keduanya sebagai jenis hal yang sama. GGUF menyimpan tensor, tokenizer, metadata arsitektur, dan chat template dalam satu file yang bisa di-memory-map, serta membawa keluarga skema blok di dalamnya — nama seperti Q4_K_M mengodekan bit per bobot, ukuran blok, dan apakah sebagian tensor dipertahankan pada presisi lebih tinggi.

Perbedaan engineering yang penting: GPTQ dan AWQ menghasilkan bobot yang dioptimalkan untuk kernel GPU, sedangkan skema GGUF didecode murah di CPU dengan file yang dipetakan, bukan dimuat. Itulah mengapa “model 7B 4-bit” nominal yang sama ada di kedua dunia dengan ukuran berbeda dan kualitas berbeda, dan mengapa perbandingan yang jujur tidak pernah formatnya — melainkan pengukuran di bawah ini, dijalankan pada task kamu sendiri.

Hampir setiap artikel tentang kuantisasi berhenti di bagian sebelumnya: menjelaskan metode, mengutip rasio kompresi, dan menyatakan bahwa kualitas “sebagian besar terjaga”. Bab 4 membahas cara tidak menipu diri sendiri, jadi mari kita cari tahu.

Model yang sama, bobot dikuantisasi in place dengan tiap skema, lalu tiga pengukuran: perplexity pada 2.048 token prosa Inggris held-out — di sini, draf kursus ini, itulah mengapa repository mengganti dengan buku public-domain tetap dan mencetak tabel dengan bentuk sama tetapi angka berbeda — serangkaian 16 pertanyaan faktual pendek dengan jawaban diketahui di bawah greedy decoding, dan fraksi token ketika model terkuantisasi setuju dengan model full-precision pada context identik.

skemamean weight errorperplexitybaterai pertanyaansetuju dengan fp32
fp32 (referensi)0,000023,0813/16100,0 %
INT8 per tensor0,040023,5813/16
INT8 per baris0,010022,9613/1698,6 %
INT4 per tensor0,6026365.416.0000/16
INT4 per baris0,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 %

Empat hal dalam tabel itu layak dinyatakan terang-terangan.

INT8 yang dilakukan dengan benar itu gratis. INT8 per baris mencetak 22,96 melawan 23,08 referensi — jarak satu bagian dari dua ratus, yaitu noise dan sebaiknya dibaca sebagai “identik”. Arah noise tidak stabil: pada corpus public-domain repository, dua skema yang sama keluar 22,24 melawan 22,18: separuh jarak itu, dan mengarah sebaliknya. Ia setuju dengan model full-precision pada 142 dari 144 token yang dihasilkan. Seperempat memori dibanding referensi fp32, separuh dibanding fp16 yang benar-benar akan kamu deploy, dan tanpa biaya terdeteksi. INT8 yang dilakukan sembarangan juga hampir gratis: satu scale per matriks memakan 0,5 poin perplexity dan tidak kehilangan jawaban baterai. Delapan bit cukup pemaaf sehingga granularitas hampir tidak penting, tepat mengapa orang menggeneralisasi dari INT8 ke INT4 dan terluka.

INT4 dengan satu scale per tensor menghancurkan model. Perplexity 365 juta: bukan menurun, melainkan musnah. Granularitas lalu menjadi seluruh permainan — per-tensor 365.416.000, per-baris 46,18, per-group-of-128 31,08, NF4 24,55. Empat bit yang sama per bobot, selisih faktor lima belas juta antara terburuk dan terbaik.

Perplexity adalah instrumen kasar dan baterai lebih kasar lagi. Antara NF4 dan group-128 INT4, gap perplexity adalah 6,5 poin dan baterai berbeda satu pertanyaan — dan confidence interval Bab 4 mengatakan satu pertanyaan dari enam belas tidak membedakan apa pun. Ada demonstrasi yang lebih tajam daripada interval: jalankan baterai yang sama dengan repetition penalty bawaan model dimatikan, yang memang arti greedy decoding, dan dua baris itu bertukar tempat. Satu pertanyaan dari enam belas bukan efek kecil, melainkan tidak ada efek. Peringatan Bab 8 juga berlaku: perplexity hanya bisa dibandingkan antara model yang berbagi tokenizer, jadi angka dari tulisan orang lain tidak bisa dibandingkan dengan milikmu.

Kolom agreement adalah yang paling tajam dari ketiganya, dan hampir gratis: jalankan model full-precision secara greedy, lalu tanyakan model terkuantisasi, di setiap posisi, apa yang akan ia pilih dengan prefix yang sama. Ia punya 144 observasi independen alih-alih 16, tidak butuh ground truth, dan menurun halus ketika baterai menurun melompat-lompat. Ia juga persis kuantitas yang dibutuhkan bagian berikutnya.

Inilah janji yang dibuat Bab 1 tentang bab ini, tiba tepat waktu: matematika mengatakan model 4-bit mungkin, dan engineering memutuskan apakah ia usable.

Bab 12 mengumumkan ini dan meninggalkan tagihannya di sini.

Idenya langsung datang dari pemisahan prefill/decode. Memverifikasi sequence usulan berisi γ\gamma token memakan biaya satu forward pass atas γ\gamma posisi — produk matriks-matriks, hampir tidak lebih mahal daripada pass atas satu posisi. Jadi:

Model kecil dan murah menghasilkan γ\gamma token kandidat secara autoregresif.

Model besar menjalankan satu forward pass atas semua γ\gamma kandidat sekaligus, menghasilkan apa yang akan ia katakan di setiap posisi.

Pertahankan prefix terpanjang tempat keduanya setuju, plus token yang disediakan model besar secara gratis pada ketidaksepakatan pertama. Buang sisanya dan mulai lagi.

Distribusi output tidak berubah. Dengan greedy decoding itu jelas — token diterima hanya jika target akan menghasilkannya. Dengan sampling, ini membutuhkan aturan acceptance yang dimodifikasi, dan Leviathan et al. membuktikan distribusi hasilnya persis milik target.10 Ini adalah optimisasi eksak kedua dalam bab ini.

Maka semuanya bergantung pada acceptance rate α\alpha, yang bisa diukur — ia adalah kolom agreement di atas, karena itu ia dihitung di sana. Menggunakan tiap model terkuantisasi sebagai draft untuk target full-precision, atas 144 posisi yang dihasilkan:

draft modelacceptancerun diterima terpanjangtoken yang diharapkan per target pass, γ=4\gamma = 4
fp32 (target itu sendiri)100,0 %485,00
INT8 per baris98,6 %484,86
NF4 block 6484,7 %203,69
INT4 group 12871,5 %132,85
INT4 per baris58,3 %72,24
INT3 group 1285,6 %21,06
INT2 group 1280,0 %01,00

Token yang diharapkan diterima per verification pass, pada panjang draft γ\gamma, adalah

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

dan speedup bersih membaginya dengan biaya draft sendiri, fraksi cc dari target per token:

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

Entri tebal adalah yang harus diingat: speculative decoding bisa membuat generation lebih lambat. Pada acceptance 30 % dengan draft yang biayanya seperlima target, kamu membayar lima forward pass dan hanya menyimpan 1,4 token. Kolom terakhir adalah jebakan lain — draft yang lebih panjang hanya membantu saat acceptance tinggi, karena ekor tebakan γ\gamma-token hampir tidak pernah tercapai. Pada acceptance 90 %, γ=8\gamma = 8 bernilai 3,40x dan pada 30 % bernilai 0,79x: konfigurasi yang sama, menang atau kalah tergantung angka yang diukur pada traffic kamu.

Distillation, dan apa yang dibawa soft label

Tautan ke bagian: Distillation, dan apa yang dibawa soft label

Kuantisasi mengecilkan model dengan menyimpan fungsi yang sama dalam bit lebih sedikit. Distillation mengecilkannya dengan melatih model lebih kecil untuk meniru model lebih besar11 — ide yang mendahului deep learning hampir satu dekade.12

Bagian halusnya adalah apa yang dipelajari student. Bukan jawaban benar: ia bisa saja dilatih langsung pada itu. Yang ditambahkan teacher adalah seluruh distribusi. Tanyakan model apa yang mengikuti sebuah frasa dan lihat melewati argmax:

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

Hard label mengatakan jug dan tidak ada lagi. Soft label mengatakan jug, dan juga bahwa cup hampir sama bagusnya, bowl masuk akal, dan large — sebuah adjective, kelanjutan gramatikal yang sepenuhnya berbeda — masih hidup. Itulah argumen aslinya: ini adalah 7, tetapi cukup mirip dengan 1, dan kemiripan itu adalah informasi yang dibuang hard label.

Itulah juga mengapa distillation memakai temperature. Membagi logits dengan TT sebelum softmax meratakan distribusi dan menaikkan bobot relatif runner-up: pada frasa ini, rasio antara token teratas dan yang ketiga turun dari 2,24 pada T=1T = 1 menjadi 1,50 pada T=2T = 2 — akar kuadrat dari yang pertama, yang memang terjadi saat membagi logits dengan dua terhadap sebuah rasio. Urutan sama, lebih banyak attention loss pada near miss. Gradient student membawa ketidakpastian teacher dan bukan hanya vonisnya.

Semua dalam bab ini sekarang menjadi satu penjumlahan:

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}}

di mana TT adalah total token resident lintas semua request concurrent. Menerapkannya: baris 7B dan 70B mengasumsikan 8 key-value head berdimensi 128, baris 13B full multi-head attention dengan 40 head, seperti itulah generasi model itu dibangun — dan itu terlihat.

8 GB

modelpresisibobotsisa setelah overheadtoken context yang muat
7Bfp1613,0 GBtidak muat
7Bint86,5 GBtidak muat
7Bint4 (g128)3,4 GB3,1 GB25.710
13Bint4 (g128)6,2 GB0,3 GB337
70Bint4 (g128)33,6 GBtidak muat

16 GB

modelpresisibobotsisa setelah overheadtoken context yang muat
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

modelpresisibobotsisa setelah overheadtoken context yang muat
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 GBtidak muat

Lihat baris 13B di tabel 8 GB. Bobotnya muat — 6,2 GB dari 8 — jadi dengan cara bicara biasa, model 13B “jalan di kartu 8 GB”. Ia punya 337 token context, yang bukan percakapan melainkan nyaris hanya prompt. “Apakah muat” adalah pertanyaan yang salah. Yang benar adalah “dengan berapa banyak context, dan untuk berapa user sekaligus”.

Lihat juga dua baris int8 16 GB. 7B mendapat 65.378 token dan 13B mendapat 3.136 — selisih dua puluh kali dari bobot ekstra 5,6 GB, karena 13B di sini punya multi-head attention dan cache-nya berbiaya 800 KB per token melawan 128 KB milik 7B. Dua model dengan ukuran mirip, satu tidak usable untuk long context, karena alasan yang tidak muncul di headline model card mana pun.

Tiga belas bab lalu ini adalah perceptron dengan dua bobot dan satu bias. Sekarang ia adalah transformer yang sudah dirancang, dilatih, di-align, diajari menghabiskan compute pada pertanyaan sulit, dan disajikan dengan biaya terukur per token — tanpa kotak yang tersisa belum dibuka.

Itu berakhir di sini, dan berakhir dengan sengaja.

Bab 14 dimulai dengan model berada di tempat lain. Bukan dalam prosesmu, bukan dalam memorimu, bukan dalam variabel yang bisa kamu print: pada mesin yang tidak kamu administrasikan, di balik API key, port, dan tagihan. Semua yang diukur di sini masih terjadi — prefill masih berjalan sebelum token pertama, cache masih tumbuh bersama percakapan, batch tempat kamu berada masih milik orang lain dan masih menentukan latensimu — tetapi mulai sekarang kamu mengamatinya melalui stream Server-Sent Events, sebuah finish_reason, dan HTTP 429 dengan header Retry-After. Pertanyaannya berubah bersama sudut pandang: bukan bagaimana gradient ini dihitung melainkan mengapa invoice-ku tiga kali lipat. Bahasanya juga berubah, dan Bab 14 menjelaskan aturan itu alih-alih sekadar mengumumkannya — sampai sini kode memegang bobot, gradient, logits, dan byte tokenizer; dari sana seterusnya ia memegang koneksi, retry, cancellation, dan state terakumulasi. Tiga belas bab di belakangmu tidak dibuang oleh penyeberangan itu. Mereka adalah deskripsi tentang apa yang berjalan di sisi lain port.


Dua penghilangan disengaja. FlashAttention (Dao et al., arXiv:2205.14135) bukan attention yang berbeda — ia menghitung fungsi yang sama dengan melakukan tiling operasi sehingga matriks skor n×nn \times n tidak pernah ditulis ke memori, itulah mengapa 67 MB pada tabel kedua bab ini lebih kecil dalam praktik daripada yang disiratkan aritmetika. Dan kernel itu sendiri didelegasikan: kuliah 10 Stanford CS336 membahas inference systems sedalam yang tidak dicoba di sini, dan repository llama.cpp serta spesifikasi GGUF adalah sumber utama untuk sisi CPU.

  1. Shazeer, N. Fast Transformer Decoding: One Write-Head is All You Need. arXiv:1911.02150 (2019). Paper ini sebagian besar adalah argumen memory-bandwidth, dan memang terbaca demikian.

  2. Ainslie, J. et al. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. arXiv:2305.13245 (2023). Mencakup resep uptraining yang mengonversi checkpoint multi-head yang sudah ada, itulah mengapa GQA menyebar begitu cepat.

  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. Memperkenalkan scheduling tingkat iterasi — continuous batching — dan selective batching.

  4. Kwon, W. et al. Efficient Memory Management for Large Language Model Serving with PagedAttention. arXiv:2309.06180 (2023), SOSP 2023. Paper yang menjadi fondasi vLLM; §3 adalah analogi operating system secara penuh.

  5. Dettmers, T., Pagnoni, A., Holtzman, A. and Zettlemoyer, L. QLoRA: Efficient Finetuning of Quantized LLMs. arXiv:2305.14314 (2023). NF4 didefinisikan di §3; enam belas nilai level yang dipakai dalam pengukuran di atas adalah yang diturunkan paper ini.

  6. Dettmers, T., Lewis, M., Belkada, Y. and Zettlemoyer, L. LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale. arXiv:2208.07339 (2022). Analisis outlier-feature di §4 adalah sumber fenomena yang diukur di atas, termasuk temuan bahwa outlier muncul sistematis pada skala besar. 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 adalah bukti bahwa distribusi output tidak berubah; Chen et al. (arXiv:2302.01318) menerbitkan ide yang sama secara independen.

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

  12. Buciluă, C., Caruana, R. and Niculescu-Mizil, A. Model Compression. KDD 2006. Distillation, sembilan tahun lebih awal, untuk ensemble alih-alih transformers.


Dibuat oleh

David Vicente Campos

Pendiri NeuraLIA Labs & salah satu pendiri MyRealFood

Saya seorang insinyur komputer lulusan Universitas León. Saya ikut mendirikan MyRealFood, tempat saya sebagai CTO membangun aplikasi yang telah digunakan jutaan orang untuk makan lebih sehat, dan saya mendirikan NeuraLIA Labs, tempat saya membangun produk AI. Di sini saya menulis tentang hal-hal yang harus saya pahami sepanjang perjalanan, sebagaimana dulu saya berharap ada yang menjelaskannya kepada saya.

Selengkapnya tentang penulis

Diterbitkan oleh NeuraLIA Labs.

Dapatkan postingan baru di inbox kamu

Berita AI, panduan, dan update produk — email singkat saat kami menerbitkan sesuatu yang layak kamu baca.

Indeks kursus

Abstract software decision engine with branching paths, probability nodes, and glowing gates.
jev11 menit baca

Model AI Jev dibuat untuk keputusan, bukan prosa

Jev dari TypeSafe AI menarik perhatian karena memperlakukan kecerdasan software sebagai persoalan probabilitas: pilih cabang yang tepat, sertakan keyakinan, dan hindari membayar LLM untuk menulis teks saat kode membutuhkan keputusan.

Abstract agent runtime sorting documents, memory blocks and pointer nodes inside a bounded context frame.
context-engineering11 menit baca

Rekayasa konteks untuk agen AI jangka panjang

Agen yang berjalan lama tidak gagal hanya karena window-nya kecil. Mereka gagal ketika file, output tool, dan riwayat lama menggeser tugas yang seharusnya diselesaikan agen.

Siap membiarkan LIA yang memilih?

Berkarya dengan semua model AI dalam satu tempat — mulai gratis hari ini.