Module 06 · Part II · The Transformer

Attention

Queries, Keys and Values, Scaled Dot-Product Attention by Hand, the Variance Proof, Masks, Permutation Equivariance, and Multi-Head Attention

By the end you can compute attention by hand, prove why scores are divided by √d_k, and explain what multiple heads add.

Builds onModule 5·
Check yourself ▸

Before you start, can you already…

  • ☐explain why recurrence hit a wall, and how attention first appeared inside RNN translation models. (Module 5)

If any is shaky, the linked module teaches it.

Open in Colab
5 Hours·Core·Intermediate·0/6

In June 2017, Vaswani and colleagues at Google published "Attention Is All You Need". Before it, sequence models read text with recurrence (section 5.2) or convolutions (small filters slid along the text, each seeing a few neighbours; section 25.1). The Transformer dropped both: every token looks directly at every other token through self-attention. Almost every large language model today is built on that design.

This module builds attention by hand on small numbers, proves why scores are divided by , explains masks, shows what attention cannot do alone (tell word order), and ends with multi-head attention and its cost.

6.1 The Question Attention Answers

For each token, attention answers: "which other tokens should I take information from, and how much?" It uses the three projections of section 1.6:

  • Queries : one row per asking token, describing what it looks for.
  • Keys K∈RM×dkK \in \mathbb{R}^{M \times d_k}: one row per token that can be looked at, describing what it offers.
  • Values V∈RM×dvV \in \mathbb{R}^{M \times d_v}: one row per key, the information handed over if chosen.

6.2 Scaled Dot-Product Attention, by HandRun this section

In self-attention all three come from the same sentence, so N=MN = M; they differ in cross-attention (section 8.1). The whole computation is one line:

Attention⁡(Q,K,V)=Softmax⁡(QK⊤dk+M)V\operatorname{Attention}(Q, K, V) = \operatorname{Softmax}\left(\frac{QK^\top}{\sqrt{d_k}} + M\right) V

Read it from the inside out:

  1. QK⊤QK^\top is an N×MN \times M table of scores: entry (i,j)(i, j) is query ii dotted with key jj (section 1.4).
  2. Dividing by dk\sqrt{d_k} keeps the scores from growing with the vector width (section 6.3).
  3. The mask MM holds 00 where attention is allowed and where it is not, so blocked pairs get weight (section 6.4).
  4. Softmax turns each row into weights that sum to 1 (section 4.3).
  5. Multiplying by VV gives each query a weighted average of the value rows.

The letter MM does double duty: as a size (N×MN \times M) it is the key count; as a matrix (+M+M) it is the N×MN \times M mask. dkd_k, the query and key width, is usually 6464 or 128128; dvd_v is the value width.

Architecture Flowchart
Diagram: Queries Q, shape N x d_k; Multiply: Q · K^T; Keys K, shape M x d_k; Raw scores, shape N x M; Divide by √d_k; Add mask M (e.g. causal: -∞ on future keys); Softmax each row (safe form); Attention weights A, each in [0, 1], shape N x M; Multiply: A · V; Values V, shape M x d_v; Output, shape N x d_v

In words. Each query is scored against every key; the scores are scaled, masked and softmaxed into weights, which average the values. Each query gets one output vector, built from the tokens it was allowed to see.

The Whole Computation on Two Tokens

Finish section 1.6's example. Its and WKW^K, plus a value matrix WVW^V with rows [1,0][1, 0], [0,1][0, 1], [0,0][0, 0], [1,1][1, 1], give:

querykeyvalue
token 1[3,−3][3, -3][−3,3][-3, 3][0,−1][0, -1]
token 2[1,1][1, 1][1,1][1, 1][1,2][1, 2]
  1. Scores. Every query against every key: , q1⋅k2=0\mathbf{q}_1 \cdot \mathbf{k}_2 = 0, q2⋅k1=0\mathbf{q}_2 \cdot \mathbf{k}_1 = 0, q2⋅k2=2\mathbf{q}_2 \cdot \mathbf{k}_2 = 2.
  2. Scale by : the rows become [−12.73, 0][-12.73, \, 0] and [0, 1.414][0, \, 1.414].
  3. Mask. With a causal mask, token 1 may not look at token 2, so its second score becomes −∞-\infty: row 1 is [−12.73, −∞][-12.73, \, -\infty].
  4. Softmax each row. Row 1 has only one allowed entry, so its weights are [1,0][1, 0]. Row 2 is [e0,e1.414]=[1,4.113][e^0, e^{1.414}] = [1, 4.113], divided by their sum 5.1135.113: weights [0.196, 0.804][0.196, \, 0.804].
  5. Weighted sum of values. Token 1's output is just v1=[0,−1]\mathbf{v}_1 = [0, -1]. Token 2's output is 0.196⋅[0,−1]+0.804⋅[1,2]=[0.804, 1.413]0.196 \cdot [0, -1] + 0.804 \cdot [1, 2] = [0.804, \, 1.413].

