Přeskočit na obsah
13/30Kapitola 13 z 30

Jak zlevnit inference: KV Cache, batching a kvantizace

Stejný model, stejná odpověď za 8,8 s i 78,9 s. Pak INT4 měřené třemi způsoby, ne jen tvrzené.

Na této stránce

Stejný model, na stejném stroji, odpovídá na stejnou otázku stejnými 48 token. Oba výstupy jsou identické token po token — ověřeno, ne předpokládáno.

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)

Změnil se jediný argument: use_cache=False. Nic na modelu, prompt, samplingu ani aritmetice není jiné a druhý běh za svou námahu není přesnější. Je devětkrát pomalejší pro nic.

To je tvar této kapitoly. Všechno v ní — cache, batch, kvantizované váhy — je pokus přestat platit za práci, která nemění odpověď, nebo zjistit, kolik stojí levnější odpověď. Kapitola 10 stanovila ceník trénování. Tohle je ceník strany, za kterou platíte navždy: nasazený model utratí zhruba 2N2N FLOPs za každý token, který vydá, při každém požadavku, po zbytek svého života.

Aby vygeneroval token, decoder-only transformer vezme celou dosavadní sekvenci, prožene ji každou vrstvou a z poslední pozice přečte rozdělení pravděpodobnosti. Pak připojí zvolený token a udělá to znovu. Ten popis je správný a přesně to dělá pomalý běh.

Je to také enormně plýtvavé a důvodem je kauzální maska z kapitoly 9. Vektory key a value pro pozici 7 se počítají ze vstupu pozice 7 a pozic před ní. Když dorazí pozice 8, pozice 7 ji nemůže vidět — to znamená kauzalita — takže key a value pozice 7 jsou přesně stejná čísla jako předtím. Pomalý běh je přesto v každém kroku počítá znovu.

Tak je uložte. Toto úložiště je key-value cache, nejdůležitější optimalizace při servírování jazykových modelů:

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)

Podívejte se, co se uvnitř smyčky předává modelu: nxt, jeden token. Ne sekvence. Query nového token se dívá přes attention na všechny uložené key a uložené key se nikdy změnit neměly. Tohle není aproximace — smyslem je kontrola identického výstupu výše. Cache nevyměňuje kvalitu za rychlost; maže redundantní aritmetiku.

Aby bylo škálování čistě vidět, odstraňte transformer a změřte jednu attention hlavu s d=64d = 64, jeden krok generování spočítaný oběma způsoby:

token v kontextupřepočítat všechnos cachepoměrmatice skóre
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

Pravý sloupec je příčina. Přepočet v každém kroku vytvoří plnou n×nn \times n attention matici — O(n2)O(n^2) z rámečku o asymptotické notaci v kapitole 9, placené jednou za token. S cache místo toho vytvoříte řádek 1×n1 \times n: při 4 096 token jde o 67 MB skóre proti 16 KB.

Počítání multiply-accumulates místo milisekund odstraní ze sporu konkrétní stroj. Pro vygenerování TT token od studeného startu:

vygenerované tokens cachepřepočetpoměr
1282,6 M192,0 M73x
51223,1 M7,36 G318x
2048293,7 M392,6 G1 336x

V jednom kroku je verze s cache lineární v kontextu a verze bez cache kvadratická; sečteno přes celé generování jde o O(T2)O(T^2) proti O(T3)O(T^3), přičemž poměr roste bez omezení. Devítinásobný rozdíl z úvodu byl měřen na 48 token — ještě pod prvním řádkem této tabulky.

Cache také mění to, co musí být v paměti. Na 8GB GPU v notebooku při generování 256 token v fp16, když vezmeme špičku alokátoru a odečteme rezidentní váhy:

špičková pracovní paměť
s cache21,8 MB
přepočet181,7 MB

8,3krát více paměti, utracené za pomalejší produkci stejných token. Tohle je slib z kapitoly 5, přicházející z nečekaného směru: tam musela reverse-mode autodiff držet každý mezivýsledek pro backward pass a aktivace dominovaly paměti při trénování. Při inference žádný backward pass není a není kvůli němu co uchovávat — takže paměti místo toho dominuje cache, a ta je záměrná volba, ne nevyhnutelný náklad.

Prefill a decode jsou dva různé stroje

Odkaz na sekci: Prefill a decode jsou dva různé stroje

Podívejte se znovu na rychlý běh: jeho první token se choval jinak než zbylých čtyřicet sedm.

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

