Quello che state leggendo fa parte di una serie di articoli pensati per spiegare, in modo quanto possibile semplificato, le operazioni matematiche che effettua un Generative Pre-Trained Transformer come ChatGPT, Claude o Gemini.
Il discorso pubblico sui Large Language Model (LLM) si è sviluppato, negli ultimi anni, attorno a un repertorio fisso di metafore: il modello "comprende" il contesto, ha neuroni artificiali che "si attivano come nel cervello", è possibile "addestrarli" per "ragionare" prima di rispondere.
Questa terminologia è funzionale ad una comunicazione divulgativa, ma introduce distorsioni che hanno conseguenze pratiche non trascurabili.
Chi progetta sistemi basati su LLM, mutuando questi frame concettuali, tende a sovrastimare la robustezza del modello, a sottovalutare la probabilità di errori e ad attribuire all'architettura capacità di verifica logica che essa strutturalmente non possiede.
Quest'articolo adotta un approccio diverso: l'analisi meccanicistica, condotta a partire dai byte del file del modello fino all'output generato.
È una sorta di experimentum in corpore vili, come quello fatto dissezionando corpi umani e animali da parte dei medici rinascimentali che imparavano l'anatomia umana, rimasta per secoli oscura per l'impossibilità religiosa di compiere autopsie sui cadaveri.
Il soggetto sul banco autoptico è llama3.1:8b, un modello da 8 miliardi di parametri distribuito nel formato GGUF e accessibile in locale tramite un programma chiamato Ollama che chiunque può scaricare:
curl -fsSL https://ollama.com/install.sh | sh ; per MacOS/Linux
irm https://ollama.com/install.ps1 | iex ; per Windows Powershell
o dal link https://ollama.com/download/.
Una volta ottenuto ollama si può scaricare llama3.1:8b semplicemente con:
ollama run llama3.1:8b
Le dimensioni ridotte di questo modello lo rendono maneggevole su hardware consumer senza sacrificare la rappresentatività architetturale: la struttura transformer che governa Llama 3.1 8B è identica, nelle sue componenti fondamentali, a quella dei modelli da 70 o 405 miliardi di parametri. Cambiano le dimensioni delle matrici, non il meccanismo.
Pur essendo un piccolo modello, llama3.1:8b è efficace, secondo i benchmark è paragonabile alla versione 4 di ChatGPT e alla 3.5 Sonnet di Claude. Purtroppo su un computer general-pourpose senza GPU l'esecuzione risulterà lenta (talvolta sfinente), ma l'obiettivo di quest'esperimento non è certo la velocità, ma sollevare il cofano di una macchina che un po' troppe persone iniziano a considerare magica.
Tramite ollama è possibile scaricare molti altri modelli anche molto migliori di llama3.1:8b come mistral-small3.2:24b, da 24 miliardi di parametri contenuto in un file dal 15Gb, o il gigantesco deepseek-r1:671b, da bel 671 miliardi di parametri in 404GB, che ovviamente non troveranno mai posto in laptop consumer.
Quest'articolo darà una visione d'insieme di tutto il percorso tecnologico che segue un modello LLM da quando gli fornisci una richiesta (prompt) a quando lui emette una risposta. Molti dettagli sono appena accennati e ogni singolo passaggio potrà eventualmente essere meglio dettagliato in un successivo articolo.
Che cosa vogliamo capire
Quando usiamo un modello come ChatGPT o Llama, raramente pensiamo a cosa stia accadendo "dentro". Digitiamo una domanda, il sistema elabora e restituisce una risposta che, secondo qualche nostra intima sensazione, è anche intelligente.
La maggior parte delle persone tratta il modello come una scatola nera: input → scatola misteriosa → output intelligente.
Ma la scatola nera ha una struttura precisa, misurabile, fisica. Comprendere questa struttura, almeno per grandi linee, è essenziale per capire perché il modello fa quello che fa, e soprattutto perché sbaglia quando sbaglia.
Un modello LLM non è un'entità amorfa che "pensa". È un file. Un singolo file binario salvato su un disco, come una foto JPEG o un documento PDF. Se lo copi, lo sposti, lo condividi, il file rimane uguale. Quando gli fai domande e da queste lui inferisce risposte, il file non "impara" non aggiorna i numeri al proprio interno. È ora, e sarà sempre in eterno quello che è. Mentre si usa però esiste uno stato temporaneo (per esempio una cache dei token già visti) e al momento di generarla la risposta può variare da una run all'altra. Non è una macchina deterministica ma statistica. Ma gran parte di ciò che il modello "sa" sta nei pesi del file, però il comportamento dipende anche da tokenizer, prompt e dal runtime che lo esegue.
Cosa contiene questo file? Come è organizzato? Quanto pesa? Quali sono i componenti fondamentali?
Questo articolo risponde a queste domande attraverso l'analisi del formato GGUF, lo standard di fatto per distribuire modelli LLM su hardware consumer. Llama3.1:8b è un modello da 8 miliardi di parametri che entra in circa 4-5 gigabyte.
Ma prima di addentrarci nei dettagli tecnici, è cruciale capire una cosa: sapere cosa contiene un modello è il primo passo per capire i suoi limiti.
Un'avvertenza, se qualcuno ti ha fatto credere che un LLM "comprenda" il linguaggio, probabilmente lo userai in attività che si aspettano coerenza logica e una verifica dei fatti. Le sue risposte ragionevoli ti convinceranno facilmente e inizierai ad affidargli sempre maggiori responsabilità. Fino a quando, quando meno te l'aspetti, qualcosa di veramente notevole distruggerà tutte le tue certezze e riguarderai in prospettiva dovunque hai applicato il sistema senza sapere se veramente puoi fidarti di tutto quello che hai fatto in precedenza. Sarà un risveglio particolarmente brusco.
Sapere che un LLM è solo un modello e cioè una collezione di matrici numeriche applicate sequenzialmente per generare probabilità, forse ti permetterà di sviluppare aspettative più realistiche su ciò che può e non può darti e, forse, lo adotterai in attività o sistemi robusti che prendano in considerazione dei suoi veri vincoli e delle distorsioni che inevitabilmente un LLM introduce.
E ora… smontiamo il modello, pezzo per pezzo.
Un modello è un ricettario gigante
Per capire cosa sia un modello LLM, partiamo da un'analogia semplice e anche un po' scontata: una ricetta di cucina.
Quando leggi una ricetta per fare un dolce, trovi due cose: ingredienti e istruzioni. Gli ingredienti sono i dati di partenza: farina, uova, zucchero, burro. Le istruzioni sono il processo: "mescola gli ingredienti secchi", "aggiungi le uova una per volta", "cuoci a 180°C per 30 minuti". Se segui le istruzioni esattamente, otterrai il dolce.
Un modello LLM funziona sullo stesso principio, ma in modo molto più complesso.
Gli "ingredienti" sono i parametri del modello; i parametri sono numeri, tantissimi numeri. Llama 3.1 8B contiene 8 miliardi di numeri. Ognuno di questi numeri è un coefficiente, un "peso" che modula come il modello elabora il testo. Non sono "conoscenza" nel senso umano, non sono fatti espliciti come "la capitale dell'Italia è Roma". Sono numeri e per lo più sono coefficienti di matrici.
Le "istruzioni" sono invece l'architettura, cioè la sequenza di operazioni matematiche che trasformano l'input in output. Queste istruzioni sono sempre le stesse, identiche per ogni modello della stessa famiglia (tutti i modelli Llama usano le stesse istruzioni). Quello che cambia sono gli 8 miliardi di numeri che riempiono quelle istruzioni.
La ricetta di un dolce sta in una mezza pagina. Un modello Llama 3.1 8B, con i suoi 8 miliardi di parametri, entra in un singolo file binario di circa 4-5 gigabyte, ma può trattare tutti i tipi di domande in qualsiasi lingua.
Come è possibile? Innanzitutto, i parametri sono compressi. Un numero in virgola mobile "normale" — quello che useresti per calcoli scientifici — occupa 4 byte. Se moltiplichi 8 miliardi × 4 byte, ottieni 32 gigabyte. È troppo per un laptop. Nei file GGUF, però, molti pesi sono quantizzati: invece di 16 bit (float16) scendono a pochi bit per parametro (per esempio circa 4 bit in media), più un po' di overhead (scale/metadata e qualche tensore tenuto più preciso). Per questo un 8B finisce tipicamente nell'ordine di 4-5 GB. Ci torniamo.
Ora immagina questa ricetta:
"Prendi l'input. Applica la trasformazione lineare W1. Fai passare il risultato attraverso una funzione di attivazione non-lineare. Applica un'altra trasformazione W2. Normalizza. Applica un modello di calcolo della attenzione mutua tra i token. Ripeti questo blocco 32 volte. Applica una trasformazione finale e infine restituisci una probabilità per ogni parola possibile."
Questa è l'architettura di un LLM.
Non c'è nessuna conoscenza nei numeri di un LLM e non c'è nessuna conoscenza nella sua architettura.
Un LLM non ha alcuna forma di "conoscenza appresa" in senso pratico: è solo una struttura di calcolo che impone vincoli alle operazioni e a come scorre l'informazione nelle sue strutture. Quello che contiene la maggior parte dell'informazione sono W1, W2 e gli altri pesi, cioè quello che forma quegli 8 miliardi di numeri registrati nel file GGUF.
Quando addestri[1] un modello, hai una ricetta fissa. Quello che cambia durante l'addestramento sono questi 8 miliardi di numeri. Un algoritmo di ottimizzazione (come SGD, stochastic gradient descent) li aggiusta ripetutamente finché il modello impara a predire il prossimo token in una sequenza. Dopo milioni di iterazioni, l'algoritmo converge e i numeri si stabilizzano. A quel punto, il file è pronto. Niente più cambia. Quello che hai è una ricetta con ingredienti fissi.
Quando usi il modello in inferenza (cioè quando lo interroghi) non c'è alcun apprendimento dei pesi. Quello che scrivi tu, non cambia il modello. Esiste però uno stato temporaneo di calcolo (per esempio la cache dei token già generati). Inoltre, pur con un modello predeterminato è possibile avere output differenti a meno di non usare sempre lo stesso seme dei numeri casuali. In informatica i numeri casuali sono sempre pseudo-casuali: un computer è una macchina deterministica e non ha accesso diretto a fenomeni fisici casuali, quindi li simula con un algoritmo. Il seme è il valore iniziale di quell'algoritmo: data la stessa sequenza di operazioni, lo stesso seme produce sempre la stessa sequenza di numeri e quindi lo stesso output. Nelle esecuzioni normali il seme viene inizializzato automaticamente da una sorgente di entropia del sistema operativo — ora di sistema, interrupt hardware e simili — il che produce risultati diversi a ogni esecuzione. Se invece si impone un seme fisso, i risultati sono riproducibili anche in presenza di un generatore casuale. In pratica: prendi l'input, applichi la ricetta con gli 8 miliardi di numeri, ottieni una distribuzione di probabilità e con questa campioni dal vocabolario l'output.
Un modello LLM di Llama, in essenza, è un singolo file, portabile, copiabile, redistribuibile con all'interno:
- Una ricetta fissa (l'architettura transformer): le operazioni matematiche che vengono applicate
- 8 miliardi di numeri (i parametri): i coefficienti che definiscono come quelle operazioni si comportano
Dal punto di vista ingegneristico, questo è il nucleo: una collezione di matrici numeriche applicate in sequenza che produce una distribuzione di probabilità sul prossimo token. Non ha un meccanismi di "verifica dei fatti" o di coerenza logica nel senso umano: genera testo seguendo le probabilità apprese.
Ma è una collezione enormemente utile. Gli 8 miliardi di numeri sono stati calibrati su terabyte di testo in modo che le probabilità che emergono da questo processo sono spesso corrette — la parola più probabile è spesso quella giusta. Non sempre. E questo è il punto chiave per capire gli LLM: la robustezza del modello deriva da questa calibrazione probabilistica, non da qualcosa di più profondo.
Sapere tutto questo — sapere che dentro il file ci sono "solo" numeri e un'architettura fissa — è il fondamento per comprendere perché gli LLM sono straordinariamente bravi in certi compiti e completamente inaffidabili in altri.
Il problema della memoria
Ora che sappiamo che un modello è "8 miliardi di numeri più una ricetta", viene naturale la domanda: quanto spazio occupano 8 miliardi di numeri?
La risposta dipende da come rappresenti i numeri. Facciamo i calcoli.
Numeri in virgola mobile: float32 e float16
Durante l'addestramento si usano formati ad alta precisione per rendere stabile l'ottimizzazione (che spesso è eseguita in precisione variabile secondo le necessità del compito). Per fissare un riferimento semplice, consideriamo che sia eseguita in float32 ovvero numeri in virgola mobile a 32 bit (4 byte).
Se memorizzassi Llama 3.1 8B in float32, avresti:
8 miliardi di parametri × 4 byte = 32 gigabyte
32 gigabyte è un numero enorme. La maggior parte dei laptop consumer ha 16 GB di RAM totale. Un modello richiederebbe il doppio della memoria disponibile. Usare un modello del genere sarebbe impraticabile per un computer normale.
Durante l'inferenza (cioè quando usi il modello, non quando lo addestri), non hai bisogno della precisione di float32. Gli errori di arrotondamento non rischiano di accumularsi e causare instabilità numerica come durante l'ottimizzazione iterativa dell'addestramento. Così i ricercatori hanno iniziato a convertire i modelli a float16: numeri in virgola mobile a 16 bit (2 byte).
Trasformando i parametri da float32 a float16, lo spazio si dimezza:
8 miliardi di parametri × 2 byte = 16 gigabyte
16 gigabyte è meglio di 32, ma ancora tanto per un laptop tipico, tenuto conto che ci vorrà altro spazio in RAM per le cache, i programmi, il sistema operativo e così via.
La barriera di portabilità
A questo punto emerge un problema cruciale: la portabilità.
Se il modello Llama 3.1 8B occupa 16 gigabyte, le persone comuni non possono facilmente scaricarlo e usarlo localmente. Il download richiederebbe molto tempo su una connessione domestica. L'hardware necessario sarebbe fuori dalla portata della maggior parte degli utenti.
Questo crea una barriera economica e tecnica. Solo chi ha accesso a cloud computing costoso (AWS, Google Cloud, Azure) o a server dedicati potrebbe usare il modello. Gli altri sono costretti a usare API remote, pagando per ogni query, senza privacy e con dipendenza da un qualche specifico fornitore.
Negli ultimi anni, la comunità open-source ha deciso di rompere questa barriera. L'obiettivo: rendere Llama 3.1 8B — o almeno una versione sufficientemente buona — scaricabile e usabile su hardware consumer.
Per farlo, c'era una sola strada: ridurre la dimensione del file.
Il compromesso: quantizzazione
La soluzione è la quantizzazione post-training: una riduzione controllata della precisione numerica che comprime il modello. Il risultato tipico, rispetto a float16, è una compressione di circa 3-4 volte (dipende dallo schema e dall'overhead).
L'intuizione è semplice: non tutti i parametri meritano la stessa precisione numerica. I parametri che hanno valori grandi sono più sensibili. I parametri con valori piccoli sono meno sensibili ai dettagli numerici. Inoltre, dopo l'addestramento, il modello ha già trovato un equilibrio. Non ha bisogno di aggiustamenti numerici infinitesimali — ha bisogno di restituire buone probabilità, punto.
La quantizzazione riduce i numeri da 16 bit a circa 4 o 5 bit per numero, usando schemi intelligenti di normalizzazione e scala adattiva. Il risultato:
8 miliardi di parametri × 4,5 bit ÷ 8 bit/byte ≈ 4,5 gigabyte
4,5 gigabyte si riescono a scaricare velocemente su una connessione decente. Possono essere caricati nella RAM di macchine con poca memoria.
Questo è il compromesso che rende gli LLM veramente open-source e decentralizzati: ridurre i bit per parametro da 16 a pochi bit e ottenere circa 3 o 4 volte di compressione reale, tenendo conto che esiste overhead e che alcuni tensori possono restare più precisi.
La domanda cruciale è: quanto costa questo compromesso in qualità?
La risposta empirica è spesso: poco, ma non è gratis. Su benchmark come MMLU o HellaSwag si osserva di solito una perdita di alcuni punti percentuali (dipende da schema, setup e task). Su molti compiti pratici la differenza può essere difficile da notare, ma su compiti sensibili la quantizzazione può pesare di più.
Perché? Una possibile intuizione è che, dopo l'addestramento, il modello abbia una certa ridondanza: non tutti i bit in più per ogni parametro cambiano davvero l'output in modo rilevante. È come comprimere un'immagine JPEG: perdi dati, ma spesso l'occhio umano non vede la differenza.
Questo è il fondamento su cui poggia il successo degli LLM open-source moderni. Senza quantizzazione, Llama rimane un giocattolo accademico. Con quantizzazione, diventa uno strumento che qualunque sviluppatore può scaricare, modificare, e distribuire.
Nel prossimo paragrafo, entriamo nel dettaglio di come la quantizzazione funziona e quale schema viene usato per Llama 3.1.
Compressione intelligente (quantizzazione in termini umani)
La quantizzazione è una delle idee più potenti nel machine learning moderno, eppure il concetto è straordinariamente semplice. Iniziamo con un'analogia che probabilmente usi ogni giorno: le foto digitali.
Da RAW a JPG: l'analogia delle immagini
Quando scarichi una foto da internet, generalmente è in formato JPEG. Un'immagine JPEG è compressa con perdita — significa che il file non contiene esattamente tutti i pixel originali, ma una versione approssimata che il cervello umano percepisce come identica.
Un'immagine RAW (il formato cosiddetto "lossless") dello stesso soggetto può pesare molte volte di più (fino a uno o diversi ordini di grandezza). Ma nel tuo browser, o nel tuo telefono, la tua esperienza visiva rimane identica. La compressione JPEG sacrifica informazione 'vera' che il tuo occhio non può distinguere.
La quantizzazione di un modello LLM funziona su esattamente lo stesso principio: sacrifica precisione numerica che al modello non serve distinguere.
Immagina che il modello LLM sia un'immagine gigantesca con 8 miliardi di pixel. Ogni pixel è un parametro, un numero. Durante l'addestramento/inferenza ad alta precisione, spesso si usano rappresentazioni come float16 (16 bit): hanno una precisione limitata ma già sufficiente per molti calcoli.
Quantizzare significa: ridurre ogni pixel a meno bit (4 o 5 bit invece di 16), comprimendo così il file. Proprio come JPEG comprime riducendo le sfumature di colore che l'occhio non percepisce, la quantizzazione riduce la precisione numerica che il modello non percepisce.
Come funziona la quantizzazione: il principio
Il principio di base della quantizzazione è semplice: se tutti i numeri di una matrice variano tra -1 e +1, non hai bisogno di rappresentarli con 16 bit. Puoi rappresentarli con 4 bit e memorizzare a parte il fattore di scala che converte quei 4 bit di nuovo nel range -1, +1. 4 bit vuol dire 24 valori cioè 16 valori, poiché ti interessano valori con il segno andranno da da -8 a +7 (lo zero 'occupa' un numero positivo).
Ecco un esempio concreto. Immagina una matrice di parametri:
Parametri originali (in float16, 16 bit ciascuno):
[ 0.127, -0.043, 0.891, -0.156, 0.034, -0.002, ... ]
Intervallo: da -0.156 a +0.891
La matrice ha migliaia di elementi e tutti variano in un intervallo limitato. Se applichi la quantizzazione a 4 bit:
- Trova il massimo valore assoluto: max = 0.891
- Normalizza tutti i valori dividendo per max: ogni parametro diventa un numero tra -1 e +1
- Converti ogni numero normalizzato in un intero a 4 bit (16 valori possibili). Per semplicità, pensa a 16 livelli equispaziati tra -1 e +1.
- Memorizza a parte il fattore di scala (0.891) con precisione maggiore
Risultato (esempio schematico):
Parametri quantizzati (4 bit ciascuno):
[1, 0, 7, -1, 0, 0, ...] (interi "signed" da -8 a +7)
Fattore di scala: 0.891
Per recuperare un'approssimazione del valore originale:
scale = 0.891 / 7
1 * scale ≈ 0.12728... (vicino a 0.127)
0 * scale = 0 (approssima un valore piccolo come -0.043)
Lo spazio risparmiato è enorme: da 16 bit a 4 bit per parametro, cioè una compressione di quattro volte. Il prezzo è una piccola perdita di precisione nel recupero dei valori originali — ma il modello continua a funzionare bene perché questa perdita è nella zona di imprecisione intrinseca del modello stesso.
K-quant: quantizzazione adattiva per blocchi
Lo schema di quantizzazione usato da Llama 3.1 è in verità più sofisticato. Si chiama k-quant e funziona per blocchi.
Invece di quantizzare l'intera matrice uniformemente, divide la matrice in blocchi piccoli (per esempio decine di parametri per blocco) e applica la quantizzazione separatamente a ogni blocco. Ogni blocco ha il suo fattore di scala indipendente.
Perché? Perché i parametri di una rete neurale non sono uniformemente distribuiti. In una matrice, potresti avere molti parametri piccoli e pochi parametri piuttosto grandi. Se usi un singolo fattore di scala basato sul massimo assoluto, stai "sprecando" la precisione a 4 bit rappresentando i numeri piccoli con troppi dettagli.
Con la quantizzazione per blocchi:
- I parametri piccoli hanno un loro blocco, con un fattore di scala piccolo, e sfruttano bene la precisione a 4 bit
- I parametri grandi hanno blocchi separati, con un fattore di scala più grande, e sfruttano bene il loro range
- Nessuno "spreca" range
Il risultato è una quantizzazione più efficiente.
Q4KM, dove la "M" sta per "medium", usa una variante mista: i layer più sensibili (embedding iniziale, layer di output) sono quantizzati a Q6K (6 bit, più preciso), mentre i layer intermedi usano Q4K (4 bit). Questo equilibrio mantiene la qualità dove serve di più.
Il costo: spesso contenuto
Allora, concretamente, quanto perde il modello?
Su benchmark standardizzati come MMLU (un test di conoscenza generale, organizzato per molte materie) o HellaSwag (un test di ragionamento di senso comune), la quantizzazione introduce tipicamente una perdita misurabile. Quanto? Dipende da setup, prompt, schema di quantizzazione e dal task: spesso parliamo di pochi punti percentuali, ma non è un numero universale.
Ma la maggior parte degli utenti di Llama non usa il sistema per eseguire un test come MMLU, mentre lo usa per:
- rispondere a domande generiche
- scrivere codice
- riassumere documenti
- fare brainstorming creativo
Su molti compiti pratici, la differenza tra float16 e Q4KM può essere difficile da notare. Se hai una domanda e la fai a entrambe le versioni, spesso avresti difficoltà a dire quale sia quale, ma dipende dal tipo di compito.
Spesso l'effetto si vede di più nelle situazioni "al limite", dove il modello è incerto e piccoli cambi numerici possono spostare quale risposta risulti più probabile. Nei casi più "facili" l'impatto può essere meno evidente. Dipende comunque dal compito e dal modo in cui usi il modello.
Portabilità come risultato
Il vincolo di partenza è fisico e non negoziabile. Un modello da 8 miliardi di parametri in float32 occupa 32 GB: il doppio della RAM disponibile sulla maggior parte dei laptop consumer, senza contare lo spazio necessario per il sistema operativo, le cache e i buffer di esecuzione. Usarlo localmente è semplicemente impossibile per la maggior parte degli utenti.
La prima riduzione disponibile è float16: dimezza la dimensione a 16 GB mantenendo una qualità praticamente identica al riferimento float32, perché la precisione aggiuntiva di quest'ultimo non è necessaria in inferenza. Sedici gigabyte sono meglio di trentadue, ma restano ancora fuori dalla portata di un laptop tipico, e non lasciano margine per nulla altro in memoria.
La quantizzazione Q4KM risolve il problema in modo definitivo. Comprimendo la maggior parte dei pesi da 16 bit a circa 4-5 bit, con fattori di scala per blocco che preservano la precisione dove il modello è più sensibile, il file scende a 4-5 GB. È una dimensione scaricabile in tempi ragionevoli su una connessione domestica e caricabile su macchine con 8 GB di RAM. Il costo in qualità esiste — su benchmark standardizzati si osserva una perdita misurabile, variabile per schema e tipo di compito — ma su molti task pratici la differenza è difficile da percepire.
Questa progressione — 32 GB inutilizzabile, 16 GB borderline, 4-5 GB praticabile — non è una curiosità tecnica. È la decisione ingegneristica che ha reso possibile distribuire modelli LLM su hardware consumer e ha aperto la strada al machine learning decentralizzato.
Nel prossimo paragrafo, andiamo dentro il file vero e proprio. Vedremo come questi numeri quantizzati e i metadati sono organizzati fisicamente nel formato GGUF.
Dentro il file: la struttura
Sappiamo già cosa contiene un modello Llama, cioè 8 miliardi di numeri quantizzati, ora vediamo come sono organizzati fisicamente sul disco.
Il formato standard per distribuire modelli LLM moderni è GGUF. È uno standard creato da Georgi Gerganov e adottato dalla comunità open-source come contenitore di fatto per modelli come Llama, Mistral, e altri.
Un file GGUF è un contenitore binario intelligente: contiene tutto ciò che serve per usare il modello in inferenza, e nient'altro. Non hai bisogno di file separati, o file di configurazione, o dipendenze esterne. Basta solo il file GGUF.
Leggere il file come un detectives
Immagina di avere il file llama3.1-8b-q4-km.gguf sul disco. È un file binario e non puoi aprirlo con un editor di testo normale. Ma puoi leggerlo con un hex editor.
Aprire il file con un qualsiasi strumento di lettura binaria rivela una struttura rigidamente sequenziale. Il loader non ha bisogno di scorrere il file alla ricerca di marcatori: sa esattamente dove guardare e cosa trovare.
I primi 4 byte contengono il magic number: i valori esadecimali 0x47, 0x47, 0x55, 0x46, che in ASCII corrispondono ai caratteri G, G, U, F. Non è una stringa decorativa. È un contratto: qualsiasi software che legga questo file verifica prima di tutto questi 4 byte. Se non corrispondono, il file non viene caricato, indipendentemente dall'estensione. Un file GGUF rinominato in .txt rimane un file GGUF; un file corrotto che perde questi 4 byte smette di essere riconoscibile.
Immediatamente dopo, nei byte 5–8, si trova la versione del formato codificata come intero little-endian: il valore 3 indica la terza revisione del formato GGUF. Seguono due interi a 64 bit che il loader legge in sequenza: il numero totale di tensori nel file (292 per Llama 3.1 8B) e il numero di coppie chiave-valore nei metadati (29). Con questi tre valori, versione, conteggio tensori, conteggio metadati, il loader sa già quanto memoria allocare e quante strutture dati inizializzare, ancora prima di leggere un singolo peso.
Nei kilobyte successivi inizia la sezione dei metadati in chiaro: stringhe ASCII leggibili che descrivono l'architettura. La stringa general.architecture seguita da llama appare nei primi 80 byte del file. Non è necessario uno strumento specializzato per leggerla: basta un editor esadecimale qualsiasi, o anche un semplice comando come strings su un sistema Unix.
Il magic number: identità del file
Un magic number è una convenzione informatica per identificare il tipo di file senza dipendere dall'estensione. È come una "firma" invisibile all'interno del file stesso.
Alcuni esempi:
- PDF: inizia con
25 50 44 46(ASCII: "%PDF") - ZIP: inizia con
50 4B(ASCII: "PK") - JPEG: inizia con
FF D8 FF(non è ASCII, ma fisso) - GGUF: inizia con
47 47 55 46(ASCII: "GGUF")
Se rinomini un file GGUF in .txt, il computer potrebbe confondersi sull'estensione. Ma se leggi i primi 4 byte e trovi "GGUF", sai con certezza cosa stai leggendo. Perfino se il file è corrotto, il primo controllo che fai è: "Sono i primi 4 byte GGUF?"
Immediatamente dopo il magic number vengono i metadati strutturali:
- Un numero di versione del formato
- Numero totale di tensori nel file
- Numero di coppie chiave-valore di metadati
Questi valori sono codificati come interi in "little-endian" (il formato che i computer moderni usano nativamente in cui i numeri, scritti in byte successivi sono composti dal più piccolo, quello meno significativo, al più grande, più significativo. L'espressione little-endian rappresenta visivamente la cosa come una fila di piccoli indiani messi in ordine d'altezza, il più basso davanti).
Con questi numeri, il loader sa esattamente di quanti tensori avrà bisogno e quanti metadati dovrà leggere.
Struttura del file GGUF: tre sezioni
Un file GGUF è organizzato in tre sezioni sequenziali:
Sezione 1: Intestazione e Magic Number
I primissimi byte. Identificano il tipo di file e la versione del formato.
Sezione 2: Metadati (key-value pairs)
Subito dopo il magic number, il file contiene un numero fisso di coppie chiave-valore, ognuna tipizzata (può essere stringa, intero, float, array, etc.).
Questi metadati descrivono l'architettura del modello:
general.architecture: "llama"llama.block_count: 32llama.embedding_length: 4096llama.context_length: 131072general.file_type: "Q4KM" (lo schema di quantizzazione)- (e molti altri campi)
Questi metadati sono memorizzati come stringhe ASCII leggibili — se apri il file con un editor esadecimale, puoi leggerli direttamente. Non sono criptati, non sono compressi. Sono qui in forma leggibile di proposito, così il loader può capire come interpretare i tensori che seguono.
Sezione 3: I Tensori (cioè i pesi veri e propri)
Dopo i metadati, il file contiene (in questo esempio) 292 blob binari — uno per ogni tensore. Ogni blob è una sequenza di byte che codifica un tensore (una matrice n-dimensionale) in forma quantizzata.
Un tensore potrebbe essere:
token_embd.weight: [128256, 4096] — embedding inizialeblk.0.attn_q.weight: [4096, 4096] — Query del primo bloccoblk.0.attn_k.weight: [1024, 4096] — Key del primo blocco- (… altri 289 tensori …)
output.weight: [128256, 4096] — layer di output finale
I numeri tra parentesi quadre indicano le dimensioni del tensore: quante righe e quante colonne. Ad esempio, token_embd.weight ha 128.256 righe e 4.096 colonne, per un totale di circa 525 milioni di valori. Approfondiremo cosa significano davvero queste dimensioni nel paragrafo finale di questo articolo.
Ogni tensore è memorizzato in forma quantizzata (Q4KM nel caso di Llama 3.1 8B), con i fattori di scala e metadati di quantizzazione incorporati. Il loader sa come decodificare ogni tensore perché i metadati lo descrivono: dimensioni, tipo di quantizzazione, ordine dei byte.
Come carica il file il modello
Quando avvii Llama (tramite Ollama, llama.cpp, o qualsiasi altro loader):
- Apre il file GGUF
- Legge il magic number per confermare il tipo
- Legge i metadati per scoprire l'architettura (quanti layer, quale quantizzazione, etc.)
- Alloca la memoria according ai metadati
- Legge sequenzialmente i 292 tensori dal file
- Prepara i pesi in memoria (spesso restano quantizzati)
- Dequantizza al volo quando serve (durante i calcoli)
Dopo questi passi, il modello è "caricato": i tensori sono in RAM in una rappresentazione efficiente per l'inferenza.
Perché GGUF è elegante
GGUF è uno standard elegante perché:
- Autocontentuto: Un singolo file contiene architettura, metadati, e pesi. Non c'è proliferazione di file.
- Leggibile: I metadati sono stringhe ASCII che puoi ispezionare con un semplice hex editor. Nessun mistero.
- Portabile: Il file non dipende da versioni specifiche di librerie o framework. Funziona su CPU, GPU, qualsiasi piattaforma supporti il loader GGUF.
- Efficiente: I tensori sono memorizzati in ordine sequenziale, ottimizzati per il caricamento veloce in memoria.
- Versionato: Se cambia il formato (versione 4, 5, etc.), il magic number e i metadati lo indicano chiaramente. Non ci sono rotture silenziose.
Nella sezione successiva, entriamo nei dettagli dei metadati — cosa significano, come influenzano le prestazioni, e come usarli per capire i vincoli del modello.
I metadati spiegati semplicemente
Abbiamo visto che un file GGUF contiene metadati leggibili che descrivono l'architettura del modello. Questi metadati non sono "decorazione" ma determinano come il modello elabora il testo, quanto è potente e quali sono i suoi vincoli.
Vediamo i metadati più importanti di Llama 3.1 8B e cosa significano.
32 blocchi transformer: stanze di elaborazione sequenziale
llama.block_count = 32
Questo significa che Llama 3.1 8B è composto da 32 "blocchi" identici, impilati l'uno sopra l'altro. Ogni blocco elabora il testo in sequenza: l'output di un blocco diventa l'input del successivo.
Un'analogia utile: immagina una catena di montaggio in una fabbrica. Il testo entra al blocco 1, viene trasformato, esce. Il testo trasformato entra al blocco 2, viene trasformato ancora, esce. E così via, per 32 volte.
Ogni blocco contiene due sub-operazioni principali:
- La Self-attention: cioé ogni token "guarda" tutti gli altri token nella sequenza e decide quali sono rilevanti. Concretamente: il token crea una "domanda" (query), la confronta con le "etichette" (chiavi) degli altri token per capire quale token è simile alla sua domanda, e poi estrae l'informazione utile (valore) da quei token simili. Il risultato è un nuovo vettore per il token originale, arricchito con l'informazione contestuale degli altri token.
- La Feed-forward network: È una rete neurale[2] densa, cioè una sequenza di moltiplicazioni di matrici intervallate da funzioni di attivazione non-lineari. Questo strato trasforma il vettore del token in modo più profondo, aggiungendo capacità di modellare trasformazioni complesse che una sola moltiplicazione lineare non può catturare.
Questi due strati lavorano insieme: la self-attention raccoglie informazione dal contesto del token, quindi con quali token un particolare token ha maggiori relazioni, e la feed-forward elabora la rappresentazione risultante per ciascun token in modo indipendente, applicando una trasformazione non lineare che raffina il contenuto semantico accumulato.
32 blocchi significano che questa coppia (self-attention + feed-forward) viene applicata 32 volte in sequenza. Ogni passaggio arricchisce e trasforma il vettore del token.
Il numero 32 è un trade-off:
- Più blocchi = maggiore capacità del modello, ma anche maggiore latenza nell'inferenza (più operazioni, più potenza di calcolo necessaria, più energia spesa, più calore generato, ecc.)
- Meno blocchi = modello meno espressivo, ma più veloce
In Llama 3.1 8B, 32 blocchi sono un punto di equilibrio ragionevole tra qualità e velocità. Modelli più grandi possono usare più blocchi, modelli più piccoli meno: qui l'importante è l'ordine di grandezza (ovvero decine di blocchi), non il numero esatto. Poiché l'effetto di questi blocchi è portare verso l'alto l'astrazione, in linea generale ciò che si riscontra è che i livelli più bassi operano a livello di connessioni sintattiche, quelli più altri a livello semantico o concettuale.
4.096 dimensioni: il linguaggio interno
llama.embedding_length = 4096
Questo è il parametro più importante per capire come Llama "lavora".
Llama non lavora basandosi su parole. Non lavora basandosi su frasi. Lavora con vettori di 4.096 numeri. In ingresso però ha un testo (il prompt), in uscita genererà testo (la risposta), ma tutto quello che c'è in mezzo sono solo numeri.
Ogni token (ogni parola o sottoparola) del testo in ingresso viene convertito in un vettore di 4.096 numeri. Questo vettore rappresenta il significato del token in uno spazio 4.096-dimensionale: uno spazio matematico che è troppo grande per visualizzare, ma perfettamente funzionale per il calcolo.
Un'analogia: immagina di descrivere una persona con una lista di 4.096 attributi: altezza, peso, colore dei capelli, tono di voce, velocità di parola, livello di educazione, tendenza politica, numero di libri letti, … fino a 4.096 attributi. Nessuna persona è descritta da una singola dimensione. Una persona è la somma di queste migliaia di caratteristiche, ognuna su una scala numerica.
Allo stesso modo, ogni token è descritto da 4.096 caratteristiche numeriche. Quando il modello vede la parola "gatto", la converte internamente in un vettore di 4.096 numeri. Quando vede "cane", lo converte in un vettore diverso. Ma i due vettori sono "vicini" nello spazio 4.096-dimensionale se il modello si è adattato in modo che gatto e cane sono concetti correlati in qualche modo.
Durante il processing attraverso i 32 blocchi, questo vettore di 4.096 dimensioni viene trasformato ripetutamente. Dopo il blocco 1, il vettore è leggermente diverso — il modello ha aggiunto più "contesto". Dopo il blocco 2, ancora più diverso. Dopo 32 blocchi, il vettore contiene tutta l'informazione contestuale del modello sul token.
Questo numero, 4.096, è chiamato d_model (model dimension) nella letteratura. È scelto come compromesso:
- 4.096 è grande abbastanza da codificare significati sfumati
- 4.096 è piccolo abbastanza da mantenere i calcoli trattabili
Modelli più grandi usano dimensioni più alte (per esempio 5.120), e modelli molto grandi possono spingersi oltre. Ma il principio è identico: ogni token è un vettore di numeri e il modello elabora solo vettori di numeri.
128K token di contesto: la memoria
llama.context_length = 131072
Llama può elaborare fino a 131.072 token (128K) in una singola query. Questa si chiama context window, la "finestra di contesto" che il modello può vedere. Convertire token in "parole" è solo un ordine di grandezza e dipende dalla lingua e dal testo.
128K token è una lunghezza enorme. Per paragone (stima grossolana, molto variabile):
- Un tweet può essere dell'ordine di decine di token
- Un articolo di notizie dell'ordine di centinaia o migliaia
- Una sezione di un libro dell'ordine di migliaia
- Una tesi può arrivare a decine di migliaia
Con 128K token, Llama può ricevere in input documenti molto lunghi. Questo non garantisce automaticamente che li "usi bene": su contesti lunghi la qualità può degradare e l'attenzione efficace può concentrarsi solo su parti del testo.
Comunque un'ampia finestra di contesto è un vantaggio enorme rispetto ai modelli precedenti. GPT-3 aveva 4K token di context (più di 30 volte meno). I primi modelli Llama ne avevano 2K o 4K.
Il vincolo dimensionale della context window è fisico e quindi difficile da aggirare. In un'implementazione "naïve", l'attenzione ha un costo che cresce come una matrice 128K×128K (cioè circa 17 miliardi di relazioni). Nella pratica, però, molte implementazioni ottimizzate evitano di materializzare tutta la matrice in memoria. In definitiva una context window grande rende un modello potente ma ha un prezzo computazionale molto alto.
32 unità di attenzione, ma solo 8 unità KV
llama.attention.head_count = 32 llama.attention.head_count_kv = 8
Qui arriviamo a un dettaglio architetturale importante parlando di GQA (Grouped-Query Attention).
Nel meccanismo base di self-attention, si hanno 32 unità di attenzione[3] identiche. Ognuna si concentra su una "domanda" indipendente: "quali altri token sono rilevanti per me?" Applicare 32 domande in parallelo permette al modello di catturare 32 diversi tipi di relazioni tra token.
Ma mantenere tutto questo in memoria è costoso. Ogni unità di attenzione mantiene una cache KV (Key-Value, Chiave-Valore) — una memoria temporanea durante l'inferenza dove memorizza le rappresentazioni "chiave" e "valore" di ogni token visto fin a quel momento. Questa cache permette al modello di non ricalcolare da zero le relazioni tra token ad ogni passo. Con 32 unità, la cache KV è grande e consuma molta memoria, specialmente con context window lungo come 128K token.
Il meccanismo GQA è un'ottimizzazione di questo schema: invece di 32 unità di attenzione indipendenti, usa 32 unità "query" ma solo 8 unità "key-value" condivise. Ogni 4 unità query condividono la stessa coppia key-value. Il modello può ancora fare 32 domande parallele, ma il costo in memoria della KV cache scende ad un quarto.
Ricordi il tensore blk.0.attn_k.weight che abbiamo visto in precedenza? Era [1024, 4096] invece di [4096, 4096]. Quella riduzione di dimensionalità è esattamente a causa di GQA con solo 8 unità KV, il tensore è quindi 4 volte più piccolo.
Questo è un esempio di come le specifiche architetturali (il numero di unità) si riflettono direttamente nelle dimensioni fisiche dei tensori salvati nel file.
Gli altri metadati
Ci sono ancora altri metadati come dimensione del vocabolario, frequenza base per il RoPE positional encoding, che è un tipo di quantizzazione, ma i quattro sopra sono quelli fondamentali. Determinano:
- Quante "stanze" di elaborazione (32 blocchi)
- Come la "stanza" parla (4.096 dimensioni)
- Quanta storia il modello ricorda (128K token)
- Come le unità di attenzione sono organizzate (32:8 con GQA)
Tutto il resto, cioè gli 8 miliardi di parametri, i 292 tensori, le formule matematiche, è costruito attorno a questi vincoli architetturali.
Nel prossimo paragrafo, entriamo nel dettaglio di cosa sia un tensore in pratica, e come questi numeri (4.096 dimensioni, 32 unità) si manifestano come matrici che il computer moltiplica milioni di volte al secondo.
I tensori: cosa sono davvero
Abbiamo parlato di "8 miliardi di parametri" astrattamente. Abbiamo detto che vivono nel file GGUF come 292 tensori quantizzati. Ora arriviamo alla domanda concreta: cosa è davvero un tensore?
Un tensore è una tabella di numeri.
Più formalmente, è un array multidimensionale, ovvero la generalizzazione di matrici a qualsiasi numero di dimensioni. Detto semplicemente, è una tabella organizzata in righe e colonne e che potenzialmente ha anche una "profondità", anzi 'molte profondità'.
Analogia: foglio Excel multidimensionale
Immagina un foglio Excel. Ha righe e colonne. Ogni cella contiene un numero. Se il foglio ha 100 righe e 50 colonne, contiene 5.000 numeri. Questo è un tensore bidimensionale — una matrice.
Ora immagina di impilare 20 fogli Excel identici l'uno sull'altro, questa è una pila a tre dimensioni dove ogni "strato" è un foglio. Se ogni foglio ha 100×50 celle, la pila intera contiene 20×100×50 = 100.000 numeri. Questo è un tensore tridimensionale.
È facile immaginare un tensore tridimensionale. Un po' meno immaginarlo a quattro o più dimensioni ma basta dire che un tensore è esattamente questo: numeri organizzati in una struttura multidimensionale regolare, dove ogni numero è accessibile tramite un indice per ogni dimensione.
valore3d = tensore3d[x_1,x_2,x_3]
valore4d = tensore4d[x_1,x_2,x_3,x_4]
...
valoreNd = tensoreNd[x_1,x_2,...,x_N]
I tensori di un blocco transformer
Ricordi che ogni blocco transformer contiene 9 tensori? Vediamo cosa sono.
Ogni blocco apre e chiude con una normalizzazione: attn_norm.weight [4096] precede il meccanismo di attenzione, ffn_norm.weight [4096] precede la rete feed-forward. Sono vettori monodimensionali, piccoli, ma indispensabili per stabilizzare i valori prima di ogni trasformazione.
Il cuore dell'attenzione è composto da quattro matrici. Le prime due, attn_q.weight [4096, 4096] e attn_output.weight [4096, 4096], operano nell'intera dimensione del modello: la prima proietta il vettore del token nella sua "domanda", l'ultima ricombina i risultati delle 32 unità parallele in un unico vettore. Le altre due, attn_k.weight [1024, 4096] e attn_v.weight [1024, 4096], sono ridotte a un quarto: è la conseguenza diretta del GQA con 8 unità KV invece di 32. Meno memoria, stessa capacità di ragionamento contestuale.
La rete feed-forward occupa tre matrici e contiene la parte più grande dei parametri del blocco. ffn_gate.weight [14336, 4096] e ffn_up.weight [14336, 4096] proiettano il vettore verso uno spazio interno più ampio, dove la trasformazione non lineare può catturare relazioni complesse. ffn_down.weight [4096, 14336] riporta il risultato alle 4096 dimensioni originali. L'asimmetria dimensionale — 14336 in entrata, 4096 in uscita — è deliberata: il modello espande per elaborare, poi comprime per trasmettere al blocco successivo.
Nove tensori, ognuno è una matrice di numeri con dimensioni diverse.
Come un tensore trasforma il testo
Scendiamo nel concreto. Immagina che un token entri nel blocco come un vettore di 4.096 numeri. Questo è il suo "significato interno" che il modello ha costruito finora.
Il primo tensore che incontra è attn_norm.weight, un vettore di 4.096 numeri. Questo tensore applica una normalizzazione, cioè una trasformazione che riporta il vettore su una scala più stabile. In pratica dipende dai valori del vettore (non è una divisione per una costante fissa) e include parametri appresi che scalano il risultato.
Dopo, il token incontra attn_q.weight: una matrice [4096, 4096]. Questo è il momento della moltiplicazione matriciale.
Ecco cosa accade matematicamente:
Vettore input: [a_1, a_2, a_3, ..., a_{4096}] (4096 numeri)
Matrice attn_q: [W_{11}, W_{12}, ..., W_1,_{40}_{96}]
[W_{21}, W_{22}, ..., W_2,_{40}_{96}]
[...]
[W_{4096},_{1}, ..., W_{4096},_{40}_{96}] (4096×4096 numeri)
Moltiplicazione:
Output_1 = W_{11}*a_1 + W_{12}*a_2 + ... + W_1,_{40}_{96}*a_{4096}
Output_2 = W_{21}*a_1 + W_{22}*a_2 + ... + W_2,_{40}_{96}*a_{4096}
.FFN..
Output_{4096} = W_{4096},_{1}*a_1 + ... + W_{4096},_{40}_{96}*a_{4096}
Risultato: [Output_1, Output_2, ..., Output_{4096}] (4096 numeri)
Ogni elemento della matrice pesa quanto il valore corrispondente del vettore di input influenza l'output. Se W11 è grande, allora a1 influenza molto Output1. Se W12 è piccolo, allora a2 influenza poco Output1.
I 4096×4096 = 16 milioni di numeri nella matrice attn_q.weight sono esattamente questi W. Sono numeri che il modello ha imparato durante l'addestramento. Non hanno "significato" umano e non rappresentano concetti o regole linguistiche in modo leggibile. Sono semplici coefficienti numerici che, quando applicati ai vettori di input, producono trasformazioni utili per il compito di predire il prossimo token.
Una cascata di trasformazioni
Ogni tensore nel blocco è una di queste trasformazioni:
- Normalizzazione (
attn_norm.weight): ridimensiona il vettore - Trasformazione Query (
attn_q.weight): crea la "domanda" per l'attenzione - Trasformazione Key (
attn_k.weight): crea le "etichette" dei token nel contesto - Trasformazione Value (
attn_v.weight): crea l'informazione da estrarre - Ricombinazione (
attn_output.weight): combina i risultati dell'attenzione - Normalizzazione (
ffn_norm.weight): ridimensiona di nuovo
7-9. Feed-forward network (gate, up, down): trasformazioni non-lineari profonde
Questi 9 tensori, applicati in sequenza, trasformano il vettore del token. Il vettore in ingresso (4096 dimensioni) emerge trasformato. Questo vettore trasformato entra nel blocco successivo, dove subisce altre trasformazioni. Dopo 32 blocchi, il vettore è radicalmente diverso da quello iniziale — ha attraversato circa 288 passaggi strutturati (tra moltiplicazioni di matrici e operazioni come normalizzazioni/attivazioni).
Riassumendo: dal file al calcolo
Mettendo tutto insieme:
- Apri il file GGUF, leggi i metadati
- Carica i 292 tensori (quantizzati in memoria)
- Trasforma l'input in token
- Ogni token entra come vettore di 4096 numeri
- Passa attraverso il blocco 1:
- Applica varie moltiplicazioni matrice-vettore (query, key, value, output, gate, up, down) e operazioni come normalizzazioni e attivazioni
- Esce come vettore diverso di 4096 numeri
- Ripeti per blocchi 2-32
- Passa il vettore finale attraverso la matrice di output ([128256, 4096])
- Il risultato è una distribuzione di probabilità su 128.256 token possibili
- Campiona il prossimo token da questa distribuzione
Tutto questo, dalle moltiplicazioni matriciali al campionamento, può avvenire molto rapidamente su hardware moderno — ma i tempi reali dipendono dalla capacità computazionale della CPU, che sono solitamente molto basse, o dall'utilizzo della GPU, che sono invece molto veloci in queste operazioni, ma anche dalla quantizzazione, dalla lunghezza del contesto e dal runtime.
Questo è quello che significa "modello LLM": una cascata di 32 blocchi di moltiplicazioni matriciali applicate al vettore di un token, dove le matrici sono state sintonizzate durante l'addestramento per produrre buone probabilità di predizione.
Riepilogo schematico: Anatomia completa
In quest'articolo ho disassemblato un modello LLM dalle sue fondamenta fisiche fino ai calcoli che esegue. Ecco uno schema compatto di tutto quello che abbiamo visto.
La struttura del modello: livelli di astrazione

