🧙 Maestro Yoda Cap. 19 · Architetture

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.

matrice 6×6 di attention weights, righe e colonne etichettate con i token di “Il gatto seduto sul tappeto dormiva”. Celle più scure dove “dormiva” attende su “gatto” e “tappeto”. Sotto, didascalia: “il modello sta decidendo chi guarda chi”

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:

  1. Q K^T — matrice n × n di similarità tra ogni query e ogni key. Dot product alto = “questo token mi parla”.
  2. / √d_k — divisione per radice della dimensione. Senza, con d_k grande, i logit diventano enormi e il softmax satura in delta di Dirac (gradient morto). La radice è la scelta che mantiene varianza ≈ 1.
  3. softmax(...) — normalizza in distribuzione di probabilità per riga. Ora ogni token ha una distribuzione su “quanto pesa ognuno degli altri”.
  4. ... · 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, V derivano dalla stessa sequenza. Tipico encoder, e tipico decoder mascherato.
  • Cross-attention: Q viene da una sequenza (es. decoder), K, V da 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) per Q K^T, dominato dalla matmul.
  • Memoria: O(n²) per la matrice di attention.

A n = 4096 token e batch grande, quella 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.
  • Softmaxe^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

  1. Apri Python, importa NumPy. Implementa la formula attention(Q, K, V) in 5 righe. No PyTorch, no librerie: vuoi sentire le dimensioni.
  2. Genera Q, K, V random 8 × 4 e calcola l’output. Verifica le shape passo per passo.
  3. Aggiungi la maschera causale: ora il token t non deve guardare t+1. Verifica numericamente che le righe della matrice di attention abbiano la coda destra a zero.
  4. Bonus: scarica nanoGPT di Karpathy (github.com/karpathy/nanoGPT) e leggi model.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.