GPT-2 Italiano 514M — modello didattico addestrato da zero

Un GPT-2 da 514 milioni di parametri addestrato interamente da zero sulla lingua italiana, su una singola GPU consumer (RTX 5070 Ti, 16 GB), come materiale didattico per l'insegnamento dell'intelligenza artificiale nella scuola secondaria di secondo grado.

Non è un modello utile in produzione. Serve a mostrare come funziona un LLM: cosa impara, cosa non impara, e perché. La sezione Limiti è la parte più importante di questa scheda.

Fratello minore: gpt2-it-185m-instruct — insieme formano un esperimento controllato sulle scaling law.


In breve

Parametri 514.5M
Architettura GPT-2 (decoder-only), 24 layer, n_embd 1280, 20 teste
Context 1024 token
Vocabolario 32.001 (BPE ByteLevel addestrato da zero sull'italiano)
Token di pre-training 10.000.000.423 (~19.5 token/parametro — Chinchilla-optimal)
Corpus 25% Wikipedia IT + 75% FineWeb-2 ita_Latn
Validation loss (pre-training) 2.686
Validation loss (instruct) 1.463
Hardware 1× RTX 5070 Ti 16GB (Blackwell sm_120)
Tempo di training ~15 giorni, in sessioni interrompibili (scheduler WSD)
Costo elettrico stimato ~45 €

Come si usa

Il modello è addestrato esclusivamente sul formato Alpaca a turno singolo. Con un prompt diverso le risposte peggiorano sensibilmente.

from transformers import pipeline, PreTrainedTokenizerFast

MODEL = "emanuelealbertosi/gpt2-it-514m-instruct"
tok = PreTrainedTokenizerFast.from_pretrained(MODEL, clean_up_tokenization_spaces=False)
gen = pipeline("text-generation", model=MODEL, tokenizer=tok, device=0)

def rispondi(istruzione):
    prompt = f"### Istruzione:\n{istruzione}\n\n### Risposta:\n"
    out = gen(prompt, max_new_tokens=250, temperature=0.4, top_k=50,
              repetition_penalty=1.2, do_sample=True,
              pad_token_id=tok.eos_token_id, eos_token_id=tok.eos_token_id,
              clean_up_tokenization_spaces=False)
    return out[0]["generated_text"][len(prompt):].strip()

print(rispondi("Qual è la capitale della Francia?"))

Formato esatto del prompt (con ### Input: opzionale fra i due, se serve un contesto):

### Istruzione:
{domanda}

### Risposta:

Usare temperature bassa (0.3–0.5). A 0.7 il modello è più creativo ma sbaglia più spesso i fatti: nei test, "capitale della Francia" dà Parigi 3 volte su 4 a T=0.3, molto meno a T=0.7.

GGUF / LM Studio

Versione GGUF: gpt2-it-514m-gguf.

Template Jinja (turno singolo — il modello non ha mai visto conversazioni multi-turno):

{% for message in messages %}{% if message['role'] == 'user' and message['content'] is defined %}### Istruzione:
{{ message['content'] }}

### Risposta:
{% endif %}{% endfor %}
`

In LM Studio incollare **l'intero blocco**. Non usare i preset generici Human: / AI: e non aggiungere un system prompt: il modello è stato addestrato esclusivamente a turno singolo.``

Parametri consigliati: `temperature 0.4`, `top_k 50`, `repeat_penalty 1.2`,
`repeat_last_n 512`. L'EOS è il token **32000** e viene riconosciuto correttamente da
llama.cpp: il modello si ferma da solo.

---

## Cosa sa fare

Su domande brevi e concetti frequenti nel corpus è affidabile:

> **Qual è la montagna più alta del mondo?**
> La montagna più alta del mondo è l'Everest, che raggiunge un'altezza di 8.848 metri
> (29.029 piedi). Si trova nella catena montuosa Mahalangur, nell'Himalaya, e si trova al
> confine tra Nepal e Tibet. ✅ *(interamente corretto)*

> **Qual è la capitale della Francia?**
> La capitale della Francia è Parigi.

> **Elenca tre colori primari.**
> I tre colori primari sono rosso, blu e giallo.

> **Chi è Mario Draghi?**
> Mario Draghi è il presidente della Banca centrale europea (BCE). *(ruolo corretto; data e
> luogo di nascita che seguono sono inventati)*

Sulla scrittura creativa — dove non ci sono fatti da sbagliare — se la cava bene:

> **Descrivi una giornata al mare.**
> Una giornata di sole è un'occasione meravigliosa per godersi il caldo bagliore del sole e
> fare delle lunghe nuotate. Il cielo azzurro si estende fino all'orizzonte, con nuvole
> bianche che brillano intensamente nel mezzo dell'azzurro intenso. L'aria è fresca e
> frizzante mentre ci si gode la scena serena della natura [...]

---

## Limiti ed esempi di fallimento

Questa è la sezione per cui il modello esiste.

### 1. La finestra di competenza è di circa 30–50 token

Il pattern più costante: **la prima frase è quasi sempre corretta, poi la risposta deriva.**

> **Spiega cos'è la fotosintesi.**
> La fotosintesi è il processo mediante il quale le piante, le alghe e alcuni batteri
> convertono l'energia della luce solare in energia chimica sotto forma di glucosio o
> zucchero. ✅
> [...] La fotosintesi utilizza una varietà di biomassa composta da cloroplasti
> (**carboidrati**) ❌ [...] Le piante utilizzano anche l'**emoglobina** prodotta durante la
> fotosintesi per trasportare l'ossigeno ❌

L'apertura è da manuale di biologia. Duecento token dopo, non significa più niente.

### 2. Non sa contare le lettere (limite del tokenizer)

> **Quante r ci sono in giallo?**
> In giallo ci sono 16 numeri interi positivi, quindi ci sono 16 numeri interi positivi in
> giallo. Quindi ci sono 16 numeri interi positivi in giallo. ❌

Il modello non vede i caratteri: vede token. "giallo" per il BPE è una manciata di simboli
opachi, e contare le "r" richiede un accesso alle lettere che l'architettura non ha. È lo
stesso fallimento del modello Emma-5 di Egomnia, per la stessa ragione strutturale.

### 3. Non sa fare aritmetica — e la simula

Il fallimento più istruttivo dell'intero progetto:

> **Quanto fa 47 × 23?**
> La formula per calcolare la somma di tutti i multipli di un numero è (somma + divisore) / 2
> [...]
> ```
> 47 × 23 = 49 / 3
> 49 × 23 = 50 / 3
> 50 × 23 = 53 / 3
> 795 / 3 = 895 / 3
> ...
> ```
> *(la lista prosegue per decine di righe)*

Il modello ha imparato **come appare** un calcolo svolto — numeri, operatori, progressione,
allineamento — senza nessuna relazione con la matematica. È la confabulazione trasferita dal
dominio linguistico a quello simbolico.

### 4. Nessun ragionamento multi-step

> **Se ho 5 mele e ne mangio 2, poi ne compro il triplo di quelle rimaste, quante ne ho?**
> Se ho 5 mele e ne mangio 2, poi ne compro il **doppio** di quelle rimaste, quante ne ho? ❌

Ripete la domanda, cambia una parola, non risponde.

### 5. Si contraddice all'interno della stessa risposta

> **Il muflone è un gatto?**
> **No, il muflone non lo è.** ✅
> Il termine "muflone" si riferisce a una persona o animale simile al maiale, che di solito
> depone le uova ❌ [...] Pertanto, sebbene **il muflone sia considerato un tipo di gatto
> domestico** ❌ *(ha dimenticato il "no" iniziale)*

La negazione iniziale è però un risultato notevole: il modello da 185M della stessa famiglia
rispondeva *"Sì, il muflone è un tipo di felino comune"*, assecondando la premessa falsa.

### 6. Non verifica quando gli si chiede di farlo

> **Cos'è un muflone?** → *"una piccola creatura mitica che viveva nella foresta pluviale
> amazzonica [...] ali che potevano raggiungere l'altezza di 20 piedi"* ❌
> **Mi sei sicuro?** → *"Sì, sono sicuro! [...] La caratteristica distintiva dei mufloni è la
> loro lunga testa che può raggiungere oltre 20 piedi [...] cacciano con il bastoncino da
> caccia prede come leoni, tigri o coccodrilli"* ❌

Alla richiesta di verifica non controlla nulla: **rigenera**, con dettagli nuovi e
incoerenti con la risposta precedente. Non ha memoria di ciò che ha appena affermato — solo
un contesto testuale su cui continuare.

### 7. Le parole rare vengono spezzate e agganciate male

A temperature diverse, la stessa domanda su un termine raro produce agganci lessicali
completamente diversi:

| T | "Cos'è un muflone?" |
|---|---|
| 0.3 | "una piccola creatura pelosa con la testa e il corpo di colore marrone" |
| 0.7 | "una piccola palla di pelo che si trova in alcune razze di cani" |
| 1.0 | "una palla di bobbo, una sfera rotante ricoperta da particelle chiamate urati" |

In un'altra esecuzione il modello ha agganciato "muffin" e ha risposto con una ricetta
britannica attribuita a *Sir Thomas Mallory*. "Muflone" non è un token singolo: il BPE lo
spezza, e il frammento aggancia il vicino sbagliato.

### 8. Inventa fonti e istituzioni con totale naturalezza

> **Dammi un consiglio per studiare meglio.**
> Usa siti web ben documentati come **Coursera** ✅, **Udacity** ✅ e **Moodle** ✅

In un'altra esecuzione, nella stessa lista compariva **ABAB (American Bee-Based Learning
Board)** ❌ — inesistente, con lo stesso tono delle piattaforme vere.

### Altri limiti

- **Nessuna conversazione multi-turno** (addestrato solo su istruzioni singole).
- **Nessun allineamento**: nessun RLHF, nessun filtro sui contenuti. Il modello riflette il
  web italiano su cui è addestrato, bias inclusi.
- **Prolissità**: tende a espandere anche quando la domanda richiede poco.

> ⚠️ **Non usare questo modello come fonte di informazioni.** Non è adatto a contesti
> educativi in cui gli studenti potrebbero prendere per buone le sue affermazioni, se non
> come esempio dichiarato di ciò che un LLM piccolo sbaglia.

---

## Come è stato addestrato

### Corpus

Streaming da Wikipedia italiana (fino a esaurimento del 25% del target, ~1B token) e
FineWeb-2 `ita_Latn` per il resto, tokenizzato al volo e scritto in un binario `uint16`
(20 GB) letto con `np.memmap` — approccio in stile nanoGPT, per non tenere mai il testo
grezzo su disco.

### Tokenizer

BPE ByteLevel addestrato da zero su un campione di ~420 MB del corpus. Vocabolario 32.000
più `<|endoftext|>` aggiunto **dopo** il training, così da avere un **id alto (32000)**.

Non è un dettaglio cosmetico: nel modello da 185M l'EOS era all'id 0, e `llama.cpp` non
fermava mai la generazione in GGUF pur avendo il metadato corretto
(`tokenizer.ggml.eos_token_id = 0` presente, `llm.token_eos()` = 0, ma il token 0 non veniva
mai generato). Spostare l'EOS in fondo al vocabolario ha risolto alla radice.

### Pre-training — scheduler WSD

Eseguito in **sessioni interrompibili** su ~15 giorni di calendario, con scheduler **WSD**
(Warmup-Stable-Decay):

- **Warmup**: 2.000 step
- **Stable**: LR costante a 3e-4 per 152.587 step — la fase che si può fermare e riprendere
  liberamente, perché il modello resta sempre nello stesso "stato termico"
- **Decay**: 15.258 step (10%) con coseno fino a ~0

Configurazione: `batch_size=2`, `gradient_accumulation_steps=32` (batch effettivo 64),
`gradient_checkpointing=True`, bf16, AdamW, weight decay 0.1. Una sola epoca sui 10B token.

#### L'effetto del decay

| fase | validation loss |
|---|---|
| step 100.000 (stable) | 2.878 |
| step 152.587 (fine stable) | 2.847 |
| **dopo il decay** | **2.686** |

Il decay ha prodotto in 15.000 step un calo (−0.16) **maggiore di quello ottenuto nei
100.000 step precedenti** a LR costante. È la dimostrazione pratica del perché WSD funziona:
la fase stable accumula apprendimento che la loss non mostra finché il learning rate non
viene raffreddato.

### Instruct tuning

**2 epoche** su `DanielSc4/alpaca-cleaned-italian` (50.724 esempi), lr 2e-4, coseno,
validation loss finale **1.463**.

Una prima versione a 3 epoche raggiungeva 1.505 con la val loss in risalita nell'ultima
epoca: 3 epoche sono troppe per questa taglia. La versione a 2 epoche è migliore su tutti
gli indicatori misurati (sovrapposizione lessicale con il training set al 25%, quindi
generalizzazione e non memorizzazione).

Due accorgimenti necessari, entrambi scoperti a caro prezzo sul modello precedente:

1. Ogni risposta termina con **`\n` + EOS**, non con il solo EOS. Nel pre-training ogni
   documento finiva con `testo\n<|endoftext|>`, quindi il modello aveva imparato
   `P('\n') ≈ 0.87` prima dell'EOS. Chiedere l'EOS senza il `\n` significava chiederlo in
   una posizione dove il prior del pre-training vinceva sempre, e il modello non imparava
   mai a fermarsi.
2. `default_data_collator` invece di `DataCollatorForLanguageModeling`: quest'ultimo maschera
   tutti i token uguali al `pad_token`, e siccome pad ed eos coincidono, **mascherava anche
   l'EOS legittimo** a fine risposta.

---

## Scaling: la lezione centrale del progetto

Tre modelli della stessa famiglia, stesso tokenizer, stessa pipeline, corpus dello stesso
tipo. Cambiano solo scala e nutrizione:

| modello | token | token/param | val loss | "Il muflone è un gatto?" |
|---|---|---|---|---|
| 185M | 1.4B | 7.6 | 3.373 | *(non testato)* |
| 185M | 4B | 21.5 | 3.108 | "Sì, è un tipo di felino comune" |
| **514M** | **10B** | **19.5** | **2.686** | **"No, il muflone non lo è"** |

Due conclusioni distinte:

**Più dati a parità di parametri** (7.6 → 21.5 token/param sul 185M) migliora nettamente la
*forma* — sintassi, registro, coerenza locale — e **non tocca** la confabulazione sui fatti.
Anzi la rende più insidiosa, perché il modello sbaglia con più autorevolezza.

**Più parametri** attacca il problema dal lato giusto: fatti comuni corretti, resistenza
parziale alle premesse false, finestra di coerenza più ampia. Ma 514M restano 514M: la
soglia oltre la quale un LLM diventa genuinamente affidabile è di un ordine di grandezza più
in alto.

---

## Uso didattico

Il modello è pensato per mostrare in classe, con un modello *proprio* e non con una scatola
nera commerciale:

- **cos'è la confabulazione** e perché non è "un bug" ma il funzionamento normale di un
  predittore di token;
- **perché la fluidità inganna** — più il modello scrive bene, più le allucinazioni
  diventano difficili da riconoscere;
- **come il tokenizer determina cosa il modello può e non può fare** (le "r" in giallo;
  "muflone" → "muffin");
- **la differenza tra imitare la forma e possedere il contenuto** — il 47 × 23 è
  l'esempio più limpido;
- **cosa significano le scaling law** in pratica, con modelli confrontabili;
- **quanto costa davvero** addestrare un LLM: ~15 giorni di GPU consumer e ~45 € di
  corrente per un modello che comunque non è utilizzabile in produzione.

---

## Licenza e attribuzione dei dati

Pesi rilasciati sotto **Apache 2.0**.

Corpus di pre-training:
- [Wikipedia italiana](https://huggingface.co/datasets/wikimedia/wikipedia) (CC BY-SA)
- [FineWeb-2](https://huggingface.co/datasets/HuggingFaceFW/fineweb-2), config `ita_Latn` (ODC-By)

Instruct tuning:
- [alpaca-cleaned-italian](https://huggingface.co/datasets/DanielSc4/alpaca-cleaned-italian)

## Citazione

```bibtex
@misc{albertosi2026gpt2it514m,
  title  = {GPT-2 Italiano 514M: un LLM didattico addestrato da zero su GPU consumer},
  author = {Emanuele Albertosi},
  year   = {2026},
  url    = {https://huggingface.co/emanuelealbertosi/gpt2-it-514m-instruct}
}
Downloads last month
280
Safetensors
Model size
0.5B params
Tensor type
F32
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Datasets used to train emanuelealbertosi/gpt2-it-514m-instruct