Prompt stál 25,6 ms na token a každý vygenerovaný token stál 166 ms. Stejný model, stejný hardware, stejné váhy, šestinásobný rozdíl na token — a opačným směrem, než většina lidí čeká. Prompt je ta levná část. Generování se dělí do dvou fází se skutečně odlišnou fyzikou:

Jeden forward pass přes celý prompt. Každý token se zpracuje paralelně, takže každá matice vah se z paměti načte jednou a násobí se maticí stovek vektorů token — matrix-matrix product se spoustou aritmetiky na přesunutý byte, přesně to, pro co je GPU stavěné. Prefill je compute-bound a jeho náklad je zhruba lineární v délce prompt.

Jeden forward pass na token, batch jedna a sekvence jedna. Každá matice vah se stále načítá z paměti celá a násobí se jedním vektorem — matrix-vector product s téměř žádnou aritmetikou na přesunutý byte. Decode je memory-bandwidth-bound a jeho náklad na token téměř nezávisí na délce kontextu.

Obě poloviny jsou měřitelné. Prefill, jeden průchod přes PP token:

token v promptsekundyms na token
160,351521,97
320,525416,42
641,049116,39
1281,655212,93
2563,096512,10

Decode, jeden token proti cache o velikosti CC:

uložené token v cachems pro jeden token
16110,05
6497,57
256108,53
1024103,86

Druhou tabulku si přečtěte dvakrát. Přechod od 16 token kontextu k 1 024 — čtyřiašedesátkrát více historie, přes kterou běží attention — nezměnil náklad kroku ničím měřitelným. Attention proti cache je skutečná práce, ale trpasličí vedle fixních nákladů na protažení půl miliardy vah paměťovou sběrnicí, aby vznikl jeden vektor. Tento fixní náklad je důvodem všeho v další části.

Tyto dvě fáze jsou původem dvou čísel, která reportuje každý serving systém. Time to first token je v podstatě prefill a roste s prompt, proto se dlouhá konverzace rozjíždí pomalu. Tokens per second je 1/decode step1/\text{decode step} a je zhruba konstantní, proto pak odpověď plyne rovnoměrně. Chat, který začíná pomalu a potom plynule streamuje, není trik vykreslování. Jsou to tyto dvě tabulky.

Cache vyměňuje aritmetiku za paměť a paměť, kterou chce, není malá. Pro každý token v kontextu drží každá vrstva jeden key vektor a jeden value vektor na každou key-value hlavu:

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}

Dvojka je za key a value; všechno ostatní je architektura. Pro model měřený v celé této kapitole — 24 vrstev, 14 query hlav, 2 key-value hlavy, rozměr hlavy 64 — je to v fp16 2×24×2×64×2=12,2882 \times 24 \times 2 \times 64 \times 2 = 12{,}288 bytů na token.

Vzorce v tomto oboru mají ve zvyku mýlit se o faktor dvě, takže to raději ověřte proti alokátoru, než tomu věřit:

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

Přesné, a zůstává to přesné napříč každým zkoušeným tvarem:

batchkontextzměřená cachepredikcešpičková pracovní paměť
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

Poslední tři řádky si zaslouží druhý pohled. Třicet dva uživatelů s 2 048 token každý, šedesát čtyři s 1 024, sto dvacet osm s 512 — cache má v každém případě 768 MB, protože všechny tři drží 65 536 token. Cache závisí pouze na celkovém počtu rezidentních token, ne na tom, jak jsou rozděleny mezi uživatele. Tato skutečnost je základem části o batching.

Kapitola 9 představila multi-query a grouped-query attention a důvod odložila do této kapitoly. Důvodem je tento vzorec, konkrétně HkvH_{kv} v něm.

Standardní multi-head attention dává každé query hlavě vlastní key a value hlavy. Zdejší model má 14 query hlav; s plnou multi-head attention by jeho cache byla 2×24×14×64×2=86,0162 \times 24 \times 14 \times 64 \times 2 = 86{,}016 bytů na token — 84 KB místo 12 KB, přesně sedmkrát více, poměr query hlav ke key-value hlavám.

Multi-query attention1 to bere až na limit: všechny query hlavy sdílejí jednu key-value hlavu. Grouped-query attention2 je kompromis, který zvítězil — několik key-value hlav, každá sdílená skupinou query hlav — protože ztráta kvality u MQA byla skutečná a u GQA není. Ani jedno nekupuje žádnou aritmetiku. Existují proto, aby tento vzorec vydělily celým číslem, a rozšířily se průmyslem ve chvíli, kdy dlouhé kontexty udělaly z cache závazné omezení.

