Inferenza dei modelli linguistici · guida interattiva

Un token alla volta: perché la risposta costa più della domanda

Un modello linguistico legge il prompt tutto insieme, come chi scorre una pagina con lo sguardo, ma scrive la risposta come chi detta: una parola, poi la successiva, e ogni parola richiede di rileggere a memoria tutto il modello. Da questa asimmetria discendono quasi tutti i numeri che si vedono usando un LLM (Large Language Model, modello linguistico di grandi dimensioni): il tempo prima del primo token, la velocità di scrittura, la memoria occupata, il prezzo diverso per i token in ingresso e in uscita. Questa guida segue il ciclo di generazione, le regole con cui si sceglie ogni token, la cache che evita di ricalcolare il passato, e come un server riesce a servire migliaia di utenti insieme.

1 · Il ciclo di generazione

Il modello non scrive una risposta: prevede un token, lo aggiunge e ricomincia

Un'analogia

Pensate al completamento automatico della tastiera del telefono, usato premendo sempre il primo suggerimento: ogni parola scelta diventa parte del testo su cui si calcola il suggerimento successivo.

Un modello linguistico decoder fa esattamente questo, con una rete molto più grande e un vocabolario di decine o centinaia di migliaia di token. Non c'è un piano della frase da qualche parte: c'è solo la domanda «dato tutto il testo fin qui, quale token viene dopo?», ripetuta.

Ogni giro del ciclo ha quattro passi. Una passata in avanti (forward pass) attraversa tutti gli strati e produce un logit, un punteggio grezzo, per ciascun token del vocabolario. La softmax trasforma i logit in una distribuzione di probabilità. Una regola di scelta, il campionamento del capitolo 2, estrae un token da quella distribuzione. Il token scelto si accoda al testo e il giro riparte.

Che cosa siano logit e softmax, e come si arriva a quei punteggi, è spiegato nella guida sul training, e la softmax si può provare a mano nel laboratorio sulle quattro classi della guida sul machine learning: qui interessa che cosa se ne fa quando il modello genera.

passo 1Passata in avantitutti gli strati, sull'ultimo token
passo 2Logit → probabilitàsoftmax su tutto il vocabolario
passo 3Sceltagreedy o campionamento
passo 4Si riaccodail token entra nel contesto, si torna al passo 1

Quando si ferma

