Query, Key, Value
Drie Lineaire Afbeeldingen van Zelfde Invoer
Na embedding (activiteit 4) draagt elke positie een 768-dimensionale vector x_t. Attention begint met het produceren van drie verschillende projecties van x:
Q (query): wat wil deze positie weten?
K (key): wat biedt deze positie aan andere posities?
V (value): welke inhoud levert deze positie als deze wordt bezocht?
Elke projectie komt uit een geleerde gewichtsmatrix:
Q = x · W_Q # W_Q vorm: (d_model, d_k)
K = x · W_K # W_K vorm: (d_model, d_k)
V = x · W_V # W_V vorm: (d_model, d_k)
Drie matrices, allemaal getraind via backpropagation. Een model leert: op deze positie, welke query haalt de nuttigste voorbije context het beste op? Welke key adverteert de inhoud van deze positie goed? Welke waarde wordt geleverd als deze wordt geselecteerd?
Bibliotheekanalogie
Stel je een bibliotheekkaartcatalogus voor. Je loopt naar binnen met een onderwerp in gedachten (je query). Elke kaart somt trefwoorden op (een key). Wanneer je onderwerp overeenkomt met de trefwoorden op een kaart, pak je de inhoud van een boek (een value). Attention doet dit voor elk token parallel: elke positie queryt elke andere positie, rangschikt de afstemming, & haalt een gewogen combinatie van value-vectoren op.
ANDREA-120M Dimensies
| Hoeveelheid | Waarde | Opmerkingen |
|---|---|---|
| d_model | 768 | Vectorgrootte op elke positie |
| n_head | 12 | Parallelle attention heads |
| d_k | 64 | Afmeting per head (= d_model / n_head) |
| T | 1024 | Contextlengte |
d_k = d_model / n_head = 768 / 12 = 64. Elke head ziet een 64-dimensionale slice van een volledige 768-dimensionale ruimte. Activity 6 (grow_a_language_model_multi_head) behandelt een per-head splitsing in detail.
Bereken d_k
Waarom delen door sqrt(d_k)
Een Score Matrix
Zodra Q & K bestaan (elk met vorm (T, d_k)), berekent attention een score matrix:
scores = Q · K^T # vorm: (T, T)
scores[i, j] = hoe sterk de query van positie i uitgelijnd is met de key van positie j. Elk (i, j)-paar krijgt één score: 1024 × 1024 = 1.048.576 scores per attention head per forward pass.
Waarom delen
Dot products van twee willekeurige d-dimensionale eenheidsvectoren hebben een grootte van orde sqrt(d). Zonder schaling groeien de scores mee met d_k:
- d_k = 64: typische dot products van orde 8.
- d_k = 256: typische dot products van orde 16.
- d_k = 4096: typische dot products van orde 64.
Grote scores produceren een piekige softmax (één positie domineert, gradients verdwijnen elders). Training stagneert. Scaling corrigeert een grootte:
scaled_scores = (Q · K^T) / sqrt(d_k)
Voor ANDREA-120M is sqrt(d_k) = sqrt(64) = 8. Elke score wordt gedeeld door 8. Magnitudes blijven ruwweg op unit-schaal ongeacht d_k. Softmax blijft goed gedragen. Gradienten stromen.
Vaswani's Originele Rechtvaardiging
Uit Attention Is All You Need (2017): 'Voor grote waarden van d_k worden de dot products groot in magnitude, waardoor de softmax-functie in regio's wordt geduwd waar deze extreem kleine gradienten heeft.' Een deler sqrt(d_k) werkt dit groeiproces tegen.
Een Codeweergave
In microgpt_cuda.cu verschijnt deze schaling als een letterlijke deling:
scores[i][j] = dot(Q[i], K[j]) * (1.0f / sqrtf(d_k));
Eén float vermenigvuldiging per score. Goedkoop. Cruciaal.
Schaal bij d_model = 4096
Waarom Positie i Positie j > i Niet Kan Zien
Een Beperking Geboren uit Generatie
ANDREA genereert één token tegelijk. Bij inferentie produceert positie 0 een eerste token, dan ziet positie 1 de output van positie 0 & produceert een tweede token, & zo verder. Een model heeft nooit toegang tot toekomstige tokens tijdens generatie.
Training moet dit weerspiegelen. Als tijdens training positie 5 aandacht kan geven aan positie 6, leert een model een shortcut: 'voorspel token 6 door token 6 te lezen'. Bij inferentie verdwijnt die shortcut (token 6 bestaat nog niet). Het gedrag van een model tijdens training-versus-inferentie zou catastrofaal divergeren.
Een Masker
Een causaal masker blokkeert aandacht van elke positie i naar elke positie j > i. Implementatie: stel scaled_scores[i][j] = -oneindig in waar j > i. Na softmax worden die waarden exp(-inf) = 0. Masker zet aandacht naar toekomstige posities netjes op nul.
for i in range(T):
for j in range(T):
if j > i:
scaled_scores[i][j] = -1e9 # effectief -inf
Na softmax (rijgewijs) somt elke rij op tot 1, maar alleen de elementen [0, i] dragen waarschijnlijkheidsmassa. Positie i mixt informatie alleen van eerdere posities.
Een mask visualiseren
Een score-matrix vorm (T, T) met mask toegepast ziet eruit als een lager-driehoekige structuur:
scaled_scores na mask, row-wise softmax:
rij 0: [1.0, 0, 0, 0, ...] # ziet alleen zichzelf
rij 1: [0.4, 0.6, 0, 0, ...] # ziet posities 0, 1
rij 2: [0.2, 0.3, 0.5, 0, ...] # ziet 0, 1, 2
rij 3: [0.1, 0.2, 0.3, 0.4, ...] # ziet 0, 1, 2, 3
...
Strikt lager-driehoekige waarschijnlijkheidsverdeling per rij. Toekomst blijft onzichtbaar.
Waarom een Decoder-Only Transformer Dit Nodig Heeft
Decoder-only modellen zoals ANDREA, GPT en LLaMA hebben allemaal één doel: voorspel het volgende token vanuit het verleden. Een causale masker maakt dat doel parallel trainbaar: elke positie berekent tegelijkertijd zijn eigen volgende-tokenvoorspelling, en geen positie kijkt vooruit door te spieken.
Masker & Smaak
Van Scores naar Output
Softmax: Scores naar Waarschijnlijkheden
Gemaskeerde, geschaalde scores variëren nog steeds over reële getallen. Softmax zet elke rij om in een waarschijnlijkheidsverdeling:
A[i][j] = exp(scaled_scores[i][j]) / sum_k exp(scaled_scores[i][k])
Drie eigenschappen zijn het resultaat:
Explanation of Translation Choices
Key Decisions: - Technical terms preserved: "Softmax", "probability distribution" → "waarschijnlijkheidsverdeling", math notation unchanged - Precise terminology: "Masked, scaled scores" → "Gemaskeerde, geschaalde scores" (standard ML Dutch terms) - Natural flow: "Scores to Probabilities" → "Scores naar Waarschijnlijkheden" - Exact preservation: All code blocks, math formulas, formatting, and placeholders untouched Dutch ML Terminology Used: | English | Dutch | |---------|-------| | probability distribution | waarschijnlijkheidsverdeling | | real numbers | reële getallen | | result | resultaat |- A[i][j] >= 0 voor alle (i, j).
- sum_j A[i][j] = 1 voor elke rij i.
- Grotere ruwe scores produceren grotere waarschijnlijkheden (monotoon).
De waarschijnlijkheidsvector van rij i vertelt een model: hoeveel moet positie i aandacht besteden aan elke vorige positie bij het berekenen van de uitvoer?
Gewogen V Som
Een finale attention-uitvoer voor positie i:
output[i] = sum_j A[i][j] · V[j]
Elke waarde-vector V[j] wordt gewogen door de attention-waarschijnlijkheid A[i][j], en vervolgens opgeteld. De uitvoer van positie i combineert waarde-vectoren van elke voorgaande positie, gewogen naar relevantie.
In matrixvorm, alle posities tegelijk:
Attention(Q, K, V) = softmax(mask(Q · K^T / sqrt(d_k))) · V
Eén regel. Een hele attention-mechanisme. Vaswani et al. schreven die regel in 2017; transformers zijn sindsdien fundamenteel niet veranderd.
Vorm van de Uitvoer per Head
Uitvoer van één attention head: vorm (T, d_k). Voor ANDREA-120M: (1024, 64). Alle 12 heads berekenen parallel; hun uitvoer wordt geconcateneerd tot (1024, 768) & gevoed in een finale lineaire projectie (W_O), daarna naar de MLP van een transformer block.
Activiteit 6 (grow_a_language_model_multi_head) behandelt een multi-head splitsing. Activiteit 7 (grow_a_language_model_transformer_block) behandelt alles wat attention omringt: residuale verbindingen, layer norm, MLP.