A to se stane rychle. U modelu třídy 7B s 32 vrstvami a 8 key-value hlavami o rozměru 128 je cache v fp16 128 KB na token:

token kontextujeden uživatel8 uživatelů64 uživatelů
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

Vlastní váhy tohoto modelu mají v fp16 13,0 GB, číslo v tabulce na konci kapitoly. Takže při kontextu 128 000 token je cache jednoho uživatele větší než model. Tohle je aritmetika, kterou kapitola 16 převádí na peníze, a proto dlouhá konverzace není jen pomalá — po celou dobu života požadavku zabírá pevný kus stroje.

Batching: číslo, které roste, a číslo, které klesá

Odkaz na sekci: Batching: číslo, které roste, a číslo, které klesá

Decode je memory-bound: váhy se táhnou přes sběrnici, aby vznikl jeden token, a aritmetické jednotky zahálejí. Tak do stejného kroku vložte více práce. Spusťte několik požadavků najednou a váhy, přečtené jednou, obslouží všechny. Měřeno na stejném modelu, každý požadavek drží 64-token cache a dekóduje jeden token:

batchlatence na krokpropustnostlatence 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

Dva pravé sloupce čtěte proti sobě, protože jsou celou pointou. Přechod z jednoho požadavku na šestnáct vynásobí propustnost 6,0 a čekání libovolného jednotlivého požadavku vynásobí 2,67. Batch zlepšil server a zhoršil každého uživatele.

To není chyba, kterou lze odladit pryč; je to samotný trade-off a na každé straně má jméno. Latence je to, co zažívá člověk čekající na odpověď. Propustnost je to, čím se dělí faktura. Žádné nastavení nezlepší obojí.

Všimněte si také, kde se to zastaví. Od 16 do 32 získá propustnost 9 %, zatímco latence se téměř zdvojnásobí: krok přestal být memory-bound a stal se compute-bound, a za tímto kolenem už batch nekupuje nic. Každé nasazení takové koleno má; jeho polohu musíte změřit na tom svém, ale jeho existenci ne.

Statický batching promarní většinu toho, co vyhraje

Odkaz na sekci: Statický batching promarní většinu toho, co vyhraje

Naivní způsob batching je nasbírat BB požadavků, spustit je společně a vrátit, až jsou všechny hotové. Jenže nekončí společně: některé odpovědi mají dvacet token a jiné pět set. Pevný batch běží, dokud neskončí jeho nejdelší člen, a každý dokončený požadavek do té doby dál zabírá svůj slot a přidává padding.

Vezměte 64 požadavků s realistickým vychýlením délek výstupu — medián 18 token, nejdelší 231, celkem 1 874 — a nasimulujte obě politiky při změřeném nákladu na krok pro osm slotů:

politikawall clockpropustnostprůměrná latence na požadavekpromarněné slot-kroky
statické batche po 8176,9 s10,6 tok/s83,2 s3,214
continuous, 8 slotů109,0 s17,2 tok/s8,1 s0

Propustnost se zlepší 1,6x. Průměrná latence se zlepší více než desetkrát, protože při statickém batching požadavek, který skončil ve čtyřech krocích, stále čeká na 231-token souseda, než o něm kdokoli uslyší.

Continuous batching3 je oprava a je tak jednoduchá, jak zní: batch není skupina, ale sada slotů, a slot, který se uvolní, přijme další požadavek z fronty hned v dalším kroku. Plánovač pracuje v granularitě jednoho token, ne jednoho požadavku. Každý serving stack v produkci to dnes dělá.

Má to druhou polovinu, kterou je cache. Sloty, které přicházejí a odcházejí, nechávají paměť cache fragmentovanou a rezervovat pro každý slot jeho maximální možný kontext plýtvá většinou rezervace. PagedAttention4 si půjčuje odpověď z operačních systémů: uložte cache do bloků pevné velikosti s tabulkou bloků pro každou sekvenci, takže cache sekvence může být fyzicky rozptýlená, ale logicky souvislá — což také umožňuje dvěma sekvencím se sdíleným prefixem sdílet bloky, které ho drží. Na tom stojí vLLM a proto je serving engine alokátor paměti s připojeným transformer.

Kvantizace a první věc, která se pokazí

