Dai pesi all'addestramento, dal rinforzo all'inferenza, fino al thinking. Cosa succede davvero dentro a un modello linguistico — spiegato anche a basso livello, senza un dottorato e senza vendere fuffa.
"È un'intelligenza artificiale, capisce quello che dico" — no. È una macchina che indovina la parola successiva. Il fatto che funzioni così bene è la parte sorprendente.
Un Large Language Model è una funzione matematica gigantesca che, dato un pezzo di testo, predice qual è il token successivo più probabile. Tutto il resto — chattare, scrivere codice, ragionare — emerge da questo.
Conosci il suggerimento di parole del telefono? Scrivi "ti voglio" e propone "bene". Un LLM è quello, ma cresciuto a dismisura: invece di guardare le ultime 2 parole ne guarda migliaia, e invece di imparare dai tuoi SMS ha letto una fetta enorme di internet, libri e codice. Non c'è un database di risposte dentro, non "cerca" niente: ha solo regolato miliardi di manopole interne finché non è diventato bravissimo a indovinare cosa viene dopo. La «comprensione» è un effetto collaterale di quanto è bravo a questo gioco.
Dai al modello "Il gatto sale sul..." e lui produce una probabilità per ogni parola possibile: tetto 41%, tavolo 22%, divano 12%, ... frigorifero 0.3%. Ne sceglie una, la attacca alla frase, e ripete. Questo è tutto il meccanismo di base. Sembra poco? Per indovinare bene il finale di milioni di frasi diverse, il modello è costretto a costruirsi al suo interno una rappresentazione di grammatica, fatti, stili, logica e perfino un po' di "buon senso". Imparare a predire bene = imparare a modellare il mondo descritto dal testo.
Una rete neurale non sa cosa sia una "parola": mastica solo numeri. Il primo passo è spezzare il testo in token e assegnare a ognuno un ID.
Un token è un pezzo di testo: a volte una parola intera (casa), spesso un pezzo di parola (intelligen + za), a volte un singolo carattere o uno spazio. In media 1 token ≈ 4 caratteri ≈ 0,75 parole in inglese (un po' meno efficiente in italiano). Il vocabolario di un modello moderno ha tipicamente da 50.000 a 200.000+ token distinti. La tecnica più usata per costruirlo si chiama BPE (Byte-Pair Encoding): parte dai singoli byte e fonde via via le coppie più frequenti.
# "Lo svogliato programma di notte" → token → ID numerici
["Lo", " svog", "liato", " programma", " di", " notte"]
↓ ↓ ↓ ↓ ↓ ↓
[ 3304, 78521, 9982, 27114, 357, 49183 ]
# Nota: "svogliato" si spezza in pezzi (parola rara),
# "programma" resta intero (parola comune). Lo spazio fa parte del token.
pip install tiktoken
import tiktoken
enc = tiktoken.get_encoding("cl100k_base") # un tokenizzatore reale
ids = enc.encode("Lo svogliato programma di notte")
print(ids) # → lista di interi
print(len(ids)) # quanti token = quanto "costa"
print([enc.decode([i]) for i in ids]) # i pezzi a ritroso
Un ID numerico (49183) di per sé non dice nulla. Il modello trasforma ogni token in un vettore di centinaia/migliaia di numeri: l'embedding. La posizione nello spazio è il significato.
Immagina una mappa dove ogni parola è una città. Parole con significato simile stanno vicine: "cane", "gatto", "criceto" formano un quartiere; "felicità", "gioia", "allegria" un altro. Ma qui la mappa non ha 2 dimensioni (latitudine/longitudine): ne ha migliaia. In tutte quelle direzioni si codificano sfumature: genere, tempo verbale, formalità, sentimento, dominio... La cosa folle è che funziona l'aritmetica: il vettore di "re" − "uomo" + "donna" cade vicinissimo a "regina". Il significato è diventato direzione e distanza.
L'embedding è una grande tabella di consultazione (una matrice): vocabolario × dimensioni. Per il token 49183 il modello pesca la riga 49183 e ottiene il suo vettore. Questi numeri non sono scritti a mano: vengono imparati durante l'addestramento, esattamente come tutti gli altri pesi.
import numpy as np
# vettori-giocattolo a 4 dimensioni (i veri ne hanno migliaia)
gatto = np.array([ 0.9, 0.8, -0.1, 0.2])
cane = np.array([ 0.8, 0.7, 0.0, 0.3])
banana = np.array([-0.6, 0.1, 0.9, -0.4])
def simil(a, b): # 1 = identici, 0 = scorrelati
return a @ b / (np.linalg.norm(a) * np.linalg.norm(b))
print(simil(gatto, cane)) # ≈ 0.99 (stesso quartiere)
print(simil(gatto, banana)) # ≈ 0.10 (lontani)
"Il cane morde l'uomo" e "L'uomo morde il cane" hanno gli stessi token ma significati opposti. Gli embedding da soli non sanno l'ordine. Per questo si aggiunge un positional encoding: un'informazione sulla posizione di ogni token nella sequenza (oggi spesso col metodo RoPE, rotary). Così il modello sa non solo quali token ci sono, ma in che ordine.
Quando senti "modello da 70B" si parla di 70 miliardi di pesi. Ma cosa sono questi pesi? Sono i numeri che contengono tutto ciò che il modello ha imparato. Qui scendiamo a basso livello.
L'unità di base è banale: prende dei numeri in ingresso, li moltiplica ciascuno per un peso, somma tutto, aggiunge un bias e passa il risultato attraverso una funzione non lineare (attivazione). Fine. Un LLM è questa operazione ripetuta miliardi di volte, organizzata in strati.
import numpy as np
ingressi = np.array([0.5, -0.2, 0.8]) # dati in input
pesi = np.array([0.9, 0.4, -0.7]) # IMPARATI nel training
bias = 0.1 # anch'esso imparato
z = ingressi @ pesi + bias # somma pesata
uscita = np.maximum(0, z) # attivazione ReLU = max(0, z)
print(uscita)
# I "pesi" sono SOLO questi numeri. Addestrare = trovarli giusti.
# Un layer fa la stessa cosa per migliaia di neuroni in parallelo
# → diventa una moltiplicazione tra matrici (per questo servono le GPU).
Quanto conta un ingresso. Imparato. È il "sapere" del modello.
Una costante che sposta il risultato. Imparato anche lui.
Termine ombrello: ogni peso + ogni bias. "70B" = 70 mld di questi.
I neuroni sono organizzati in layer (decine, a volte centinaia). L'uscita di uno entra nel successivo. Più layer = più capacità di catturare pattern astratti. La "magia" degli LLM è in gran parte una questione di scala: tanti parametri + tanti dati + tanto calcolo → capacità che nei modelli piccoli non c'erano (le cosiddette abilità emergenti).
| Modello (storico) | Parametri | Ordine di grandezza |
|---|---|---|
| GPT-2 (2019) | 1,5 miliardi | Gira su un buon PC |
| GPT-3 (2020) | 175 miliardi | Serve un cluster |
| Modelli "open" tipici | 7B – 70B | Da portatile a workstation |
| Frontier (oggi) | centinaia di mld+ (spesso MoE) | Datacenter |
L'architettura che ha fatto esplodere tutto, dal paper "Attention Is All You Need" (2017). Il meccanismo chiave si chiama self-attention: ogni token decide a quali altri token "dare retta".
Stai leggendo la frase "Il robot ha preso la palla perché era rotolata via". Per capire "era", devi sapere che si riferisce alla palla, non al robot. L'attention è esattamente questo: per ogni parola, il modello fa una "riunione" con tutte le altre e decide quanto ascoltare ciascuna. Ogni parola lancia una domanda (Query: "a chi mi riferisco?"), ogni altra parola espone un'etichetta (Key: "io sono un oggetto femminile rotolabile") e un contenuto (Value). Le domande che combaciano con le etichette giuste ricevono più peso. Così "era" sa di guardare "palla".
Da ogni embedding il modello ricava tre vettori — Query, Key, Value — moltiplicandolo per tre matrici di pesi (anch'esse imparate). L'attenzione di un token verso un altro è il prodotto della sua Query con la Key dell'altro: più sono allineati, più alto il punteggio. I punteggi si normalizzano con un softmax (diventano percentuali che sommano a 1) e si usano per fare una media pesata dei Value.
import numpy as np
def softmax(x):
e = np.exp(x - x.max(axis=-1, keepdims=True))
return e / e.sum(axis=-1, keepdims=True)
def attention(Q, K, V):
d = Q.shape[-1]
punteggi = Q @ K.T / np.sqrt(d) # quanto ogni token guarda gli altri
pesi = softmax(punteggi) # → percentuali che sommano a 1
return pesi @ V # media pesata dei contenuti
# Attention(Q,K,V) = softmax(Q·Kᵀ / √d) · V — tutto qui.
Non si fa una sola "riunione" ma molte in parallelo (le head): una testa può seguire i riferimenti grammaticali, un'altra il tema, un'altra la punteggiatura. I risultati si combinano. Più teste = più tipi di relazione catturati contemporaneamente.
I modelli precedenti (RNN/LSTM) leggevano parola per parola, in sequenza: lenti e smemorati sui testi lunghi. Il transformer guarda tutta la sequenza in parallelo: si addestra molto più velocemente sulle GPU e gestisce contesti lunghi senza "dimenticare" l'inizio.
Un LLM è una pila di blocchi identici, ognuno con due parti: l'attention (i token si scambiano informazioni) e una rete feed-forward (ogni token elabora ciò che ha raccolto). Con in più due trucchi che rendono possibile impilarne tanti senza che l'addestramento esploda: le connessioni residue (scorciatoie che saltano il blocco) e la normalizzazione.
All'inizio i pesi sono numeri casuali: il modello sputa fuori spazzatura. L'addestramento è il processo che, miliardi di volte, corregge un pochino ogni peso per sbagliare un po' meno. Ecco la meccanica esatta.
Si prende una valanga di testo (web, libri, codice, articoli: migliaia di miliardi di token) e si gioca a un gioco automatico: si mostra al modello una frase troncata e gli si chiede il token successivo. La risposta giusta è già nel testo (la parola che veniva davvero dopo), quindi non serve nessun umano a etichettare: è auto-supervisionato. È il motivo per cui si può fare su scala planetaria.
Tre parole che spaventano, tre idee semplici:
for batch in dati: # miliardi di pezzi di testo
pred = modello(batch.input) # 1. predici il prossimo token
loss = cross_entropy(pred, batch.target) # 2. quanto hai sbagliato?
gradienti = loss.backward() # 3. backprop: colpa di quali pesi?
pesi = pesi - lr * gradienti # 4. correggi (lr = learning rate)
# ...ripeti per settimane su migliaia di GPU...
# Aggiornamento di un peso: w := w - (passo) × (pendenza della loss su w)
Moltiplica: miliardi di parametri × migliaia di miliardi di token × molti passaggi. Sono 10²³ – 10²⁵ operazioni, settimane o mesi su migliaia di GPU/acceleratori, per milioni di dollari di elettricità e hardware. Per questo i modelli base li allenano in pochi (grandi laboratori), e tu li scarichi già addestrati. Il risultato di questa fase è un base model: sa completare testo in modo impressionante, ma non sa ancora chattare né seguire istruzioni. Se gli scrivi "Qual è la capitale della Francia?" potrebbe continuare con "Qual è la capitale della Spagna?" — perché sta completando un elenco di domande, non rispondendo.
Il base model è un genio autistico che continua testo. Per renderlo un assistente che risponde alle domande serve un secondo addestramento, più piccolo e mirato: il fine-tuning supervisionato (SFT).
Si prende il base model e lo si continua ad addestrare — stesso meccanismo (predici il prossimo token, loss, backprop) — ma su un dataset molto più piccolo e curato di esempi conversazionali: coppie istruzione → risposta ideale scritte o selezionate da umani. Decine/centinaia di migliaia di esempi del tipo "domanda dell'utente / risposta utile, ben formattata, educata".
{
"utente": "Spiega cos'è un firewall a un bambino di 10 anni.",
"assistente": "Immagina un buttafuori all'ingresso di una festa: \
controlla chi può entrare e chi no. Un firewall fa \
lo stesso con i dati che arrivano dal tuo computer..."
}
# Migliaia di esempi come questo insegnano il FORMATO "assistente":
# rispondere, non continuare; essere chiari; seguire l'istruzione.
Dopo l'SFT, alla domanda sulla capitale della Francia il modello risponde "Parigi" invece di continuare l'elenco. Non ha imparato fatti nuovi (quelli erano già nei pesi dal pretraining): ha imparato come comportarsi, in che forma usare ciò che già sa.
L'SFT insegna a rispondere. Ma quale risposta è "migliore" tra due entrambe corrette? Qui entra il rinforzo dalle preferenze umane (RLHF): la fase che rende i modelli utili, onesti e meno tossici.
Un cuoco (il modello) sa già cucinare (post-SFT). Ora vuoi che cucini come piace ai clienti. Non puoi assaggiare ogni piatto possibile, quindi: (1) fai cucinare due versioni dello stesso piatto e chiedi a degli assaggiatori umani quale preferiscono; (2) con migliaia di questi giudizi addestri un critico automatico (reward model) che impara a dare un voto come farebbero gli umani; (3) lasci il cuoco cucinare migliaia di piatti e lo premi/correggi in base al voto del critico, spingendolo verso ciò che piace. Questo "premio che guida l'apprendimento" è il rinforzo.
Il passo 3 usa algoritmi di reinforcement learning come PPO, oppure metodi più recenti e diretti come DPO che saltano il reward model esplicito e ottimizzano direttamente sulle preferenze. L'obiettivo dichiarato è rendere il modello utile, onesto e innocuo (in inglese: helpful, honest, harmless).
Per matematica, codice e logica c'è una scorciatoia potente: la risposta è verificabile in automatico (il test passa o no, il risultato è giusto o no). Si può quindi dare il "premio" senza umani, semplicemente controllando se la soluzione è corretta. È uno degli ingredienti chiave dei moderni modelli di reasoning (prossimo capitolo): il modello prova tante strade, e viene rinforzato su quelle che arrivano alla risposta giusta.
Addestramento finito, pesi congelati. Ora il modello lavora: prende il tuo testo e genera la risposta un token alla volta. Questa fase si chiama inferenza ed è quella che "paghi" ogni volta.
testo = tokenizza(prompt)
while not fine:
logit = modello(testo)[-1] # punteggi per il PROSSIMO token
probs = softmax(logit / temperatura) # → distribuzione
prossimo = campiona(probs, top_p=0.9) # scegline uno
testo.append(prossimo) # attaccalo e ripeti
if prossimo == FINE_TESTO: break
# Ogni token costa un intero passaggio nella rete. Per questo
# le risposte lunghe sono più lente e costose.
Sono le manopole che decidono quanto rischiare nella scelta del token:
Rigenerare l'attention per tutti i token a ogni passo sarebbe spreco: si tengono in memoria le Key e Value già calcolate (KV cache). Velocizza tantissimo, ma occupa VRAM che cresce con la lunghezza. La context window è il massimo di token (prompt + risposta) che il modello può tenere "in testa" insieme: oltre, deve dimenticare o troncare.
I modelli che "ragionano" e ti mostrano un blocco di thinking non hanno un cervello in più. Stanno facendo una cosa semplice e potente: generare passaggi intermedi prima della risposta. Ecco perché funziona.
Chiedi a un LLM "un bar ha 23 mele, ne usa 20 e ne compra 6, quante ne ha?". Se risponde di getto, spesso sbaglia. Se invece genera i passaggi — "23 − 20 = 3; 3 + 6 = 9" — ci azzecca molto più spesso. Perché? Ogni token generato è un'occasione di calcolo in più: scrivendo i passaggi, il modello si costruisce da solo gli appoggi intermedi invece di dover "indovinare" il risultato finale in un colpo. È la catena del ragionamento (chain-of-thought).
I modelli di nuova generazione vengono addestrati apposta a fare questo monologo interno, usando il rinforzo con ricompensa verificabile (RLVR, cap. 8): provano lunghe catene di ragionamento su problemi di matematica e codice, e vengono premiati quando arrivano alla soluzione giusta. Imparano così a pianificare, verificare e correggersi da soli prima di consegnare la risposta.
<thinking>
L'utente chiede X. Provo l'approccio A...
Aspetta, questo caso limite non torna. Riprovo con B.
Verifico: 2+2=4, coerente. Ok, la risposta è B.
</thinking>
La risposta è B, perché ... ← questo è ciò che l'utente legge
# Il blocco "thinking" sono token generati ESATTAMENTE come gli altri.
# Non è un processo separato: è lo stesso "predici il prossimo token",
# ma allenato a usarlo come brutta copia prima della bella.
Capire il meccanismo serve soprattutto a sapere dove e perché si rompe. E a capire perché un modello "da 70B" non gira sul tuo portatile a caso.
Il modello predice ciò che è plausibile, non ciò che è vero: non ha un concetto interno di "verità" né una fonte da consultare. Se una risposta falsa «suona» statisticamente giusta, la produce con la stessa sicurezza di una vera. Le allucinazioni non sono un bug da togliere con una patch: sono una conseguenza diretta di come funziona. Si mitigano (RAG, citazioni, verifica), non si azzerano.
Dopo l'addestramento i pesi sono fissi. Il modello non ricorda le tue chat passate e non impara dalle tue correzioni: tutto ciò che "ricorda" in una conversazione sta solo nella context window e sparisce dopo. Per dargli info nuove: mettile nel prompt (o via RAG), oppure ri-addestra (fine-tuning). Da qui anche il knowledge cutoff: non sa nulla accaduto dopo la fine del suo addestramento.
Un modello è letteralmente un file di numeri (i pesi). Per farlo girare devi caricarlo in memoria — idealmente nella VRAM della GPU. Regola spannometrica: parametri × byte-per-peso.
| Precisione | Byte/peso | Un modello 7B occupa ~ | Note |
|---|---|---|---|
| FP16 / BF16 | 2 | ~14 GB | Qualità piena, "nativa" |
| INT8 (Q8) | 1 | ~7 GB | Quasi indistinguibile |
| INT4 (Q4) | ~0,5 | ~4 GB | Gira su GPU consumer, lieve calo |
La quantizzazione è il trucco che comprime i pesi (da 16 bit a 8 o 4) per farli stare in meno memoria, perdendo pochissima qualità. È il motivo per cui oggi puoi far girare modelli seri in locale. Aggiungi un po' di overhead per la KV cache e il contesto.
Tutta la terminologia in un posto solo, più il ciclo di vita completo dall'inizio alla risposta. Bookmark e via.
| Termine | In parole povere |
|---|---|
| Token | Il pezzetto di testo (≈ ¾ di parola) che il modello mastica. Unità di misura e di prezzo. |
| Parametro / Peso | Uno dei miliardi di numeri regolabili. Insieme contengono tutto il "sapere". |
| Embedding | Il vettore che rappresenta un token. Vicinanza = significato simile. |
| Transformer | L'architettura degli LLM, basata sull'attenzione. |
| Attention | Il meccanismo per cui ogni token decide a quali altri dare peso. |
| Loss | Quanto il modello sbaglia la predizione. Addestrare = ridurla. |
| Backpropagation | Calcola di chi è la "colpa" dell'errore, peso per peso. |
| Gradient descent | Aggiusta i pesi a piccoli passi per ridurre la loss. |
| Pretraining | La grande fase iniziale: imparare a predire testo dal nulla. |
| SFT / Fine-tuning | Secondo addestramento mirato per insegnare un comportamento. |
| RLHF | Rinforzo dalle preferenze umane: allinea utilità e sicurezza. |
| DPO / PPO | Algoritmi che realizzano il "rinforzo" del passo precedente. |
| Logit | Il punteggio grezzo per ogni token candidato, prima del softmax. |
| Softmax | Trasforma i logit in probabilità che sommano a 1. |
| Temperature | Manopola della casualità: 0 = prevedibile, alta = creativo. |
| Context window | Quanti token (prompt + risposta) il modello tiene insieme. |
| KV cache | Memoria delle attention già calcolate, per non rifare i conti. |
| Chain-of-thought | Generare i passaggi intermedi: migliora il ragionamento. |
| Inferenza | Il modello "in funzione" che genera la risposta. |
| Quantizzazione | Comprimere i pesi (16→4 bit) per usare meno memoria. |
| MoE | Mixture of Experts: attiva solo una parte dei parametri per token. |
| Allucinazione | Risposta plausibile ma falsa. Conseguenza del meccanismo, non bug. |
| RAG | Recuperare documenti e metterli nel contesto per dare fatti aggiornati. |