Part 1 · Chapter 3

Attention

How a token reads its context: queries, keys, values, and a weighted sum.

Where we are

Chapter 2 gave each token a vector and a way to transform it, but every token was processed alone. The embedding for "it" is the same row of W_E in every sentence ever written, and the MLP transforms it the same way every time. Nothing so far lets one token's vector depend on the other tokens. This chapter adds that, and it is the single mechanism the whole transformer is named after. Chapter 4 wraps it into a block; Chapter 5 shows how its matrices are learned.

The question this chapter answers: how does a token gather information from the tokens before it, and how is "which tokens" decided?

Picture this

A conference reception. Everyone wears a name badge, and on the badge, under the name, is a line about what they can help with: "compilers", "tax law", "knows where the good coffee is". Everyone also carries something to give away: a business card, a tip, a story.

You walk in with a question in your head. Not a sentence, a need: "I need someone who knows compilers". You do not read every badge with equal care. You scan them, and the ones that match your need light up. You spend most of your evening with the compiler people, some with the person whose badge said "linkers", almost none with tax law. By the end you have absorbed what they gave you, in proportion to how much time you spent with each. You leave changed by the room, and the change is a weighted blend of what the people you attended to had to offer.

Three things about this scene map exactly onto the mechanism. Your need and their badge lines are different kinds of thing, even though they are about the same topic; matching happens between the two. What a person gives you is a third thing, different from their badge. And the blend is weighted, not a vote: you can take 60% from one person and 5% from another.

Map it
At the receptionIn the machineThe word we will use
Your need, as you walk inThe token's vector projected by a learned matrix into "what I am looking for"query q = x W_Q
The line on each badgeEach token's vector projected by another matrix into "what I offer"key k = x W_K
What each person hands youEach token's vector projected by a third matrix into "what I pass on"value v = x W_V
How well a badge matches your needThe dot product of your query with their keyscore q·k
How you divide your eveningSoftmax over the scores: positive weights summing to 1attention weights
What you absorbedThe weighted sum of the valuesoutput Σ wⱼ vⱼ
You can only talk to people who arrived before youPositions after the query are excludedcausal mask
Everyone is simultaneously a guest and a badge-wearerEvery token is a query and a key and a value, all at onceself-attention

3.1The problem: one vector, many meanings

Two sentences:

The cat sat on the mat because it was tired.
The cat sat on the mat because it was soft.

The token "it" refers to the cat in the first and to the mat in the second. Yet after the embedding lookup of Chapter 2, both occurrences hold the identical vector, row W_E["it"]. The MLP will transform that vector identically too, because it only ever sees one token at a time. To predict "tired" versus "soft", or to do anything that depends on what "it" refers to, the model needs a way to move information from "cat" or "mat" into the vector at position "it". And which one to move from cannot be fixed in advance; it depends on the sentence.

The cat sat on the mat because it was tired "it" needs cat The cat sat on the mat because it was soft same "it" vector, needs mat
Why is a per-token function not enough? The embedding of "it" is identical in both sentences. Whatever "it" should mean here has to be fetched from a different earlier token in each case, and the choice depends on content, not position.

So the requirement is precise. We need an operation that, for each token, (1) decides which earlier tokens are relevant, based on the vectors themselves, and (2) copies something from those tokens into this one, in proportion to relevance. Attention is that operation, and the elegant part is that both "which" and "something" are computed with the tools of Chapter 2: dot products and matrices.

3.2Three views of one vector: Q, K, V

Start with the token vectors stacked as rows: X of shape [T, C]. Attention multiplies X by three learned matrices to get three new sets of vectors:

Q = X · W_Q      W_Q : [C, d]      queries   "what am I looking for?"
K = X · W_K      W_K : [C, d]      keys      "what do I offer to those looking?"
V = X · W_V      W_V : [C, d]      values    "what do I hand over if chosen?"