Odkaz na sekci: Kvantizace a první věc, která se pokazí

Druhou polovinou účtu jsou samotné váhy. Půl miliardy parametrů po čtyřech bytech je 1,98 GB; po dvou bytech 0,99 GB; po jednom bytu 0,49 GB. Méně bitů na váhu zmenší model na disku, zmenší ho v paměti a — protože decode je bandwidth-bound — zrychlí každý krok, protože je potřeba přesunout méně bytů.

Nejjednodušší schéma je symetrická kvantizace podle absolutního maxima a vejde se do tří řádků:

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

Zvolte měřítko tak, aby se největší váha mapovala na největší celé číslo, vydělte, zaokrouhlete, uložte celá čísla a měřítko. Rekonstruujte vynásobením zpět. Není na tom nic chytrého a funguje to — až do chvíle, kdy ne.

Měřeno na skutečných vahách modelu: všech 168 projekčních matic, 357,8 milionu parametrů, relativní chyba WW^/W\lVert W - \hat{W}\rVert / \lVert W \rVert:

schémaprůměrná relativní chybanejhorší matice
INT8, jedno měřítko pro celou matici0,04000,1487
INT8, jedno měřítko na výstupní řádek0,01000,0149
INT4, jedno měřítko pro celou matici0,60260,9931
INT4, jedno měřítko na výstupní řádek0,17900,2589
INT4, jedno měřítko na skupinu 1280,13230,1992
NF4, jedno měřítko na blok 640,09520,1205
INT3, jedno měřítko na skupinu 1280,30440,4123
INT2, jedno měřítko na skupinu 1280,77900,8076

Čtvrtý řádek je kolaps. Relativní chyba 0,99 na nejhorší matici znamená, že rekonstrukce si z originálu v podstatě neponechala nic — matice byla nahrazena šumem přibližně správné velikosti. Příčina je vidět ve stejném experimentu na jedné matici:

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

Jedna váha ze šesti tisíc leží za šesti směrodatnými odchylkami a největší je 24 odchylek daleko. S jedním měřítkem pro celou matici tato jedna váha určuje velikost kroku pro všech 4,3 milionu vah. Při 8 bitech existuje 256 kroků a typická váha stále dopadne na smysluplný. Při 4 bitech jich je 16, krajní je rezervovaný pro hodnotu, kterou nemá skoro nic, a běžné váhy — tedy všechny — se zaokrouhlí na dvě nebo tři různé úrovně.

Všechno po tomto řádku je stejná oprava v různé granularitě: dejte měřítku menší území. Měřítko na výstupní řádek vydělí chybu 3,4; měřítko na skupinu 128 po sobě jdoucích vah ji vydělí znovu. Nákladem je účetnictví — 16bitové měřítko na skupinu 128 je 4+16/128=4.1254 + 16/128 = 4.125 bitů na váhu místo 4 — a získá zpět většinu mezery.

NF4 na to jde z druhé strany.5 Úrovně nemusí být rovnoměrně rozestoupené. Váhy uvnitř bloku jsou přibližně normálně rozdělené, takže zvolte šestnáct úrovní jako kvantily normálního rozdělení: husté u nuly, kde váhy skutečně jsou, řídké v ocasech, kde nejsou. Stejné čtyři bity, stejné blokové škálování, menší blok — 4,25 bitu na váhu proti 4,125 u skupiny 128 — a změřená chyba klesá z 0,1323 na 0,0952, o 28 % níž. Část je jemnější blok a zbytek umístění úrovní tam, kde je hmota; oddělit obojí by vyžadovalo třetí řádek.

Box o floating-point v kapitole 2 skončil slibem: že tato kapitola bude kvantizovat váhy na 8 a 4 bity a najde hrstku outlier features, které se odmítají nechat zmáčknout. Tady jsou a vysvětlují, proč „prostě zaokrouhlit čísla“ u aktivací nikdy nemohlo fungovat.

Váhy výše se chovaly špatně. Aktivace jsou úplně jiná liga. Vezměte obyčejný 84-token prompt, zachyťte residual stream v každé vrstvě a změřte největší velikost, které dosáhne každý z 896 rozměrů:

vrstvanejvětší |h|největší |h| mediánového rozměrupoměrrozměry nad 6x mediánu
16,190,33918x2
41543,481,550996x34
81571,631,4981049x36
121575,031,5461019x34
161579,601,617977x32
201577,982,361668x24
24204,4410,76019x12

