Salta al contingut
Entendre els LLMs
Bloc 4nucli3-4 h
IntuïtiuMatemàtic

Abans d'això: Anatomia d'un LLM modern

15 · Inferència, decodificació i serving

Per a un enginyer, aquest és probablement el fitxer més útil de tots. El 99% del que faràs amb LLMs és inferència, i la diferència entre una implementació ingènua i una de bona és de 10-50× en cost.


1. Les dues fases (i per què tot depèn d'aquesta distinció)# sense fórmules

FaseQuè faLimitada perParal·lelisme
Prefillprocessa el prompt sencercàlcul (compute-bound)tots els tokens alhora
Decodegenera token a tokenmemòria (memory-bound)1 token per pas

Mètriques que has de mesurar

  • TTFT (time to first token): dominat pel prefill → creix amb la longitud del prompt.
  • TPOT / ITL (time per output token): dominat per l'ample de banda.
  • Throughput (tokens/s agregats): el que determina el teu cost per milió de tokens.
  • Latència p50/p95/p99 sota càrrega real, no en buit.

Prefill contra decode

Les dues fases fan gairebé els mateixos FLOPs per token. Llavors, per què una va limitada per càlcul i l'altra per memòria?

El rellotge: el que es pot cronometrar aquí

prefilldecode
  • prefill sencer: creix amb L
  • prefill, per token
  • un pas de decode

Les dues línies de baix van juntes, i és el fet que importa: el cost per token és el mateix a les dues fases. La de dalt creix amb L perquè processa L tokens; la de punts gairebé no es mou perquè en processa un. I les dues pugen lentament amb el context, que és el cost de l'atenció contra el cache.

El comptador: bytes de pesos llegits per token generat

decodeprefill

La línia del decode és plana: generi el que generi, cada token demana llegir tots els pesos. La del prefill baixa amb pendent −1, perquè la mateixa lectura es reparteix entre més tokens. La distància vertical entre les dues és el factor L, i a 512 tokens ja són 512×. Això és el que una GPU nota i el rellotge de dalt no pot ensenyar.

70,55 G paràmetres actius de 70,55 G totals.

990 TFLOPs en bf16 i 3.350 GB/s d'ample de banda.

8 k
32

Els pesos es llegeixen una vegada per a tot el lot.

bf16 · 2
sostre en decode
23,3 tok/s
TPOT
42,9 ms
TTFT
1,17 s
amb lot de 32
472 tok/s
guany del lot
20,3×
intensitat aritmètica
296 FLOP/byte
Mesurat aquí, el temps per token de les dues fases és el mateix: ràtio 2,41

Això és el que surt de cronometrar-ho de debò amb el mini-GPT, i cal dir-ho abans de res perquè contradiu el que s'esperaria: en aquest entorn, un token de decode no és més lent que un de prefill. A 128 tokens de context, el prefill fa 76 µs per token i un pas de decode en fa 94.

I ha de ser així. Aquest codi és JavaScript escalar: no hi ha unitats vectorials ni milers de fils, de manera que no hi ha res a paral·lelitzar. El prefill de L tokens fa L vegades la feina d'un token i triga L vegades més. Que la ràtio surti ~1 no és un error de la mesura: és la demostració que les dues fases fan la mateixa feina aritmètica per token, que és justament la premissa que fa desconcertant el §1.

La diferència de la GPU, doncs, no pot sortir dels FLOPs. Surt de l'altra columna.

Bytes de pesos llegits per token generat: 28,32 k en decode contra 221,25 en prefill

Aquest és el número que ho explica tot, i no s'estima: es compta. Un forward llegeix els pesos una vegada. En prefill, aquell forward processa L tokens, de manera que a cada token li toquen θ/L\theta/L bytes. En decode, el forward treu un token i li toquen θ\theta sencers. La ràtio és exactament LL:

Lpesos/token prefillpesos/token decoderàtioFLOPs/token prefillFLOPs/token decode
161,8 k28,3 k16×58,82 k60,74 k
32885,028,3 k32×60,86 k64,83 k
64442,528,3 k64×64,96 k73,02 k
128221,328,3 k128×73,15 k89,41 k
256110,628,3 k256×89,54 k122,18 k
51255,328,3 k512×122,30 k187,71 k

Les dues últimes columnes gairebé no es mouen —els FLOPs per token són els mateixos a les dues fases— i les dues del mig es diferencien per un factor L. Aquí hi ha tot el §1: una màquina que pot fer 296 operacions per cada byte que llegeix es queda esperant memòria quan li demanes una operació per byte, i treballa a ple quan li'n demanes mil. El prefill li'n demana mil; el decode, una.

El sostre de 23,7 tokens/s surt de dividir l'ample de banda pels bytes, no d'una constant

El text fa el càlcul a mà amb un 70B en fp16 i una GPU de 3 TB/s i en treu ~21 tokens/s. Aquí surt de decodeTokensPerSecond, amb els paràmetres comptats a partir de les formes del model card:

Paràmetres actius70,552 G
× 2 bytes141,1 GB per token
Ample de banda de la H100 80 GB3.350 GB/s
Sostre, només pesos23,7 tokens/s
…amb el KV-cache de 8 k tokens inclòs23,3 tokens/s

L'última fila és el que el §2 anticipa: el cache també s'ha de llegir a cada pas, i amb un context llarg comença a pesar. A 8 k tokens li menja 2 % del sostre.

I el batching: amb 32 seqüències, els pesos es llegeixen una sola vegada per a totes i el throughput puja a 472 tokens/s, que són 20,3×. Molt per sota del factor sencer: aquí el cache de les seqüències ja pesa més que els pesos, i el batching ha deixat de ser gratis. És el «fins a cert punt».

La conseqüència operativa: qualsevol cosa que redueixi bytes llegits per token millora el decode directament, i això inclou la quantització (menys bytes per paràmetre), GQA i MLA (menys cache), i els MoE (menys paràmetres actius). Res del que redueixi FLOPs sense reduir bytes no farà res. És la mateixa lliçó que el §2 del fitxer 14 treu de FlashAttention, vista des de l'altra punta del sistema.

2. El KV-cache# sense fórmules

Sense cache, generar el token tt requeriria recalcular l'atenció de tots els anteriors: O(L2)O(L^2) repetit LL vegades. Amb cache, guardes K\mathbf{K} i V\mathbf{V} de cada token i cada capa, i cada pas nou costa O(L)O(L).

mida=2⋅ncapes⋅nkv⋅dh⋅L⋅bytes\text{mida} = 2\cdot n_{capes}\cdot n_{kv}\cdot d_h\cdot L\cdot \text{bytes}

Exemple: Llama-3-70B (80 capes, 8 caps KV, dhd_h=128) amb 8k tokens en fp16: 2×80×8×128×8192×2≈2.72\times80\times8\times128\times8192\times2 \approx 2.7 GB per seqüència. Amb 100 usuaris concurrents, 270 GB només de cache.

Tècniques per gestionar-lo:

  • GQA/MLA (reducció estructural, vegeu 12).
  • PagedAttention (vLLM): gestiona el cache en blocs de mida fixa com les pàgines de memòria virtual d'un SO. Elimina la fragmentació (que malbaratava el 60-80% de la memòria) i permet compartir blocs entre seqüències amb prefix comú (copy-on-write).
  • Quantització del cache a FP8: la meitat de memòria i la meitat de trànsit per pas d'atenció; amb formats e4m3 la pèrdua de qualitat és molt petita i l'acceleració en decode és directa.
  • Prefix caching / prompt caching: reutilitzar el cache d'un prefix compartit (system prompt, documents, historial) entre peticions. És, de lluny, l'optimització amb més retorn per esforç en aplicacions reals: estalvis del 50-90% en cost i TTFT.
  • Desallotjament i compressió (H2O, SnapKV, StreamingLLM): descartar tokens poc atesos.

El KV-cache en serving: fragmentació, pàgines i prefixos

El fitxer 12 va comptar quants bytes fa el cache. Aquí es veu què se'n fa: quina part es malbarata i quina es pot compartir.

Com queda la memòria amb cada mètode

Blocs contigus: cada seqüència reserva el màxim17,1 k de 57,3 k tokens · 70 % malbaratat
Pàgines de 16 tokens: es reserva el que cal17,1 k de 17,2 k tokens · 1 % malbaratat

Catorze seqüències, la mateixa escala a les dues files. La part plena és el que es fa servir i la buida és el que s'ha reservat i no es farà servir mai. Es veu a ull que la segona fila ocupa molt menys, i la taula de sota en diu quant.

2.048 escalars per token i capa.

4 k tokens

El que una seqüència podria arribar a fer servir. El mètode contigu ho reserva sencer.

100 %

A 0, totes les seqüències fan el context màxim. És el control que ensenya d'on surt la fragmentació.

16

vLLM en fa servir 16. Més petita vol dir menys malbaratament i més metadades.

80 GB
Format del cache

Quantitzar el cache a fp8 en divideix per dos els bytes i el trànsit del pas d'atenció.

2 k tokens

System prompt, un document, l'historial d'una conversa.

50
malbaratat, contigu
77 %
malbaratat, paginat
0,8 %
seqüències, contigu
59
seqüències, paginat
253
prefill estalviat
96 %
TTFT amb prefix
245 → 5 ms
Amb blocs contigus es malbarata el 77 % de la memòria; amb pàgines, el 0,8 %

El text diu que la fragmentació «malbaratava el 60-80 % de la memòria». Aquí es reprodueix amb una simulació honesta: 300 seqüències amb longituds que van de 33 a 4.096 tokens, i una mitjana de 967.

Mètodereservatfet servirmalbaratatblocshi caben
Contigu (reserva el màxim)241,7 k54,6 k77 %5959
Paginat (16 tokens per pàgina)244,1 k242,1 k0,8 %15.258253

La causa és una sola decisió: el cache contigu ha de créixer en un tros seguit i no es pot moure, de manera que cal reservar tot el que la seqüència podria arribar a fer servir abans de saber on s'aturarà. Una resposta que acaba als 33 tokens amb 4 k reservats en té la resta buits i inservibles.

El que queda amb pàgines és només el tros buit de l'última de cada seqüència: 15 tokens com a molt, que sobre 967 de mitjana és el 0,8 % que es llegeix a la taula. Amb pàgines d'un sol token seria exactament zero, i el que ho impedeix són les metadades, no la idea.

Baixa la variabilitat a zero per veure d’on surt de debò la fragmentació

Aquest és el control que fa creïble la mesura. La fragmentació no surt d'haver reservat memòria: surt de la variabilitat de les longituds. Amb el control de variabilitat a zero, totes les seqüències fan exactament el context màxim, i llavors reservar el màxim és reservar el que caldrà: el malbaratament del mètode contigu és zero i els dos mètodes donen el mateix.

Ara mateix és a 100 %, i el mètode contigu en malbarata el 77 %. Les respostes d'un LLM tenen longituds molt desiguals —moltes curtes i una cua llarga— i per això aquest és el cas real i no el pitjor cas imaginat.

Prefix caching: 96 % menys de prefill i el TTFT passa de 245 a 5 ms

Amb 50 peticions sobre un prefix de 2 k tokens, processar-lo cada vegada costaria 102,00 k tokens de prefill; reutilitzant el cache del prefix, 4,00 k. El text diu que és «de lluny l'optimització amb més retorn per esforç» i en promet estalvis del 50-90 %: aquí en surt un 96 %.

El TTFT baixa en la mateixa proporció, i és la meitat que es nota des de fora: la primera petició espera 245 ms abans de veure la primera lletra i totes les altres, 5.

Quan no es pot aplicar, que és la part que costa cara si no es pensa: només es comparteixen blocs sencers i idèntics des del primer token. Amb pàgines de 16, d'aquest prefix se'n comparteixen 125 blocs —2.000 tokens— i la resta es recalcula sempre. I si al capdamunt del prompt hi poses la data i l'hora, el primer bloc ja no coincideix amb el de fa un minut i tot el cache queda invalidat. Les coses variables van al final del prompt, no al principi.

Quantitzar el cache: la mateixa comptabilitat amb menys bytes
Formatbytes/escalarcache a 4 kseqüències a 80 GB
FP3242,68 GB29
BF1621,34 GB59
FP8 (e4m3)10,67 GB119
INT810,67 GB119

La fórmula del §2, 2 ncapes nkv dh L bytes2\,n_{capes}\,n_{kv}\,d_h\,L\,\text{bytes}, és lineal en els bytes: passar de bf16 a fp8 dobla les seqüències concurrents i, com que el cache també s'ha de llegir a cada pas d'atenció, en redueix el trànsit a la meitat. Amb e4m3 el text diu que la pèrdua de qualitat és molt petita, i el widget de la quantització ho mesura sobre pesos de debò.

Les tres tècniques ataquen coses diferents i es poden fer alhora. PagedAttention recupera la memòria que la fragmentació es menjava; el prefix caching evita tornar a calcular el que ja està calculat; i quantitzar el cache redueix els bytes de cada escalar. La primera i la tercera et deixen servir més converses; la segona et baixa el cost i el TTFT de les que ja serveixes.

3. Continuous batching# sense fórmules

El batching estàtic malbarata GPU: si una seqüència del batch acaba a 20 tokens i una altra a 2000, esperes la més llarga.

El continuous batching (o in-flight batching) opera a nivell d'iteració: quan una seqüència acaba, entra'n una de nova immediatament. Guany típic de 2-4× en throughput, i és el motiu principal pel qual has d'usar vLLM, SGLang o TensorRT-LLM en comptes d'un bucle amb model.generate().

Altres tècniques de sistema:

  • Chunked prefill: trosseja prefills llargs perquè no bloquegin els decodes en curs (millora p99).
  • Desagregació prefill/decode: executar cada fase en GPUs diferents, optimitzades per a cada perfil.
  • Speculative decoding (secció 5).

Continuous batching

El text promet 2-4× de throughput. Simula la cua i mira't el número que en surt, i d'on surt: quantes ranures del lot estan esperant sense fer res.

la franja que el text promet: 2-4×
  • guany mesurat en aquesta simulació
  • la franja 2-4× del text

Les ranures del lot, iteració a iteració

Estàtic · 69 % ocioses
2,5 de 8 ranures ocupades de mitjana
Continu · 7 % ocioses
7,4 de 8 ranures ocupades de mitjana

Cada quadret és una ranura del lot; les plenes són les que estan generant i les buides, les que esperen. Cada iteració costa el mateix tant si les ranures estan plenes com si no, i per això les buides són cares.

8
80
100 %

A 0, totes les respostes fan la mateixa llargada. És el control que ensenya d'on surt el guany.

totes alhora

Trosseja els prefills llargs perquè no bloquegin els decodes en curs.

512
#5
throughput estàtic
118 tok/s
throughput continu
325 tok/s
guany
2,75×
ranures ocioses, estàtic
69 %
ranures ocioses, continu
7 %
p95 de latència
62,9 → 23,1 s
El guany que surt d'aquesta simulació és 2,75×, i el text en promet 2-4×

Amb 80 peticions, 8 ranures i longituds de sortida que van de 8 a 390 tokens, el batching estàtic treu 118 tokens/s i el continu, 325.

throughputiteracionsranures ocupadesTTFT p95latència p95
Estàtic1183.1852,5 de 862,30 s62,85 s
Continu3251.0617,4 de 819,07 s23,07 s

La columna que explica el guany és la de ranures ocupades. El batching estàtic en té 2,5 de 8 de mitjana i el continu, 7,4: és exactament la mateixa ràtio que el guany, perquè cada iteració costa el mateix passi el que passi i el que canvia és quants tokens en surten.

Amb batching estàtic, el 69 % de les ranures està esperant la seqüència més llarga

Aquesta és la GPU ociosa del §3, quantificada. Un lot estàtic no es toca fins que s'ha buidat del tot: si una seqüència acaba a 8 tokens i una altra a 390, la ranura de la primera es queda 382 iteracions sense fer res mentre la segona acaba. El continu hi fa entrar una petició nova el mateix instant que una acaba, i per això les seves ranures ocioses baixen al 7 %.

És també el motiu pel qual el guany no és el mateix a tots els lots. Amb una sola ranura no hi ha res a omplir i el guany és 1; a mesura que el lot creix, hi ha més ranures que poden quedar esperant i el continu en treu més. Mira el gràfic: la corba puja i després s'aplana quan el lot ja és més gran que la cua.

Baixa la variabilitat a zero per veure d’on surt el guany

El control negatiu que fa creïble la resta. El continuous batching no accelera res per si mateix: omple ranures que altrament estarien buides. Si totes les respostes fan la mateixa llargada, cap ranura no espera ningú, i els dos modes donen exactament el mateix.

Això vol dir que el 2-4× del text no és una propietat de vLLM sinó una propietat de la teva càrrega. Si serveixes classificacions que sempre responen «sí» o «no», el continuous batching no et donarà res; si serveixes un xat on unes respostes fan deu tokens i unes altres en fan dos mil, te'n donarà molt. Abans de mesurar el guany, mira la distribució de longituds de sortida.

Chunked prefill: el pitjor interval entre tokens és de 97 ms

El chunked prefill no millora el throughput —la feina de prefill és la mateixa— i per això el seu efecte no es veu a cap de les columnes de dalt. El que millora és el pitjor interval entre dos tokens d'una petició que ja estava decodificant quan n'ha entrat una amb un prompt llarg: sense trossejar, aquell prefill es fa tot en una iteració i tothom es queda congelat.

Ara mateix està actiu amb trossos de 512 tokens, i el pitjor interval és de 97 ms (p99: 97 ms). Apaga'l i torna'l a engegar amb un prompt llarg a la cua: el throughput no es mou i aquest número sí. És per això que el text en parla en termes de p99 i no de mitjana.

Aquesta simulació descansa sobre una sola hipòtesi, que és la del §1: una iteració de decode costa el mateix amb una seqüència que amb trenta-dues, perquè el que costa és llegir els pesos i es llegeixen una vegada. Si això no fos cert, res del que el widget ensenya no ho seria: el batching no seria gratis, les ranures buides no costarien res i el continuous batching no guanyaria res. Tot el §3 és una conseqüència del §1.

4. Quantització# sense fórmules

FormatBitsPèrdua típicaÚs
BF16/FP1616cap (referència)baseline
FP8 (e4m3)8mínimaestàndard a Hopper/Blackwell, també per al KV-cache
INT8 (SmoothQuant, W8A8)8molt petitaGPUs sense FP8
INT4 / NF4 / AWQ / GPTQ4petita-moderadaexecució local, models grans en poca VRAM
MXFP4 i formats de microescalat4petitageneració nova de hardware
2-3 bits2-3notablerecerca, casos extrems

Conceptes:

  • PTQ (post-training quantization): quantitzes un model ja entrenat. GPTQ (aproximació de segon ordre per capes), AWQ (protegeix els canals "salients" identificats per activacions).
  • QAT (quantization-aware training): simules la quantització durant l'entrenament; millor qualitat, molt més car.
  • Outliers d'activació: el problema central. Uns pocs canals tenen magnituds enormes i arruïnen l'escalat; d'aquí SmoothQuant (mou dificultat de les activacions als pesos) i les tècniques de rotació (QuaRot, SpinQuant).

Quantització: menys bits, i què costa

Quantitza els pesos de debò i torna a mesurar la pèrdua. I després mira el problema central: què passa quan uns pocs canals tenen magnituds enormes.

Un mini-GPT de 32 dimensions i 2 capes —28.832 paràmetres— sobre el corpus en català. Un cop entrenat, els seus pesos es quantitzen de debò a cada format i es torna a fer el forward: la columna de pèrdua de la taula surt d'aquí i no de cap estimació d'error.

Gra de l'escala

Una escala per a tot, o una per grup. Només importa amb quantització entera.

64 pesos

Els quantitzadors reals fan servir grups de 64 o 128.

40×

Factor que s'aplica a tres canals de les activacions. A 1 no n'hi ha cap.

pèrdua original
—
pèrdua a INT4
—
degradació
—
memòria
—
contra bf16
—
ràtio d’outliers
—
Entrena el model per poder-lo quantitzar

Quantitzar un model sense entrenar no diu res: la pèrdua ja és la de l'atzar i arrodonir els pesos no la pot empitjorar gaire. El que fa informativa la mesura és tenir un model que prediu, i mirar quant li costa perdre bits.

La quantització és l'optimització que toca les dues coses alhora: menys bytes per paràmetre vol dir menys memòria (hi caben més converses) i menys bytes per llegir a cada pas de decode (més tokens per segon). És l'única de tot el fitxer que millora el sostre del §1 directament, i per això és la primera que s'aplica.

5. Speculative decoding# sense fórmules

Idea: un model petit i ràpid (draft) proposa kk tokens; el model gran els verifica tots alhora en una sola passada (cosa que és barata, perquè el decode està limitat per memòria i no per càlcul) i accepta el prefix correcte.

  • Garanteix la mateixa distribució de sortida que el model gran (amb el mostreig de rebuig adequat). No és una aproximació.
  • Acceleració típica: 1,5–3×, depèn de com d'alineat estigui el draft.
  • Variants sense model draft: Medusa (caps addicionals que prediuen t+2t{+}2, t+3t{+}3...), EAGLE (predicció a nivell de característiques, la família més eficaç actualment), n-gram/prompt lookup (per a tasques amb molta còpia del prompt, com edició de codi — trivial d'implementar i sorprenentment efectiu), i multi-token prediction entrenada al propi model (DeepSeek-V3).

Speculative decoding

Un model petit proposa i el gran verifica. La promesa és que la sortida no canvia: quina de les dues lectures d'aquesta frase és certa?

Dos models sobre el mateix corpus en català: el gran, de 32 dimensions i 3 capes, i el draft, de 16 i 1. El draft s'entrena una fracció dels passos segons el control, que és el que fa que es pugui veure la taxa d'acceptació moure's.

k = 4

Més propostes vol dir més potencial i més feina llençada quan una es rebutja.

Acoblament

El primer dona la seqüència idèntica; el segon accepta més sovint. No es poden tenir les dues coses.

1 pas de cada 1

Com d'alineat està el draft amb el model gran. D'això depèn tot.

1,0
32
#11
passos del gran
0
passos del draft
0
taxa d’acceptació
—
tokens per passada del gran
—
passades del gran
—
seqüència idèntica
—
Entrena els dos models

Calen dos models sobre el mateix corpus: un de gran (32 dimensions, 3 capes) i un draft petit (16 dimensions, 1 capa). La taxa d'acceptació depèn de com d'alineats estiguin, i el control «entrenament del draft» permet deixar-lo enrere a posta per veure-ho.

Per què verificar k tokens costa el mateix que generar-ne un: perquè el decode va limitat per l'ample de banda i no per la potència, i el §1 ho mesura. Llegir els pesos una vegada per a k posicions és gairebé tan barat com llegir-los per a una, exactament com el prefill. Speculative decoding és, doncs, una manera de convertir una mica del decode en prefill, i per això funciona.

6. Decodificació: com es tria el token# sense fórmules

Tens una distribució pp sobre VV tokens. Què en fas?

MètodeDescripcióQuan
Greedy (T=0T=0)el més probabletasques deterministes, avaluació
Temperaturapi∝exp⁡(zi/T)p_i \propto \exp(z_i/T)T<1T<1 més conservador, T>1T>1 més creatiu
Top-knomés els kk millorssenzill, kk fix és poc adaptatiu
Top-p (nucleus)el conjunt mínim amb massa acumulada ≥p\geq pel més usat; adaptatiu
Min-pllindar relatiu al token més probablerobust a temperatures altes
Penalitzacionsrepetició, freqüència, presènciaevita bucles; compte amb degradar la qualitat
Beam searchmanté bb hipòtesistraducció i tasques amb sortida curta; dolent per a text obert (genera text avorrit i repetitiu)

Sortides estructurades: constrained decoding amb una gramàtica o esquema JSON (Outlines, XGrammar, guidance) força la validesa emmascarant els logits invàlids. És gairebé gratis i elimina tota una classe d'errors de parsing. Compte: restringir massa pot degradar la qualitat del contingut si el model "vol" escriure raonament abans.

Com es tria el token

Tens una distribució sobre el vocabulari. Cada mètode en talla un tros diferent: mira quanta massa deixa viva cadascun i com canvia el text que en surt.

Un mini-GPT de 32 dimensions i 2 capes sobre el corpus en català. Un cop prediu, la seva distribució a cada posició té la forma que fa interessants els mètodes de truncament: un pic i una cua llarga. La fita del bigrama sobre el mateix text és 2,30 nats.

1,00

Divideix els logits abans de la softmax. A 0 és greedy i no mostreja res.

apagat
apagat

El conjunt mínim amb massa acumulada ≥ p.

apagat

Llindar relatiu al token més probable: fora tot el que baixi de p·màxim.

apagada

Divideix els logits positius dels tokens ja vistos i multiplica els negatius.

#20

Quina distribució es mira. N'hi ha de molt punxegudes i de molt planes.

candidats vius
—
massa conservada
—
entropia
—
perplexitat
—
sense truncar
—
token més probable
—
Entrena el model per tenir una distribució de debò

Sense entrenar, la distribució del model és gairebé uniforme i cap mètode de truncament no diu res: top-p amb 0,9 es quedaria amb el 90 % del vocabulari i la cua tallada no existiria. El que fa interessant la tria del token és tenir una distribució punxeguda amb cua llarga, i això demana un model que prediu.

Els tres mètodes de truncament fan la mateixa cosa amb criteris diferents: trien un subconjunt i hi renormalitzen. El que els separa és si el criteri s'adapta a la forma de la distribució. I hi ha una decisió que no és de mostreig sinó de producte: beam search manté b hipòtesis i optimitza la probabilitat de la seqüència sencera, cosa que en text obert dona exactament el que no vols —el text més probable és el més avorrit i el més repetitiu—, mentre que en traducció, on la sortida és curta i hi ha una resposta bona, funciona bé.

7. Càlcul del que costa# sense fórmules

Memòria necessària ≈ pesos + KV-cache + activacions + fragmentació:

pesos    = N_params × bytes_per_param
kv_cache = 2 × capes × caps_kv × d_head × context × batch × bytes
overhead ≈ 10-20%

Tokens/s màxims en decode ≈ ample_de_banda / bytes_llegits_per_token.


La calculadora del que costa

Pesos, cache, activacions i overhead d'una banda; ample de banda dividit per bytes de l'altra. Hi cap el model, i quant et costa el milió de tokens?

On va la memòria a mesura que creix el context

la màquinapesoscache
  • pesos: no depenen del context
  • cache de 32 seqüències
  • total
  • el que tens

Les dues escales són logarítmiques. La línia dels pesos és plana i la del cache té pendent 1: on es creuen és el punt a partir del qual afegir context costa més que tenir el model. On la corba del total travessa la ratlla vermella és el context màxim que aquesta màquina pot servir amb 32 seqüències concurrents.

Els quatre models a 8 k tokens de context, amb 8 × H100 80 GB i el format triat
Modelpesoscache/seqseq. que hi cabentok/s (1 seq)TTFTdòlars/M
GPT-2 XL3 GB2,52 GB2194.630,80,00 s0,65
Llama 3 70B141 GB2,68 GB154186,40,15 s1,76
Mixtral 8×7B93 GB1,07 GB431998,80,03 s0,47
DeepSeek-V31.333 GB0,51 GBno hi cap402,00,07 s—

La columna de dòlars per milió és la que es compara amb els preus d'una API, i la de «seqüències que hi caben» és la que diu si el número de l'altra vol dir res. Fixa't en DeepSeek-V3: té els pesos més grossos de tots amb diferència i tanmateix genera de pressa, perquè el que es llegeix per token són els 33,08 G paràmetres actius i no els 666,55 G totals. Les dues xifres van en sentits contraris i totes dues són certes: aquesta és la fitxa del MoE en una fila.

Les dues fórmules del §7

mem≈N⋅b⏟pesos+2 ncapes nkv dh L B bkv⏟KV-cache+activacions+overhead\text{mem} \approx \underbrace{N\cdot b}_{\text{pesos}} + \underbrace{2\,n_{capes}\,n_{kv}\,d_h\,L\,B\,b_{kv}}_{\text{KV-cache}} + \text{activacions} + \text{overhead}
tokens/s≈ample de bandabytes llegits per token\text{tokens/s} \approx \frac{\text{ample de banda}}{\text{bytes llegits per token}}

La primera decideix si hi cap i la segona, com de ràpid va. No són independents: quantitzar baixa les dues alhora, i allargar el context les puja les dues. L'única decisió que millora la segona sense tocar la primera és pujar el lot, i el §3 explica per què no és gratis del tot.

70,55 G paràmetres totals, 70,55 G actius.

8 × H100 80 GB

640 GB i 26.800 GB/s en total.

8 k
32
15 %

Fragmentació, buffers del runtime, gràfics de CUDA. El text en diu 10-20 %.

3,00 dòlars
memòria necessària
261 GB
hi cap?
sí
seqüències que hi caben
154
throughput
3.778 tok/s
TTFT / TPOT
0,15 s / 5,4 ms
per milió de tokens
1,76 dòlars
Memòria: 261 GB de 640 disponibles

Les quatre línies de la primera fórmula del §7, amb el que aporta cadascuna. La conclusió del fitxer 12 —que el cache, i no els pesos, és el que limita quantes converses pots servir— es llegeix directament a la segona fila quan el context és llarg:

Componentbytesdel totald'on surt
Pesos141,1 GB54 %70,55 G × 2 bytes
KV-cache85,9 GB33 %2.048 esc./token·capa × 80 × 8 k × 32
Activacions0,35 GB0,1 %buffers d'un pas de decode
Overhead (15 %)34,1 GB13 %fragmentació i runtime

Les activacions d'inferència són un 0,1 % i es poden oblidar —en entrenament són el terme dominant, i és el tema del fitxer 17. Aquí la partida es juga entre les dues primeres files, i el context decideix quina guanya: a 8 k tokens i 32 seqüències, el cache és un 61 % dels pesos. El creuament per a una sola seqüència és als 430,6 k tokens; amb 32 concurrents arriba 32× abans, als 13,5 k.

Tokens per segon: 26.800 GB/s ÷ 143,79 GB per token

La segona fórmula del §7, amb els dos números que la formen. Per generar un token cal llegir tots els pesos actius —70,55 G paràmetres a 2 bytes, que són 141,1 GB— i tot el cache de la seqüència, que a 8 k tokens són 2,68 GB. Dividit per l'ample de banda, això dona 186,4 tokens/s per seqüència, i no hi ha kernel que ho pugui passar.

Amb 32 seqüències concurrents els pesos es llegeixen una sola vegada per a totes i el throughput agregat puja a 3.778 tokens/s, que són 20,3× i no 32×: la diferència és el cache, que sí que es multiplica. Aquí ja s’ha perdut la major part del factor: el cache pesa més que els pesos i afegir seqüències ja no és gratis.

I el número que explica per què les dues fases del §1 es comporten diferent: aquesta H100 80 GB pot fer 296 operacions per cada byte que llegeix. Un pas de decode en demana ~2, de manera que la màquina passa el 99,3 % del temps esperant memòria.

Cost: 1,76 dòlars per milió de tokens de sortida

La xifra amb què es prenen les decisions. 8 × 3,00 dòlars l'hora són 24,00 dòlars l'hora, i a 3.778 tokens/s surten 13,60 M tokens per hora.

Les tres palanques, i quina val més en aquest punt: quantitzar els pesos redueix els bytes per token i allibera memòria per a més cache; escurçar el context redueix el cache directament; i pujar el lot és gratis fins que el cache passa davant dels pesos. Prova-les amb els controls: la que més mou el cost depèn d'on siguis, i aquest és tot el motiu de tenir una calculadora en comptes d'una regla.

Aquesta calculadora és el fitxer sencer en dues fórmules, i val la pena veure quines decisions dels §2 al §5 hi entren i quines no. La quantització i GQA/MLA hi entren pels bytes; el prefix caching i el continuous batching no hi surten, perquè no canvien el sostre sinó com te n'acostes. El 🔨 del text demana fer això mateix amb vLLM i una GPU de debò, i és bon consell: aquests números són el límit físic, i el que mesuris sempre hi quedarà per sota.

8. Comprova que ho has entès#

0 de 5 resoltes

  1. Per què el decode està limitat per memòria i el prefill per càlcul?
  2. Calcula el KV-cache d'un model de 32 capes, 8 caps de KV i dh=128d_h=128, amb un context de 32.768 tokens, batch 16, en fp16. Dona el resultat en bytes.
    bytesMarge acceptat: 2 %
  3. Per què speculative decoding no canvia la distribució de sortida?
  4. Quin efecte té el prefix caching sobre el TTFT i sobre el cost, i quan no es pot aplicar?
  5. Per què beam search és mala idea per a generació de text obert, si busca la seqüència més probable?

9. Recursos#

Marca els que hagis llegit
  • Kwon et al. (2023), Efficient Memory Management for LLM Serving with PagedAttention (vLLM).

  • Documentació de vLLM i SGLang (i els seus blogs tècnics: són excel·lents).

  • Leviathan et al. (2023), Fast Inference from Transformers via Speculative Decoding.

  • Holtzman et al. (2019), The Curious Case of Neural Text Degeneration (nucleus sampling).

  • Lin et al. (2023), AWQ; Frantar et al. (2022), GPTQ; Xiao et al. (2022), SmoothQuant.