Attention: queries, keys, values and masks
Before this lesson: lesson 03 (backpropagation), lesson 05 (LayerNorm and residual connections) and lesson 08 (tokens, embeddings and what attention replaced). The softmax Jacobian is also derived in linear-algebra lesson 16. You need the rule "the variance of a sum of independent terms is the sum of their variances" (probability-and-statistics track). Time: about 70 minutes, plus the notebook.
Why this matters #
A recurrent network (lesson 08) carries information from token 1 to token 1000 through 999 sequential steps. Attention lets every token read from every other token in one step, and all tokens are processed in parallel on a GPU. This one operation is the core of large language models (GPT, LLaMA), of BERT, of the vision transformer (lesson 11), of speech models such as Whisper, of AlphaFold 2, and of most weather and climate foundation models. Interviews test it at three levels: write it from a blank file, derive the scaling, and count its cost. The cost grows with the square of the number of tokens, which is the main limit of long contexts.
1. Predict first #
2. The picture #
One query: a soft dictionary lookup #
A Python dictionary {"cat": v1, "dog": v2} does a hard lookup: the query "cat" matches
exactly one key and returns its value. Attention does a soft lookup. There are items.
Item has a key and a value . A
query asks for something. Attention does three steps:
- Score every item: . A large dot product means "this key matches the query".
- Weights: , the softmax of the scores. So and .
- Output: the weighted average .
The output is a blend of all values, with more weight on the values whose keys match the query. Because and , the output is a convex combination: it always lies inside the region spanned by the values. A hard lookup is the limit where one weight is 1 and the others are 0.
Drag the yellow query in the left plane. The scores are dot products with the violet keys; the table shows them and their softmax weights. In the right plane the blue output is the weighted average of the grey values; the rings show the weights. Try a short query and a long query: a longer query (or a smaller temperature ) makes the weights nearly one-hot and the output jumps to one value. Notice that the keys decide the weights, but the values decide where the output lands.
Three items have scores and values , , . What is the attention output?
, so the weights are and . Returning only is a hard lookup; attention blends all values. would need equal weights.
Self-attention: every token is a query #
A sequence of tokens is a matrix : one row per token, is the model width. (We use the row convention of PyTorch, as in the whole track.) In self-attention, the queries, keys and values all come from the same sequence. Three learned matrices and give
Row of is the query of token ; row of and of are the key and the value of token . Now every token does the lookup above at the same time:
Entry of is , the score of query for key . Row of holds the attention weights of token : it is positive and sums to 1. Row of is , the output of token .
The mixing weights are computed from the input. A convolution (lesson 07) also mixes neighbouring tokens, but with weights that are fixed after training and that depend only on the offset. Attention decides for every input again which tokens to mix.
The whole matrix of one head, for six tokens and head size 2 (this figure is shared with linear-algebra lesson 15, which writes for and uses the column convention). Left: the scaled scores; right: the weights . Click a row to pick a query; its output is drawn as the weighted average of the values. Move the scale to 0: every row becomes uniform. Turn on the causal mask: the upper triangle of becomes 0.
One attention head with tokens, model width and head size . What is the shape of , and of the head output ?
and are (one row per token), so is : one score for every pair of tokens. is also , and times () is : one output row per token. would be , which mixes features, not tokens.
3. Definitions and main results #
Scaled dot-product attention #
Definition. For queries , keys , values and a mask :
where the softmax acts on each row separately. With no mask, .
Cost. needs floating-point operations (FLOPs; one multiply and one add per term), needs , and needs numbers of memory. For self-attention : the cost grows like .
Why divide by #
The figure first, then the proof. The figure draws one query and 16 keys with random entries and shows the softmax weights, with and without the scaling.
Move the head size and switch the scaling. Top: the 16 softmax weights of one query, sorted. Bottom: the median size of the softmax Jacobian over many random draws, with the scaling and without it; the yellow dot is the current draw. Without scaling, a larger gives larger logits, one weight close to 1, and a Jacobian close to 0: almost no gradient reaches the queries and keys. With scaling, the picture is the same for every .
Theorem (variance of the dot product). Let be independent, with independent entries of mean 0 and variance 1. Then and .
Proof.
- . By independence, . So .
- .
- The terms for different are independent, so their variances add: .
So has standard deviation , and has variance 1 for every head size.
Theorem (softmax Jacobian). For , , that is,
Proof.
- with , and .
- Quotient rule: .
is symmetric and positive semidefinite, and : adding the same constant to all logits changes nothing. Backpropagation needs only the product with the upstream gradient :
which costs instead of .
Saturation. Let the largest weight be . Then every entry of is at most in absolute value. Why: every entry has a factor or with an index different from , and that factor is at most ; the one exception is . So a nearly one-hot softmax passes almost no gradient back to its logits, and from there to and .
Putting the two together. Without the scaling, logits have standard deviation : 8 for . The difference of two logits has standard deviation , and . The softmax then puts almost all weight on one or a few keys. In a simulation with 128 keys and , the median of the largest weight is about 0.8 and about 95% of the keys get a weight below ; with the largest weight is about 0.98. The effect grows with . Dividing by keeps the logits of order 1 at initialization, for every head size.
Backward pass through attention, and the scaling used by μP
With , (rows), and the upstream gradient :
The line for is the softmax VJP above, applied to each row. FlashAttention uses the same formulas, computed tile by tile. The notebook checks them against autograd.
The variance argument assumes that and are independent. If they are strongly correlated, for example , then grows like , not like . The maximal-update parameterization (μP, Yang et al., Tensor Programs V) therefore scales the logits by , so that the right learning rate transfers across model widths.
The entries of are independent with mean 0 and variance 1. What is the variance of , and of ?
Each term has variance , and 64 independent terms give variance 64 (standard deviation 8). Dividing by divides the variance by 64, so the scaled logits have variance 1. "8" is the standard deviation, not the variance.
Masks #
A mask is added to the logits before the softmax. An entry gives : query gives no weight to key .
- Causal mask (decoders, language models): query may attend to key only if . So for and above the diagonal. Then token never reads a future token, and training on all positions at once gives the same outputs as generating one token at a time.
- Padding mask: sequences in a batch have different lengths, so short ones are filled with padding tokens. A padded key gets in every row.
- Masks combine by adding them (for boolean "may attend" masks: a logical AND).
- Add the mask before the softmax. Then the masked weights are exactly 0, and the visible weights of each row still sum to 1.
- A fully masked row has every logit equal to . A direct implementation computes and returns NaN, and the NaN spreads through the whole network. Some library kernels return zeros for such rows instead; do not rely on either. Make sure every query sees at least one key.
Choose a mask and look at the weights on the right. Masked cells are magenta; the column "sum" is the sum of each row. Try "causal + padding" with left padding: rows 1 and 2 have no visible key and become NaN. Then choose "after softmax (bug)": the masked weights are set to 0 after the softmax, and the rows no longer sum to 1.
Conventions differ between libraries. In F.scaled_dot_product_attention, a boolean mask
entry True means "may attend". In nn.MultiheadAttention, True in attn_mask or
key_padding_mask means "blocked". Read the documentation of the function you call.
A student implements causal attention by computing first and then setting the weights above the diagonal to 0. What goes wrong?
The softmax denominator still contains the future keys. So the visible weights are too small (their sum is below 1), and they change when a future token changes: information from the future leaks into the past. Adding before the softmax removes the future keys from the denominator too.
Multi-head attention #
One head gives each query one probability distribution over the keys. Multi-head attention runs heads in parallel, each with its own projections, so that a token can attend to several places at once (for example, to the previous token and to a related word far away).
Definition. Let divide , and let be the head size. Take and bias vectors . Head uses the columns to of the first three matrices: , and the same entries of the biases. Then
Here puts the head outputs side by side (), and mixes the heads.
- Implementation: no loop over heads. One matrix product gives of shape . View it as , then move the head axis to the front: . Batched matrix products then compute all heads at once. With a batch axis: .
- Parameters: give , the four biases give : in total , for every . Heads split the width; they do not add parameters.
- Cost for tokens: the four projections need FLOPs; the scores and the weighted sums of all heads need . The two are equal at ; for longer sequences the term dominates. Lesson 11 adds the MLP of the transformer block to this count.
- Low rank: the score of head is , a bilinear form whose matrix has rank at most (linear-algebra lesson 15).
Step through the reshapes. Each cell is one number of ; its text is the token it came from, and its colour is the head it belongs to (in this figure, colours mark heads). With "view, then transpose" every head block holds all 4 tokens and only its own features. Switch to "reshape directly (bug)" at step 3: the shapes are the same, but each block now mixes tokens and features. No error is raised, and the model is silently wrong.
Multi-head attention with , biases, and heads. How many parameters does it have? And with ?
are for every ; the heads only cut the columns of into blocks of . So the count is for any that divides . The last option forgets that each head's projection is , not .
Cross-attention #
In cross-attention, the queries come from one sequence and the keys and values from another: with rows, and , from a second sequence with rows (for example, the output of an encoder). Then is , and the output has one row per query. Cross-attention uses the padding mask of , but never a causal mask: the whole second sequence is known. It is the standard way to condition a network on other information, such as a translation model reading the source sentence, or an image generator reading a text prompt.
Permutation equivariance #
A permutation matrix reorders the rows of a matrix: for a permutation of . It is orthogonal: .
Theorem (self-attention is permutation-equivariant). Without a mask, and with , , : for every permutation matrix .
Proof.
- The input gives , and in the same way and .
- The scores are . In entries: . Rows and columns are reordered in the same way.
- The row-wise softmax commutes with this reordering: , because runs over all indices. So .
- , because .
The same holds for multi-head attention (each head is equivariant, and the concatenation and act on each token separately), and for a whole transformer encoder, because the MLP, LayerNorm and residual connections also act on each token separately. Without positions, the model sees a set of tokens, not a sequence: "dog bites man" and "man bites dog" give the same three output vectors, only in a different order. Lesson 10 adds positions.
A causal mask breaks the symmetry. The masked scores of the input are , and for . Query sees exactly keys, so a decoder can infer positions even without a positional encoding.
Answers to section 1. (1) has standard deviation , and the difference of two such logits has standard deviation . The softmax is then nearly one-hot (almost all weight on one or a few keys), most entries of its Jacobian are close to 0, and the gradients to and become very small. Dividing by fixes this. (2) The outputs are shuffled in the same way and are otherwise unchanged (permutation equivariance). A pooled output, such as the mean over the tokens, does not change at all.
4. Worked example #
One head, three tokens. Take , and , so :
No mask. Row 1: and . The sum is , so the weights are . The other rows in the same way:
Check row 1 of : .
Causal mask. Row 1 sees only key 1: weights , output . Row 2 sees keys 1 and 2, with logits : , , weights , output . Row 3 sees all keys and does not change: . The output of token 1 no longer depends on tokens 2 and 3.
Jacobian of row 2 (causal). , so
using .
Saturation in numbers. Logits give and . The same logits times 8, which is what gives without the scaling, are : and . The largest Jacobian entry is about 660 times smaller.
Counting. Multi-head attention with , (so ) and biases has parameters. For tokens: projections GFLOP; scores and weighted sums GFLOP. At the two parts cost the same.
5. In code #
PyTorch checks the worked example and the claims of section 3. The notebook builds the same functions from NumPy.
import torch
import torch.nn as nn
import torch.nn.functional as F
torch.manual_seed(0)
f64 = torch.float64
# (a) the worked example, without and with the causal mask
X = torch.tensor([[1., 0.], [0., 1.], [1., 1.]], dtype=f64)
print(F.scaled_dot_product_attention(X, X, X).numpy().round(3))
# [[0.802 0.599] [0.599 0.802] [0.752 0.752]]
print(F.scaled_dot_product_attention(X, X, X, is_causal=True).numpy().round(3))
# [[1. 0. ] [0.33 0.67 ] [0.752 0.752]]
# (b) Var(q·k) = d_k, and Var(q·k / sqrt(d_k)) = 1
for d_k in (16, 64, 256):
q, k = torch.randn(100_000, d_k, dtype=f64), torch.randn(100_000, d_k, dtype=f64)
s = (q * k).sum(-1)
print(d_k, round(s.var().item(), 1), round((s / d_k**0.5).var().item(), 2))
# 16 16.0 1.0 / 64 63.8 1.0 / 256 255.3 1.0 (Monte Carlo: close to d_k and 1)
# (c) softmax Jacobian = diag(p) - p pᵀ, and it is tiny when p is nearly one-hot
for z in ([2., 1., 0.], [16., 8., 0.]):
z = torch.tensor(z, dtype=f64)
J = torch.autograd.functional.jacobian(lambda s: torch.softmax(s, -1), z)
p = torch.softmax(z, -1)
print(torch.allclose(J, torch.diag(p) - torch.outer(p, p)), f"{J.abs().max().item():.2e}")
# True 2.23e-01
# True 3.35e-04
# (d) 4D² + 4D parameters for every H, and permutation equivariance
print([sum(p.numel() for p in nn.MultiheadAttention(512, H).parameters()) for H in (1, 8, 64)])
# [1050624, 1050624, 1050624]
mha = nn.MultiheadAttention(64, 4, batch_first=True, dtype=f64)
attn = lambda x, mask=None: mha(x, x, x, attn_mask=mask, need_weights=False)[0]
x, perm = torch.randn(1, 10, 64, dtype=f64), torch.randperm(10)
print(torch.allclose(attn(x)[:, perm], attn(x[:, perm]))) # True
causal = nn.Transformer.generate_square_subsequent_mask(10, dtype=f64)
print(torch.allclose(attn(x, causal)[:, perm], attn(x[:, perm], causal))) # False
F.scaled_dot_product_attention divides by itself, with the last dimension of
the queries. generate_square_subsequent_mask returns the additive mask (0 and ).
6. Where it shows up #
- Language models. GPT and LLaMA are stacks of causal self-attention (decoder-only). BERT uses self-attention without a causal mask (encoder-only). At generation time the keys and values of past tokens are stored in a KV cache; multi-query and grouped-query attention share key and value heads between query heads to make this cache smaller (lesson 11).
- Long inputs. The term limits the context length. FlashAttention computes exact attention in tiles with an online softmax: the extra memory becomes , while the FLOPs stay .
- Conditioning by cross-attention. Machine translation (the original transformer), speech recognition (Whisper: the text decoder reads the audio encoder), and text-to-image latent diffusion (the image tokens of the denoiser read the text tokens).
- Many inputs, few queries. The Perceiver lets a small set of learned query vectors cross-attend to a very large input, so the cost grows like instead of . ClimaX merges the physical variables of each weather patch by cross-attention with one learned query.
- Science. AlphaFold 2 applies attention along the rows and columns of a sequence alignment and over pairs of residues. Attention restricted to the neighbours of each node is graph attention (lesson 13). Attention without positions is a natural layer for sets, such as point clouds or particles.
7. Common mistakes #
- Masking after the softmax. The visible weights then sum to less than 1, and they depend on the masked keys through the normalizer. Add to the logits before the softmax.
- A fully masked row. It gives NaN (or silent zeros). Check padding masks for rows with no visible key.
- A flipped mask convention.
Truemeans "attend" in one API and "blocked" in another. - A wrong head split. Reshaping directly to mixes tokens and features. View as , then transpose.
- Scaling by instead of , the head size.
- "More heads, more parameters." The count is for every that divides .
- A causal mask in an encoder or in cross-attention. Only decoder self-attention is causal.
- Reading row of as "who attends to token ". Row holds the weights of query ; column shows how much each query reads from token .
8. Check yourself #
- Cross-attention with batch , queries, keys, and
heads. Give the shapes of the per-head queries, the keys, the attention weights and the output.
Answer
. Queries ; keys and values ; weights ; per-head outputs ; after merging the heads and : , one row per query. - Logits . Compute and the softmax Jacobian.
Answer
. with . - The entries of and are independent with mean 0 and variance . What is
? What keeps close to 1 in a network?
Answer
: each term has variance , and there are terms, divided by . The scaling works only if and have entries of unit scale. A normalized input to the attention layer (LayerNorm, lesson 05) and a variance-preserving initialization of (lesson 04) give this at initialization; qk-norm keeps it during training. - Why is the mask added before the softmax and not applied after it?
Answer
Before: masked weights are exactly 0 and the visible weights are normalized to sum to 1. After: the normalizer still contains the masked (for example, future) keys, so the visible weights do not sum to 1 and they depend on the masked tokens. For a causal model this is a leak of future information. - A model has . For which number of tokens do the attention scores and weighted
sums ( FLOPs) cost more than the four projections ()?
Answer
. - Can a decoder-only model without any positional encoding know token positions?
Answer
Yes, it can infer them. Under the causal mask, query averages over exactly tokens. For example, a head with equal weights gives the first token the weight , which encodes . The mask breaks permutation equivariance.
Write scaled dot-product attention with a mask.
, with the softmax applied to each row and .
Scores for two keys. What are the attention weights?
, so the weights are .
Why do we divide the logits by ?
For independent unit-variance entries, . Large logits make the softmax nearly one-hot, its Jacobian close to 0, and the gradient vanishes. Dividing by gives logits of variance 1.
What is the Jacobian of ?
, that is, .
What does a causal mask do, and how is it built?
Query may only attend to keys : add to the logits above the diagonal, before the softmax.
What happens to an attention row where every key is masked?
All logits are ; the softmax computes and returns NaN (some kernels return zeros). Every query must see at least one key.
How many parameters does multi-head attention with width and heads have?
( and their biases), independent of .
How do you split of shape into heads?
View it as , then transpose to , with .
How does the cost of self-attention grow with the number of tokens ?
The scores and weighted sums cost FLOPs and the weights need memory per head: quadratic in . The projections cost .
What does permutation equivariance of self-attention mean, and what breaks it?
: shuffling the tokens shuffles the outputs in the same way. A causal mask or positional information breaks it.
9. Interview angle #
- "Implement multi-head attention with a causal mask." — Write the shapes first; view then
transpose for the heads; add the mask before the softmax; compare with
F.scaled_dot_product_attention. - "Why divide by ?" — has variance at initialization; large logits saturate the softmax, its Jacobian goes to 0, and learning stops.
- "What is the cost of self-attention?" — FLOPs per layer and memory for the weights unless the computation is tiled (FlashAttention).
- "Why several heads, and does it add parameters?" — Several attention patterns at once; no, the count stays .
- "Self-attention vs cross-attention?" — Same formula; in cross-attention the queries come from one sequence and the keys and values from another.
- "What does attention do if you shuffle the tokens?" — Without positions or a causal mask, the outputs are shuffled in the same way (permutation equivariance).
10. Practice #
The notebook for this lesson runs in Colab. It uses NumPy for the exercises and compares with PyTorch when it is installed (it is on Colab). Five exercises, each with a check cell and a folded solution:
- Scaled dot-product attention with a boolean mask, checked on the worked example and against
F.scaled_dot_product_attention. - A Monte Carlo check of , and a plot of the largest weight with and without the scaling.
- The softmax Jacobian and its VJP, checked by finite differences, and its size as the logits grow.
- Multi-head attention from scratch (view and transpose), matching
nn.MultiheadAttentionwith copied weights. - A permutation-equivariance test, with and without the causal mask.
11. Watch and read #
- Vaswani et al., Attention Is All You Need (2017): §3.2 (scaled dot-product and multi-head attention; footnote 4 is the variance argument) and Table 1 (cost per layer).
- Simon Prince, Understanding Deep Learning (free PDF), chapter 12: §12.2–12.3 (dot-product self-attention, the scaling, multiple heads), §12.7 (the causal mask in a decoder) and §12.8 (encoder–decoder and cross-attention).
- Rush et al., The Annotated Transformer, sections "Attention" and "Applications of Attention in our Model": the paper as runnable code.
- Zhang, Lipton, Li, Smola, Dive into Deep Learning, chapter 11 (attention as queries, keys and values; multi-head and self-attention), with code.
- Stanford CS224N, the lecture "Self-Attention and Transformers" (slides and notes on the course page).
Runs in your browser with nothing to install. The notebook repeats the key demos and has exercises with checks; solutions are inside, folded.
No Google account needed: Python runs inside your browser. The first start downloads about 40 MB; your changes are saved in this browser only.