Rozměr 62 dosahuje 1 579,6, zatímco mediánový rozměr nikdy nepřekročí 1,6. Není to náhoda jednoho token ani jedné vrstvy: stejný rozměr je tam ve vrstvě 4 a stále tam je ve vrstvě 20, s téměř stejnou hodnotou. To jsou outlier features,6 a jsou systematické — vlastnost natrénovaného modelu, ne vstupu.

Histogram těchto 896 maxim po rozměrech ve vrstvě 16 dělá tvar nezaměnitelným:

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

Devět set rozměrů v úhledné hromádce pod 8, vůbec nic po tři oktávy, a potom jeden jediný rozměr na vzdáleném konci. Teď tento tensor kvantizujte na INT8 a spočítejte, co se stane:

schémarelativní chybapoužité různé celočíselné úrovně, celý tensor
jedno měřítko pro celý tensor0,108314 z 256
jedno měřítko na token (na řádek)0,0433158
celý tensor, 1 outlier rozměr ponechán v fp320,044248
celý tensor, 4 outlier rozměry ponechány v fp320,027957
celý tensor, 16 outlier rozměrů ponecháno v fp320,0085102

Čtrnáct úrovní z 256. Měřítko nastavila hodnota 1 579,6, takže každý krok má šířku 12,44 a typická aktivace — medián velikosti 0,26, devadesátý devátý percentil 2,51 — nemá kam dopadnout. Po rozměrech je to ještě ostřejší:

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

Jedna úroveň. Celý rozměr, každý token, kvantizovaný na stejné číslo. Bylo přiděleno osm bitů a použilo se zhruba nula; model čtoucí tyto aktivace dostane konstantu.

Toto měření je ospravedlněním každé techniky, kterou lidé skutečně používají:

Držte outliers mimo. LLM.int8()6 rozkládá násobení matic: rozměry s extrémními velikostmi se počítají v 16 bitech, všechno ostatní v INT8, a poloviny se sečtou. Tabulka výše je účtenka — odstranění čtyř rozměrů zmenší chybu téměř čtyřikrát. SmoothQuant7 místo toho přesune obtíž: vydělí aktivace faktorem na kanál a vynásobí jím odpovídající sloupec vah, čímž produkt zůstane nezměněn a outlier se přesune z tensor, který ho neunese, do toho, který může.

Vyberte zaokrouhlení, ne jen zaokrouhlujte. Nic výše se neptá, k čemu matice je. GPTQ8 kvantizuje sloupec po sloupci a po každém upraví zbývající sloupce v plné přesnosti, aby kompenzovaly už zavedenou chybu — minimalizuje chybu výstupu vrstvy na skutečných vstupech, ne chybu jejích vah. AWQ9 si všímá, že malý zlomek kanálů vah je mnohem důležitější než zbytek, najde je ze statistik aktivací a před kvantizací je zvětší, aby dopadly na jemnější úrovně. Obě techniky potřebují kalibrační sadu; ani jedna nepotřebuje gradienty.

Zobrazit podrobnosti

GGUF a co s tím má společného souborový formát.

GGUF není metoda kvantizace; je to kontejner, který používá llama.cpp, a zmatek ve srovnáních gguf vs gptq vzniká z toho, že se obě věci berou jako stejný druh věci. GGUF drží tensory, tokenizer, metadata architektury a chat šablonu v jednom memory-mappable souboru a nese v sobě rodinu blokových schémat — názvy jako Q4_K_M kódují bity na váhu, velikost bloku a to, zda jsou některé tensory ponechány ve vyšší přesnosti.

Inženýrský rozdíl, na kterém záleží: GPTQ a AWQ produkují váhy optimalizované pro GPU kernel, zatímco schémata GGUF se levně dekódují na CPU se souborem namapovaným, ne načteným. Proto stejný nominální „4bitový 7B model“ existuje v obou světech s různými velikostmi a různou kvalitou, a proto poctivé srovnání nikdy není formát — je to měření níže, spuštěné na vašem vlastním úkolu.

Co kvantizace skutečně stojí, změřeno

Odkaz na sekci: Co kvantizace skutečně stojí, změřeno

Skoro každý článek o kvantizaci končí v předchozí části: vysvětlí metodu, ocituje kompresní poměr a prohlásí, že kvalita je „do značné míry zachována“. Kapitola 4 byla o tom, jak si nelhat, tak to zjistěme.

