Questioni architetturali, strategiche e tecniche emerse durante lo sviluppo del progetto SLM.
Un modello da 46 milioni di parametri ma con più layer (ad esempio 24 invece di 12) sarebbe in grado di fare più cose? E a quale costo computazionale?
Con d_model=512 e 12 layer, ogni layer occupa circa 3,15 milioni di parametri: 1,05M per i meccanismi di attenzione (quattro proiezioni Q, K, V, O di dimensione 512×512) e 2,10M per la rete feed-forward (due proiezioni da 512 a 2048 e viceversa). L'embedding occupa i restanti 8,19M.
Se si vuole mantenere lo stesso budget di parametri aumentando la profondità, è necessario ridurre d_model. Tre configurazioni a confronto:
| Configurazione | Layer | d_model | d_k per testa | Parametri totali |
|---|---|---|---|---|
| Attuale | 12 | 512 | 64 | ~46M |
| Più profonda | 24 | 384 | 48 | ~48M |
| Intermedia | 18 | 448 | 56 | ~47M |
Il numero di layer determina quante volte il modello può rielaborare la rappresentazione di un token prima di produrre output. Più layer migliorano le dipendenze a lungo raggio, la coerenza sintattica su più frasi e la capacità di composizione gerarchica. Il numero di parametri, invece, determina la ricchezza di ogni singola rappresentazione: meno d_model significa vettori-token più poveri, con meno dimensioni disponibili per ogni testa di attenzione.
Le leggi di scaling empiriche (Kaplan et al. 2020, Chinchilla 2022) indicano che per un budget fisso di parametri esiste un rapporto ottimale tra profondità e larghezza, e quel rapporto è già vicino a quello della configurazione attuale (12 layer, d_model 512). Portare a 24 layer con d_model 384 significa ridurre le teste di attenzione a 48 dimensioni ciascuna, contro le 64 attuali: un calo che annulla il vantaggio della profondità.
A parità di parametri totali, il costo di training e di inferenza è sostanzialmente identico: il numero di operazioni per forward pass è proporzionale ai parametri totali, non alla profondità isolata. La profondità è sequenziale (ogni layer dipende dall'output del precedente) ma il costo dominante rimane la moltiplicazione matriciale complessiva, che non cambia.
Risposta sintetica: no, più layer con lo stesso budget non sblocca nuove categorie di capacità. Offre margini di qualità minimi (forse −0,1/−0,2 di val loss) a fronte di rappresentazioni per-token più povere. La configurazione attuale è già vicina all'ottimo per questo budget.
Sarà possibile in futuro aumentare il numero di parametri del modello senza doverlo ricostruire da zero?
Caricare un checkpoint esistente (ad esempio best.pt con d_model=512 e 12 layer) in un'architettura più grande (ad esempio d_model=1024 e 24 layer) produce un errore immediato. Le matrici peso hanno dimensioni diverse: W_Q è 512×512 nel modello piccolo, 1024×1024 in quello grande. I dizionari dei parametri non sono compatibili e il caricamento fallisce.
La più rilevante si chiama Net2Net (Chen et al., 2015) ed è una trasformazione function-preserving: permette di espandere un modello senza perdere ciò che ha già imparato.
Per espandere la larghezza (d_model da 512 a 1024): ogni neurone viene duplicato e i pesi in uscita corrispondenti vengono dimezzati. La somma rimane invariata, quindi il modello espanso produce output matematicamente identici all'originale al momento dell'inizializzazione. Da quel punto si riprende il training: si parte già dalla qualità del modello piccolo, non da zero.
Per aggiungere layer: i nuovi layer vengono inseriti come funzioni identità, con i pesi della parte trainable inizializzati a zero. L'output del nuovo layer è uguale al suo input, e il training impara gradualmente a usarli.
| Tecnica | Funziona? | Costo reale |
|---|---|---|
| Net2Net width expansion | Sì, formalmente corretto | Implementazione non banale, poi servono molti token di fine-tuning |
| Identity layer insertion | Sì, semplice | Convergenza lenta: i nuovi layer partono senza conoscenza |
| LoRA su modello esistente | Sì, ma non è espansione | Aggiunge parametri trainabili senza modificare i pesi base |
| Caricamento diretto in architettura diversa | No | Errore di shape incompatibile |
Anche con Net2Net, il vantaggio è concreto ma non trasforma il problema. Per passare da 46M a 200M parametri, anche partendo dal checkpoint corrente, servono comunque centinaia di milioni di token aggiuntivi di training. Il risparmio stimato rispetto a una partenza da zero è del 20–40% di token, non una continuazione trasparente.
Risposta sintetica: tecnicamente esistono percorsi (Net2Net) che evitano la ricostruzione completa, ma nella pratica il risparmio è parziale e l'implementazione ha un costo. Per un salto significativo di parametri, la knowledge distillation è più conveniente della re-inizializzazione progressiva.
Si potrebbe abbassare ancora la perplexity del modello attuale?
Al termine di Run #6 il modello ha val loss 3.2965, perplexity ~27. La curva di apprendimento non era piatta: l'ultimo shard (ag) aveva il minimo a step 8000, l'ultimo disponibile, con la loss ancora in discesa. Esistono almeno tre leve distinte per ridurla ulteriormente, con costi e ritorni diversi.
Run #6 ha processato circa 917 milioni di token, raggiungendo la parità Chinchilla per un modello da 46 milioni di parametri (la legge Chinchilla indica 20 token per parametro come ottimo di efficienza). Tuttavia la parità Chinchilla è un ottimo di efficienza, non un limite assoluto: continuare ad addestrare oltre quel punto ha rendimenti decrescenti, ma non nulli.
Il corpus italiano più ricco disponibile senza licenze restrittive è CulturaX IT, che contiene circa 60 miliardi di token dopo filtraggio. Usarne anche solo una frazione significativa, 3–5 miliardi di token in un Run #7, porterebbe la val loss probabilmente sotto 3.1, con perplexity ~22–24. Il costo stimato su RTX 4090: 3–4 ore, circa 5 euro.
Il limite di questa leva: oltre una certa quantità di dati, il modello da 46M parametri non ha la capacità di memorizzare ulteriori pattern. La curva si appiattisce e continuare non porta vantaggi misurabili.
Ogni architettura ha un pavimento di perplexity che non può superare indipendentemente da quanti dati riceve. Per il modello attuale (d_model=512, 12 layer, 46M param) quel pavimento è probabilmente intorno a 3.0–3.1 sul corpus italiano corrente.
Passare a 125M parametri (d_model=768, 12–16 layer) abbassa strutturalmente quel pavimento. Non è un miglioramento marginale: è un cambio di categoria. Con lo stesso volume di training data, un modello da 125M raggiunge perplexity intorno a 18–20 sullo stesso corpus. Con il corpus Chinchilla-ottimale per 125M (2.5 miliardi di token), si scende ulteriormente.
Il costo è significativo: ricostruzione completa del modello, nuovo training da zero, 10–15 ore su RTX 4090 per il solo pre-training Chinchilla. È la leva più potente ma anche la più costosa.
Il corpus di Run #6 mescola Wikipedia italiana e testi web generici (CulturaX). La qualità non è uniforme: i testi web includono contenuti ridondanti, errori grammaticali, formati strutturati (tabelle HTML, codice, metadati) che il modello deve "imparare a ignorare".
Sostituire una frazione del corpus web con testi più curati, saggistica, giornalismo di qualità, narrativa contemporanea, migliora la perplexity su testo italiano standard anche senza aumentare il volume totale. La stima è un guadagno di 0.1–0.2 di val loss rispetto al corpus attuale, a parità di step.
Questa leva è anche la più rilevante per applicazioni specifiche: un modello addestrato su testi giornalistici ha perplexity più bassa su articoli di giornale, anche se la perplexity complessiva su Wikipedia non cambia. La qualità del corpus definisce il dominio dove il modello è più fluente.
Dipende dall'obiettivo. Se si vuole abbassare la perplexity con il minimo sforzo sul modello da 46M: Run #7 con 2–3 miliardi di token di CulturaX IT filtrato è il passo più diretto. Se si vuole un salto qualitativo vero, con un modello capace di rispondere a domande semplici anche senza fine-tuning, la strada è scala a 125M parametri. Le due leve non si escludono: si può fare Run #7 ora e scala in un momento successivo.
Risposta sintetica: sì, la perplexity può scendere ulteriormente. Le tre leve sono più dati (Run #7, ~5 euro, perplexity ~22–24), architettura più grande (125M param, costo maggiore, perplexity ~18–20) e corpus di qualità superiore (guadagno marginale di 0.1–0.2 senza costi di compute aggiuntivi). La leva più conveniente nell'immediato è il corpus: costa zero in compute e migliora la qualità del testo generato anche se la val loss scende poco.
Per l'inferenza è necessario usare il modello quantizzato?
No, non è necessario. Il modello fp32 da 218 MB è perfettamente usabile per l'inferenza, anche su hardware consumer. La domanda più utile non è "devo quantizzare?" ma "quale formato conviene in questo contesto?"
Il formato fp32 (virgola mobile a 32 bit) è quello prodotto direttamente dalla conversione del checkpoint PyTorch. Ogni peso occupa 4 byte. Il formato Q8_0 quantizza ogni peso a 8 bit con un fattore di scala per blocco, riducendo la dimensione a circa un quarto.
Per un modello da 46 milioni di parametri la differenza in megabyte è limitata: 218 MB fp32 contro 55 MB Q8_0. Entrambi entrano interamente nella RAM unificata di un M2, anche nei tagli base.
Il collo di bottiglia dell'inferenza su Metal non è la capacità di calcolo della GPU ma la banda memoria: quanto velocemente si riesce a spostare i pesi dalla RAM alle unità di calcolo per ogni token generato. Un modello più compatto occupa meno spazio in cache e richiede meno trasferimenti per token.
Il risultato è controintuitivo: Q8_0 è più veloce di fp32 anche quando la RAM è abbondante, perché riduce la pressione sulla banda memoria. La differenza è misurabile: nell'ordine del 20–40% di token al secondo in più.
La quantizzazione a 8 bit introduce un errore di approssimazione sui pesi. Su modelli grandi (7B+) questo errore può essere misurabile sulla perplexity. Su un modello da 46M parametri l'errore è sotto la soglia percepibile: la perplexity varia di meno di 0.01, una differenza invisibile nell'output generato.
Questo perché i modelli piccoli hanno pesi con range di valori più regolari e distribuzioni più strette: il quantizzatore a 8 bit li rappresenta con alta fedeltà. La perdita di precisione diventa rilevante solo su modelli con distribuzioni di pesi irregolari o outlier estremi, caratteristica dei modelli molto grandi.
Il fp32 è necessario in tre casi: fine-tuning e calcolo di gradienti (la backpropagation richiede precisione piena), export verso formati che non supportano la dequantizzazione (alcuni framework di deployment), e debug di anomalie nell'output dove si vuole escludere artefatti di quantizzazione come causa.
Per tutti gli altri usi — inferenza locale, server API, test, dimostrazione — Q8_0 è il formato corretto.
Risposta sintetica: no, non è obbligatorio, ma Q8_0 è la scelta migliore per l'inferenza su Apple Silicon: più veloce del fp32 per la minore pressione sulla banda memoria, e senza perdita di qualità misurabile su un modello da 46M parametri. Il fp32 serve solo per fine-tuning o operazioni che richiedono precisione piena sui pesi.
Qual è la differenza tra SFT e LoRA? Sono alternativi o si combinano?
SFT (Supervised Fine-Tuning) è una strategia di addestramento: definisce il formato dei dati (coppie prompt → completion con loss mascherata sul solo completion) e l'obiettivo (modificare il comportamento del modello, non la sua conoscenza del mondo). Non dice niente su quali parametri vengono aggiornati.
LoRA (Low-Rank Adaptation) è un metodo di ottimizzazione: definisce quanti parametri aggiornare e come. Non dice niente sul formato dei dati.
Sono ortogonali. Si combinano: il fine-tuning più usato in pratica è SFT con LoRA, cioè addestramento su coppie prompt/completion con aggiornamento solo dei parametri LoRA.
Durante il pre-training il modello impara a fare una cosa sola: predire il token successivo. Sa continuare il testo, non sa rispondere. Se gli scrivi "Chi ha scritto la Divina Commedia?" risponde con un'altra domanda — perché nel corpus le liste di domande sono frequenti e continuarle è statisticamente plausibile.
SFT cambia il comportamento senza cambiare la conoscenza. Il modello sa già cos'è Dante. Non sa ancora che deve rispondere invece di continuare liberamente. Il dataset SFT lo insegna.
La differenza tecnica rispetto al pre-training è la masked loss: la cross-entropy viene calcolata solo sui token del completion, non sul prompt. I token del prompt ricevono target=-100, che F.cross_entropy ignora per convenzione. L'obiettivo è che il modello impari a produrre risposte giuste, non a predire le domande.
Con SFT senza LoRA (full fine-tuning), tutti i parametri del modello vengono aggiornati su ogni esempio del dataset. Con un dataset piccolo (100–200 esempi) e un modello da 45.9M parametri, il rischio è che il modello dimentichi il pre-training: i pesi si spostano verso il nuovo comportamento e le connessioni per il vocabolario generale si indeboliscono.
Invece di aggiornare direttamente la matrice W, LoRA aggiunge una perturbazione a basso rango:
W_aggiornato = W_frozen + (α/r) · B · A
Dove A ha shape (r × d_in) e B ha shape (d_out × r), con r piccolo (tipicamente 4–16). W rimane congelato. Solo A e B vengono addestrati.
Per una matrice 512×512 con r=8:
Per il modello medium (12 layer × 4 matrici per layer):
Poiché W non cambia mai, il base model può restare fisso in RAM. Adapter diversi (uno per dominio, uno per task) vengono caricati sopra lo stesso modello di base a runtime. Il costo di ogni specializzazione è ~1.5 MB di RAM aggiuntiva e zero overhead sul base model.
| Full SFT | SFT + LoRA | |
|---|---|---|
| Parametri aggiornati | 45.9M | ~393k |
| Checkpoint size | 175 MB (copia completa) | ~1.5 MB (solo A e B) |
| Catastrophic forgetting | Alto rischio con dataset piccoli | Basso — W è frozen |
| Adapter multipli su stesso base model | No | Sì — swap a runtime |
| Adatto a dataset piccoli (<500 esempi) | No | Sì |
Risposta sintetica: SFT e LoRA non sono alternativi. SFT descrive il formato dei dati e l'obiettivo; LoRA descrive quali parametri aggiornare. In produzione si usano insieme. Il full SFT (senza LoRA) è utile per capire il comportamento di base del fine-tuning prima di aggiungere l'astrazione di LoRA — è il percorso che seguiamo nella roadmap: Phase 09 (full SFT) → Phase 10 (LoRA).
Se volessimo ripartire da zero con un modello più grande, qual è la dimensione massima realistica data l'infrastruttura attuale?
La legge di Chinchilla dice che per addestrare bene un modello servono circa 20 token per parametro. Con i 210M di token attualmente disponibili, il punto di ottimale Chinchilla è intorno ai 10M di parametri. Il modello attuale da 45,9M parametri è già sovradimensionato rispetto al corpus — è Chinchilla-ottimale per 918M token, che è esattamente il target di espansione corpus che avevamo pianificato.
Scalare a 350M parametri senza espandere il corpus significherebbe avere 0,6 token per parametro: il modello sarebbe drasticamente undertrainato e produrrebbe risultati peggiori dell'attuale.
| Parametri | Architettura indicativa | Token Chinchilla-ottimali | Tempo su RTX 4090 | Costo Vast.ai |
|---|---|---|---|---|
| 45,9M (attuale) | d=512, L=12 | 918M | ~76 min | ~$2 |
| 150M | d=768, L=18 | 3B | ~6 ore | ~$9 |
| 350M | d=1024, L=24 | 7B | ~18 ore | ~$27 |
| 700M | d=1280, L=32 | 14B | ~48 ore | ~$72 |
In addestramento bf16 con gradient accumulation (Phase 07), la memoria per pesi + gradienti + stati AdamW è circa 16 byte per parametro. Per 1B parametri: 16 GB — entra in 24 GB con margine. Il collo di bottiglia reale è il corpus, non la VRAM.
d_model, num_layers, num_heads in GPTConfig. Il sistema di preset è già parametrizzato.
Che caratteristiche di reasoning e comprensione avrebbe un modello da 150M, 350M, 700M e 1B parametri, assumendo training Chinchilla-ottimale?
| Capacità | Stato |
|---|---|
| Morfologia italiana (accordo, coniugazione) | ✓ solida |
| Completamento coerente a livello di frase | ✓ |
| Registro stilistico consistente | ✓ parziale |
| Coerenza a livello di paragrafo | ✗ si perde dopo 3–4 frasi |
| Recall di fatti dal corpus | ✗ inaffidabile |
| Inferenza logica semplice | ✗ |
| Tracking di entità attraverso il testo | ✗ |
Il modello sa come si scrive l'italiano molto meglio dell'attuale, ma non sa ancora di cosa sta scrivendo. Con SFT: adatto a task strutturati semplici (classificazione, NER, formato fisso).
Questo è il range già osservato empiricamente nel benchmark con Minerva 350M. I risultati di quel confronto (ricetta della pasta funziona, crisi del '29 confusa; Puglia con dettagli, Ungaretti corretto) riflettono esattamente questa soglia.
| Capacità | Stato |
|---|---|
| Coerenza di paragrafo | ✓ |
| Recall di fatti comuni dal corpus | ✓ selettivo |
| Registro appropriato per dominio | ✓ |
| Inferenza logica in-context | ✗ quasi mai |
| Tracking entità su più paragrafi | ~ inaffidabile |
| Con SFT: generalizzazione a istruzioni non viste | ~ parziale |
| Ragionamento causale ("perché/quindi") | ✗ |
Qui cominciano ad apparire comportamenti che assomigliano al ragionamento, ma rimangono fragili e non generalizzano in modo affidabile.
| Capacità | Stato |
|---|---|
| Coerenza multi-paragrafo (3–5 paragrafi) | ✓ |
| Tracking entità e coreference | ✓ |
| Inferenza semplice in-context | ~ emerge, non affidabile |
| Ragionamento causale a un passo | ~ emergente |
| Aritmetica semplice in linguaggio naturale | ~ emergente |
| Con SFT: istruzioni multi-step | ✓ miglioramento netto |
| Comprensione di testi argomentativi | ✗ pattern, non comprensione |
| Capacità | Stato |
|---|---|
| In-context learning da pochi esempi (few-shot) | ✓ più affidabile |
| Tracking di plot su testi lunghi | ✓ |
| Ragionamento causale a più passi | ~ compare, non generalizza |
| Aritmetica in linguaggio naturale | ✓ semplice |
| Comprensione testi argomentativi | ~ struttura sì, implicazioni no |
| Meta-ragionamento | ✗ non emerge sotto 7B |
Il ragionamento multi-step affidabile e il meta-ragionamento non emergono prima dei 7–13B parametri con training adeguato. Sotto quella soglia si ottengono miglioramenti graduali nella fluenza e nella conoscenza fattuale, ma il "ragionamento" rimane essenzialmente pattern matching su testo simile al corpus.
Per i use case concreti del progetto (intent recognition, calendari, RAG locale): 150M è sufficiente con SFT su dati di qualità. 350M aggiunge robustezza. 700M–1B ha senso solo se il task richiede comprensione contestuale profonda o istruzioni multi-step complesse.
Il ragionamento degli LLM è reale o solo simulazione dovuta al calcolo probabilistico? Come può una matrice di probabilità diventare ragionamento logico? E la proprietà emergente dipende dal corpus di training?
Il meta-ragionamento è la capacità di ragionare sul proprio processo di ragionamento: "non sono sicuro di questo, rivedo", "questo approccio non funziona, cambio strategia", "so di non sapere X". Negli LLM si manifesta come chain-of-thought spontaneo, calibrazione dell'incertezza, auto-correzione, rilevamento di contraddizioni nel proprio output.
Come si misura:
La domanda contiene un'assunzione da esaminare. Il ragionamento umano è impulsi elettrochimici che seguono leggi fisiche in reti neurali biologiche. Se si applica l'argomento "è solo X, non ragionamento vero" ricorsivamente all'essere umano, si arriva a un regresso che svuota il concetto di ragionamento di qualsiasi significato.
Il punto filosoficamente rilevante non è il substrato (silicio vs carbonio, probabilità vs elettrochimica), ma la struttura: il sistema implementa operazioni che producono output consistenti con l'inferenza logica e che generalizzano oltre gli esempi visti?
La risposta empirica è: parzialmente sì, con limiti precisi. I LLM falliscono sistematicamente su problemi che richiedono consistenza logica rigorosa su contesti lunghi o aritmetica con grandi numeri. Ma generalizzano su ragionamento analogico e inferenza da contesto. Questo pattern è incompatibile sia con "puro pappagallo statistico" sia con "ragionamento formale vero".
Nessun singolo peso della rete codifica "implicazione logica". Ma la composizione di miliardi di pesi, addestrata su testo umano che contiene ragionamento, può implementare il ragionamento come proprietà emergente.
L'emergenza è il meccanismo centrale. Nessuna singola molecola d'acqua è "bagnata": la bagnatura emerge dall'interazione collettiva. Analogamente, il ragionamento negli LLM non è localizzato in nessun componente — è distribuito sull'intera rete. Non è simulazione della bagnatura: è bagnatura.
Il ragionamento umano dipende da fattori biochimici (hardware), condizionamenti esperienziali (corpus di training) e scelte volitive (analogamente: SFT, RLHF, contesto di inferenza). Una persona educata secondo determinati principi può scegliere di seguirli o distaccarsene.
Questo parallelo illumina direttamente gli LLM:
Parzialmente, con un limite preciso:
Sì, in modo irreversibile. L'ordine corretto è una pipeline a senso unico:
Pre-training → SFT (opzionale) → LoRA
L'SFT modifica tutti i pesi W del modello su un punto di convergenza specifico. Se riprendi il pre-training su un corpus più grande, quel punto cambia — anche di poco — e l'allineamento sintattico e semantico introdotto dall'SFT si disintegra. Va rifatto.
Un adapter LoRA è calibrato per sommarsi a specifici pesi W: ΔW = (α/r) · B @ A. Se W cambia, la somma porta il modello in un punto diverso dello spazio dei parametri. Il risultato può essere degradazione delle performance, output incoerenti, o entrambe le cose. L'adapter non è portabile tra versioni del base model.
| Step | Cosa fa | Dipende da |
|---|---|---|
| Pre-training | Apprende lingua, morfologia, semantica su corpus ampio | Corpus generale |
| SFT | Orienta il modello al formato istruzione/risposta | Base model stabile |
| LoRA | Specializza su dominio specifico con pochi parametri trainabili | Base model stabile (+ SFT se presente) |
Prima di qualsiasi run di pre-training aggiuntivo, considera tutti gli adapter LoRA e i checkpoint SFT esistenti come deprecati. LoRA è sempre l'ultimo passo della pipeline. Se vuoi specializzare dominio senza perdere il lavoro fatto, l'unica strada è freezare il base model e fare LoRA — non pre-training.
Il modello estrae l'espressione temporale così come appare nel testo: "date": "domani", "date": "venerdì prossimo", "deadline": "primo del mese". Non risolve mai le date in valori assoluti. Questo è il contratto corretto tra il layer di comprensione del linguaggio e il layer applicativo.
Un assistente calendario completo ha due livelli distinti con responsabilità separate:
| Layer | Input | Output | Strumento |
|---|---|---|---|
| NLU (questo modello) | Frase in italiano | JSON con intent + entità grezze | LoRA adapter SLM |
| Date resolver | Espressione temporale + data corrente | Data ISO assoluta | dateparser o logica custom |
| Calendar API | JSON con date assolute | Evento creato | Google Calendar, CalDAV… |
Tre ragioni concrete:
dateparser. Addestrare un modello a fare la stessa cosa è spreco di capacità parametrica.Con dateparser (supporto italiano nativo) la risoluzione è tre righe:
import dateparser
from datetime import datetime
def resolve(expr: str, anchor: datetime = None) -> str | None:
dt = dateparser.parse(expr, languages=["it"], settings={
"PREFER_DATES_FROM": "future",
"RELATIVE_BASE": anchor or datetime.now(),
})
return dt.date().isoformat() if dt else None
resolve("venerdì prossimo") # → "2026-10-16"
resolve("primo del mese") # → "2026-11-01"
resolve("domani") # → "2026-10-11"
resolve("31 ottobre") # → "2026-10-31"
Utente: "Crea appuntamento con Marco venerdì alle 15"
↓ SLM + LoRA adapter
{"intent":"create_event","contact":"Marco","date":"venerdì","time":"15:00"}
↓ date resolver (anchor = oggi 2026-10-10)
{"contact":"Marco","start":"2026-10-16T15:00:00","end":"2026-10-16T16:00:00"}
↓ Calendar API
Evento creato ✓