Il ciclo non ha una lunghezza prefissata. Si ferma in tre casi: il modello sceglie un token di fine (un token speciale che durante l'addestramento chiudeva ogni risposta, spesso chiamato EOS, End Of Sequence), il testo prodotto contiene una sequenza di stop indicata da chi chiama, oppure si raggiunge il limite massimo di token in uscita. Nel terzo caso la risposta è semplicemente tagliata: le API (Application Programming Interface, interfacce di programmazione) lo segnalano con un motivo di arresto diverso, ed è buona pratica controllarlo.

Il prompt in parallelo, la risposta in fila

Il prompt è già tutto noto prima di cominciare. Grazie alla maschera causale, che impedisce a ogni token di guardare quelli successivi, il modello può elaborare tutti i token del prompt nella stessa passata: ogni posizione calcola il proprio vettore guardando solo quelle precedenti, e i calcoli di posizioni diverse non dipendono l'uno dall'altro. È la fase di prefill, e la sua ultima posizione produce già i logit del primo token di risposta.

La risposta invece non si può parallelizzare: il token 5 non esiste finché il token 4 non è stato scelto. È la fase di decode: una passata per ogni token in uscita. Mille token di risposta sono mille passate in sequenza, mentre mille token di prompt sono una passata sola. Il capitolo 4 mostra perché questa differenza pesa su tempi e prezzi.

PREFILL · 1 passata La capitale d ' Italia è 6 token insieme → logit del 1º token DECODE · 1 passata per token Romapassata 2 ,passata 3 unapassata 4 cittàpassata 5 … tempo: la prima passata fa più lavoro, ma le successive sono tante e non si possono sovrapporre

Schema: nella fase di prefill i sei token del prompt «La capitale d'Italia è» entrano in una sola passata, che produce i logit del primo token di risposta. Nella fase di decode ogni token successivo, «Roma», «,», «una», «città», richiede una passata propria, in sequenza.

Laboratorio: il ciclo, passata dopo passata

simulazione

Premi «Passata successiva» e guarda quanti token entra in ogni passata e come cresce il contesto; prova poi a togliere la cache KV (key-value, capitolo 3).

Distribuzione del token scelto in questa passata
Passate in avanti–
Token elaborati in questa passata–
Token elaborati in totale–
Contesto–

Le probabilità sono inventate e si mostrano solo i quattro candidati migliori su un vocabolario che ne ha decine di migliaia; la scelta è sempre quella più probabile (greedy). I conteggi di token elaborati sono quelli reali di un decoder: con la cache ogni passata di decode elabora il solo token nuovo; senza, rielabora ogni volta tutto il testo.

Mappa concettuale · il ciclo di generazione
  • Ciclo di generazione
    • ogni giro è fatto di Passata in avanti
      • produce logit
      • poi la softmax li trasforma in probabilità scelta: capitolo 2
    • si ripete finché non c'è un arresto
      • può essere token di fine, sequenza di stop o limite
    • ha due fasi Prefill e decode
      • il prefill elabora il prompt in una passata
      • il decode produce un token per passata capitolo 4
Passata in avanti
Un attraversamento completo della rete, dagli embedding ai logit. Nel decode ne serve una per ogni token generato.
32 strati in un modello da 8 miliardi di parametri
Prefill
La prima fase della generazione: tutti i token del prompt elaborati insieme, in una passata.
2.000 token di prompt → 1 passata
Decode
La seconda fase: un token per passata, in sequenza, fino al token di fine o al limite.
500 token di risposta → 500 passate
Token di fine
Token speciale che il modello ha imparato a emettere per chiudere la risposta; quando viene scelto, il ciclo si ferma.
<|eot_id|> in Llama 3
2 · Scegliere il token

La distribuzione la decide il modello, la scelta la decidono poche regole fuori dal modello

Il modello restituisce una distribuzione; quale token prendere è una decisione separata, fatta da poche righe di codice nel server, e cambia molto il carattere del testo.

Un'analogia

È come una giuria che dà un voto a ogni candidato: si può proclamare sempre il primo, oppure estrarre a sorte pesando i voti, magari dopo aver escluso chi ha preso troppo poco.

Greedy (avido) significa prendere sempre il token più probabile. È deterministico e va bene per risposte brevi e fattuali, ma su testi lunghi tende a ripetersi e a scegliere la strada più banale. Campionare significa estrarre a caso secondo le probabilità: il testo diventa più vario, al prezzo di qualche scelta improbabile. La temperatura, già vista nella guida sul training, divide i logit prima della softmax: sotto 1 accentua le differenze e avvicina il greedy, sopra 1 le appiattisce. A temperatura 0 la maggior parte delle librerie passa direttamente al greedy.

Tagliare la coda: top-k, top-p, min-p

Il problema del campionamento puro è la coda: decine di migliaia di token con probabilità minuscole che, sommate, pesano. Estrarre spesso significa prima o poi pescare dalla coda, e un solo token assurdo può far deragliare il resto della frase. I filtri tolgono la coda e rinormalizzano ciò che resta, cioè dividono per la somma rimasta in modo che le probabilità tornino a sommare 1.

RegolaChe cosa tienePregioLimite
Top-ki k token più probabilisemplice, costo fissonumero rigido: troppi candidati quando il modello è sicuro, troppo pochi quando è incerto
Top-p (nucleus)i token più probabili fino a superare una probabilità cumulata psi adatta: pochi candidati se la distribuzione è appuntita, molti se è piattacon temperature alte la coda si gonfia e il nucleo si allarga molto
Min-pi token con probabilità almeno pmin × quella del token miglioresoglia relativa alla sicurezza del modello, regge meglio le temperature altepiù recente, non tutte le API lo espongono

Top-p è stato proposto da Holtzman e colleghi (2019), che hanno mostrato come il testo generato con greedy o beam search tenda alla ripetizione e quello campionato senza filtri all'incoerenza; min-p da Nguyen e colleghi (2024).

Laboratorio: temperatura e filtri su una distribuzione vera

matematica reale

Muovi temperatura, top-k, top-p e min-p: le barre grigie sono le probabilità originali, quelle colorate le probabilità dopo i filtri, già rinormalizzate.

Contesto: «Il gatto si è seduto sul» → prossimo token?
Token rimasti–
Probabilità del primo–
Entropia–

Dodici candidati con logit inventati ma plausibili; un vocabolario vero ha una coda di decine di migliaia di token, che rende i filtri ancora più importanti. L'ordine applicato qui è: penalità, temperatura, top-k, top-p, min-p. Le librerie non usano tutte lo stesso ordine, e il risultato può cambiare un po'. Le 20 estrazioni sono indipendenti, tutte dallo stesso contesto: servono a vedere la frequenza, non a generare una frase.

Penalità di ripetizione, di frequenza e di presenza

Un altro modo di correggere i logit è guardare il testo già scritto. La penalità di presenza sottrae un valore fisso al logit di ogni token che è già comparso almeno una volta; la penalità di frequenza sottrae un valore proporzionale a quante volte è comparso. È la forma usata, per esempio, dalle API (interfacce di programmazione) di OpenAI. La penalità di ripetizione introdotta con il modello CTRL (Keskar e colleghi, 2019) invece divide i logit positivi dei token già visti per un fattore (1,2 nel lavoro originale) e moltiplica quelli negativi.

Attenzione

Sono strumenti grossolani: il modello non distingue una ripetizione noiosa da una necessaria, come il nome di una variabile in un codice o un termine tecnico in una spiegazione. Con i modelli moderni addestrati a seguire istruzioni si usano poco, e valori alti peggiorano la qualità.

Seed e non determinismo, anche a temperatura 0

Il campionamento usa un generatore di numeri pseudocasuali: fissando il seed (il seme da cui il generatore parte) si ottengono le stesse estrazioni, come mostra il laboratorio. Ci si aspetterebbe allora che a temperatura 0, o con lo stesso seed, la stessa domanda dia sempre la stessa risposta. Nei servizi reali spesso non succede, per ragioni numeriche più che casuali.

  1. La virgola mobile non è associativa. In aritmetica a precisione finita (a + b) + c può differire di poco da a + (b + c). Una somma di migliaia di termini dà risultati leggermente diversi se cambia l'ordine.
  2. L'ordine dipende dal carico. Il server mette insieme le richieste di più utenti nello stesso batch (capitolo 5); a seconda di quante sono, le librerie scelgono strategie di calcolo diverse, che sommano in ordine diverso. La vostra richiesta non è calcolata sempre nello stesso modo.
  3. Basta un pareggio quasi perfetto. Se due token hanno logit vicinissimi, una differenza nell'ultima cifra cambia il vincitore. Da lì la risposta prosegue su un'altra strada, e le differenze si accumulano.
Da ricordare

Per questo alcuni provider offrono il seed come garanzia «per quanto possibile», non assoluta. Ottenere un'inferenza davvero riproducibile richiede calcoli che diano lo stesso risultato qualunque sia la dimensione del batch, e costa un po' di velocità.

Mappa concettuale · scegliere il token
  • Scelta del token
    • può essere Greedy o campionamento
      • il greedy prende il token più probabile
      • la temperatura cambia quanto le probabilità sono appiattite
    • si migliora con Filtri sulla coda
      • sono top-k, top-p, min-p
      • poi si rinormalizza
    • si corregge con Penalità sui token già usati
      • sono di presenza, frequenza, ripetizione
    • non è sempre riproducibile per Virgola mobile e batch
      • si fissa con il seed, per quanto possibile
Greedy
Scelta che prende sempre il token con la probabilità più alta. Equivale a temperatura 0.
«divano» al 41% → «divano», sempre
Top-p (nucleus)
Filtro che tiene il gruppo più piccolo di token la cui probabilità cumulata supera p, poi rinormalizza.
p = 0,9: si scarta l'ultimo 10% di coda
Min-p
Filtro che tiene i token con probabilità almeno uguale a una frazione di quella del token migliore.
min-p 0,1 con il primo al 40% → soglia 4%
Rinormalizzare
Dividere le probabilità rimaste per la loro somma, così che tornino a sommare 1.
30% e 20% rimasti → 60% e 40%
Seed
Valore iniziale del generatore pseudocasuale: stesso seed, stesse estrazioni, se il resto del calcolo è identico.
seed = 42
3 · La cache KV (key-value)

Il passato non cambia, quindi si calcola una volta sola e si tiene in memoria

Nell'attenzione ogni token produce tre vettori: una query, una key e un value. Il token corrente confronta la propria query con le key di tutti i token precedenti e mescola i loro value secondo quei punteggi (il meccanismo è descritto nel capitolo sugli strati della guida sul training). Per generare il token 501 servono quindi le key e i value dei 500 token precedenti, in ogni strato.

Il punto chiave è la maschera causale. Siccome un token non guarda mai quelli che vengono dopo, il suo vettore in ogni strato non cambia quando la risposta si allunga: le key e i value del token 37 sono gli stessi alla passata 40 e alla passata 400. Ricalcolarli ogni volta sarebbe puro spreco.

La cache KV (key-value, chiavi e valori) li conserva: a ogni passata di decode si calcolano query, key e value del solo token nuovo, si aggiungono key e value alla cache e si legge la cache per l'attenzione. Il decoder con maschera causale, e il perché un encoder come BERT non può farlo, è il tema della guida sulle architetture.

Quanto lavoro si risparmia

Prendiamo un prompt di 1.000 token e una risposta di 500. Con la cache il modello elabora 1.000 token nel prefill e poi uno per passata: circa 1.500 elaborazioni di token in tutto. Senza cache, ogni passata rielabora l'intero testo: 1.000 la prima volta, 1.001 la seconda, fino a 1.499.

Calcolo · lavoro senza cache

senza cache=500 × 1.000 + (0 + 1 + … + 499)
=500.000 + 124.750
=624.750
circa 400 volte il lavoro fatto con la cache (1.500 token), e il rapporto cresce con la lunghezza

Il prezzo si paga in memoria. La query del token corrente serve una volta e si butta; key e value invece restano per tutta la conversazione, per ogni strato e per ogni testa che ne ha di proprie.

Quanta memoria occupa

byte per token = 2 × strati × teste KV × dtesta × byte per numeroil 2 conta key e value; bf16 (bfloat16, virgola mobile a 16 bit) usa 2 byte per numero

Per Llama 3 8B: 32 strati, 8 teste KV, ogni testa di dimensione 128, numeri in bf16. Il conto dà:

Calcolo · cache KV di Llama 3 8B

byte per token=2 × 32 × 8 × 128 × 2
key e value, strati, teste KV, dimensione di una testa, byte per numero in bf16
=131.072 byte
cioè=128 KiB per token

Un contesto di 8.192 token occupa 1 GiB per conversazione; uno di 128.000 token, il massimo di Llama 3.1, circa 16 GiB, quanto i pesi del modello stesso. La memoria della cache cresce in modo lineare con i token e con il numero di conversazioni servite insieme.

Condividere key e value tra le teste: MQA e GQA

Nell'attenzione originale a più teste (MHA, Multi-Head Attention) ogni testa ha query, key e value propri: Llama 2 7B, con 32 teste, mette in cache 32 coppie di key e value per strato, cioè 512 KiB per token.

La MQA (Multi-Query Attention, Shazeer 2019) fa condividere a tutte le teste di query una sola coppia di key e value; la GQA (Grouped-Query Attention, Ainslie e colleghi 2023) sta in mezzo: le teste di query sono divise in gruppi e ogni gruppo condivide una coppia. Llama 3 8B ha 32 teste di query e 8 teste KV, quindi gruppi da quattro, e una cache quattro volte più piccola di quella di un'attenzione completa delle stesse dimensioni.

Le query restano tutte: ogni testa continua a cercare cose diverse, ma lo fa negli stessi archivi. La perdita di qualità della GQA è piccola rispetto alla MQA, che ne ha una più evidente; per questo la GQA è diventata la scelta più comune. Altre strade riducono ancora la cache: comprimerla in un vettore latente più piccolo (MLA, Multi-head Latent Attention, in DeepSeek-V2 e V3), limitare alcuni strati a una finestra degli ultimi token, oppure quantizzarla a 8 bit.

Laboratorio: calcolatore della cache KV

matematica reale

Scegli un modello oppure muovi strati, teste KV e contesto, e guarda quanta memoria serve per token, per conversazione e per tutte le conversazioni servite insieme.

Per token–
Per conversazione–
Totale cache–
Token per GiB–
Su una GPU (Graphics Processing Unit, scheda acceleratrice) da 80 GB: pesi del modello (se noti) e cache
pesi in bf16cache KVoltre gli 80 GBlibero

La formula è esatta per l'attenzione standard, MHA, GQA e MQA; non vale per varianti come MLA o le finestre scorrevoli. I numeri reali di un server sono un po' più alti: frammentazione (capitolo 5), attivazioni temporanee e memoria riservata dalla libreria. 1 GiB = 2³⁰ byte; GPU da 80 GB = 80·10⁹ byte, come si dichiarano le schede.

Mappa concettuale · la cache KV
  • Cache KV
    • è possibile grazie a Maschera causale
      • perché il passato non cambia mai
    • fa risparmiare Calcolo
      • evita di rielaborare tutto il testo a ogni passata
    • costa Memoria
      • si calcola con 2 × strati × teste KV × dtesta × byte
      • cresce in modo lineare con i token
    • si riduce con GQA e MQA
      • condividono key e value tra le teste di query
Cache KV
Key e value dell'attenzione dei token già elaborati, conservati per ogni strato e testa KV per non ricalcolarli a ogni nuovo token.
128 KiB per token in Llama 3 8B
GQA (Grouped-Query Attention)
Attenzione in cui gruppi di teste di query condividono la stessa coppia di key e value: cache più piccola, qualità quasi invariata.
32 teste di query, 8 teste KV
MQA (Multi-Query Attention)
Caso estremo della GQA: una sola coppia di key e value per tutte le teste di uno strato.
Falcon 7B, PaLM
4 · Prefill e decode

Il prompt consuma calcolo, la risposta consuma memoria e tempo

Una GPU (Graphics Processing Unit) può fare due cose lentamente: calcolare e spostare dati dalla propria memoria ai circuiti di calcolo. Quale delle due fa da collo di bottiglia dipende da quante operazioni si fanno per ogni byte letto, la cosiddetta intensità aritmetica.

Un'analogia

Pensate a un cuoco con un dispensa lontana: se per ogni ingrediente preso fa cento piatti, conta la sua velocità ai fornelli; se per ogni ingrediente fa un piatto solo, conta quanto in fretta va e torna dalla dispensa.

Prefill limitato dal calcolo, decode limitato dalla memoria

Ogni passata deve leggere tutti i pesi dalla memoria: 16 GB per un modello da 8 miliardi di parametri in bf16 (bfloat16, 2 byte per numero). Nel prefill, quella lettura serve a elaborare migliaia di token insieme: ogni peso letto viene usato migliaia di volte, e il limite è la potenza di calcolo. Si dice che il prefill è compute-bound.

Nel decode con una sola conversazione, invece, la stessa lettura di 16 GB serve per un token: circa due operazioni per ogni peso, cioè per ogni due byte. I circuiti di calcolo aspettano quasi sempre la memoria: il decode è memory-bound.

In più ogni passata rilegge l'intera cache KV (key-value) della conversazione, che cresce token dopo token. Con una banda di memoria di qualche TB/s (terabyte al secondo), leggere 16 GB richiede alcuni millisecondi: è il tempo minimo per token, qualunque sia la potenza di calcolo della scheda.

prefillTanti token, una passata
usamigliaia di operazioni per ogni peso letto
limitepotenza di calcolo (FLOPS, operazioni in virgola mobile al secondo)
crescecon i token del prompt; per prompt molto lunghi anche col quadrato, per via dell'attenzione
si misura nel tempo al primo token
decodeUn token per passata
usacirca un'operazione per byte letto
limitebanda di memoria: pesi + cache KV a ogni token
crescecon i token in uscita, in modo lineare, e un po' con la lunghezza del contesto
si misura nel tempo per token in uscita

Le metriche: TTFT, TPOT, ITL

Il TTFT (Time To First Token, tempo al primo token) è quanto si aspetta prima di vedere comparire qualcosa: rete, coda sul server e prefill. Cresce con la lunghezza del prompt. Il TPOT (Time Per Output Token, tempo per token in uscita) è il tempo medio tra un token e il successivo dopo il primo; l'ITL (Inter-Token Latency, latenza tra token) è la stessa grandezza misurata token per token, utile per vedere gli scatti. La latenza totale è circa:

Calcolo · latenza totale

latenza totale≈TTFT + (nuscita − 1) × TPOT
nuscita sono i token in uscita: il primo arriva con il TTFT, ognuno degli altri costa un TPOT

In una chat con streaming conta molto il TTFT; in un'elaborazione in batch di documenti conta il numero totale di token al secondo.

Un caso che sorprende

I modelli che «ragionano» prima di rispondere generano token di ragionamento, visibili o no. Sono token in uscita a tutti gli effetti: allungano l'attesa e si pagano come output.

Perché nei listini l'uscita costa di più

I provider fanno pagare a milione di token, e il token in uscita costa di solito alcune volte quello in ingresso (rapporti tra 3 e 8 sono comuni). Non è arbitrario: un token di prompt è una piccola frazione di una passata eseguita a pieno regime di calcolo, mentre un token di risposta occupa una passata intera, limitata dalla memoria, e tiene occupata per tutto il tempo la cache KV della conversazione. Per lo stesso motivo alcuni listini alzano il prezzo oltre una certa lunghezza del contesto.

Prompt caching: riusare il prefill di un prefisso

Molte richieste cominciano allo stesso modo: lo stesso prompt di sistema, le stesse definizioni dei tool, lo stesso documento lungo su cui si fanno più domande. La cache KV di quel prefisso è identica ogni volta, perché dipende solo dai token che vengono prima. Il prompt caching dei provider la conserva per qualche minuto dall'ultimo uso: la richiesta successiva che inizia con esattamente lo stesso prefisso salta quella parte del prefill.

Il TTFT scende e i token letti dalla cache costano una frazione del prezzo normale (per alcuni provider un decimo); scriverli in cache può costare un po' di più.

Due conseguenze pratiche. Il prefisso deve coincidere token per token: basta un orario o un identificativo all'inizio del prompt di sistema per invalidare tutto, quindi le parti che cambiano vanno in fondo. E non va confuso con la cache delle risposte, che un'applicazione o un gateway tiene per restituire una risposta già data a una domanda uguale o simile, senza chiamare il modello: quella è descritta nella guida sull'ingegneria dei sistemi basati su LLM (Large Language Model). Il prompt caching risparmia il prefill, ma la risposta viene generata ogni volta.

Laboratorio: dove va il tempo, dove vanno i soldi

valori illustrativi

Muovi i token in ingresso e in uscita e la parte di prompt già in cache: guarda come si dividono il tempo (prefill contro decode) e il costo.

Tempo della richiesta
Costo della richiesta
prefill / ingressoingresso letto dalla cachedecode / uscita
TTFT (tempo al primo token)–
TPOT (tempo per token)–
Latenza totale–
Costo–

Modello a «tetto» semplificato. GPU ipotetica con 3,35 TB/s di banda e 989 TFLOPS (mille miliardi di operazioni al secondo) in bf16, gli ordini di grandezza di una scheda da datacenter recente, usata all'80% della banda e al 50% del calcolo; con 4 GPU tutto moltiplicato per 4, come se la divisione fosse perfetta. Una sola richiesta, senza coda né rete. La quantizzazione accorcia il decode (meno byte da leggere) ma qui non il prefill. Prezzi inventati: 3 $ per milione di token in ingresso, 0,30 $ letti dalla cache, 15 $ in uscita.

Mappa concettuale · prefill e decode
  • Costo di una richiesta
    • prima fase Prefill
      • è limitato dal calcolo (compute-bound)
      • si misura con TTFT
    • seconda fase Decode
      • è limitato dalla memoria (memory-bound)
      • si misura con TPOT e ITL
    • spiega Prezzi di ingresso e uscita
      • l'uscita costa più dell'ingresso
    • si abbassa con Prompt caching
      • riusa il prefill di un prefisso identico
TTFT (Time To First Token)
Tempo tra l'invio della richiesta e l'arrivo del primo token: coda, rete e prefill.
cresce con la lunghezza del prompt
TPOT (Time Per Output Token)
Tempo medio tra due token in uscita consecutivi, dopo il primo. Il suo inverso è la velocità di scrittura vista dall'utente.
10 ms → 100 token al secondo
Memory-bound
Calcolo limitato dalla velocità con cui i dati arrivano dalla memoria, non dalla potenza di calcolo.
il decode con poche conversazioni
Prompt caching
Riuso, da parte del provider, della cache KV di un prefisso identico già visto di recente: meno prefill, prezzo ridotto per quei token.
prompt di sistema e tool sempre uguali
5 · Servire molti utenti

Il decode spreca la GPU, quindi si servono tante conversazioni nella stessa passata

Se nel decode la GPU (Graphics Processing Unit) passa il tempo a leggere i pesi, la mossa ovvia è usare ogni lettura per più conversazioni. Leggere 16 GB di pesi e usarli per 64 token di 64 utenti diversi costa quasi quanto usarli per uno solo.

Un'analogia

È come un autobus: il viaggio costa più o meno lo stesso con un passeggero o con cinquanta. Il batching fa salire più passeggeri; tutto il resto di questo capitolo serve a farne salire di più, o a far andare l'autobus più veloce.

Batching continuo

Il batching statico raccoglie un gruppo di richieste e le porta avanti insieme fino alla fine: chi ha chiesto una risposta di 20 token aspetta chi ne ha chiesta una di 2.000, e i posti liberati restano vuoti. Il batching continuo, introdotto dal sistema Orca (Yu e colleghi, 2022), decide la composizione del batch a ogni passata: una conversazione che finisce esce subito, una richiesta nuova entra al suo posto.

Oggi è il comportamento normale dei motori di inferenza come vLLM, SGLang e TensorRT-LLM. Una variante, il prefill a blocchi (chunked prefill), spezza i prompt lunghi in pezzi da mescolare alle passate di decode, così l'arrivo di un documento enorme non blocca la scrittura degli altri utenti.

PagedAttention: la cache a pagine

Il limite al numero di conversazioni insieme è la memoria della cache KV (key-value, capitolo 3). I primi sistemi riservavano a ogni richiesta uno spazio contiguo per la lunghezza massima possibile, perché non si sa in anticipo quanto sarà lunga la risposta. Kwon e colleghi (2023) hanno misurato che in quei sistemi solo il 20–40% circa della memoria della cache conteneva davvero dati utili: il resto era prenotato e mai usato, o frammentato.

PagedAttention, il cuore di vLLM, applica l'idea della memoria virtuale dei sistemi operativi: la cache è divisa in blocchi di dimensione fissa (16 token per impostazione predefinita) assegnati man mano che servono, anche non contigui, con una tabella che dice dove sta ogni blocco.

Lo spreco scende sotto il 4%, nel batch entrano molte più conversazioni e il throughput, il numero di token prodotti al secondo dal server, nel lavoro originale cresce di 2–4 volte a parità di latenza. I blocchi si possono anche condividere: più richieste con lo stesso prefisso puntano agli stessi blocchi, che è un modo di realizzare il prompt caching del capitolo 4.

Laboratorio: batch, throughput e velocità per utente

valori illustrativi

Aumenta le conversazioni nel batch e confronta la prenotazione per la lunghezza massima con la cache a pagine: guarda quante entrano davvero e come cambiano throughput e velocità di ciascuno.

throughput totale (token/s del server)velocità per utente (token/s)
Nel batch / in coda–
Throughput totale–
Velocità per utente–
Limite–

Modello da 8 miliardi di parametri in bf16 (bfloat16, 2 byte per peso) con cache di Llama 3 8B (128 KiB per token) su una GPU ipotetica da 80 GB con le stesse prestazioni del capitolo 4; 4 GB lasciati alla libreria. Ogni passata dura il massimo tra il tempo per leggere pesi e cache e il tempo per calcolare. Si ignorano il prefill delle richieste nuove, la crescita delle conversazioni durante la generazione e lo spreco residuo della cache a pagine.

Decodifica speculativa: un modello piccolo propone, quello grande verifica

Il batching aumenta i token al secondo del server, non quelli della singola conversazione. Per accelerare la singola risposta si sfrutta la stessa asimmetria in un altro modo: siccome il decode è limitato dalla memoria, una passata del modello grande su 5 token costa quasi quanto una su un token solo. La decodifica speculativa (Leviathan e colleghi, 2023; in parallelo Chen e colleghi, DeepMind) usa un modello bozza piccolo e veloce che propone γ token in sequenza; il modello grande li controlla tutti in una passata sola, come fosse un pezzetto di prefill.

La verifica accetta ogni token proposto x con probabilità:

Calcolo · accettazione di un token

probabilità di accettare x=min(1, p(x) q(x))

dove p è la probabilità del modello grande e q quella della bozza.

Al primo rifiuto si ricampiona da una distribuzione corretta e il resto della bozza si butta; se tutti vengono accettati, la stessa passata regala un token in più. Il risultato notevole è che il testo prodotto ha esattamente la stessa distribuzione che avrebbe dato il modello grande da solo: si guadagna velocità senza perdere qualità.

Il guadagno dipende da quanto spesso la bozza indovina, cioè dal tasso di accettazione α, e varia con il tipo di testo: alto per codice e testo prevedibile, basso per testo creativo. Esistono varianti senza modello separato, che aggiungono al modello grande piccole teste che indovinano più token in avanti (per esempio Medusa).

Laboratorio: quanto accelera la decodifica speculativa

matematica reale

Muovi il tasso di accettazione, il numero di token proposti e il costo della bozza: le barre mostrano l'accelerazione attesa per ogni lunghezza della bozza.

Token per passata del modello grande–
Accelerazione–
γ migliore–

Formule del lavoro di Leviathan e colleghi: token attesi per passata (1 − αγ+1) / (1 − α); accelerazione = token attesi / (γ·c + 1), con c il costo di una passata della bozza in unità del modello grande. Assumono che ogni token sia accettato in modo indipendente con la stessa probabilità α e che verificare γ + 1 token costi come una passata normale, cosa vera solo finché il decode resta limitato dalla memoria: con batch molto grandi il vantaggio cala.

Quantizzazione dei pesi: meno byte da leggere

Se il decode è limitato dai byte letti, dimezzare i byte dei pesi accelera quasi della metà il decode e libera memoria per la cache. La quantizzazione rappresenta i pesi con meno bit: da 16 (bf16, bfloat16) a 8 (int8 o FP8, virgola mobile a 8 bit) o a 4, con un fattore di scala per ogni gruppo di pesi per non perdere l'ordine di grandezza. Metodi come GPTQ (quantizzazione post-addestramento per modelli GPT) e AWQ (Activation-aware Weight Quantization, quantizzazione che tiene conto delle attivazioni) scelgono come arrotondare guardando dati di esempio, per sbagliare meno proprio sui pesi che contano di più.

PrecisioneByte per pesoPesi di un 8BPesi di un 70BQualità, di solito
bf16216 GB140 GBriferimento
int8 / FP818 GB70 GBdifferenze minime nella maggior parte dei test
int40,5 (più le scale)≈ 4–5 GB≈ 35–40 GBperdita piccola ma misurabile, più evidente nei modelli piccoli e nei compiti difficili

Si quantizza anche la cache KV, di solito a 8 bit, con la stessa logica: più conversazioni nella stessa memoria. Prima di adottare una versione quantizzata conviene misurarla sui propri compiti.

Mappa concettuale · servire molti utenti
  • Servire molti utenti
    • si fa con Batching continuo
      • cambia il batch a ogni passata
      • è limitato dalla memoria della cache KV capitolo 3
    • libera memoria con PagedAttention
      • divide la cache in blocchi di dimensione fissa
    • accelera la singola risposta con Decodifica speculativa
      • una bozza propone, il grande verifica
    • riduce i byte con Quantizzazione
      • rappresenta i pesi con meno bit
Batching continuo
Composizione del batch ricalcolata a ogni passata: chi finisce esce, chi arriva entra, senza aspettare il gruppo.
iteration-level scheduling in Orca
PagedAttention
Gestione della cache KV a blocchi di dimensione fissa, allocati su richiesta e anche condivisi tra richieste, come le pagine della memoria virtuale.
vLLM, blocchi da 16 token
Throughput
Token prodotti al secondo dal server, sommando tutte le conversazioni. Cresce con il batch, mentre la velocità di ciascuno cala.
token/s per GPU
Decodifica speculativa
Un modello bozza propone più token, il modello grande li verifica in una passata: stessa distribuzione d'uscita, meno passate.
α = 0,8, γ = 4 → circa 2,8 volte
Quantizzazione
Rappresentare pesi (o cache) con meno bit per numero, accettando un piccolo errore di arrotondamento.
bf16 → int4: un quarto dei byte
6 · Output strutturato e decodifica vincolata

Per avere JSON valido non si chiede al modello di essere bravo: si vietano i token sbagliati

Un'applicazione spesso non vuole prosa ma dati: un oggetto JSON (JavaScript Object Notation) con certi campi, la chiamata a un tool con argomenti validi. Chiederlo nelle istruzioni funziona quasi sempre, e «quasi» è il problema: basta un «Certo! Ecco il JSON:» davanti, una virgola in più o un valore fuori elenco per far fallire il parser. La decodifica vincolata risolve il problema nel punto in cui nasce, la scelta del token.

A ogni passata, prima del campionamento, il server calcola quali token sono compatibili con lo schema dato il testo prodotto fin lì, e porta a meno infinito il logit di tutti gli altri. Dopo la softmax quei token hanno probabilità zero; i token ammessi si dividono la probabilità rimasta, rinormalizzata come nei filtri del capitolo 2. Il modello sceglie liberamente, ma solo tra le continuazioni valide.

Dalla grammatica alla maschera

Lo schema, per esempio un JSON Schema o una grammatica, viene tradotto in un automa: una macchina a stati che sa, in ogni punto, quali caratteri possono venire dopo. Il lavoro difficile è passare dai caratteri ai token, perché un token può contenere più caratteri e attraversare più stati dell'automa. Librerie come Outlines (Willard e Louf, 2023), XGrammar e le grammatiche di llama.cpp precalcolano per ogni stato l'insieme dei token ammessi, così la maschera costa poco a ogni passata.

Le modalità di output strutturato «stretto» delle API (interfacce di programmazione) dei provider e il function calling vincolato si basano su questo meccanismo: è il motivo per cui una chiamata a tool ha argomenti sintatticamente validi, come si vede nella guida da assistente ad agente.

JSON Schema · il ticket del laboratorio
{
  "type": "object",
  "properties": {
    "stato": {
      "enum": ["aperto", "chiuso"]
    },
    "priorita": {
      "type": "integer",
      "minimum": 1, "maximum": 5
    }
  },
  "required": ["stato", "priorita"],
  "additionalProperties": false
}

Laboratorio: la maschera sui logit, token per token

simulazione

Avanza una passata alla volta con e senza vincolo: con il vincolo i token incompatibili con lo schema spariscono e gli altri si dividono la loro probabilità.

Candidati di questa passata

Probabilità inventate e solo quattro o cinque candidati per passata; per semplicità i candidati sono gli stessi nei due modi, mentre un modello vero, dopo un «Certo», continuerebbe con altra prosa. La maschera e la rinormalizzazione sono quelle reali: probabilità zero ai token vietati, divisione per la somma dei rimasti.

Che cosa garantisce e che cosa no

  1. Garantisce la forma. Il risultato rispetta la sintassi e lo schema: campi richiesti, valori di un elenco, tipi. Il parser non fallisce.
  2. Non garantisce il contenuto. «priorita»: 4 è valido anche quando la priorità giusta era 1. Lo schema vieta l'impossibile, non lo sbagliato.
  3. Può peggiorare la risposta. Il vincolo spinge il modello lontano dalle continuazioni che preferiva. Se una risposta richiede ragionamento, conviene lasciargli un campo di testo libero prima del campo con la conclusione: i token si generano nell'ordine dei campi.
  4. Ha un costo di preparazione. Schemi molto grandi o ricorsivi richiedono tempo per costruire l'automa; i provider di solito lo mettono in cache dopo il primo uso e non supportano ogni parola chiave del JSON Schema.
Mappa concettuale · output strutturato
  • Decodifica vincolata
    • parte da Schema o grammatica
      • si traduce in un automa
    • a ogni passata costruisce Maschera sui logit
      • porta a −∞ i token non ammessi
      • poi si rinormalizza come nel capitolo 2
    • garantisce La forma
      • ma non il contenuto
Decodifica vincolata
Generazione in cui, a ogni passata, i logit dei token incompatibili con una grammatica o uno schema vengono azzerati in probabilità prima della scelta.
logit → −∞ per «Certo» quando serve «{»
JSON Schema
Linguaggio standard per descrivere la forma di un documento JSON: tipi, campi obbligatori, valori ammessi.
"enum": ["aperto", "chiuso"]
Riepilogo

Dove agisce ogni tecnica, e che cosa cambia

ConcettoDove agisceChe cosa cambiaChe cosa costa o rischia
Generazione autoregressivail ciclo interoprompt in una passata, risposta in una passata per tokenla risposta non si parallelizza
Temperatura, top-k, top-p, min-pla scelta, dopo i logitquanto è vario o prevedibile il testotroppo alti: incoerenza; troppo bassi: ripetizione
Penalitài logit dei token già usatimeno ripetizionipenalizza anche le ripetizioni necessarie
Cache KV (key-value)attenzione, ogni stratoil decode elabora solo il token nuovomemoria lineare in token e conversazioni
GQA, MQA (attenzione a query raggruppate o a query multiple)architettura dell'attenzionecache più piccola, da alcune a decine di volte rispetto all'attenzione completapiccola perdita di qualità, maggiore con MQA
Prefill contro decodeuso della GPU (scheda acceleratrice)TTFT (tempo al primo token) dal prompt, TPOT (tempo per token) dalla banda di memoriauscita più cara dell'ingresso nei listini
Prompt cachingprefill di un prefisso identicoTTFT e costo dell'ingresso più bassiserve un prefisso stabile; dura pochi minuti
Batching continuo, PagedAttentionil serverpiù conversazioni per passata, throughput più altoogni utente scrive un po' più lento
Decodifica speculativail decode della singola conversazionemeno passate del modello grande, stessa distribuzioneserve una bozza che indovini spesso
Quantizzazionepesi e cache in memoriameno byte da leggere, più spazio per la cacheperdita di qualità crescente sotto gli 8 bit
Decodifica vincolatamaschera sui logitoutput sempre conforme allo schemaforma garantita, contenuto no