Stejný model, váhy kvantizované na místě každým schématem, potom tři měření: perplexita na 2 048 token odložené anglické prózy — zde koncept tohoto kurzu, proto repozitář nahrazuje pevnou public-domain knihou a tiskne tabulku stejného tvaru s jinými čísly — baterie 16 krátkých faktických otázek se známými odpověďmi při greedy decoding a podíl token, na kterých kvantizovaný model souhlasí s modelem v plné přesnosti při identickém kontextu.

schémaprůměrná chyba vahperplexitabaterie otázeksouhlasí s fp32
fp32 (reference)0,000023,0813/16100,0 %
INT8 na tensor0,040023,5813/16
INT8 na řádek0,010022,9613/1698,6 %
INT4 na tensor0,6026365,416,0000/16
INT4 na řádek0,179046,186/1658,3 %
INT4 skupina 1280,132331,0810/1671,5 %
NF4 blok 640,095224,5511/1684,7 %
INT3 skupina 1280,3044213,090/165,6 %
INT2 skupina 1280,779026,325,4360/160,0 %

Čtyři věci v této tabulce stojí za jasné vyslovení.

Správně udělané INT8 je zdarma. INT8 na řádek má skóre 22,96 proti 23,08 reference — mezera jedna část ze dvou set, což je šum a má se číst jako „identické“. Směr šumu není stabilní: na public-domain korpusu v repozitáři vycházejí stejná dvě schémata 22,24 proti 22,18: polovina této vzdálenosti a opačným směrem. S modelem v plné přesnosti souhlasí na 142 ze 144 vygenerovaných token. Čtvrtina paměti proti fp32 referenci, polovina proti fp16, které byste skutečně nasadili, a žádný zjistitelný náklad. INT8 udělané nedbale je také skoro zdarma: jedno měřítko na matici stojí 0,5 bodu perplexity a žádné odpovědi v baterii. Osm bitů odpouští dost na to, aby granularita sotva záležela, což je přesně důvod, proč lidé zobecňují z INT8 na INT4 a narazí.

INT4 s jedním měřítkem na tensor model zničí. Perplexita 365 milionů: ne zhoršené, anihilované. Granularita je pak celá hra — per-tensor 365,416,000, per-row 46,18, per-group-of-128 31,08, NF4 24,55. Stejné čtyři bity na váhu, faktor patnáct milionů mezi nejhorším a nejlepším.

Perplexita je hrubý nástroj a baterie ještě hrubší. Mezi NF4 a group-128 INT4 je mezera perplexity 6,5 bodu a baterie se liší o jednu otázku — a interval spolehlivosti z kapitoly 4 říká, že jedna otázka ze šestnácti nerozlišuje vůbec nic. Existuje ostřejší ukázka než interval: spusťte stejnou baterii s vypnutou standardní penalizací opakování modelu, což je to, co greedy decoding skutečně znamená, a tyto dva řádky si prohodí místa. Jedna otázka ze šestnácti není malý efekt, je to žádný efekt. Platí i varování z kapitoly 8: perplexita je srovnatelná jen mezi modely sdílejícími tokenizer, takže číslo z cizího článku nelze porovnat s vaším.

Sloupec shody je nejostřejší ze tří, a téměř zdarma: spusťte model v plné přesnosti greedy, pak se kvantizovaného zeptejte v každé pozici, co by zvolil při stejném prefixu. Má 144 nezávislých pozorování místo 16, nepotřebuje ground truth a degraduje hladce tam, kde baterie degraduje skokově. Je to také přesně veličina, kterou potřebuje další část.

Toto je slib, který kapitola 1 dala o této kapitole, doručený včas: matematika říká, že 4bitový model je možný, a inženýrství rozhodne, zda je použitelný.

Kapitola 12 to oznámila a účet nechala tady.

Myšlenka vychází přímo z rozdělení prefill/decode. Ověřit navrženou sekvenci γ\gamma token stojí jeden forward pass přes γ\gamma pozic — matrix-matrix product, sotva dražší než průchod přes jednu. Takže:

Malý, levný model vygeneruje γ\gamma kandidátních token autoregresivně.

Velký model spustí jeden forward pass přes všech γ\gamma kandidátů najednou a vytvoří, co by řekl na každé pozici.

Ponechte nejdelší prefix, na kterém se oba shodnou, plus token, který velký model dodá zdarma při první neshodě. Zbytek zahoďte a začněte znovu.

