Un LLM è una funzione che riceve del testo e restituisce una probabilità per ogni possibile token successivo. Tutto il resto — il «sapere», la capacità di rispondere, il ragionamento — è il risultato di come quella funzione viene aggiustata, numero per numero, su quantità enormi di esempi. La guida risponde a quattro domande, nell'ordine in cui conviene farsele: come una frase entra nel modello e ne esce il token successivo, con un modello in miniatura da seguire numero per numero; come un errore su un singolo token diventa una modifica a miliardi di pesi; come sono fatti i dati; perché servono più fasi di addestramento e che problema risolve ciascuna. I dettagli tecnici (tokenizer, attention, RoPE, i conti dei 7 miliardi) stanno in una parte di approfondimento alla fine, da leggere quando il quadro è chiaro.
Parte I
Scrivo «il gatto nero dorme sul» e il modello completa con «divano». Per farlo deve aver usato tutta la frase: «dorme» gli dice che si parla di un posto in cui si dorme, «sul» che manca un nome di luogo. In che modo, concretamente, cinque parole diventano quella previsione?
Per rispondere non basta descrivere il transformer: bisogna arrivarci. Questo capitolo parte dal modo più ingenuo di prevedere una parola (contare), mostra dove si rompe, e introduce un'idea alla volta — i vettori, la rete neurale, la memoria, l'attention — ciascuna come rimedio al limite della precedente. Alla fine il transformer non sarà un oggetto misterioso ma l'ultimo passo di una sequenza di rimedi, e un modello in miniatura lo eseguirà numero per numero sulla nostra frase.
Qualunque modello di linguaggio comincia allo stesso modo, perché una funzione matematica non può lavorare su lettere. Primo passo: la frase viene spezzata in token, pezzi di testo presi da un vocabolario fisso, e ogni token è sostituito dal suo numero d'ordine nel vocabolario, l'ID. In un modello vero i token sono frammenti di parola (il vocabolario ha 30.000–250.000 voci); nei nostri esempi, per leggibilità, un token è una parola intera. «il gatto nero dorme sul» diventa [1, 2, 4, 5, 7].
Secondo passo: ogni ID viene sostituito da un vettore, cioè una lista di d numeri, letta da una tabella che ha una riga per ogni token del vocabolario: la matrice di embedding E. L'ID 2 diventa la riga 2 di E. Perché non lavorare direttamente con gli ID? Perché un ID è un'etichetta: che «gatto» sia 2 e «cane» sia 3 non dice che si somigliano. Un vettore invece può dirlo: se «gatto» e «cane» hanno vettori simili, tutto ciò che il modello impara sull'uno vale anche per l'altro. Vedremo tra poco che è proprio questo il motivo per cui i vettori servono.
Un esempio con due soli numeri rende l'idea concreta. Immagina vettori da due soli numeri, ognuno con un significato preciso: il primo dice quanto è regale, il secondo quanto è femminile, tutti e due da 0 a 1.
| Parola | Quanto è regale | Quanto è femminile | Vettore |
|---|---|---|---|
| uomo | 0,1 | 0,1 | [0,1 ; 0,1] |
| donna | 0,1 | 0,9 | [0,1 ; 0,9] |
| re | 0,9 | 0,1 | [0,9 ; 0,1] |
| regina | 0,9 | 0,9 | [0,9 ; 0,9] |
Ora il famoso conto re − uomo + donna, un passo alla volta.
Calcolo · re − uomo + donna
Sommare e togliere numeri, qui, vuol dire aggiungere e togliere caratteristiche. Nel laboratorio puoi rifare il conto sul disegno e provarlo con altre coppie.
Premi i pulsanti da 1 a 4 nell'ordine e guarda dove si sposta il punto; poi scegli un'altra coppia e ripeti.
I numeri sono inventati, scelti perché il conto torni esatto. In un modello vero il vettore ha migliaia di numeri senza nome, e il conto arriva solo vicino alla parola giusta.
E nei modelli veri? Il vettore di ogni token non ha 2 numeri ma migliaia. Quanti, lo decide chi progetta il modello prima di allenarlo, ed è un compromesso: più numeri lasciano spazio a più sfumature ma costano più calcoli e più memoria. GPT-2 piccolo ne usa 768, i modelli da 7 miliardi di pesi come Llama 2 e Mistral 4.096 (il valore che ritroverai negli esempi di questa guida, che usa come modello di riferimento Llama 2 7B: è aperto, di una taglia che si può eseguire sul proprio computer e ben documentato), GPT-3 12.288. Si scelgono numeri «tondi» per il computer, come 4.096 = 212, perché le schede grafiche fanno i conti a blocchi di 64 o 128 numeri e così non sprecano spazio.
Nessuno dà a questi numeri un nome come «regale» o «femminile»: all'inizio sono numeri a caso, e il modello li sistema da solo durante l'allenamento, leggendo moltissimo testo, con il ciclo del capitolo 2. Alla fine le parole usate in modo simile hanno vettori simili, e alcune direzioni prendono un significato, ma solo in modo approssimato: per questo re − uomo + donna arriva vicino a regina, non esattamente sopra.
Fin qui nessuna previsione: abbiamo solo una lista di punti in uno spazio a d dimensioni, uno per token, ciascuno ignaro degli altri. Il problema da risolvere è: data questa lista, produrre una probabilità per ogni token del vocabolario come prossimo token. Tutto ciò che segue sono modi diversi di costruire questa funzione.
Se dovessi prevedere la parola successiva senza nessuna teoria, che cosa farei?
Conterei. Prenderei un testo molto lungo e, per ogni parola, annoterei quante volte è seguita da ciascun'altra: dopo «il» viene «gatto» 40 volte, «cane» 35, «pesce» 10… Per prevedere, guardo l'ultima parola e leggo la riga corrispondente della tabella, trasformata in percentuali. È un modello a bigrammi (coppie di parole), ed è esattamente il modello del capitolo 3: una tabella 15 × 15, riga = parola attuale, colonna = parola successiva, e la «previsione» è leggere una riga. Funziona, e in un certo senso contiene già i passi 1 e 5 del transformer: dal testo agli ID, e da una riga di numeri a una distribuzione di probabilità.
Ma dopo «sul» questo modello propone «divano», «letto», «tavolo» e «tetto» con le stesse proporzioni che ha visto nel testo, qualunque cosa ci fosse prima. Non sa che la frase parlava di dormire. Per saperlo dovrebbe guardare più indietro: contare le terne (dopo «dorme sul» viene…), le quaterne, e così via. E qui si rompe.
Due motivi, e il transformer nasce per rispondere a entrambi.
1 · La tabella esplode. Con un vocabolario di V token e un contesto di n token, le combinazioni possibili sono Vn. Già con 32.000 token e un contesto di appena 3, sono 32.000³ ≈ 3 × 10¹³ righe: più di quante parole esistano in tutti i testi mai scritti. Quasi tutte le combinazioni non compaiono mai nel corpus, quindi il modello non ha nulla da contare e resta muto davanti a frasi perfettamente normali. Un contesto di migliaia di token, come quello degli LLM attuali, è impensabile per una tabella.
Da dove viene Vn. Ogni riga della tabella è un contesto possibile, cioè una sequenza di n token. Per riempire la prima posizione puoi scegliere uno qualsiasi dei V token; per ognuna di queste scelte, la seconda posizione ne ammette di nuovo V; e così via fino alla n-esima. Le scelte si moltiplicano: V × V × … × V, con n fattori, cioè Vn. Con V = 3 token e n = 2 sono 3 × 3 = 9 contesti; con n = 3 sono 3 × 3 × 3 = 27. Ogni nuova parola di contesto moltiplica le righe per V, non le aumenta di V: è per questo che la crescita è esponenziale. A ogni riga poi si associano V colonne (la parola successiva), quindi le celle da riempire sono Vn+1.
Calcolo · quante righe ha la tabella
Le celle da riempire sono una per colonna: Vn+1.
Ogni riga è un contesto, ogni colonna la parola successiva. Le celle colorate sono quelle che un testo di sole 12 parole è riuscito a riempire: le altre restano a zero, e il modello non può dire nulla su quelle righe. Aumenta il contesto n e guarda la tabella allungarsi mentre le celle piene restano le stesse.
Le celle piene sono scelte a caso, solo per illustrare. Il punto è la proporzione: un testo finito riempie un numero finito di celle, mentre la tabella cresce a ogni parola di contesto in più.
2 · Non generalizza. Anche se la tabella ci stesse, «il gatto dorme sul» e «il cane dorme sul» sarebbero due righe separate, contate separatamente. Aver visto mille volte che dopo «il gatto dorme sul» viene «divano» non dice nulla su «il cane dorme sul». Un essere umano, invece, trasferisce: gatto e cane sono entrambi animali che dormono. Serve un modo per cui ciò che si impara su una parola valga anche per le parole simili, e un modo per cui la previsione dipenda dal contenuto del contesto, non dalla sua forma esatta.
I vettori del secondo passo risolvono il secondo problema: se «gatto» e «cane» hanno vettori vicini, una funzione che lavora sui vettori darà risultati vicini per i due. Resta da costruire quella funzione, che prenda in ingresso i vettori del contesto e restituisca i punteggi del token successivo. Lo strumento è la rete neurale.
Che cos'è, in concreto, una rete neurale? Ne basta una definizione da tre righe.
Un neurone è una funzione che riceve dei numeri, li moltiplica ciascuno per un proprio peso, somma tutto e applica una soglia: se la somma è negativa restituisce 0, altrimenti la somma stessa (la funzione ReLU, Rectified Linear Unit). Con i pesi giusti un neurone diventa un rilevatore: «c'è la caratteristica dormire ma non la caratteristica luogo?» è un neurone con peso +1 su «dormire», −1 su «luogo», e la ReLU che spegne tutto il resto. Uno strato è un gruppo di neuroni che leggono gli stessi ingressi, ciascuno con i propri pesi; una rete è una sequenza di strati in cui l'uscita di uno è l'ingresso del successivo. I pesi non li scrive nessuno: si parte da numeri casuali e si aggiustano con il procedimento del capitolo 2. Qualunque cosa faccia un LLM, è fatta di questi mattoni: moltiplicazioni per pesi, somme, soglie.
Esci di casa e decidi se portare l'ombrello. Senza pensarci guardi tre cose: quante nuvole ci sono, che cosa dice la previsione, quanto vento tira. Ognuna la puoi misurare da 0 (per niente) a 1 (moltissimo).
Non tutte contano allo stesso modo. La previsione di pioggia conta molto, le nuvole un po' meno. Il vento conta al contrario: con il vento forte l'ombrello si rovescia, e ti passa la voglia di portarlo. Quanto conta ogni cosa si chiama peso: un numero grande se conta molto, vicino a zero se conta poco, negativo se spinge verso il no.
Un neurone artificiale fa esattamente questo conto. Moltiplica ogni ingresso per il suo peso, somma tutto e confronta il totale con una soglia: se il totale la supera, il neurone «si accende» e la risposta è sì. È un votante che ascolta tre consiglieri, dando a ognuno più o meno fiducia.
Muovi il cursore del vento verso destra e guarda quando il neurone si spegne; poi prova a cambiare i pesi e la soglia.
Nel disegno, lo spessore di ogni freccia è il peso: più è spessa, più quell'ingresso conta. Le frecce verdi spingono verso il sì, quelle rosse tratteggiate (peso negativo) verso il no. I numeri sono quelli veri del conto: un neurone artificiale fa proprio questo, niente di più.
Un neurone da solo sa fare poco. Il trucco è metterne tanti in fila, a strati: quello che esce dai neuroni di uno strato diventa l'ingresso dei neuroni dello strato dopo. Così il secondo strato non ragiona più su «nuvole» e «vento», ma su idee già un po' elaborate dal primo.
Nell'esempio qui sotto i tre ingressi vanno a due neuroni nel mezzo. Il primo dà molto peso a nuvole e pioggia: potremmo chiamarlo «sembra brutto tempo». Il secondo dà molto peso al vento: potremmo chiamarlo «è scomodo». Il neurone finale ascolta questi due e decide: ombrello sì o no.
Questa volta ogni neurone non dice solo sì o no, ma quanto è acceso, da 0 a 1, come una luce con il regolatore: il suo valore è il totale pesato, ma mai sotto 0 e mai sopra 1. Alla fine, se il neurone dell'ombrello arriva almeno a 0,5, si prende l'ombrello.
Scegli un tempo o muovi i tre cursori e segui i numeri che scorrono da sinistra a destra, strato dopo strato.
I pesi sono scelti a mano perché la rete dia risposte sensate; in una rete vera li trova l'allenamento (capitolo 2). Anche i nomi «sembra brutto tempo» ed «è scomodo» li abbiamo dati noi, guardando i pesi: la rete non li conosce.
Le parole che si sentono in giro. I cerchi del disegno si chiamano nodi: sono i neuroni. Le linee che li collegano si chiamano archi: ogni arco porta un peso. Questa rete ha 8 pesi (6 tra ingressi e strato di mezzo, 2 verso l'uscita). Lo strato di mezzo si dice nascosto perché non lo si vede né all'ingresso né all'uscita. Una rete con tanti strati nascosti si dice profonda: è il «deep» di deep learning. Una rete grande ha miliardi di pesi, ma ogni neurone fa sempre il conto del laboratorio: moltiplica, somma, decide quanto accendersi.
In una rete vera nessuno decide che cosa debba fare un neurone nascosto. I «significati» dei neuroni li trova la rete da sola durante l'allenamento, e spesso non hanno un nome semplice. Per vedere una rete vera, abbastanza piccola da disegnarla tutta (8 ingressi, 6 neuroni nascosti, 4 uscite, 82 pesi), apri la guida sull'auto che impara a guidare, nella sezione sulla rete neurale.
Il primo modello neurale di linguaggio (Bengio e colleghi, 2003) fa la cosa più diretta: prende i vettori degli ultimi n token, li mette uno accanto all'altro in un unico vettore lungo n × d, lo passa attraverso una rete di uno o due strati, e dall'ultimo strato esce un punteggio per ogni token del vocabolario. Risolve la generalizzazione: «gatto» e «cane» entrano con vettori simili, quindi escono con previsioni simili, e la rete ha pochi milioni di pesi invece di 10¹³ celle. Ma eredita dalla tabella un limite: la finestra è fissa. Con n = 4 il modello non vede mai il quinto token indietro, e i pesi che leggono «il token due posizioni fa» sono diversi da quelli che leggono «il token tre posizioni fa», quindi la stessa parola spostata di un posto è, per la rete, un ingresso completamente diverso.
Facciamolo con numeri piccoli, perché qui si nasconde la prima domanda che viene a tutti: come si mettono insieme i vettori di più token? Nel modello a finestra fissa la risposta è la più semplice possibile: si concatenano. Se «il» ha vettore [1, 0] e «gatto» ha vettore [0, 1], l'ingresso della rete per il contesto «il gatto» è il vettore [1, 0, 0, 1]: le due coordinate del primo seguite dalle due del secondo. Non è una somma (la somma darebbe [1, 1], e non si saprebbe più chi era il primo e chi il secondo) e non ha nessuna proprietà matematica speciale: è mettere i numeri in fila. Il primo strato della rete deve quindi avere 4 ingressi, uno per numero.
Che cos'è W₁. Lo strato è fatto di neuroni, e ogni neurone ha un peso per ciascuno dei 4 ingressi. Se i neuroni sono 3, i pesi sono 3 × 4 = 12 numeri, e si scrivono in una tabella con una riga per neurone e una colonna per ingresso: la matrice W₁. Il conto dello strato è «ogni neurone moltiplica gli ingressi per la sua riga e somma», cioè il prodotto matrice × vettore h = W₁x: una tabella 3 × 4 per una colonna di 4 numeri dà una colonna di 3 numeri.
La regola delle dimensioni è sempre la stessa: il numero di colonne di W deve essere uguale alla lunghezza del vettore che entra, il numero di righe è la lunghezza di quello che esce.
Il secondo strato riceve i 3 numeri e deve produrre un punteggio per ciascuno dei 5 token del vocabolario: W₂ è 5 × 3. I numeri dentro W₁ e W₂ non li sceglie nessuno: qui sotto sono stati trovati con il procedimento del capitolo 2 su cinque esempi e poi arrotondati a una cifra.
Calcolo · le dimensioni della rete a finestra fissa
Vocabolario di 5 token con vettori di 2 numeri. Scegli i due token del contesto e segui il conto: concatenazione, primo strato (W₁, 3 neuroni con ReLU), secondo strato (W₂, un punteggio per token), softmax. Poi passa a una finestra di 3 token e guarda che cosa succede a W₁.
Che cosa è reale: i vettori di E, la concatenazione, i due prodotti matrice × vettore, la ReLU, la softmax. I pesi sono stati trovati con la discesa del gradiente sui cinque esempi «il gatto → dorme», «gatto dorme → sul», «dorme sul → divano», «sul divano → il», «divano il → gatto»; su contesti mai visti la rete risponde comunque qualcosa, spesso senza senso, perché cinque esempi non bastano a generalizzare. Nel modello di Bengio i vettori erano 50–100 numeri, la finestra 4–5 token e lo strato nascosto aveva centinaia di neuroni; la forma del calcolo è questa.
Perché la concatenazione non scala. Con la finestra a 3 token W₁ ha bisogno di 6 colonne, e i 12 pesi della rete a 2 token non servono più: è una rete diversa, da addestrare da capo. Con i numeri di un modello vero il problema è enorme: 100 token di contesto con vettori di 768 numeri danno un ingresso di 100 × 768 = 76.800 numeri, e un primo strato di appena 512 neuroni avrebbe 512 × 76.800 ≈ 39 milioni di pesi solo lì; con 1.000 token, dieci volte tanto.
E c'è il difetto già visto: le colonne di W₁ che leggono il primo token sono diverse da quelle che leggono il secondo, quindi la rete deve imparare due volte che cosa significa «gatto», una per posizione. È per questi due motivi che un transformer non concatena i vettori: la frase entra come una matrice con una riga per token (3 token × 4 numeri), e ogni riga passa per gli stessi pesi; il legame tra le righe lo crea l'attention, come vedremo tra due sezioni.
Calcolo · il costo della concatenazione
Come si usa tutto il contesto, lungo quanto si vuole, senza una finestra fissa?
La risposta classica è la rete ricorrente (RNN, Recurrent Neural Network): si legge un token alla volta e si tiene un unico vettore di stato, una «memoria» di dimensione fissa. A ogni token lo stato viene ricalcolato da una rete che riceve lo stato precedente e il vettore del token nuovo: stato ← f(stato, token). Dopo aver letto «il gatto nero dorme sul», lo stato dovrebbe contenere, compresso in d numeri, tutto ciò che serve per prevedere «divano». Il contesto non ha più un limite di lunghezza, e gli stessi pesi servono per ogni posizione.
Per anni è stato il modo standard di fare modelli di linguaggio (nelle varianti LSTM e GRU), e ha tre difetti che si pagano sempre di più man mano che il contesto cresce. Comprime tutto in un vettore: ciò che è stato letto cento token fa deve sopravvivere a cento aggiornamenti dello stato, e in pratica svanisce. È sequenziale: per calcolare lo stato al token 1.000 bisogna aver calcolato i 999 precedenti, uno dopo l'altro, e le GPU, che eccellono a fare migliaia di operazioni in parallelo, restano ferme. È difficile da addestrare: i gradienti del capitolo 2 devono risalire la stessa catena di mille passi, e lungo la strada si spengono o esplodono.
E se, invece di comprimere il passato in una memoria, lo tenessimo tutto a disposizione e lasciassimo che ogni token vada a guardare ciò che gli serve?
È l'idea dell'attention, ed è l'unica idea davvero nuova del transformer. Si tengono i vettori di tutti i token della frase, così come sono. Quando bisogna fare la previsione nella posizione di «sul», si costruisce il nuovo vettore di «sul» come media pesata dei vettori di tutti i token fino a lì, se stesso compreso. I pesi della media non sono fissi: vengono calcolati dal contenuto, in tre mosse.
Con i numeri del modello in miniatura, nella posizione di «sul» i pesi vengono: «il» 2%, «gatto» 2%, «nero» 2%, «dorme» 28%, «sul» 65%. Il nuovo vettore di «sul» è quindi 0,28 × vettore di «dorme» + 0,65 × vettore di «sul» + briciole: nelle dimensioni leggibili del modello, «dormire» passa da 0,30 a 0,48 e compare «luogo» 0,39 e «preposizione» 0,65. Il vettore di «sul» adesso contiene «dorme», e la contiene perché «sul» l'ha cercata, non perché una memoria l'ha trasportata fin lì. Un piccolo blocco di neuroni (lo strato successivo) legge questo vettore, trova «dormire + preposizione» e aggiunge «luogo»: il confronto finale con il vocabolario premia «divano» e «letto».
Calcolo · i pesi dell'attention nella posizione di «sul»
Perché risolve i tre difetti della memoria: niente compressione, il token a distanza mille è a disposizione quanto quello accanto, basta che la domanda lo trovi; tutto in parallelo, i pesi di ogni posizione sono prodotti scalari indipendenti, che una GPU calcola in un colpo solo per tutta la frase; gradienti brevi, da «sul» a «dorme» c'è un passo, non una catena. Il prezzo è il costo: ogni token confronta la propria domanda con tutti i precedenti, quindi il lavoro cresce con il quadrato della lunghezza della frase. È il compromesso che definisce gli LLM attuali.
Un vettore lungo o una matrice? Vale la pena fissare la differenza con il modello a finestra fissa, perché è il punto in cui si fa più confusione. Lì i vettori dei token venivano concatenati in un unico vettore lungo, e la rete aveva un ingresso per ogni numero. Qui no: la frase entra come una matrice X con una riga per token (per «il gatto dorme» con vettori di 4 numeri, 3 righe × 4 colonne), e ogni riga passa per gli stessi pesi.
Il nuovo vettore di un token è una combinazione lineare pesata delle righe: 0,06 × riga di «il» + 0,78 × riga di «gatto» + 0,15 × riga di «dorme» (è il conto del laboratorio del capitolo 9: «dorme» va a cercare il suo soggetto), con i pesi calcolati dal contenuto e diversi per ogni token e per ogni frase. Non è una somma secca (che perderebbe chi conta di più), non è una concatenazione (che fisserebbe la lunghezza): è questa media pesata la «composizione del contesto» di cui si parla a proposito dei transformer. Il calcolo completo, con tutte le matrici e le loro dimensioni, numero per numero, è nel capitolo 9, sezione D.
Calcolo · «dorme» guarda indietro
Un transformer (Vaswani e colleghi, 2017, «Attention is all you need») è una rete neurale costruita così: i token diventano vettori; poi, per N volte di seguito, un blocco fatto di due parti ricalcola ogni vettore — prima l'attention, in cui ogni token raccoglie informazione dai precedenti con la media pesata appena vista, poi un piccolo gruppo di neuroni (l'MLP, Multi-Layer Perceptron) che elabora il vettore di ciascun token per conto suo; infine l'ultimo vettore viene confrontato con ogni riga del vocabolario per ottenere i punteggi.
Il nome dice quello che fa: trasforma una sequenza di vettori in una sequenza di vettori, della stessa lunghezza, N volte, arricchendola a ogni passaggio. Niente memoria da trascinare, niente finestra fissa, niente ciclo sequenziale: solo blocchi identici impilati, con pesi diversi.
Gli LLM come GPT, Llama o Claude sono transformer di questo tipo (detti decoder-only: ogni posizione vede solo i token alla sua sinistra, perché deve prevedere il successivo). Un modello da 7 miliardi di parametri ha N = 32 blocchi e vettori di 4.096 numeri; il modello in miniatura qui sotto ha N = 1 e vettori di 8 numeri. La struttura è la stessa, ed è riassunta in cinque passi.
Due cose vanno dette subito, perché sono il cuore di tutto il documento. Primo: in questi cinque passi non c'è una sola regola scritta a mano; ci sono solo moltiplicazioni e somme tra la frase e un insieme di numeri fissi, i pesi (la tabella E, la matrice Wq che costruisce le domande, i pesi dei neuroni dell'MLP). Secondo: il training cambia solo quei numeri, mai i cinque passi. Nel modello in miniatura i pesi li abbiamo scritti noi, a mano, perché si capisse che cosa fanno; in un modello vero li trova il procedimento del capitolo 2.
Il modello qui sotto è un transformer con vocabolario di 12 token, vettori di 8 numeri e un solo blocco (attention + MLP a 7 neuroni). Per renderlo leggibile, le 8 dimensioni hanno un nome (animale, dormire, mangiare, luogo, preposizione, fine, articolo, cibo) e i pesi sono scelti a mano in modo che le dimensioni significhino qualcosa: in un modello vero le dimensioni sono 4.096, nessuna ha un nome, e i pesi li trova il training. Ma il calcolo è lo stesso dei cinque passi. Scegli una frase, clicca un token per vedere la previsione fatta in quel punto della frase, e segui i cinque pannelli: il pannello 3 è l'attention della sezione precedente con i numeri veri, il pannello 4 sono i neuroni della sezione sulla rete neurale.
Che cosa è reale e che cosa no. Reali: la lettura delle righe di E, i pesi del mescolamento (prodotto scalare tra la «domanda» dell'ultimo token e il vettore di ciascun token, poi softmax), la media pesata, il blocco di elaborazione (sette regole del tipo «se c'è X senza Y, aggiungi Z», realizzate come ReLU di un prodotto scalare, cioè esattamente come un neurone), i logit come prodotto scalare con le righe di E, la softmax. Scritti a mano: i valori di tutti questi pesi, e i nomi delle dimensioni. In un modello vero le dimensioni non si leggono e le regole non si elencano, perché emergono dal training distribuite su miliardi di numeri; ma la forma del calcolo è questa.
Nel modello in miniatura i pesi sono di tre tipi, e in un modello vero sono gli stessi tre, solo enormemente più grandi.
Un mixer audio con miliardi di manopole. All'inizio le manopole sono in posizione casuale e il suono è rumore. Il training gira ogni manopola di una frazione, miliardi di volte, finché il suono è quello giusto. Nessuno regola una manopola a mano, e nessuno sa che cosa rappresenti la singola manopola: hanno senso solo tutte insieme. Nel modello in miniatura le abbiamo regolate noi, 96 + 64 + 56 manopole, ed è per questo che le dimensioni hanno un nome; nel capitolo 3 c'è un modello con 225 manopole che si regolano da sole, e le puoi guardare mentre si muovono.
Che cosa fa in più un modello vero. Quattro cose, nessuna delle quali cambia la logica dei cinque passi:
Ciascuno di questi punti ha una sezione nel capitolo 9, con il calcolo completo dell'attention (query, key e value), la codifica della posizione (RoPE), il blocco MLP, la normalizzazione e i conti esatti dei 7 miliardi di pesi.
Il modello prevede un token. Come fa a scrivere una risposta di cento parole?
Ripetendo i cinque passi cento volte. Il token scelto viene aggiunto in fondo alla frase, e la frase allungata rientra nel modello dal passo 1: è ciò che fa il pulsante «Aggiungi il token previsto» del laboratorio. Si chiama generazione autoregressiva: ogni token prodotto diventa contesto per il successivo, finché il modello produce il token speciale di fine (<eos>, end of sequence). A ogni passo si può prendere il token più probabile (greedy) oppure estrarne uno a caso secondo le probabilità, eventualmente dopo averle rese più piatte o più appuntite con un parametro detto temperatura; è per questo che la stessa domanda può ricevere risposte diverse. Il costo è proporzionale: cento token, cento passaggi completi.
I token di un modello vero non sono parole intere: sono frammenti presi da un vocabolario di 30.000–250.000 voci costruito con un algoritmo (BPE) che fonde le sequenze di caratteri più frequenti. Le parole comuni restano intere, quelle rare si spezzano, lo spazio fa parte del token che lo segue. Qui la divisione è simulata con una regola semplice; l'algoritmo vero, eseguibile passo per passo, è nel capitolo 9.
Arrivati qui, quasi tutti si fanno le stesse sette domande. Le risposte sono brevi; non servono per andare avanti, ma tolgono di mezzo i dubbi più comuni.
«I tre numeri dell'ombrello sono i pesi?»
No, sono gli ingressi: quante nuvole ci sono, quanta pioggia è prevista, quanto vento tira. Cambiano ogni giorno, perché ogni giorno il tempo è diverso.
I pesi stanno sulle frecce e dicono quanto conta ogni ingresso. Sono della rete, non della giornata, e finito l'allenamento restano fissi. Lunedì e martedì il neurone riceve ingressi diversi, ma li moltiplica per gli stessi pesi.
Con il testo è lo stesso. I vettori dei token sono gli ingressi: quali vettori entrano dipende dalla frase che scrivi. I pesi sono la rete: la tabella E, la matrice che costruisce le domande, i neuroni dell'MLP, sempre gli stessi qualunque frase entri.
«La rete ha tanti nodi d'ingresso quanti numeri hanno tutti i vettori della frase?»
No. Una frase di 4 token porta 4 × 4.096 = 16.384 numeri, ma la rete non ha 16.384 ingressi. È un insieme di pesi fatto per un solo vettore da 4.096 numeri, e lo stesso insieme si applica a ogni vettore, tutti nello stesso momento: come quattro calcolatrici uguali che fanno lo stesso conto nello stesso istante.
Anche dentro la rete la larghezza resta quella. Tra uno strato e l'altro ogni vettore ha sempre 4.096 numeri. L'unico punto in cui si allarga è la parte in cui ogni vettore «ragiona da solo»: lì passa a 11.008 numeri e poi torna a 4.096. Non esiste un ingresso più grande o più piccolo del vettore: la rete è costruita su misura per lei.
Per questo una frase più lunga non cambia la rete. Con 4 token ci sono 4 vettori, con 400 ce ne sono 400: più vettori, stessi pesi. Cambia solo quanto lavoro c'è da fare. L'esempio con le matrici, numero per numero e con uno step di training su una frase corta e una lunga, è nel capitolo 2, nella sezione sulle frasi di lunghezza diversa.
«Dove stanno gli embedding? In memoria?»
La parola «embedding» indica due cose diverse, e stanno in posti diversi.
La prima è la tabella dei vettori: una riga per ogni token del vocabolario, 32.000 righe da 4.096 numeri, circa 131 milioni di numeri. Fa parte dei pesi del modello: sta su disco insieme a tutti gli altri pesi e viene caricata in memoria quando il modello si avvia. Durante l'uso non cambia mai.
La seconda sono i vettori del tuo prompt: le righe copiate dalla tabella per i token che hai scritto, poi trasformate strato dopo strato. Queste esistono solo mentre il modello fa i conti, nella memoria della GPU, e alla fine non vengono salvate.
C'è una riserva che dura un po' di più. Mentre scrive la risposta, un token alla volta, il modello conserva una parte dei conti già fatti per i token passati: i vettori «che cosa offro» e «che cosa passo» dell'attention (key e value, capitolo 9), per ogni strato. Quando arriva un token nuovo non deve ricalcolarli: li rilegge. Le domande invece non si conservano, perché servono una volta sola.
Questa riserva si chiama KV cache (key-value cache, la memoria delle key e dei value: le etichette e i contenuti) e viene buttata quando la risposta è finita; alcuni servizi la tengono per qualche minuto, per riusarla se il messaggio successivo comincia allo stesso modo. Quanto lavoro fa risparmiare e quanta memoria occupa lo spiega la guida sull'inferenza, nel capitolo sulla cache KV: è un approfondimento facoltativo.
«Spezzando la frase in token, non si perde l'ordine e la relazione tra le parole?»
No, per due motivi: la posizione e l'attenzione.
La posizione. Se ogni token diventasse solo il suo vettore (la riga di E), «cane morde uomo» e «uomo morde cane» darebbero gli stessi tre vettori, in un ordine diverso, e i conti dell'attention, che pesano e sommano, non avrebbero un modo diretto per sapere chi viene prima. Per questo a ogni vettore si aggiunge l'informazione su dove si trova nella frase: si chiama codifica posizionale.
In Llama si usa RoPE (Rotary Position Embedding, codifica della posizione per rotazione): le domande e le key dell'attention vengono ruotate di un angolo che dipende dalla posizione del token. Così il punteggio tra due token dipende anche da quanto sono lontani l'uno dall'altro.
L'attenzione. La relazione tra le parole non è scritta da nessuna parte all'ingresso: la rete la ricostruisce. In ogni strato i vettori si scambiano informazioni guardando a sinistra, con l'attention, e quello che ciascuno raccoglie finisce dentro il suo vettore. Alla fine il vettore di «sul» «contiene» anche il gatto e il dormire. Spezzare in token non perde la relazione, perché ogni vettore se la porta dietro.
Accendi e spegni la posizione e cambia frase: guarda se i vettori delle due frasi restano uguali e se la domanda «chi morde?» riesce a rispondere.
I vettori sono scritti a parole perché si possano leggere, e i punteggi sono inventati: il bonus di 2,2 per «viene prima di morde» è scelto perché esca 90%. Il passaggio dai punteggi alle percentuali è invece la softmax vera, fatto solo tra «cane» e «uomo». In Llama la posizione non è un'etichetta attaccata al vettore: RoPE ruota domande ed etichette di un angolo che dipende dalla posizione, in ogni strato. In un modello vero, dove ogni vettore guarda solo a sinistra, un indizio d'ordine trapela anche senza posizione (chi sta più a sinistra vede meno vettori), ma è debole e indiretto: per questo tutti i modelli moderni la aggiungono in modo esplicito.
«Se la frase è lunghissima, viene spezzata in pezzi?»
Il limite non è lo strato d'ingresso: come hai visto, la rete accetta quanti vettori vuoi, sempre con gli stessi pesi. Il limite è la finestra di contesto (context window): il numero massimo di token che il modello può guardare insieme, prompt e risposta compresi. Per Llama 2 è di 4.096 token.
Attenzione a una coincidenza scomoda: anche il vettore di ogni token, in Llama 2 7B, ha 4.096 numeri. Sono due cose diverse che per caso hanno lo stesso valore. La larghezza del vettore dice quanti numeri descrivono un token; la finestra dice quanti token possono stare insieme. In altri modelli i due numeri sono diversi: Llama 3.1 8B ha ancora vettori da 4.096 numeri, ma una finestra da 128.000 token.
Perché c'è un limite. Il primo motivo è il costo. Nell'attention ogni token confronta la sua domanda con i vettori di tutti i token che lo precedono, lui compreso. Con 4 token i confronti sono 1 + 2 + 3 + 4 = 10; con 1.000 token sono circa mezzo milione.
Calcolo · i confronti dell'attention
Si dice che i conti dell'attention crescono con il quadrato della lunghezza.
Il secondo motivo è l'allenamento. Il modello ha imparato su testi lunghi al massimo quanto la sua finestra, e oltre non sa usare bene posizioni che non ha mai visto.
Che cosa succede se il testo non ci sta. Il modello non lo spezza in pezzi collegati tra loro: guarda una finestra sola. Quello che avanza viene tagliato, oppure il programma rifiuta il testo troppo lungo. Sono i programmi costruiti intorno al modello a decidere che cosa farci stare: per esempio riassumere le parti più vecchie di una conversazione, oppure cercare nei documenti solo i pezzi utili alla domanda e mettere nel prompt quelli. Questo secondo metodo si chiama RAG (Retrieval-Augmented Generation, generazione aiutata dalla ricerca).
Se vuoi vedere come si fa in pratica, c'è la guida sull'ingegneria dei sistemi basati sui modelli linguistici: il capitolo sul retrieval per il RAG, il capitolo sulla memoria per i riassunti e per la scelta di che cosa tenere. È un approfondimento facoltativo.
I modelli più recenti hanno finestre molto più ampie: da 128.000 a oltre un milione di token. Il principio non cambia: la finestra è grande ma finita, e quello che non ci entra il modello non lo vede.
«I vettori dei token si sommano? Si concatenano? Che cosa vuol dire "comporre il contesto"?»
Sono tre operazioni diverse, e il transformer ne usa una sola.
La somma ([1, 0] + [0, 1] = [1, 1]) butta via l'informazione su chi era chi e in che ordine: «cane morde uomo» e «uomo morde cane» darebbero lo stesso vettore. Nessun modello di linguaggio la usa così com'è.
La concatenazione ([1, 0] e [0, 1] → [1, 0, 0, 1]) conserva tutto ma fissa la lunghezza: una rete costruita per 2 token ha un primo strato con 4 colonne, e per 3 token servirebbe un'altra rete. È il modello a finestra fissa del laboratorio sulla rete numero per numero, e con 100 token da 768 numeri l'ingresso sarebbe di 76.800 numeri.
La combinazione pesata dell'attention tiene i vettori separati, uno per riga di una matrice, e costruisce il nuovo vettore di ogni token come somma dei vettori precedenti ciascuno moltiplicato per un peso che somma a 1 con gli altri: 0,06 × «il» + 0,78 × «gatto» + 0,15 × «dorme». I pesi non sono fissi: li calcola la rete dal contenuto (domanda · etichetta, poi softmax), quindi cambiano a ogni token e a ogni frase. È questo che si intende con «comporre il contesto», e non ha nessuna proprietà matematica misteriosa: è una media pesata, con pesi calcolati sul momento. Nel capitolo 9, sezione D, c'è il conto completo con tutte le matrici.
«Le matrici dell'attention vengono da tre training diversi? E come fa il training a "creare" Q, K e V?»
No: un solo training, e Q, K, V non vengono creati dal training ma calcolati a ogni passaggio. Conviene separare due famiglie di numeri.
I parametri sono i numeri che il modello possiede: la tabella E, le tre matrici Wq, Wk, Wv di ogni strato, i pesi dell'MLP. All'inizio sono casuali; esistono già prima di vedere qualunque frase; vivono su disco e vengono caricati in memoria. Le attivazioni sono i numeri che nascono quando una frase attraversa il modello: la matrice X dei vettori, poi Q = X Wq, K = X Wk, V = X Wv, i pesi dell'attention, l'uscita di ogni strato, i logit. Sono il risultato di moltiplicazioni tra la frase e i parametri, cambiano a ogni frase e alla fine si buttano.
Il training tocca solo la prima famiglia, e tutta insieme. Il modello riceve «il gatto», prevede «dorme» con probabilità 0,03, la loss è −log 0,03 ≈ 3,5; la backpropagation del capitolo 2 risale la catena loss → logit → uscita dello strato → attention → Q, K, V → Wq, Wk, Wv e consegna a ciascuna delle tre matrici la propria derivata; l'optimizer sposta tutte e tre di un passo nella direzione che abbassa quella stessa loss. Non c'è un «training per le query» e uno per le key: c'è un unico obiettivo, prevedere il token successivo, e ogni parametro che contribuisce alla previsione riceve la sua correzione. Al passaggio successivo, con i nuovi parametri, Q, K e V vengono semplicemente ricalcolati.
In sintesi · capitolo 1
Prevedere il token successivo contando le sequenze non scala e non generalizza; i vettori rendono simili le parole simili, la rete neurale costruisce la funzione che dai vettori del contesto produce i punteggi. Il modello a finestra fissa non vede lontano; la rete ricorrente vede tutto ma comprime il passato in una memoria, procede in sequenza e si addestra male. L'attention tiene tutti i vettori e lascia che ogni token costruisca il proprio come media pesata dei precedenti, con pesi calcolati dal contenuto: è così che «tutto il contesto» entra nella previsione. Il transformer è una pila di blocchi attention + MLP che trasformano una sequenza di vettori in una sequenza di vettori, seguita dal confronto con il vocabolario. Non ci sono regole scritte a mano, solo pesi; generare testo significa ripetere tutto un token alla volta.
Concetti chiave
Autodiagnosi · dovresti saper rispondere senza rileggere
All'inizio i pesi sono casuali e il modello dà a «Parigi» una probabilità del 2%, come a qualsiasi altro token. Alla fine gli dà il 90%. Che cosa è successo ai 7 miliardi di numeri nel frattempo, e chi ha deciso come cambiarli?
Nessuno li ha decisi. Il training è un ciclo meccanico che ripete quattro mosse: esegui il forward pass del capitolo 1 su un pezzo di testo, misura con un numero quanto la previsione è lontana dal token vero, calcola per ogni peso in che direzione spostarlo perché quel numero scenda, e spostalo di poco. Il ciclo gira milioni di volte. Questo capitolo lo smonta nei suoi pezzi, ciascuno con la sua domanda: da dove vengono gli esempi, come si misura l'errore, come si calcola la direzione (è qui che vive la backpropagation), quanto grande è il passo, con che ritmo si servono i dati. Il capitolo 3 rimette insieme i pezzi in un training vero, eseguibile nel browser.
Per imparare che dopo «La capitale della Francia è» viene «Parigi» servirebbe qualcuno che scriva milioni di coppie domanda → risposta. Chi le ha scritte?
Nessuno. Nel pre-training il dataset non contiene domande e risposte, né etichette. È solo testo: miliardi di documenti (pagine web, libri, articoli scientifici, codice sorgente), salvati di solito in file JSONL (JSON Lines, un oggetto JSON per riga), un documento per riga.
Le coppie «input → risposta attesa» si ricavano automaticamente dal testo stesso: per ogni posizione, l'obiettivo è semplicemente il token che viene dopo. Un documento di 1.000 token contiene 999 esempi, uno per posizione, tutti gratis. Se da qualche parte nel corpus c'è scritto «la capitale della Francia è Parigi», quella frase è già un esempio con la sua soluzione. Per questo si parla di apprendimento auto-supervisionato: il testo fa da domanda e da soluzione insieme.
Prima di arrivare al modello, il testo grezzo passa per una lunga pipeline di pulizia. È la parte meno visibile del lavoro, ma è quella che incide di più sulla qualità:
Ogni laboratorio usa una sua «ricetta» e la tiene segreta. Le proporzioni indicano l'ordine di grandezza, non dati ufficiali. Verso la fine del pre-training si alza spesso la quota di dati di alta qualità (matematica, codice, testi curati): è la fase chiamata annealing o mid-training.
Le tre voci SFT, DPO e RLVR anticipano la Parte II: le fasi successive usano dataset di forma diversa, e ogni forma corrisponde a un modo diverso di calcolare l'errore. Il meccanismo di questo capitolo resta lo stesso per tutte.
Il modello dà a «Parigi» il 12% e a «Lione» il 30%. Quanto ha sbagliato? Serve un numero, perché solo un numero si può far scendere.
La loss (funzione di perdita) è quel numero. Negli LLM si usa quasi sempre la cross-entropy (entropia incrociata) sul token successivo, che guarda una cosa sola: la probabilità che il modello ha dato al token corretto. La formula è loss = −log p(token corretto). Se il modello dà il 100% al token giusto la loss è 0; con il 50% vale 0,69; con il 12% vale 2,12; con l'1% vale 4,6.
Calcolo · la loss per diverse probabilità
Più la probabilità è bassa, più la loss sale, e sale sempre più in fretta: sbagliare «di molto» costa molto più che sbagliare «di poco». Che cosa facesse «Lione» non conta direttamente: conta solo che la sua probabilità ha rubato spazio a «Parigi».
Due proprietà la rendono adatta. È derivabile: cambiando di pochissimo un peso, la loss cambia di pochissimo e in modo calcolabile, ed è questo che permette il passo successivo. E ha un significato leggibile: e elevato alla loss è la perplexity, «tra quanti token equiprobabili il modello sta esitando». Loss 2,12 significa perplexity 8,3: come tirare a indovinare tra otto candidati. Un buon modello di pre-training arriva a una loss intorno a 2 sul testo web (perplexity ≈ 7): non può scendere a zero, perché il testo è davvero incerto (dopo «il» può venire quasi qualunque cosa).
Calcolo · dalla loss alla perplexity
La loss di un intero batch è la media delle loss di tutti i suoi token: migliaia o milioni di numeri riassunti in uno. Tutto il training consiste nel far scendere quel numero medio sui dati che il modello non ha ancora visto.
Ho un numero, la loss, e 7 miliardi di manopole. Per ogni manopola devo sapere: se la giro un po' a destra, la loss sale o scende? E di quanto? Provare una manopola alla volta richiederebbe 7 miliardi di forward pass per ogni step.
La risposta a quella domanda, per tutte le manopole insieme, è il gradiente: un numero per ogni peso, con lo stesso segno e la stessa proporzione di «quanto cambierebbe la loss se aumentassi questo peso di pochissimo».
Gradiente positivo: aumentando il peso la loss sale, quindi va diminuito. Gradiente negativo: va aumentato. Gradiente grande: quel peso conta molto per questo errore; gradiente zero: non c'entra. Il gradiente è la derivata della loss rispetto a ciascun peso, e la backpropagation (retropropagazione) è l'algoritmo che li calcola tutti con un costo pari a circa due forward pass, non sette miliardi.
Il punto di partenza è semplice. Per softmax seguita da cross-entropy, la derivata della loss rispetto a ciascun logit ha una forma che si può scrivere a mano: gradiente del logit j = p(j) − y(j), dove y è il vettore one-hot del token corretto (1 su «Parigi», 0 altrove). Con i nostri numeri: il logit di «Parigi» ha gradiente 0,12 − 1 = −0,88 (va alzato, e molto); quello di «Lione» 0,30 − 0 = +0,30 (va abbassato, in proporzione a quanto ha rubato); un token con probabilità 0,001 ha gradiente +0,001, cioè resta quasi fermo.
Calcolo · il gradiente sui logit
Nessuna regola scritta a mano dice «premia Parigi, punisci Lione»: lo dice la derivata.
Poi si torna indietro, strato per strato. I logit sono il prodotto del vettore finale con la testa di output; il vettore finale è l'uscita del blocco 32, che dipende dall'uscita del blocco 31, e così via fino a E. Ogni operazione del forward pass è una funzione semplice (una moltiplicazione di matrici, una somma, una ReLU, una softmax) di cui si conosce la derivata.
La regola della catena dice che la derivata di una composizione è il prodotto delle derivate dei pezzi: per sapere quanto la loss dipende da un peso del blocco 12, si moltiplica «quanto la loss dipende dall'uscita del blocco 32» per «quanto l'uscita del 32 dipende da quella del 31» … per «quanto l'uscita del 12 dipende da quel peso».
Il backward pass percorre la rete all'indietro, dalla loss verso l'input, e a ogni strato fa due cose: calcola il gradiente dei pesi di quello strato (e lo mette da parte) e passa allo strato precedente il gradiente rispetto al suo ingresso. Ogni valore intermedio del forward pass serve a questo calcolo: per questo durante il training si tengono in memoria le attivazioni, e per questo il training costa molta più memoria dell'inferenza.
Il laboratorio seguente fa esattamente questo su una «rete» di due pesi, abbastanza piccola da seguire ogni moltiplicazione. La struttura è identica a quella di un transformer: una catena di funzioni, derivate locali, prodotto all'indietro.
Leggi i gradienti da destra verso sinistra: la loss dipende da y come (y − t); y dipende da w₂ come h e da h come w₂; la ReLU lascia passare il gradiente se a > 0 e lo azzera altrimenti; a dipende da w₁ come x. Il gradiente di w₁ è il prodotto di tutti i fattori lungo la catena: è la regola della catena. Prova a portare w₁ sotto zero: la ReLU si spegne, il gradiente di w₁ diventa 0 e quel peso non impara più da questo esempio, qualunque sia l'errore. In un transformer la catena ha centinaia di anelli, ma ogni anello è semplice come questi.
Perché costa «solo» due forward pass. Il backward pass esegue, per ogni matrice del forward, due moltiplicazioni di matrici della stessa dimensione (una per il gradiente dei pesi, una per il gradiente da passare indietro): circa il doppio del lavoro del forward. Il conto complessivo di uno step di training è quindi circa 6 operazioni per peso per token (2 del forward + 4 del backward), la formula che si usa per stimare il costo di un pre-training: 6 × parametri × token. Il training automatico delle derivate (autograd) è ciò che le librerie come PyTorch fanno per te: registrano ogni operazione del forward e applicano la regola della catena al contrario.
Calcolo · il costo di uno step
Il gradiente di un batch è la media dei gradienti dei suoi esempi. Un peso che in un esempio dovrebbe salire e in un altro scendere riceve una spinta piccola: il modello impara ciò che è coerente tra gli esempi, non il caso singolo. Nel capitolo 3 lo vedrai nella matrice del gradiente: le celle dei token che non compaiono nel batch restano a zero.
Ora so, per ogni peso, in che direzione muoverlo. Di quanto?
La regola base è w ← w − η · gradiente: ogni peso si muove nel verso opposto al suo gradiente, di una quantità proporzionale a esso e a un fattore globale, il learning rate η.
Immagina la loss come un paesaggio collinare in 7 miliardi di dimensioni, dove ogni punto è una combinazione di pesi e l'altezza è la loss: il gradiente indica la salita più ripida, e il gradient descent (discesa del gradiente) fa un passo in discesa. Ma il gradiente è una pendenza locale, vale solo nei dintorni del punto: un passo troppo lungo atterra su un pendio diverso, magari più in alto; un passo troppo corto impiega un'eternità.
Il learning rate è il compromesso, ed è l'iperparametro più delicato di tutto il training: negli LLM vale tipicamente 10⁻⁴–10⁻³ nel pre-training e 10⁻⁵ nel fine-tuning, con pesi che stanno intorno a ±0,02.
La forma «stocastica» del nome, SGD (Stochastic Gradient Descent), ricorda che il gradiente è calcolato su un batch, un campione dei dati, e quindi è una stima rumorosa della pendenza vera. Gli LLM usano un ottimizzatore più sofisticato, AdamW, che adatta il passo a ogni singolo peso in base alla storia recente dei suoi gradienti (ne tiene una media mobile e una media dei quadrati): i pesi con gradienti sempre piccoli ricevono passi relativamente più grandi, quelli con gradienti grandi e oscillanti vengono frenati. Il principio però è lo stesso del laboratorio qui sotto.
Il dataset ha 15 mila miliardi di token. Non si può calcolare la loss su tutti insieme, né aggiornare i pesi dopo ogni singolo token. Con che ritmo si servono i dati al modello?
Questi cinque termini descrivono proprio quel ritmo, dal più piccolo al più grande:
Una rete ha un numero fisso di nodi d'ingresso. Come fanno una frase di 3 token e una di 6 a entrare nella stessa rete, e come fa la rete a imparare da tutte e due?
I nodi d'ingresso non contano i token: contano i numeri di un solo vettore. Nell'esempio qui sotto ogni token è un vettore di 2 numeri, quindi la rete ha 2 nodi d'ingresso. La frase entra come una matrice X con una riga per token e una colonna per nodo: 3 token danno una matrice 3 × 2, 6 token una matrice 6 × 2. Una frase più lunga aggiunge righe, mai colonne.
Il conto di uno strato è il prodotto X W. La matrice dei pesi W ha tante righe quante sono le colonne di X (2) e non sa quante righe ha X: si applica a ogni riga allo stesso modo, una riga alla volta, e restituisce una riga per token.
È come una formula di un foglio di calcolo trascinata in basso: la formula è una sola, le righe sono quante ne hai.
Con la regola delle dimensioni del capitolo 1: (n × 2)(2 × 2) = n × 2, qualunque sia n.
C'è un solo punto in cui la grandezza dipende dalla frase: l'attention. Per far parlare i token tra loro serve una tabella con una casella per ogni coppia di token, quindi n × n: 3 × 3 per la frase corta, 6 × 6 per la lunga. Ma quella tabella non è fatta di pesi: si calcola ogni volta dalla frase, con i prodotti scalari tra le righe, come nella sezione sull'attention del capitolo 1. Cresce con la frase senza aggiungere niente da imparare.
E il training? Il bersaglio è la frase stessa spostata di un posto: la riga del token 1 deve prevedere il token 2, la riga del token 2 il token 3, e così via. Con n righe escono n previsioni e n errori, e la loss è la loro media. Il gradiente di W si calcola con un prodotto in cui n sparisce: (2 × n)(n × 2) = 2 × 2. In pratica è la somma dei suggerimenti di tutte le righe: ogni token dice di quanto vorrebbe spostare ciascuno dei 4 pesi, e i suggerimenti si sommano. Che le righe siano 3 o 6, il gradiente ha la forma di W e lo step W ← W − η × gradiente si fa sempre allo stesso modo.
Passa dalla frase corta alla lunga e guarda quali dimensioni cambiano; poi premi «Uno step di training» qualche volta e torna all'altra frase: i 4 pesi di W sono gli stessi, e migliorano anche lì.
Come si legge. Nel pannello 1 ogni riga è il vettore di un token, letto dalla tabella E (il 1 · 0, gatto 0,3 · 1, dorme −0,8 · 0,6, sul −0,8 · −0,6, divano 0,3 · −1). Nel pannello 2 la riga i dice quanto il token i guarda ciascun token fino a sé (le caselle in grigio sono i token futuri, che non può vedere); ogni riga somma a 1. Nel pannello 6 ogni riga è una previsione, con il bersaglio evidenziato: è la casella che il training vuole portare verso 1. Che cosa è semplificato: i punteggi dell'attention sono il prodotto scalare diretto tra i vettori (un modello vero li moltiplica prima per Wq e Wk, capitolo 9, sezione D), non c'è codifica della posizione, non c'è MLP, la tabella E è fissa e si allena soltanto W, con learning rate η = 1. In un modello vero si allenano tutti i pesi, e il gradiente di ciascuno ha la forma del suo peso per lo stesso motivo.
Calcolo · le dimensioni, qualunque sia n
Frasi di lunghezze diverse nello stesso batch. Una sola frase può avere qualunque lunghezza; un batch no, perché le sequenze vengono messe una sopra l'altra in un tensore B × L × d, e una pila di matrici deve avere righe della stessa lunghezza.
Nel pre-training non è un problema: il testo dei documenti viene messo in fila, separato dal token di fine documento, e tagliato a pezzi di esattamente L token (si chiama packing).
Nel fine-tuning, dove ogni esempio è una conversazione con la sua lunghezza, le sequenze corte si allungano con un token di riempimento (padding) e una maschera esclude quelle posizioni dall'attention e dalla loss: le righe di riempimento esistono nei conti, ma non votano nel gradiente.
Allora perché esiste una lunghezza massima del contesto? Non per i pesi, che non cambiano con la lunghezza, ma per due altri motivi. Il primo è il costo: la tabella dell'attention è n × n, quindi raddoppiando la frase memoria e calcolo dell'attention si quadruplicano. Il secondo è l'esperienza: il modello ha imparato le posizioni solo fino alla lunghezza delle sequenze viste in training, e oltre quella la codifica della posizione gli presenta distanze che non ha mai incontrato.
Dopo milioni di step i pesi sono diversi. Ma «diversi» come? Si può vedere qualcosa di sensato in quei numeri?
Nei singoli pesi no: nessuna manopola significa qualcosa da sola. Nelle righe di E, però, qualcosa si vede. Ogni step aggiorna solo le righe dei token presenti nel batch, e token che compaiono in contesti simili (prima delle stesse parole, dopo le stesse parole) ricevono gradienti simili: i loro vettori si avvicinano.
Nessuno impone che «gatto» stia vicino a «cane»; emerge dal compito di prevedere il token successivo, perché prevedere bene ciò che segue «gatto» e ciò che segue «cane» richiede informazioni quasi uguali. È la stessa idea di word2vec, ma qui gli embedding si allenano insieme al resto del modello. Il laboratorio seguente è uno schema qualitativo di questo fenomeno: le posizioni finali sono disegnate a mano, ma proiettando con tecniche come PCA o t-SNE gli embedding di un modello vero si osservano raggruppamenti dello stesso tipo.
Quanta memoria serve per allenare. Il capitolo 1 diceva che i pesi di un 7B occupano 14 GB in bf16. Per allenarli non bastano: servono anche i gradienti (una copia della stessa forma dei pesi), le due medie mobili di AdamW per ogni peso, e di solito una copia «master» dei pesi in precisione piena (fp32, 4 byte) perché accumulare milioni di piccoli aggiornamenti in 16 bit perderebbe le cifre. In totale circa 16 byte per parametro: oltre 110 GB per un 7B, prima ancora di contare le attivazioni del forward pass da conservare per il backward. È il motivo per cui il training richiede molte GPU anche per modelli che in inferenza stanno su una sola, e per cui esistono tecniche come LoRA (sotto) che allenano solo una piccola frazione di pesi.
Calcolo · la memoria per allenare un 7B
| Per ogni peso, durante il training | Byte | Per un modello da 7B |
|---|---|---|
| Peso in bf16 (quello usato nel forward) | 2 | 14 GB |
| Gradiente in bf16 | 2 | 14 GB |
| AdamW: media dei gradienti + media dei quadrati (fp32) | 8 | 56 GB |
| Copia master del peso in fp32 | 4 | 28 GB |
| Totale (senza attivazioni) | 16 | ≈ 112 GB |
In sintesi · capitolo 2
Il training non «insegna» nulla in senso umano: ripete un ciclo di quattro mosse. Il testo stesso fornisce gli esempi (il token successivo è la soluzione); la cross-entropy riassume l'errore in un numero, −log della probabilità data al token giusto; la backpropagation calcola, con la regola della catena percorsa all'indietro, quanto quel numero dipende da ciascuno dei miliardi di pesi (il gradiente), partendo dalla formula p − y sui logit; e ogni peso si sposta di un passo η nel verso opposto. I dati sono serviti in batch di sequenze, ogni batch è uno step, un giro completo del dataset è un'epoch. Dopo milioni di step la struttura emerge nei pesi senza che nessuno l'abbia scritta.
Concetti chiave
Autodiagnosi · dovresti saper rispondere senza rileggere
I capitoli 1 e 2 descrivono il meccanismo a parole. Si può vederlo girare, con numeri veri, senza fidarsi?
Sì. Qui sotto c'è un modello linguistico minuscolo che si allena davvero nel tuo browser: dataset, tokenizzazione, sequenze, batch, forward pass, loss, gradienti e aggiornamento, con la matematica reale del capitolo 2 (la formula p − y per il gradiente, la regola w ← w − η · gradiente per l'aggiornamento). Segui gli 8 passaggi con Avanti e confronta ogni schermata con la sezione corrispondente: i passi 1–3 sono i dati, il 4 è il forward pass del capitolo 1, il 5 la loss, il 6 il backward pass, il 7 lo step. Dopo il primo giro, il ciclo riparte dal passo 4 con il batch successivo; quando i batch finiscono, inizia una nuova epoch.
Semplificazione onesta: un transformer usa tutto il contesto a sinistra, mentre questo modello guarda solo l'ultimo token (un modello a bigrammi). I suoi pesi sono una tabella 15 × 15: riga = token attuale, colonna = token successivo, e il «forward pass» è leggere una riga e applicarle la softmax. Il flusso del training però è identico a quello di un LLM, e la loss, il gradiente e l'aggiornamento sono calcolati con le stesse formule.
Ogni punto è la loss di un batch, prima dell'aggiornamento. La linea tratteggiata è log 15 ≈ 2,71, la loss di un modello che tira a indovinare tra 15 token. Nota che non arriva a zero: dopo «il» il dataset stesso è ambiguo.
Riga = token attuale, colonna = token successivo. Verde: peso positivo (successione favorita). Rosso: peso negativo. Passa sopra una cella per il valore. Sono le 225 «manopole» del capitolo 1.
La generazione è autoregressiva come in un LLM: si estrae un token secondo le probabilità, lo si usa come nuovo input, e si ripete fino a <eos>. Dopo 20 epoch il modello produce frasi del dataset e qualche ricombinazione plausibile («il cane dorme sul divano»): ha imparato le transizioni, non le frasi. Un LLM fa la stessa cosa con un contesto di migliaia di token invece di uno.
Parte II
Da qui in poi seguiremo un solo prompt, «Qual è la capitale della Francia?», e guarderemo che cosa risponde il modello dopo ogni fase. Per ciascuna fase le domande sono sempre le stesse quattro: quale problema lascia aperto la fase precedente; da dove arrivano gli esempi e com'è fatta una riga del dataset; come si calcola l'errore (la loss) e quindi che cosa viene rinforzato; quale limite resta, che la fase successiva dovrà affrontare.
Perché servono migliaia di miliardi di token di testo qualsiasi per un modello che alla fine deve «rispondere alle domande»? E che cosa esce, esattamente, da questa fase?
Il problema di partenza. Un modello con pesi casuali non sa nulla: né grammatica, né fatti, né che cosa sia una domanda. Insegnargli tutto questo con esempi scritti a mano è impossibile per quantità: la conoscenza implicita in una lingua (concordanze, idiomi, fatti, modi di argomentare, convenzioni del codice) è troppo vasta per essere elencata. Il pre-training aggira il problema con l'auto-supervisione del capitolo 2: il testo esistente è già pieno di esempi, basta usare ogni token come obiettivo dei precedenti.
I dati. Testo grezzo, nella forma vista nel capitolo 2: una riga JSONL per documento, nessuna etichetta, una miscela di web, codice, libri, articoli. La scala è il punto: Llama 2 è stato pre-addestrato su circa 2.000 miliardi di token, Llama 3 su 15.000 miliardi.
Una regola empirica (lo studio «Chinchilla», 2022) suggerisce che, a parità di calcolo, convenga circa 20 token per parametro; i modelli recenti ne usano molti di più, perché un modello piccolo allenato a lungo costa meno da servire, e il costo di inferenza oggi pesa più di quello di training.
La loss. Esattamente la cross-entropy del capitolo 2, su ogni token di ogni documento: il segnale più denso che esista, una correzione per ogni posizione. Minimizzarla su migliaia di miliardi di esempi costringe il modello a comprimere nel modo più efficiente possibile le regolarità del testo, e le regolarità del testo sono grammatica, fatti, stile, e i primi embrioni di ragionamento (per prevedere il token dopo «quindi 17 × 23 =» conviene saper moltiplicare). Nessuna di queste cose è un obiettivo esplicito: sono tutte scorciatoie per prevedere meglio.
Il costo. Con la formula del capitolo 2 (6 operazioni per parametro per token): 6 × 7 miliardi × 2.000 miliardi ≈ 8 × 10²² operazioni, circa 184.000 ore di GPU A100 per il solo Llama 2 7B; i modelli di frontiera sono centinaia di volte più costosi.
Calcolo · il costo del pre-training di Llama 2 7B
È la fase che si fa una volta, e da cui tutte le altre partono: le fasi successive costano in totale pochi punti percentuali di questa.
Nel capitolo 2 il learning rate era un numero. Perché nei training veri cambia durante il training?
Per due motivi opposti, all'inizio e alla fine. All'inizio i pesi sono casuali, i gradienti sono enormi e le medie mobili di AdamW non si sono ancora stabilizzate: un passo pieno fin dal primo step spinge i pesi in direzioni sbagliate e la loss «esplode», talvolta fino a produrre NaN (Not a Number) e a dover ripartire da un checkpoint. Per questo il learning rate parte da circa zero e sale linearmente per qualche centinaio o migliaio di step: il warm-up.
È come scaldare un motore a freddo prima di dare gas.
Alla fine succede il contrario: con il passo pieno il modello continua a oscillare intorno al minimo senza assestarsi; riducendo η (decadimento coseno, oppure lineare) i pesi si posano nel punto migliore della valle. Llama 2, per esempio: picco 3 × 10⁻⁴, warm-up di 2.000 step, decadimento coseno fino al 10% del picco, batch da 4 milioni di token.
Le curve del learning rate sono quelle reali (lineare, coseno, WSD); la curva della loss è uno schema qualitativo che riproduce le forme osservate nei training veri, non il risultato di un modello. Prova a portare il warm-up a 0%: nei primi step la loss mostra picchi (spike) di instabilità. Con WSD (Warmup-Stable-Decay, riscaldamento-stabile-decadimento) la loss resta più alta nella fase piatta e cala di colpo nel decadimento finale; il vantaggio pratico è che puoi prolungare la fase stabile e decidere quando fermarti.
Il risultato è un base model: un modello che continua qualunque testo nel modo più probabile secondo il corpus. Sa moltissimo, ma non sa che cosa tu voglia da lui, perché non è mai stato addestrato su una «richiesta»: solo su testo che prosegue.
Non è un errore: sul web una domanda del genere compare spessissimo dentro un quiz, seguita da altre domande. Il modello ha fatto la cosa per cui è stato ottimizzato, prevedere la continuazione più probabile. Sa la risposta (se gli scrivi «La capitale della Francia è» completa con «Parigi»), ma non sa che tu la vuoi. La conoscenza c'è; manca il comportamento.
Due cose che si vedono in produzione. La mid-training o annealing: nell'ultima parte del pre-training si alza la quota di dati curati (matematica, codice, testi di qualità) mentre il learning rate decade, e il modello ne ricava un guadagno sproporzionato. E la valutazione: un base model si misura con la loss su un testo di validazione (perplexity) e con batterie di domande a scelta multipla completate come testo, non conversando con lui.
In sintesi · fase 1
Il base model sa che la capitale è Parigi ma risponde con altre domande. Come gli si insegna a rispondere, senza riaddestrarlo da capo?
Il problema. Il comportamento «rispondi alla richiesta» non è nel corpus di pre-training, o lo è solo di rado e mescolato a tutto il resto. Ma il meccanismo del capitolo 2 non chiede che i dati siano testo trovato: chiede solo coppie contesto → token successivo. Se si costruiscono testi in cui a una richiesta segue una risposta ben fatta, e li si dà in pasto allo stesso ciclo di training, il modello imparerà a continuare una richiesta con una risposta. È un cambio di comportamento (behavior shift), non di conoscenza: la conoscenza è già nei pesi, si sta solo insegnando al modello in che forma tirarla fuori.
I dati. Conversazioni scritte apposta: una richiesta e la risposta che vorremmo, redatte da annotatori umani o generate da un modello più forte e filtrate. Ogni esempio viene formattato con un chat template: token speciali che delimitano i ruoli (<utente>, <assistente>, <fine>), così il modello impara anche dove finisce la richiesta e dove deve fermarsi.
La quantità è sorprendentemente piccola rispetto al pre-training: da qualche migliaio a qualche centinaio di migliaia di esempi (Llama 2 ne ha usati circa 27.000, scelti per qualità; lo studio LIMA ha mostrato risultati notevoli con 1.000). Conta la qualità: il modello imita esattamente quello che vede, errori e stile compresi.
La loss. Sempre la cross-entropy sul token successivo, con una sola modifica: si calcola solo sui token della risposta. I token della richiesta entrano nel forward pass come contesto, ma la loro previsione non viene valutata (si dice che sono mascherati). Il motivo: non vogliamo che il modello impari a generare richieste, e le richieste scritte da utenti non sono un buon testo da imitare.
Il learning rate è molto più basso che nel pre-training (10⁻⁵ contro 10⁻⁴), per 1–3 epoch: pochi aggiustamenti, perché si vuole spostare il comportamento senza cancellare il sapere.
Nel forward pass tutti i 17 token vengono elaborati e per ciascuno esce una distribuzione sul token successivo; nel calcolo della loss si fa la media solo sulle 8 posizioni della risposta. Il token <fine> è incluso: il modello deve imparare anche a smettere.
Le percentuali sono uno schema qualitativo, non misure: mostrano il tipo di spostamento che l'SFT produce. All'inizio la massa di probabilità sta sulle continuazioni «da web» (quiz, articoli, forum); dopo poche migliaia di esempi si sposta sulla risposta diretta. La conoscenza (Parigi) non cambia: cambia la forma in cui viene emessa.
Due usi che tornano. Il cold start dei modelli di ragionamento avviene qui: si aggiungono esempi con catene di ragionamento esplicite (CoT, Chain-of-Thought) per insegnare il formato «prima penso, poi rispondo», così che la fase di RL abbia da dove partire. E l'SFT è anche il modo in cui si fa la distillazione classica: le risposte le scrive un modello grande, e quello piccolo le imita (ne riparleremo nella fase 5).
Il limite. Il modello imita e basta. Vede una sola risposta «giusta» per richiesta, quindi non impara che cosa renda una risposta migliore di un'altra, né può superare la qualità di chi ha scritto i dati.
E c'è un rischio sottile: se gli esempi di SFT contengono fatti che il modello non aveva nel pre-training, impara che «rispondere con sicurezza anche quando non si sa» è il comportamento atteso, cioè impara ad allucinare. Per questo i dati di SFT devono insegnare la forma, non nuova conoscenza.
Risponde. Ma risponderebbe così anche se la domanda fosse ambigua, o se la risposta migliore fosse più lunga, o più breve, o chiedesse un chiarimento: non ha mai visto un confronto tra risposte, solo risposte.
In sintesi · fase 2
«Parigi.» e «La capitale della Francia è Parigi; è anche la città più popolosa del Paese, con circa 2 milioni di abitanti.» sono entrambe corrette. Quale preferiscono le persone? E come si insegna una preferenza, che non è un token da prevedere?
Il problema. Molte qualità di una risposta (utilità, tono, lunghezza giusta, prudenza, sicurezza) non hanno una «risposta corretta» da imitare: sono relative. Ma chiedere a una persona quale di due risposte sia migliore è facile, veloce e affidabile, molto più che chiederle di scrivere la risposta ideale. Il dataset diventa quindi fatto di coppie: per lo stesso prompt una risposta preferita (chosen) e una scartata (rejected).
Entrambe sono di solito generate dal modello stesso dopo l'SFT; il giudizio viene da annotatori umani o da un modello giudice (in quel caso si parla di RLAIF, Reinforcement Learning from AI Feedback).
La strada storica: RLHF. Il primo metodo (InstructGPT, 2022) usava le coppie in due tempi. Prima si addestra un reward model, una copia del modello con in cima un solo numero in uscita, a dare punteggio più alto alla risposta preferita (loss di classificazione sulla coppia).
Poi si usa l'apprendimento per rinforzo: il modello genera risposte, il reward model le valuta, e un algoritmo chiamato PPO (Proximal Policy Optimization) alza la probabilità delle risposte ben valutate, tenendo il modello vicino a quello di partenza con una penalità (la divergenza KL, spiegata sotto). Funziona, ma richiede quattro modelli in memoria, una generazione continua durante il training e una messa a punto notoriamente fragile.
DPO: la scorciatoia. Nel 2023 un articolo ha mostrato che il giro si può saltare. L'obiettivo di RLHF, scritto per esteso, ha una soluzione in forma chiusa: il modello ottimo è quello per cui β · log( π(risposta) / πref(risposta) ) si comporta da reward.
In parole: quanto il modello nuovo ha alzato la probabilità di una risposta rispetto al modello di riferimento (quello uscito dall'SFT, congelato) è già, implicitamente, il punteggio che le assegna.
Allora si può addestrare direttamente il modello perché quel «reward implicito» sia più alto per la chosen che per la rejected, con una semplice loss di classificazione sulla coppia: niente reward model, niente generazione durante il training, niente RL.
DPO dimostra che il modello stesso, confrontato con il riferimento congelato, contiene già un reward implicito. Basta una loss supervisionata, stabile ed economica, dello stesso tipo di quelle dei capitoli precedenti: forward pass su chosen e rejected (con il modello e con il riferimento), loss, backward, step.
La loss, letta pezzo per pezzo. Per ogni coppia si calcola, con il modello e con il riferimento, la log-probabilità dell'intera risposta chosen e dell'intera rejected (somma dei log p dei suoi token, cioè la cross-entropy con il segno cambiato). Δc = log π(chosen) − log πref(chosen) dice quanto il modello ha alzato la chosen rispetto a com'era; Δr lo stesso per la rejected.
Il margine è β · (Δc − Δr), e la loss è −log σ(margine): piccola se il margine è grande e positivo, grande se il modello preferisce la rejected. Il gradiente è pesato da σ(−margine): le coppie già separate contano poco, quelle sbagliate contano molto.
β (tipicamente 0,1–0,5) regola quanto è «costoso» allontanarsi dal riferimento: più è alto, più basta un piccolo Δ per ottenere lo stesso margine, e meno il modello si sposta.
Calcolo · la loss di DPO
Il modello ha imparato che gli annotatori preferivano risposte complete ma non prolisse, cortesi, con un'apertura al seguito. Nessuno ha scritto questa risposta: è emersa dal confronto tra migliaia di coppie. Nota anche il rischio tipico: se gli annotatori preferiscono sistematicamente la risposta più lunga, il modello impara a essere lungo (length bias), anche quando non serve.
DPO è offline: le coppie sono fisse, e il modello non genera nulla durante il training; dopo pochi step le risposte che produrrebbe non sono più quelle del dataset, e il segnale si allontana da ciò che fa davvero. Ed è soggettivo per costruzione: ottimo per stile, utilità, tono e sicurezza, ma per i compiti in cui esiste una risposta verificabile (matematica, codice, formati) il giudizio umano è lento, costoso e meno affidabile di un controllo automatico. Soprattutto, imitare o preferire non insegna a cercare: per problemi che richiedono tentativi, nessun dataset statico contiene i tentativi giusti.
In sintesi · fase 3
Per «Quanto fa 17 × 23?» non serve chiedere a nessuno quale risposta sia migliore: 391 è giusto e il resto è sbagliato. Si può usare questo fatto per far migliorare il modello, invece di fargli imitare soluzioni scritte da altri?
Il problema. Nei compiti con una risposta verificabile (matematica, codice con test, formati strutturati, giochi) l'imitazione ha un tetto: il modello può al massimo diventare bravo quanto le soluzioni che vede, e le soluzioni scritte da persone non contengono i passi che a lui servirebbero (le sue esitazioni, i suoi controlli, i suoi errori tipici). Per superare il tetto bisogna lasciare che sia il modello a produrre i tentativi, e rinforzare quelli che funzionano. È l'apprendimento per rinforzo (RL, Reinforcement Learning), che qui ha un vocabolario suo.
I dati. Il dataset contiene solo il problema e il modo di verificarlo: una domanda con la risposta finale attesa, oppure un esercizio di programmazione con i suoi test. Nessuna soluzione scritta per esteso. Le soluzioni le genera il modello, durante il training, e un programma (il verificatore) le controlla: per la matematica confronta il risultato con quello atteso, per il codice esegue i test, per un JSON controlla lo schema.
Da qui il nome RLVR: il reward è verificabile, oggettivo, e per questo difficile da «truccare» (il reward hacking, in cui il modello trova un modo di ottenere punti senza risolvere il problema, resta possibile se il verificatore è lasco: per esempio un test troppo debole).
Come si trasforma un reward in un gradiente. Qui c'è il passaggio nuovo rispetto a tutto il resto del documento. La loss dei capitoli precedenti confrontava la distribuzione del modello con un token obiettivo; qui non c'è un token obiettivo, c'è un numero alla fine della sequenza. L'idea del policy gradient è semplice: per ogni rollout, si calcola la log-probabilità che il modello ha dato ai propri token, e la si moltiplica per il vantaggio.
Se il vantaggio è positivo, il gradiente spinge ad alzare la probabilità di quei token (come se fossero il target di un SFT); se è negativo, ad abbassarla. L'aggiornamento è la stessa mossa w ← w − η · gradiente di sempre: cambia solo come si costruisce il segnale.
GRPO: il vantaggio si calcola dentro il gruppo. Perché serve «quanto ci si aspettava»? Se un problema è facile e il modello lo risolve 9 volte su 10, il decimo tentativo sbagliato è informativo, i nove giusti quasi no; se è difficile e lo risolve 1 volta su 10, è quel tentativo giusto a contare.
Nel PPO classico questa aspettativa la stima un secondo modello (il critic), grande quanto la policy. GRPO (2024) la sostituisce con una media: per ogni prompt il modello genera un gruppo di N risposte, e il vantaggio di ciascuna è il suo reward meno la media del gruppo, diviso per la deviazione standard.
Sopra la media si rinforza, sotto si scoraggia, e se tutte hanno lo stesso reward (tutte giuste o tutte sbagliate) il vantaggio è zero per tutte e quel gruppo non insegna nulla. Si aggiunge una penalità KL verso il modello di riferimento, per non allontanarsi troppo, e un clipping che limita quanto un singolo step può cambiare le probabilità.
Calcolo · il vantaggio in GRPO
I reward e i vantaggi sono calcolati davvero con la formula di GRPO; l'effetto dell'aggiornamento sulla probabilità di risposta corretta è una dinamica semplificata (nella realtà dipende dai gradienti sui pesi). Prova la difficoltà «Difficile»: quasi tutti i gruppi sono tutti sbagliati, i vantaggi valgono zero e il modello non impara niente. Per questo nell'RLVR si selezionano prompt di difficoltà intermedia (curriculum). Allo stesso modo, un prompt che il modello risolve sempre non insegna più nulla.
Il critic stima «quanto mi aspetto di guadagnare da qui» ed è grande quanto la policy.
La media del gruppo sostituisce il critic come baseline, il verificatore sostituisce il reward model: all'incirca metà della memoria.
Che cosa emerge. Qui il modello non imita più: esplora. Genera ragionamenti suoi e rinforza quelli che portano alla risposta giusta, e così può superare i dati di partenza. È in questa fase che nei modelli di ragionamento (DeepSeek-R1 è il caso documentato più noto, 2025) compaiono comportamenti che nessuno ha scritto negli esempi: ricontrollare i passaggi, tornare indietro dopo un errore, provare una seconda strada, scrivere catene di ragionamento sempre più lunghe quando il problema lo richiede. Sono comportamenti che aumentano la probabilità di reward, e per questo vengono rinforzati.
Serve un verificatore, quindi la fase funziona solo dove la correttezza è controllabile da un programma: non per un saggio, una consulenza, una conversazione. Il segnale è sparso: un numero per migliaia di token generati, e il modello deve scoprire da solo quale passo ha causato l'errore, con molti tentativi; per questo l'RL è la fase più costosa in calcolo dopo il pre-training, dominata dalla generazione dei rollout. E un modello così allenato è grande e lento da servire: la sua capacità di ragionare costa a ogni risposta.
Il passo di verifica non era negli esempi: è stato rinforzato perché i rollout che ricontrollavano ottenevano reward più spesso.
In sintesi · fase 4
Ho un modello da 70 miliardi di parametri che ragiona bene e uno da 7 che costa un decimo. Come trasferisco la capacità dal primo al secondo?
Il problema. Rifare l'RL sul modello piccolo funziona male: un modello piccolo scopre da solo meno soluzioni, quindi riceve meno reward, quindi impara più lentamente. Ma il modello grande (il teacher) ha già le risposte, e molto di più: per ogni contesto ha un'intera distribuzione sul token successivo. La distillazione usa il teacher come fonte di segnale per lo student. Il punto delicato è chi scrive il testo su cui lo student impara.
Distillazione classica (off-policy). Il teacher genera molte soluzioni, e lo student le imita con un SFT: segnale denso (ogni token), ma sulle traiettorie del teacher. Lo student vede solo ragionamenti perfetti. Quando poi, in inferenza, sbaglia un passo, si trova in un territorio mai visto in training e non sa recuperare: è l'exposure bias (distorsione da esposizione). L'errore si propaga.
RL sullo student (on-policy, sparso). Lo student genera, il verificatore giudica. Impara sui propri errori, ma con un numero per sequenza: deve scoprire da solo, in molti tentativi, quale dei suoi passi era sbagliato.
OPD: on-policy e denso. Lo student genera la soluzione; poi, per ogni token che ha scritto, il teacher dice quale distribuzione avrebbe avuto lui in quel punto, dato il testo dello student fino a lì.
La loss confronta le due distribuzioni token per token con la divergenza KL inversa (reverse KL, KL(student ‖ teacher)): penalizza lo student dove assegna probabilità a token che il teacher riterrebbe improbabili.
Il risultato è un segnale che ha le due qualità insieme: è sui propri errori (on-policy) ed è denso (ogni token). Un errore in un passo viene segnalato in quel passo, e i passi corretti successivi non vengono puniti, perché il teacher li giudica nel contesto dello student. I risultati pubblicati (2024–2025) riportano guadagni paragonabili all'RL con un ordine di grandezza in meno di calcolo.
Le probabilità del teacher sono scelte a mano per un esempio leggibile (in un sistema reale sono i veri logit del teacher sul testo dello student), ma la logica delle tre modalità è esatta: chi genera il testo, e quanti numeri di feedback arrivano per sequenza.
| Chi genera il testo | Feedback | Pregio | Difetto | |
|---|---|---|---|---|
| Distillazione off-policy (SFT sul teacher) | Il teacher | Ogni token | Denso, economico | Exposure bias: lo student non vede i propri errori |
| RL con verificatore | Lo student | 1 numero per sequenza | Sui propri errori | Sparso: molti tentativi per capire dove si sbaglia |
| OPD | Lo student | Ogni token, dal teacher | Sui propri errori e denso | Serve un teacher migliore, e farlo girare su ogni token |
Lo student non supererà il teacher: la distillazione comprime, non scopre. E il teacher deve essere interrogato su ogni token generato dallo student, il che costa, anche se molto meno di generare i rollout dell'RL. In pratica le fasi si combinano: RL sul modello grande per scoprire, OPD per trasferire ai modelli piccoli.
In sintesi · fase 5
Parte III
Il capitolo 1 ha mostrato i cinque passi su un modello in miniatura con pesi scritti a mano. Questo capitolo apre ciascun passo come è realizzato in un modello reale: come si costruisce il vocabolario (A), l'algebra che serve, in otto passi con laboratori (B), la matrice di embedding e la posizione (C), il calcolo completo di uno strato con attention a tre matrici, MLP, connessioni residue e normalizzazione (D), la testa di output con softmax e temperatura (E), i conti esatti di un modello da 7 miliardi di parametri (F), e la backpropagation su una rete a due strati con molti step (G). Si può leggere in qualsiasi ordine; ogni sezione è autonoma.
Nel modello in miniatura il vocabolario era di 12 parole intere. Un modello vero ne ha 30.000–250.000, e non sono parole: chi decide quali frammenti di testo diventano token?
Nel capitolo 1 il passo 1 era dato per scontato: il testo viene spezzato in token, frammenti presi da un vocabolario fisso (tipicamente 30.000–250.000 voci), e ogni frammento è sostituito dal suo ID, la sua posizione nel vocabolario. Una frase come «La capitale della Francia è» diventa qualcosa come [1529, 8871, 503, 9412, 1032]: «La», «·capitale», «·della», «·Francia», «·è» (il punto · segna lo spazio, che fa parte del token seguente). In italiano un token corrisponde in media a 3–4 caratteri: le parole comuni restano intere, quelle rare si spezzano in pezzi.
Perché token e non parole o lettere? Con le lettere le sequenze sarebbero lunghissime e ogni simbolo direbbe pochissimo; con le parole il vocabolario sarebbe sterminato e qualsiasi parola nuova, errore di battitura o nome proprio sarebbe irrappresentabile. I token sono il compromesso: frequenti abbastanza da avere un significato, piccoli abbastanza da comporre qualsiasi testo.
Chi decide quali frammenti entrano nel vocabolario? Un algoritmo, il BPE (Byte Pair Encoding, codifica per coppie di byte), che si «allena» sul corpus prima del modello, una volta sola:
Il risultato: le sequenze frequenti («gatt», «ione», «·della») diventano un token solo, mentre le parole rare vengono composte da pezzi. Per tokenizzare un testo nuovo si applicano le stesse regole di fusione, nello stesso ordine in cui sono state imparate. Il laboratorio qui sotto esegue l'algoritmo vero su un corpus minuscolo.
Il vocabolario parte dai simboli di base (i singoli caratteri presenti nel corpus) più i token speciali, e ogni merge aggiunge un token con un nuovo ID. I simboli di base non vengono mai tolti, anche quando nel corpus non compaiono più da soli: servono per scrivere parole mai viste. Il training del tokenizer si ferma quando il vocabolario raggiunge la dimensione scelta; da lì in poi ID e token non cambiano più.
Il simbolo · rappresenta lo spazio: come nei tokenizer reali (GPT, Llama) lo spazio fa parte del token che lo segue, quindi «·gatto» e «gatto» sono token diversi. I tokenizer reali lavorano sui byte UTF-8 (Unicode Transformation Format, 8 bit) e fanno decine di migliaia di merge su gigabyte di testo, ma l'algoritmo è esattamente questo. Le coppie che compaiono una volta sola non vengono fuse (frequenza minima 2). Cambiando i due vincoli il laboratorio ripete da capo lo stesso numero di merge, così puoi confrontare i risultati.
Il vocabolario dipende dal corpus, e poi si congela. Il tokenizer si allena su un campione rappresentativo dei dati di pre-training, una volta sola. Dopo, cambiarlo è impossibile senza riaddestrare il modello: ogni token ha la sua riga nella matrice di embedding e la sua uscita nell'ultimo strato, quindi un vocabolario diverso renderebbe inutili quei pesi. Un modello multilingua usa un solo vocabolario condiviso, allenato su una miscela bilanciata di lingue; per questo i vocabolari multilingua sono più grandi (128.000–256.000 token).
Che cosa si configura. L'iperparametro principale è la dimensione del vocabolario, cioè quanti merge fare. La lunghezza dei singoli token non si fissa: emerge dalle frequenze. Nei fatti però è limitata dalla pre-tokenizzazione: prima del BPE un'espressione regolare divide il testo in blocchi (parole con lo spazio iniziale, numeri, punteggiatura) e i merge non superano mai quei confini. Alcune librerie offrono anche un tetto esplicito, facoltativo: max_sentencepiece_length in SentencePiece (default 16 caratteri) e max_token_length nel BpeTrainer di Hugging Face (default nessun limite).
Il capitolo 1 parla di liste di numeri moltiplicate per tabelle di pesi. In matematica una lista di numeri si chiama vettore e una tabella si chiama matrice. Questo richiamo costruisce, in otto passi brevi, le poche idee che servono per capire che cosa succede ai vettori dentro il modello. Basta la matematica delle superiori: ogni passo si apre con le tre cose da portarsi a casa, poi un esempio con numeri piccoli, poi la regola, e quasi sempre un laboratorio con la matematica vera.
Ogni passo si chiude con un riquadro richiudibile, «Dove ritorna nel modello»: collega l'idea ai pezzi del modello e contiene i conti più avanzati. È chiuso e facoltativo: alla prima lettura puoi saltarlo, e riaprirlo quando un capitolo successivo ci rimanda.
Se queste nozioni ti sono familiari, salta al Promemoria in fondo: raccoglie le definizioni in voci brevi, da riaprire quando un capitolo successivo ne usa una.
Un vettore è una lista ordinata di numeri, e la stessa lista si può vedere come un punto o come una freccia. Due vettori si sommano numero per numero, e moltiplicare un vettore per un numero allunga, accorcia o capovolge la sua freccia. Se le parole diventano vettori, le parole simili sono punti vicini, e perfino le frecce tra due parole possono avere un significato.
Un vettore è una lista ordinata di numeri. Le coordinate di una città, [latitudine ; longitudine], sono un vettore di 2 numeri; il vettore di un appartamento, [metri quadri ; locali ; piano ; prezzo], un vettore di 4 numeri. Quanti numeri contiene si chiama dimensione del vettore: i vettori dei token del capitolo 1 sono vettori di dimensione 4.096.
Tre modi di vedere la stessa lista. Con due numeri si può disegnare. [2 ; 1] si legge come una lista (è così che la vede il computer), come un punto del piano (2 passi a destra, 1 in su) o come una freccia che parte dall'origine e arriva in quel punto. Nella guida si passa dall'una all'altra: «due embedding vicini» è la lettura a punti, «due embedding che puntano nella stessa direzione» quella a frecce.
Somma. Due vettori si sommano numero per numero: [2 ; 1] + [−1 ; 1,5] = [2 − 1 ; 1 + 1,5] = [1 ; 2,5]. Con le frecce: si appoggia l'inizio della seconda freccia sulla punta della prima, e il risultato va dall'origine all'ultima punta. La sottrazione funziona allo stesso modo, numero per numero.
Moltiplicare per un numero. 2 · [−1 ; 1,5] = [−2 ; 3]: ogni elemento viene moltiplicato. La freccia si allunga (numero maggiore di 1), si accorcia (tra 0 e 1) o si gira dall'altra parte (numero negativo), ma resta sulla stessa retta. In questo contesto un numero singolo si chiama scalare.
Calcolo · somma e prodotto per un numero
Da 2 a 4.096 numeri. Uno spazio a 4.096 dimensioni non si può disegnare, ma le regole non cambiano: si somma e si moltiplica numero per numero. Un embedding da 4.096 numeri è un punto in quello spazio, e le parole usate in modo simile, come «gatto» e «cane», finiscono in punti vicini.
Più avanti vedrai che dentro il modello si sommano molti vettori: è il motivo per cui la somma è così importante.
Muovi i cursori di a e di b, poi il numero t per cui si moltiplica b: guarda dove finisce la punta della freccia scura a + t·b.
In grigio tratteggiato c'è b prima di essere moltiplicato per t. I due lati tratteggiati colorati mostrano che si arriva nello stesso punto anche facendo prima t·b e poi a: la somma è la diagonale del parallelogramma che ha a e t·b come lati (regola del parallelogramma).
Parole come punti: re − uomo + donna. È l'esempio del capitolo 1, riletto con le parole di questo passo. Ogni parola è un vettore di 2 numeri, [quanto è regale ; quanto è femminile]: uomo = [0,1 ; 0,1], donna = [0,1 ; 0,9], re = [0,9 ; 0,1], regina = [0,9 ; 0,9]. La freccia che porta da «uomo» a «donna» si calcola con una sottrazione, numero per numero; partendo da «re» e aggiungendo quella freccia si arriva su «regina».
Calcolo · da uomo a donna, da re a regina
Un piano con l'asse orizzontale «quanto è regale» e quello verticale «quanto è femminile», tutti e due da 0 a 1. Uomo in [0,1 ; 0,1] e donna in [0,1 ; 0,9], a sinistra; re in [0,9 ; 0,1] e regina in [0,9 ; 0,9], a destra. Da uomo a donna sale una freccia continua, donna − uomo = [0 ; 0,8]. Da re sale una freccia tratteggiata identica e parallela, che arriva esattamente su regina.
Con numeri inventati il conto torna esatto. Con i vettori veri non è così: in un sistema chiamato word2vec (Mikolov e colleghi, 2013) si osservò che re − uomo + donna arriva vicino a «regina», che è solo la parola più vicina al punto calcolato, e molte analogie di questo tipo non funzionano affatto. I numeri veri sono centinaia o migliaia e nessuno ha dato loro un nome come «regale»: è un indizio che lo spazio degli embedding ha una struttura geometrica, non una regola esatta.
Il residual stream è una somma di vettori. Dentro il modello (sezione D) il vettore di un token non viene sostituito strato dopo strato. Ogni strato calcola una correzione, cioè un altro vettore di 4.096 numeri, e la aggiunge al vettore che riceve: uscita = ingresso + correzione. Le correzioni sono due per strato: una dallo scambio di informazioni tra token (l'attention) e una dalla rielaborazione del singolo token (la MLP, Multi-Layer Perceptron, percettrone multistrato). Dopo N strati il vettore finale è quindi una somma di 2N + 1 vettori: l'embedding di partenza più tutte le correzioni. Questo flusso che si arricchisce si chiama residual stream («flusso residuo»).
La somma aiuta anche il training. Nella somma l'ingresso passa invariato, quindi il segnale che dice a ogni peso come correggersi (il gradiente, sezione G) ha sempre una strada diretta che non viene moltiplicata da nessuno strato, e non si spegne attraversandone decine.
Combinazione lineare. Mettendo insieme somma e moltiplicazione per un numero si ottiene s·a + t·b, una combinazione lineare di a e b. Se a e b non sono paralleli, scegliendo s e t si raggiunge qualsiasi punto del piano: due direzioni indipendenti bastano a descrivere uno spazio a due dimensioni. L'idea ritorna nel passo sulle trasformazioni qui sotto: il risultato di matrice × vettore è una combinazione lineare delle colonne della matrice.
Una precisazione sull'analogia. Nei test di word2vec la parola più vicina a re − uomo + donna si cerca escludendo le tre parole di partenza: senza questa esclusione spesso la più vicina resterebbe «re» stesso. Le analogie che riescono sono una parte, non tutte, e la «direzione del femminile» è solo approssimativamente la stessa per tutte le coppie.
Il prodotto scalare prende due liste della stessa lunghezza, moltiplica i numeri a coppie e somma i risultati. Il totale è grande e positivo se le due liste «vanno d'accordo», vicino a zero se non hanno niente in comune, negativo se sono opposte. Con le frecce misura quanto due frecce puntano nella stessa direzione, tenendo conto anche di quanto sono lunghe.
Due amici danno un voto da −2 a +2 agli stessi cinque film. Per capire se hanno gusti simili si moltiplicano i loro voti film per film e si sommano i prodotti: se un film piace a entrambi (+ × +) o non piace a nessuno dei due (− × −) il contributo è positivo, se sono in disaccordo è negativo.
Con i numeri. Anna vota [2 ; 1 ; −1 ; 0 ; 2], Bruno [1 ; 2 ; −2 ; 1 ; 1]. Film per film: 2·1 + 1·2 + (−1)·(−2) + 0·1 + 2·1 = 2 + 2 + 2 + 0 + 2 = 8, un totale alto: gusti simili. Carla vota [−2 ; −1 ; 1 ; 0 ; −2], l'opposto di Anna: −4 − 1 − 1 + 0 − 4 = −10, gusti opposti. Questo totale è il prodotto scalare dei due vettori di voti.
Calcolo · i gusti di Anna, Bruno e Carla
Con le frecce. Lo stesso numero si calcola anche con le lunghezze delle due frecce e l'angolo θ tra loro. Il coseno dell'angolo, cos θ, vale 1 se le frecce puntano nello stesso verso, 0 se sono perpendicolari, −1 se sono opposte:
Quindi il segno dipende solo dall'angolo: positivo sotto i 90°, zero a 90° (le frecce si dicono ortogonali, cioè perpendicolari), negativo oltre. La grandezza invece dipende anche dalle lunghezze: una freccia molto lunga dà prodotti scalari grandi anche con poca somiglianza di direzione. Per confrontare solo le direzioni serve il coseno del prossimo passo.
L'ombra. Il laboratorio mostra un'altra lettura. Immagina di illuminare b dall'alto rispetto alla retta su cui sta a: l'ombra di b su quella retta è lunga |b| cos θ e dice quanta parte di b va nella direzione di a. Si chiama proiezione di b su a, e il prodotto scalare è la lunghezza di a moltiplicata per quell'ombra.
Ruota a e b e cambia la lunghezza di b: osserva l'ombra di b sulla retta di a (il segmento spesso) e il segno del prodotto scalare.
a ha lunghezza fissa 1,2. Il segmento spesso sulla retta di a è l'ombra (la proiezione) di b: quando cade dalla parte opposta ad a, il prodotto scalare è negativo. Le due formule del riquadro danno sempre lo stesso numero.
Perché le due formule coincidono. Per il teorema di Carnot, il teorema di Pitagora generalizzato a un triangolo qualsiasi. Si scrive in due modi la lunghezza del terzo lato, a − b, e si confrontano le due righe:
Calcolo · perché le due formule coincidono
Calcolo · l'ombra di b su a
Nell'attention: query · key. Negli strati (sezione D) ogni token calcola due vettori: una query, «che cosa sto cercando», e una key, «che cosa offro». Il punteggio con cui il token i guarda il token j è il prodotto scalare qᵢ · kⱼ, diviso per √dk: tanto più alto quanto più ciò che i cerca è allineato con ciò che j offre. Tutti i punteggi insieme sono un unico prodotto tra matrici, Q·Kᵀ (passo sulle shape).
In uscita: un punteggio per ogni token. Il punteggio (logit) del token v è il prodotto scalare tra l'ultimo vettore del modello e la riga di v nella matrice di output: V prodotti scalari, uno per voce del vocabolario. Vince la riga più allineata; la softmax trasforma i punteggi in probabilità (sezione C e capitolo 2).
Perché nell'attention si divide per √dk. Se query e key hanno dk elementi indipendenti, con media 0 e varianza 1, il loro prodotto scalare è la somma di dk termini casuali di varianza 1:
Calcolo · perché si divide per √dk
Senza la divisione, con dk = 128 i punteggi oscillerebbero di circa ±11, e una softmax su numeri così distanti mette quasi tutto il peso su un solo token: i gradienti verso gli altri diventano quasi nulli e il training si inceppa. Dividere per √dk ≈ 11,3 riporta la deviazione standard a 1, qualunque sia la dimensione (Vaswani e colleghi, 2017). È lo stesso fenomeno che, visto dall'altra parte, rende quasi perpendicolari i vettori casuali nel riquadro del prossimo passo.
La norma di un vettore è la lunghezza della sua freccia, e si calcola con il teorema di Pitagora. Dividendo un vettore per la sua norma si ottiene una freccia lunga 1 che punta nello stesso verso. La similarità coseno confronta due vettori guardando solo dove puntano, non quanto sono lunghi: 1 se puntano nello stesso verso, 0 se sono perpendicolari, −1 se sono opposti.
Lunghezza con Pitagora. La freccia [3 ; 4] fa 3 passi a destra e 4 in su: è l'ipotenusa di un triangolo rettangolo con cateti 3 e 4, quindi è lunga √(3² + 4²) = √25 = 5. Con più numeri si fa lo stesso: si sommano i quadrati di tutti gli elementi e si prende la radice. La lunghezza di a si scrive |a| e si chiama norma.
Calcolo · la norma
Una freccia lunga 1. Dividendo ogni elemento per la norma, la freccia diventa lunga 1 senza cambiare direzione: [3 ; 4] / 5 = [0,6 ; 0,8]. Si chiama vettore unitario. È come portare tutte le frecce alla stessa lunghezza per confrontare soltanto dove puntano.
Similarità coseno. a = [3 ; 4] e b = [6 ; 8] puntano nello stesso verso, ma b è lungo il doppio. Il prodotto scalare, 3·6 + 4·8 = 50, dipende anche dalle lunghezze. Dividendolo per le due norme, 5 e 10, si ottiene 50 / 50 = 1: stessa direzione. Con c = [4 ; −3], invece, a · c = 12 − 12 = 0: frecce perpendicolari, coseno 0.
Calcolo · la similarità coseno
È la misura usata per cercare testi simili: si confronta il vettore di una domanda con quello di ogni documento e si tengono i documenti con il coseno più alto. Nel laboratorio del prodotto scalare, qui sopra, il coseno è l'ultima riga del riquadro grigio.
In spazi con migliaia di dimensioni succede una cosa che il piano non lascia immaginare: due direzioni prese a caso sono quasi sempre quasi perpendicolari. Il riquadro qui sotto lo mostra con un laboratorio.
Normalizzare dentro il modello. Negli strati (sezione D), prima di ogni calcolo il vettore di un token viene riportata a una scala standard con RMSNorm (Root Mean Square Normalization, normalizzazione per la radice della media dei quadrati). Calcola la grandezza tipica di un elemento, poi divide ogni elemento per quella e lo moltiplica per un guadagno gi imparato, uno per dimensione:
Calcolo · la normalizzazione RMSNorm
Senza i guadagni ogni vettore esce con norma esattamente √d (√2 nell'esempio): la direzione resta, la scala viene azzerata. Serve perché il residual stream (riquadro del passo sui vettori) è una somma che cresce strato dopo strato: senza normalizzazione ogni pezzo dello strato riceverebbe numeri su scale diverse, e i gradienti con loro.
In alta dimensione quasi tutto è ortogonale. Prendi due direzioni a caso nel piano: l'angolo tra loro è uniforme, e coseni vicini a ±1 capitano spesso. In 4.096 dimensioni due vettori casuali sono quasi sempre quasi perpendicolari, con coseno tra −0,05 e +0,05. Il motivo: il prodotto scalare di due vettori casuali è una somma di d termini di segno casuale, che in parte si annullano e crescono solo come √d, mentre il prodotto delle norme cresce come d. Il rapporto va come 1/√d:
Calcolo · il coseno in alta dimensione
Perché conta. Se si accetta una piccola tolleranza (per esempio coseni entro ±0,1), il numero di direzioni «quasi indipendenti» che entrano in uno spazio cresce in modo esponenziale con d, ben oltre il numero delle dimensioni: è una conseguenza del lemma di Johnson-Lindenstrauss. La ricerca sull'interpretabilità suggerisce che i modelli sfruttino proprio questo spazio: rappresentano molti più concetti che dimensioni, ciascuno come una direzione quasi ortogonale alle altre, accettando una piccola interferenza (superposition, sovrapposizione; Elhage e colleghi, 2022). Vale anche al contrario: in 4.096 dimensioni un coseno di 0,2 tra due vettori non è «poco», è quasi 13 deviazioni standard sopra quello che darebbe il caso.
Sposta la dimensione d da 2 a 4.096 e guarda l'istogramma dei coseni di 1.000 coppie di vettori casuali stringersi intorno allo zero.
Ogni elemento dei vettori è estratto da una gaussiana (media 0, varianza 1) con un generatore pseudo-casuale con seme (mulberry32) e la trasformazione di Box-Muller: lo stesso seme dà sempre lo stesso istogramma. La curva è la distribuzione esatta del coseno tra due direzioni casuali in d dimensioni, con densità proporzionale a (1 − x²)(d−3)/2. Gli embedding veri non sono vettori casuali: di solito condividono una componente comune, e il coseno tra due token non correlati spesso non è centrato sullo zero.
Una matrice è una tabella di numeri che funziona come una macchina: entra un vettore, ne esce un altro. Ogni numero dell'uscita è il prodotto scalare tra una riga della matrice e il vettore d'ingresso. Una matrice con m righe e n colonne prende vettori di n numeri e restituisce vettori di m numeri.
Un esempio 2 × 2. La matrice A = [[2, 1], [0, 1]] ha due righe: la prima contiene 2 e 1, la seconda 0 e 1 (in una riga di testo si scrive così, una coppia di parentesi quadre per ogni riga; «2 × 2» vuol dire 2 righe e 2 colonne). Entra il vettore x = [1 ; 1]. La prima riga per x dà 2·1 + 1·1 = 3; la seconda dà 0·1 + 1·1 = 1. Esce Ax = [3 ; 1].
Calcolo · A · x, riga per riga e colonna per colonna
Ogni riga è un rilevatore. Il prodotto scalare misura quanto due vettori vanno d'accordo, quindi ogni riga della matrice «misura» quanto il vettore d'ingresso le somiglia, e l'uscita è l'elenco delle misure. Una matrice con 32.000 righe, una per token del vocabolario, applicata al vettore finale del capitolo 1 dà 32.000 misure: sono i punteggi del passo 5.
Colonna per colonna. Lo stesso risultato si ottiene prendendo x₁ volte la prima colonna più x₂ volte la seconda: 1·[2 ; 0] + 1·[1 ; 1] = [3 ; 1]. Le colonne dicono quindi dove finiscono le frecce [1 ; 0] e [0 ; 1].
Che cosa fa al piano. In due dimensioni si vede che cosa fa una matrice a tutti i punti insieme: la griglia può ruotare, allungarsi, inclinarsi o schiacciarsi, ma le rette restano rette, le parallele restano parallele e l'origine resta ferma. Per questo si dice che una matrice è una trasformazione lineare. Quattro casi tipici, tutti nel laboratorio:
Matrici non quadrate. Una matrice con 3 righe e 2 colonne prende vettori di 2 numeri e ne restituisce 3, uno per riga: può allungare o accorciare la lista. Dentro il modello capita spesso.
Premi i pulsanti con le matrici di esempio, oppure muovi i quattro numeri di A: guarda come si deforma la griglia e dove finisce la freccia x = [1 ; 1].
La griglia sottile di fondo è il piano di partenza, quella colorata è il piano dopo A. Le due frecce colorate sono le colonne di A, cioè dove finiscono [1 ; 0] e [0 ; 1]; il parallelogramma che formano è il quadratino di lato 1 dopo la trasformazione. Il numero «det A» nel riquadro grigio dice per quanto si moltiplica la sua area (si chiama determinante, vedi il riquadro in fondo al passo).
Il caso più semplice: un vettore con un solo 1. Un vettore one-hot ha tutti 0 tranne un 1, per esempio [0 ; 0 ; 1 ; 0 ; 0]: uno solo è «acceso» (hot). Moltiplicandolo per una matrice, come nel laboratorio qui sotto, tutti i termini con lo 0 si annullano e resta solo la riga che corrisponde all'1. È così che il modello trova il vettore di un token: la tabella degli embedding (E) è una matrice con una riga per token, e l'ID dice quale riga prendere (sezione C).
Scegli un token e guarda quale riga della matrice E sopravvive alla moltiplicazione.
Il vettore one-hot «seleziona» una riga: moltiplicando, tutti i termini con lo 0 si annullano e resta solo la riga dell'ID acceso. Per questo, nella pratica, l'embedding di un token si ottiene leggendo direttamente la sua riga, senza fare davvero la moltiplicazione.
Il determinante dice per quanto la trasformazione moltiplica le aree:
Vale 1 per rotazioni e tagli, s₁·s₂ per la scala, 0 quando il piano viene schiacciato su una retta (la proiezione: il rango scende da 2 a 1, passo sul basso rango), negativo quando viene ribaltato come in uno specchio.
Composizione: l'ordine conta. Applicare prima B e poi A equivale ad applicare una sola matrice, il prodotto A·B: A(Bx) = (AB)x, che si legge da destra a sinistra. Scambiare l'ordine cambia il risultato. Con R = [[0, −1], [1, 0]] (rotazione di 90°) e S = [[2, 0], [0, 1]] (raddoppia la prima coordinata):
Calcolo · l'ordine delle trasformazioni conta
Due conseguenze: una pila di strati soltanto lineari collassa in una matrice sola (passo sulla non linearità), e nel calcolo all'indietro dei gradienti il segnale attraversa gli strati in ordine inverso, moltiplicato ogni volta per la trasposta Wᵀ, cioè la matrice dei pesi con righe e colonne scambiate (sezione G).
Le matrici negli strati. Nella MLP di ogni strato (sezione D) una prima matrice allarga il vettore da d a un numero più grande (4d nel transformer originale, 11.008 ≈ 2,7d in Llama 2 7B) e una seconda la riporta a d; nell'attention ogni «testa» la proietta da d a pochi numeri (passo sul basso rango). Le rotazioni servono a RoPE (Rotary Position Embedding, embedding posizionale rotazionale), che ruota coppie di elementi di query e key di un angolo che dipende dalla posizione del token (sezione C). L'identità, la matrice che lascia tutto com'è, è ciò che fa la connessione residua sul suo «binario diretto».
Il one-hot nella loss. Il one-hot compare anche nel calcolo dell'errore: è il vettore «obiettivo» y, con un 1 sul token giusto, con cui si confronta la distribuzione di probabilità p del modello; il gradiente rispetto ai punteggi è p − y (capitolo 2).
Un tensore è una tabella di numeri con un numero qualsiasi di indici: un numero solo ne ha zero, una lista uno, una tabella due, una pila di tabelle tre. La shape (forma) è l'elenco delle lunghezze, per esempio [2 × 3 × 4]: 2 tabelle di 3 righe e 4 colonne, 24 numeri in tutto. Dentro un LLM i dati viaggiano quasi sempre come tensori, perché si elaborano tante frasi e tanti token insieme.
Un foglio di calcolo aiuta a vedere la scala. Una casella contiene un numero; una riga di caselle è una lista; un foglio è una tabella con righe e colonne; una cartella di fogli tutti uguali aggiunge una terza direzione, «quale foglio»; uno scaffale di cartelle ne aggiunge una quarta. In matematica sono lo scalare, il vettore, la matrice e poi i tensori con tre, quattro o più indici (si dice anche assi).
Rango 0 · scalare shape [ ], 1 numero. Per esempio l'errore medio del modello su un gruppo di frasi.
Rango 1 · vettore shape [d], d numeri. Il vettore di un token.
Rango 2 · matrice shape [L × d]. I vettori di una frase di L token, una per riga.
Rango 3 shape [B × L × d]: B frasi elaborate insieme, ognuna una matrice L × d.
Rango 4 shape [B × h × L × L]: una fila di B cubi. Compare dentro gli strati (vedi il riquadro in fondo al passo).
Cinque disegni affiancati, dal più semplice al più ricco. Lo scalare è una sola casella, con shape vuota. Il vettore è una fila di d caselle. La matrice è una griglia di L righe e d colonne. Il tensore di rango 3 è una pila di B griglie L × d: i suoi tre assi sono B (la profondità della pila), L (le righe) e d (le colonne). Il tensore di rango 4 è una fila di B cubi, ciascuno di forma h × L × L.
Shape e numero di elementi. Due frasi di 3 token ciascuna, ogni token con un vettore di 4 numeri: il tensore ha shape [2 × 3 × 4] e contiene 2 · 3 · 4 = 24 numeri. In generale il numero di elementi è il prodotto delle lunghezze nella shape. Con le lettere della guida, [B × L × d] vuol dire B frasi, ciascuna di L token, ciascuno con un vettore di d numeri.
Calcolo · gli elementi di un tensore
Il numero di indici si chiama rango del tensore. Attenzione: è un uso diverso della parola «rango» rispetto al passo 8, dove conta le direzioni indipendenti di una matrice.
Indici e fette. Un singolo numero si trova con un indice per asse, contando da 0 come nei programmi: X[1, 2, 0] è il primo numero del vettore del terzo token della seconda frase. Fissare solo alcuni indici vuol dire prendere una fetta, con un asse in meno per ogni indice fissato:
| Espressione | Shape | Che cos'è |
|---|---|---|
| X | [2 × 3 × 4] | tutte e due le frasi |
| X[1] | [3 × 4] | la seconda frase: una matrice, un foglio della pila |
| X[1, 2] | [4] | il vettore del terzo token della seconda frase |
| X[1, 2, 0] | [ ] | un numero solo |
Un'avvertenza sul nome: in fisica «tensore» ha un significato più ricco; nel deep learning vuol dire soltanto tabella di numeri con più indici (in NumPy, la libreria di calcolo di Python, lo stesso oggetto si chiama ndarray, «array a n dimensioni»).
Non è il tensore della fisica. In fisica un tensore è un oggetto che cambia in un modo preciso quando si cambia sistema di coordinate, come lo sforzo dentro un materiale o la curvatura dello spazio-tempo. Nel deep learning la parola indica soltanto un array multidimensionale, senza nessuna regola di trasformazione. In PyTorch il rango si legge con ndim. Lo scalare ha shape vuota, [ ], e un solo elemento: il prodotto di nessun fattore vale 1. Un asse lungo 1 non cambia il numero di elementi ma cambia la shape: [L × d] e [1 × L × d] contengono gli stessi numeri, e il secondo è un batch di una sola sequenza.
Dove compaiono in un LLM. Con le dimensioni di Llama 2 7B (d = 4.096, h = 32 teste dell'attention da dk = d/h = 128 numeri, vocabolario V = 32.000, contesto L = 4.096) e un batch (gruppo di sequenze, capitolo 2) di B = 8 sequenze:
| Tensore | Shape | Con i numeri di Llama 2 7B | Elementi |
|---|---|---|---|
| Loss del batch | [ ] | [ ] | 1 |
| Guadagni di RMSNorm, un bias | [d] | [4.096] | 4.096 |
| ID dei token del batch | [B × L] | [8 × 4.096] | 32.768 |
| Pesi di uno strato, per esempio WQ | [d × d] | [4.096 × 4.096] | 16.777.216 |
| Matrice di embedding E | [V × d] | [32.000 × 4.096] | 131.072.000 |
| Attivazioni (residual stream) | [B × L × d] | [8 × 4.096 × 4.096] | 134.217.728 |
| Q, K o V divise in teste | [B × h × L × dk] | [8 × 32 × 4.096 × 128] | 134.217.728 |
| Punteggi dell'attention, per blocco | [B × h × L × L] | [8 × 32 × 4.096 × 4.096] | 4.294.967.296 |
| Logit | [B × L × V] | [8 × 4.096 × 32.000] | 1.048.576.000 |
Calcolo · le attivazioni di uno strato
I pesi hanno shape fisse, decise dall'architettura; le attivazioni dipendono da B e L, cioè da quanto testo si elabora insieme. I punteggi dell'attention sono il tensore più grande perché crescono con L²: oltre 4 miliardi di numeri per blocco, che è il motivo per cui tecniche come FlashAttention evitano di scriverli tutti in memoria.
Assi per posizione, e i due punti. In [B × L × d] l'asse 0 è il batch, l'asse 1 la posizione nella sequenza, l'asse 2 (o −1, «l'ultimo») la feature, cioè la coordinata del vettore. I due punti «:» vogliono dire «tutto l'asse»: X[:, l] ha shape [B × d] ed è il token in posizione l di ogni sequenza; X[:, :, j] ha shape [B × L] ed è la feature j di tutti i token.
Le operazioni che servono davvero. Con i tensori un modello fa poche cose, sempre le stesse.
1 · Elemento per elemento. Due tensori con la stessa shape si combinano casella per casella, e il risultato ha ancora quella shape: la connessione residua X + F(X) somma due tensori [B × L × d], e le funzioni non lineari del passo sulla non linearità si applicano a ogni numero separatamente.
2 · Broadcasting. Quando le shape sono diverse, si allineano partendo da destra: ogni coppia di lunghezze deve essere uguale, oppure una delle due deve valere 1 o mancare. L'asse lungo 1 (o mancante) viene ripetuto quanto serve, senza copiare i dati. È così che un vettore di bias si somma a tutto il batch:
3 · Prodotto matriciale in batch. L'operatore @ di PyTorch moltiplica le ultime due dimensioni con la regola delle matrici e tratta tutti gli assi precedenti come un elenco di prodotti indipendenti, da fare in parallelo. Gli assi di batch devono coincidere o potersi ripetere per broadcasting, come i pesi, che sono gli stessi per tutti:
4 · Reshape e transpose: dividere le teste. Per l'attention a più teste, il vettore di d numeri di ogni token si legge come h pezzi da dk = d/h, poi l'asse delle teste si porta davanti a quello dei token, così che ogni testa abbia la sua matrice [L × dk]:
Nessuno dei due passaggi sposta numeri in memoria. Un tensore è salvato come una sola fila di numeri, contigua, in cui l'ultimo indice è quello che varia più in fretta; lo stride di un asse dice di quante posizioni si salta per far avanzare di 1 quell'indice. Il reshape cambia solo il modo di contare le posizioni, il transpose scambia due stride:
5 · Riduzioni lungo un asse. Una somma, una media o un massimo lungo un asse lo fanno sparire: la somma sull'asse d trasforma [B × L × d] in [B × L], un numero per token. RMSNorm calcola la media dei quadrati sull'ultimo asse e tiene l'asse con lunghezza 1 (keepdim=True), così il risultato [B × L × 1] si può riusare per broadcasting per dividere ogni vettore. La softmax sull'ultimo asse è una riduzione a metà: la shape non cambia, ma ogni riga viene normalizzata in modo da sommare a 1. La loss, infine, fa la media su tutte le B · L posizioni e torna uno scalare.
Dove ritorna. X [B × L × d] @ W [d × d] è un prodotto tra matrici ripetuto per B sequenze con gli stessi pesi: shape e costo si contano come nel passo sulle shape, moltiplicando per B. Dopo reshape e transpose le h teste sono un asse in più, e Q·Kᵀ calcola B · h matrici di punteggi in un colpo solo; la softmax lavora sull'ultimo asse, riga per riga (sezione D).
Scegli le dimensioni e un'operazione, poi clicca una cella (o passaci sopra) per fissare gli indici b e l: guarda come cambiano shape, numero di elementi e stride.
Ogni fetta della pila è una sequenza del batch: è disegnata a celle solo quella scelta con b, le altre ne mostrano il bordo. d scorre solo sui multipli di h, perché ogni testa riceve dk = d/h colonne. I valori sono interi casuali con seme fisso, gli indici contano da 0. Shape, stride e posizioni in memoria sono quelli di un tensore contiguo in ordine per righe, come lo crea PyTorch; in un modello vero d vale migliaia e le righe B · L sono milioni.
Due matrici si possono moltiplicare solo se il numero di colonne della prima è uguale al numero di righe della seconda: [m × n] · [n × p] dà [m × p]. Ogni numero del risultato è il prodotto scalare di una riga della prima con una colonna della seconda. Serve a trasformare i vettori di tutti i token in un colpo solo, mettendole una sotto l'altra.
Un esempio con i numeri. A ha 2 righe e 3 colonne, B ha 3 righe e 2 colonne:
| 1 | 0 | 2 |
| 0 | 1 | 1 |
| 1 | 2 |
| 0 | 1 |
| 3 | 0 |
| 7 | 2 |
| 3 | 1 |
Calcolo · A · B, elemento per elemento
La regola delle dimensioni interne. In [2 × 3] · [3 × 2] le due dimensioni «interne», 3 e 3, devono coincidere, perché sono la lunghezza dei prodotti scalari tra righe e colonne; nel risultato spariscono, e restano le «esterne»: [2 × 2]. [2 × 3] · [2 × 3] invece non si può fare: le righe della prima hanno 3 numeri, le colonne della seconda solo 2.
L'ordine conta. A·B e B·A in generale sono diversi. Nell'esempio, B·A si può calcolare ([3 × 2] · [2 × 3]) ma dà una matrice [3 × 3], non [2 × 2]. Il vettore del passo sulle trasformazioni qui sotto è solo il caso di una matrice con una colonna: [2 × 2] · [2 × 1] = [2 × 1].
Perché serve: tutti i token insieme. Una frase di L token si scrive come una matrice X con una riga per token, cioè [L × d]. Moltiplicandola per una matrice di pesi W di shape [d × d] si trasformano tutti i vettori in un colpo solo: la riga i del risultato è il vettore del token i trasformata. Con la frase del capitolo 1: [4 × 4.096] · [4.096 × 4.096] = [4 × 4.096], quattro vettori entrano e quattro escono. È come una fabbrica che mette mille sedie sul nastro e accende la cabina di verniciatura una volta sola: le GPU (Graphics Processing Unit, processori grafici) fanno migliaia di moltiplicazioni in parallelo.
Quanto costa. Ogni numero del risultato richiede n moltiplicazioni e circa altrettante somme, e il risultato ha m · p numeri: con le dimensioni di un modello vero il conto arriva a miliardi di operazioni per ogni token. I numeri precisi, e da dove viene la regola per cui allenare un modello costa circa 6 operazioni per peso per token, sono nel riquadro.
Convenzioni. In questa guida i token sono righe e si scrive X·W; la libreria PyTorch salva la matrice di uno strato lineare come [uscite × ingressi] e calcola X·Wᵀ, che è la stessa cosa scritta al contrario. Quando le shape non tornano il codice si ferma con un errore, e seguire le shape passo per passo è il modo più rapido di capire un'architettura.
L'attention in quattro prodotti. Negli strati (sezione D) tre proiezioni danno una matrice [L × d] per ciascuno dei tre ruoli (query, key, value), divisa poi tra h teste di dk = d/h colonne ciascuna con il reshape e il transpose del riquadro sui tensori. Per ogni testa i conti sono:
Le uscite delle teste si affiancano di nuovo in [L × d] e passano per la quarta matrice, WO. Con un batch di B sequenze X diventa il tensore [B × L × d] e i punteggi [B × h × L × L]; le matrici dei pesi sono le stesse per tutte le sequenze, e il costo si moltiplica per B.
Quanto costa, in FLOP. C [m × p] ha m·p elementi, ciascuno un prodotto scalare lungo n: n moltiplicazioni e n − 1 somme, cioè circa 2·n FLOP (floating-point operation, operazione in virgola mobile). I conti, dal singolo prodotto all'intero training, con N parametri:
Calcolo · quanto costa un prodotto tra matrici
Per uno strato lineare W ha d² parametri, quindi i suoi 2·L·d² FLOP sono una moltiplicazione e una somma per ogni peso e per ogni token. Vale per tutte le matrici del modello, quindi il calcolo in avanti (forward) di un modello con N parametri costa circa 2N FLOP per token. Il calcolo all'indietro (backward, sezione G) costa circa il doppio: per ogni strato servono due prodotti della stessa taglia, uno per il gradiente rispetto all'ingresso (con Wᵀ, da passare allo strato sotto) e uno per il gradiente rispetto ai pesi. In totale circa 6N FLOP per token: è la regola «calcolo ≈ 6 × parametri × token» delle scaling laws.
Il termine che cresce con L². Due prodotti dell'attention non toccano nessun parametro: Q·Kᵀ e (pesi)·V costano circa 2·L²·d FLOP ciascuno per blocco, sommando tutte le teste. La regola dei 6N li ignora, e finché L è piccolo rispetto a d va bene; quando il contesto si allunga crescono con il quadrato di L e diventano la parte più cara. Il laboratorio lo mostra passaggio per passaggio.
Cambia la lunghezza del contesto L, la dimensione d e il numero di teste, e segui shape e FLOP di ogni passaggio: le barre rosse sono quelle che crescono con L².
Conti per una sola sequenza e un solo blocco; con un batch di B sequenze tutto si moltiplica per B, con N blocchi per N. La softmax, la maschera e la divisione per √dk costano pochi FLOP per elemento e non sono contate. Tecniche come FlashAttention evitano di salvare in memoria l'intera matrice dei punteggi, ma i FLOP restano proporzionali a L².
Due moltiplicazioni per matrici di fila equivalgono sempre a una moltiplicazione sola: senza nient'altro, impilare tanti strati non servirebbe a niente. Per questo tra una matrice e l'altra si mette una funzione «con una piega», applicata a ogni numero separatamente; la più semplice è la ReLU, che trasforma i numeri negativi in 0. Con tante pieghe si può imitare quasi qualsiasi curva.
Con le sole matrici un foglio si può ruotare, allungare e inclinare, ma non piegare: le rette restano rette. E due matrici applicate una dopo l'altra fanno esattamente quello che fa una matrice sola, il loro prodotto: dieci strati soltanto lineari in fila equivalgono a uno strato solo.
Il collasso, con i numeri. Prendi W₁ = [[1, 2], [0, 1]] e W₂ = [[1, 0], [−1, 1]], e applicale in fila a x = [1 ; 1]; poi rifai il conto con il loro prodotto W₂W₁ (calcolato con la regola del passo sulle shape):
Calcolo · due strati lineari collassano in uno
Due strati con 8 pesi si comportano come uno con 4: la profondità non ha aggiunto niente.
Una piega: la ReLU. La ReLU (Rectified Linear Unit, unità lineare rettificata) lascia passare i numeri positivi e trasforma i negativi in 0: ReLU(3) = 3, ReLU(−2) = 0. Su un vettore si applica a ogni numero: ReLU([−1 ; 2]) = [0 ; 2]. Il suo grafico è una retta con un gomito nello zero. Si mette tra due matrici:
Con la piega in mezzo il collasso non avviene più. Per x = [1 ; −1]: W₁x = [1 − 2 ; −1] = [−1 ; −1], la ReLU lo porta a [0 ; 0] e W₂ dà [0 ; 0]. La matrice unica W₂W₁ darebbe invece [1 − 2 ; −1 + 1] = [−1 ; 0]. La piega ha impedito alle due matrici di fondersi in una.
Calcolo · con la ReLU in mezzo il collasso non avviene
Tante pieghe, una curva. Ogni unità con la ReLU mette un gomito in un punto diverso, e la somma di tanti gomiti è una linea spezzata. Con abbastanza pezzi una spezzata segue bene quanto si vuole qualsiasi curva continua su un intervallo: è l'idea del teorema di approssimazione universale, e il laboratorio la costruisce a mano.
Aumenta il numero di unità k e guarda la spezzata colorata avvicinarsi alla curva grigia (sin x); poi passa a «Identità (nessuna)» e osserva che resta solo una retta.
La rete è f(x) = b + Σ cⱼ · ReLU(x − xⱼ): ogni unità ha un gomito nel punto xⱼ e un peso d'uscita cⱼ. Qui i pesi non sono imparati: sono calcolati a mano perché la spezzata passi per sin x nei punti segnati, distribuiti a passo uguale su [−π, π]. In grigio tratteggiato i singoli gomiti cⱼ · ReLU(x − xⱼ), la cui somma è la curva colorata. Con il training (spiegato più avanti nel capitolo) una rete troverebbe da sola gomiti e pesi, di solito un po' migliori.
I termini costanti non salvano. Aggiungere a ogni strato un termine costante (il bias) non cambia la conclusione: una funzione lineare più una costante, composta con un'altra della stessa forma, resta della stessa forma.
Nella MLP di ogni strato. In ogni blocco del transformer la MLP (Multi-Layer Perceptron, percettrone multistrato) allarga il vettore da d a un numero più grande (4d nel transformer originale, 11.008 ≈ 2,7d in Llama 2 7B), applica la non linearità elemento per elemento e la riporta a d: in un modello da 7 miliardi di parametri sono circa 11.000 unità non lineari per token, in ogni strato (sezione D). I modelli moderni usano versioni più morbide della ReLU, come GELU (Gaussian Error Linear Unit) e SiLU (Sigmoid Linear Unit), o la variante «a cancello» SwiGLU (Swish-Gated Linear Unit), ma il ruolo è lo stesso. Anche l'attention ha una sua non linearità, la softmax.
Nel calcolo dei gradienti. Nel backward la derivata della ReLU vale 1 dove l'unità è accesa e 0 dove è spenta: il gradiente passa oppure si ferma (sezione G).
Come sono calcolati i pesi del laboratorio. Ogni unità ha peso d'ingresso 1 e bias −xⱼ; cⱼ è la variazione di pendenza della spezzata nel nodo j (interpolazione lineare a tratti). Raddoppiando le unità l'errore massimo si divide circa per 4, perché l'errore dell'interpolazione lineare cala con il quadrato del passo tra i nodi.
Molte matrici grandi contengono tanti numeri ma poca informazione davvero indipendente. Una matrice grande si può allora ottenere moltiplicando due matrici sottili, e servono molti meno numeri per descriverla. Il prezzo è che le sue uscite possono muoversi solo in poche direzioni: quante, si chiama rango.
La tavola pitagorica ha 100 caselle, ma per ricostruirla bastano 20 numeri, da 1 a 10 in riga e da 1 a 10 in colonna: ogni casella è il prodotto della sua riga per la sua colonna.
Una colonna per una riga. Con la colonna u = [1 ; 2 ; 3] e la riga v = [1, 0, 2] si costruisce una tabella 3 × 3: la casella nella riga i e nella colonna j è uᵢ · vⱼ.
Calcolo · una colonna per una riga
Qualunque vettore entri in questa matrice, l'uscita è sempre un multiplo di [1 ; 2 ; 3]: tutte le uscite stanno su una sola retta. Si dice che la matrice ha rango 1. In generale il rango è il numero di direzioni indipendenti in cui possono cadere le uscite.
Due matrici sottili. Generalizzando: una matrice d × d si può costruire come B · A, con B di shape [d × r] e A di shape [r × d], e r piccolo. Per la regola delle shape il risultato è [d × d], ma servono solo 2·d·r numeri invece di d·d. Con d = 1.000 e r = 10: 20.000 numeri invece di 1.000.000, cinquanta volte meno.
Calcolo · quanti numeri risparmia il basso rango
Applicarla è altrettanto economico: x passa prima per A, che lo riduce a r numeri (un collo di bottiglia), poi per B, che lo riporta a d.
Scegli la dimensione d e il rango r, e confronta quanti numeri servono per una matrice d × d piena e per la coppia di matrici sottili B·A.
La proiezione ha rango 1. La proiezione vista nel passo sulle trasformazioni qui sotto è una matrice di rango 1: qualunque x entri, l'uscita cade sulla stessa retta. In notazione matematica una matrice di rango 1 si scrive u·vᵀ, dove vᵀ (la «trasposta» di v) è v scritto come riga.
LoRA (Low-Rank Adaptation, adattamento a basso rango). Nel fine-tuning con LoRA (Hu e colleghi, 2021) la matrice originale W resta congelata e si allena solo una correzione a basso rango, con B inizializzata a zero, così all'inizio il modello è identico all'originale (il paper moltiplica anche B·A per un fattore di scala α/r). I conti per d = 4.096:
Calcolo · LoRA con d = 4.096
Su un modello da 7 miliardi di parametri, LoRA con r = 16 sulle matrici WQ e WV di tutti i 32 blocchi allena circa 8,4 milioni di parametri, lo 0,12% del totale. Funziona perché la modifica che serve al fine-tuning ha, di fatto, un rango basso: nel paper anche valori di r molto piccoli danno risultati vicini al fine-tuning completo.
Le teste dell'attention lavorano a basso rango. Negli strati (sezione D) ogni testa ha le sue proiezioni WQ e WK da d a dk = d/h numeri: in un modello da 7 miliardi di parametri, da 4.096 a 128. Il punteggio qᵢ · kⱼ usa quindi la matrice WQ·WKᵀ, grande d × d ma di rango al massimo 128: ogni testa confronta i token guardando solo un sottospazio di 128 direzioni, diverso da quello delle altre teste. Con 32 teste si ottengono 32 punti di vista diversi sugli stessi 4.096 numeri.
Le stesse nozioni degli otto passi, in un vettore ciascuna, da riaprire quando un capitolo successivo le usa. Alcune voci citano pezzi del modello spiegati più avanti nel capitolo.
Nel capitolo 1 ogni ID diventava una riga di una tabella di 12 × 8 numeri scritti a mano. Da dove vengono i 131 milioni di numeri della tabella di un modello vero?
Il primo strato di ogni LLM sostituisce ciascun ID con un vettore di d numeri (d = 4.096 in un modello da 7B), cioè un punto in uno spazio a d dimensioni: l'embedding del token. Da questo momento il testo non esiste più: esistono L punti in uno spazio a 4.096 dimensioni, uno per token, e il resto del modello sposta e mescola questi punti.
Come si calcola: non si calcola, si legge. Il modello contiene una matrice di embedding E con una riga per ogni token del vocabolario (V righe × d colonne). L'embedding del token con ID 9412 è semplicemente la riga 9412 di E. Matematicamente equivale a moltiplicare un vettore one-hot (tutti zeri, con un 1 in posizione 9412) per E, come mostra il laboratorio «one-hot × matrice» della sezione B; nella pratica si fa una lettura diretta (lookup), molto più veloce. La matrice di input [B × L] di ID diventa così un tensore [B × L × d] di vettori.
Da dove vengono i numeri di E? Non esiste un embedding «giusto» da assegnare a un token: nessuno li sceglie. Il procedimento è puramente meccanico:
Questa matrice è costruita sul vocabolario del laboratorio BPE qui sopra: se esegui altri merge, compaiono nuove righe. Qui d = 4 per leggibilità.
Premendo «Ri-inizializza» i numeri cambiano completamente, e va bene così: all'inizio servono solo valori piccoli e diversi tra loro, così che ogni riga parta da un punto diverso. Anche i simboli di base (le singole lettere) hanno la loro riga, perché restano nel vocabolario per rappresentare le parole mai viste.
La riga di E è identica ogni volta che il token compare: «banca» ha lo stesso embedding in «la banca del fiume» e in «il conto in banca». Sono gli strati successivi a mescolarlo con i token vicini, finché all'ultimo strato i due «banca» sono vettori diversi, uno vicino a «fiume», l'altro vicino a «conto». Nel modello in miniatura del capitolo 1 era il passo 3 a produrre questa differenza.
La posizione si aggiunge a parte. La riga di E non sa dove si trova il token: «il cane morde l'uomo» e «l'uomo morde il cane» avrebbero gli stessi cinque vettori. I primi transformer sommavano un secondo vettore, l'embedding posizionale; i modelli attuali usano quasi tutti RoPE (Rotary Position Embedding, embedding posizionale rotazionale), che codifica la posizione ruotando i vettori dentro l'attention, in modo che il prodotto scalare tra due token dipenda dalla loro distanza.
| Embedding di token | Embedding semantico (retrieval) | |
|---|---|---|
| Rappresenta | Un singolo token, senza contesto | Un'intera frase o un documento |
| Si ottiene | Leggendo una riga di E, all'ingresso del modello | Dall'ultimo strato di un modello, facendo la media (pooling) dei vettori contestuali di tutti i token |
| Si allena | Insieme all'LLM, prevedendo il token successivo | Con training contrastivo: frasi simili vicine, frasi diverse lontane |
| Dimensioni tipiche | V × d, es. 128.000 × 4.096 ≈ 524 milioni di pesi | Un vettore da 384–3.072 numeri per testo |
La parola «embedding» indica entrambe le cose e genera confusione: quello di cui parla questo documento è il primo, la riga di E. Il secondo, usato nei sistemi di retrieval, è trattato nella guida sulle architetture LLM.
Nel capitolo 1 il passo 3 «mescola il contesto» e il passo 4 «elabora». Come sono fatti davvero, in un transformer?
Attraverso una pila di strati (layer). Uno strato è un blocco di calcoli con i propri pesi: riceve un vettore per ogni token e restituisce un vettore per ogni token, della stessa dimensione. Entra una matrice [L × d], esce una matrice [L × d]. Siccome ingresso e uscita hanno la stessa forma, gli strati si possono impilare: l'uscita di uno è l'ingresso del successivo. «Deep» in deep learning significa proprio questo: molti strati in fila. Un modello da 7B ne ha 32, i modelli più grandi oltre 100.
Ogni blocco del transformer contiene due sotto-strati, con ruoli diversi e complementari.
1 · Attention: i token si scambiano informazioni. È l'unico punto del modello in cui un token «guarda» gli altri. Per ogni token il modello calcola tre vettori moltiplicando il suo vettore per tre matrici di pesi: una query («che cosa sto cercando»), una key («che cosa offro») e un value («che cosa passo se vengo scelto»). Il prodotto scalare tra la query di un token e la key di ciascun token precedente dice quanto quel token è rilevante; i punteggi vengono divisi per √d (per tenerli su una scala stabile) e una softmax li trasforma in pesi che sommano a 1; il nuovo vettore è la media pesata dei value.
La causal mask esclude i token successivi: ogni posizione vede solo il passato, perché in generazione il futuro non esiste ancora.
Nel modello in miniatura del capitolo 1 la «domanda» dell'ultimo token era la query, il vettore di ogni token faceva da key e da value insieme, e la matrice 8 × 8 era Wq: un transformer vero separa i tre ruoli con tre matrici distinte, così «che cosa cerco», «che cosa offro» e «che cosa passo» possono essere diversi.
2 · MLP: ogni token elabora ciò che ha raccolto. Ogni vettore, da solo, viene moltiplicato per una matrice che lo allarga (d → 4d numeri nel transformer originale; in Llama 2 7B 4.096 → 11.008), passa per una funzione non lineare e viene moltiplicato per una seconda matrice che lo riporta a d numeri. Nella variante SwiGLU di Llama le matrici sono tre: due allargano, e una delle due fa da «cancello» che regola quanto passa dell'altra. Gli stessi pesi valgono per tutti i token. Le sette regole «se X senza Y, aggiungi Z» del modello in miniatura sono un MLP con 7 neuroni nascosti: la prima matrice calcola i gate (prodotto scalare + ReLU), la seconda aggiunge i contributi.
Qui, si ritiene, avviene gran parte del «ricordare»: un vettore che contiene «capitale» e «Francia» viene trasformato in uno che contiene «Parigi». Gli studi di interpretabilità mostrano che i fatti sono distribuiti su molti pesi degli MLP dei blocchi centrali, non in una cella precisa.
Connessione residua e normalizzazione. Ogni sotto-strato non sostituisce il vettore, ma gli aggiunge una correzione: uscita = ingresso + contributo. Il vettore di ogni token attraversa così tutti i blocchi come un «flusso» (il residual stream) che si arricchisce strato dopo strato; è anche ciò che permette ai gradienti del capitolo 2 di risalire cento strati senza spegnersi. Prima di ogni sotto-strato una normalizzazione (RMSNorm, Root Mean Square Normalization) riporta i numeri su una scala stabile.
Il calcolo (prodotto scalare query · key, divisione per √dk, softmax, media pesata dei value) è quello vero di una testa di attention. I vettori query e key dei sette token sono scelti a mano, con 4 dimensioni invece di 128, per imitare una testa che ha imparato a collegare verbi e soggetti: in un modello reale li producono le matrici Wq, Wk, Wv moltiplicate per il vettore del token, e il training le aggiusta. Un blocco ha molte teste in parallelo (32 in un modello da 7B), ognuna con le sue matrici: una segue il soggetto, un'altra la parola precedente, un'altra la punteggiatura. Nessuno le programma: i ruoli emergono dal training.
Tutte le matrici in una volta, con le dimensioni scritte accanto. Nel laboratorio qui sopra query e key erano date; qui si parte un passo prima, dai vettori dei token, e si fa l'intero conto di una testa di attention su «il gatto dorme» con vettori di d = 4 numeri e query/key/value di 2 numeri. La frase è una matrice X di 3 righe × 4 colonne.
Il modello possiede tre matrici di pesi 4 × 2, Wq, Wk, Wv; moltiplicando X per ciascuna si ottengono Q, K, V, tre matrici 3 × 2 (una riga per token, come X). Il prodotto Q Kᵀ è una matrice 3 × 3: nella casella (i, j) c'è il prodotto scalare tra la query del token i e la key del token j, cioè quanto i trova interessante j. Si trasforma «3 token × 4 caratteristiche» in «3 token × 3 relazioni».
Dopo la divisione per √2 e la softmax riga per riga (con la maschera causale: un token non vede quelli dopo di sé) si ha la matrice A dei pesi; infine O = A V, ancora 3 × 2, dove la riga i è la combinazione pesata dei value: O₃ = a₃₁V₁ + a₃₂V₂ + a₃₃V₃. Niente, in questa catena, ha dimensioni «misteriose»: ogni prodotto è tra una matrice r × c e una c × s, e dà una r × s.
Calcolo · le dimensioni di una testa di attention
Clicca un token per seguire la sua riga attraverso tutte le matrici. «Pesi casuali» sostituisce Wq, Wk, Wv con numeri a caso, come all'inizio del training: il conto resta identico, l'attention diventa insensata; «Pesi del modello» li rimette. Questo è esattamente ciò che il training cambia: le tre matrici W, non le formule.
Che cosa è reale: tutti i prodotti, la divisione per √dk, la maschera, la softmax. Scelti a mano: i vettori di X e le tre matrici W, con numeri a una cifra per poterli seguire; in un 7B, d = 4.096, ogni testa ha query/key/value di 128 numeri, le teste sono 32 per strato e le loro uscite O vengono affiancate e moltiplicate per una quarta matrice Wo prima di essere aggiunte al residual stream. Il laboratorio precedente partiva da query e key già fatte: erano le righe di Q e K di questo.
La domanda «Wq, Wk, Wv vengono da tre training diversi?» nasce dal confondere le matrici dei pesi con le matrici che si calcolano da esse. I parametri esistono prima della frase, sono gli stessi per ogni frase, stanno su disco e cambiano solo durante il training. Le attivazioni nascono dalla frase, cambiano a ogni frase, vivono nella memoria della GPU durante il calcolo e si buttano alla fine (salvo K e V dei token passati, conservati nella KV cache durante la generazione). Il training ha un solo obiettivo, la loss sul token successivo, e la backpropagation consegna a ogni parametro la propria derivata nello stesso passaggio: Wq, Wk, Wv, E e i pesi dell'MLP si aggiornano insieme, a ogni step, perché tutti hanno contribuito alla stessa previsione.
| Parametri (pesi) | Attivazioni | |
|---|---|---|
| Esempi | E, Wq, Wk, Wv, Wo, le due matrici dell'MLP, i guadagni delle normalizzazioni | X, Q, K, V, Q Kᵀ, A, O, l'uscita dell'MLP, il residual stream, i logit, le probabilità |
| Quando nascono | All'inizializzazione, con numeri casuali, prima di qualunque frase | A ogni forward pass, moltiplicando la frase per i parametri |
| Dipendono dalla frase? | No: identici per ogni input | Sì: diversi per ogni frase e per ogni posizione |
| Chi li cambia | Solo il training: optimizer, un passo per step, tutti insieme dalla stessa loss | Nessuno: si ricalcolano e si scartano (K, V dei token passati restano nella KV cache durante la generazione) |
| Dove stanno | Su disco (il file del modello) e in memoria quando il modello è caricato | Solo in memoria, durante il calcolo; nel training se ne tengono molte per la backpropagation |
| Quanti in un 7B | ≈ 7 miliardi, fissi | Proporzionali a batch × lunghezza × d, per ogni strato |
Dopo 32 blocchi il vettore dell'ultimo token contiene tutto il contesto. Come diventa esattamente una probabilità per ogni token del vocabolario?
Con un ultimo prodotto scalare, ripetuto V volte. La testa di output è una matrice con una colonna per ogni token del vocabolario: il vettore finale viene moltiplicato per ciascuna colonna, e ogni prodotto scalare è un logit, il punteggio grezzo di quel token come candidato. Un vettore finale «vicino» alla colonna di «·Parigi» produce un logit alto per «·Parigi».
Alcuni modelli (GPT-2, Gemma) usano come testa di output la stessa matrice E dell'ingresso (weight tying, condivisione dei pesi): prevedere il token successivo diventa letteralmente «cercare la riga di E più simile al vettore prodotto». La maggior parte dei modelli grandi attuali, Llama compreso, tiene invece una matrice separata.
I logit possono essere qualsiasi numero, positivo o negativo. La funzione softmax li trasforma in probabilità che sommano a 1: eleva e a ciascun logit e divide per la somma; sottraendo prima il massimo si evita l'overflow, senza cambiare il risultato.
La temperatura T divide i logit prima della softmax: T < 1 esagera le differenze (il token migliore prende quasi tutto), T > 1 le appiattisce. Si usa solo in generazione: il training e la misura della loss lavorano sempre a T = 1. Il laboratorio qui sotto mostra entrambe le cose su sei candidati.
Formula · softmax con temperatura
La softmax e la loss sono calcolate davvero sui sei logit mostrati (il vocabolario vero ne ha decine di migliaia; qui sei candidati bastano). Il cursore «avanzamento» scala i logit per simulare che cosa fa il training: all'inizio sono quasi uguali, ogni token ha probabilità ≈ 1/6 e la loss vale log 6 ≈ 1,79; il training li separa. La temperatura divide i logit prima della softmax e si usa solo quando il modello genera: sotto 1 rende l'output più deterministico, sopra 1 più vario. La riga «loss» è il numero del capitolo 2, −log p(token corretto).
Ora che il percorso è chiaro, i numeri di un modello reale si possono leggere componente per componente. Llama 2 7B ha d = 4.096, 32 blocchi, vocabolario di 32.000 token, MLP interno di 11.008.
| Componente | Matrici | Pesi |
|---|---|---|
| Embedding E | 32.000 × 4.096 | 131 milioni |
| Attention (per blocco) | 4 matrici 4.096 × 4.096 (query, key, value, output) | 67 milioni |
| MLP (per blocco) | 3 matrici 4.096 × 11.008 | 135 milioni |
| Un blocco completo | attention + MLP | ≈ 202 milioni |
| 32 blocchi | 32 × 202 milioni | ≈ 6,48 miliardi |
| Testa di output | 4.096 × 32.000 (non condivisa con E) | 131 milioni |
| Totale | ≈ 6,74 miliardi → «7B» |
Oltre il 95% dei pesi sta negli strati, non nell'embedding; l'MLP ne contiene circa due terzi. Per generare un token il modello esegue circa 2 operazioni per peso, cioè ~14 miliardi di moltiplicazioni-somme: una GPU moderna le fa in pochi millisecondi, e il tempo di risposta è dominato dal leggere i 14 GB di pesi dalla memoria, non dal calcolo.
Il capitolo 2 ha mostrato la regola della catena su due pesi e una ReLU, un passo alla volta. Qui la stessa cosa su una rete con una non linearità morbida (tanh), con la possibilità di fare molti step di seguito e di guardare la loss scendere.
Un LLM ha miliardi di pesi distribuiti su decine di strati, e la loss dipende da ciascuno solo attraverso tutti gli strati che vengono dopo. La backpropagation (retropropagazione) è il modo di calcolare tutti quei gradienti in un colpo solo.
L'intuizione: alla fine della catena c'è un errore, e la «colpa» di quell'errore viene distribuita all'indietro, strato per strato. Ogni strato riceve dal successivo quanto ha pesato la sua uscita sull'errore, ne prende la sua parte e passa il resto allo strato precedente.
Derivata, in una riga. La derivata di f rispetto a w è la pendenza: se sposti w di un pochino ε, f cambia di circa (derivata × ε). Positiva vuol dire che aumentando w la loss sale, quindi il peso va abbassato.
Regola della catena. Se la loss dipende da w solo attraverso una grandezza intermedia h, le pendenze si moltiplicano:
Come un ingranaggio: se h gira 3 volte più veloce di w, e L 2 volte più veloce di h, allora L gira 6 volte più veloce di w. Con tanti strati in fila si moltiplicano tanti fattori, uno per strato.
Una rete minuscola a due strati. Un ingresso x, un primo peso w₁, una non linearità tanh, un secondo peso w₂, un'uscita ŷ confrontata con l'obiettivo y:
Calcolo · forward e backward su una rete a due strati
Entrambi i gradienti sono negativi: alzare w₁ e w₂ avvicina ŷ a y. Il passo di discesa w ← w − η · ∂L/∂w lo fa per tutti e due insieme. Nota che il backward riparte da ∂L/∂ŷ e lo riusa: il gradiente di w₁ non si ricalcola da zero, si ottiene da quello già pronto per lo strato sopra. È questo che rende la backpropagation economica: costa circa quanto due forward pass, qualunque sia il numero di pesi.
Perché il training consuma tanta memoria. Il backward usa i valori del forward: h per ∂L/∂w₂, (1 − h²) per passare attraverso tanh, x per ∂L/∂w₁. Per questo durante il training si tengono in memoria le attivazioni di tutti gli strati, per ogni token del batch, finché il backward non le ha consumate. In inferenza non serve: ogni strato può buttare via il suo ingresso appena ha prodotto l'uscita. Quando la memoria non basta si usa il gradient checkpointing: si salvano solo alcune attivazioni e le altre si ricalcolano durante il backward, scambiando calcolo con memoria.
Con decine di strati il gradiente di un peso in basso è un prodotto di decine di fattori: se sono quasi tutti sotto 1 (per esempio una tanh saturata, dove 1 − h² ≈ 0) il prodotto svanisce e i primi strati non imparano; se sono sopra 1 esplode. Le due soluzioni sono quelle viste nella sezione D: la connessione residua (uscita = ingresso + contributo) dà al gradiente una strada diretta, senza moltiplicazioni, fino agli strati più bassi, e la normalizzazione tiene le attivazioni su una scala in cui i fattori restano vicini a 1.
Non tutte le reti si allenano così. Nella guida sull'auto che impara a guidare i pesi si trovano senza calcolare nessun gradiente, per neuroevoluzione: funziona con 82 pesi, ma con miliardi di pesi la backpropagation è l'unica strada praticabile.
Muovi x, w₁, w₂ e y e guarda come cambiano i due gradienti; poi premi «Fai uno step» più volte e osserva la loss scendere.
La «verifica numerica» ricalcola ogni gradiente come (L(w + ε) − L(w − ε)) / 2ε con ε = 0,0001, spostando davvero il peso e rifacendo il forward: è il controllo che si usa per verificare un'implementazione della backpropagation. Una rete vera fa le stesse moltiplicazioni con matrici al posto dei singoli numeri.
In sintesi · capitolo 9
Ogni fase esegue lo stesso ciclo del capitolo 2: forward pass, un numero che misura l'errore, backpropagation, un passo sui pesi. Quello che la distingue è nelle due colonne centrali della tabella: da dove arrivano gli esempi e come si calcola l'errore. La colonna «densità» dice quanti numeri di feedback arrivano per sequenza, ed è la ragione per cui le fasi costano in modo così diverso.
| Fase | Problema che risolve | Dati | Errore (loss) | Chi genera | Densità del segnale | Limite che lascia |
|---|---|---|---|---|---|---|
| Pre-training | Pesi casuali: nessuna conoscenza | Testo grezzo, migliaia di miliardi di token | Cross-entropy sul token successivo | Nessuno (testo esistente) | %D10% ogni token | Continua, non risponde |
| SFT | Manca il comportamento da assistente | Prompt → risposta scritta | Cross-entropy sui soli token della risposta | Persone o modello forte | %D10% ogni token della risposta | Imita; non sa che cosa è «meglio» |
| DPO | Qualità relative: stile, utilità, sicurezza | Coppie chosen / rejected | −log σ(margine) sul reward implicito | Dataset fisso (offline) | %D1% 1 confronto per coppia | Offline, soggettivo; non insegna a cercare |
| RLVR + GRPO | Superare l'imitazione dove si può verificare | Problema + verificatore | Log-prob dei token × vantaggio nel gruppo | Il modello stesso (online) | %D1% 1 numero per sequenza | Solo domini verificabili; modello grande |
| OPD | Trasferire la capacità a un modello piccolo | Prompt + teacher | Reverse KL student ‖ teacher, per token | Lo student (online) | %D10% ogni token dello student | Non supera il teacher |
Nella pratica l'ordine non è rigido. Molti laboratori alternano più cicli di SFT e RL, applicano DPO dopo l'RL per sistemare lo stile, usano OPD al posto dell'RL per i modelli piccoli, e in alcune ricette recenti il post-training è una lunga fase di RL che ingloba le altre. Le fasi restano però cinque risposte alle stesse due domande: che esempi, e che misura dell'errore.