Come funziona una LLM
Un motore di probabilità sui token: cosa calcola, come genera, cosa sa davvero — e perché funziona.
1. Motore di probabilità
Un Large Language Model non "capisce" il testo nel senso ordinario del termine. Esegue un calcolo: data una sequenza di token in ingresso, restituisce una distribuzione di probabilità sull'intero vocabolario.
P( token_{t+1} | token_1, token_2, ..., token_t )
La domanda a cui risponde il modello è: "Dati questi token in quest'ordine, qual è la probabilità di ciascun token del vocabolario come prossima uscita?"
Esempio concreto. Dato il contesto "il cane abbaia nel", il modello potrebbe restituire:
P("cortile") = 0.18
P("parco") = 0.12
P("bosco") = 0.09
P("silenzio") = 0.06
... ← altri 15.995 token con probabilità residua
Non esiste una risposta giusta. Esiste una distribuzione. Il testo generato nasce dal campionamento ripetuto da questa distribuzione — un token alla volta, ogni token aggiunto al contesto per il passo successivo.
2. Il token — unità atomica
Il modello non lavora con parole intere né con singoli caratteri. Lavora con token: sotto-unità di testo determinate statisticamente dal corpus di addestramento tramite BPE (Byte-Pair Encoding).
BPE parte dai caratteri e unisce iterativamente le coppie più frequenti finché non raggiunge la dimensione del vocabolario target. Il risultato: parole frequenti diventano un token singolo, parole rare si spezzano in pezzi.
"programmare" → [4721] # token unico — parola comune "trasparenza" → [8843, 12041] # due token "antidisestablishmentarianism" → [5-6 token] # parola rarissima
Nel nostro SLM con vocabolario da 16.000 token addestrato su italiano:
- Parole frequenti ("il", "di", "che", "questo") → 1 token ciascuna
- Parole composte o rare → 2–4 token
- Wikipedia italiana (~200 MB) → ~50 milioni di token
Il tokenizer è un contratto tra dati e modello. Il modello addestrato con SPM da 16k non può usare un tokenizer diverso — ogni checkpoint è inseparabile dal suo vocabolario. Vedi Vocabolario, Embedding e Parametri per i dettagli.
3. La finestra di contesto
Il modello non ha memoria illimitata. Vede solo gli ultimi seq_len token — la finestra di contesto. Tutto il testo precedente è invisibile.
seq_len = 512 → ~350 parole → ~2 pagine di testo
Quando si supera seq_len, i token più vecchi vengono scartati. Non è una limitazione accidentale — l'attention ha complessità O(seq_len²) in memoria. Raddoppiare seq_len quadruplica la RAM richiesta.
Nel progetto SLM abbiamo ridotto da 512 a 256 per restare nei limiti degli 8 GB di memoria unificata disponibili. Su macchine da 24–32 GB si può tornare a 512 o oltre.
4. La forward pass
Ogni volta che il modello elabora un batch di token, attraversa sempre lo stesso percorso deterministico e differenziabile. Durante il training questo percorso viene eseguito miliardi di volte.
Il passaggio critico è lm_head: uno strato Linear che proietta ogni posizione da d_model (es. 512) a vocab_size (16.000). Produce un numero per ogni token del vocabolario — i logit. Softmax normalizza i logit in probabilità.
Durante il training, la cross-entropy loss misura quanto la distribuzione predetta si discosta dal token corretto. Il gradiente di questa loss rispetto a ogni parametro guida l'aggiornamento AdamW. Vedi AdamW e Ottimizzazione.
5. Dal logit alla parola
Il modello produce 16.000 logit. Come si sceglie quale token emettere? Dipende dalla strategia di sampling.
Greedy decoding
Prende sempre il token con probabilità massima: argmax(logits). Deterministico e veloce, ma produce output prevedibili e ripetitivi. È quello che usa attualmente --generate in train.py.
Temperature scaling
Prima del softmax, divide tutti i logit per un valore T:
logits_scalati = logits / T probabilità = softmax(logits_scalati)
Top-k sampling
Considera solo i k token con probabilità più alta, azzera tutti gli altri, poi campiona. Con k=50, ogni step sceglie tra i 50 candidati più plausibili.
Top-p (nucleus) sampling
Considera il minimo insieme di token la cui probabilità cumulata raggiunge p. Se i primi 3 token coprono già il 90% della probabilità, con p=0.9 si campiona solo da quei 3. L'insieme si adatta dinamicamente alla forma della distribuzione.
Temperatura, top-k e top-p si usano tipicamente insieme. Valori comuni per testo narrativo: T=0.8, top_k=50, top_p=0.95. Sono i parametri della fase 5 nel piano di sviluppo — vedi la roadmap.
Logit bias — escludere token specifici
A volte non basta regolare la temperatura o il nucleus: si vuole impedire al modello di generare certi token del tutto. Il logit bias permette di farlo: prima che il softmax trasformi i logit in probabilità, si somma un valore scelto al logit di uno o più token specifici. Sommando −100 al logit di un token lo si esclude praticamente dal campionamento: la probabilità risultante è così vicina a zero da non poter mai essere selezionata.
Nella pratica, ogni token nel vocabolario ha un indice numerico. Passare [[id, -100]] a una richiesta di inferenza significa "non generare mai questo token, qualunque cosa suggerisca il modello". Si possono escludere decine o centinaia di token in un colpo solo.
Perché è stato necessario con il nostro modello. La conversione in formato GGUF richiede l'aggiunta di 256 token speciali — uno per ogni byte possibile (0x00–0xFF) — agli indici 16.000–16.255. Servono per garantire che il tokenizer sappia sempre come codificare qualsiasi testo, anche quello con caratteri insoliti. Il problema: il corpus di training conteneva artefatti di encoding (tab, caratteri di controllo, frammenti binari da HTML mal decodificato). Il modello ha imparato che questi byte compaiono dopo certe sequenze. Quando incontra un contesto insolito — un nome proprio raro, un prompt che non ha mai visto — non sa bene cosa generare, la distribuzione di probabilità si appiattisce, e i byte token, anche con probabilità bassa, diventano competitivi. Il campionatore li pesca. Una volta partito su questa strada, il contesto diventa "sequenza di byte", il che rinforza la scelta di altri byte: loop. L'output è una serie di caratteri illeggibili.
La soluzione: escludere tutti e 256 i byte token con logit bias = −100. Il vocabolario operativo torna a 16.000 token, il modello non può più imboccare quella via d'uscita.
Il logit bias è uno strumento chirurgico: non cambia il modello, non richiede un nuovo training, agisce solo in fase di generazione. È utile ogni volta che si conosce in anticipo una categoria di token che non dovrebbe mai comparire nell'output.
6. Cosa sa davvero il modello
Il modello non ha un dizionario di regole grammaticali. Ha distribuzioni condizionali compresse in milioni di parametri float32.
"Sa" che dopo "il" viene quasi sempre un sostantivo o aggettivo maschile — non perché glielo abbia detto qualcuno, ma perché ha osservato questa co-occorrenza miliardi di volte nel corpus. "Sa" che "non" precede spesso un verbo — stessa ragione. La conoscenza è implicita e distribuita: nessun singolo parametro codifica una regola; le regole emergono dall'interazione dell'intero sistema durante la forward pass.
Il nostro SLM da 46M parametri, addestrato su Wikipedia italiana, conosce la distribuzione superficiale della lingua. Non ragiona, non pianifica, non verifica. Produce token statisticamente plausibili dato il contesto. La comprensione emerge solo a scale molto più grandi (1B+ parametri, dati enormi e variegati).
7. Genesi del progetto: obiettivi e vincoli
Prima di scegliere un'architettura vale la pena capire cosa stiamo costruendo, perché, e con quali vincoli concreti. Le scelte tecniche — scala dei parametri, tokenizer, seq_len — non sono arbitrarie: sono la risposta a questi vincoli.
L'obiettivo: un modello base riaddestrabilee
Il SLM da 46 milioni di parametri non è pensato per la produzione conversazionale diretta — quella scala richiede almeno 1–3B parametri per qualità accettabile. Il suo scopo è duplice:
- Formativo. Costruire ogni componente dall'interno — attention, FFN, tokenizer, optimizer — per capirne il funzionamento reale, non solo l'interfaccia di libreria.
- Writing assistant. Dopo il pre-training su Wikipedia italiana, un fine-tuning sul corpus narrativo personale (~550 KB) crea un modello che conosce la distribuzione stilistica dell'autore e può supportare la scrittura in italiano.
I due track paralleli
Il progetto segue due binari simultanei e complementari:
- Track 1 — SLM from scratch (questo modello). Comprensione profonda di ogni meccanismo. Scala piccola, controllo totale. Output: competenze trasferibili + writing assistant fine-tunato sulla propria voce.
- Track 2 — LoRA su modelli esistenti. Fine-tuning di LLaMA 3.1 8B o Mistral 7B con adapter LoRA per i prodotti operativi. ~25 MB di adapter per dominio, modello base condiviso in RAM. Le competenze acquisite nel Track 1 si applicano direttamente qui.
Vincoli hardware: sviluppo su Mac
Training, inferenza e iterazione girano su un singolo Mac. Non è negoziabile: non ci si può appoggiare a un server cloud per ogni esperimento intermedio. Apple Silicon usa memoria unificata — CPU e GPU (MPS) condividono lo stesso pool fisico, eliminando i trasferimenti PCIe tipici di NVIDIA ma rendendo ogni GB occupato dal sistema un GB sottratto al training.
| Macchina | RAM unificata | Limite pratico SLM | Stato |
|---|---|---|---|
| Mac Air M2 | 8 GB | seq_len=256, batch=8, modello ~46M fp32 |
Attuale |
| Mac Air M5 | 32 GB | seq_len=512+, batch=16+, modelli 1B+ |
Target |
Il vincolo si manifesta soprattutto nell'attention: la matrice di attenzione scala come batch × seq_len². Con batch=16 e seq_len=512 su 8 GB si va in OOM (out of memory). Ridurre seq_len da 512 a 256 dimezza le sequenze ma quadruplica la capienza effettiva in RAM — è il compromesso che usiamo attualmente.
Strategia: pre-training → fine-tuning → LoRA
Il percorso pianificato:
- Pre-training su Wikipedia italiana (~200 MB, ~50M token) — il modello apprende la distribuzione statistica dell'italiano.
- Fine-tuning letterario sul corpus narrativo personale — il modello adatta la propria distribuzione alla voce e allo stile dell'autore.
- Adapter LoRA per specializzazioni ulteriori — senza ri-addestrare l'intero modello. I pesi base rimangono congelati; si aggiungono coppie di matrici sottili (~25 MB) che modificano il comportamento su un dominio specifico.
LoRA è nella fase 10 della roadmap. Il pre-training attuale (fase 1–4) è il prerequisito: serve un modello base ben addestrato prima di applicarci qualunque tecnica di adattamento.
8. Decoder-only — perché solo generazione
Esistono tre architetture principali di transformer, con usi diversi:
| Architettura | Esempi | Attenzione | Uso tipico |
|---|---|---|---|
| Encoder-only | BERT, RoBERTa | Bidirezionale — vede tutto il testo in entrambe le direzioni | Classificazione, NER, similarity |
| Decoder-only | GPT, LLaMA, SLM | Causale — ogni token vede solo i token alla sua sinistra | Generazione testuale |
| Encoder-Decoder | T5, BART, mT5 | Encoder bidirezionale + decoder causale | Traduzione, summarization |
Il nostro SLM è decoder-only. La causal mask garantisce che il token in posizione t veda solo i token nelle posizioni 0..t — mai quelli futuri. Questo rende possibile il training in parallelo (la loss si calcola su tutti i token del batch in una sola forward pass) e la generazione autoregressiva (si genera un token alla volta, ognuno aggiunto al contesto per il passo successivo).
Per il writing assistant descritto nella sezione precedente — generazione di continuazioni nello stile dell'autore — decoder-only è la scelta corretta. Un encoder-only (BERT) non genera; un encoder-decoder (T5) genera ma è pensato per trasformazioni input→output strutturate, non per completamento libero.
🗓