Olcsóbb inferencia: KV cache, batching és kvantálás
Ugyanaz a modell ugyanarra a kérdésre 8,8 és 78,9 mp alatt, byte-ra azonos kimenettel. Aztán INT4, háromféleképp mérve.
Ezen az oldalon
Ugyanaz a modell, ugyanazon a gépen, ugyanarra a kérdésre válaszolva ugyanazzal a 48 token mennyiséggel. A két kimenet tokenről tokenre azonos — ellenőrizve, nem feltételezve.
with a key-value cache: 8.85 s ( 6.01 tokens/second)
without a key-value cache: 78.95 s ( 0.60 tokens/second)Egyetlen argumentum változott: use_cache=False. Semmi sem más a modellben, a promptban, a samplingban vagy az aritmetikában, és a második futás a fáradságért cserébe nem pontosabb. Kilencszer lassabb a semmiért.
Erről szól ez a fejezet. Minden benne — a cache, a batch, a kvantált súlyok — arra tett kísérlet, hogy ne fizessünk olyan munkáért, amely nem változtatja meg a választ, vagy hogy kiderítsük, mennyibe kerül egy olcsóbb válasz. A 10. fejezet megadta a betanítás árlistáját. Ez annak az oldalnak az árlistája, amelyért örökké fizetsz: egy telepített modell nagyjából FLOP-ot költ minden kibocsátott token esetén, minden kérésnél, élete végéig.
Hová ment a második futás ideje
Link a szakaszhoz: Hová ment a második futás idejeEgy token generálásához egy decoder-only transformer veszi az eddigi teljes szekvenciát, végigfuttatja minden rétegen, majd az utolsó pozícióból kiolvassa a valószínűségi eloszlást. Ezután hozzáfűzi a kiválasztott token elemet, és újra megteszi ugyanezt. Ez a leírás helyes, és pontosan ezt csinálja a lassú futás.
Csakhogy ez óriási pazarlás is, ennek oka pedig a 9. fejezet causal maskja. A 7. pozíció key és value vektorai a 7. pozíció bemenetéből és az előtte lévő pozíciókból számolódnak. Amikor megérkezik a 8. pozíció, a 7. pozíció nem láthatja — ezt jelenti a causal —, ezért a 7. pozíció key és value értékei pontosan ugyanazok a számok, mint korábban. A lassú futás mégis újraszámolja őket, minden lépésben.
Tehát tárold el őket. Ez a tároló a key-value cache, a nyelvi modellek servingjének egyetlen legfontosabb optimalizációja:
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)Nézd meg, mi kerül a modellbe a cikluson belül: nxt, egyetlen token. Nem a szekvencia. Az új token query-je minden cache-elt key ellen végez attentiont, a cache-elt key értékek pedig eleve nem változhattak volna. Ez nem közelítés — a fenti azonos kimenetet ellenőrző próba épp erről szól. A cache nem minőséget cserél sebességre; redundáns aritmetikát töröl.
A skálázódás tiszta megmutatásához vedd ki a transformer részt, és mérj meg egyetlen attention headet mellett, a generálás egy lépését mindkét módon számolva:
| token a contextben | minden újraszámolása | cache használatával | arány | score mátrix |
|---|---|---|---|---|
| 128 | 0,59 ms | 0,062 ms | 10x | 65,536 B vs 512 B |
| 256 | 1,20 ms | 0,163 ms | 7x | 262,144 B vs 1,024 B |
| 512 | 7,03 ms | 0,078 ms | 90x | 1,048,576 B vs 2,048 B |
| 1024 | 17,31 ms | 0,114 ms | 152x | 4,194,304 B vs 4,096 B |
| 2048 | 59,83 ms | 0,214 ms | 279x | 16,777,216 B vs 8,192 B |
| 4096 | 236,18 ms | 0,284 ms | 832x | 67,108,864 B vs 16,384 B |
A jobb oldali oszlop az ok. Az újraszámolás minden lépésben felépíti a teljes attention mátrixot — a 9. fejezet aszimptotikus jelölésről szóló dobozából ismert költséget, minden token után egyszer. Cache használatával ehelyett egy sort építesz: 4 096 token esetén 67 MB score áll szemben 16 KB-tal.
Ha milliszekundumok helyett multiply-accumulate műveleteket számolunk, a gép kikerül az érvelésből. token generálásához cold startból:
| generált token | cache használatával | újraszámolva | arány |
|---|---|---|---|
| 128 | 2,6 M | 192,0 M | 73x |
| 512 | 23,1 M | 7,36 G | 318x |
| 2048 | 293,7 M | 392,6 G | 1,336x |
Lépésenként a cache-elt változat lineáris a context méretével, a cache nélküli pedig négyzetes; egy teljes generáláson összegezve áll szemben értékkel, az arány pedig korlátlanul nő. A nyitó példában mért kilencszeres különbség 48 token alatt jött ki — jóval az első táblasor alatt.
A cache azt is megváltoztatja, minek kell memóriában lennie. Egy 8 GB-os laptop GPU-n 256 token fp16 generálásánál, az allocator csúcsértékéből kivonva a rezidens súlyokat:
| csúcs munkamemória | |
|---|---|
| cache használatával | 21,8 MB |
| újraszámolva | 181,7 MB |
8,3-szor több memória, ugyanazoknak a token elemeknek a lassabb előállítására. Ez az 5. fejezetben tett ígéret, váratlan irányból megérkezve: ott a reverse-mode autodiffnek életben kellett tartania minden köztes értéket a backward passhoz, és az aktivációk uralták a betanítás memóriáját. Inference során nincs backward pass, és nincs mit megőrizni hozzá — így a memóriát ehelyett a cache uralja, ez pedig tudatos választás, nem elkerülhetetlen költség.
A prefill és a decode két különböző gép
Link a szakaszhoz: A prefill és a decode két különböző gépNézd meg újra a gyors futást: az első token másképp viselkedett, mint a másik negyvenhét.
prefill, 40 prompt tokens : 1.0224 s -> 25.6 ms per token
decode, 47 steps : 0.1665 s mean per stepA prompt költsége tokenenként 25,6 ms volt, minden generált token pedig 166 ms-ba került. Ugyanaz a modell, ugyanaz a hardver, ugyanazok a súlyok, hatszoros különbség tokenenként — ráadásul abba az irányba, amit a legtöbben nem várnak. A prompt az olcsó rész. A generálás két olyan fázisra válik szét, amelyek fizikája valóban eltér:
Prefill
Link a szakaszhoz: PrefillEgy forward pass a teljes prompton. Minden token párhuzamosan feldolgozódik, így minden súlymátrix egyszer töltődik be memóriából, és több száz token vektor mátrixával szorzódik — mátrix-mátrix szorzat, sok aritmetikával minden mozgatott byte után, pontosan az, amire egy GPU készült. A prefill compute-bound, költsége pedig nagyjából lineáris a prompt hosszával.
Egy forward pass tokenenként, egyes batch és egyes szekvencia mellett. Minden súlymátrix továbbra is teljes egészében betöltődik memóriából, és egy egyetlen vektorral szorzódik — mátrix-vektor szorzat, szinte semmi aritmetikával minden mozgatott byte után. A decode memory-bandwidth-bound, és tokenenkénti költsége alig függ a context hosszától.
Mindkét fél mérhető. Prefill, egy pass tokenen:
| prompt token | másodperc | ms tokenenként |
|---|---|---|
| 16 | 0,3515 | 21,97 |
| 32 | 0,5254 | 16,42 |
| 64 | 1,0491 | 16,39 |
| 128 | 1,6552 | 12,93 |
| 256 | 3,0965 | 12,10 |
Decode, egy token méretű cache ellen:
| cache-elt token | ms egy tokenre |
|---|---|
| 16 | 110,05 |
| 64 | 97,57 |
| 256 | 108,53 |
| 1024 | 103,86 |
Olvasd el kétszer a második táblát. Ha 16 token contextből 1 024-re lépünk — hatvannégyszer több történet, amin attentiont kell végezni —, egy lépés költsége mérhetően nem változott. A cache elleni attention valódi munka, de eltörpül amellett a fix költség mellett, hogy félmilliárd súlyt át kell húzni a memória buszon egyetlen vektor előállításához. Ez a fix költség az oka mindennek a következő szakaszban.
Ez a két fázis az eredete annak a két számnak, amelyet minden serving rendszer jelent. A time to first token lényegében prefill, és a prompttal együtt nő, ezért érződik lassú indulásúnak egy hosszú beszélgetés. A tokens per second értéke , és nagyjából állandó, ezért folyik utána egyenletesen a válasz. Egy lassan induló, majd simán streamelő chat nem renderelési trükk. Ez a két tábla.
A cache maga is a számla
Link a szakaszhoz: A cache maga is a számlaA cache aritmetikát cserél memóriára, és a kívánt memória nem kicsi. A context minden token eleme után minden réteg egy key vektort és egy value vektort tart minden key-value headhez:
A 2 a key és value miatt van; minden más az architektúra. A fejezetben végig mért modellnél — 24 réteg, 14 query head, 2 key-value head, 64-es head dimenzió — fp16-ban ez byte tokenenként.
Ezen a területen a képletek hajlamosak kettes faktorral tévedni, ezért ellenőrizd az allocatoron, ne csak hidd el:
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/tokenPontos, és pontos marad minden kipróbált shape esetén:
| batch | context | mért cache | előrejelzett | csúcs munkamemória |
|---|---|---|---|---|
| 1 | 512 | 6,0 MB | 6,0 MB | 15,4 MB |
| 1 | 16,384 | 192,0 MB | 192,0 MB | 207,3 MB |
| 1 | 65,536 | 768,0 MB | 768,0 MB | 793,7 MB |
| 8 | 4,096 | 384,0 MB | 384,0 MB | 401,5 MB |
| 32 | 2,048 | 768,0 MB | 768,0 MB | 794,2 MB |
| 64 | 1,024 | 768,0 MB | 768,0 MB | 797,0 MB |
| 128 | 512 | 768,0 MB | 768,0 MB | 816,4 MB |
Az utolsó három sort érdemes újra megnézni. Harminckét felhasználó fejenként 2 048 tokennel, hatvannégy 1 024-gyel, százhuszonnyolc 512-vel — a cache mindhárom esetben 768 MB, mert mindhárom összesen 65 536 token elemet tart. A cache csak a rezidens token elemek teljes számától függ, nem attól, hogyan oszlanak meg a felhasználók között. Ez a tény a batching szakasz alapja.
Honnan jön az MQA és a GQA
Link a szakaszhoz: Honnan jön az MQA és a GQAA 9. fejezet bevezette a multi-query és grouped-query attentiont, az okot pedig erre a fejezetre halasztotta. Az ok ez a képlet, különösen a benne lévő .
A standard multi-head attention minden query headnek saját key és value headeket ad. Az itt szereplő modellnek 14 query headje van; teljes multi-head attention mellett a cache mérete byte lenne tokenenként — 84 KB 12 KB helyett, pontosan hétszer több, a query headek és key-value headek aránya.
A multi-query attention1 ezt a végletekig viszi: minden query head egyetlen key-value headet oszt meg. A grouped-query attention2 a győztes kompromisszum — néhány key-value head, mindegyiket query headek egy csoportja osztja meg —, mert az MQA minőségvesztése valós volt, a GQA-é pedig nem az. Egyik sem vesz aritmetikát. Azért léteznek, hogy ezt a képletet elosszák egy egész számmal, és abban a pillanatban elterjedtek az iparágban, amikor a hosszú contextek miatt a cache lett a kötő korlát.
Ami gyorsan megtörténik. Egy 7B-osztályú, 32 rétegű, 8 darab 128 dimenziós key-value headdel rendelkező modellnél a cache fp16-ban 128 KB tokenenként:
| context token | egy felhasználó | 8 felhasználó | 64 felhasználó |
|---|---|---|---|
| 4,000 | 0,49 GB | 3,91 GB | 31,2 GB |
| 32,000 | 3,91 GB | 31,25 GB | 250,0 GB |
| 128,000 | 15,62 GB | 125,00 GB | 1,000,0 GB |
| 1,000,000 | 122,07 GB | 976,56 GB | 7,812,5 GB |
Ennek a modellnek a saját súlyai fp16-ban 13,0 GB-ot foglalnak, ahogy a fejezet végi táblázatban szerepel. Így 128 000 tokenes contextnél egy felhasználó cache-e nagyobb, mint a modell. Ezt az aritmetikát fordítja pénzre a 16. fejezet, és ezért nem pusztán lassú egy hosszú beszélgetés — a kérés teljes élettartama alatt lefoglal egy fix szeletet egy gépből.
Batching: a szám, amely nő, és a szám, amely csökken
Link a szakaszhoz: Batching: a szám, amely nő, és a szám, amely csökkenA decode memory-bound: a súlyok áthúzódnak a buszon egy token előállításához, az aritmetikai egységek pedig tétlenek. Tegyél tehát több munkát ugyanabba a lépésbe. Futtass több kérést egyszerre, és az egyszer beolvasott súlyok mindegyiket kiszolgálják. Ugyanazon a modellen mérve, minden kérés 64 tokenes cache-t tartva és egy token elemet dekódolva:
| batch | késleltetés lépésenként | throughput | késleltetés B=1-hez képest |
|---|---|---|---|
| 1 | 0,1286 s | 7,78 tok/s | 1,00x |
| 2 | 0,1839 s | 10,88 tok/s | 1,43x |
| 4 | 0,1909 s | 20,95 tok/s | 1,49x |
| 8 | 0,2781 s | 28,76 tok/s | 2,16x |
| 16 | 0,3430 s | 46,64 tok/s | 2,67x |
| 32 | 0,6302 s | 50,78 tok/s | 4,90x |
Olvasd egymással szembe a két jobb oldali oszlopot, mert ez a lényeg. Egy kérésről tizenhatra lépve a throughput 6,0-szorosára nő, miközben bármely egyedi kérés várakozása 2,67-szeresére nő. A batch jobbá tette a szervert, és rosszabbá minden felhasználót.
Ez nem olyan hiba, amit ki lehet hangolni; ez maga a csere, és mindkét oldalnak neve van. A latency az, amit a válaszra váró ember tapasztal. A throughput az, amivel a számlát elosztják. Nincs olyan beállítás, amely mindkettőt javítja.
Figyeld meg azt is, hol áll meg. 16-ról 32-re a throughput 9%-ot nyer, miközben a latency majdnem megduplázódik: a lépés már nem memory-bound, hanem compute-bound lett, és ezen a térden túl a batch semmit sem vesz. Minden deploymentnek van ilyen térde; a helyét a sajátodon kell megmérni, de a létezését nem.
A statikus batching elpazarolja annak nagy részét, amit megnyer
Link a szakaszhoz: A statikus batching elpazarolja annak nagy részét, amit megnyerA naiv batching úgy működik, hogy összegyűjt kérést, együtt futtatja őket, majd akkor tér vissza, amikor mind kész. De nem együtt készülnek el: egyes válaszok húsz token hosszúak, mások ötszáz. Egy fix batch addig fut, amíg a leghosszabb tagja be nem fejeződik, és minden kész kérés addig is elfoglalja a helyét, paddinget termelve.
Vegyünk 64 kérést realisztikusan ferde kimeneti hosszakkal — medián 18 token, leghosszabb 231, összesen 1 874 —, és szimuláljuk mindkét policy-t a nyolc slotra mért lépésenkénti költséggel:
| policy | falióra-idő | throughput | átlagos latency kérésenként | elpazarolt slot-lépések |
|---|---|---|---|---|
| statikus 8-as batchek | 176,9 s | 10,6 tok/s | 83,2 s | 3,214 |
| continuous, 8 slot | 109,0 s | 17,2 tok/s | 8,1 s | 0 |
A throughput 1,6x javul. Az átlagos latency több mint tízszeresen javul, mert statikus batching alatt egy négy lépésben végzett kérés még mindig megvár egy 231 tokenes szomszédot, mielőtt bárki hallana róla.
A continuous batching3 a megoldás, és pontosan olyan egyszerű, amilyennek hangzik: a batch nem csoport, hanem slotok halmaza, és egy felszabaduló slot a legközelebbi lépésben beengedi a következő sorban álló kérést. Az ütemező egy token, nem pedig egy kérés granularitásán dolgozik. Minden production serving stack ma már ezt csinálja.
Van egy második fele is: a cache. A be- és kilépő slotok fragmentálják a cache memóriát, és ha minden slotnak lefoglalod a lehető legnagyobb contextet, a foglalás nagy része kárba vész. A PagedAttention4 az operációs rendszerektől kölcsönzi a választ: a cache fix méretű blokkokban tárolódik, szekvenciánként blokktáblával, így egy szekvencia cache-e fizikailag szétszóródhat, miközben logikailag összefüggő marad — ez pedig azt is lehetővé teszi, hogy két közös prefixű szekvencia megossza az azt tartó blokkokat. Erre épül a vLLM, és ezért egy serving engine valójában egy memóriakezelő, amelyre rácsatoltak egy transformert.
Kvantálás, és az első dolog, ami elromlik
Link a szakaszhoz: Kvantálás, és az első dolog, ami elromlikA számla másik fele maguk a súlyok. Félmilliárd paraméter négy byte-tal számolva 1,98 GB; két byte-tal 0,99 GB; egy byte-tal 0,49 GB. Kevesebb bit súlyonként kisebb modellt jelent lemezen, kisebbet memóriában, és — mivel a decode bandwidth-bound — minden lépést gyorsabbá tesz, mert kevesebb byte-ot kell mozgatni.
A legegyszerűbb séma a szimmetrikus abszolút maximum kvantálás, és három sorban elfér:
qmax = 2 ** (bits - 1) - 1
scale = W.abs().max() / qmax
Wq = torch.round(W / scale).clamp(-qmax - 1, qmax)
W_hat = Wq * scale # dequantizedVálassz egy skálát úgy, hogy a legnagyobb súly a legnagyobb egész számra képeződjön le, ossz, kerekíts, tárold az egész számokat és a skálát. Rekonstruálj visszaszorzással. Nincs benne semmi okos, és működik — egészen addig, amíg nem.
A modell valódi súlyain mérve: mind a 168 projekciós mátrix, 357,8 millió paraméter, relatív hiba :
| séma | átlagos relatív hiba | legrosszabb mátrix |
|---|---|---|
| INT8, egy skála a teljes mátrixra | 0,0400 | 0,1487 |
| INT8, egy skála kimeneti soronként | 0,0100 | 0,0149 |
| INT4, egy skála a teljes mátrixra | 0,6026 | 0,9931 |
| INT4, egy skála kimeneti soronként | 0,1790 | 0,2589 |
| INT4, egy skála 128-as csoportonként | 0,1323 | 0,1992 |
| NF4, egy skála 64-es blokkonként | 0,0952 | 0,1205 |
| INT3, egy skála 128-as csoportonként | 0,3044 | 0,4123 |
| INT2, egy skála 128-as csoportonként | 0,7790 | 0,8076 |
A negyedik sor az összeomlás. A 0,99-es relatív hiba a legrosszabb mátrixon azt jelenti, hogy a rekonstrukció lényegében semmit sem őriz meg az eredetiből — a mátrixot nagyjából megfelelő nagyságrendű zaj váltotta fel. Az ok ugyanebben a kísérletben látható egyetlen mátrixon:
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 %)Hatezerből egy súly hat szórásnál is messzebb ül, a legnagyobb pedig 24-re. Egyetlen skálával a teljes mátrixra ez az egy súly állítja be a lépésközt mind a 4,3 millió súlyhoz. 8 biten 256 lépés van, és a tipikus súly még mindig értelmes lépcsőre esik. 4 biten 16 van, a legkülső pedig egy olyan értéknek van fenntartva, amilyen szinte sehol sincs, így a hétköznapi súlyok — vagyis gyakorlatilag mind — két-három különböző szintre kerekednek.
A sor után minden ugyanennek a javításnak más granularitása: adj kisebb területet a skálának. Kimeneti soronként 3,4-gyel osztja a hibát; 128 egymást követő súlyból álló csoportonként újra osztja. A költség könyvelés — egy 16 bites skála 128-as csoportonként bit súlyonként 4 helyett —, és visszaveszi a rés nagy részét.
Az NF4 a másik oldalról közelít.5 A szinteknek nem kell egyenlő távolságra lenniük. Egy blokkon belül a súlyok közel normális eloszlásúak, ezért válaszd a tizenhat szintet egy normális eloszlás kvantiliseiként: sűrűn nulla közelében, ahol a súlyok tényleg vannak, ritkán a széleken, ahol nincsenek. Ugyanaz a négy bit, ugyanaz a blokkskálázás, kisebb blokkon — 4,25 bit súlyonként a group-128 4,125 értékével szemben —, és a mért hiba 0,1323-ról 0,0952-re esik, 28%-kal alacsonyabbra. Ennek egy része a finomabb blokk, a többi az, hogy a szintek oda kerülnek, ahol a tömeg van; a kettő szétválasztásához kellene egy harmadik sor.
Az outlier feature-ök
Link a szakaszhoz: Az outlier feature-ökA 2. fejezet lebegőpontos doboza ígérettel zárult: ebben a fejezetben 8 és 4 bitre kvantáljuk a súlyokat, és találunk néhány outlier feature-t, amely nem hajlandó összepréselődni. Itt vannak, és megmagyarázzák, miért nem működhetett az aktivációkon a „csak kerekítsük a számokat” megközelítés.
A fenti súlyok rosszul viselkedtek. Az aktivációk egészen más ligában játszanak. Vegyünk egy hétköznapi, 84 token hosszú promptot, rögzítsük a residual streamet minden rétegnél, és mérjük meg, az 896 dimenzió mindegyike mekkora legnagyobb magnitúdót ér el:
| réteg | legnagyobb |h| | medián dimenzió legnagyobb |h| értéke | arány | dimenziók a medián 6x értéke felett |
|---|---|---|---|---|
| 1 | 6,19 | 0,339 | 18x | 2 |
| 4 | 1543,48 | 1,550 | 996x | 34 |
| 8 | 1571,63 | 1,498 | 1049x | 36 |
| 12 | 1575,03 | 1,546 | 1019x | 34 |
| 16 | 1579,60 | 1,617 | 977x | 32 |
| 20 | 1577,98 | 2,361 | 668x | 24 |
| 24 | 204,44 | 10,760 | 19x | 12 |
A 62. dimenzió eléri az 1 579,6-ot, miközben a medián dimenzió soha nem haladja meg az 1,6-ot. Nem egy token vagy egy réteg véletlenje: ugyanaz a dimenzió ott van a 4. rétegnél, és még mindig ott van a 20.-nál is, szinte ugyanazzal az értékkel. Ezek az outlier feature-ök,6 és rendszerszerűek — a betanított modell tulajdonságai, nem a bemeneté.
A 896 dimenziónkénti maximum hisztogramja a 16. rétegnél félreérthetetlenné teszi az alakot:
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 | # 1Kilencszáz dimenzió rendezett kupacban 8 alatt, három oktávon át semmi, majd egyetlen dimenzió magában a távoli végén. Most kvantáld ezt a tenzort INT8-ra, és számold meg, mi történik:
| séma | relatív hiba | használt különböző egész szintek, teljes tenzor |
|---|---|---|
| egy skála a teljes tenzorra | 0,1083 | 14 a 256-ból |
| egy skála tokenenként (soronként) | 0,0433 | 158 |
| teljes tenzor, 1 outlier dimenzió fp32-ben tartva | 0,0442 | 48 |
| teljes tenzor, 4 outlier dimenzió fp32-ben tartva | 0,0279 | 57 |
| teljes tenzor, 16 outlier dimenzió fp32-ben tartva | 0,0085 | 102 |
Tizennégy szint 256-ból. A skálát 1 579,6 állította be, ezért minden lépés 12,44 széles, és a tipikus aktivációnak — medián magnitúdó 0,26, kilencvenkilencedik percentilis 2,51 — nincs hová esnie. Dimenziónként még élesebb:
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 levelsEgy szint. Az egész dimenzió, minden token, ugyanarra a számra kvantálva. Nyolc bit lett kiosztva, és nagyjából nulla lett felhasználva, a modell pedig, amely ezeket az aktivációkat olvassa, egy konstans értéket kap.
Ez a mérés igazolja az összes olyan technikát, amelyet az emberek ténylegesen használnak:
Tartsd távol az outliereket. Az LLM.int8()6 felbontja a mátrixszorzást: a szélsőséges magnitúdójú dimenziók 16 biten számolódnak, minden más INT8-ban, majd a felek összeadódnak. A fenti tábla a nyugta — négy dimenzió eltávolítása majdnem négyszeresére csökkenti a hibát. A SmoothQuant7 ehelyett áthelyezi a nehézséget: az aktivációkat elosztja egy csatornánkénti faktorral, a megfelelő súlyoszlopot pedig megszorozza vele; ez változatlanul hagyja a szorzatot, és az outliert abból a tenzorból, amely nem tudja elnyelni, átteszi abba, amelyik igen.
Válaszd meg a kerekítést, ne csak kerekíts. A fentiek közül semmi sem kérdezi meg, mire való a mátrix. A GPTQ8 oszlopról oszlopra kvantál, és mindegyik után igazítja a megmaradó teljes pontosságú oszlopokat, hogy kompenzálja a már elkövetett hibát — a réteg kimenetének hibáját minimalizálva valós bemeneteken, nem a súlyaiét. Az AWQ9 megfigyeli, hogy a súlycsatornák kis része sokkal többet számít, mint a többi, aktivációs statisztikákból megtalálja őket, és kvantálás előtt felskálázza, hogy finomabb szintekre essenek. Mindkettőhöz kalibrációs készlet kell; egyikhez sem kellenek gradientek.
Részletek megjelenítése
GGUF, és mi köze egy fájlformátumnak mindehhez.
A GGUF nem kvantálási módszer; ez az a konténer, amelyet a llama.cpp használ, és a gguf vs gptq összehasonlítások zavara abból jön, hogy a kettőt ugyanannak a dologtípusnak kezelik. A GGUF tenzorokat, tokenizer elemet, architektúra-metaadatokat és chat template-et tart egyetlen memory-mappable fájlban, és egy családnyi blokksémát hordoz magában — az olyan nevek, mint Q4_K_M, a bit/súly értéket, a blokkméretet és azt kódolják, hogy egyes tenzorok magasabb pontosságon maradnak-e.
A fontos mérnöki különbség: a GPTQ és az AWQ GPU kernelre optimalizált súlyokat állít elő, míg a GGUF sémái olcsón dekódolhatók CPU-n, mapped fájlból, betöltés helyett. Ezért létezik ugyanaz a névleges „4 bites 7B modell” mindkét világban eltérő méretekkel és eltérő minőséggel, és ezért az őszinte összehasonlítás soha nem a formátum — hanem az alábbi mérés, a saját feladatodon futtatva.
Mibe kerül valójában a kvantálás, mérve
Link a szakaszhoz: Mibe kerül valójában a kvantálás, mérveSzinte minden kvantálásról szóló cikk az előző szakasznál megáll: elmagyarázza a módszert, idéz egy tömörítési arányt, és kijelenti, hogy a minőség „nagyrészt megmarad”. A 4. fejezet arról szólt, hogyan ne csapd be magad, ezért derítsük ki.
Ugyanaz a modell, a súlyok helyben kvantálva minden sémával, majd három mérés: perplexity 2 048 tokennyi félretett angol prózán — itt e kurzus vázlatán, ezért a repository egy fix public-domain könyvre cseréli, és ugyanolyan alakú, más számokat tartalmazó táblát nyomtat —, 16 rövid, ismert válaszú tényszerű kérdésből álló battery greedy decoding alatt, valamint annak hányada, hogy a kvantált modell azonos context mellett ugyanazt a token elemet választja-e, mint a teljes pontosságú.
| séma | átlagos súlyhiba | perplexity | kérdés-battery | egyezés fp32-vel |
|---|---|---|---|---|
| fp32 (referencia) | 0,0000 | 23,08 | 13/16 | 100,0 % |
| INT8 tenzoronként | 0,0400 | 23,58 | 13/16 | — |
| INT8 soronként | 0,0100 | 22,96 | 13/16 | 98,6 % |
| INT4 tenzoronként | 0,6026 | 365,416,000 | 0/16 | — |
| INT4 soronként | 0,1790 | 46,18 | 6/16 | 58,3 % |
| INT4 group 128 | 0,1323 | 31,08 | 10/16 | 71,5 % |
| NF4 block 64 | 0,0952 | 24,55 | 11/16 | 84,7 % |
| INT3 group 128 | 0,3044 | 213,09 | 0/16 | 5,6 % |
| INT2 group 128 | 0,7790 | 26,325,436 | 0/16 | 0,0 % |
Négy dolgot érdemes ebben a táblában kimondani egyenesen.
A jól megcsinált INT8 ingyen van. A soronkénti INT8 22,96-ot ér el a referencia 23,08 értékével szemben — kétszázadrésznyi rés, ami zaj, és „azonosként” olvasandó. Hogy a zaj melyik irányba mutat, nem stabil: a repository public-domain korpuszán ugyanez a két séma 22,24 és 22,18 értékre jön ki, fele ekkora távolsággal, a másik irányba mutatva. A 144 generált tokenből 142-nél egyezik a teljes pontosságú modellel. Az fp32 referenciához képest negyed memória, a ténylegesen telepítendő fp16-hoz képest fele, és kimutatható költség nélkül. A gondatlanul megcsinált INT8 is majdnem ingyen van: egy skála mátrixonként 0,5 perplexity pontba kerül, és nem vesz el battery-választ. A nyolc bit elég megbocsátó ahhoz, hogy a granularitás alig számítson, pontosan ezért általánosítanak az emberek INT8-ról INT4-re, és sérülnek meg.
Az egy skálás, tenzoronkénti INT4 elpusztítja a modellt. Perplexity 365 millió: nem romlott, megsemmisült. Ezután a granularitás a teljes játék — tenzoronként 365,416,000, soronként 46,18, 128-as csoportonként 31,08, NF4 24,55. Ugyanaz a négy bit súlyonként, tizenötmilliós faktor a legrosszabb és a legjobb között.
A perplexity durva műszer, a battery még durvább. Az NF4 és a group-128 INT4 között a perplexity rés 6,5 pont, a battery pedig egy kérdésben tér el — a 4. fejezet confidence intervalja szerint pedig tizenhatból egy kérdés semmit sem különböztet meg. Van az intervallumnál élesebb demonstráció: futtasd ugyanazt a battery-t úgy, hogy a modell alapértelmezett repetition penalty-je ki van kapcsolva, ami a greedy decoding tényleges jelentése, és ez a két sor helyet cserél. Tizenhatból egy kérdés nem kis hatás, hanem nincs hatás. A 8. fejezet figyelmeztetése is érvényes: a perplexity csak azonos tokenizert használó modellek között hasonlítható össze, ezért valaki más írásából származó szám nem vethető össze a tiéddel.
Az egyezési oszlop a három közül a legélesebb, és majdnem ingyen van: futtasd a teljes pontosságú modellt greedy módon, majd kérdezd meg a kvantáltat minden pozícióban, mit választott volna ugyanazzal a prefixszel. 16 helyett 144 független megfigyelése van, nem kell hozzá ground truth, és simán romlik ott, ahol a battery ugrásokban romlik. Pontosan ez az a mennyiség is, amelyre a következő szakasznak szüksége van.
Ez az ígéret, amelyet az 1. fejezet tett erről a fejezetről, menetrend szerint megérkezve: a matematika szerint lehetséges a 4 bites modell, a mérnöki munka dönti el, használható-e.
Speculative decoding
Link a szakaszhoz: Speculative decodingA 12. fejezet bejelentette ezt, és a számlát itt hagyta.
Az ötlet közvetlenül a prefill/decode szétválásából jön. Egy javasolt, tokenből álló szekvencia ellenőrzése egy forward pass pozíción — mátrix-mátrix szorzat, alig drágább, mint az egyen végzett pass. Tehát:
Egy kicsi, olcsó modell autoregresszíven generál jelölt token elemet.
A nagy modell egy forward passban fut le az összes jelöltön egyszerre, előállítva, mit mondott volna minden pozícióban.
Tartsd meg a leghosszabb prefixet, amelyben a kettő egyezik, plusz azt a token elemet, amelyet a nagy modell ingyen ad az első eltérésnél. Dobd el a többit, és kezdd újra.
A kimeneti eloszlás változatlan. Greedy decoding mellett ez nyilvánvaló — egy token csak akkor fogadódik el, ha a célmodell is azt állította volna elő. Sampling mellett módosított elfogadási szabály kell hozzá, Leviathan és társai pedig bizonyítják, hogy az eredő eloszlás pontosan a célmodellé.10 Ez a fejezet második exact optimalizációja.
Ezért minden az acceptance rate értékén múlik, amely mérhető — ez a fenti egyezési oszlop, ezért számoltuk ki ott. Minden kvantált modellt draftként használva a teljes pontosságú célmodellhez, 144 generált pozíción:
| draft modell | acceptance | leghosszabb elfogadott futás | várható token cél-passonként, |
|---|---|---|---|
| fp32 (maga a cél) | 100,0 % | 48 | 5,00 |
| INT8 soronként | 98,6 % | 48 | 4,86 |
| NF4 block 64 | 84,7 % | 20 | 3,69 |
| INT4 group 128 | 71,5 % | 13 | 2,85 |
| INT4 soronként | 58,3 % | 7 | 2,24 |
| INT3 group 128 | 5,6 % | 2 | 1,06 |
| INT2 group 128 | 0,0 % | 0 | 1,00 |
A verification passonként várhatóan elfogadott token szám draft hossz mellett:
és a nettó gyorsulás ezt osztja el a draft saját költségével, amely a cél tokenenkénti költségének hányada:
| acceptance | , | , | , | , |
|---|---|---|---|---|
| 30 % | 1,19x | 1,02x | 0,79x | 0,79x |
| 50 % | 1,61x | 1,38x | 1,08x | 1,11x |
| 70 % | 2,31x | 1,98x | 1,54x | 1,78x |
| 90 % | 3,41x | 2,93x | 2,28x | 3,40x |
A félkövér bejegyzést kell megjegyezni: a speculative decoding lassabbá teheti a generálást. 30%-os acceptance mellett, amikor a draft a cél ötödébe kerül, öt forward passért fizetsz, és 1,4 token elemet tartasz meg. Az utolsó oszlop a másik csapda — a hosszabb draft csak magas acceptance mellett segít, mert egy tokenes tipp farka szinte soha nem érhető el. 90%-os acceptance mellett 3,40x-et ér, 30%-nál 0,79x-et: ugyanaz a konfiguráció nyereség vagy veszteség, a saját forgalmadon mért számtól függően.
Distillation, és mit hordoz egy soft label
Link a szakaszhoz: Distillation, és mit hordoz egy soft labelA kvantálás úgy zsugorít egy modellt, hogy ugyanazt a függvényt kevesebb biten tárolja. A distillation úgy zsugorítja, hogy egy kisebb modellt tanít be egy nagyobb utánzására11 — ez az ötlet közel egy évtizeddel megelőzte a deep learninget.12
A finom rész az, hogy miből tanul a student. Nem a helyes válaszból: arra közvetlenül is betanítható lett volna. Amit a teacher hozzáad, az a teljes eloszlás. Kérdezd meg a modellt, mi követ egy kifejezést, és nézz túl az argmaxon:
"She poured the milk into the"
' jug' 0.1355 ' cup' 0.1051 ' bowl' 0.0605 ' large' 0.0380 ' milk' 0.0360A hard label azt mondja: jug, és semmi mást. A soft label azt mondja: jug, és azt is, hogy cup majdnem ugyanolyan jó volt, bowl plauzibilis, és large — egy melléknév, egy teljesen más nyelvtani folytatás — még mindig él. Ez az eredeti érv: ez egy 7-es, de elég sokban hasonlít egy 1-esre, és a hasonlóság olyan információ, amelyet a hard label kidob.
Ezért használ a distillation temperature-t is. Ha a logit értékeket értékkel osztjuk a softmax előtt, az ellapítja az eloszlást, és növeli a második helyezettek relatív súlyát: ennél a kifejezésnél a top token és a harmadik aránya mellett 2,24-ről mellett 1,50-re esik — az első négyzetgyökére, pontosan ezt teszi az aránnyal a logit értékek kettővel osztása. Ugyanaz a sorrend, több loss attention a közeli melléfogásokon. A student gradientje a teacher bizonytalanságát hordozza, nem csak az ítéletét.
Mi fér el 8, 16 és 24 GB-on
Link a szakaszhoz: Mi fér el 8, 16 és 24 GB-onEbben a fejezetben minden most egyetlen összeg:
ahol az összes párhuzamos kérésben rezidens token teljes száma. Alkalmazva: a 7B és 70B sorok 8 darab 128 dimenziós key-value headet feltételeznek, a 13B sor teljes multi-head attentiont 40 headdel, mert az adott modellgenerációk így épültek — és ez látszik is.
8 GB
| model | precision | súlyok | overhead után szabad | elférő context token |
|---|---|---|---|---|
| 7B | fp16 | 13,0 GB | nem fér el | — |
| 7B | int8 | 6,5 GB | nem fér el | — |
| 7B | int4 (g128) | 3,4 GB | 3,1 GB | 25,710 |
| 13B | int4 (g128) | 6,2 GB | 0,3 GB | 337 |
| 70B | int4 (g128) | 33,6 GB | nem fér el | — |
16 GB
| model | precision | súlyok | overhead után szabad | elférő context token |
|---|---|---|---|---|
| 7B | fp16 | 13,0 GB | 1,5 GB | 11,972 |
| 7B | int8 | 6,5 GB | 8,0 GB | 65,378 |
| 7B | int4 (g128) | 3,4 GB | 11,1 GB | 91,246 |
| 13B | int8 | 12,1 GB | 2,4 GB | 3,136 |
| 13B | int4 (g128) | 6,2 GB | 8,3 GB | 10,822 |
24 GB
| model | precision | súlyok | overhead után szabad | elférő context token |
|---|---|---|---|---|
| 7B | fp16 | 13,0 GB | 9,5 GB | 77,508 |
| 7B | int8 | 6,5 GB | 16,0 GB | 130,914 |
| 7B | int4 (g128) | 3,4 GB | 19,1 GB | 156,782 |
| 13B | int8 | 12,1 GB | 10,4 GB | 13,622 |
| 13B | int4 (g128) | 6,2 GB | 16,3 GB | 21,308 |
| 70B | int4 (g128) | 33,6 GB | nem fér el | — |
Nézd meg a 13B sort a 8 GB-os táblában. A súlyok elférnek — 6,2 GB a 8-ból —, tehát a szokásos beszédmód szerint egy 13B modell „fut 8 GB-os kártyán”. 337 tokennyi contextje van, ami nem beszélgetés, hanem alig egy prompt. A „belefér?” rossz kérdés. A helyes: „mekkora contexttel, és egyszerre hány felhasználónak?”
Nézd meg a két 16 GB-os int8 sort is. A 7B 65 378 tokenig jut, a 13B 3 136-ig — hússzoros különbség 5,6 GB extra súly miatt, mert a 13B itt multi-head attentiont használ, és cache-e tokenenként 800 KB-ba kerül a 7B 128 KB-jával szemben. Két hasonló méretű modell, az egyik használhatatlan hosszú contextre, olyan okból, amely egyetlen model card címében sem jelenik meg.
Merre tovább
Link a szakaszhoz: Merre továbbTizenhárom fejezettel ezelőtt ez egy perceptron volt két súllyal és egy bias értékkel. Most már egy transformer, amelyet megterveztek, betanítottak, alignáltak, megtanítottak compute-ot költeni nehéz kérdésekre, és mért tokenenkénti költséggel serving alá tettek — úgy, hogy egyetlen doboz sem maradt benne bontatlanul.
Ez itt véget ér, és szándékosan ér véget.
A 14. fejezet úgy indul, hogy a modell valahol máshol van. Nem a folyamatodban, nem a memóriádban, nem egy változóban, amelyet ki tudsz printelni: egy olyan gépen, amelyet nem te adminisztrálsz, egy API key, egy port és egy számla mögött. Minden itt mért dolog továbbra is történik — a prefill továbbra is lefut az első token előtt, a cache továbbra is nő a beszélgetéssel, a batch, amelyben benne vagy, továbbra is valaki másé, és továbbra is eldönti a latency-det —, de innentől egy Server-Sent Events streamen, egy finish_reason elemen és egy Retry-After headerrel érkező HTTP 429-en keresztül figyeled. A kérdések a nézőponttal együtt változnak: nem az, hogy hogyan számolódik ez a gradient, hanem az, hogy miért triplázódott meg a számlám. A nyelv is változik, és a 14. fejezet ezt a szabályt magyarázza el, nem csak bejelenti — idáig a kód súlyokat, gradienteket, logit értékeket és tokenizer byte-okat tartott; onnantól kapcsolatot, retry-t, megszakítást és felhalmozott állapotot tart. A mögötted lévő tizenhárom fejezetet az átlépés nem dobja ki. Annak leírása, ami a port túloldalán fut.
Források és módszer
Link a szakaszhoz: Források és módszerKét kihagyás szándékos. A FlashAttention (Dao et al., arXiv:2205.14135) nem másfajta attention — ugyanazt a függvényt számolja ki úgy, hogy csempézi a műveletet, ezért a score mátrix soha nem íródik ki memóriába, és ezért kisebb a gyakorlatban e fejezet második táblájának 67 MB-ja, mint amit az aritmetika sugall. Maguk a kernelek pedig delegálva vannak: a Stanford CS336 10. előadása olyan mélységben tárgyalja az inference rendszereket, amire ez nem vállalkozik, a llama.cpp repository és a GGUF specifikáció pedig a CPU-oldal elsődleges forrásai.
Hivatkozások
Link a szakaszhoz: Hivatkozások-
Shazeer, N. Fast Transformer Decoding: One Write-Head is All You Need. arXiv:1911.02150 (2019). A cikk nagyrészt memory-bandwidth érv, és annak is olvasható. ↩
-
Ainslie, J. et al. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints. arXiv:2305.13245 (2023). Tartalmazza az uptraining receptet, amely egy meglévő multi-head checkpointot alakít át, ezért terjedt el ilyen gyorsan a GQA. ↩
-
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. Bevezeti az iteration-level schedulinget — continuous batching — és a selective batchinget. ↩
-
Kwon, W. et al. Efficient Memory Management for Large Language Model Serving with PagedAttention. arXiv:2309.06180 (2023), SOSP 2023. A cikk, amelyre a vLLM épül; a 3. szakasz teljes egészében az operációs rendszerekkel vett analógia. ↩
-
Dettmers, T., Pagnoni, A., Holtzman, A. and Zettlemoyer, L. QLoRA: Efficient Finetuning of Quantized LLMs. arXiv:2305.14314 (2023). Az NF4 a 3. szakaszban van definiálva; a fenti mérésben használt tizenhat szintérték az, amelyet ez a cikk vezet le. ↩
-
Dettmers, T., Lewis, M., Belkada, Y. and Zettlemoyer, L. LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale. arXiv:2208.07339 (2022). A 4. szakasz outlier-feature elemzése a fent mért jelenség forrása, beleértve azt a megállapítást, hogy az outlierek skálán rendszerszerűen jelennek meg. ↩ ↩2
-
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). ↩
-
Frantar, E., Ashkboos, S., Hoefler, T. and Alistarh, D. GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers. arXiv:2210.17323 (2022). ↩
-
Lin, J. et al. AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration. arXiv:2306.00978 (2023). ↩
-
Leviathan, Y., Kalman, M. and Matias, Y. Fast Inference from Transformers via Speculative Decoding. arXiv:2211.17192 (2022). Az 1. tétel bizonyítja, hogy a kimeneti eloszlás változatlan; Chen et al. (arXiv:2302.01318) ugyanezt az ötletet függetlenül publikálta. ↩
-
Hinton, G., Vinyals, O. and Dean, J. Distilling the Knowledge in a Neural Network. arXiv:1503.02531 (2015). A temperature és a „dark knowledge” érv. ↩
-
Buciluă, C., Caruana, R. and Niculescu-Mizil, A. Model Compression. KDD 2006. Distillation, kilenc évvel korábban, ensemble-ökre, nem transformerekre. ↩