each of Q, K, V has shape [T, d]; row t belongs to token t
X [T, C] The cat sat it same rows feed all three · W_Q · W_K · W_V Q [T, d]one query per token K [T, d]one key per token V [T, d]one value per token Q·Kᵀ [T, T] every query against every key softmax weights, rows sum to 1 weights · V [T, d] each token's output: a blend of values
Where do queries, keys, and values come from? From the same token vectors, through three different learned matrices. Queries meet keys to decide the weights; the weights then blend the values. The colours are fixed for the rest of the book: blue for Q, orange for K, green for V.

Why three matrices and not one? Because the three jobs are different, and forcing them through one representation would make them fight. What "it" looks for (a recent noun) is not what "it" offers to later tokens (a pronoun, probably the subject) and neither is what it should hand over if a later token attends to it. Three matrices let training tune each role independently. Each is a change of viewpoint in the sense of Chapter 2: the same 4096-dimensional token, re-expressed for a purpose.

Note the width d. It is smaller than C: Llama 3 uses d = 128 against C = 4096 or 16384 public. A single attention head works in a narrow slice of the representation. Chapter 4 explains why that is fine: there are many heads, each with its own slice.

3.3Scores: every query against every key

Now the reception scan. Each query is dotted with every key. For token i and token j, the score is qᵢ · kⱼ: large when what i seeks aligns with what j offers. Doing this for all pairs at once is a single matrix multiplication:

S = Q · Kᵀ         [T, d] × [d, T]  →  [T, T]

S[i, j] = qᵢ · kⱼ  =  how much token i wants to look at token j
row i    = token i's scores over every key

Read the shape. T × T: one number for every ordered pair of tokens. For a 4-token sentence that is 16 numbers. For a 128k-token context it is 16 billion per head per layer, and that single fact drives much of Part 2's engineering. For now, four tokens.

query of "it" [0.6, 0.2] keys of every token The [0.5, 0] cat [4, 0] sat [0, 2] it [1.3, 0] q · k = 0.6·0.5 + 0.2·0 = 0.30 0.6·4 + 0.2·0 = 2.40 0.6·0 + 0.2·2 = 0.40 0.6·1.3 + 0.2·0 = 0.78 row "it" of S [0.30, 2.40, 0.40, 0.78]
What does one row of the score matrix contain? The query of one token, dotted with the key of every token. Here "it" scores highest against "cat", because its query was shaped to seek what "cat"'s key advertises. Every other row of S is the same procedure for a different query.

3.4Scaling, softmax, and the mask

Raw scores are not weights yet. Three steps turn a row of scores into a row of weights that sum to one and only look backwards.

Divide by √d

Scores are dot products of length-d vectors. If the entries of q and k have spread about 1, the dot product has spread about √d: it is a sum of d terms, and sums of random terms grow like the square root of their count. With d = 128 the raw scores would routinely differ by 10 or 20, and the softmax of Chapter 1 would turn that into a near-certain choice of one key before any learning has happened. Dividing every score by √d restores the spread to about 1, so the softmax starts soft and the model can learn to sharpen it where sharpness is warranted. This is the "scaled" in scaled dot-product attention, and it is a fix for a numerical fact, not a modelling idea.

Static view of the widget. With d = 64 and random unit-variance q and k, the raw scores have spread about 8 and the softmax puts almost all its weight on one key by accident. After dividing by √64 = 8 the spread is about 1 and the weights are gently varied. At d = 4 the two panels are nearly identical, because √4 = 2 barely matters.

Softmax per row

Each row of the scaled score matrix goes through the softmax from Chapter 1: exponentiate, divide by the row's sum. Now each row is a set of positive weights summing to 1: token i's attention distribution over the keys. Exactly the same function that turns logits into next-token probabilities, used here to turn "how relevant" into "what fraction of my attention". The properties carry over: order is preserved, big gaps are amplified, nothing is ever exactly zero.

The causal mask

One more rule, and it comes from the game in Chapter 1. When the model predicts the token after position i, it must not have seen positions i+1, i+2, …. Otherwise the training is a cheat: "predict the next token" becomes "read the next token". So before the softmax, every score where the key is after the query is replaced by −∞. Exponentiate −∞ and you get exactly 0: those positions receive no weight and contribute nothing. Everything above the diagonal of the [T, T] matrix is switched off.