Each output is a mix of value vectors chosen by the scores. And the mask changed the answer: without it, nearly all of token 1's weight would go to token 2, which it is not supposed to see.

Figure 6.2
Attention, one step at a time

The two tokens from section 1.6, pushed through every step of the attention formula. Switch the mask and the scaling to see what each changes downstream.

d_k = 2, so the scale is √2 ≈ 1.414. Rows are queries (the token asking); columns are keys (the token being looked at).
Predict, then try

Step to the softmax with the causal mask switched off. Where does token 1 put almost all of its weight, and why does the very negative score −12.73-12.73 make the answer so lopsided?

In words. A stepper walks the two-token example from queries and keys through scores, scaling, mask and softmax to the output, highlighting the entries involved. Mask and scaling can be toggled, and later steps update.

6.3 Why Divide by the Square Root of the Key WidthRun this section

The division is not a tuning trick; it follows from a short calculation with the rules of section 3.2. At the start of training, treat each query and key entry as independent with mean 0 and variance 1: . One raw score is

z=q⊤k=∑i=1dkqikiz = \mathbf{q}^\top \mathbf{k} = \sum_{i=1}^{d_k} q_i k_i

a sum of dkd_k products. Find its mean and variance.

  1. The mean is zero, by rules 1 and 2, since each :
E[z]=∑i=1dkE[qiki]=∑i=1dkE[qi]E[ki]=0\mathbb{E}[z] = \sum_{i=1}^{d_k} \mathbb{E}[q_i k_i] = \sum_{i=1}^{d_k} \mathbb{E}[q_i] \mathbb{E}[k_i] = 0
  1. Each product has variance 1. Its mean is 0, and by independence E[qi2ki2]=E[qi2] E[ki2]\mathbb{E}[q_i^2 k_i^2] = \mathbb{E}[q_i^2]\,\mathbb{E}[k_i^2], each factor being a variance of 1:
Var⁡(qiki)=E[(qiki)2]−(E[qiki])2=E[qi2]E[ki2]−0=(1)(1)=1\operatorname{Var}(q_i k_i) = \mathbb{E}[(q_i k_i)^2] - (\mathbb{E}[q_i k_i])^2 = \mathbb{E}[q_i^2] \mathbb{E}[k_i^2] - 0 = (1)(1) = 1
  1. The variances add (rule 4), over dkd_k terms:
Var⁡(z)=∑i=1dkVar⁡(qiki)=∑i=1dk1=dk\operatorname{Var}(z) = \sum_{i=1}^{d_k} \operatorname{Var}(q_i k_i) = \sum_{i=1}^{d_k} 1 = d_k
  1. So a typical score has size dk\sqrt{d_k}:
σz=Var⁡(z)=dk\sigma_z = \sqrt{\operatorname{Var}(z)} = \sqrt{d_k}

At dk=64d_k = 64 a typical score is about ±8\pm 8; at dk=128d_k = 128, about ±11.3\pm 11.3.

What Goes Wrong Without the Scaling

A gap of gg between two scores makes one weight ege^{g} times the other. At dk=128d_k = 128, gaps of 20 are common, and e20e^{20} is about 500 million, so softmax puts almost all weight on one key. This saturation stalls learning: section 4.3 showed that the softmax Jacobian is then nearly zero, so the query and key weights barely change.

By rule 3, dividing by dk\sqrt{d_k} divides the variance by dkd_k:

Var⁡(zdk)=1dkVar⁡(z)=dkdk=1\operatorname{Var}\left(\frac{z}{\sqrt{d_k}}\right) = \frac{1}{d_k} \operatorname{Var}(z) = \frac{d_k}{d_k} = 1

Scaled scores have variance 1 at any head width, so softmax starts training where its gradients are healthy.

Figure 6.3
Unscaled scores spread with √d_k and saturate the softmax