Výstupní rozdělení se nemění. U greedy decoding je to zřejmé — token se přijme jen tehdy, pokud by ho vytvořil target. U samplingu to vyžaduje upravené pravidlo přijetí a Leviathan et al. dokazují, že výsledné rozdělení je přesně rozdělení target.10 Toto je druhá přesná optimalizace v této kapitole.

Všechno tedy závisí na míře přijetí α\alpha, která je měřitelná — je to sloupec shody výše, proto se tam počítal. Použijeme každý kvantizovaný model jako draft pro target v plné přesnosti, přes 144 generovaných pozic:

draft modelpřijetínejdelší přijatý běhočekávané token na target pass, γ=4\gamma = 4
fp32 (samotný target)100,0 %485,00
INT8 na řádek98,6 %484,86
NF4 blok 6484,7 %203,69
INT4 skupina 12871,5 %132,85
INT4 na řádek58,3 %72,24
INT3 skupina 1285,6 %21,06
INT2 skupina 1280,0 %01,00

Očekávaný počet token přijatých na ověřovací průchod při délce draft γ\gamma je

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

a čisté zrychlení to dělí vlastním nákladem draft, zlomkem cc target na token:

přijetíc=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

Tučný údaj je ten, který si zapamatovat: speculative decoding může generování zpomalit. Při 30 % přijetí s draft, který stojí pětinu target, zaplatíte za pět forward pass a ponecháte 1,4 token. Poslední sloupec je druhá past — delší draft pomáhá jen při vysokém přijetí, protože konec γ\gamma-token odhadu se téměř nikdy nedosáhne. Při 90 % přijetí má γ=8\gamma = 8 hodnotu 3,40x a při 30 % hodnotu 0,79x: stejná konfigurace, výhra nebo prohra podle čísla změřeného na vašem provozu.

Kvantizace zmenšuje model uložením stejné funkce do méně bitů. Distilace ho zmenšuje tím, že trénuje menší model, aby napodoboval větší11 — myšlenka, která předchází deep learning téměř o deset let.12

Jemná část je z čeho se student učí. Ne ze správné odpovědi: na té mohl být trénován přímo. Učitel přidává celé rozdělení. Zeptejte se modelu, co následuje po frázi, a podívejte se za argmax:

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

Tvrdý label říká jug a nic dalšího. Měkký label říká jug, a také že cup bylo skoro stejně dobré, bowl věrohodné a large — přídavné jméno, úplně jiné gramatické pokračování — stále živé. To je původní argument: tohle je 7, ale vypadá to dost jako 1, a podobnost je informace, kterou tvrdý label zahodí.

Proto také distilace používá teplotu. Vydělení logits hodnotou TT před softmax zploští rozdělení a zvýší relativní váhu pronásledovatelů: na této frázi spadne poměr mezi top token a třetím z 2,24 při T=1T = 1 na 1,50 při T=2T = 2 — druhou odmocninu prvního, což je to, co vydělení logits dvěma udělá s poměrem. Stejné pořadí, více attention ztráty na těsné omyly. Gradient studenta nese nejistotu učitele, ne jen jeho verdikt.

Všechno v této kapitole je teď jeden součet:

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

kde TT je celkový počet rezidentních token napříč všemi souběžnými požadavky. Aplikace: řádky 7B a 70B předpokládají 8 key-value hlav o rozměru 128, řádek 13B plnou multi-head attention se 40 hlavami, jak byly tyto generace modelů stavěné — a je to vidět.

8 GB

modelpřesnostváhyvolné po režiitoken kontextu, které se vejdou
7Bfp1613,0 GBnevejde se
7Bint86,5 GBnevejde se
7Bint4 (g128)3,4 GB3,1 GB25,710
13Bint4 (g128)6,2 GB0,3 GB337
70Bint4 (g128)33,6 GBnevejde se

16 GB

modelpřesnostváhyvolné po režiitoken kontextu, které se vejdou
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

modelpřesnostváhyvolné po režiitoken kontextu, které se vejdou
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 GBnevejde se

Podívejte se na řádek 13B v 8GB tabulce. Váhy se vejdou — 6,2 GB z 8 — takže běžným způsobem řeči 13B model „běží na 8GB kartě“. Má 337 token kontextu, což není konverzace, ale sotva prompt. „Vejde se to“ je špatná otázka. Správná zní „s kolika kontextem a pro kolik uživatelů najednou“.