scaled scores Thecatsatit Thecatsatit 0.00 0.00 1.41 0.00 0.71 5.66 0.00 1.84 0.00 0.00 1.41 0.00 0.21 1.70 0.28 0.55 red: key is after query → −∞ → softmax rows attention weights Thecatsatit 1.00 0 0 0 0.01 0.99 0 0 0.16 0.16 0.67 0 0.13 0.56 0.14 0.18 each row sums to 1; nothing above the diagonal "The" can only see itself: weight 1.00, no choice. "it" spreads over four, mostly "cat".
What does the mask do to the weight matrix? Every entry where the key comes after the query is forced to exactly zero, so each token's weights are a distribution over itself and its past. The first token has no past and attends entirely to itself.

The mask has a consequence that is easy to miss and central to Chapter 7: with it in place, every position's prediction can be computed at the same time from one pass over the text, because position i's output provably used nothing after i. Training runs all T next-token games of a sequence in parallel. Without the mask that would be impossible.

3.5The output: a weighted sum of values

The last step is the one that actually moves information. Each token's output is the sum of all value vectors, each multiplied by that token's weight on it:

out_i  =  Σ_j  W[i, j] · v_j              for all rows at once:   Out = W · V     [T, T] × [T, d] → [T, d]

For "it", with weights [0.13, 0.56, 0.14, 0.18] over [The, cat, sat, it]: the output is 13% of The's value, 56% of cat's, 14% of sat's, 18% of its own. It is now a vector that is mostly what "cat" hands over. That is the information movement the chapter set out to build: the vector at position "it" now carries content that came from position "cat", chosen because their query and key matched, in a proportion set by softmax.

Geometrically, in the language of Chapter 2: the output is a point inside the shape spanned by the value vectors, pulled toward the ones with large weight. Attention cannot invent a value that no token offered; it can only blend what is there. New content is the MLP's job. Attention's job is routing.

By hand

Four tokens, C = 3, one head of width d = 2. The token vectors are deliberately simple so the projections are readable. The numbers below are the same ones the widget starts from and the same ones code/ch03/one_head_by_hand.py prints.

X (T=4, C=3)              W_Q (3×2)        W_K (3×2)        W_V (3×2)
The  [1.0, 0.0, 0.0]      ┌ 0  1 ┐         ┌ 0.5  0 ┐        ┌ 1    0   ┐
cat  [0.0, 1.0, 0.0]      │ 2  0 │         │ 4    0 │        │ 0    1   │
sat  [0.0, 0.0, 1.0]      └ 0  1 ┘         └ 0    2 ┘        └ 0.5  0.5 ┘
it   [0.2, 0.3, 0.0]

step 1  project              Q = X·W_Q            K = X·W_K            V = X·W_V
        The                  [0.0, 1.0]           [0.5, 0.0]           [1.0, 0.0]
        cat                  [2.0, 0.0]           [4.0, 0.0]           [0.0, 1.0]
        sat                  [0.0, 1.0]           [0.0, 2.0]           [0.5, 0.5]
        it                   [0.6, 0.2]           [1.3, 0.0]           [0.2, 0.3]

step 2  scores for "it"      q_it · k_j  =  [0.30, 2.40, 0.40, 0.78]
step 3  scale by √2          ÷ 1.414     =  [0.21, 1.70, 0.28, 0.55]
step 4  mask                 nothing after "it", so unchanged
step 5  softmax              exp → [1.24, 5.46, 1.33, 1.74], sum 9.76
                             weights   =  [0.13, 0.56, 0.14, 0.18]
step 6  blend values         0.13·[1,0] + 0.56·[0,1] + 0.14·[0.5,0.5] + 0.18·[0.2,0.3]
                             =  [0.13 + 0 + 0.07 + 0.04,  0 + 0.56 + 0.07 + 0.05]
                             =  [0.23, 0.68]

Look at what W_Q and W_K did. "it"'s vector has a little weight on the second coordinate (the "noun-like" one, 0.3). W_Q amplifies that coordinate ×2 into the first query dimension, and W_K amplifies it ×4 into the first key dimension. So a token that is noun-like seeks along dimension 1, and a token that is a noun advertises along dimension 1. The match between "it" and "cat" is manufactured by those two matrices. In a trained model nobody designs this; the matrices are pushed there by the loss, because sentences where "it" fetches its referent are easier to continue.

