Scaled dot-product and multi-head attention, fully derived
Q, K, V, the √d_k argument, masking and heads — every number computed by hand
This is the single most important equation in modern AI. Everything a language model "knows about context" flows through . Deriving it — including why the scaling factor is , how masks make a model autoregressive, and why heads cost parameters and time — is what lets you read any transformer codebase (7.8 Build GPT from scratch (nanoGPT walkthrough)), reason about inference cost (7.10 Efficient inference: KV cache, GQA, FlashAttention, speculative decoding, quantization) and understand interpretability results about attention heads (8.11 Interpretability: what is happening inside).
You'll be able to
- Derive scaled dot-product attention from the soft-lookup idea and compute it by hand for a 3-token example
- Prove that the dot product of unit-variance queries and keys has variance d_k and explain why that motivates the √d_k scaling
- Implement masking, multi-head attention and the output projection in PyTorch and count their parameters
- State the time and memory complexity of attention and explain which term dominates for long contexts
Key ideas
- Attention(Q,K,V) = softmax(QKᵀ/√d_k)V: scores measure query–key alignment, softmax turns them into weights, and the output is a convex combination of values.
- If query and key components have unit variance, q·k has variance d_k; dividing by √d_k keeps the softmax out of its saturated, zero-gradient regime.
- Masking sets scores to −∞ before the softmax; the causal mask (j > i) makes a model autoregressive and allows parallel training.
Show 4 more key ideas
- Multi-head attention runs h heads on d/h-dimensional projections and concatenates them through W^O; total parameters ≈ 4d² per layer.
- Time is O(n²d) for scores and O(nd²) for projections; attention matrices need O(h·n²) memory — the long-context bottleneck.
- Attention outputs lie inside the convex hull of the value vectors: attention mixes, it never creates new directions on its own.
- In code, attention is two matrix products and a softmax; the heads are a reshape, not a loop.
Suggested path through this lesson
Queries, keys and values
Start from the soft dictionary lookup of 6.7 Attention is born. A dictionary maps a key to a value; a soft dictionary returns a weighted blend of all values, with weights given by how well the query matches each key. Attention builds the query, key and value for every token from the same input vectors with three learned projections:
where holds the token vectors, and . Row of is "what token is looking for", row of is "what token offers", and row of is "what token will contribute if chosen".
Scaled dot-product attention
Read it in three steps. Scores: is ; entry measures alignment (1.1 Vectors, dot products and norms). Weights: a softmax over each row turns scores into a probability distribution, , so every output is a convex combination of values. Output: ; row is , a blend of other tokens' values chosen by token 's query.
Why divide by
Suppose the components of and are independent with mean and variance (roughly true after normalization and sensible initialization, 4.6 Initialization and normalization). Then
The raw scores have standard deviation ; with that is , and a softmax of inputs spread over tens of units is nearly one-hot, where its gradient (recall ) is almost zero (4.2 Activation functions). Dividing by restores unit variance and keeps the softmax in its responsive regime.
Masking
To forbid token from seeing token , set (in code, a large negative number) before the softmax so that exactly and the remaining weights renormalize. The causal mask zeroes all — this is what makes a decoder autoregressive (7.6 Decoder-only: GPT and autoregressive generation) and lets one forward pass train every next-token prediction in parallel. A padding mask hides filler tokens in batched sequences.
A complete worked example
Three tokens, , with
Scores (row = query against all keys), then scaled by :
Row-wise softmax (using , , ):
Output : row 1 is ; row 2 is ; row 3, whose query matches itself best, is . Every output lies inside the triangle spanned by the three value vectors — attention can only mix, never invent.
With a causal mask, token 1 may only see itself (), token 2 sees tokens 1–2 (weights and , giving ), and token 3 is unchanged. The attention-calc demo below lets you edit these matrices and watch every number update.
Math check. Each row of sums to 1: . ✓ And the row-1 output is the midpoint of and nudged toward — exactly what weights should produce.
Multi-head attention
One softmax produces one weighting per query; a token cannot simultaneously attend "to the subject" and "to the previous adjective". Multi-head attention runs attentions in parallel on lower-dimensional projections and concatenates:
with (GPT-2: , , ). Each head sees a different 64-dimensional subspace, so heads specialize: previous-token heads, heads that track syntax, and the induction heads that copy patterns and underlie in-context learning (8.11 Interpretability: what is happening inside).
Parameters. The three projections for all heads together are , and adds : about per layer (biases are negligible). For that is M.
Cost. Computing for one head is an by product: , so all heads cost time and memory for the attention matrices, plus for the projections. Doubling the context quadruples attention compute — the motivation for FlashAttention and the KV cache (7.10 Efficient inference: KV cache, GQA, FlashAttention, speculative decoding, quantization) and for sub-quadratic alternatives (7.12 Beyond vanilla attention: state space models, long context and hybrids).
Minimal implementation
import torch, torch.nn.functional as F
def attention(Q, K, V, causal=False):
d_k = Q.size(-1)
scores = Q @ K.transpose(-2, -1) / d_k ** 0.5 # (..., n, n)
if causal:
n = scores.size(-1)
future = torch.triu(torch.ones(n, n, dtype=torch.bool), diagonal=1)
scores = scores.masked_fill(future, float('-inf'))
A = F.softmax(scores, dim=-1)
return A @ V, A
class MultiHeadAttention(torch.nn.Module):
def __init__(self, d_model, n_heads):
super().__init__()
self.h, self.d_k = n_heads, d_model // n_heads
self.qkv = torch.nn.Linear(d_model, 3 * d_model, bias=False)
self.out = torch.nn.Linear(d_model, d_model, bias=False)
def forward(self, x, causal=True): # x: (B, n, d)
B, n, d = x.shape
q, k, v = self.qkv(x).split(d, dim=-1)
heads = lambda t: t.view(B, n, self.h, self.d_k).transpose(1, 2) # (B, h, n, d_k)
z, _ = attention(heads(q), heads(k), heads(v), causal)
return self.out(z.transpose(1, 2).reshape(B, n, d))
Run attention on the matrices above (as float tensors) and you will reproduce the numbers to three decimals. Note that there is nothing recurrent and nothing convolutional: every operation is a matrix product or a softmax, which is why the whole layer parallelizes across positions.
Where this shows up later
- Positions must be injected because this layer is permutation-equivariant — 7.3 Positional information: sinusoidal, learned, RoPE, ALiBi.
- The attention sub-layer sits inside a residual block with LayerNorm and an FFN — 7.4 Inside the block: residuals, LayerNorm, FFN and embeddings; its parameters are a third of each layer.
- The causal mask defines decoder-only models (7.6 Decoder-only: GPT and autoregressive generation); cross-attention reuses this exact function with from an encoder (7.7 Encoder–decoder: T5, BART and choosing an architecture).
- Memory and compute of the matrix drive FlashAttention, GQA and the KV cache (7.10 Efficient inference: KV cache, GQA, FlashAttention, speculative decoding, quantization); attention heads are the objects interpretability studies (8.11 Interpretability: what is happening inside).
Common pitfalls (4)
- Applying the mask after the softmax (zeroing weights) instead of before it — the remaining weights would no longer sum to one.
- Scaling by d_k instead of √d_k, or forgetting the scale entirely with large d_k, which makes training sluggish because the softmax saturates.
- Believing heads see different tokens — all heads see all tokens; they see different subspaces of each token's vector.
- Counting multi-head parameters as h × 4d² — the heads share the d-dimensional budget, so the total stays ≈ 4d² regardless of h.
Recap: all 7 key points
- Attention(Q,K,V) = softmax(QKᵀ/√d_k)V: scores measure query–key alignment, softmax turns them into weights, and the output is a convex combination of values.
- If query and key components have unit variance, q·k has variance d_k; dividing by √d_k keeps the softmax out of its saturated, zero-gradient regime.
- Masking sets scores to −∞ before the softmax; the causal mask (j > i) makes a model autoregressive and allows parallel training.
- Multi-head attention runs h heads on d/h-dimensional projections and concatenates them through W^O; total parameters ≈ 4d² per layer.
- Time is O(n²d) for scores and O(nd²) for projections; attention matrices need O(h·n²) memory — the long-context bottleneck.
- Attention outputs lie inside the convex hull of the value vectors: attention mixes, it never creates new directions on its own.
- In code, attention is two matrix products and a softmax; the heads are a reshape, not a loop.
Watch
The clearest animation of Q, K, V and the attention pattern; watch first.
Read
Try it live
Interactive demo
runs in your browserExercises
- By hand: a new worked example
Change the value matrix to and recompute the full attention output for the three queries, then with the causal mask. Expected: row sums of still equal 1 and every output lies inside the triangle with vertices , , .
- NumPy vs PyTorch
Implement
attentionin NumPy (with a numerically stable softmax that subtracts the row max) and check it against the PyTorch snippet on random matrices. Expected: maximum absolute difference below . - Experiment: what the √d_k does
Sample 10,000 pairs of standard-normal vectors for , compute the dot products, and report their standard deviation before and after scaling. Then feed a row of 64 such scores through a softmax with and without scaling and compute its entropy. Expected: standard deviations ≈ unscaled and ≈ 1 scaled; the unscaled softmax at is nearly one-hot (entropy close to 0 nats).
- Look inside GPT-2
With Hugging Face
transformers, loadgpt2withoutput_attentions=True, run a sentence such as "The cat sat on the mat because it was tired", and plot the 12 heads of layer 5 as heatmaps. Expected: at least one head attends mostly to the previous token and one spreads attention to the first token (an "attention sink").
Quiz
Flashcards
These cards are now in your spaced-repetition queue. Review all due cards →