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