SLM · Studio e Proposta

Fine-tuning, corpus e valutazione

Fondamenti tecnici dei parametri di fine-tuning, analisi del corpus letterario, framework di valutazione per confrontare Run #3, Run #4 (RoPE) e il modello specializzato.

1 · Parametri di fine-tuning

Il fine-tuning (continued pre-training) parte da un modello già addestrato. L'obiettivo non è re-imparare la lingua italiana, ma spostare la distribuzione di probabilità verso un registro specifico — in questo caso, prosa narrativa non accademica. Ogni parametro controlla un aspetto di questo spostamento.

Contesto di riferimento per il calcolo: Run #3 — 47.8 M token Wikipedia IT, max_lr = 3×10⁻⁴, batch 16, seq 256, 30 000 step. Corpus fine-tuning attuale: 925 K token (780 K letterario + 145 K techno-fiction).
max_lr 5×10⁻⁵ – 1×10⁻⁴  ·  pretraining: 3×10⁻⁴ ridurre a 17–33%

Regola il ritmo con cui i gradienti del corpus letterario sovrascrivono la rappresentazione interna già formata. Un valore troppo alto cancella la conoscenza enciclopedica (catastrofic forgetting); troppo basso e il segnale del corpus non si propaga.

Derivazione teorica: LR_ft = √(n_ft / n_pre) × LR_pre
= √(925 K / 47 800 K) × 3×10⁻⁴ = √0.0194 × 3×10⁻⁴ ≈ 0.139 × 3×10⁻⁴ ≈ 4.2×10⁻⁵
Il range 5×10⁻⁵ – 1×10⁻⁴ copre questa stima con un margine conservativo.

Segnale di allarme: se la val loss su un probe set Wikipedia aumenta di più del 10% rispetto al baseline (attuale perplexity ~59), il LR è troppo alto.

troppo alto → forgetting + overfitting troppo basso → nessun spostamento di registro
batch_size 8  ·  pretraining: 16 dimezzare

Con batch 8 e seq 256 ogni step vede 8 × 256 = 2 048 token. Il corpus ha 925 K token: un'epoca = ⌊925 000 / 2 048⌋ = 452 step.

Ridurre il batch serve principalmente a ottenere più aggiornamenti per token (452 step/epoca vs 226 con batch 16), aumentando la granularità dell'adattamento. Il gradiente è più rumoroso, il che agisce come regolarizzatore implicito contro l'overfitting su un corpus piccolo.

1 epoca = 452 step 2 epoche = 904 step 3 epoche = 1 356 step (limite)
seq_len 256 — vincolato dal checkpoint non modificare

Il checkpoint di Run #3 contiene pos_encoding.pe con shape [1, 256, 512]. Cambiare seq_len richiederebbe di reinizializzare il positional encoding, distruggendo l'allineamento tra le posizioni imparate durante il pretraining. Il modello userebbe pesi posizionali randomici per le posizioni esistenti o nuove.

Questa è una limitazione dell'approccio sinusoidale fisso. RoPE (Run #4) non ha questa restrizione: le rotazioni sono calcolate al volo in base alla posizione, quindi seq_len è modificabile senza ricaricare pesi.

