Puntata 202
Puntata 202 — Self-attention
Livello: 🧙 Maestro Yoda · Capitolo 19 · Architetture
Query, Key, Value. Sembra un’interrogazione di SQL, ma è il motivo per cui sai parlare con un modello.
Il problema da risolvere
Hai una sequenza di token. Per ognuno vuoi produrre una rappresentazione contestualizzata. “Banca” in “banca del fiume” non è la stessa “banca” di “banca dati”: devi sapere chi le sta attorno.
Soluzione: per ogni token, calcola una media pesata delle rappresentazioni degli altri token, dove i pesi dipendono da quanto sono rilevanti.
Domanda da un milione di dollari: come decidi i pesi? La risposta è il scaled dot-product attention.
Q, K, V: tre proiezioni dello stesso input
Per ogni token con embedding x ∈ R^d calcoli tre vettori:
Q = x · W_Q (query — "cosa sto cercando?")
K = x · W_K (key — "cosa offro io agli altri?")
V = x · W_V (value — "cosa porto effettivamente se mi pesano?")
W_Q, W_K, W_V sono matrici di parametri apprese durante il training. Stesso input x, tre proiezioni diverse, tre ruoli funzionali distinti.
Analogia bibliotecaria: vai in biblioteca con una richiesta (Q), ogni libro ha un’etichetta (K), e se l’etichetta è simile alla richiesta prendi il contenuto del libro (V).
La formula, ufficialmente
Per una sequenza di n token, impili Q, K, V in matrici n × d_k:
Attention(Q, K, V) = softmax( Q K^T / √d_k ) · V
Tre operazioni in fila, leggiamole una alla volta:
Q K^T— matricen × ndi similarità tra ogni query e ogni key. Dot product alto = “questo token mi parla”./ √d_k— divisione per radice della dimensione. Senza, cond_kgrande, i logit diventano enormi e il softmax satura in delta di Dirac (gradient morto). La radice è la scelta che mantiene varianza ≈ 1.softmax(...)— normalizza in distribuzione di probabilità per riga. Ora ogni token ha una distribuzione su “quanto pesa ognuno degli altri”.... · V— combinazione convessa dei value: l’output è la media pesata dei value.
Output: una matrice n × d_k dove ogni riga è la rappresentazione contestualizzata di un token.
Esempio numerico minimale
Sequenza di 3 token, d_k = 2. Supponiamo dopo la proiezione:
Q = [[1, 0], K = [[1, 0], V = [[10, 0],
[0, 1], [0, 1], [ 0,10],
[1, 1]] [1, 1]] [ 5, 5]]
Q K^T:
[[1, 0, 1],
[0, 1, 1],
[1, 1, 2]]
/√2 ≈ /1.414:
[[0.71, 0.00, 0.71],
[0.00, 0.71, 0.71],
[0.71, 0.71, 1.41]]
softmax per riga:
[[0.36, 0.18, 0.46],
[0.18, 0.36, 0.46],
[0.25, 0.25, 0.50]]
Moltiplica per V:
out_1 = 0.36·[10,0] + 0.18·[0,10] + 0.46·[5,5] ≈ [5.9, 4.1]
Il token 1 ha mescolato i propri value con quelli del token 3 (a cui è più simile). Banale, ma è esattamente quello che fa GPT-5 con qualche miliardo di parametri in più.
Self vs cross attention
- Self-attention:
Q, K, Vderivano dalla stessa sequenza. Tipico encoder, e tipico decoder mascherato. - Cross-attention:
Qviene da una sequenza (es. decoder),K, Vda un’altra (es. encoder output). Usato nei traduttori encoder-decoder e nei VLM dove il testo attende su patch di immagine.
Stesso meccanismo, due interpretazioni semantiche diverse.
La maschera (causale)
Nel decoder di GPT vuoi che il token t veda solo i token ≤ t, altrimenti baci ai testi al training time (label leak).
Soluzione: prima del softmax sommi una maschera triangolare che mette -∞ (in pratica -1e9) nelle celle “futuro”:
mask = [[ 0, -∞, -∞],
[ 0, 0, -∞],
[ 0, 0, 0]]
Dopo softmax, le celle -∞ collassano a 0: il futuro è morto, l’autoregressione è salva.
Costo computazionale
- Tempo:
O(n² · d)perQ K^T, dominato dalla matmul. - Memoria:
O(n²)per la matrice di attention.
A n = 4096 token e batch grande, quella n² diventa GB di memoria. Da qui nascono FlashAttention (tiling + ricomputo per non materializzare la matrice intera) e le ricerche su attention lineare/locale/SSM (vedi 205).
Cosa “impara” davvero il modello
Non l’algoritmo (quello è fisso). Impara le matrici W_Q, W_K, W_V (e quelle per ogni head, vedi 208) tali che la similarità Q·K codifichi quello che a quel layer è rilevante.
Studi di interpretability (vedi 284) mostrano che a layer diversi emergono pattern semantici distinti: head che fanno coreferenza (“lui” → “Marco”), head che leggono sintassi, head che traducono caso/numero/genere, head che fanno “induction” (copiare pattern visti prima). Nessuno l’ha scritto a mano: emerge dal training.
Glossario lampo
- Logit — input pre-softmax. Numeri reali qualsiasi.
- Softmax —
e^x_i / Σ e^x_j. Trasforma logit in probabilità. - Dot product — somma elementwise di un prodotto. Misura di allineamento tra vettori.
- Head — istanza separata di Q/K/V con matrici sue (vedi 208 per la versione multi-head).
TEST hands-on
- Apri Python, importa NumPy. Implementa la formula
attention(Q, K, V)in 5 righe. No PyTorch, no librerie: vuoi sentire le dimensioni. - Genera
Q, K, Vrandom8 × 4e calcola l’output. Verifica le shape passo per passo. - Aggiungi la maschera causale: ora il token
tnon deve guardaret+1. Verifica numericamente che le righe della matrice di attention abbiano la coda destra a zero. - Bonus: scarica
nanoGPTdi Karpathy (github.com/karpathy/nanoGPT) e leggimodel.py. Sono ~300 righe. Tutto quello che abbiamo detto è lì dentro.
Take-away
La self-attention non “capisce” niente: calcola similarità tra proiezioni apprese. Tutto il magico comportamento emergente nasce da questa operazione elementare, ripetuta in parallelo su molte teste e su molti layer, con miliardi di parametri ottimizzati su trilioni di token.
➡️ Prossima puntata: Encoder, decoder, encoder-decoder — BERT vs GPT vs T5 spiegati dalla forma.