Left: scores of random queries and keys. Right: in 300 trials of one query against 64 keys, how often the softmax puts more than 99% of its weight on one key.

Entries of queries and keys are drawn from N(0, 1), the start-of-training model used in the proof. The horizontal axis is fixed at ±48.
Predict, then try

Turn scaling off and raise dkd_k from 16 to 256. Predict what happens to the histogram of scores and to the share of trials in which one key takes more than 99% of the weight. Then turn scaling on.

In words. A histogram of random query-key scores at a chosen head width, with or without scaling. A second panel runs softmax trials over 64 keys and reports how often the largest weight exceeds 0.99, and how small p times one minus p becomes when it does.

Measured (lab 2b, experiments/variance_scaling.py). The variance result holds at every head width. Over 200,000 random draws per width, Var⁡(q⊤k)/dk\operatorname{Var}(\mathbf{q}^\top\mathbf{k}) / d_k comes out at 0.99870.9987, 1.00401.0040, 1.00291.0029 and 1.00381.0038 for dk=32,64,128,256d_k = 32, 64, 128, 256. The scaled variance is 1.01.0 throughout.

Measured (lab 2b). Over 500 random trials at dk=128d_k = 128 with 6464 keys, 35%35\% of the unscaled trials push pmax⁡p_{\max} above 0.990.99. With scaling, 0%0\% do. In the trials that do saturate, the gradient term pmax⁡(1−pmax⁡)p_{\max}(1 - p_{\max}) is 0.00200.0020, close to zero. The result is a fraction of trials because saturation depends on the random draw: one draw clears 0.990.99 while the next can fall short.

6.4 MasksRun this section

A mask added to the scores puts −∞-\infty on forbidden pairs, so softmax spreads each row's weight over the allowed keys only. Three kinds cover every model in the course:

  • No mask. Every token sees every token. Encoders use this (section 8.2), so a token's meaning can depend on both sides.
  • Causal mask. Token ii sees tokens 11 to ii only. Every text-writing model uses this (section 8.4), since token i+1i+1 must not be visible while predicting it. For four tokens, with ⋅\cdot for 00:
Mcausal=[⋅−∞−∞−∞⋅⋅−∞−∞⋅⋅⋅−∞⋅⋅⋅⋅]M_{\text{causal}} = \begin{bmatrix} \cdot & -\infty & -\infty & -\infty \\ \cdot & \cdot & -\infty & -\infty \\ \cdot & \cdot & \cdot & -\infty \\ \cdot & \cdot & \cdot & \cdot \end{bmatrix}
  • Padding mask. Short sequences in a batch are filled out with placeholder tokens; the mask puts −∞-\infty in their columns so no real token attends to them.

Masks combine by adding. Section 16.3 meets a fourth kind, the sliding window. Every row must keep at least one allowed key, or the softmax computes 0/00/0 = NaN.

6.5 Attention Cannot See Word OrderRun this section

A score qikj⊤\mathbf{q}_i \mathbf{k}_j^\top is built from two tokens' vectors and nothing else; no position appears anywhere. Shuffle the tokens and, with no mask, the outputs come back shuffled the same way and otherwise unchanged. This is permutation-equivariance: for any permutation matrix PP (one that reorders rows), Attention⁡(PX,PX,PX)=PAttention⁡(X,X,X)\operatorname{Attention}(P X, P X, P X) = P \operatorname{Attention}(X, X, X). In "The dog bit the man" and "The man bit the dog", "dog" gets exactly the same output vector. The model cannot tell who bit whom.

Figure 6.5
Without positions, shuffling the input only shuffles the output

Self-attention (no mask) over four words. Each row is one word’s output vector. Hover a cell to read its value, then shuffle and compare the row for the same word.

Random weights, d = 8. Position vectors, when switched on, depend on the slot a word occupies, not on the word.
Predict, then try

Shuffle the tokens. Before looking at the output panel, predict whether any output vector changes its values, or only its position in the list.

In words. Self-attention on a short sentence, showing every token's output vector. Shuffling the tokens reorders the outputs without changing their values. A switch adds position vectors, and then shuffling changes the outputs themselves.

The fix is to give each token its position before attention: section 7.1 shows the classic way, and section 15.1 the modern way, which builds position into the query–key score itself.

6.6 Multi-Head AttentionRun this section

