Capitolo 18 · Inferenza · 8 min
Perché il 2° token è più veloce del 1°
La KV cache e la generazione autoregressiva. Prefill vs decode, TTFT, e perché la cache cambia tutto.
L'illusione del tempo di risposta
Poni una domanda a ChatGPT. Impiega un secondo prima di iniziare a rispondere. Poi le parole escono quasi istantaneamente, alla velocità di una lettura veloce.
Questa asimmetria non è un vezzo dell'interfaccia. È l'impronta di un'ottimizzazione fondamentale, senza la quale generare testo con un LLM costerebbe cento volte di più: il KV cache.
Come un Transformer genera un token
A ogni passo di generazione, il Transformer deve produrre un nuovo token. Per farlo, calcola l'attention dell'ultimo token su tutti i token precedenti. È ciò che gli permette di tenere conto del contesto intero.
Ma l'attention richiede, per ogni token del contesto, due vettori: una chiave (K) e un valore (V). Senza ottimizzazione, a ogni nuovo token generato, il modello ricalcola K e V per tutta la sequenza — compresi i token già trattati al passo precedente. È un lavoro O(n²) sulla lunghezza della sequenza: raddoppiare il numero di token quadruplica il costo.
È inutile: questi vettori non sono cambiati. Il token n°3 ha la stessa chiave K₃ che aveva al passo precedente.
Il KV cache: non ricalcolare mai ciò che si ha già
L'idea è banale e decisiva. Si tengono in memoria (sulla GPU) le K e le V di tutti i token già trattati. A ogni nuovo passo di generazione, si calcolano soltanto la K e la V del nuovo token, che si aggiungono al cache.
Attenzione a non sopravvalutare il guadagno: ciò che diventa costante è il calcolo delle K e delle V del nuovo token. L'attention, invece, deve sempre confrontare quel token con ciascuna delle chiavi in cache — e questo resta proporzionale a n. Ciò che si risparmia è il ricalcolo di tutto il prefisso: ogni passo passa da O(n²) a O(n).
Lancia la generazione sulle due colonne qui sotto e guarda il contatore delle operazioni piuttosto che l'animazione. Chiediti a ogni passo: cosa viene davvero ricalcolato, e cosa lo è una volta sola?
Senza cache, ogni nuovo token ricalcola tutta l'attention sul prefisso — costo in O(n²). Con la cache, si calcola solo la nuova riga. È ciò che separa il primo token (lento, prefill) dal secondo (veloce, decode).
A sinistra, senza cache: ogni passo ridisegna tutte le righe. A destra, con cache: si aggiunge semplicemente una riga. Dopo qualche token, lo scarto in operazioni cumulate diventa considerevole.
Prefill vs decode: due fasi ben distinte
La generazione con un LLM si divide in due fasi che gli ingegneri di inferenza distinguono accuratamente.
Prefill. Il modello riceve il prompt completo e calcola K e V per tutti i suoi token in parallelo. È veloce in termini di throughput — la GPU è satura — ma richiede tempo se il prompt è lungo. È ciò che determina il TTFT (Time To First Token), la latenza prima che la prima parola appaia.
Decode. Il modello genera il resto, un token alla volta, riutilizzando il cache. Ogni passo è veloce individualmente, ma sequenziale: non si possono parallelizzare i token futuri, dato che ognuno dipende dal precedente. È ciò che determina l'ITL (Inter-Token Latency).
Queste due fasi hanno profili completamente diversi:
| Prefill | Decode | |
|---|---|---|
| Parallelizzabile? | Sì (tutti i token insieme) | No (sequenziale) |
| Limite hardware | Compute (FLOPs) | Memoria (lettura del cache) |
| Effetto della lunghezza | Lineare in N | Lineare in N per token |
| Metrica chiave | TTFT | ITL |
Su un prompt lungo, il prefill può richiedere qualche secondo. Su un output lungo, è il decode che domina — ed è limitato dalla velocità con cui si può leggere il KV cache dalla memoria HBM della GPU.
Perché i provider fatturano gli "input token" diversamente
Se guardi i prezzi di OpenAI, Anthropic o Google, gli input token sono sistematicamente meno cari degli output token — spesso da 4× a 8× meno. Non è arbitrario. Il prefill, che tratta gli input token, è massicciamente parallelo e usa efficacemente la GPU. Il decode, invece, genera gli output token uno per uno e sfrutta male l'hardware.
Più sottilmente: Anthropic, OpenAI e altri offrono ora il prefix caching. Se più richieste iniziano con lo stesso system prompt, si calcola il KV cache di questo prefisso una volta sola, e lo si riutilizza. È ciò che rende gli agenti e i chatbot multi-turno economicamente sostenibili: senza prefix caching, ogni turno costerebbe il ritrattamento completo dell'intera conversazione.
Il costo nascosto: la memoria GPU
Il KV cache non è gratuito in memoria. Occupa:
memoria = 2 × n_layers × n_kv_heads × d_head × seq_len × batch_size × 2 byte (FP16)
n_kv_heads è il numero di head che possiedono davvero le proprie K e V. In un Transformer multi-head classico vale semplicemente n_heads. Nei modelli recenti è molto più piccolo — vediamo subito sotto perché.
Per un modello da 70 miliardi di parametri con un contesto di 128.000 token e un batch di 1, parliamo di diverse decine di GB. È spesso ciò che limita la lunghezza di contesto praticabile, più della capacità stessa del modello di ragionare sulla sequenza.
Per spingersi oltre, esistono diverse tecniche:
- Cache quantization: stoccare K e V in INT8 o INT4 invece che in FP16, dividendo la memoria per 2 o 4.
- MQA / GQA (Multi-Query / Grouped-Query Attention): condividere le K/V tra più head. Llama 2 70B e Llama 3 usano GQA, riducendo drasticamente la dimensione del cache.
- Sliding window attention: tenere solo una finestra recente del cache (Mistral, Gemma).
- PagedAttention (vLLM): trattare il cache come pagine di memoria virtuale, per gestire meglio il batching dinamico.
Quantization, in due parole
La parola torna ovunque in questa parte : QLoRA in 4 bit (capitolo 14), cache quantization qui sopra, modelli GGUF che si scaricano da Hugging Face. È il momento di spiegare cosa significa.
Quantizzare vuol dire rappresentare ogni parametro del modello con meno bit. Un numero in virgola mobile a 32 bit (FP32) occupa 4 byte. In FP16, 2 byte. In INT8, 1 byte. In INT4, mezzo byte — la memoria occupata dai pesi viene divisa per 8 rispetto al FP32 originale.
| Precisione | Byte / parametro | Modello 70B occupa |
|---|---|---|
| FP32 | 4 | 280 GB |
| FP16 / BF16 | 2 | 140 GB |
| INT8 | 1 | 70 GB |
| INT4 | 0,5 | 35 GB |
| INT2 (estremo) | 0,25 | 17,5 GB |
Il trucco : un peso 0,237 non diventa esattamente 0,237 in INT4 (che ha solo 16 valori possibili), ma il suo valore più vicino in una griglia discreta. La perdita di qualità dipende dal modello e dal metodo, ma è tipicamente piccola fino a INT8, modesta in INT4 (qualche % di degrado sui benchmark), significativa sotto.
Le tecniche moderne (GPTQ, AWQ, GGUF) non quantizzano tutti i pesi nello stesso modo — preservano la precisione dei layer sensibili e comprimono più aggressivamente gli altri. E la quantization del KV cache, menzionata sopra, applica esattamente la stessa idea alle attivazioni stoccate in memoria durante la generazione.
È questa la tecnica che permette di far girare Llama 70B su un MacBook con 64 GB di RAM, dove la versione FP16 richiede un cluster.
La lezione
Senza il KV cache, gli LLM in produzione sarebbero impraticabili. Una conversazione lunga, un agente che ragiona, un chatbot che ricorda ciò che hai detto cinque messaggi prima — niente di tutto questo reggerebbe economicamente.
Ma questo cache è anche ciò che vincola la lunghezza del contesto. Quando si parla di "finestra da 1 milione di token", è in gran parte un problema di memoria del cache, non un problema di calcolo dell'attention.
Il KV cache non è un'ottimizzazione tra le tante. È ciò che trasforma l'attention da meccanismo teorico a infrastruttura di produzione.
Il che sposta la questione della taglia. Un modello non costa solo ciò che si è pagato per addestrarlo una volta: costa memoria e calcolo a ogni token, su ogni richiesta, per sempre. Allora, se un modello più piccolo può essere servito miliardi di volte a una frazione del prezzo, « più grande » vuol dire ancora « migliore »? Il capitolo successivo mostra che la ricerca ha già deciso — e non nel senso che si crede.
Aggiornato il