Podívejte se také na dva řádky int8 v 16GB tabulce. 7B dostane 65 378 token a 13B dostane 3 136 — dvacetinásobný rozdíl z 5,6 GB vah navíc, protože zdejší 13B má multi-head attention a jeho cache stojí 800 KB na token proti 128 KB u 7B. Dva modely podobné velikosti, jeden nepoužitelný pro dlouhý kontext, z důvodu, který se neobjeví v titulku žádné model card.

Před třinácti kapitolami to byl perceptron se dvěma vahami a bias. Teď je to transformer, který byl navržen, natrénován, zarovnán, naučen utrácet výpočet na těžké otázky a servírován za změřený náklad na token — bez jediné krabice, která by v něm zůstala neotevřená.

Tady to končí, a končí to záměrně.

Kapitola 14 začíná s modelem někde jinde. Ne ve vašem procesu, ne ve vaší paměti, ne v proměnné, kterou můžete vytisknout: na stroji, který nespravujete, za API klíčem, portem a účtem. Všechno měřené zde se stále děje — prefill stále běží před prvním token, cache stále roste s konverzací, batch, ve kterém jste, stále patří někomu jinému a stále rozhoduje o vaší latenci — ale odteď to pozorujete přes stream Server-Sent Events, finish_reason a HTTP 429 s hlavičkou Retry-After. Otázky se mění s pozorovacím bodem: ne jak se počítá tento gradient, ale proč se mi ztrojnásobila faktura. Mění se i jazyk a kapitola 14 toto pravidlo vysvětluje místo toho, aby ho oznámila — až sem kód držel váhy, gradienty, logits a byty tokenizer; odtud dál drží spojení, retry, zrušení a nahromaděný stav. Třináct kapitol za vámi se přechodem nezahazuje. Jsou popisem toho, co běží na druhé straně portu.


Dvě vynechávky jsou záměrné. FlashAttention (Dao et al., arXiv:2205.14135) není jiná attention — počítá stejnou funkci dlaždicováním operace tak, aby se matice skóre n×nn \times n nikdy nezapisovala do paměti, proto je 67 MB ve druhé tabulce této kapitoly v praxi menší, než aritmetika naznačuje. A samotné kernels jsou delegované: přednáška 10 Stanford CS336 pokrývá inference systémy do hloubky, o kterou se toto nepokouší, a repozitář llama.cpp a specifikace GGUF jsou primární zdroje pro CPU stranu.

  1. Shazeer, N. Fast Transformer Decoding: One Write-Head is All You Need. arXiv:1911.02150 (2019). Článek je z velké části argument o šířce pásma paměti a tak se i čte.

  2. Ainslie, J. et al. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. arXiv:2305.13245 (2023). Obsahuje recept na uptraining, který převádí existující multi-head checkpoint, což je důvod, proč se GQA rozšířilo tak rychle.

  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. Zavádí plánování na úrovni iterací — continuous batching — a selektivní batching.

  4. Kwon, W. et al. Efficient Memory Management for Large Language Model Serving with PagedAttention. arXiv:2309.06180 (2023), SOSP 2023. Článek, na kterém stojí vLLM; §3 je úplná analogie s operačními systémy.

  5. Dettmers, T., Pagnoni, A., Holtzman, A. and Zettlemoyer, L. QLoRA: Efficient Finetuning of Quantized LLMs. arXiv:2305.14314 (2023). NF4 je definováno v §3; šestnáct hodnot úrovní použitých ve výše uvedeném měření jsou ty, které tento článek odvozuje.

  6. Dettmers, T., Lewis, M., Belkada, Y. and Zettlemoyer, L. LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale. arXiv:2208.07339 (2022). Analýza outlier-feature v §4 je zdrojem jevu měřeného výše, včetně zjištění, že outliers vznikají systematicky ve velkém měřítku. 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). Věta 1 je důkaz, že výstupní rozdělení se nemění; Chen et al. (arXiv:2302.01318) publikovali stejnou myšlenku nezávisle.

  11. Hinton, G., Vinyals, O. and Dean, J. Distilling the Knowledge in a Neural Network. arXiv:1503.02531 (2015). Teplota a argument „dark knowledge“.

  12. Buciluă, C., Caruana, R. and Niculescu-Mizil, A. Model Compression. KDD 2006. Distilace o devět let dříve, pro ensembles místo transformers.

Necháte výběr modelu na LIA?

Tvořte se všemi modely AI na jednom místě – začněte ještě dnes zdarma.