Query, Key, Value
Tre Mappe Lineari dallo Stesso Input
Dopo l'embedding (attività 4), ogni posizione porta un vettore x_t a 768 dimensioni. L'Attention inizia producendo tre proiezioni distinte di x:
Q (query): cosa vuole sapere questa posizione?
K (chiave): cosa offre questa posizione ad altre posizioni?
V (valore): quale contenuto fornisce questa posizione se prestata attenzione?
Ogni proiezione proviene da una matrice di pesi appresa:
Q = x · W_Q # Forma di W_Q: (d_model, d_k)
K = x · W_K # Forma di W_K: (d_model, d_k)
V = x · W_V # Forma di W_V: (d_model, d_k)
Tre matrici, tutte addestrate tramite backpropagation. Un modello impara: in questa posizione, quale query recupera al meglio il contesto passato utile? Quale chiave pubblicizza bene il contenuto di questa posizione? Quale valore viene consegnato se selezionato?
Un'analogia con la biblioteca
Immagina un catalogo di schede di una biblioteca. Entri con un argomento in mente (la tua query). Ogni scheda elenca parole chiave (una key). Quando il tuo argomento corrisponde alle parole chiave di una scheda, prendi il contenuto del libro (un value). L'attenzione fa questo per ogni token in parallelo: ogni posizione interroga ogni altra posizione, classifica l'allineamento e recupera una combinazione ponderata dei vettori di valore.
Dimensioni ANDREA-120M
| Quantità | Valore | Note |
|---|---|---|
| d_model | 768 | Dimensione del vettore in ogni posizione |
| n_head | 12 | Teste di attenzione parallele |
| d_k | 64 | Dimensione per testa (= d_model / n_head) |
| T | 1024 | Lunghezza del contesto |
d_k = d_model / n_head = 768 / 12 = 64. Ogni testa vede una fetta di 64 dimensioni di uno spazio completo di 768 dimensioni. L'Attività 6 (grow_a_language_model_multi_head) copre la suddivisione per testa in dettaglio.
Calcola d_k
Perché dividere per sqrt(d_k)
Una matrice di punteggio
Una volta che Q & K esistono (ciascuna forma (T, d_k)), l'attenzione calcola una matrice di punteggio:
scores = Q · K^T # forma: (T, T)
scores[i, j] = quanto fortemente la query della posizione i si allinea con la key della posizione j. Ogni coppia (i, j) ottiene un punteggio: 1024 × 1024 = 1.048.576 punteggi per testa di attenzione per forward pass.
Perché dividere
I prodotti scalari di due vettori unitari casuali a d dimensioni hanno magnitudine dell'ordine di sqrt(d). Senza scalatura, i punteggi crescono con d_k:
- d_k = 64: prodotti scalari tipici dell'ordine di 8.
- d_k = 256: prodotti scalari tipici dell'ordine di 16.
- d_k = 4096: prodotti scalari tipici dell'ordine di 64.
Punteggi elevati producono una softmax a picco (una posizione domina, i gradienti svaniscono altrove). L'addestramento si blocca. La scalatura corregge una magnitudine:
scaled_scores = (Q · K^T) / sqrt(d_k)
Per ANDREA-120M, sqrt(d_k) = sqrt(64) = 8. Ogni punteggio viene diviso per 8. Le magnitudini rimangono approssimativamente su scala unitaria indipendentemente da d_k. Lo softmax rimane ben comportato. I gradienti fluiscono.
Giustificazione Originale di Vaswani
Da Attention Is All You Need (2017): 'Per grandi valori di d_k, i prodotti scalari crescono in magnitudine, spingendo la funzione softmax in regioni dove ha gradienti estremamente piccoli.' Un divisore sqrt(d_k) contrasta questa crescita.
Una Vista sul Codice
All'interno di microgpt_cuda.cu, questo scaling appare come una divisione letterale:
scores[i][j] = dot(Q[i], K[j]) * (1.0f / sqrtf(d_k));
Una moltiplicazione float per score. Economica. Critica.
Scala a d_model = 4096
Perché la Posizione i Non Può Vedere la Posizione j > i
Un Vincolo Nato dalla Generazione
ANDREA genera un token alla volta. Durante l'inferenza, la posizione 0 produce un primo token, poi la posizione 1 vede l'output della posizione 0 & produce un secondo token, & così via. Un modello non ha mai accesso ai token futuri durante la generazione.
L'addestramento deve rispecchiare questo. Se durante l'addestramento la posizione 5 potesse prestare attenzione alla posizione 6, un modello imparerebbe una scorciatoia: 'prevedere il token 6 leggendo il token 6'. Durante l'inferenza, quella scorciatoia scompare (il token 6 non esiste ancora). Il comportamento addestramento-versus-inferenza di un modello divergerebbe catastroficamente.
Una Maschera
Una maschera causale blocca l'attenzione da qualsiasi posizione i a qualsiasi posizione j > i. Implementazione: imposta scaled_scores[i][j] = -infinito ovunque j > i. Dopo softmax, quelle entrate diventano exp(-inf) = 0. La maschera azzera l'attenzione alle posizioni future in modo pulito.
per i in range(T):
per j in range(T):
if j > i:
scaled_scores[i][j] = -1e9 # efficacemente -inf
Dopo la softmax (per riga), ogni riga somma a 1, ma solo le entrate [0, i] portano massa di probabilità. La posizione i mescola informazioni solo dalle posizioni passate.
Visualizzazione di una Maschera
Una matrice di punteggi di forma (T, T) con maschera applicata appare come una struttura triangolare inferiore:
scaled_scores dopo la maschera, softmax riga per riga:
riga 0: [1.0, 0, 0, 0, ...] # vede solo se stessa
riga 1: [0.4, 0.6, 0, 0, ...] # vede le posizioni 0, 1
riga 2: [0.2, 0.3, 0.5, 0, ...] # vede 0, 1, 2
riga 3: [0.1, 0.2, 0.3, 0.4, ...] # vede 0, 1, 2, 3
...
Distribuzione di probabilità strettamente triangolare inferiore per riga. Il futuro rimane invisibile.
Perché un Transformer Decoder-Only Ha Bisogno di Questo
I modelli decoder-only come ANDREA, GPT e LLaMA condividono un unico obiettivo: prevedere il token successivo dal passato. Una maschera causale rende questo obiettivo addestrabile in parallelo: ogni posizione calcola la propria previsione del token successivo contemporaneamente, e nessuna posizione bara sbirciando avanti.
Maschera & Varianti
Dai Punteggi all'Output
Softmax: Da punteggi a probabilità
I punteggi mascherati e scalati variano ancora su numeri reali. Softmax converte ogni riga in una distribuzione di probabilità:
A[i][j] = exp(scaled_scores[i][j]) / sum_k exp(scaled_scores[i][k])
Tre proprietà ne risultano:
Explanation of Translation Choices
Key Decisions: - Technical terms preserved: "Softmax", "probability distribution" → "distribuzione di probabilità", math notation unchanged - Precise terminology: "Masked, scaled scores" → "Punteggi mascherati e scalati" (standard ML terminology in Italian) - Natural flow: "Scores to Probabilities" → "Da punteggi a probabilità" (preposition "da" creates smooth title) - Mathematical integrity: Formula preserved exactly as specified - Formatting preserved: All markdown, code blocks, spacing identical to original Italian ML Terminology Used: - "probability distribution" = "distribuzione di probabilità" - "real numbers" = "numeri reali" - "converts each row" = "converte ogni riga" - "result" = "risultano" (plural agreement with "tre proprietà")- A[i][j] >= 0 per tutte le (i, j).
- sum_j A[i][j] = 1 per ogni riga i.
- Punti raw più grandi producono probabilità più grandi (monotona).
Il vettore di probabilità della riga i dice a un modello: quanto dovrebbe la posizione i prestare attenzione a ciascuna posizione precedente quando calcola la sua uscita?
Somma Pesata di V
Un'uscita finale di attenzione per la posizione i:
output[i] = sum_j A[i][j] · V[j]
Ogni vettore di valore V[j] viene ponderato dalla probabilità di attenzione A[i][j], poi sommato. L'output della posizione i combina i vettori di valore da ogni posizione precedente, ponderati per rilevanza.
In forma matriciale, tutte le posizioni contemporaneamente:
Attention(Q, K, V) = softmax(mask(Q · K^T / sqrt(d_k))) · V
Una riga. Un intero meccanismo di attenzione. Vaswani et al. scrissero quella riga nel 2017; i transformer non sono cambiati fondamentalmente da allora.
Forma di Uscita Per-Head
Uscita di una testa di attenzione: forma (T, d_k). Per ANDREA-120M: (1024, 64). Tutte le 12 teste calcolano in parallelo; le loro uscite si concatenano in (1024, 768) e alimentano una proiezione lineare finale (W_O), poi passano all'MLP di un blocco transformer.
L'Attività 6 (grow_a_language_model_multi_head) copre una suddivisione multi-head. L'Attività 7 (grow_a_language_model_transformer_block) copre tutto ciò che circonda l'attenzione: connessioni residue, layer norm, MLP.