$ python code/ch03/one_head_by_hand.py
weights (softmax per row) =
 [[1.    0.    0.    0.   ]
 [0.007 0.993 0.    0.   ]
 [0.164 0.164 0.673 0.   ]
 [0.127 0.559 0.136 0.178]]
output = weights · V =
 [[1.    0.   ]
 [0.007 0.993]
 [0.5   0.5  ]
 [0.23  0.681]]

Now do it live. Change what "it" is like and watch where its attention goes; pick other query tokens; switch the mask off and see the future leak in.

Static view of the widget. For query "it" with x = [0.2, 0.3, 0]: keys [0.5,0], [4,0], [0,2], [1.3,0]; scores [0.30, 2.40, 0.40, 0.78]; scaled [0.21, 1.70, 0.28, 0.55]; weights [0.13, 0.56, 0.14, 0.18]; output [0.23, 0.68]. Raising x₂ toward 1 makes "it" attend almost entirely to "cat"; raising x₃ instead swings it to "sat". With the mask off, "The" would attend 67% to "sat".

3.6All the shapes at once

Here is one head, end to end, with every shape. This is the picture to memorise; everything in Chapter 4 is this picture repeated and wrapped.

X[T, C] W_Q [C,d] W_K [C,d] W_V [C,d] Q[T, d] K[T, d] V[T, d] Q·Kᵀ / √d[T, T] mask, softmax[T, T] weights · V[T, d] cost per head ≈ 2·T·C·d ×3 (projections) + 2·T²·d (scores) + 2·T²·d (blend). The T² terms are attention's signature.
What are the shapes through one head? Three projections from [T, C] to [T, d]; a [T, T] score matrix from queries against keys; the same shape after mask and softmax; and a [T, d] output from weights against values. Everything that involves T twice is what makes long contexts expensive.

3.7What heads learn to do

Nothing in the mechanism says what a head should attend to. W_Q and W_K are learned, so the matching rule is learned; W_V is learned, so what gets moved is learned. Training discovers whatever routing lowers the next-token loss. When researchers look inside trained models, the heads they find are surprisingly legible public:

Static view of the widget. Five idealised weight matrices over a 13-token sequence: a previous-token diagonal, an identity diagonal, a first-column sink, an induction pattern where the second "Dursley" attends to what followed the first, and a subject-to-verb link. Real heads are noisier blends of these.

The reception analogy has a limit worth naming here. Real heads are not people with intentions. A head is two small matrices and a softmax, and "previous-token head" is a description of what its weights happen to do, discovered after the fact. Chapter 26 shows how such descriptions are found and how far they can be trusted.

3.8Beacon's numbers

QuantityLlama 3 8BLlama 3.1 405BEvidence
Head width d128128public
Query heads per layer32128public (so heads × d = C; Chapter 4)
Key/value heads per layer88public (fewer than query heads: grouped-query attention, Chapter 12)
Context length at release8k → 128k128kpublic
Closed frontier modelsHead counts and widths not disclosed; contexts of 200k to 1M tokens advertisedpublic for contexts, unknown for internals
Back of the envelope

How big is the score matrix? One head, one layer, in a long context.

context T                 128,000 tokens
score matrix entries      T² = 1.6 × 10¹⁰            per head, per layer
bytes at 2 bytes/entry    3.3 × 10¹⁰  ≈ 33 GB          per head, per layer
× 32 heads                ≈ 1 TB                       per layer, one forward pass

compare: a top GPU holds 80–192 GB of memory in total

Materialising the full weight matrix for a long context does not fit on any GPU, by a factor of ten per layer. Yet 128k-token contexts are served every day. The resolution is that nobody materialises it: the scores are computed in tiles and consumed by the softmax and the value blend before the next tile is produced, so only a tile ever exists in memory. That technique is FlashAttention, and it is Chapter 10's centrepiece. The arithmetic does not go away (it is still 2·T²·d operations per head), but the memory does.