One attention computation gives one set of weights per query, so linking a verb to its subject and a pronoun to its noun must blend into one average. Instead, run several smaller attention computations, heads, side by side, each with its own projections, and join the results:

MultiHead⁡(Q,K,V)=[head⁡1; head⁡2; … ; head⁡h]WO\operatorname{MultiHead}(Q, K, V) = \left[\operatorname{head}_1; \, \operatorname{head}_2; \, \dots; \, \operatorname{head}_h\right] W^O
head⁡i=Attention⁡(QWiQ, KWiK, VWiV)\operatorname{head}_i = \operatorname{Attention}\left(Q W_i^Q, \, K W_i^K, \, V W_i^V\right)

hh is the number of heads, and [ ; ][\,;\,] means concatenate along the feature axis. and WiV∈Rdmodel×dvW_i^V \in \mathbb{R}^{d_{\text{model}} \times d_v} make head ii's queries, keys and values; the output projection WO∈R(h⋅dv)×dmodelW^O \in \mathbb{R}^{(h \cdot d_v) \times d_{\text{model}}} maps the joined outputs back to model width. Usually dk=dv=dmodel/hd_k = d_v = d_{\text{model}}/h: the paper uses h=8h = 8 and dmodel=512d_{\text{model}} = 512, so dk=64d_k = 64, and eight heads cost about the same as one head of width 512.

Architecture Flowchart
Diagram: Token vectors X, shape N x d_model; Head 1, e.g. subject–verb links; Head 2, e.g. pronoun → the noun it refers to; Head 3, e.g. punctuation and boundaries; Head 8, e.g. who did what to whom; Concatenate [head_1; head_2; ...; head_8]; Output projection W^O, 512 x 512; Multi-head output, shape N x d_model

In words. Each head projects the tokens to its own 64 features and runs full attention there. The labels are patterns heads in trained models have been seen to learn; nobody assigns them. The outputs are joined and mixed by one output projection.

Figure 6.6
Four heads, four patterns, one sentence

Each head has its own query and key projections, so each computes its own attention table over the same tokens. Together the tables stack into one tensor, a layer per head. Pick a head to bring its table forward, and a query word to see where each head sends it.

Illustrative weights set by hand to mimic patterns found in trained models (previous-token heads, coreference heads). Rows are queries and columns are keys; darker means more weight.
Predict, then try

Pick the query word it and look at the four heads. Before you do, guess which head's weights would let the model work out what it refers to.

In words. Four heads read "The animal didn't cross the street because it was tired", with hand-set weights mimicking trained models: one looks at the previous word, one sends "it" to "animal", one sends each verb to its subject, and one attends to the sentence start and "because". The four tables stack into one tensor, a layer per head.

Why the Output Projection WOW^O Is Needed

  1. It mixes the heads. After joining, each head occupies its own 64 features; every output feature of WOW^O combines all 512, so it can use what every head found.
  2. It writes back into the shared vector. Each layer adds its output to the token's vector (the residual stream, section 7.2), and WOW^O lets each head write in any direction of it, not just its own slice. It also fixes the shape when h⋅dv≠dmodelh \cdot d_v \neq d_{\text{model}}.

The Cost of Multi-Head Attention

Costs are counted in FLOPs (floating-point operations). For NN tokens, counting multiply-adds:

  1. Projecting queries, keys and values for all heads: three N×dmodelN \times d_{\text{model}} by dmodel×dmodeld_{\text{model}} \times d_{\text{model}} products, 3⋅N⋅dmodel23 \cdot N \cdot d_{\text{model}}^2.
  2. The scores QK⊤Q K^\top: each head multiplies N×dkN \times d_k by dk×Nd_k \times N, costing N⋅dk⋅NN \cdot d_k \cdot N. Over hh heads that is , because h⋅dk=dmodelh \cdot d_k = d_{\text{model}}.
  3. The weighted sum AVA V: likewise h⋅(N⋅N⋅dv)=N2⋅dmodelh \cdot (N \cdot N \cdot d_v) = N^2 \cdot d_{\text{model}}.
  4. The output projection WOW^O: N⋅dmodel2N \cdot d_{\text{model}}^2.

Adding up (the softmax is small and left out):

FLOPsMHA=4Ndmodel2+2N2dmodel\text{FLOPs}_{\text{MHA}} = 4 N d_{\text{model}}^2 + 2 N^2 d_{\text{model}}

