Note sugli LLM 2.0
Premessa doverosa: non sono un tecnico, non ho una laurea in informatica e non sono un programmatore, nella vita faccio tutt'altro. Sono semplicemente un appassionato che ha voluto approfondire un po' il funzionamento degli LLM, e queste sono le note che mi sono scritto per fissare quello che ho capito durante lo studio.
Riguardano esclusivamente la fase di inferenza, cioè cosa succede quando scriviamo un prompt e il modello genera una risposta. La fase di addestramento non viene trattata.
Il documento è volutamente semplificato e alcuni concetti fondamentali come Residual Connection, Layer Normalization, Positional Encoding (RoPE), Multi-Head Attention e la distinzione tra prefill e decode vengono trattati in modo introduttivo e conto di approfondirli ulteriormente nelle prossime versioni.
Se trovate errori, imprecisioni o semplificazioni eccessive, segnalatemeli pure nei commenti. L'obiettivo di queste note non è insegnare, ma imparare: è un appunto personale che ho deciso di condividere e che verrà aggiornato e corretto nel tempo.
Versione 2.0 — 10 agosto 2026
Un Transformer è una gigantesca pipeline di trasformazioni vettoriali e matriciali. L'intelligenza non nasce da un singolo passaggio, ma dall'applicazione ripetuta dello stesso tipo di trasformazione attraverso decine di layer, ciascuno con parametri propri. Ogni layer modifica progressivamente la rappresentazione dei token fino ad ottenere una comprensione sempre più accurata del contesto.
Dal prompt ai vettori
Dato un prompt da parte dell'utente, il modello — o probabilmente è meglio dire l'inference engine — tramite il tokenizzatore suddivide il prompt in pezzi chiamati token. Ad ogni token viene poi assegnato un ID, e tramite questo ID viene cercato all'interno della Embedding Matrix il suo vettore corrispettivo.
In sostanza la primissima fase del Transformer non fa altro che dividere il prompt in token, assegnare un ID ad ognuno di essi e, tramite questo ID, cercare il corrispettivo nella Embedding Matrix. Va da sé che ogni modello è strettamente legato a questo meccanismo, perché ogni token/ID deve corrispondere in modo univoco al proprio vettore.
Possiamo vedere la Embedding Matrix come un dizionario: il vettore ci dà le caratteristiche di "partenza" del token stesso.
Ripetuto questo passaggio per tutti i token, i vettori ottenuti vengono impilati in un'unica matrice, con una riga per ogni token e tante colonne quante sono le dimensioni dell'embedding. È questa matrice a rappresentare l'intero prompt iniziale.
Dentro un layer: Attention
A questo punto la matrice deve passare al primo layer. Possiamo suddividere per semplicità il layer in due grandi fasi: Attention e MLP. Alla fine del layer otterremo una nuova matrice di Hidden State, che verrà passata al layer successivo.
Ma cerchiamo di essere più precisi. La matrice, che possiamo chiamare hidden state, è come se venisse copiata: una parte di essa prende una strada che la porta verso la normalizzazione e poi passa alla vera e propria Attention.
Nella prima fase i token, tramite Query, Key e Value, cercano di comunicarsi informazioni. Per la natura degli LLM questi possono vedere solo sé stessi ed i token che li precedono: il primo token vedrà soltanto sé stesso, il secondo vedrà sé stesso ed il primo, e così via.
Tramite una Query, che possiamo immaginare come una domanda, il token cerca di capire quanto siano importanti gli altri token per comprendere il contesto. Gli altri token possiedono una Key, che possiamo immaginare come l'etichetta di un libro. Tramite delle operazioni matematiche il token capisce quali Key sono più rilevanti e, utilizzando queste informazioni, combina i rispettivi Value.
Il Value rappresenta l'informazione che quel token può fornire. È doveroso dire che potremmo fare esempi del tipo: nel caso di un animale una caratteristica potrebbe essere "la pelosità". Però questo è vero solo concettualmente: in realtà non possiamo sapere esattamente cosa ogni dimensione di un embedding stia rappresentando.
Ad ogni modo, tramite questo meccanismo che chiamiamo Attention, il Transformer riesce, layer dopo layer, a comprendere il contesto sempre meglio.
Multi-Head Attention
Abbiamo visto che non parliamo più di un singolo Q, K e V per ogni token, ma di Multi-Head Attention. Dallo stesso Hidden State vengono ricavate più Query, Key e Value tramite proiezioni apprese; ciascuna head lavora su rappresentazioni di dimensione più piccola e può osservare relazioni differenti tra gli stessi token.
Abbiamo anche visto successivamente che non è nemmeno sempre così, perché abbiamo sicuramente N Query head, ma possiamo avere, a seconda del tipo di Attention utilizzato, Key e Value head in quantità minore.
Se usiamo MQA possiamo avere addirittura una sola Key head e una sola Value head condivise da tutte le Query head; con GQA, invece, abbiamo N Query head divise in gruppi, e ogni gruppo di Query condivide una Key head e una Value head.
Positional Encoding e RoPE
C'è un altro argomento di cui dobbiamo parlare: il problema è che l'Attention, fino a questo punto, non possiede da sola un'informazione sufficientemente ricca sulla posizione dei token.
A tale scopo esiste il Positional Encoding. Uno dei meccanismi utilizzati nei moderni LLM è RoPE, che interviene sulle Query e sulle Key all'interno dell'Attention e permette al loro confronto di incorporare importanti informazioni sulle relazioni posizionali e sulla distanza relativa tra i vari token.
Residual Connection
Passata la fase dell'Attention, si somma l'hidden state che aveva preso l'altra strada, cioè l'hidden state originale in ingresso al sottoblocco Attention. Questa tecnica prende il nome di Residual Connection.
La seconda metà del layer: MLP
A questo punto si arriva all'MLP. L'hidden state risultante dall'Attention è come se venisse copiato e mantenuto intatto, mentre l'altra copia passa attraverso la normalizzazione e poi l'MLP.
La MLP, a differenza dell'Attention, lavora indipendentemente su ogni token, effettuando ulteriori trasformazioni tramite due trasformazioni lineari separate da una funzione di attivazione. La funzione di attivazione introduce non linearità nel modello — un punto che conto di approfondire più avanti.
Successivamente, l'output prodotto dall'MLP viene sommato all'hidden state prodotto dall'Attention. A questo punto avremo l'hidden state finale del layer, pronto a passare al layer successivo.
Dall'ultimo layer alla parola scelta
Tutti i token attraversano tutti i layer. Al termine dell'ultimo layer avremo quindi una matrice di Hidden State, una riga per ogni token del prompt. Viene applicata un'ultima normalizzazione prima dell'Output Head. A questo punto viene preso solamente l'Hidden State corrispondente all'ultimo token e, tramite l'Output Head, si ottiene un logit per ogni token del vocabolario.
Il logit però non è ancora una probabilità. Viene quindi usata la Softmax, che trasforma tutti i logit in probabilità. Prima della Softmax può essere applicata anche la temperatura, un parametro utile a modificare la distribuzione delle probabilità.
In questa fase abbiamo dunque una probabilità associata ad ogni token del vocabolario.
Successivamente l'inference engine, tramite parametri come Min-P, Top-K e Top-P, senza entrare nel merito di ognuno di essi, screma i token candidati. Questa fase, insieme alla scelta finale del token, costituisce il sampling.
Una volta fatto ciò, l'inference engine utilizza un generatore pseudocasuale inizializzato tramite un valore chiamato seed e, grazie anche all'effetto della temperatura, sceglie il token finale, che non sarà per forza il più probabile ma potrebbe, ad esempio, essere scelto tra i primi più probabili. Questo aumenta la varietà delle risposte di un LLM. (Questa definizione non è perfettamente corretta dal punto di vista tecnico, ma è accettabile per una comprensione iniziale.)
Prefill
Tutta la fase in cui vengono elaborati i token del prompt, attraversando il Transformer e costruendo contestualmente la KV Cache, prende il nome di Prefill.
Il primo nuovo token viene quindi prodotto come risultato del Prefill.
A questo punto abbiamo nella KV Cache le Key e i Value dei token precedenti per ogni layer e per ogni KV head prevista dall'architettura, in modo da non doverli ricalcolare ogni volta. Se il modello utilizza GQA, il numero di KV head è inferiore rispetto al numero di Query head, perché gruppi di Query condividono le stesse Key e Value head.
Decode
La fase successiva di generazione dei token utilizzerà questi K e V già presenti nella KV Cache, evitando di doverli ricalcolare per tutti i token precedenti, e questa fase prende il nome di Decode.
Durante ogni passo di Decode viene elaborato il nuovo token, vengono aggiunti alla KV Cache i suoi nuovi K e V e viene generato il token successivo.
La generazione è quindi autoregressiva: il nuovo token viene aggiunto al contesto e viene utilizzato per generare quello successivo.
Context Window, RAM, VRAM e Memory Bandwidth
Quanti token possiamo mantenere contemporaneamente nel contesto ce lo dice la Context Window.
Questi concetti ci permettono di introdurre un altro importante argomento sulle memorie. Infatti, non è soltanto una questione di quantità di RAM/VRAM, ma anche una questione di banda.
Grandi quantità di dati, come i pesi del modello e la KV Cache, devono essere continuamente lette dalla memoria e portate verso le unità di calcolo.
Su GPU, per esempio, questi dati vengono normalmente letti dalla VRAM verso le unità di calcolo della GPU; su CPU, dalla RAM verso la CPU e le sue cache.
Quindi la memory bandwidth è molto importante, soprattutto durante il Decode.
Commenti
Ancora nessun commento. Puoi essere il primo.