And the cost per token during generation? When the model produces token T+1, its one new query is scored against T keys: T·d multiplies per head, then a blend of T values. Linear in T, per new token. That is why long conversations get slower and more expensive as they grow, and why the keys and values of past tokens are worth keeping around rather than recomputing (the KV cache, Chapter 7).

Break it

Remove the scaling. With d = 128, raw scores have spread about 11. Softmax of scores that differ by 11 is one-hot: each token attends to a single key, chosen almost at random at initialisation. Chapter 5's gradients through a saturated softmax are near zero, so the head barely learns. Training is slower and less stable; some heads never recover. The fix costs one division.

Use the same matrix for queries and keys. Then a token's score against itself, q·q, is its squared length: always the largest possible match for a token of that length, so every token attends mostly to itself. The "what I seek" and "what I offer" roles collapse, and the asymmetric relations language needs (a verb seeking its subject is not a subject seeking its verb) cannot be expressed. Models with tied Q and K exist as experiments and are worse inferred from published ablations.

Drop the softmax and use raw scores as weights. Weights can now be negative and unbounded; the output is no longer a blend inside the span of the values but an arbitrary combination whose scale grows with T. Longer contexts produce larger outputs for no reason, and the downstream layers see inputs whose size depends on position. Softmax makes attention scale-free in T: whether there are 4 keys or 100k, the output is an average.

Remove the causal mask. During training, the token at position i can attend to position i+1, which is exactly the token it is supposed to predict. Loss falls to nearly zero in a few steps, and the model has learned to read one position ahead. At inference there is no position ahead, and the model is useless. The mask is the only thing that keeps the training game honest.

Replace the weighted sum with "take the top-1 value". A hard choice: no blending, and no gradient signal about the keys that were nearly chosen. The model cannot represent "60% cat, 30% mat" and cannot learn smoothly. Soft weights are what make the routing trainable.

Rebuild the model

Say it back. The embedding gives every token the same vector regardless of context, and the MLP treats each token alone; something must move information between positions, and which positions must depend on content. Attention does this with three learned viewpoints on each token vector: a query (what I seek), a key (what I offer), a value (what I hand over). Every query is dotted with every key to get a T × T score matrix; the scores are divided by √d so their spread does not depend on head width; scores against future positions are set to −∞; each row is softmaxed into weights that sum to one. Each token's output is the weighted sum of the values, a blend of what the tokens it attended to handed over, pulled toward the closest matches. Attention routes; it does not create. Which routes exist is learned through W_Q and W_K, and trained heads turn out to implement legible jobs like "copy the previous token" and "find what followed this token last time". The T² in the score matrix is the cost of letting every token consider every other, and it shapes the engineering of long contexts.

X [T,C]tokens Q K V = X·Wthree viewpoints Q·Kᵀ/√dscores [T,T] maskno future softmaxrows sum to 1 · Vblend values [T,d] learned: W_Q, W_K decide who talks to whom · W_V decides what is said · softmax makes it a blend
What is the whole chapter in one line? Project three ways, score queries against keys, mask the future, softmax, blend the values. Chapter 4 runs many of these in parallel and adds the result back onto the token.
Exercises
  1. By hand. In the worked example, change the query token's vector to x_it = [0.0, 0.0, 1.0] (make "it" look exactly like "sat"). Recompute its query, its four scores, the scaled scores, the softmax weights, and the output. Check with the widget. Which token does it now attend to, and why does the answer follow from W_Q and W_K rather than from W_V?
  2. Calculation. A model has 128 heads of width 128 per layer and 126 layers. For a context of 32k tokens, how many multiply-adds does the Q·Kᵀ step cost across the whole model for one forward pass? How does that compare with the projections X·W_Q, X·W_K, X·W_V at C = 16384? At what context length do the two costs cross?
  3. Code. Modify one_head_by_hand.py to remove the /√d scaling and multiply all of X by 4. Print the weight matrix. Then restore the scaling and print again. Explain in two sentences why the first matrix is nearly one-hot and what that would do to learning.
Further reading