(Counting multiply and add separately doubles both terms; the comparison is unchanged.) The first term grows with NN, the second with N2N^2, and the second wins when 2N2dmodel>4Ndmodel22N^2 d_{\text{model}} > 4N d_{\text{model}}^2, that is, N>2dmodelN > 2d_{\text{model}}. Past that length the N×NN \times N score table dominates compute and memory. This is why long contexts are expensive.

Exercises

Check

In one sentence each, what are the query, the key and the value of a token for?

Apply

Scores for one query are [2,0,0][2, 0, 0] (already scaled). Compute the attention output when the values are the scalars [1,4,7][1, 4, 7].

Apply

Using the rules of section 3.2, prove that Var⁡(z/dk)=1\operatorname{Var}(z / \sqrt{d_k}) = 1 when zz is a sum of dkd_k independent products, each with mean 0 and variance 1.

Apply

Write the causal mask for three tokens, and say what the softmax weights of the first row must be whatever the scores are.

Apply

For dmodel=4096d_{\text{model}} = 4096, at what sequence length does the 2N2dmodel2N^2 d_{\text{model}} term of multi-head attention's cost overtake the 4Ndmodel24N d_{\text{model}}^2 term?

Extend

What does a head compute if its softmax weights are exactly uniform over all NN tokens? Why is that not useless?

Extend

Show that self-attention without a mask or positions is permutation-equivariant: if the rows of XX are reordered by a permutation matrix PP, the output rows are reordered the same way.

Core Academic References

  • Vaswani et al. (2017): Attention Is All You Need. NeurIPS 2017. arXiv:1706.03762
  • Bahdanau, Cho & Bengio (2014): Neural Machine Translation by Jointly Learning to Align and Translate. ICLR 2015. arXiv:1409.0473
  • Lee et al. (2019): Set Transformer: A Framework for Attention-based Permutation-Invariant Neural Networks. ICML 2019. arXiv:1810.00825

Measured labs

These labs test what this module says by running real code. Each card states the idea, what the experiment found, and what it does not prove. The numbers come from experiments/results.json, the output of running experiments/run_all.sh on the tiny-transformers reference code at version 137f7ef. The project checks fail if a measured number in the module text stops matching that file.

Lab 2b — why attention divides by √d_k, measured

What the module says

Section 6.3 shows that if every entry of the query q\mathbf{q} and key k\mathbf{k} is random with mean 0 and variance 1, their dot product q⊤k\mathbf{q}^\top\mathbf{k} has variance dkd_k, the vector length. Dividing by dk\sqrt{d_k} brings the variance back to 1. Without that division, scores grow with dkd_k, softmax puts nearly all weight on one key, and the gradient nearly vanishes.

What the experiment found

Over 200,000 random draws for each vector length, the variance divided by dkd_k is 0.9987, 1.0040, 1.0029 and 1.0038 for dk=32,64,128,256d_k = 32, 64, 128, 256: essentially 1 each time, as the math predicts. After dividing by dk\sqrt{d_k} the variance is about 1.0. The softmax effect varies from draw to draw, so it is reported as a share of trials. With dk=128d_k = 128 and 64 keys, 35% of unscaled trials put more than 0.99 of the weight on one key, against 0% of scaled trials. In those peaked trials the gradient term p(1−p)p(1-p) for the top key averages 0.0020.

variance ÷ dkd_k at dk=32d_k = 32
0.9987

the math predicts 1

variance ÷ dkd_k at dk=128d_k = 128
1.0029

the math predicts 1

variance after dividing by dk\sqrt{d_k}, dk=256d_k = 256
1.0038

stays near 1 whatever dkd_k is

share of trials with top weight above 0.99
0.35 unscaled → 0 scaled

500 trials, 64 keys, dk=128d_k = 128

gradient term p(1−p)p(1-p) in the peaked trials
0.00202255

the gradient the module says vanishes

What this does not prove

Why the result is reported this way. Whether softmax becomes peaked depends on the random draw: one draw puts more than 0.99 on a single key and the next does not, so a single draw proves nothing and the lab reports a share of trials instead. The lab also reports pmax⁡(1−pmax⁡)p_{\max}(1 - p_{\max}) rather than the size of the whole gradient matrix, because p(1−p)p(1-p) is largest at p=0.5p = 0.5: the matrix can grow while the term that matters is falling toward zero.

Run it yourself

bash
cd tiny-transformers && .venv/bin/python experiments/variance_scaling.py

The code being tested