liberlume-content-v3
lang: it
title: Viaggio dentro la macchina che risponde: come si muovono i byte nell'inferenza
summary: Che cosa succede, fisicamente, quando un modello linguistico risponde, e che cosa cambia se a rispondere è una macchina sola, in casa? Un percorso dentro i principi di funzionamento, dall'attention fino al mestiere dell'inferenza, con le sue due fasi, la sua organizzazione dei dati, le tecniche per farla costare meno, letto seguendo un filo: dove stanno i byte e quanto costa muoverli. Sullo sfondo un'idea: l'inferenza locale, come scelta e come possibilità, conta già oggi e conterà sempre di più. A tenerla concreta, un motore scritto in C che porta un modello da centinaia di miliardi di parametri a girare in locale: DwarfStar, con cui antirez mostra fin dove ci si può spingere, e che quella strada è percorribile.
btc-anchor: 958989,00000000000000000000e45855edc8644c6749248b727561a338a19637f2c7e9
prev: sha256:3004482ee4659f18ab49275c29f814924754203d338de48460ddc8cd28f907c4
--- body ---
Da qualche mese una parte del mio tempo va a un esercizio preciso: provare a
capire i principi di funzionamento dei motori di inferenza in generale, e in
particolare di quelli realizzati per girare localmente, in linea di principio
in una macchina sola; e a comprendere (per quanto mi è possibile) perché sono
realizzati in un certo modo. Ogni riferimento al codice di antirez non è
puramente casuale. Non sono un programmatore esperto e rispetto al mondo AI
sono poco più che un «curioso», ma lo stesso ho provato a capire, ho provato
a informarmi. E so bene che il viaggio è comunque appena iniziato.
Partire dal motore di inferenza è un punto di osservazione strano ma, credo,
istruttivo: da lì dentro la teoria dei transformer smette di essere un elenco
di sigle e diventa una fila di problemi concreti, quasi tutti dello stesso
tipo: c'è un mucchio enorme di numeri, e bisogna decidere dove tenerli e
quando e come spostarli.
Queste pagine sono, prima di tutto, il mio tentativo di rimettere in fila
(anche con l'aiuto di un LLM, quasi ironico questo aspetto) quel che ho
«accumulato» e provato a capire. Dovevano occuparsi d'inferenza, quella
locale in particolare; ma è stato quasi naturale cominciare dall'inizio, da
come un modello viene addestrato e dai principi del suo funzionamento,
perché senza quelle basi si riesce a dire poco. Il percorso va quindi più
indietro del previsto: prima le basi, poi l'articolo che ha dato il via a
tutto, nel 2017, e da lì le tecniche con cui oggi si fa rispondere un modello
ottimizzando le risorse di chi lo fa girare.
Non un manuale: una mappa con pochi punti di ingresso, scelti perché aprono
il resto. È cresciuta più del previsto, e ho scelto di non sfoltirla: troppe
omissioni finirebbero per non rendere ciò che permette «alla macchina di
rispondere una parola alla volta». Una linea, però, è tracciata. Alcuni dei
passaggi più ostici degli algoritmi coinvolti sono rappresentati ma non
approfonditi, presi per buoni e resi per come li ho capiti; per quei dettagli
il posto giusto restano le fonti specialistiche, e alcune sono in coda.
Il pensiero di fondo, non troppo celato, è che l'inferenza locale, come
scelta e come possibilità, conti già oggi per le aziende, per le comunità e,
per certi aspetti, per le singole persone, e conterà sempre di più: forse
l'ago della bilancia per gli scenari di adozione e di sviluppo. Capire
qualcosa dei meccanismi alla base è utile qualunque sia il ruolo di ciascuno.
Da qui in avanti la voce torna impersonale; ma le domande sono nate lì.
La mappa annunciata comincia dalle sue tappe; ogni titolo è un punto di
ingresso:
- [1. Che cosa impara una macchina](#1-che-cosa-impara-una-macchina)
- [2. L'addestramento: la discesa e le tre fasi](#2-laddestramento-la-discesa-e-le-tre-fasi)
- [3. Interludio: il pappagallo e la biblioteca](#3-interludio-il-pappagallo-e-la-biblioteca)
- [4. 2017: l'attention, ovvero la gestione del contesto](#4-2017-lattention-ovvero-la-gestione-del-contesto)
- [5. Il panorama dei modelli](#5-il-panorama-dei-modelli)
- [6. L'inferenza: un mestiere fatto di due lavori](#6-linferenza-un-mestiere-fatto-di-due-lavori)
- [7. Gli appunti sul tavolo: la KV cache](#7-gli-appunti-sul-tavolo-la-kv-cache)
- [8. Le ottimizzazioni: sei risposte alla stessa domanda](#8-le-ottimizzazioni-sei-risposte-alla-stessa-domanda)
- [9. Dentro il codice: quattro zoom](#9-dentro-il-codice-quattro-zoom)
- [10. Che cosa ci si porta dal viaggio](#10-che-cosa-ci-si-porta-dal-viaggio)
- [11. Le domande che contano](#11-le-domande-che-contano)
- [Riferimenti](#riferimenti)
## 1. Che cosa impara una macchina
C'è un modo utile di arrivare agli LLM partendo da zero, e passa per tre
restringimenti successivi. Il machine learning è l'idea di non scrivere le regole a
mano: si sceglie una funzione con tanti parametri liberi e si lascia che siano
gli esempi a fissarli. Una rete neurale è una famiglia particolare di queste
funzioni, fatta di strati che si passano numeri: ogni strato combina i valori
in ingresso con i propri parametri e passa il risultato al successivo. Un
modello linguistico è una rete addestrata su un compito solo, all'apparenza
modesto: dato un testo, assegnare una probabilità a ogni possibile seguito
immediato. I tre restringimenti però vanno riempiti, perché ciascuno nasconde
una scelta, e le scelte spiegano più delle definizioni.
**Da dove arriva la correzione.** La tassonomia classica del machine learning
distingue tre modi di imparare, a seconda di chi dice alla macchina che sta
sbagliando. Nell'apprendimento **supervisionato** ogni esempio porta attaccata
la risposta giusta (questa foto è un gatto, questa email è spam): si impara
una corrispondenza. In quello **non supervisionato** le etichette non ci sono,
e si cerca la struttura che i dati hanno già per conto loro: gruppi,
regolarità, modi di comprimere. In quello **per rinforzo** non c'è neanche la
risposta giusta: c'è un ambiente, ci sono azioni, e ogni tanto arriva un
premio; si impara la strategia che lo massimizza (è la famiglia dei programmi
che giocano a Go). Gli LLM nascono da una variante economicamente decisiva
della prima famiglia, l'**auto-supervisione** (self-supervised learning): il
testo fa da etichetta a sé stesso, perché il seguito da indovinare, nel testo,
c'è già. Nessun etichettatore umano, e ogni testo esistente diventa materiale
di addestramento: è la scelta che sblocca la scala.
Il seguito da indovinare, peraltro, non è una parola intera: è un **token**,
l'unità che il modello vede davvero. Il testo viene spezzato da un tokenizer
in pezzi presi da un vocabolario di decine di migliaia di voci; una parola
comune è un token, una parola rara o un nome proprio si spezza in più token.
**Perché non le parole.** Sembra una complicazione gratuita, visto che le
parole sono già lì; e invece è una scelta stretta fra vincoli molto
concreti, che vale la pena vedere, perché tornano tutti più avanti.
Il vincolo duro è che il vocabolario dev'essere **chiuso e finito**, e
deciso prima di vedere qualunque testo: l'uscita del modello è una lista con
una casella per voce, e una lista si dimensiona in anticipo. Le parole,
però, sono un insieme aperto: nomi propri, neologismi, sigle, refusi, lingue
in cui le parole si compongono a piacere. A lavorare con le parole si finisce
sempre per tenere in lista una casella «voce sconosciuta», che davanti a un
testo vero è una resa. Con i pezzi sotto-parola non serve: poche decine di
migliaia di voci bastano a scrivere qualunque cosa, componendo. Nella
variante che spezza fino ai byte non esiste per costruzione nulla di
inesprimibile, nemmeno una lingua mai vista o un pezzo di file binario
incollato per sbaglio.
Stabilito questo, resta da decidere quanto grossi debbano essere i pezzi. Le
due scelte estreme si smentiscono a vicenda, e ciascuna rompe quello che
l'altra sistema. Tagliare fine, un carattere per token, dà un vocabolario
minuscolo e nessuna voce fuori lista; in cambio allunga smisuratamente le
sequenze, e la lunghezza è la grandezza più cara di questo mestiere: si
vedrà più avanti che il costo cresce col quadrato della lunghezza.
Tagliare grosso, una parola per token, tiene le sequenze corte ma gonfia il
vocabolario; e un vocabolario grande si paga, perché il modello dedica
parametri a ciascuna voce, per riconoscerla quando entra e per poterla
proporre quando esce. Il conto cresce dunque insieme alle voci: un milione
di voci vorrebbe dire miliardi di parametri spesi soltanto per quello. Fra i
due estremi c'è tutto lo spazio dei pezzi sotto-parola, ed è lì che l'uso si
è assestato.
C'è infine una ragione statistica, e riguarda ciò che si riesce a imparare.
Una voce incontrata tre volte in tutto l'addestramento resta stimata male,
comunque la si rappresenti; spezzando, i pezzi frequenti ricevono moltissimi
esempi, e la morfologia si condivide fra parole diverse (un prefisso, un
suffisso, una radice), così che una parola mai vista non sia un muro ma una
composizione di pezzi noti. Il vocabolario, del resto, non lo progetta
nessuno: si ricava dai dati, e il criterio è la compressione. La ricetta più
diffusa parte dai singoli caratteri e fonde ripetutamente la coppia adiacente
più frequente, finché non si raggiunge la taglia voluta; è un algoritmo di
compressione degli anni Novanta, riadattato al testo. I pezzi che ne escono
non coincidono con le unità di senso che un linguista riconoscerebbe, non
rispettano la grammatica di nessuna lingua e non devono farlo: sono i tagli
che rendono il testo più corto, e basta.
I vantaggi hanno un prezzo, e lo paga l'intuizione. Da qui la prima coppia
da non confondere: il modello non ragiona a parole, ragiona a token, e molte
stranezze (il conteggio delle lettere, i prezzi «per token», la velocità di
generazione) si capiscono solo da lì.
**Da numeri a punti nello spazio.** Un token, appena uscito dal tokenizer, è
solo un indice di vocabolario, e un indice non ha significato: la voce 4.812
non è più simile alla 4.813 che alla 40.000. La prima mossa della rete è
quindi trasformare ogni token in un **embedding**: una lista di migliaia di
numeri (un vettore), cioè un punto in uno spazio a molte dimensioni. È facile
immaginarsi un dizionario compilato da qualcuno, e non esiste: la tabella che
assegna a ogni voce del vocabolario il suo punto è essa stessa un blocco di
parametri della rete, punti a caso all'inizio, sistemati dall'addestramento
come tutto il resto. L'idea ha una storia propria. Prima dei transformer si
addestravano piccole reti solo a prevedere le parole vicine, e il
sottoprodotto si rivelò più prezioso del compito: le
rappresentazioni vettoriali che la rete si era costruita per riuscirci
(word2vec, 2013). Ed è qui che il significato comincia ad avere una
geometria: nella rappresentazione giusta, parole usate in modi simili
finiscono vicine, e le **direzioni** catturano proprietà. L'esempio
classico: lo spostamento che porta da «re» a «regina» somiglia molto a quello
che porta da «uomo» a «donna»; in simboli, v(re) − v(uomo) + v(donna) ≈
v(regina), ed è un'aritmetica che sugli embedding si può verificare davvero.
Sembra un miracolo e non lo è: l'addestramento premia le rappresentazioni che comprimono,
e se migliaia di contesti trattano «re» e «regina» con la stessa impalcatura di frasi,
salvo il genere, la soluzione più economica è dare ai due token quasi lo stesso punto,
separato da una stessa direzione. Il significato, per come lo vede la rete, è l'uso;
la geometria è solo il modo più compatto di scriverlo. È la prima apparizione
di un'idea che tornerà: negli spazi di rappresentazione giusti il significato
diventa geometria, e la geometria si può misurare, confrontare, perfino
spingere.
L'ingresso del modello vive dunque in questo spazio, un punto per token; e
ci vive anche l'uscita, si vedrà tra poco: il punto a cui la rete arriva in
cima viene confrontato con i punti delle voci del vocabolario, ed è da quel
confronto che nascono le probabilità. Da un capo all'altro, insomma, il
modello lavora su coordinate; solo all'ultimo un confronto trasforma la
geometria in una lista di percentuali.
**La forma della funzione.** Ogni strato della rete, quando viene attraversato,
fa due cose sole. La prima è lineare: pesare e sommare, i valori in ingresso
moltiplicati per i parametri (in blocco è una moltiplicazione di matrici,
l'operazione per cui le GPU sono nate, e che tornerà di continuo).
Vale la pena vederla scritta una volta, in miniatura, perché è il mattone
di tutto il resto:
```
[ y1 ] [ w11 w12 ] [ x1 ] y1 = w11·x1 + w12·x2
[ ] = [ ] · [ ] →
[ y2 ] [ w21 w22 ] [ x2 ] y2 = w21·x1 + w22·x2
```
due ingressi, due uscite, quattro pesi: ogni uscita è una miscela pesata di
tutti gli ingressi, e i pesi w sono i parametri che l'addestramento
fisserà. Negli strati veri x ha migliaia di componenti e W miliardi di
caselle, ma l'operazione è esattamente questa. La seconda è una **non
linearità**, nel gergo la funzione di attivazione: una funzione fissa,
volutamente banale, applicata numero per numero. Le due cose stanno in fila,
non fianco a fianco: prima la pesatura, poi la funzione sul risultato.
Nella miniatura di sopra l'uscita vera dello strato non è y₁ ma
f(y₁) = f(w₁₁·x₁ + w₁₂·x₂), e così per ogni componente: la somma pesata è
l'argomento della non linearità, e ciò che ne esce va allo strato
successivo. Strato su strato si forma una pila, e conviene fissare subito
l'immagine con cui il testo la guarderà sempre: un edificio, uno strato per
piano. I token entrano al piano terra, ogni piano lavora sull'uscita di
quello sotto, e attraversare la rete è salire fino in cima. Le funzioni che
si incontrano nei nomi meritano una definizione ciascuna, perché sono tutte
più semplici della loro fama:
- **ReLU** (rectified linear unit): se il numero è positivo passa invariato,
se è negativo diventa zero. Tutto qui: max(0, x). Un interruttore, al
costo di un confronto.
- **GELU e SiLU**: la stessa idea con il gomito arrotondato. Invece di
azzerare i negativi di colpo, li fanno sfumare dolcemente verso lo zero
(la SiLU, per dire, è x·σ(x), con σ la sigmoide che scivola da 0 a 1); la
transizione liscia aiuta le derivate a non spegnersi.
- **softmax**: un attrezzo a parte, che dentro i piani non compare: prende
una lista di punteggi qualunque e la trasforma in percentuali che sommano
a 1, amplificando le differenze in modo esponenziale (chi ha il punteggio
più alto prende molto più della sua parte proporzionale). In simboli:
softmax(xᵢ) = exp(xᵢ) / Σⱼ exp(xⱼ). Serve ovunque si debbano trasformare
punteggi in probabilità, e i posti sono due: in cima alla pila, una volta
sola, per scegliere il token successivo; e, si vedrà, dentro l'attention.
Sembra un reparto di dettagli ed è invece il punto di tutto: comporre
funzioni lineari dà ancora una funzione lineare, quindi cento strati di sole
pesature equivarrebbero a un unico strato, e la profondità sarebbe
un'illusione contabile. È la non linearità a rendere vera la profondità, e
con lei la capacità di rappresentare ciò che proporzionale non è: soglie,
eccezioni, interazioni tra parole lontane. Quale non linearità usare, invece,
è una scelta quasi prosaica, con tre criteri: spezzare la linearità, avere
derivate che non si spengono (perché conti si vede nell'addestramento, tra un
attimo), costare quasi niente, visto che verrà valutata un numero astronomico
di volte. Dentro i piani, dunque, lavorano l'interruttore e il gomito,
numero per numero, a ogni piano; la softmax non è la non linearità di
nessun piano. Compare in cima alla pila, dove chiude il giro, e qui va
detto da dove arrivano i punteggi che trasforma, perché c'è un salto che
di solito si sorvola: l'ultimo piano non consegna un punteggio per voce,
consegna ancora un punto nello spazio delle rappresentazioni, migliaia di
coordinate come quelle dell'embedding. I punteggi nascono da un confronto,
il prodotto scalare tra quel punto d'arrivo e il vettore di ciascuna voce
del vocabolario: quanto il punto d'arrivo punta nella direzione di ogni
voce. E in molti modelli la tabella usata per il confronto è la stessa
dell'ingresso, riusata al contrario (nel gergo, weight tying): la mappa che
al piano terra porta le voci nello spazio, in cima misura la vicinanza del
punto d'arrivo a ognuna. Il resto lo fa la softmax, da quei punteggi, uno
per voce, alle percentuali che sommano a 1; e tornare dal punto al token non
richiede più geometria: si sceglie una casella della lista, e la posizione
della casella è già il token, nessuna coordinata da invertire.
È lì che la rete diventa la funzione promessa all'inizio, testo dentro,
distribuzione di probabilità fuori. Nel
singolo passaggio non c'è nulla di misterioso, moltiplicazioni e somme più
un interruttore: la capacità sta nell'accumulo, decine di strati e miliardi
di parametri.
Le «due cose sole» di poco fa erano due per onestà aritmetica: è da quelle
che esce il risultato. Accanto a loro lavora però un terzo ingrediente,
minore per conto proprio e indispensabile per il tutto: una
**normalizzazione**, che prima della pesatura riporta i numeri di ciascun
token su una scala comune. Non cambia ciò che lo strato calcola, non mette
in comunicazione token diversi e restituisce tanti numeri quanti ne ha
ricevuti; e i parametri che porta con sé sono un'inezia rispetto a quelli
delle matrici. Serve a impedire che, salendo di piano, i valori si gonfino o
si spengano, e perché sia indispensabile si vedrà guardando l'addestramento:
è la prima delle due invenzioni che rendono governabili le pile alte.
E i numeri che uno strato passa al successivo hanno un nome
che servirà più avanti: le **attivazioni**, la parte viva del calcolo
accanto ai parametri fissi.
**Quanto entra e quanto esce.** Dire che il modello prende un testo e
restituisce probabilità non basta: serve sapere con quali forme, perché è
dalle forme che si capisce che cosa può variare e che cosa no. Il testo,
spezzato dal tokenizer, è una fila di token, e al piano terra ciascuno viene
sostituito dal proprio embedding, il punto nello spazio a molte dimensioni
visto sopra: una lista di qualche migliaio di numeri. Per ragionare sulle
forme servono tre nomi, e conviene prenderli adesso: **T** è quanti token
stanno in fila, **d** quanti numeri compongono il punto di un token, **V**
quante voci ha il vocabolario. L'ingresso non è dunque un vettore, ma una
pila di vettori: T righe da d numeri ciascuna.
Delle due dimensioni dell'ingresso, una sola varia. La d è fissata
dall'architettura e non cambia mai: né da una richiesta all'altra, né
salendo di piano, perché ogni piano riceve T righe da d e ne restituisce
altrettante. Non è un vezzo: si vedrà che ogni piano somma il proprio
risultato a ciò che gli era entrato, e due cose si sommano solo se hanno la
stessa forma. La T invece è la lunghezza del testo in quel momento, e cambia
di continuo.
Il che solleva una domanda legittima. Una rete a taglia fissa, se le si
cambia la lunghezza dell'ingresso, non torna più: le matrici non combaciano.
Come fa questa a digerire con gli stessi parametri un prompt di dieci token
e uno di diecimila? La risposta sta tutta in un fatto: **nessuna matrice di
parametri ha un lato lungo T**.
```
ingresso T × d T token, d numeri per token
ogni piano T × d → T × d la forma non cambia salendo
in cima T × d → T × V confronto con le voci del vocabolario
(in inferenza serve solo l'ultima riga)
i parametri: tabella degli embedding V × d
dentro ogni piano d × d, oppure d × (multiplo di d)
confronto finale V × d (spesso la stessa tabella)
```
Le matrici, come si vede, sono fatte di d e di V, mai di T. E il motivo si
legge nelle due cose che un piano sa fare. Ciò che lavora **su un token per
volta** applica gli stessi pesi a ciascuna delle T righe, e di quante siano
non gli importa: sono T ripetizioni dello stesso conto. Ciò che invece
**mescola i token fra loro** produce sì una tabella grande quanto T per T,
ma quella tabella è calcolata sul momento, non è fatta di parametri: nasce
dai numeri di quel testo e muore con lui. I parametri, insomma, sono
indifferenti alla lunghezza; a crescere con T sono il lavoro da fare e la
memoria dei valori intermedi, che è poi tutta la storia dei costi di queste
pagine. In una pila classica un piano pesa all'incirca dieci volte d²
parametri, e i miliardi di cui si parla sono quel numero moltiplicato per i
piani: una questione di larghezza e di altezza, mai di lunghezza del testo.
Vale la pena vedere le due moltiplicazioni-cardine con le righe e le colonne
al loro posto, perché è lì che il flusso del calcolo si fa concreto. La prima
è un piano qualunque; ogni riga è un token, e la si segue da sola:
```
ingresso pesi del piano uscita
┌── d ──┐ ┌─── d ───┐ ┌── d ──┐
t1│ · · · │ │ \ │ t1│ o o o │
t2│ · · · │ · │ \ W │ = t2│ # # # │
t3│ · · · │ │ \ │ t3│ o o o │
└───────┘ └─────────┘ └───────┘
T × d d × d T × d
la riga t2 dell'uscita nasce SOLO dalla riga t2 dell'ingresso;
ogni # è una somma pesata come nella miniatura di poco fa (la riga
di t2 per una colonna di W). Le righe non si parlano fra loro: farle
parlare è il compito della sezione 4.
```
La seconda è la mossa in cima alla pila, e usa una riga sola: l'ultima, quella
che porta la previsione del token successivo. Va a incontrare la tabella del
vocabolario e ne esce una lista lunga quanto il vocabolario, un punteggio per
voce:
```
ultima riga tabella del vocabolario punteggi
dell'uscita (una colonna per voce)
┌── d ──┐ ┌────── V ──────┐
│▓ ▓ ▓ ▓│ · │ v1 v2 ... vV │ = │p1 p2 … pV│ → softmax
└───────┘ └───────────────┘ └──────────┘
1 × d d × V 1 × V
un punteggio per voce: quanto il punto d'arrivo «punta» verso
ognuna. La tabella è la V × d dell'ingresso, riusata al contrario
(il weight tying di poco fa). In inferenza serve solo questa riga;
le altre T−1 prevedono token che nel testo ci sono già, e non si
calcolano.
```
Una sola porta potrebbe lasciare entrare la lunghezza fra i parametri, e
riguarda un dettaglio di cui non si è ancora parlato: al modello, in qualche
modo, va detto anche in che ordine stanno i token, perché una fila di punti,
da sola, non porta scritto quale viene prima. Se quell'informazione viene
messa in una tabella con una riga per ciascuna posizione possibile, allora un
lato lungo T fra i parametri c'è, e con lui un tetto rigido scritto nei pesi.
È una delle ragioni per cui le ricette più recenti l'ordine preferiscono
calcolarlo, e nella sezione 4 si vedrà come. Un tetto, in ogni caso, resta:
è la **finestra di contesto**, il T massimo che un modello dichiara di
reggere, e non è un limite di forma ma di costo e di addestramento.
Resta l'uscita, e qui il conto va fatto per intero, perché il testo finora
lo ha sottinteso. In cima non esce una lista di V percentuali: ne escono
**T**, una per ogni posizione, ciascuna lunga quanto il vocabolario. Il
modello, cioè, non prevede soltanto che cosa venga dopo l'ultimo token:
prevede, in un colpo solo, che cosa venga dopo ognuno dei token che ha
davanti. In inferenza ne serve una sola, l'ultima: le altre prevedono token
che nel testo ci sono già, e i motori si guardano bene dal calcolarle. In
addestramento si usano tutte, ed è ciò che rende sostenibile far leggere a
una macchina migliaia di miliardi di token.
Il primo giro, allora, non parte da vuoto: i token in ingresso sono quelli
del prompt, e T è la sua lunghezza. Poi il testo si allunga di una riga per
volta, e T con lui. Se davvero non ci fosse nulla da cui partire, si semina
con un token speciale d'inizio: T vale 1, e mai 0.
**Che cosa si fa scendere.** «Imparare» deve diventare un numero, altrimenti
non si ottimizza: una **funzione di perdita** (la loss, e così viene
chiamata quasi ovunque), che misura quanto la previsione è lontana dal vero.
La scena della misura è concreta: si prende un testo vero, lo si percorre
posizione per posizione, e a ognuna si chiede al modello la sua previsione.
Qui c'è un malinteso da sgombrare subito: il modello non produce un token
secco da mettere accanto a quello vero per dire giusto o sbagliato. La sua
previsione, **per ciascuna posizione**, è un vettore, una lista. La
dimensione di questa lista è quella dell'intero vocabolario: un elemento per
ogni voce (token) possibile, e in ogni elemento una percentuale, con le
percentuali che sommano a 1. Di liste, quindi, ne escono T, una per
posizione, e conviene guardarne una sola alla volta. Il token vero, che nel
testo di addestramento c'è già, durante questa fase serve solo a stabilire
**quale casella andare a leggere** tra quelle del vettore in uscita a quella
posizione:
```
testo: «il gatto dorme sul » token vero, dal testo: «divano»
uscita del modello:
divano 0,32 ← si legge solo questa: p(token vero) = 0,32
tappeto 0,25
letto 0,18
… (tutte le altre voci del vocabolario; la somma fa 1) …
```
Nessun confronto diretto tra due token, dunque: una lettura. Il numero trovato
nella casella in corrispondenza della posizione del token nel vocabolario dice
quanta fiducia (probabilità) il modello attribuiva a che la continuazione del testo
si concretizzasse proprio con quel token.
Quel numero, però, così com'è non serve ancora: la perdita deve essere **una
sola** per tutto il testo, e le probabilità delle singole posizioni non si
sommano, si moltiplicano. La probabilità che il modello assegna a un testo
intero è infatti il prodotto delle probabilità che assegna a ogni sua
posizione, una dopo l'altra: migliaia di numeri minori di 1 moltiplicati fra
loro, un risultato che precipita sotto qualunque soglia rappresentabile. Il
logaritmo serve esattamente a questo, e non è un ornamento: trasforma quel
prodotto in una somma. Cambiato di segno, per averlo positivo, dà la
grandezza su cui si lavora davvero, la **sorpresa** di una singola posizione:
```
sorpresa = −log p(token vero)
```
Vale zero quando la previsione era certa e giusta (p = 1), e cresce senza
limite man mano che il modello sbagliava di più. La perdita è la media di queste
sorprese su tutte le posizioni, ed è la scelta canonica per i modelli
linguistici: nel gergo, la cross-entropy. Conviene vederla al lavoro su un
testo intero, una riga per posizione. Sono passi diversi della stessa
lettura, non alternative dello stesso passo:
```
testo di addestramento: «il gatto dorme sul divano»
pos. il modello ha davanti token vero p(token vero) sorpresa
1 «il » gatto 0,05 3,0
2 «il gatto » dorme 0,20 1,6
3 «il gatto dorme » sul 0,45 0,8
4 «il gatto dorme sul » divano 0,32 1,1
────────
perdita = media = 1,6
```
Ogni riga è una scena come quella di prima: tutto il vocabolario in uscita,
una sola casella letta. La perdita è la media della colonna di destra, e
niente altro. Si legge anche, in quella colonna, il carattere della
cross-entropy: punisce in modo sproporzionato gli errori sicuri di sé. Una
posizione in cui il modello dava al token vero lo 0,1% vale da sola 6,9,
quanto una settantina di posizioni indovinate al 90%. È una proprietà
voluta, non un effetto collaterale: la perdita deve premiare la
**calibrazione** oltre che l'azzeccarci, e un modello che dà il 60% al token
giusto distribuendo sensatamente il resto vale più di uno che spara certezze
e ogni tanto sbaglia di grosso. L'altro requisito è meno visibile ma altrettanto
vincolante: la perdita dev'essere derivabile, perché tutto
l'addestramento, come si vedrà, è fatto di derivate.
Fissata la meccanica, si possono guardare due cose che erano già lì. La
prima: questa sorpresa è quella di Shannon, la stessa misura di informazione
del [diavoletto di Maxwell](/it/diavoletto-di-maxwell/): non una somiglianza,
la stessa grandezza, e un modello linguistico si addestra minimizzandola. La
seconda dice che cosa significhi, in fondo, «imparare il linguaggio»:
siccome la sorpresa è il logaritmo della probabilità cambiato di segno,
minimizzare la sorpresa media è la stessa identica operazione che
massimizzare la probabilità che il modello assegna al testo reale. Il
compito, riscritto senza gergo: rendere il mondo scritto il meno sorprendente
possibile.
**Con che numeri si misura.** Le scelte sopra definiscono il sistema; per
descriverlo e confrontarlo bastano poche grandezze. Tre lo definiscono:
quanti **parametri** ha, quanti **dati** ha visto (misurati in token), quanto
**calcolo** è servito ad addestrarlo. Non sono indipendenti: una regolarità
empirica ormai robusta (le scaling laws) dice che la qualità migliora in modo
prevedibile quando le tre crescono insieme, ed è la scommessa su cui si è
costruita l'ultima decade di modelli sempre più grandi. Per giudicare,
invece, nessuna delle tre basta: si misura la perdita su testo **mai visto**
in addestramento, perché una funzione con miliardi di parametri la tentazione
di imparare a memoria ce l'ha tutta (overfitting), e generalizzare è proprio
ciò che la memoria non dà. La stessa perdita su testo nuovo ha una traduzione
più leggibile, la **perplexity**: tra quante alternative equiprobabili il
modello sta esitando, in media, a ogni token (perplexity 8, esita tra otto;
più bassa, meglio). La traduzione, del resto, è letterale: perplexity =
exp(loss), l'esponenziale della perdita media, cioè il logaritmo di poco fa
disfatto. Sulla frase del gatto, perdita 1,6 vuol dire perplexity 5: come se
a ogni parola il modello stesse scegliendo, alla cieca, fra cinque
possibilità equivalenti. E siccome predire bene il
testo non è ancora «fare le cose», sopra tutto questo stanno i **benchmark**,
batterie di prove condivise (domande, problemi, codice) con il loro pregio
(rendono confrontabile ciò che non lo sarebbe) e le loro malattie note
(finire dentro i dati di addestramento, e modelli allenati al test più che al mestiere).
Il confronto onesto dichiara le grandezze e le misure insieme: i parametri da soli sono
una cilindrata, non una velocità.
Generare testo, allora, è chiamare questa funzione in un ciclo: le si dà il
testo finora, sceglie il token successivo, lo si accoda, si ricomincia. Con
le forme di poco fa accanto, il giro si legge per intero:
```
giro 1 testo: «il gatto dorme sul » T = 4 → riga 4 → «divano»
giro 2 testo: «il gatto dorme sul divano» T = 5 → riga 5 → «,»
giro 3 testo: «il gatto dorme sul divano,» T = 6 → riga 6 → « e»
```
Conviene guardare bene che cosa passa da un giro all'altro, perché è meno di
quanto si immagini: passa **solo il testo**. Il token appena scelto viene
accodato e al giro dopo rientra dal piano terra come tutti gli altri, un
indice di vocabolario che diventa un embedding, indistinguibile da quelli del
prompt; non resta traccia del giro precedente, nessun risultato intermedio,
nessuno stato nascosto. Il modello, ogni volta, rilegge tutto da capo. È uno
spreco evidente, e sarà il primo posto in cui l'inferenza andrà a
risparmiare.
Vale la pena fermarsi anche sulla parola «sceglie», che nasconde un
passaggio. La rete non sceglie: la rete finisce con la distribuzione, e lì il
suo lavoro è concluso. Prendere una casella è un passo separato, applicato
da fuori, con una sua politica: si può prendere sempre la voce con la
percentuale più alta, oppure estrarre a sorte rispettando le percentuali,
lasciando che ogni tanto passi un candidato meno ovvio. È il motivo per cui
la stessa domanda non dà due volte la stessa risposta. Il dettaglio di come
si estrae ha un mestiere suo, che qui non serve aprire: non muove byte, non
occupa memoria e costa una frazione trascurabile del giro. È il solo pezzo
della filiera in cui il costo non abita.
Tutta la potenza e tutto il costo degli LLM stanno invece dentro questo
ciclo. È qui che
la parola «generativa» trova il suo senso preciso, spogliata del marketing:
un classificatore produce un'etichetta (questa foto è un gatto, questa email
è spam) e lì si ferma; un sistema generativo impara la distribuzione dei
dati stessi, e da una distribuzione si può estrarre qualcosa di nuovo, che
non era scritto da nessuna parte. Un LLM è il caso più letterale: la sua
uscita che conta è una distribuzione di probabilità sul prossimo token, e
generare è estrarne uno, accodarlo, ripetere; vale per il testo come per le immagini o
l'audio, cambia lo spazio, non l'idea. Un modello linguistico è una funzione
che, dato un testo, assegna una probabilità a ogni possibile pezzettino di
parola successivo: il resto del testo è la storia di quanto costa valutarla,
miliardi di volte al giorno.
La mappa di tutto il pezzo. La funzione al centro è sempre la stessa, con i parametri fissati una volta per tutte; è il testo ad allungarsi, un token per giro. Tutta la potenza e tutto il costo degli LLM abitano dentro questo ciclo: il resto del testo racconta quanto costa un giro, e come lo si fa costare meno.
## 2. L'addestramento: la discesa e le tre fasi
Resta da dire come scendono, i parametri, verso la perdita minima. Prima,
però, il legame che regge tutto e che rischia di passare inosservato: la
probabilità sul token vero non è un giudizio dato da fuori, è un'uscita
della funzione stessa, quella casella della distribuzione che la softmax
produce in cima alla pila. Fissato il testo, la casella dipende solo dai
parametri; e conviene, da qui in poi, vedere ogni parametro come una
**manopola**: l'effetto di girarne una risale l'edificio, piano dopo piano,
fino alle percentuali in cima. La catena completa:
```
giro una manopola → si muovono le attivazioni dei piani sopra
→ si muovono i punteggi in cima alla pila
→ la softmax ridistribuisce le percentuali
→ p(token vero) sale o scende
→ la sorpresa scende o sale
```
Il token vero, in tutto questo, non si tocca: sta nel testo, e continua a
fare il suo unico mestiere, indicare la casella da leggere. A muoversi sono
i numeri nelle caselle; e siccome le percentuali sommano a 1, alzare la
casella del token vero significa per forza togliere probabilità alle altre.
Imparare, visto da qui, è travasare probabilità verso le caselle giuste, un
pelo di manopola alla volta.
A testo fissato, quindi, la perdita è una funzione dei soli parametri, e la
domanda «di quanto si muove la sorpresa se giro questa manopola di un
pelo?» è ben posta per ciascuna delle miliardi di manopole. La risposta ha
un nome, la **derivata parziale**, e sotto il nome c'è un rapporto fra due
variazioni, che si può leggere in numeri. Si prenda la miniatura della
sezione 1, con gli ingressi fissati e, poniamo, una perdita che al momento
vale 2,000; si sposti il solo w₁₁ di un centesimo, lasciando ferme tutte le
altre manopole, e si rifaccia il conto:
```
w₁₁ = 0,500 → perdita = 2,000
w₁₁ = 0,510 → perdita = 2,003 variazione: +0,003 per +0,010
derivata parziale della perdita rispetto a w₁₁ = 0,003 / 0,010 = 0,3
```
Quanta perdita per quanto giro: è tutto lì. Il valore dice quanto quella
manopola conta rispetto alle altre, e il segno dice il verso: positivo
significa «alzandola, la perdita sale», quindi per farla scendere quella
manopola va abbassata. «Parziale» non aggiunge mistero: vuol dire soltanto
che se ne muove una alla volta, tenendo ferme tutte le altre. Va detto
subito che nessuno calcola così: spostare a turno miliardi di manopole e
rifare ogni volta il conto sarebbe la definizione stessa dell'impossibile.
Ma la grandezza cercata è esattamente questa, e conviene tenerla davanti
mentre si guarda la procedura vera, che ottiene gli stessi numeri per una
strada incomparabilmente più corta.
La miniatura dice anche da che cosa dipendono: girare w₁₁ muove y₁ in
proporzione a x₁, l'ingresso che quel peso moltiplica, quindi un peso al
lavoro su un ingresso grande sposta l'uscita più di uno al lavoro su quasi
zero, un dettaglio che tra un attimo diventerà la spartizione delle colpe.
Il meccanismo della discesa, a questo punto, ha nomi intimidatori e un'idea
semplice, e conviene vederlo come un giro di tre passi, ripetuto miliardi di
volte:
1. **Andata: la previsione.** Si mostra alla rete un blocco di testo; per
ogni posizione la rete produce la sua distribuzione sul token
successivo, e la funzione di perdita legge la casella del token che nel
testo c'è davvero: la sorpresa media su tutto il blocco è la perdita.
2. **Ritorno: la backpropagation**, in italiano retropropagazione. Per
ciascuno dei miliardi di parametri serve proprio quel rapporto, la
derivata parziale di poco fa. Uno per volta è escluso: sarebbero
miliardi di andate per un solo passo. La backpropagation li ottiene
**tutti insieme**, con un'unica discesa dalla cima al piano terra, e il
suo conto è la notizia buona dell'intera procedura: quei miliardi di
numeri costano all'incirca quanto due andate. L'insieme di tutte queste
derivate, un numero per manopola e quindi una lista lunga quanto il
modello intero, ha un nome che spiega il passo successivo: il
**gradiente**, la direzione, nello spazio dei parametri, in cui la
perdita sale più ripida.
3. **Passo: la discesa del gradiente.** Se il gradiente indica dove la
perdita sale, ci si muove dalla parte opposta: ogni manopola gira di un
passetto contro la propria colpa. L'ampiezza del passetto (il learning
rate) è il compromesso permanente: passi troppo lunghi scavalcano la
valle, passi troppo corti non ci arrivano mai, e ogni passo calpesta un
po' di ciò che si era imparato prima. In simboli, tutto il passo è:
θ ← θ − η·∇L (θ i parametri, η il learning rate, ∇L il gradiente della
perdita); l'intero addestramento è questa riga, ripetuta.
L'edificio della pila e i due sensi di marcia. Si sale con l'andata: i token entrano al piano terra, ogni piano lavora sull'uscita di quello sotto, e in cima la softmax consegna le percentuali, dove la sorpresa si legge su una casella sola. Si scende col ritorno: la colpa parte dalla cima e a ogni piano viene riscalata da un fattore; quanto contino quei fattori lo dicono i numeri subito sotto.
Come faccia la backpropagation a ottenere tutte quelle derivate in un colpo
solo merita di essere visto, perché è anche la spiegazione di due o tre cose
che altrimenti sembrano capricci.
Una manopola dei piani bassi non tocca la perdita direttamente: la tocca
attraverso tutto ciò che le sta sopra. Muove le attivazioni del suo piano,
che muovono quelle del piano successivo, e così via fino alla casella in
cima. Il suo effetto complessivo è il **prodotto** degli effetti locali
incontrati lungo la strada, e per un edificio di tre piani sta in una riga:
```
∂L/∂w = ∂L/∂a₃ · ∂a₃/∂a₂ · ∂a₂/∂w
quanto la quanto la quanto il quanto il
perdita perdita piano 3 piano 2
sente la sente il sente il sente la
manopola piano 3 piano 2 manopola
```
È la regola della catena, e va letta da destra a sinistra come un viaggio:
la manopola muove il suo piano, il suo piano muove quello sopra, quello
sopra muove la perdita. Il punto che fa funzionare tutto è che **ogni
fattore è locale**: riguarda un piano soltanto, e per calcolarlo basta
sapere che cosa quel piano ha appena fatto nell'andata. Ecco perché il conto
si può fare all'indietro una volta sola. Si parte dalla cima con il primo
fattore; scendendo, a ogni piano attraversato lo si moltiplica per il
fattore di quel piano. Ciò che arriva a un piano qualunque è già il prodotto
di tutti quelli di sopra: a quel piano basta usarlo per i propri pesi e
passarlo, riscalato, a quello sotto. Nessun percorso viene mai ripetuto, ed
è da qui che viene il costo di due sole andate.
Il gergo storico chiama questa contabilità **credit assignment**, assegnare
a ciascun peso la sua parte di merito e di colpa, e «colpa» è l'immagine
giusta, perché la spartizione non è equa per principio: chi ha contribuito
di più all'errore ne riceve di più. È il dettaglio della miniatura arrivato
al dunque: un peso che moltiplicava un ingresso grande ha mosso l'uscita
più di uno che moltiplicava quasi zero, e la derivata registra esattamente
questo.
Ed è la stessa riga a spiegare perché la sezione 1 chiedeva derivate «che
non si spengono». Su una pila vera quel prodotto non ha tre fattori: ne ha
uno per piano, decine in fila. La colpa non passa intera da un piano
all'altro, viene moltiplicata a ogni attraversamento, e le catene di
moltiplicazioni sono spietate con i fattori lontani da 1:
```
colpa in cima = 1, pila di 30 piani
fattore 1,0 a piano → 1,0³⁰ = 1 arriva in fondo intera
fattore 0,9 a piano → 0,9³⁰ ≈ 0,04 ne arriva il 4%
fattore 0,5 a piano → 0,5³⁰ ≈ 10⁻⁹ si spegne per strada
fattore 1,1 a piano → 1,1³⁰ ≈ 17 il male opposto: esplode
```
Sotto 1 la colpa muore prima di arrivare in fondo e i piani bassi restano
senza guida; sopra 1 esplode e travolge tutto: la stabilità di una pila
profonda vuole fattori vicini a 1. Il fattore di ogni piano dipende anche
dalla derivata della funzione di attivazione, ed ecco i criteri «quasi
prosaici» della sezione 1 al lavoro: l'interruttore netto della ReLU passa
la colpa intera sui positivi ma la azzera in blocco sui negativi (via libera
o muro); il gomito arrotondato di GELU e SiLU tiene il fattore piccolo ma
mai zero, e una via per arrivare in fondo resta sempre aperta.
Ed è qui che si guadagna la normalizzazione nominata nella sezione 1.
Riportando a ogni piano i numeri su una scala comune, tiene le attivazioni
lontane dagli estremi, e con le attivazioni i fattori del ritorno: la catena
resta in una zona in cui non si spegne né esplode. Non aggiunge matematica,
non insegna nulla al modello, non pesa quasi niente in byte; è igiene, e
senza igiene le pile alte semplicemente non si addestrano. Una seconda
invenzione servirà allo stesso scopo, e arriverà con l'architettura del
2017.
Tutto il giro sta in un'immagine sola: un paesaggio. Ogni punto è
una regolazione completa delle manopole, cioè uno dei modelli possibili; la
quota di quel punto è la perdita che quel modello fa segnare sul testo; fare
un passo è girare tutte le manopole di un po', spostandosi in un punto
vicino. Chi cammina è il modello stesso, e il sentiero è l'addestramento. Il
paesaggio ha miliardi di dimensioni e nebbia fitta: non si vede la valle, si
sente solo la pendenza sotto i piedi (il gradiente), e ci si fida che
scendere localmente, un passetto alla volta, porti da qualche parte. Che
funzioni non era affatto ovvio, ed è in gran parte una scoperta empirica:
non serve trovare il punto più basso del mondo, basta una valle buona.
Nessun passo di questa procedura è intelligente; imparare è far scendere un
numero, misurare la sorpresa e girare ogni parametro di un passetto nella
direzione che la riduce, migliaia di miliardi di volte.
Un dettaglio pratico completa il quadro: il passo non si calcola su tutto il
testo disponibile, ma su un blocco alla volta (il batch), e la pendenza
sentita a ogni passo è quindi una stima rumorosa di quella vera; è il motivo
del nome completo della procedura, discesa **stocastica** del gradiente. Il
rumore non è solo un difetto: scuote la discesa quel tanto che basta a non
fermarsi nella prima conca del paesaggio.
Il filo conduttore, qui, presenta il conto più salato dell'intero testo: ogni
passo di addestramento muove **tutti** i byte del modello almeno due volte
(andata per prevedere, ritorno per le colpe), più i byte di contorno che
l'**ottimizzatore** tiene per ciascun parametro (la contabilità della
discesa, che per ogni manopola conserva qualche numero di servizio, come la
media dei passi recenti). È il motivo fisico per cui
l'addestramento occupa interi datacenter per mesi, ed è la spesa colossale
pagata **una volta**. Ciò che ne esce è un blocco di numeri: gli scaffali
della biblioteca, riempiti una volta per tutte.
«Addestramento», al singolare, è poi una semplificazione: per un LLM moderno
le fasi sono almeno tre, e rispondono a domande diverse. Il **pretraining**
(il pre-addestramento) è l'auto-supervisione della sezione 1 spinta alla
scala massima: decide che cosa il modello sa, e domina i costi di ordini di
grandezza. Il **fine-tuning supervisionato** riprende quel modello e
lo corregge su esempi curati di dialogo: decide la forma, cioè che a una
domanda si risponde, invece di proseguirla con altre domande simili come
farebbe un puro completatore di testo. L'apprendimento dalle **preferenze**
(la famiglia di RLHF: risposte messe a confronto, giudizi umani o di un modello
giudice trasformati in premio con gli attrezzi del rinforzo) decide che
cosa il modello sceglie quando le risposte ammissibili sono tante:
tono, prudenza, stile. Stessa meccanica di discesa in tutte e tre,
budget incomparabili: la conoscenza costa come un'infrastruttura,
il comportamento come un progetto.
Da quel momento il modello non impara più. Ogni risposta, per brillante che
sia, lo lascia identico a com'era: la conversazione vive nel contesto, che a
fine sessione svanisce, non nei parametri. L'ha già raccontata, come «memoria
vuota», [il costo energetico di un modello linguistico](/it/02-articolo/);
qui basta fissarne la conseguenza architettonica: tutto ciò
che segue, l'inferenza con le sue fasi e le sue ottimizzazioni, opera su
parametri **immutabili**. È un vincolo, ma anche il vantaggio che rende
l'inferenza ottimizzabile: si può comprimere, copiare, distribuire e mettere
in cache ciò che non cambia mai. L'addestramento fissa i parametri una volta
per tutte: da lì in poi il modello non impara più, risponde e basta.
## 3. Interludio: il pappagallo e la biblioteca
«Risponde e basta» apre una domanda che divide il campo da anni: che cosa sta
facendo, esattamente, mentre risponde? Nel 2021, quando i modelli erano già
fluenti ma non ancora in tasca a tutti, un articolo molto discusso diede alla
posizione scettica il suo nome definitivo: **pappagallo stocastico**. La tesi
è pulita: un sistema addestrato a minimizzare la sorpresa sul testo (sezione
1) manipola forme, non significati; ricuce frammenti visti in addestramento
seguendo le probabilità, senza alcun aggancio al mondo di cui il testo parla.
E la tesi sembrava ovvia per una ragione onesta: è la descrizione letterale
della ricetta. Nella funzione di perdita non c'è un termine per la verità né
per la comprensione; c'è quanto spesso le parole si seguono. Chi coniò
l'etichetta non faceva caricatura: leggeva la definizione. In più metteva in
guardia da un rischio reale, scambiare la fluenza per comprensione, che resta
sensato anche se la tesi si rivelasse sbagliata.
Col tempo però le prove sul campo hanno aperto crepe difficili da ignorare.
La più vicina al filo di questo testo è un conto di byte: il testo di
addestramento pesa decine di terabyte, i parametri un paio di ordini di
grandezza di meno. Un pappagallo letterale non ci starebbe: per predire bene
con così poco spazio bisogna **comprimere**, e comprimere a quel livello
significa trovare regolarità (grammatica, fatti, relazioni, stili) che
valgono anche su testo mai visto. A questo si sono aggiunti comportamenti che
la ricucitura spiega male: problemi risolti in combinazioni mai apparse in
addestramento, e l'apprendimento dal contesto (in-context learning: a
parametri rigorosamente fermi, il modello impara un compito nuovo dagli
esempi nel prompt, cosa che nessuna delle sezioni precedenti prevedeva
esplicitamente di ottenere).
La crepa più interessante, però, non viene dal comportamento: viene dal
guardare **dentro**. Un esperimento ormai classico ha addestrato un piccolo
transformer solo su sequenze di mosse di Othello, mai una scacchiera: nelle
sue attivazioni si trova, leggibile e manipolabile, una rappresentazione
dello stato della scacchiera, che il modello si è costruito da solo perché
comprimeva meglio le mosse. E nei modelli linguistici grandi il campo che si
chiama interpretabilità meccanicistica ha imparato a isolare le **feature**:
non «neuroni» singoli né zone fisiche del chip, ma direzioni e combinazioni
sparse nello spazio delle attivazioni che si accendono quando la generazione
tocca un concetto (un luogo, un tono, una pratica di codice insicura), spesso
indipendenti dalla lingua e dalla formulazione di superficie. È la versione
precisa dell'intuizione che «si attivano zone diverse a seconda
dell'argomento»: vero, purché si aggiunga che le zone sono geometrie
distribuite nel tavolo di lettura, non reparti della biblioteca. E per
l'apprendimento dal contesto di poco fa esiste perfino un candidato
meccanico: le induction heads, coppie di teste di attention che sembrano
implementare «ho già visto A seguito da B; ora rivedo A, propongo B»; con la
cautela d'obbligo, perché dire che una testa «fa» qualcosa è una lettura a
posteriori di un pattern, non una funzione programmata.
Se una feature si può isolare, si può anche spingere. Amplificarla o
spegnerla a mano, durante la generazione, sposta il comportamento in modo
prevedibile: è stato mostrato con un esperimento volutamente teatrale (un
modello con la feature di un celebre ponte tenuta accesa, che infilava il
ponte in ogni risposta), e in laboratorio si isolano direzioni per
comportamenti interi, l'adulazione, il rifiuto di rispondere. È il concetto
alla base dello **steering**: intervenire sulle attivazioni, non sui pesi né
sul prompt. Nel lessico di questo testo: agire sul tavolo di lettura,
lasciando gli scaffali intatti, la coppia parametri/attivazioni vista
all'inizio messa al lavoro. E non è più solo laboratorio: c'è già chi lo
espone come opzione a riga di comando in un motore di inferenza reale, una
direzione per strato in un file da mezzo megabyte, per regolare verbosità o
disponibilità a certi argomenti senza toccare i pesi. Va detto con la stessa
onestà: sono strumenti giovani, le feature isolate sono una frazione minima
di quelle che esistono,
e dal poter spingere un comportamento non segue che si sappia leggere tutto.
Perché allora restano due partiti? In parte perché metà del litigio abita
nella parola «conoscenza»: se conoscere richiede esperienza del mondo,
intenzione, aggancio alla verità, il pappagallo resta pappagallo per
definizione, e nessun esperimento sulle attivazioni lo scalfisce; se basta
struttura interna che generalizza, si lascia isolare e si lascia manovrare,
l'evidenza si accumula dall'altra parte. E in parte perché le poste in gioco
sono diverse: chi teme l'antropomorfizzazione e l'hype ha ragioni prudenziali
serie (la fluenza inganna, e il marketing rincara); chi lavora dentro i
modelli vede strutture che la metafora del pappagallo non copre più. La
posizione di questo testo è minima e dichiarata: la biblioteca è troppo
piccola per contenere ciò che ha letto, e questo da solo obbliga a qualcosa
di più interessante della ripetizione; che lo si chiami conoscenza è una
scelta di vocabolario, che ci sia struttura è ormai un'osservazione.
C'è un ultimo effetto del guardare dentro, e vale la pena dichiararlo,
perché accompagna tutto il resto del testo. Aprire la macchina produce due
sensazioni opposte, e non si elidono: convivono. La prima è una specie di
aridità: là dentro non c'è nessuno; ci sono sequenze di numeri, pesature, la
stessa iterazione ripetuta miliardi di volte, e ogni parola brillante esce
da quel ciclo. La seconda è il suo contrario: proprio perché nessuno ha
scritto quelle strutture riga per riga, trovarle (la scacchiera, le feature,
le direzioni che si lasciano spingere) mostra un'organizzazione che
l'addestramento ha fatto emergere da sé, che nessun progettista ha dettato e
che si sta ancora imparando a leggere. Il confine tra ciò che si sa spiegare
e ciò che è semplicemente emerso, negli scaffali, non è netto. Chi apre la
macchina si porta via entrambe le cose, il disincanto e la meraviglia; ed è
probabilmente il segno di averla aperta davvero.
## 4. 2017: l'attention, ovvero la gestione del contesto
Per predire il seguito di «il cane ha morso il» serve sapere di che scena si
parla, chi è il soggetto, che cosa è già successo: serve il **contesto**. Le
architetture precedenti (le reti ricorrenti) leggevano i token uno alla volta,
portandosi dietro un riassunto che sbiadiva: il passato lontano si perdeva, e
soprattutto la lettura era sequenziale per costruzione, impossibile da
parallelizzare bene sull'hardware che intanto diventava sempre più parallelo.
Il paper del 2017, «Attention is all you need», propone di rovesciare
l'impostazione: niente lettura in fila, niente riassunto che si trascina.
Tutti i token della sequenza vengono elaborati **insieme**, e ciascuno decide
da solo, dinamicamente, a quali altri prestare attenzione, qualunque sia la
distanza. Il meccanismo si chiama attention e l'intuizione sta comoda nella
biblioteca: ogni token formula una domanda (la sua query), espone
un'etichetta (la sua key) e porta un contenuto (il suo value). Da dove
escano va detto subito, perché non sono ingredienti nuovi: domanda,
etichetta e contenuto sono tre pesature del punto del token, del tipo visto
nella sezione 1, con tre matrici di parametri che l'addestramento fissa come
tutto il resto. Si confronta la domanda di ciascuno con le etichette di
tutti, i punteggi diventano pesi, e
ogni token riceve una combinazione pesata dei contenuti altrui: un riassunto
su misura del resto della frase. Scritta come nel paper, l'intera operazione
sta in una riga:
Attention(Q, K, V) = softmax(Q·Kᵀ / √dₖ) · V
e letta a pezzi è esattamente la biblioteca: Q·Kᵀ confronta ogni domanda con
tutte le etichette (una matrice di punteggi, un numero per ogni coppia di
token); la divisione per √dₖ li rimette in scala; il softmax li trasforma in
percentuali; la
moltiplicazione finale per V fa la media pesata dei contenuti. Tre
moltiplicazioni di matrici e un softmax: la fama della formula è tutta
nella scala a cui viene eseguita.
Vale la pena seguirla anche con le forme in mano, perché è lì che si vede
nascere il conto che governerà tutta la seconda metà del testo. Entrano T
righe da d, come a ogni piano; le tre pesature ne ricavano altrettante,
una domanda, un'etichetta e un contenuto per token:
```
ingresso T × d
normalizzazione T × d riga per riga, non mescola niente
Q, K, V T × d ciascuna, dalle tre pesature (matrici d × d)
Q · Kᵀ T × T la tabella dei punteggi: ogni token per ogni token
softmax T × T ogni riga diventa percentuali che sommano a 1
(…) · V T × d si torna alla forma d'ingresso
```
Quel **T × T** in mezzo è l'oggetto da tenere d'occhio: è l'unica cosa, in
tutto il transformer, che non è fatta di d, e raddoppiare la lunghezza del
testo lo quadruplica. Le matrici dei parametri, intorno, restano d × d e non
se ne accorgono. Si noti
anche che all'uscita la forma è quella d'ingresso, T × d: è ciò che permette
di impilare i piani a piacere, e di sommare il risultato a quanto era
entrato.
Quel √dₖ merita una sosta, perché sembra un aggiustamento arbitrario e non
lo è. Il confronto fra domanda ed etichetta somma un contributo per ogni
dimensione dello spazio: più dimensioni ci sono, più termini si sommano, e
più i punteggi si allargano. Crescono, tipicamente, come la radice del
numero di dimensioni, che è appunto √dₖ. Dividere per quella radice
rimette i punteggi dove il softmax lavora bene: senza, in uno spazio ampio i
punteggi arriverebbero già così distanti da far collassare le percentuali su
un vincitore unico, e l'attenzione, invece di distribuirsi, si inchioderebbe
su un token solo.
L'operazione non viene eseguita una volta sola, ma con più «teste» in
parallelo, e qui va tolto di mezzo un equivoco frequente: le teste non
moltiplicano il costo. Il punto di ogni token viene diviso in fette: con h
teste, ciascuna lavora su d/h numeri, e su quella fetta fa il suo giro
completo di domande, etichette e contenuti, con la sua tabella T × T; alla
fine le h uscite, lunghe d/h ciascuna, si riaccostano a formare di nuovo d.
È il √dₖ di poco fa a chiarirsi qui, per inciso: quel dₖ è la fetta, d/h,
non l'intero d. Otto teste su fette da un ottavo costano quanto una testa
sola sull'intero: si compra varietà di sguardi, non potenza di calcolo. Ciò
che si moltiplica per h sono le tabelle dei punteggi, che sono h invece di
una, e infatti è dalla loro parte che si andrà a risparmiare. Che cosa guardino, poi, è
questione più aperta di quanto si racconti: le teste stanno in sottospazi
separati **perché possano** specializzarsi, e alcune si lasciano leggere
davvero (una segue l'accordo fra soggetto e verbo, un'altra riporta i
pronomi al loro antecedente), ma molte non mostrano nessun ruolo pulito, e
una quota consistente si può togliere senza che il modello se ne accorga.
Impilato per decine di strati e alternato a blocchi di puro calcolo, questo
è il transformer. Il piano dell'edificio, visto da vicino, ha dunque due
stanze: l'attention, l'unico posto in cui i token si parlano, e il blocco di
calcolo, la vecchia coppia pesatura e non linearità al lavoro su ogni token
per conto suo. Davanti a ciascuna delle due, nelle ricette moderne, sta la
normalizzazione della sezione 1: lavora su una riga per volta, non mescola
niente e riconsegna la forma che ha ricevuto, T × d, con una manciata di
parametri, dell'ordine di d contro i d² delle matrici che la circondano. È
anche il motivo per cui, più avanti, si vedrà che nessuno si sogna di
comprimerla. E accanto alle due stanze corre una via diretta: il risultato
di ciascuna si somma a ciò che entrava, e la parte che non va cambiata
prosegue intatta, senza dover essere ricostruita piano per piano. Sembra un
dettaglio idraulico ed è invece la seconda delle due invenzioni annunciate
nella sezione 1, quella che insieme alla normalizzazione rende addestrabili
le pile profonde: la somma offre alla colpa del ritorno una scorciatoia in
cui il fattore vale 1, e la catena di moltiplicazioni della sezione 2 smette
di essere spietata.
Un dettaglio va detto per intero, perché lasciato a metà obbliga a fidarsi:
l'attention, da sola, non conosce l'ordine delle parole. Confronta ogni
token con tutti gli altri, non una sequenza: «il cane ha morso l'uomo» e
«l'uomo ha morso il cane» produrrebbero gli stessi confronti. L'ordine va
quindi reintrodotto, e la soluzione è nello spirito della sezione 1: anche
la posizione diventa geometria. La prima ricetta dà alla posizione un punto
nello spazio (calcolato con una formula fissa nel paper del 2017, imparato
come gli embedding in molti modelli venuti dopo) e lo somma all'embedding
del token: il punto che entra al piano terra dice insieme che cosa è e dove
sta, e la rete impara a leggere le due informazioni dalle stesse coordinate.
Le varianti moderne spostano il trucco dentro l'attention (nel gergo, le
rotary embeddings, RoPE): invece di marcare il punto all'ingresso, ruotano
domanda ed etichetta di un angolo che cresce con la posizione, così che il
confronto tra due token senta la loro distanza, non il posto assoluto; ed è
la distanza, per la lingua, l'informazione che conta. Il mestiere di
codificarla al meglio si affina ancora, ma il principio resta uno: la
posizione non è un cartellino appeso al token, è scritta nel punto.
Negli LLM si aggiunge una sola regola, la maschera causale: ogni token può
guardare solo chi lo precede, mai il futuro. La ragione va detta per intero,
perché non è una precauzione formale ma ciò che rende l'addestramento
economico. Nella sezione 1 il testo veniva percorso «posizione per
posizione», e si potrebbe immaginare un passaggio per ciascuna: non è così.
Una sola andata su un blocco di testo produce **contemporaneamente** la
previsione di tutte le sue posizioni, ed è per questo che addestrare su
migliaia di miliardi di token è pensabile. Ma se ogni posizione vede l'intera
frase, quella di turno vede anche il token che dovrebbe indovinare, e il
compito diventa copiare. La maschera è il prezzo che rende lecito il
risparmio: tutte le posizioni insieme, ciascuna murata nel proprio passato.
Anche qui una miniatura vale mille parole: ecco i pesi di attention di una
frase di quattro token, dopo softmax e maschera,
```
t1 t2 t3 t4
t1 [ 1.00 0 0 0 ] ogni riga: come quel token
t2 [ 0.33 0.67 0 0 ] distribuisce la sua attenzione
t3 [ 0.25 0.25 0.50 0 ] (e somma a 1)
t4 [ 0.10 0.20 0.30 0.40 ]
```
dove gli zeri sopra la diagonale sono la maschera al lavoro (i punteggi
verso il futuro vengono messi a −∞ prima del softmax, che li trasforma in
zeri esatti). Questa tabella, un numero per ogni coppia di token, è
l'oggetto centrale dell'attention: conviene fissarla bene, perché è lei che
tra poco crescerà col quadrato.
Un piano della pila, disegnato dall'alto in basso per comodità di lettura (nell'edificio della sezione 1, questo percorso è salire di un piano). L'attention è l'unico punto in cui i token si parlano: ogni query interroga le key di tutti, i punteggi pesano i value, e la maschera causale (il triangolo) vieta di guardare avanti. Il blocco di calcolo lavora invece su ogni token per conto suo. Le vie tratteggiate sono le somme con la via diretta: ciò che non serve cambiare passa indisturbato, ed è quello che tiene in piedi l'addestramento di pile profonde. Decine di questi piani, impilati, fanno il transformer.
La ragione per cui l'attention ha vinto, però, non è l'eleganza: è che si
calcola in parallelo. Il confronto di tutte le domande con tutte le etichette
è ancora lei, la moltiplicazione di matrici. Il prezzo è la tabella di poco
fa: T × T confronti, un costo che cresce col **quadrato** della lunghezza.
Mille token sono un milione di
confronti e non li nota nessuno; un milione di token sono 10^12 confronti, e
non c'è hardware che li regga fatti in modo ingenuo. Vale la pena essere
precisi su quel «non li nota nessuno», perché l'altra stanza del piano, il
blocco di calcolo, cresce soltanto in proporzione alla lunghezza: sui
contesti brevi è lei a dominare il conto, e il quadrato resta un termine
minore. Il sorpasso avviene più avanti, e più il contesto si allunga più
diventa netto: è per questo che il quadrato non è un problema di sempre, ma
il problema del contesto lungo. Questo quadrato è il
personaggio che tornerà in quasi tutte le ottimizzazioni della seconda metà
del testo. L'attention è il meccanismo con cui ogni token guarda tutti gli
altri e decide da solo quali contano; ha vinto perché si calcola in parallelo,
e il suo prezzo, il quadrato della lunghezza, è il debito che l'inferenza si
porta dietro da allora.
## 5. Il panorama dei modelli
Prima di entrare nel mestiere dell'inferenza conviene una fotografia di ciò
che c'è in circolazione: «modello» è una parola sola per oggetti molto
diversi, e le differenze che contano si lasciano ordinare lungo pochi assi.
Gli assi invecchiano piano; le risposte, in fretta (se ne riparla in coda
alla sezione). Sei domande bastano a inquadrare quasi ogni modello che si
incontra:
| La domanda | Le risposte, oggi | Perché conta |
|---|---|---|
| Chi può averlo? | chiuso (si usa solo via servizio); a pesi aperti (si scaricano, con una licenza da leggere); aperto per davvero (pesi, codice e dati) | senza pesi aperti, niente macchina propria |
| Quanto pesa? | da pochi miliardi a migliaia di miliardi di parametri | la memoria per tenerlo: la prima voce di ogni conto |
| Come lavora per token? | denso (tutti i parametri, sempre) oppure a esperti (pochi al lavoro per volta) | calcolo e banda per token; se ne riparla tra le ottimizzazioni |
| Che appunti prende? | attention piena; compressa; selettiva | il costo del contesto lungo (sezioni 7 e 8) |
| Che mestiere sa fare? | base; assistente; «ragionatore» (spende token per pensare prima di rispondere); multimodale (anche immagini e audio) | è l'eredità delle tre fasi della sezione 2 |
| In che forma viaggia? | piena precisione; già compresso alla nascita; quantizzazioni di comunità | i byte da scaricare, tenere e muovere |
Sul primo asse serve una precisazione, perché il lessico corrente confonde:
la maggior parte dei modelli detti «open» è **a pesi aperti**, non open
source. Dal 2024 esiste una definizione formale di AI open source (pesi,
codice e informazioni sufficienti sui dati, con piena libertà d'uso: sta
nei riferimenti), e quasi nessun modello di punta la soddisfa: si scaricano
i pesi, la ricetta resta in casa. Non è un cavillo: con i soli pesi si può
far girare, ispezionare, adattare; non si può rifare. E anche tra i pesi
aperti le licenze non sono tutte uguali: alcune sono licenze libere piene,
altre portano condizioni d'uso da leggere prima di costruirci sopra. Sugli
altri assi una regolarità del momento va notata: quasi tutti i grandi
modelli a pesi aperti di oggi sono a esperti, perché è l'architettura che
rende sostenibile la taglia, e le attention compresse o selettive si stanno
diffondendo dai capofila al resto del campo.
Con questi assi in mano, la scelta del motore su misura che si incontrerà
tra poco smette di sembrare un caso: DeepSeek V4 Flash è a pesi aperti con
licenza libera, a esperti (pochi parametri al lavoro per token), con gli
appunti più compressi della categoria, distribuito già compresso alla
nascita, e regge bene la compressione spinta. Ognuna di queste voci è un
requisito per la macchina sola: un modello chiuso non si può nemmeno
scaricare; uno denso della stessa taglia non entrerebbe nei conti di banda;
uno con gli appunti ingombranti brucerebbe la memoria col solo contesto.
Non tutti i modelli si possono far girare in casa, e non è questione di
impegno: la scelta del modello è metà della scelta.
Una cautela chiude la fotografia, ed è essa stessa una lezione: questa
tassonomia è un'istantanea di metà 2026, e la frontiera si sposta a
settimane, non ad anni; i nomi e i numeri citati in queste pagine
invecchieranno presto, qualcuno forse è già invecchiato. Gli assi, però, si
muovono molto più piano delle risposte, ed è il motivo per cui conviene
impararli. Capire le basi e restare aggiornati non sono alternative: la
prima cosa serve proprio a rendere possibile la seconda, e questo testo
prova a dare la prima sapendo di non poter dare la seconda.
## 6. L'inferenza: un mestiere fatto di due lavori
Qui il testo arriva al suo centro, e conviene entrarci con la domanda del
lettore: che cosa succede tra l'invio del prompt e la prima parola che
compare? Succedono due lavori diversi, e quasi tutta l'ingegneria
dell'inferenza nasce dal fatto che sono diversi davvero.
Il primo lavoro è il **prefill**: il prompt, che può essere lungo (istruzioni,
documenti, la conversazione precedente), viene digerito tutto insieme. I token
ci sono già tutti, quindi l'attention può lavorare in parallelo su tutta la
sequenza: è il regime per cui il transformer è nato, le matrici sono grandi,
le GPU piene, il limite è la capacità di calcolo. È il tempo di attesa prima
della prima parola.
Il secondo lavoro è la **generazione**: da lì in poi il modello produce un
token alla volta, e ogni token nuovo dipende da tutti i precedenti. Niente più
parallelismo sulla sequenza: una fila, un passo per token. E a ogni passo,
per produrre un pezzettino di parola, il modello deve consultare **tutti** i
suoi parametri: ogni scaffale della biblioteca, riletto per intero, per
scegliere una sillaba. I conti da fare su quei numeri sono pochi; il lavoro
vero è portarli dai chip di memoria ai circuiti di calcolo. Il racconto
energetico già richiamato ha dato la misura fisica (dentro un chip, andare a
prendere un numero in memoria costa da cento a settecento volte più che farci
un conto); qui si vede la conseguenza architettonica: il prefill è limitato
dal calcolo, la generazione dalla **banda di memoria**. Due colli di
bottiglia diversi nella stessa
risposta.
A questo punto entra in scena la prova a terra annunciata all'inizio, e
merita una presentazione vera e propria. **DwarfStar** (ds4) è un motore di inferenza open source,
licenza MIT, che Salvatore Sanfilippo (antirez) ha scritto in C con una
scelta radicale: non un motore generalista, ma un motore **su misura**, per
un modello specifico e per l'hardware che ha davanti. Nasce con DeepSeek V4
Flash sui Mac a memoria unificata, con l'obiettivo dichiarato di farlo
girare bene su una macchina sola; il perimetro però si è già allargato, e
nella direzione che conta: non solo a un hardware nuovo (i sistemi CUDA, gli
APU consumer), ma a un modello di un'altra famiglia, GLM 5.2, affiancato a
DeepSeek. La specializzazione è il metodo, non il recinto; che il recinto si
sia spostato senza cadere è la prova migliore che la scommessa regge, e come
il trapianto sia avvenuto si legge nel codice, alla sezione 9. Ottimizzare
per una coppia
modello-hardware alla volta è ciò che permette di tirare fuori il massimo; a
che cosa serva sapere quanto è il massimo, lo diranno le domande finali.
Pochi file di C senza dipendenze, i kernel riscritti per ogni hardware, e
dentro, leggibili una per una, quasi tutte le mosse di questo testo:
la quantizzazione asimmetrica, la cache compressa con il suo indexer,
la cache su disco, lo streaming da SSD, l'inferenza distribuita,
perfino lo steering dell'interludio (è ds4 il «motore di inferenza reale» citato lì).
È il motivo per cui fa da prova a terra ideale: in un motore generalista
le scelte si nascondono dietro l'astrazione, in uno specializzato stanno
nude nel codice; e una suite di vettori di test, confrontati byte per byte
con le risposte dell'API ufficiale, tiene onesto tutto il resto.
Il README del progetto è esso stesso una piccola lezione di inferenza,
e sta nei riferimenti in coda. E una trasparenza del progetto merita eco
qui, perché fa il paio con l'apertura di queste pagine: gli autori
dichiarano apertamente che il codice è scritto con forte assistenza di un
LLM, con gli umani a guidare le idee, i test e il debugging.
Vale la pena guardare da vicino che cosa significhi «su misura», perché non
è uno slogan: è scritto nel codice, a partire dal commento d'apertura del
file principale. Lì il motore dichiara le sue forme fisse: accetta i layout
noti del suo modello e si rifiuta di partire davanti a qualunque altro. Un
motore generalista fa l'opposto per mestiere: legge la forma della rete dai
metadati del file e deve saper far girare qualunque forma vi trovi, e la
flessibilità si paga in strati di astrazione, casi limite, occasioni perse.
Conoscere la forma in anticipo significa che ogni struttura dati e ogni
kernel possono assumerla. E la specializzazione si vede a occhio nudo,
nell'elenco dei file dei kernel:
```
softmax.metal norm.metal flash_attn.metal le operazioni di qualunque motore
dsv4_kv.metal dsv4_hc.metal dsv4_rope.metal la cache compressa, le teste,
la posizione: di quel modello
```
accanto agli attrezzi universali, kernel che portano nel nome il modello:
la sua cache compressa, la sua compressione delle teste, la sua codifica
della posizione (la rotazione della sezione 4), ciascuna col kernel scritto
apposta.
Il su misura non si ferma ai kernel: è una filiera. I pesi non sono i file
«della community»: sono file co-progettati col motore, prodotti da un
quantizzatore dedicato, un paio di migliaia di righe di C che dichiarano
nel proprio commento d'apertura di tenere solo i pezzi che servono alle
ricette di quel modello (è lì che vive la quantizzazione asimmetrica appena
nominata). E c'è la rete di sicurezza che permette di spingere così: i
vettori di test di poco fa, catturati dall'API con la decodifica più
prudente e riprodotti in locale alla lettera, così che una regressione del
tokenizer o dell'attention emerga subito, prima di diventare risposte
sbagliate; e una suite di una novantina di problemi che percorre lo stesso
cammino di inferenza degli utenti, da rilanciare dopo ogni modifica a un
kernel o a una quantizzazione. L'ambizione dichiarata è far sembrare un
modello locale «finito», non solo eseguibile; la lezione di metodo è che
ogni ottimizzazione arriva insieme allo strumento che ne misura il danno.
C'è infine un debito, dichiarato con la stessa franchezza: senza llama.cpp
e GGML questo motore non esisterebbe (i layout di quantizzazione, le
tabelle, perfino alcuni kernel adattati), al punto che il copyright di
quegli autori resta nel file di licenza. Il motore su misura poggia sulle
spalle del generalista; lo supera in un punto solo perché rinuncia a tutto
il resto.
I suoi numeri fissano la firma delle due fasi: sulla stessa macchina e con lo
stesso modello, il benchmark segna un ordine di 250 token al secondo in
prefill e ~20 in generazione (numeri di metà 2026, ordini di grandezza e non
misure fisse: il progetto stesso avverte che l'ottimizzazione corre più in
fretta dei benchmark pubblicati). Non è un difetto del software: la si
ritrova, con numeri diversi, su qualunque sistema. Ancora più netta la controprova
dell'inferenza distribuita: dividendo il modello su
due macchine, il prefill **accelera** (i pezzi di prompt si lavorano in
pipeline), la generazione **rallenta** (ogni token paga un viaggio di rete in
più, e i viaggi non si accorciano mettendosi in due).
Quanto contino i viaggi lo dice la scala dei collegamenti, ed è una prova
che chiunque può rifare con due computer. Stesse due macchine, stesso
modello (due M5 Max, quant Flash da 91 GB, un prompt da qualche migliaio di
token): su un cavo diretto, con mezzo millisecondo di latenza, la
generazione fa 25 token al secondo; sul WiFi, con la latenza che sale verso
i cento millisecondi, scende a 10; su una VPN attraverso Internet, a meno
di 4. Il prefill percorre la stessa scala in modo diverso: viaggia a
blocchi grandi in catena di montaggio, quindi a contare è quanta roba passa
al secondo; la generazione spedisce pacchetti minuscoli ma uno per token,
quindi a contare è quanto ci mette il singolo pacchetto ad arrivare. È la
domanda del filo conduttore con una valuta in più: «quanto costa muoverli»
si paga in byte e in millisecondi, e le due fasi pagano in valute diverse.
La conferma più pulita è un esperimento negativo del progetto: dimezzare i
byte delle attivazioni in viaggio non ha spostato quasi nulla, tanto che
l'opzione rischia di sparire. A generazione il conto non è il volume del
pacco: è il numero di consegne.
La stessa firma si legge, rovesciata, nell'hardware. A parità di modello e di
motore, la velocità di generazione insegue la banda di memoria quasi in
proporzione: una workstation con RAM tradizionale (80-90 GB/s) genera 2-5
token al secondo, un portatile con memoria unificata da 400 GB/s ne fa una
ventina, una macchina da 800 GB/s una trentina. E il controesempio chiude il
conto meglio di qualunque argomento: una scheda video con la banda più alta
di tutte (1000 GB/s) ma 24 GB a bordo **non parte nemmeno**, perché il
modello non ci entra. Per la generazione servono insieme banda e capienza:
è il motivo per cui le macchine a memoria unificata, che di banda ne hanno
meno ma condividono decine o centinaia di GB tra CPU e GPU, hanno vinto
proprio questo caso d'uso. Vale anche il paradosso simmetrico: un piccolo
sistema nato apposta per l'AI, con capacità e calcolo in abbondanza ma banda
modesta, va fortissimo in prefill e genera alla metà di un computer da
scrivania.
Nessuna scheda tecnica riporta la voce «banda per generare»: è quella da
andare a cercare. Rispondere è fatto di due lavori diversi, digerire il
prompt tutto insieme e produrre un token alla volta, e chi ottimizza, chi
compra, chi confronta deve sapere quale dei due sta pagando.
Le due fasi della stessa risposta. Il prefill lavora tutti i token del prompt insieme: le matrici sono grandi, il limite è il calcolo, e la sua durata è l'attesa prima della prima parola. La generazione è un pettine di passi identici, un token per passo: il lavoro dominante è rileggere i parametri a ogni passo, e il limite è la banda di memoria. I passi del pettine sono disegnati alla stessa distanza, ma il loro costo cresce piano col contesto: è il tema della sezione successiva.
## 7. Gli appunti sul tavolo: la KV cache
C'è un'assurdità nascosta nella generazione, così com'è stata descritta: se
ogni token nuovo deve guardare tutti i precedenti, al passo mille il modello
dovrebbe ricalcolare da capo le etichette e i contenuti (le key e i value) di
mille token, al passo milleuno di milleuno, e così via. Il costo già
quadratico diventerebbe intollerabile a ogni singolo passo.
La soluzione è l'oggetto più importante e meno raccontato dell'inferenza: le
key e i value dei token già visti **non cambiano mai** (i parametri sono
fissi, il passato pure), quindi si calcolano una volta e si tengono da parte.
Sono gli appunti sul tavolo di lettura: la **KV cache**. A ogni passo il
modello calcola key e value solo del token nuovo e riusa tutto il resto. La
generazione torna lineare; in cambio, gli appunti occupano memoria, e
crescono con la lunghezza della conversazione, strato per strato, testa per
testa. E «memoria», qui, non è una metafora: sono byte precisi, con un
indirizzo, al punto che si possono salvare e ricaricare, come si vedrà tra
le ottimizzazioni. Quando si discute se un modello «ricordi», questa è
l'unica memoria di lavoro che esiste davvero.
Questo scambio (ricalcolo evitato contro memoria occupata) fa della KV cache
il personaggio-cerniera di tutto il testo. È il motivo per cui il contesto
lungo costa anche quando il modello «non sta facendo niente»; è la voce di
memoria che compete con i parametri per lo stesso spazio; ed è l'oggetto che
quasi ogni ottimizzazione della prossima sezione tocca, comprime o sposta. E
il suo peso, per una volta, si può stimare a mano: byte della cache ≈ 2 (per
K e V) × strati × teste × dimensione per testa × byte per numero × token nel
contesto. Un numero per fissare la scala: in un transformer standard, la
cache di un contesto da un milione di token occuperebbe centinaia di
gigabyte, più del modello stesso. DeepSeek V4 Flash, con un'attention progettata per comprimere
gli appunti (se ne riparla tra poco), la porta a ~26 GB: enorme in assoluto,
piccolo per la categoria, ed è la singola scelta che rende pensabile il
contesto da un milione di token su una macchina sola. La generazione non
ricalcola il passato perché lo tiene negli appunti: la KV cache è la vera
memoria della conversazione, e il suo peso in byte governa più decisioni di
progetto di qualunque altro numero.
Lo scambio della KV cache, nei suoi due lati. A sinistra il guadagno: le key e i value dei token già visti non cambiano mai, quindi restano scritti sul tavolo e a ogni passo si aggiunge una riga sola, quella del token nuovo. A destra il prezzo: gli appunti crescono in linea retta con la lunghezza del contesto, strato per strato e testa per testa, ed è la voce di memoria che compete con gli scaffali per lo stesso spazio.
## 8. Le ottimizzazioni: sei risposte alla stessa domanda
Vista da fuori, l'ingegneria dell'inferenza sembra un catalogo di sigle. Vista
dal filo conduttore, è una famiglia di risposte alla stessa domanda: dove
stanno i byte e quanto costa muoverli. Qui se ne scelgono sei, non per
completezza ma perché coprono le mosse fondamentali; ognuna è dichiarata per
quello che è, un modo di avere meno byte, di muoverli meno, o di muoverli
mentre si fa altro.
**Meno byte per parametro: la quantizzazione.** I parametri nascono come
numeri a 16 o 32 bit; scriverli con meno bit (8, 4, perfino 2) li rende meno
precisi ma molto più piccoli, e siccome il costo dominante è trasportarli,
un modello dimezzato è (quasi) un modello due volte più veloce a generare.
Il come è meno brutale di quanto sembri: i pesi si raggruppano in piccoli
blocchi (una trentina di valori) e per ogni blocco si salva un fattore di
scala; i pochi bit di ciascun peso dicono solo la posizione relativa dentro
il blocco, e la scala riposiziona il blocco intero. E i bit non si spendono
alla cieca: si fa passare nel modello del testo di calibrazione, si misura
quali pesi vengono sollecitati davvero (il profilo si chiama imatrix), e la
precisione si concentra lì. Non va confusa con la distillazione, che è un
modello **diverso** e più piccolo addestrato a imitare il grande: la
quantizzazione è lo stesso modello, scritto con numeri più corti.
La prova a terra di ds4 mostra quanto il mestiere sia fine. DeepSeek V4
Flash in piena precisione peserebbe ~570 GB; la versione a 2 bit di ds4 pesa
~80 GB con qualità quasi intatta, ma solo perché la quantizzazione è
**asimmetrica**: si comprimono al minimo le parti del modello attive a
tratti (gli «esperti», circa il 90% dei parametri: li presenta la mossa che
segue), mentre ciò che lavora su ogni token, le normalizzazioni delle
sezioni 1 e 4, il router che
sceglie gli esperti, le proiezioni dell'attention, resta in alta
precisione. Il risultato non è
«tutto a 2 bit», è una **gerarchia di precisioni**, dai 2 bit degli esperti
fino ai 16 delle parti più delicate, decisa componente per componente.
Comprimere non è un'operazione uniforme: si comprime dove l'errore si può
permettere.
Il risultato viaggia in un file-contenitore, il GGUF dell'ecosistema
llama.cpp: dentro ci sono i tensori già quantizzati, i metadati
dell'architettura e il tokenizer, così il file basta a sé stesso. E il
motore non lo copia in memoria: lo **mappa** (mmap) e legge i pezzi a
richiesta; dove la memoria è unificata, perfino la GPU lavora direttamente
dentro il file mappato, senza farne copia. Anche aprire un modello, visto
dal filo conduttore, è una scelta su dove stanno i byte. Due note oneste
chiudono la mossa. La prima:
il modello «in chiaro» spesso non è mai esistito (DeepSeek distribuisce pesi
già compressi alla nascita, in formati a 8 e 4 bit: il quantizzatore di ds4
traduce tra due forme già compresse, non degrada un originale perfetto). La
seconda: che si possa buttare via la maggior parte dei bit senza crollo dice
qualcosa di profondo sugli scaffali: la conoscenza non abita in pochi numeri
di precisione chirurgica, è scritta in modo distribuito e ridondante,
robusto al rumore; e il fatto che l'imatrix migliori le cose conferma che
non tutti i pesi sono uguali. È la stessa lezione dell'interludio, vista dal
lato dei byte: negli scaffali c'è struttura, non dettatura.
**Meno byte al lavoro per token: il MoE.** Il Mixture of Experts cambia
l'architettura: invece di un unico blocco di calcolo per strato, molti
«esperti» più piccoli, e un router che per ogni token ne attiva pochi.
DeepSeek V4 Flash ha 284 miliardi di parametri totali ma ne attiva ~13 per
token. Attenzione alla coppia: i parametri **totali** vanno comunque tenuti
tutti in memoria (il router potrebbe chiamare chiunque al prossimo token),
sono i parametri **attivi** a determinare calcolo e banda per token. Il MoE è
quindi un patto esplicito col filo conduttore: memoria abbondante in cambio di
trasporto ridotto. È l'architettura ideale dove la memoria è larga e il
calcolo scarso, e infatti prospera sulle macchine a memoria unificata.
**Meno byte di appunti: comprimere la KV cache.** L'attention si può
riprogettare perché gli appunti nascano già compressi: rappresentazioni
latenti piccole da «riaprire» al volo (la MLA di DeepSeek), righe di cache
condivise tra gruppi di posizioni, finestre locali per i primi strati. È la
strada dei ~26 GB per un milione di token della sezione 7. Qui la matematica
**cambia**, e la qualità va rivalidata: è la differenza, da tenere stretta,
con l'ottimizzazione pura di implementazione. L'esempio canonico di
quest'ultima è Flash Attention: stessa identica matematica, ma calcolata a
blocchetti dentro la memoria velocissima del chip invece di materializzare la
matrice dei confronti in quella lenta. Nessuna approssimazione, 2-4 volte più
veloce: il quadrato della sezione 4 non sparisce, ma smette di viaggiare.
C'è poi una mossa gemella, più radicale, ed è la firma di DeepSeek V4: **non
rileggere tutti gli appunti**. Un piccolo indexer dà un punteggio ai token
passati, e l'attention vera si calcola solo sui più rilevanti (l'ordine di
grandezza: qualche centinaio, scelti in un contesto da un milione).
Comprimere risponde a «quanto pesa la cache»; selezionare risponde a «quanti
confronti faccio», ed è questa, più della compressione, la mossa che doma
davvero il quadrato sul contesto lungo. Anche qui la matematica cambia, e la
qualità va rivalidata.
**Gli stessi byte per più utenti: il batching.** Se la generazione è limitata
dal trasporto dei parametri, e i parametri sono gli stessi per tutti, allora
servire più richieste insieme è quasi gratis: si rileggono gli scaffali una
volta e si fanno i conti per dieci tavoli di lettura. È il motivo per cui il
throughput di un servizio cresce molto prima che la latenza del singolo
utente peggiori, ed è la vera economia di scala dei provider: il costo per
token crolla col carico. E non è più solo cosa da grandi provider: lo stesso
motore locale può tenere residenti più sessioni indipendenti e servirle
insieme, così una macchina in casa o in azienda risponde a più conversazioni
rileggendo gli scaffali una volta sola. La coppia latenza/throughput va tenuta distinta
proprio qui: ottimizzare l'una non è ottimizzare l'altro, e i benchmark che
non dichiarano quale stanno misurando non stanno misurando niente.
**Non rifare il prefill: la cache che persiste.** Gli appunti, una volta
presi, si possono anche salvare. Se molte richieste condividono lo stesso
prefisso (il system prompt, i documenti di un agente, la conversazione fin
qui), la KV cache di quel prefisso si calcola una volta e si ricarica,
saltando il prefill. ds4 la spinge fino in fondo: cache su disco, indicizzata
dall'hash del prompt, con la scommessa dichiarata che gli SSD moderni sono
abbastanza veloci da farne un cittadino di prima classe. È la stessa idea del
«prompt caching» dei provider API, vista dal lato del motore.
**Muovere i byte mentre si calcola: la memoria gerarchica.** Quando il
modello non entra nella memoria veloce, l'alternativa alla resa è la
gerarchia: tenere vicino ciò che serve sempre, e far viaggiare il resto
(dalla RAM alla VRAM, o dall'SSD alla RAM) mentre i circuiti lavorano su
altro, nascondendo la latenza dietro il calcolo. ds4 lo fa in entrambe le
direzioni (cache gestita tra RAM e VRAM per i contesti enormi, streaming
degli esperti da SSD per i modelli più grandi della RAM), con due finezze
che dicono tutto del mestiere: la cache degli esperti rifiuta di esistere se
non può essere bloccata in RAM (meglio piccola e prevedibile che grande e
soggetta a paging), e il precarico parte da una hotlist compilata nel
programma, la classifica degli esperti più richiesti misurata su carichi
reali: perfino tra gli esperti, alcuni byte sono più caldi di altri.
L'effetto d'insieme dello streaming è un cambio di categoria: la RAM smette
di essere una soglia (il modello ci sta, o non ci sta) e diventa una scala
di velocità.
Il decoding speculativo è parente della stessa famiglia: qualcuno propone
qualche token, il modello grande li verifica in un colpo solo, trasformando
passi sequenziali in un passo parallelo. Nel motore convivono due modi di
procurarsi le proposte, ed è istruttivo. Nel primo il suggeritore non è
affatto un secondo modello: DeepSeek è addestrato anche a proporre il token
dopo il prossimo (multi-token prediction, MTP), quindi il modello fa da
suggeritore di sé stesso e al motore resta solo verificare. Nel secondo
(DSpark, un draft ausiliario di poco più di cinque gigabyte rilasciato
apposta) un modellino separato legge lo stato interno del grande e propone
fino a cinque token avanti. In entrambi i casi il grande resta l'unico
giudice: un suffisso rifiutato o poco sicuro ricade sulla decodifica
ordinaria. E in entrambi i casi il progetto ammette senza giri di parole che
per ora il guadagno è marginale, con la ragione coerente con tutto questo
testo: la speculazione paga quando il passo singolo spreca banda, ma su
un'architettura che ha già ridotto i byte per token (esperti, cache
compressa, indexer) al trucco resta poco margine; le continuazioni prevedibili
come il codice ci guadagnano di più, i prompt poveri di struttura possono
perfino rallentare. Anche le ottimizzazioni hanno un budget, ed è sempre lo
stesso: i byte che il passo singolo lascia sul tavolo.
La morale della sezione non è il catalogo: è che le sei mosse si compongono,
e che ognuna dichiara il suo prezzo (memoria, qualità da rivalidare,
complessità). Quasi ogni ottimizzazione dell'inferenza è una risposta diversa
alla stessa domanda, dove stanno i byte e quanto costa muoverli; quando se ne
incontra una nuova, chiedersi a quale delle sei famiglie appartiene è più
utile che impararne la sigla.
La mappa della sezione: sei mosse attorno alla stessa domanda. A sinistra si riducono i byte da muovere (scriverli più corti, attivarne meno, far nascere piccoli gli appunti); a destra si muovono meglio quelli che restano (una lettura degli scaffali per molti lettori, il lavoro già fatto che si riusa, il trasporto nascosto sotto il calcolo). Nessuna mossa è gratis: il prezzo dichiarato è parte della mossa.
## 9. Dentro il codice: quattro zoom
Le mosse si possono anche guardare da molto più vicino, al livello a cui
vivono davvero: il codice. Questa sezione scende lì, quattro volte: su tre
delle sei mosse (gli appunti che persistono, gli scaffali in streaming, la
quantizzazione) e sul mestiere distribuito incontrato nella sezione 6,
scelte perché ognuna
insegna qualcosa che da fuori non si vede. Chi preferisce non scendere può
saltare alla sezione che segue senza perdere il filo. La prova a terra è
sempre la stessa, DwarfStar, per il motivo detto alla sua presentazione: in
un motore su misura le scelte stanno nude nel codice.
**Gli appunti su disco, da vicino.** La cache persistente sembra un'idea
semplice, gli appunti si salvano e si ricaricano; il codice mostra quanta
cura serve perché funzioni davvero. La chiave di ogni file è l'impronta del
prompt preso come sequenza di byte, non come sequenza di token, e la scelta
ha un motivo sottile: il modello può aver generato un token il cui testo,
rimandato indietro dal client al giro successivo, si rispezza in due token
diversi; i byte invece coincidono, quindi il confronto sui byte trova
comunque il prefisso riusabile, e si ri-tokenizza solo il seguito nuovo.
Dentro il file, prima degli appunti veri e propri, c'è un'intestazione di
48 byte documentata campo per campo:
```
"KVC" versione bit di quantizzazione degli esperti
motivo del salvataggio (nuovo / continuazione / sfratto / spegnimento)
quanti token copre quanti riusi ha avuto (i «colpi»)
quando è nata quando è stata usata l'ultima volta
```
e non è pedanteria: è ciò che serve allo sfratto per essere giusto. Quando
lo spazio finisce, la politica non è un semplice «via il più vecchio»: i
colpi si raffreddano con una mezza vita di sei ore, così un prefisso usato
spessissimo ieri non scavalca per sempre uno usato poco fa. C'è poi una
finezza che è filo conduttore allo stato puro: questi file si leggono con
la lettura ordinaria, non con la mappatura in memoria usata per il modello,
e il motivo è dichiarato: non aggiungere altre mappe di memoria virtuale a
un processo che ne ha già una enorme. Perfino la scelta della chiamata di
sistema è una decisione su dove stanno i byte. Su questa base il progetto
costruisce il passo successivo, che qui basta nominare: il suo agente
nativo (un assistente da terminale costruito direttamente sopra il motore)
tratta ogni conversazione come il suo file di appunti, e passare da una
sessione all'altra è aprire un file diverso, senza rifare nessun prefill.
Se la memoria della conversazione è un file, la conversazione diventa
portatile come un file.
**Gli scaffali in streaming, da vicino.** Lo streaming da SSD della sezione
8 ha un'interfaccia che è già mezza lezione: non si elencano file né
esperti, si dichiara un budget, quanta memoria dedicare alla cache degli
esperti, e il piano lo fa il motore. Prima mette al riparo ciò che deve
stare in RAM comunque (le parti del modello che lavorano su ogni token),
poi traduce i gigabyte dichiarati in un numero di esperti residenti,
scegliendoli a partire dalla hotlist vista tra le ottimizzazioni; tutto il
resto abita sull'SSD e viene letto quando il router lo chiama, mentre i
circuiti lavorano su altro. La disciplina resta quella già detta, o
bloccata in RAM o niente; quel che lo zoom aggiunge è il contratto: chi usa
la macchina non gestisce gli scaffali, dichiara quanto spazio ha, e il
motore ricava il miglior piano possibile da quel vincolo. Il risultato è lo
spettro di velocità della sezione 8, reso operativo: più budget, più
esperti residenti, più token al secondo; meno budget, si scende di marcia,
ma si gira. Messi accanto, lo zoom sugli appunti e questo dicono la stessa
cosa da due lati: in questo progetto il disco non è un ripiego, è memoria
di prima classe; gli appunti ci si salvano, gli scaffali ci abitano, e la
RAM smette di essere un requisito d'ingresso per diventare una scelta di
velocità.
**La quantizzazione, spinta al singolo piano.** La gerarchia di precisioni
vista tra le ottimizzazioni si può spingere più in là, ed è già successo,
con un esperimento istruttivo. La domanda: tutti i piani dell'edificio
soffrono la compressione allo stesso modo? Lo strumento: un attrezzo che
non dequantizza nulla; legge due versioni già compresse dello stesso
modello, una a 2 bit e una a 4, usa la prima come base e vi copia dentro,
byte per byte, i soli esperti degli ultimi sei piani presi dalla versione
a 4. Il risultato: un file da ~91 GB invece dei ~153 della versione a 4 bit
piena che, nei controlli di concordanza delle risposte, si comporta
statisticamente più vicino al 4 pieno che al 2 puro. E la misura è
dichiarata per quello che è, con le parole del progetto: non è un
benchmark, è un segnale utile. Ipotesi, intervento chirurgico a colpi di
byte, misura contro un riferimento, onestà sul valore della misura: il
metodo, in quattro passi.
**Tagliare l'edificio, in due modi.** L'inferenza distribuita della sezione 6
ha un protocollo, e il protocollo racconta l'architettura meglio di uno
schema. Il modello si affetta per piani contigui: una macchina possiede il
piano terra e i primi trenta piani, l'altra i restanti fino all'uscita; il
coordinatore tiene per sé tokenizzazione, estrazione del token e prompt.
Ogni passo di generazione è così un viaggio verticale che attraversa le
macchine: lo stato del token sale i piani della prima, attraversa la rete,
sale i piani della seconda, e i punteggi tornano al coordinatore. È,
fisicamente, il viaggio di rete in più per token della sezione 6. I
lavoratori si presentano dichiarando identità del modello, profilo di
quantizzazione e fetta di piani; ogni pacco di lavoro porta con sé le
impronte della storia dei token prima e dopo la propria campata, così ogni
macchina verifica di stare lavorando sulla stessa conversazione. E la
gestione degli errori distingue due mali diversi: se le impronte non
tornano (i byte sono arrivati, ma la storia è sbagliata) si rigioca la
storia dei token sulla stessa rotta; se la connessione muore (i byte non
sono arrivati affatto) si abbandona la rotta e si aspetta un'altra
macchina. E gli appunti? Restano dove nascono: ogni macchina tiene la KV
cache dei propri piani, perché spedirla a ogni passo costerebbe più del
ricalcolo che evita; a viaggiare è solo lo stato del token, al confine tra
le fette. Quando una sessione si salva su disco, il coordinatore chiede a
ciascuna macchina il pezzo dei suoi piani e ricompone un file solo, lo
stesso degli appunti visti nel primo zoom: l'edificio è affettato, la
memoria della conversazione no. Il resto, i formati dei pacchi e gli accorgimenti che tengono
piena la catena di montaggio del prefill, sta nel codice, leggibile; e la
documentazione chiude con l'onestà di rito: niente cifratura né
autenticazione, macchine fidate, stessa versione del programma dai due
lati. Un protocollo giovane che dichiara i suoi limiti. A livello di rete,
nel codice, sta tutto in pochi tipi di pacchetto: il lavoratore si presenta
con un `HELLO` (identità del modello, famiglia, profilo di quantizzazione,
fetta di piani), il coordinatore manda `WORK` e riceve `RESULT`, e a ogni
pacco viaggia l'impronta a 64 bit della storia dei token prima e dopo la
campata, quella che smaschera la macchina che sta lavorando sulla
conversazione sbagliata.
C'è un secondo modo di tagliare, e taglia nell'altra direzione. Invece di
dare piani interi a macchine diverse, si spacca in due il lavoro *dentro
ogni piano*, e le due macchine lavorano lo stesso token nello stesso
momento, scambiandosi somme parziali di una manciata di kilobyte ai
«cancelli» interni al calcolo. I due tagli servono a cose diverse:
```
PER PIANI (pipeline) PER LARGHEZZA (tensor parallel)
┌─────┐ A: i primi piani ┌──┬──┐ A e B sullo STESSO piano,
│ ▓▓▓ │A ↑ un token sale │AA│BB│ sullo STESSO token, insieme
│ ▓▓▓ │ attraversa la rete │AA│BB│ ogni piano spaccato a metà
══════════ 1 hop / token │AA│BB│ scambio: somme parziali,
│ ░░░ │B poi i piani di B └──┴──┘ pochi KB a ogni «cancello»
│ ░░░ │
└─────┘ serve a: ENTRARCI serve a: LATENZA più bassa
(sommare la RAM) + modello intero RESIDENTE
```
La logica è tutta nel filo conduttore. Con una macchina sola e un modello
troppo grande, gli esperti fanno la spola dall'SSD, e si va piano; mettersi
in due in larghezza vuol dire che ogni macchina tiene residente **metà**
degli esperti e non tocca mai la metà dell'altra, così la RAM sommata regge
il modello intero e a viaggiare sono solo quei pochi kilobyte per piano. È
la differenza col taglio per piani, dove le due macchine si sommano sì la
RAM ma pagano un viaggio di rete per ogni token: qui i viaggi sono tanti ma
minuscoli, e invece di far entrare un modello più grande abbassano la
latenza. La misura lo dice senza appello, sullo stesso modello che da solo
non ci sta:
```
GLM 5.2, 188 GB due Mac (tensor) un Mac (streaming da SSD)
generazione ~17 token/s ~5 token/s
prefill (4096 token) ~94 token/s ~3–5 token/s
memoria tutto residente esperti letti dall'SSD
```
Da qui si capisce anche che cosa sia trasferibile. La scommessa del
progetto è un modello alla volta, non un modello per sempre: il modello
potrà cambiare, il vincolo resta (inferenza locale credibile su macchine
personali di fascia alta). E il trapianto è già avvenuto due volte, dentro
il progetto stesso: quando il perimetro si è allargato agli APU consumer il
motore non è stato generalizzato, si sono riscritti i kernel per il nuovo
hardware riusando il formato dei pesi, la cache, la filiera di test; e quando
è arrivato GLM 5.2, un modello di un'altra famiglia, si sono aggiunti i suoi
pezzi su misura (i suoi kernel, il suo layout di pesi, le sue fixture di
qualità) senza smontare il resto. Hardware nuovo e modello nuovo: la stessa
mossa, nelle due direzioni in cui il recinto poteva spostarsi.
Nel codice si vede perfino la forma diversa dei tre backend (una cartella
di kernel per i Mac, un file unico per le GPU NVIDIA, una collezione di
header per gli APU AMD): la matematica è la stessa, la forma del codice
segue l'hardware. È il senso pratico della specializzazione come metodo:
chi ha davanti un hardware diverso, o domani un modello diverso, può rifare
la parte su misura conservando le trovate, perché le trovate vivono sopra i
kernel, non dentro.
I quattro zoom condividono uno spirito, ed è il contenuto vero di questa
sezione. In ogni punto del sistema, anche il più piccolo (la chiamata di
sistema con cui si legge un file di cache, la precisione di sei piani in
cima all'edificio), c'è margine per fare meglio; e il metodo è sempre lo
stesso: si fa al meglio il dettaglio, si misura l'impatto, se l'impatto c'è
si tiene e si passa al dettaglio successivo; se non c'è, lo si dice e si
torna indietro, come per l'esperimento negativo sulle attivazioni in
viaggio o il guadagno «per ora marginale» della speculazione. C'è perfino
la mossa inversa della quantizzazione, nel backend delle GPU NVIDIA: dove
la memoria veloce avanza, alcuni pesi compressi vengono ri-espansi in un
formato più comodo per i circuiti di calcolo, spendendo byte per comprare
tempo, con un budget controllato e un ripiego dichiarato (se lo spazio non
c'è, si torna ai kernel compressi, e il programma dice perché). Nessuna
mossa è decisiva da sola: è l'accumulo disciplinato di dettagli misurati
che porta un modello da centinaia di miliardi di parametri su una macchina
sola. In un motore di inferenza ogni dettaglio ha margine per essere reso
più efficiente; il metodo è farlo al meglio, misurare l'impatto, tenere ciò
che rende e passare al dettaglio successivo. E il codice è scritto per
essere letto: chi vuole vedere il mestiere all'opera non deve fidarsi di
questo racconto, può aprire il repository e trovare le stesse idee, con i
numeri accanto.
A colpo d'occhio, allora, la superficie del progetto e le mosse di questo
testo che vi si leggono dentro:
```
DWARFSTAR (ds4): istantanea del 21 luglio 2026
modelli DeepSeek V4 (Flash e PRO) · GLM 5.2 (altra famiglia)
backend Metal (Mac) · CUDA (NVIDIA) · ROCm (APU AMD)
su misura kernel per modello+hardware · quantizzatore dedicato ·
vettori di test byte-per-byte · suite di ~90 problemi
ridurre i byte da muovere muovere meglio gli stessi byte
· quantizzazione asimmetrica · batching (anche in locale)
· Mixture of Experts · cache che persiste (su disco)
· KV cache compressa + indexer · memoria gerarchica (streaming SSD)
in due macchine (o più)
· per piani (pipeline) somma la RAM, un hop di rete per token
· per larghezza (tensor) stesso token insieme: meno latenza,
modello intero residente
ridurre i passi
· speculazione MTP (sé stesso) o DSpark (draft esterno),
guadagno per ora marginale
```
Non è la mappa di un prodotto da comprare: è l'inventario delle idee che
questo testo ha attraversato, ciascuna leggibile nel punto del codice dove
vive. La stessa cosa detta all'inizio, ora con i nomi al loro posto.
## 10. Che cosa ci si porta dal viaggio
La sintesi che si propone ha la forma di un elenco di domande e risposte:
una mappa degli argomenti trattati fin qui. Questa mappa precede le altre domande (quelle della
sezione conclusiva) che, si spera, la lettura di questo testo aiuta ad
affrontare in modo un po' più consapevole.
**Perché la risposta arriva a scatti, una parola alla volta?** Perché la
macchina è fatta così: produce un pezzettino di parola, lo accoda a quel che
c'è già, e ricomincia da capo. Non esiste una risposta già pronta che viene
trasmessa a rate: ogni pezzettino nasce da una consultazione completa degli
scaffali, e lo scatto che si vede è il ritmo vero di quelle consultazioni.
**Perché i listini distinguono le parole inviate da quelle ricevute?** Perché
sono due lavori diversi. Le parole inviate vengono digerite tutte insieme, in
parallelo: un lavoro veloce per pezzo. Le parole ricevute nascono una alla
volta, e ognuna costa una rilettura completa degli scaffali: un lavoro lento
per pezzo. Chi vende fa pagare di più il lavoro che costa di più.
**Perché una conversazione lunga costa e rallenta, anche nei momenti in cui
nessuno scrive?** Perché la conversazione vive negli appunti sul tavolo: per
ogni pezzo di testo la macchina tiene la sua riga di appunti, e il tavolo
cresce con la conversazione. Quegli appunti occupano memoria pregiata anche
da fermi, e a ogni parola nuova vanno consultati: un tavolo più pieno è una
memoria più cara e una consultazione più lenta.
**La macchina impara qualcosa da quello che le scrivo?** No, non mentre
risponde. Gli scaffali si riempiono una volta sola, prima; la conversazione
vive sul tavolo di lettura, e a fine sessione il tavolo si sgombra. Perché la
macchina imparasse davvero qualcosa di nuovo bisognerebbe rifare, almeno in
parte, il riempimento degli scaffali: un lavoro enorme, che nessuna
conversazione fa partire da sola.
**Un modello compresso è un modello peggiore?** La domanda giusta non è
quanto è stato compresso, ma dove. La conoscenza sugli scaffali non abita in
poche cifre di precisione chirurgica: è scritta in modo distribuito e
ridondante, e regge bene l'arrotondamento, purché si comprima di più dove
l'errore si può permettere e quasi per niente dove non si può. E un modello
compresso bene non si presume: si misura, sulle stesse prove dell'originale.
**Perché la stessa domanda non dà due volte la stessa risposta?** Perché
l'uscita della macchina non è una parola: è una graduatoria di probabilità su
tutte le parole possibili, e da quella graduatoria si estrae. L'estrazione ha
una manopola (nel gergo, la temperatura): tutta prudenza, sempre il candidato
in cima, risposte ripetibili ma piatte; oppure un po' di azzardo, ogni tanto
un candidato meno ovvio, risposte più varie. Di solito la manopola sta un po'
sopra lo zero, ed è per questo che due risposte alla stessa domanda si
somigliano senza coincidere.
**La macchina può girare anche in casa?** Sì, ed è meno strano di quanto
sembri: quasi tutto il mestiere raccontato fin qui lavora in quella
direzione. Di alcune macchine gli scaffali sono pubblici e si possono
copiare; si possono scrivere più fitti, stringendo di più dove l'errore si
può permettere; in molte macchine moderne, per ogni parola ne lavora solo
una piccola parte; e gli appunti possono nascere compatti e, all'occorrenza,
vivere su disco. Il risultato è una biblioteca intera dentro un computer che
si può comprare, sulla scrivania di una persona come nell'armadio di
un'azienda: più lenta di quella di un datacenter, spesso qualche passo
indietro rispetto alle più avanzate, ma completa, con i dati che non escono
dalla porta e con qualche strumento in mano che il servizio da fuori non
presta. Quanto renda davvero sul proprio caso, però, non è scritto in
nessuna scheda: è una prova che si può fare, con le domande della sezione
conclusiva in mano; e riguarda chiunque abbia opportunità e necessità, la
curiosità di una persona come l'hardware e le competenze di
un'organizzazione.
## 11. Le domande che contano
Resta il punto dichiarato all'inizio: a che cosa serve tutto questo a chi non
scrive kernel, ma deve scegliere, valutare, o anche solo leggere l'attualità
senza farsi trascinare. Serve a sapere quali domande fare, e la lista è
corta.
**Quanta memoria serve, e per che cosa?** (Parametri totali, non attivi; più
la KV cache al contesto che si intende usare davvero: è la domanda che decide
se un sistema sta in una macchina, in una GPU, in un telefono.) **Il carico è
da prefill o da generazione?** (Documenti lunghi e risposte brevi sono un
mestiere; chat lunghe e risposte lunghe un altro; i colli di bottiglia sono
opposti.) **Che quantizzazione, e validata come?** (Il numero di bit non
basta: la domanda è dove si è compresso e con quali controlli di qualità.)
**Latenza o throughput?** (Per un utente singolo conta la prima; per un
servizio il secondo; i numeri di marketing scelgono sempre il più
fotogenico.) **E il contesto lungo, quanto costa davvero?** (Non «quanto
contesto supporta», ma quanti byte di appunti si porta dietro e chi li paga.)
Sono domande sulla memoria, quasi tutte. Non è un caso, ed è il modo in cui
questo testo vorrebbe essere ricordato: per valutare un sistema non serve
sapere tutto, servono poche domande giuste, e sono quasi tutte domande sui
byte, su dove stanno e su quanto costa muoverli.
C'è un confronto concreto in cui queste domande si fanno sentire tutte
insieme: il modello servito in cloud contro il modello fatto girare in casa.
Il divario di qualità va detto senza sconti: i modelli di frontiera vivono
nei datacenter di chi li ha addestrati, e ciò che gira su una macchina
propria di solito insegue, con mesi o anni di ritardo. Ma le sei mosse della
sezione 8 stanno spostando la finestra: quantizzazione asimmetrica,
MoE sulle macchine a memoria unificata, cache su disco sono esattamente le
tecniche che portano un modello da centinaia di miliardi di parametri su una
macchina sola, ed è la ragione per cui un motore come ds4 può esistere. Le
domande di questa sezione sono il modo tecnico di decidere il confronto:
quanta memoria, quale carico, quale quantizzazione validata come, quale
latenza serve davvero. E per chi fa girare in casa, il costo ha anche unità
domestiche, calore, rumore, batteria: non a caso un motore locale espone una
manopola che scambia velocità con silenzio.
Il resto della decisione non è tecnico, ed è bene dichiararlo: dove vanno i
dati (in locale non escono), chi può cambiare prezzo, condizioni o modello da
un giorno all'altro (il lock-in), che cosa significhi per un'azienda o per un
paese dipendere dall'inferenza altrui (la sovranità è anche questo), e se
l'inferenza diventerà una commodity o resterà un servizio di pochi. Sono
poste in gioco che meritano un discorso a parte, non un inciso; qui basta il
punto di contatto: chi le affronta senza le domande di questa sezione sta
scegliendo al buio, perché cloud e locale non sono due prezzi, sono due
profili di byte.
C'è infine una lettura di DwarfStar che vale la pena esplicitare, perché
tiene insieme le due metà della decisione, e il progetto la suggerisce,
nemmeno troppo velatamente: un motore su misura, spinto fino in fondo su un
modello solo, è prima di tutto uno **strumento di misura**. Stabilisce dove
arriva davvero l'inferenza locale, oggi, su macchine che si possono
comprare. All'inferenza locale si arriva per strade diversissime: perché è
l'unica soluzione possibile, perché è un compromesso accettabile o una tappa
di passaggio, per un'opportunità da costruire, per una delle poste in gioco
appena dette (la sovranità, la privacy), per avere in mano strumenti che il
cloud non espone (lo steering dell'interludio); la lista è lunga apposta.
Ma qualunque sia la strada, la scelta sensata ha lo stesso prerequisito:
sapere che cosa si può ottenere davvero, misurato e non immaginato.
Scartare la via locale solo perché oggi esistono soluzioni più comode o
apparentemente più ovvie significa rinunciare a conoscerne le potenzialità,
e in certi scenari quella via potrebbe rivelarsi l'unica percorribile.
Vale allora una regola semplice, che è il senso di tutto questo testo applicato
a una decisione sola: un'opzione mai misurata non è un'opzione, è una speranza.
La biblioteca, per chiudere il cerchio. I parametri sono scaffali riempiti
una volta e mai più toccati; rispondere è consultarli, un pezzettino di
parola alla volta, con gli appunti sul tavolo per non rileggere tutto a ogni
passo; e quasi tutta l'ingegneria vista qui è il mestiere di far funzionare
una biblioteca immensa servita da corridoi stretti: scrivere più fitto,
avvicinare gli scaffali, servire più lettori con lo stesso giro. Quando
arriverà la prossima sigla (arriverà), le domande da farle sono già sul
tavolo.
Resta una misura che questo testo prende in prestito dal racconto energetico
da cui è partito, e che lascia volutamente aperta: lì si concludeva che la
metrica adeguata non è l'energia per token, è **l'energia per risultato
giusto**. Le tecniche viste qui abbassano tutte il costo del token; se quel
token serva davvero, nessuna delle grandezze di questo testo sa dirlo. Ed
è il confine onesto su cui fermarsi: sapere dove stanno i byte e quanto costa
muoverli dice quanto costa una risposta; quanto vale è un'altra domanda, e
resta a chi la legge.
## Riferimenti
- A. Vaswani et al., *Attention Is All You Need*, 2017 —
[arxiv.org/abs/1706.03762](https://arxiv.org/abs/1706.03762)
- J. Su et al., *RoFormer: Enhanced Transformer with Rotary Position
Embedding*, 2021 — [arxiv.org/abs/2104.09864](https://arxiv.org/abs/2104.09864) (le rotary
embeddings/RoPE citate in §4: la posizione come rotazione di domanda ed
etichetta)
- R. Sennrich, B. Haddow, A. Birch, *Neural Machine Translation of Rare Words
with Subword Units*, ACL 2016 — [arxiv.org/abs/1508.07909](https://arxiv.org/abs/1508.07909) (la
tokenizzazione sotto-parola di §1: il vocabolario chiuso che sa comunque
scrivere tutto, ricavato dai dati con un criterio di compressione; il
metodo BPE adattato da P. Gage, *A New Algorithm for Data Compression*,
1994)
- P. Michel, O. Levy, G. Neubig, *Are Sixteen Heads Really Better than One?*,
NeurIPS 2019 — [arxiv.org/abs/1905.10650](https://arxiv.org/abs/1905.10650) (le teste dell'attention di
§4: molte si possono togliere senza danno; con E. Voita et al., *Analyzing
Multi-Head Self-Attention*, ACL 2019 — [arxiv.org/abs/1905.09418](https://arxiv.org/abs/1905.09418),
per le poche teste con un ruolo leggibile e le molte senza)
- D. Rumelhart, G. Hinton, R. Williams, *Learning representations by
back-propagating errors*, Nature 1986 —
[doi.org/10.1038/323533a0](https://doi.org/10.1038/323533a0) (la retropropagazione di §2: le derivate
di tutti i parametri in una sola discesa)
- C. E. Shannon, *A Mathematical Theory of Communication*, 1948 —
[doi.org/10.1002/j.1538-7305.1948.tb01338.x](https://doi.org/10.1002/j.1538-7305.1948.tb01338.x) (la sorpresa −log p di
§1, la stessa grandezza del post sul diavoletto)
- T. Mikolov et al., *Efficient Estimation of Word Representations in Vector
Space*, 2013 — [arxiv.org/abs/1301.3781](https://arxiv.org/abs/1301.3781) (word2vec, ora citato in §1:
le regolarità lineari degli embedding e l'esempio re/regina)
- J. Kaplan et al., *Scaling Laws for Neural Language Models*, 2020 —
[arxiv.org/abs/2001.08361](https://arxiv.org/abs/2001.08361) (per il cenno alle scaling laws in §1)
- T. Dao et al., *FlashAttention: Fast and Memory-Efficient Exact Attention
with IO-Awareness*, 2022 — [arxiv.org/abs/2205.14135](https://arxiv.org/abs/2205.14135)
- DeepSeek-AI, *DeepSeek-V2* (introduzione della Multi-head Latent
Attention), 2024 — [arxiv.org/abs/2405.04434](https://arxiv.org/abs/2405.04434) (l'origine dell'idea
degli appunti compressi citata in §8 come MLA)
- DeepSeek-AI, model card di *DeepSeek V4 Flash* —
[huggingface.co/deepseek-ai/DeepSeek-V4-Flash](https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash) (la fonte dei numeri
del modello citati nel testo; nota terminologica: il model card chiama il
meccanismo CSA+HCA, Compressed Sparse Attention + Heavily Compressed
Attention, dove la letteratura dice MLA; l'indexer di §8 è la parte
«Sparse» di CSA)
- C. Olsson et al., *In-context Learning and Induction Heads*, Anthropic
2022 —
[transformer-circuits.pub/2022/in-context-learning-and-induction-heads/](https://transformer-circuits.pub/2022/in-context-learning-and-induction-heads/)
(il candidato meccanico citato nell'interludio)
- Specifica del formato GGUF (ecosistema ggml/llama.cpp) —
[github.com/ggml-org/ggml/blob/master/docs/gguf.md](https://github.com/ggml-org/ggml/blob/master/docs/gguf.md)
- Open Source Initiative, *The Open Source AI Definition* (OSAID), v1.0,
2024 — [opensource.org/ai/open-source-ai-definition](https://opensource.org/ai/open-source-ai-definition) (la
definizione formale di AI open source citata nel panorama dei modelli:
pesi, codice e informazioni sui dati, con piena libertà d'uso)
- Y. Leviathan et al., *Fast Inference from Transformers via Speculative
Decoding*, 2022 — [arxiv.org/abs/2211.17192](https://arxiv.org/abs/2211.17192)
- E. M. Bender, T. Gebru et al., *On the Dangers of Stochastic Parrots: Can
Language Models Be Too Big?*, FAccT 2021 —
[dl.acm.org/doi/10.1145/3442188.3445922](https://dl.acm.org/doi/10.1145/3442188.3445922) (l'origine dell'etichetta,
per l'interludio)
- T. Brown et al., *Language Models are Few-Shot Learners*, 2020 —
[arxiv.org/abs/2005.14165](https://arxiv.org/abs/2005.14165) (l'in-context learning citato
nell'interludio)
- K. Li et al., *Emergent World Representations: Exploring a Sequence Model
Trained on a Synthetic Task*, ICLR 2023 —
[arxiv.org/abs/2210.13382](https://arxiv.org/abs/2210.13382) (l'esperimento Othello dell'interludio)
- A. Templeton et al., *Scaling Monosemanticity: Extracting Interpretable
Features from Claude 3 Sonnet*, Anthropic 2024 —
[transformer-circuits.pub/2024/scaling-monosemanticity/](https://transformer-circuits.pub/2024/scaling-monosemanticity/) (le feature,
e l'esperimento del ponte tenuto acceso)
- A. Arditi et al., *Refusal in Language Models Is Mediated by a Single
Direction*, 2024 — [arxiv.org/abs/2406.11717](https://arxiv.org/abs/2406.11717) (una direzione per un
comportamento intero: la base dello steering)
- S. Sanfilippo (antirez), *DwarfStar (ds4)*, motore di inferenza in C per
DeepSeek V4 Flash e GLM 5.2, licenza MIT — [github.com/antirez/ds4](https://github.com/antirez/ds4)
(lo stato del progetto e tutti i suoi numeri citati nel testo sono
un'istantanea verificata il 21 luglio 2026: il progetto si muove in fretta,
il repository è la fonte aggiornata)
- [«Costo energetico di un modello linguistico»](/it/02-articolo/), su questo
sito (il costo dell'inferenza, la «memoria vuota», il rapporto
calcolo/trasporto)
- M. Horowitz, *Computing's energy problem (and what we can do about it)*,
ISSCC 2014 — [doi.org/10.1109/ISSCC.2014.6757323](https://doi.org/10.1109/ISSCC.2014.6757323) (il rapporto
100-700x tra accesso alla memoria e calcolo richiamato in §6: la stessa
fonte del racconto energetico)