Numeri chiave e trade-off
| Aspetto | Numero | Impatto |
|---|---|---|
| Parametri | 8 miliardi | Capacità di memorizzare pattern |
| Dimensionalità | 4.096 | Capacità di codificare significato |
| Blocchi | 32 | Profondità di elaborazione |
| Context window | 128K token | Lunghezza massima di testo processabile |
| Unità di attenzione | 32 query, 8 KV | Relazioni parallele, efficienza memoria |
| Vocabolario | 128K token | Granularità del linguaggio |
Spazio (float16) |
~16 GB | Difficile su consumer |
| Spazio (Q4KM) | ~4-5 GB | Spesso usabile localmente |
| Perdita (Q4KM) | variabile | Di solito piccola, ma dipende dal task |
Il flusso di dati: da byte a probabilità
Testo in input
↓
[Tokenizzazione] → Sequenza di token ID
↓
[Embedding] → Vettori 4.096-dimensionali
↓
[Blocco 1] → Self-attention + FFN
↓
[Blocco 2] → Self-attention + FFN
↓
... (30 blocchi omessi)
↓
[Blocco 32] → Self-attention + FFN
↓
[Layer output] → Moltiplicazione per [128K, 4096]
↓
[Softmax] → Normalizzazione in probabilità
↓
[Sampling] → Scelta del prossimo token ID
↓
Testo in output
Cosa abbiamo imparato
- Un modello è un file fisico, non magia. Contiene numeri e una ricetta fissa.
- 8 miliardi di parametri = 8 miliardi di coefficienti numerici determinati dall'addestramento.
- La quantizzazione comprime 16 GB a circa 4 o 5 GB riducendo molti pesi da 16 bit a pochi bit per numero (per esempio circa 4 bit in media, con overhead e qualche tensore più preciso). Il costo in qualità è in genere contenuto, ma dipende da schema e task.
- L'architettura (32 blocchi, 4096 dimensioni, 128K context) determina tutto il resto.
- Ogni blocco applica due operazioni: self-attention (raccoglie contesto) e feed-forward (elabora profondamente).
- Ogni operazione è una moltiplicazione matriciale: vettore × matrice = vettore trasformato.
- Dopo 32 blocchi, il vettore del token è stato trasformato radicalmente e contiene il "significato contextuale" completo del token.
- L'output finale è una distribuzione di probabilità su 128.256 token possibili: il modello non "sa" quale token viene dopo, ma solo quanto è probabile che venga estratto un altro tra i token del vocabolario.
Quello che NON è un modello
- NON è intelligenza artificiale consapevole
- NON è un database esplicito di fatti memorizzati
- NON ha "comprensione" nel senso umano
- NON "ragiona" nel senso umano: calcola probabilità
- NON ha un meccanismo nativo di verifica logica/fattuale delle sue risposte
- NON apprende ma adatta i pesi durante l'inferenza
- NON contiene neuroni in senso organico, ma numeri, numeri e solo numeri
- NON ha memoria permanente tra le conversazioni (a meno che il sistema esterno non la aggiunga)
È una funzione matematica addestrata a predire il prossimo token in una sequenza: una semplificazione utile per ragionare sui suoi vincoli.
Quello che un modello PERMETTE di fare
- Predire il prossimo token con sorprendente accuratezza
- Rispondere a domande sulla base di pattern nel testo di addestramento
- Scrivere codice, poesia, prosa coerente
- Elaborare lunghe sequenze (128K token)
- Funzionare interamente localmente, senza internet
- Essere modificato, fine-tuned, adattato a task specifici
Questo articolo finisce qui e, in prima approssimazione può bastare ad avere una conoscenza superficiale di questo strumento.
Ognuno dei passi che sono stati qui solo accennati potrebbe (e dovrebbe) essere spiegato più nel dettaglio perché vedere i dettagli più intimi di ognuno di questi livelli non è solo affascinante, ma dà una idea ancora più chiara di dove si annidano i principali limiti degli LLM e quali sono le distorsioni (ormai praticamente ineliminabili) che si portano dietro.
Nei primi articoli non sarà neppure necessaria una competenza matematica particolarmente avanzata, direi che sarà sufficiente l'algebra lineare da primi anni di una facoltà STEM.
Nel prossimo articolo si vedrà come il testo grezzo, quello che solitamente viene chiamato il prompt, viene convertito nei vettori 4096-dimensionali che il modello usa per le sue elaborazioni numeriche. Il processo è chiamato tokenizzazione ed è il primo passo della pipeline di un transformer.
Note a piè di pagina:
[1] Addestrare è una di quelle metafore cariche di significati errati che viene usata comunemente in questo caso. Continuerò a usare queste metafore per non appesantire la terminologia, ma dovresti piuttosto pensare di usare un termine meno profondo, e che veicoli meno significati organici o antropomorfi, ad esempio in altri articoli ho usato "adattamento".
[2] Si chiamano reti neurali nella letteratura tecnica ma non hanno alcuna relazione con gli effettivi neuroni biologici. Sono solo strutture matematiche i cui coefficienti vengono adattati attraverso un processo iterativo fino a che non si stabilizzano. Questa è una delle principali metafore tossiche nel campo perché sostiene la deriva argomentativa che porta a pensare che una intelligenza artificiale sia prodotta da un cervello elettronico in cui agiscono reti di neuroni proponendo un modello della realtà totalmente abusivo e falso.
[3] Nella letteratura inglese si parla di « attention heads » (teste), che qui rendiamo come unità. Il meccanismo di Multi-Head Attention è stato concepito come un sistema che "guarda" la sequenza da più angolazioni simultaneamente — e in inglese head ha una lunga tradizione metaforica in questo senso. L'analogia di fondo è con una testina di lettura: nei nastri magnetici, nei lettori CD, nei dischi rigidi, la read head è il componente che esegue la lettura su una traccia specifica. In un sistema con più testine parallele — multi-head — ogni testina legge la stessa superficie ma si specializza su un segnale diverso. Il transformer fa esattamente questo: ogni head scorre la stessa sequenza di token ma con matrici WQ, WK, WV diverse, specializzandosi su un tipo di relazione diverso. C'è anche una seconda metafora implicita, più cognitiva: head in inglese può indicare una prospettiva o un fronte di analisi — "on multiple fronts", "multiple heads of inquiry". Un'organizzazione che attacca un problema su più fronti simultanei usa multiple heads. Il paper originale "Attention Is All You Need" (Vaswani et al., 2017) usa proprio questa intuizione: invece di avere un unico meccanismo di attenzione con vettori grandi, conviene avere H meccanismi più piccoli e specializzati che operano in parallelo, ognuno libero di imparare a rilevare relazioni diverse. Quindi head porta con sé entrambe le sfumature: la testina tecnica che legge un segnale specifico, e la prospettiva analitica che si focalizza su un aspetto specifico del problema. In italiano nessuna delle traduzioni cattura entrambe — testina sarebbe tecnicamente la più fedele alla metafora originale, ma nel contesto suona ancora più strana di testa. Una terza linea genealogica, più teorica, passa per la macchina di Turing (Turing, 1936). Nella formalizzazione originale, la head è il componente che scorre un nastro infinito, legge il simbolo corrente e decide un'azione — scrittura, spostamento, cambio di stato. È un meccanismo di accesso sequenziale e posizionale. L'analogia con la testina magnetica non è casuale: entrambe condividono la stessa metafora di un componente attivo che si muove su una superficie passiva. Nel 2014, Graves, Wayne e Danihelka hanno sviluppato la Neural Turing Machine precisamente per rendere differenziabile quel meccanismo: la testina turinghiana diventa un vettore di pesi che distribuisce l'attenzione sull'intera memoria invece di puntare a una sola cella. È in questa transizione — da accesso discreto a accesso ponderato — che nasce il meccanismo di attenzione come operazione apprendibile. I transformer di Vaswani et al. (2017) ereditano quella formalizzazione, moltiplicandola in H istanze parallele. Il termine head nei transformer non è dunque un riferimento esplicito a Turing, ma si inserisce in una catena concettuale in cui la testina turinghiana, la testina magnetica e la prospettiva analitica convergono nello stesso lessico.

Potrebbe interessarti
Due anni di AI: da chatbot curiosi a strumenti che scrivono software
CIA World Factbook: quando l'intelligence divenne Open Source
Le radio delle spie e la cybersecurity moderna: dalle Number Stations alla Direttiva NIS2 e DORA