solo con RoPE (Run #4): seq_len modificabile
warmup_steps 50 step  ·  pretraining: 200 ridurre

Il warmup serve a portare il LR da zero al massimo senza destabilizzare i momenti di AdamW nel momento iniziale. Nel pretraining, il modello parte da pesi casuali e l'ottimizzatore non ha storia: il warmup lungo (200 step) è necessario.

Nel fine-tuning, lo stato dell'ottimizzatore (m₁, m₂) viene caricato dal checkpoint. I momenti sono già orientati verso le direzioni del pretraining Wikipedia. Un warmup breve (50 step) è sufficiente per smorzare la transizione verso i nuovi gradienti letterari senza perdere questa "memoria" dell'ottimizzatore.

max_steps 700 – 1 200 step  ·  ~1.5–2.7 epoche monitorare

Con 925 K token e batch 8: il range 700–1 200 corrisponde a 1.5–2.7 passaggi sul corpus. Oltre le 3 epoche (1 356 step) il rischio di overfitting aumenta significativamente su un corpus di questa dimensione.

Non è un numero fisso: la val loss è il segnale corretto. Salvare checkpoint ogni 100 step e fermarsi quando la val loss inizia a risalire e rimane sopra il minimo per due checkpoint consecutivi.

checkpoint ogni 100 step early stopping a +2 checkpoint dal best
val_split 10% del corpus letterario + probe Wikipedia doppia misurazione

Il val set letterario misura l'adattamento allo stile target. Ma questa metrica da sola è insufficiente: un modello che dimentica Wikipedia avrà val loss bassa sul letterario semplicemente perché ha "dimenticato" ogni alternativa.

È necessario un secondo val set: un probe di ~50 K token da Wikipedia IT (estratti dal corpus di pretraining, tenuti da parte). La val loss su questo probe deve rimanere entro il 5–8% del baseline (perplexity attuale ~59 → non oltre ~65).

val letterario: segnale di adattamento val Wikipedia: segnale di forgetting

Configurazione di lancio

python3 train.py \
  --resume \
  --data data/corpus_ft.txt \
  --tokenizer bpe-spm \
  --preset medium \
  --seq 256 \
  --batch 8 \
  --steps 1200 \
  --lr 7e-5 \
  --min-lr 7e-6 \
  --warmup 50 \
  --eval-interval 100 \
  --device mps
ParametroPretrainingFine-tuningMotivazione
max_lr3×10⁻⁴7×10⁻⁵scala sul ratio dei corpus
min_lr3×10⁻⁵7×10⁻⁶mantiene il rapporto 10×
warmup20050ottimizzatore già caldo
batch168più step/epoch, regolarizzazione
seq256256vincolato dal checkpoint
steps30 000700–1 2001.5–2.7 epoche sul corpus
tempo M2~230 h~7–12 hstima a ~35 s/step con batch 8

2 · Analisi corpus e proposta di augmentation

Stato attuale

Il corpus attuale copre un asse stilistico coerente: prosa italiana non accademica. I testi identificati per campionamento:

Testo/AutoreRegistroStima token
Amedeo Balbi (astrophysics)divulgazione scientifica narrativa~150 K
Narrativa contemporanea italiana (prima persona)emotivo, confessionale~200 K
Elena Ferrante — L'Amica Genialenarrativa femminile, Napoli~200 K
Il Colpo Gobbo + hard-boiled noircrime/heist, prima persona~130 K
Traduzione Vonnegut + noir americanoironia dark, americano tradotto~100 K
corpus.txt — Cercando Natashatechno-fiction, prima persona~145 K
Totale attuale—925 K
Corpus attuale925 K token
Target minimo (5% del pretraining)2.4 M token
Target ottimale4.8 M token

Regola empirica per CPT: il corpus di fine-tuning dovrebbe essere il 5–10% del volume del pretraining per un adattamento stabile. Con 47.8 M token Wikipedia: target minimo 2.4 M, ottimale 4.8 M. Il gap attuale è di 1.5–3.9 M token.

Fonti di augmentation consigliate

FonteVolume stim.RegistroCoerenza stilisticaNote
Altri EPUB dallo stesso asse
narrativa contemporanea IT, noir, divulgazione
300 K–1 Mcontemporaneo alta Tool esistente: epub_to_text.py. Massimo allineamento con il corpus attuale.
Project Gutenberg IT
Pirandello, Svevo, Deledda, Fogazzaro
500 K–2 Mletterario classico media Italiano ottocentesco/novecentesco. Distanza linguistica dal contemporaneo. Utile per volume, rischio di spostare il registro verso l'arcaico.
Testi personali
manoscritti, blog, note lunghe
10 K–200 Kvoce individuale massima Segnale più forte per un writing assistant. Anche 50 K token di testi propri valgono più di 500 K di Gutenberg per questo obiettivo.
Wikipedia IT slice (anti-forgetting)
50 K token dal corpus di pretraining
50 Kenciclopedico ancora Non per ampliare il registro ma per ridurre il forgetting. Mischiare 10–15% di Wikipedia nel corpus di fine-tuning mantiene il modello ancorato alla conoscenza del mondo.
Long-form journalism IT
Internazionale, Limes, Il Post (articoli lunghi)
300 K–1 Msaggistico-narrativo media Registro intermedio tra divulgazione Balbi e Wikipedia. Aggiunge densità informativa con stile narrativo accessibile. Richiede scraping.

Proposta corpus target

Corpus ft v1 (minimo, subito fattibile):
925 K esistenti + 3–5 EPUB dallo stesso asse stilistico (noir, narrativa contemporanea) ≈ 1.5–2.5 M token. Copre il target minimo. Fine-tuning overnight su M2, risultato buono.

Corpus ft v2 (ottimale):
v1 + testi personali (qualsiasi volume) + Wikipedia slice 50 K ≈ 2–3 M token + voce propria. Questo è il target per un writing assistant genuinamente personalizzato.

3 · Cosa può fare Run #3 adesso

Run #3 è un modello di linguaggio autocompletivo, senza instruction fine-tuning né RLHF. Questa distinzione è fondamentale per calibrare le aspettative.

CapacitàRun #3 oraPost fine-tuningMai (senza arch. change)
Generazione testo italiano grammaticale ✓ alta qualità✓ registro raffinato—
Conoscenza enciclopedica (geografia, storia) ✓ presente ma imprecisa⚠ parziale perdita—
Continuazione stilistica (stesso registro del prompt) ⚠ tende all'enciclopedico✓ narrativo—
Risposta a domande fattuali accurate ✗ allucinazioni✗senza RAG
Seguire istruzioni ("scrivi una email su...") ✗✗richiede SFT + chat template
Coerenza narrativa oltre 256 token ⚠ degrada⚠ stessa finestrasenza ctx esteso
Generazione oltre seq=256 (extrapolazione) ✗ PE sinusoidale✗ (Run #3)✓ Run #4 RoPE
Scoring perplexity su un testo dato ✓ direttamente✓—
Aspettativa critica da gestire: il modello non "capisce" nel senso generale. Può generare testo stilisticamente coerente e plausibile, ma le associazioni fattuali sono probabilistiche — "governo comunista" non implica comprensione della storia. Questo è normale e atteso per un modello di 46 M parametri senza SFT.

4 · Framework di valutazione

Metriche quantitative (automatizzabili)

MetricaFormulaCosa misuraTarget
Perplexity PP = eval_loss quanto il modello è "sorpreso" dal testo di test; più basso = migliore Run #3: 59 → fine-tuning: <50 su literary val
Forgetting ratio ΔPP/PP_base incremento percentuale di perplexity sul probe Wikipedia dopo fine-tuning <8% (safe) · <15% (accettabile)
Dist-N |unique n-grams| / |total n-grams| diversità lessicale nelle generazioni; basso = testo ripetitivo Dist-4 > 0.70
Rep-3 3-gram ripetuti / totale tasso di ripetizione in una generazione; alto = loop o degenerazione <0.05 (5%)
Extrapolation loss val_loss a seq=512 solo per confronto Run #3 vs Run #4 — la PE sinusoidale degrada fuori distribuzione Run #4 << Run #3 a seq 512

Battery di test (prompts)

20 prompt fissi, 5 categorie, usati con tutti i modelli comparati. Temperatura 0.8, top-k 40, 128 token generati.

ENCICLOPEDICO
"La città di Napoli è"
"Nel 1943 il governo italiano"
testa conoscenza del mondo; Run #3 dovrebbe eccellere, il fine-tuned potrebbe degradare
forgetting probe
NARRATIVO
"Non mi ero mai reso conto che"
"Il sole tramontava lento sul"
"Mia madre era il tipo di persona"
testa adattamento stilistico; dopo fine-tuning dovrebbe avvicinarsi al registro letterario
stile target
HARD-BOILED
"Entrò nell'ufficio e capii subito"
"L'avevo seguita per tre giorni"
testa il registro noir; diretto confronto pre/post fine-tuning su corpus con hard-boiled
benchmark differenziale
SCIENTIFICO
"La luce si propaga a"
"Siamo soli nell'universo?"
testa la divulgazione scientifica; atteso miglioramento post fine-tuning (Balbi nel corpus)
stile target
EXTRAPOLATION
stessi prompt a seq=512
solo Run #3 vs Run #4 (RoPE). La PE sinusoidale produce output degradato oltre il training seq. RoPE mantiene la qualità.
solo A/B RoPE

Valutazione qualitativa (rubrica)

Per ogni prompt, votare 1–5 su quattro dimensioni. Valutazione cieca: shuffle dei modelli, nessuna etichetta visibile.

Dimensione1 — scarso3 — accettabile5 — ottimo
Correttezza grammaticale frasi spezzate, errori di accordo grammatica corretta, alcune costruzioni goffe nativo, fluente, costruzioni complesse corrette
Coerenza di registro salta tra enciclopedico, narrativo, frammentato registro prevalente ma con discontinuità registro mantenuto per tutti i 128 token
Coerenza tematica il soggetto cambia entro 20 token il soggetto si mantiene ma devia il filo narrativo/tematico è sostenuto
Aderenza stilistica al corpus suona come Wikipedia accenni al registro target ma ibrido riconoscibile come prosa narrativa italiana

5 · Proposta operativa

PASSO 1 · ~1h
Implementa RoPE e avvia Run #4 su Vast.ai
~50 righe di diff in attention.py, transformer.py, model.py. Stima: 1h 5m + setup su Vast.ai ≈ 1h 20m totali, ~$0.60. Baseline RoPE necessario per il confronto.
PASSO 2 · ~30 min
Amplia il corpus letterario
Estrai 3–5 EPUB dallo stesso asse stilistico (noir, narrativa contemporanea, divulgazione). Target: portare il corpus a ≥2 M token. Prepara wikipedia probe set: head -c 50M data/corpus_bpe.txt > data/wiki_probe.txt
PASSO 3 · overnight M2
Fine-tuning letterario da Run #3
1 200 step (2.7 epoche), batch 8, lr 7e-5. Stima: ~7–12 h su M2. Monitora val letterario + val Wikipedia probe. Early stop a +2 checkpoint dal best.
PASSO 4 · ~2h
Comparazione A/B sistematica
Genera la battery (20 prompt × 4 modelli: Run#3, Run#4, FT-R3, FT-R4). Calcola PP, Dist-4, Rep-3 su tutti. Valutazione qualitativa cieca su un subset di 10 generazioni per modello.
PASSO 5 · facoltativo
GGUF export da Run #4
Solo Run #4 (RoPE) è nativo-compatibile con llama.cpp. Run #3 richiederebbe un custom shim per la PE sinusoidale. Implementa tools/slm_to_gguf.py dopo la comparazione.
Domanda aperta prima del passo 2: i testi personali (manoscritti, note lunghe, testi scritti in prima persona) sono il singolo maggiore acceleratore di qualità per un writing assistant. Anche 30–50 K token di prosa propria in italiano valgono più di 1 M di Gutenberg. Se esiste del materiale, va aggiunto qui.

6 · Supervised Fine-Tuning — meccanica interna

Il Continued Pre-Training (CPT, sezioni 1–5) e il Supervised Fine-Tuning (SFT) sembrano la stessa operazione — "training su un checkpoint esistente" — ma differiscono in modo sostanziale nel formato dei dati, nella funzione di loss e negli obiettivi. Capire la differenza è necessario per progettare train_sft.py correttamente.

6.1 · CLM vs SFT — perché il formato dati cambia

Pre-training / CPT (Causal Language Modeling su testo continuo): il corpus è una sequenza piatta di token. Il training chunka questa sequenza in finestre di seq_len token e per ogni finestra calcola la loss su tutti i token: ogni token deve predire il successivo. Non c'è struttura semantica esplicita tra le posizioni — il modello impara la distribuzione generale della lingua.

SFT (coppie prompt → completion): i dati sono strutturati in coppie dove il prompt è fisso (l'istruzione, il template parziale) e la completion è ciò che il modello deve imparare a produrre. Non si può trattarle come testo continuo, perché il modello imparerebbe anche a "riprodurre la domanda" — il che è inutile e introduce segnale di gradiente verso la distribuzione sbagliata.

AspettoCLM / CPTSFT
Inputtesto continuo, nessuna strutturacoppie (prompt, completion)
Loss calcolata sututti i tokensolo i token di completion
Dataset tipicomilioni – miliardi di tokencentinaia – migliaia di esempi
Obiettivomodellare la lingua in generaleorientare il comportamento su un task
Format file.txt sliding window.jsonl con campi prompt/completion

6.2 · Loss mascherata sul prompt (ignore_index=-100)

In PyTorch, F.cross_entropy(logits, labels) accetta un parametro ignore_index (default -100): ogni posizione in labels con valore -100 viene ignorata nel calcolo della loss — contribuisce zero al gradiente, nessun aggiornamento dei pesi.

In SFT si costruisce il batch così:

# Esempio batch SFT: una convocazione condominiale
prompt     = "Redigi la convocazione dell'assemblea ordinaria del 15/10/2026."
completion = "Gentili condomini, si convoca l'assemblea ordinaria del condominio..."

# Tokenizziamo la concatenazione
input_ids = tokenize(prompt + "\n\n" + completion)
# len(prompt_tokens) = 18, len(completion_tokens) = 64

# Labels: -100 sul prompt, token reale sulla completion (shiftati di 1)
labels = [-100] * 18 + input_ids[19:]   # completion[1:] è il target

Senza questa maschera il modello riceve gradiente anche dai token del prompt — impara a massimizzare la probabilità delle parole della domanda, ottimizzando la distribuzione sbagliata. La convergenza è molto più lenta e il modello tende a confondere cosa deve ricevere (prompt) con cosa deve produrre (completion).

Labels sono shiftati di uno rispetto a input_ids. Il modello al passo i vede i token 0…i e deve predire il token i+1. Quindi labels[i] = input_ids[i+1]. L'implementazione corretta usa input_ids[1:] come labels e input_ids[:-1] come input, oppure si gestisce internamente con uno shift di offset. Errare su questo dettaglio è la causa più comune di training SFT che converge ma produce output errati.

6.3 · Catastrophic forgetting

Il pre-training ha codificato la distribuzione dell'italiano in 30 000 step su 47.8 M token. Il SFT spinge i pesi verso un sotto-dominio di questa distribuzione. Se la spinta è troppo forte — LR alta, molte epoche, dataset grande — il modello sovrascrive le rappresentazioni generali e "dimentica" come scrivere di altro.

Condizione SFTForgetting attesoComportamento
200–300 esempi, LR 1e-5, 3 epochetrascurabile (<5%)modello scrive ancora italiano generico correttamente
1 000–5 000 esempi, LR 3e-5, 5 epochemoderato (5–15%)italiano generico leggermente degradato, dominio OK
10 000+ esempi, LR 1e-4, 10+ epochesevero (>20%)il modello "parla solo" di condominio / moto / etc.

Mitigazioni standard:

6.4 · Shift degli iperparametri

ParametroPre-trainingCPT letterarioSFT (Branch A)
max_lr3×10⁻⁴7×10⁻⁵1×10⁻⁵ – 3×10⁻⁵
min_lr3×10⁻⁵7×10⁻⁶1×10⁻⁶
warmup_steps2005010–30
batch_size1684–8
epochs~2.6 pass sul corpus1.5–2.72–5
loss targettutti i tokentutti i tokensolo completion
val setWikipedia sliceletterario + Wikipedia probedomain test set + Wikipedia probe
Motivazione LR bassaEvitare catastrophic forgetting. Il modello si trova già in un minimo buono — passi piccoli.

6.5 · Formati dati per SFT — tre opzioni

Opzione A — completamento con separatore testuale (consigliata per Branch A)

# data/sft_condominio.jsonl
{"prompt": "Redigi una convocazione assemblea ordinaria. Data: 15/10/2026. Luogo: sala condominiale. ODG: bilancio preventivo, lavori straordinari.", "completion": "Gentili condomini,\n\nSi convoca l'assemblea ordinaria del condominio..."}
{"prompt": "Scrivi una diffida per morosità. Condomino: Mario Rossi, scala B, interno 4. Debito: €1.234,00 per quote condominiali arretrate.", "completion": "Raccomandata A/R\n\nEgregio Sig. Mario Rossi..."}

Il separatore \n\n (o "### Risposta:\n") nella concatenazione identifica il punto di switch da labels=-100 a labels=token. Nessun token speciale aggiunto al vocabolario. Nessun rischio di destabilizzare gli embedding esistenti.

Opzione B — chat template (ChatML / LLaMA-style)

<|im_start|>user
Redigi una convocazione...
<|im_end|>
<|im_start|>assistant
Gentili condomini...
<|im_end|>

Richiede l'aggiunta di token speciali (<|im_start|>, <|im_end|>, user, assistant) al vocabolario BPE-SPM. I nuovi embedding vengono inizializzati casualmente e non hanno training — fonte di instabilità nelle prime iterazioni. Appropriato per modelli destinati a interfaccia chat. Overkill per Branch A.

Opzione C — testo puro con mascheratura by offset

Concatenazione raw senza separatore esplicito, con mascheratura calcolata dal numero di token del prompt. Fragile (un token in più/meno nel prompt sposta tutto) e non consigliata.

Per Branch A (condominio, moto, scrittura): Opzione A. Separatore testuale esplicito, JSONL, nessun token speciale. Implementazione semplice, stabile, facile da ispezionare. Il modello impara a generare il testo dopo il separatore — sufficiente per tutti i task NLG del Branch A.

6.6 · Evaluation specifica per SFT

La val loss su testo continuo (usata in pre-training e CPT) è insufficiente per valutare l'SFT. Una val loss bassa dice che il modello assegna alta probabilità ai token del test set — non che il testo generato abbia la struttura corretta.

MetricaCosa misuraQuando usarla
PPL su domain test set adattamento al dominio SFT (20% holdout del dataset) segnale primario durante training
PPL su Wikipedia probe catastrophic forgetting segnale secondario durante training
chrF / BLEU sovrapposizione con reference su task template (convocazione, verbale) post-training su completions di test set
Validità strutturale la convocazione ha data + luogo + ODG? il JSON è parsabile? rule-based check automatizzabile
Ispezione manuale qualità percepita, appropriatezza del registro, errori factual dopo ogni esperimento, su 5–10 prompt non visti
La metrica più importante per Branch A è l'ispezione manuale. Con 200–300 esempi SFT, l'overfitting è raro e la val loss converge rapidamente. Ciò che non si vede dai numeri: se il modello genera una convocazione con la data sbagliata, un nome di condominio inventato, o un verbale privo dell'ordine del giorno. Questi errori si vedono solo leggendo l'output.

SLM · Fine-tuning e Valutazione — ← Diario · Documentazione completa · Home