The one operation every frontier model is built around, derived from a soft dictionary lookup, with every shape, gradient, mask, and FLOP accounted for.
Chapter 4 · Section 4.1 · 45 min

Attention as a soft dictionary lookup

Chapter 3 ended with one operation in three steps. A query scores every stored vector, a softmax turns the scores into weights, and the output is the weighted average of the stored vectors (§3.6). In its barest form, the one you wrote in Exercise 3.6d, every row of X\mathbf{X} plays every part at once,

softmax⁡ ⁣(XX⊤d)X.\operatorname{softmax}\!\left(\frac{\mathbf{X}\mathbf{X}^\top}{\sqrt d}\right)\mathbf{X}.

You also met the knobs. The score can be additive (Bahdanau) or a dot product (Luong), dividing the scores by a temperature slides the result from a plain average to a hard pick of one vector, and padding is hidden by setting its scores to −∞-\infty before the softmax.

That operation is not yet a layer that could replace an RNN, and Chapter 3 closed with a list of what it lacks. This chapter removes the items one at a time. This section takes the first two. In Exercise 3.6d (and in Bahdanau's model, where the annotations hj\mathbf{h}_j were both scored and averaged) the same vector was what got matched and what got read out. What changes when those are two different things, as they are in every dictionary? And Chapter 3 said the soft version of the lookup is the one a network can learn. Why exactly, how strong is the learning signal, and how does temperature change it? The answers frame the rest of the chapter, from the learned projections of §4.2 to the scaling of §4.3.

Keys are what you match, values are what you get

Every program you have written uses lookups. rent["two-bedroom"] finds the key that matches and hands back its value, and the key and the value are different objects, often of different types. Let's make that numeric. We have nn entries. Entry jj has a key kj∈Rdk\mathbf{k}_j \in \R^{d_k}, which is what a query is compared against, and a value vj∈Rdv\mathbf{v}_j \in \R^{d_v}, which is what comes back when the entry is chosen. A query q∈Rdk\mathbf{q} \in \R^{d_k} arrives. A hard lookup scores every key with some similarity function, picks the winner, and returns its value,

j⋆=arg⁡max⁡j sim⁡(q,kj),o=vj⋆.j^\star = \arg\max_j \ \operatorname{sim}(\mathbf{q}, \mathbf{k}_j), \qquad \mathbf{o} = \mathbf{v}_{j^\star}.

The query and the keys must live in the same space, since they are compared. The values need not, which is why dvd_v can differ from dkd_k. Chapter 3's attention is the special case kj=vj\mathbf{k}_j = \mathbf{v}_j.

It helps to write the same lookup as a weighted sum. Let a∈Rn\mathbf{a} \in \R^n be the one-hot vector ej⋆\mathbf{e}_{j^\star}. Then

o=∑j=1najvj,a=ej⋆.\mathbf{o} = \sum_{j=1}^{n} a_j \mathbf{v}_j, \qquad \mathbf{a} = \mathbf{e}_{j^\star}.

In plain words, a hard lookup is a weighted sum of all the values in which one weight is 1 and the rest are 0.

Here is a tiny example we will use throughout the section. The keys are apartment sizes, 40, 60, 80, and 100 square meters, and the values are monthly rents of 900, 1200, 1500, and 2100. The similarity is closeness, sim⁡(q,kj)=−(q−kj)2\operatorname{sim}(q, k_j) = -(q - k_j)^2. The question is about size and the answer is about money, so neither could stand in for the other. That is the separation of keys from values in its simplest form, and §4.2 shows why a Transformer wants it even when both come from the same token. Ask for a 69.9 m² apartment and the nearest key is 60, so you get 1200. Ask for 70.1 m² and you get 1500.

A lookup you cannot learn

A change of 0.2 m² in the query moved the answer by 300, while a small change almost anywhere else moves it by nothing at all. That staircase is the problem. The output is piecewise constant in q\mathbf{q} and in every kj\mathbf{k}_j, so its derivative is zero almost everywhere and undefined at the jumps. Gradient descent asks "if the query were slightly different, how would the output change?" and the hard lookup answers "not at all" until, suddenly, it changes completely. A network built on hard lookups gets no signal about what it should be looking for. There is a second, quieter problem. A hard lookup cannot blend. If the right answer lies between two entries, it cannot say so.

Soft weights, and why softmax makes them

The fix is to let the weights be any probability vector, not just a one-hot one,

o=∑j=1najvj,aj≥0,∑j=1naj=1.\mathbf{o} = \sum_{j=1}^{n} a_j \mathbf{v}_j, \qquad a_j \ge 0, \quad \sum_{j=1}^{n} a_j = 1 .

In plain words, the output is a weighted average of the values, and the weights say how much each entry counts.

A weighted average with nonnegative weights that sum to 1 is a convex combination. Geometrically, o\mathbf{o} always lies inside the convex hull of the values, the smallest convex shape containing all the vj\mathbf{v}_j. A hard lookup sits at a corner of that shape, and a uniform average sits near its middle. Everything in between is now available.

Chapter 3 made the weights with a softmax of the scores sj=sim⁡(q,kj)s_j = \operatorname{sim}(\mathbf{q}, \mathbf{k}_j), aj=esj/∑lesla_j = e^{s_j}/\sum_{l} e^{s_l}, and you have used the same function since §0.4. It is worth asking why this function, of all the ways to turn scores into weights. We want weights that are positive for any real score (including negative ones), that grow with the score, that are smooth so that gradients flow, and that can come as close to the hard argmax as we like. The exponential gives all four, through one property,

aiaj=esi−sj.\frac{a_i}{a_j} = e^{s_i - s_j} .

Every extra unit of score multiplies an entry's weight relative to another by e≈2.72e \approx 2.72. Only differences between scores matter. Adding the same constant cc to every score changes nothing, because

softmax⁡(s+c1)j=esj+c∑lesl+c=ecesjec∑lesl=softmax⁡(s)j.\operatorname{softmax}(\mathbf{s} + c\mathbf{1})_j = \frac{e^{s_j + c}}{\sum_l e^{s_l + c}} = \frac{e^{c} e^{s_j}}{e^{c}\sum_l e^{s_l}} = \operatorname{softmax}(\mathbf{s})_j .

This shift invariance is more than a curiosity. It is why we can subtract the largest score before exponentiating (the stable softmax of §0.4), and in §4.2 it will tell us that one of the biases people put in attention layers does nothing at all.

Why not just divide each score by the sum of the scores? Scores can be negative, so the "weights" could be negative. The sum can be zero, so you could divide by zero. And sj/∑lsls_j / \sum_l s_l is not invariant to shifts, so the answer would depend on an arbitrary choice of zero point. The name also deserves a correction. §0.4 showed that log-sum-exp is the smooth version of the max, max⁡lsl≤log⁡∑lesl≤max⁡lsl+log⁡n\max_l s_l \le \log\sum_l e^{s_l} \le \max_l s_l + \log n, and that its gradient is the softmax. The gradient of the max itself is the one-hot indicator of the winner, so softmax is really a soft argmax, a smooth stand-in for "which one is biggest", written as a probability vector. That is exactly what a soft lookup needs.

Back to the rents. With the similarity sj=−(q−kj)2/(2σ2)s_j = -(q - k_j)^2 / (2\sigma^2) and a query of 75 m², the softmax weights depend on the width σ\sigma, which sets how quickly similarity falls off with distance.

σ\sigma (m²)weights on 40, 60, 80, 100soft renthard rent
200.090, 0.315, 0.404, 0.19114661500
100.002, 0.259, 0.704, 0.03514421500
50.000, 0.018, 0.982, 0.00014951500

With a narrow σ\sigma the soft lookup is almost the hard one. With a wide σ\sigma it blends the neighbors. Every entry is a smooth function of the query, so the network can learn.

Python · cell 1

Temperature decides how hard the lookup is

The width σ\sigma in the rent example is a special case of the knob you met in §3.6. Divide every score by a temperature τ>0\tau > 0 before the softmax,

aj=softmax⁡(s/τ)j=esj/τ∑lesl/τ.a_j = \operatorname{softmax}(\mathbf{s}/\tau)_j = \frac{e^{s_j/\tau}}{\sum_l e^{s_l/\tau}} .

In plain words, a low temperature exaggerates the differences between scores, and a high temperature shrinks them.

Chapter 3's widget showed the two limits. Here is why they hold. The ratio of two weights is now ai/aj=e(si−sj)/τa_i/a_j = e^{(s_i - s_j)/\tau}. Suppose the largest score sj⋆s_{j^\star} is unique. For any other entry, si−sj⋆<0s_i - s_{j^\star} < 0, so as τ→0\tau \to 0 the ratio ai/aj⋆=e(si−sj⋆)/τ→0a_i / a_{j^\star} = e^{(s_i - s_{j^\star})/\tau} \to 0. All the weight flows to the winner, and the soft lookup becomes the hard one. As τ→∞\tau \to \infty every ratio tends to e0=1e^0 = 1, so the weights become uniform and the lookup returns the plain average of all the values.

What happens in between is less obvious. Here are the numbers for three scores s=(2,1,0.1)\mathbf{s} = (2, 1, 0.1), together with the entropy H(a)=−∑jajlog⁡ajH(\mathbf{a}) = -\sum_j a_j \log a_j of the weights, which measures how spread out they are (0 for a one-hot vector, log⁡3≈1.099\log 3 \approx 1.099 for a uniform one, see §0.5).

τ\tauweightsentropy H(a)H(\mathbf{a})
0.250.982, 0.018, 0.0000.094
0.50.864, 0.117, 0.0190.454
10.659, 0.242, 0.0990.847
20.502, 0.304, 0.1941.026
100.366, 0.331, 0.3031.096

The entropy rises steadily with temperature. That is not a coincidence of this example. It is a theorem, and the proof is short enough to be worth knowing.

∂ Entropy always increases with temperature

Write β=1/τ\beta = 1/\tau (the inverse temperature), Z(β)=∑leβslZ(\beta) = \sum_l e^{\beta s_l}, and aj=eβsj/Za_j = e^{\beta s_j}/Z. Then log⁡aj=βsj−log⁡Z\log a_j = \beta s_j - \log Z, so

H(β)=−∑jaj(βsj−log⁡Z)=log⁡Z−β Ea[s],H(\beta) = -\sum_j a_j(\beta s_j - \log Z) = \log Z - \beta\, \mathbb{E}_{\mathbf{a}}[s],

where Ea[s]=∑jajsj\mathbb{E}_{\mathbf{a}}[s] = \sum_j a_j s_j is the average score under the weights. Two derivatives are needed. First, ddβlog⁡Z=1Z∑lsleβsl=Ea[s]\frac{d}{d\beta}\log Z = \frac{1}{Z}\sum_l s_l e^{\beta s_l} = \mathbb{E}_{\mathbf{a}}[s]. Second, dajdβ=aj(sj−Ea[s])\frac{d a_j}{d\beta} = a_j (s_j - \mathbb{E}_{\mathbf{a}}[s]), so

ddβEa[s]=∑jsj aj(sj−Ea[s])=Ea[s2]−Ea[s]2=Var⁡a(s).\frac{d}{d\beta}\mathbb{E}_{\mathbf{a}}[s] = \sum_j s_j\, a_j (s_j - \mathbb{E}_{\mathbf{a}}[s]) = \mathbb{E}_{\mathbf{a}}[s^2] - \mathbb{E}_{\mathbf{a}}[s]^2 = \operatorname{Var}_{\mathbf{a}}(s).

Putting the pieces together,

dHdβ=Ea[s]−Ea[s]−βVar⁡a(s)=−βVar⁡a(s)≤0.\frac{dH}{d\beta} = \mathbb{E}_{\mathbf{a}}[s] - \mathbb{E}_{\mathbf{a}}[s] - \beta \operatorname{Var}_{\mathbf{a}}(s) = -\beta\operatorname{Var}_{\mathbf{a}}(s) \le 0 .

The variance is zero only when all the scores are equal. Otherwise entropy strictly decreases as β\beta grows, which means it strictly increases with τ=1/β\tau = 1/\beta. ∎

Monotonicity is what makes the temperature a well-behaved control. For any target sharpness between "one-hot" and "uniform" there is exactly one temperature that achieves it, and you can find it by bisection (Exercise 4.1d does).

You will meet temperature all over the book. Dividing attention scores by dk\sqrt{d_k} is a fixed temperature chosen for a statistical reason, the subject of §4.3. Sampling from a language model at temperature τ\tau is the same formula applied to the next-token logits (§6.4). And contrastive image-text models learn their temperature as a parameter, because the right sharpness for matching images to captions is not known in advance. CLIP initializes it at τ=0.07\tau = 0.07 and clips it so that the logits are never multiplied by more than 100 (Radford et al. 2021).

Drag the query in the widget below. Switch between the two similarity functions, and sweep the temperature from nearly hard to nearly uniform.

How does a soft lookup turn similarity into weights?
Drag the query (black) or any key. Each key holds a number, its value. The lookup returns the weighted average of the values, with weights from a softmax over similarities.
Loading interactive figure…

That last point deserves a sentence of its own. With dot-product scores, the length of the query acts as an inverse temperature. A network that can change the norms of its queries and keys can therefore choose, token by token, how sharp each lookup should be. This is part of why dot-product attention is so flexible, and also, as §4.3 will show, part of why it can become unstable.

Why the soft version can be learned

Now the payoff. How does the output change when one score changes? We need the derivative of a softmax weight with respect to a score, which §0.2 derived with the quotient rule as the softmax Jacobian,

∂ai∂sj=ai(1[i=j]−aj),\frac{\partial a_i}{\partial s_j} = a_i\big(\mathbb{1}[i=j] - a_j\big),

where 1[i=j]\mathbb{1}[i=j] is 1 when i=ji = j and 0 otherwise. The output is o=∑iaivi\mathbf{o} = \sum_i a_i \mathbf{v}_i, so

∂o∂sj=∑iai(1[i=j]−aj)vi=ajvj−aj∑iaivi=aj(vj−o).\frac{\partial \mathbf{o}}{\partial s_j} = \sum_i a_i\big(\mathbb{1}[i=j] - a_j\big)\mathbf{v}_i = a_j \mathbf{v}_j - a_j \sum_i a_i \mathbf{v}_i = a_j(\mathbf{v}_j - \mathbf{o}) .

In plain words, raising the score of entry jj pulls the output toward that entry's value, with a strength equal to the entry's current weight.

This little formula explains a lot. If entry jj currently has almost no weight, nudging its score barely matters. If it already has nearly all the weight, then vj≈o\mathbf{v}_j \approx \mathbf{o} and nudging it barely matters either. Gradients flow best when the weights are spread over a few candidates. That is our first glimpse of saturation, the failure mode the dk\sqrt{d_k} scaling of §4.3 exists to prevent. With a temperature, the formula picks up a factor 1/τ1/\tau, so very hot or very cold lookups both learn slowly, for opposite reasons. The hard lookup is the limit τ→0\tau \to 0, where the weights are one-hot and the gradient is exactly zero.

We can go one step further and differentiate with respect to the query itself, using dot-product scores sj=q⊤kjs_j = \mathbf{q}^\top\mathbf{k}_j, so ∂sj/∂q=kj⊤\partial s_j / \partial \mathbf{q} = \mathbf{k}_j^\top. By the chain rule,

∂o∂q=∑jaj(vj−o) kj⊤=∑jaj(vj−o)(kj−kˉ)⊤  ∈  Rdv×dk,\frac{\partial \mathbf{o}}{\partial \mathbf{q}} = \sum_j a_j(\mathbf{v}_j - \mathbf{o})\,\mathbf{k}_j^\top = \sum_j a_j (\mathbf{v}_j - \mathbf{o})(\mathbf{k}_j - \bar{\mathbf{k}})^\top \;\in\; \R^{d_v \times d_k},

where kˉ=∑jajkj\bar{\mathbf{k}} = \sum_j a_j \mathbf{k}_j. The second form is allowed because ∑jaj(vj−o)=o−o=0\sum_j a_j(\mathbf{v}_j - \mathbf{o}) = \mathbf{o} - \mathbf{o} = \mathbf{0}, so subtracting kˉ\bar{\mathbf{k}} adds nothing.

In plain words, the Jacobian of the output with respect to the query is the covariance between values and keys under the attention weights. The query learns to move in key directions that are associated with the values the loss wants. With kj=vj\mathbf{k}_j = \mathbf{v}_j, as in Chapter 3, this is the covariance of the stored vectors with themselves, so a query can find an entry only by resembling what it wants to read. Separate keys let it find an entry by one property and read out another, the way the rent example finds an apartment by its size and reads its price.

Let's verify that claim numerically before believing it.

Python · cell 2
Warm-up

A batched soft lookup with separate keys and values

Write soft_lookup_batch(Q, K, V, tau=1.0) for a whole batch of queries at once. Q has shape (m, d_k), K has shape (n, d_k), and V has shape (n, d_v). Score with dot products, divide by tau, and return (O, A), where A of shape (m, n) holds one row of weights per query and O of shape (m, d_v) holds one output per query. Use matrix products, no loops. This is Exercise 3.6d with the keys and values separated from the queries, and with a temperature.

Hidden tests, hints, and AI help, from $1.99 a month.

The soft lookup's gradient is not just nonzero, it points somewhere useful. The next exercise uses it to learn a query, which is the whole reason for making the lookup soft.

Core

Learn a query by gradient descent

A soft lookup with dot-product scores at temperature tau returns o=softmax⁡(Kq/τ)⊤V\mathbf{o} = \operatorname{softmax}(\mathbf{K}\mathbf{q}/\tau)^\top\mathbf{V}. We want it to return a target vector, so the loss is L=12∥o−target∥2\mathcal{L} = \tfrac12\lVert\mathbf{o} - \text{target}\rVert^2. Implement query_step(q, K, V, target, tau=1.0) returning (loss, grad), where grad is ∂L/∂q\partial\mathcal{L}/\partial\mathbf{q} of shape (d_k,), computed from the covariance formula above (no finite differences). K is (n, d_k), V is (n, d_v). The tests check your gradient numerically, run gradient descent with it, and confirm that a saturated lookup passes no gradient at all.

Hidden tests, hints, and AI help, from $1.99 a month.

Look at the third test. A query that has locked onto the wrong entry at a tiny temperature has a large loss and a zero gradient. Nothing in the loss can move it. That is the hard lookup's failure, recovered as the cold limit of the soft one, and the reason trained attention keeps its scores in a moderate range.

Which similarity? Dot product, distance, or a small network

The softmax does not care where the scores come from. §3.6 compared Bahdanau's additive score with Luong's dot and general scores, and found the dot product cheapest, since a whole batch of scores is one matrix product. One more family is worth adding, because it connects attention to a much older idea, and it shows that the choice matters less than you might think.

Negative squared distance, sj=−∥q−kj∥2s_j = -\lVert\mathbf{q} - \mathbf{k}_j\rVert^2, is what the rent example used. Expand it,

−∥q−kj∥2=2 q⊤kj−∥q∥2−∥kj∥2.-\lVert\mathbf{q} - \mathbf{k}_j\rVert^2 = 2\,\mathbf{q}^\top\mathbf{k}_j - \lVert\mathbf{q}\rVert^2 - \lVert\mathbf{k}_j\rVert^2 .

The term ∥q∥2\lVert\mathbf{q}\rVert^2 is the same for every jj, so by shift invariance it disappears inside the softmax. What is left is a dot product (times 2) plus a per-key bias −∥kj∥2-\lVert\mathbf{k}_j\rVert^2 that penalizes long keys. When all keys have the same norm, distance attention and dot-product attention give identical weights. This is the same identity, ∥u−v∥2=∥u∥2+∥v∥2−2u⊤v\lVert\mathbf{u}-\mathbf{v}\rVert^2 = \lVert\mathbf{u}\rVert^2 + \lVert\mathbf{v}\rVert^2 - 2\mathbf{u}^\top\mathbf{v}, that tied cosine to distance in §1.3.

Additive attention, sj=w⊤tanh⁡(W1q+W2kj)s_j = \mathbf{w}^\top\tanh(\mathbf{W}_1\mathbf{q} + \mathbf{W}_2\mathbf{k}_j), is more flexible per score but cannot be written as one matrix product. Vaswani et al. (2017) report that the two perform similarly for small dkd_k and that additive attention wins over unscaled dot products for large dkd_k, the observation that led them to scale the dot product (§4.3).

similarityformulaparametersbatched costused by
dot productq⊤kj\mathbf{q}^\top\mathbf{k}_jnoneone matmulTransformers, Luong "dot"
bilinearq⊤Wkj\mathbf{q}^\top\mathbf{W}\mathbf{k}_jdk2d_k^2two matmulsLuong "general", and Q K V in disguise (§4.2)
negative distance−∥q−kj∥2-\lVert\mathbf{q}-\mathbf{k}_j\rVert^2noneone matmul plus normskernel regression, RBF networks
additivew⊤tanh⁡(W1q+W2kj)\mathbf{w}^\top\tanh(\mathbf{W}_1\mathbf{q}+\mathbf{W}_2\mathbf{k}_j)about 2dk22d_k^2an MLP per pairBahdanau 2015

Let's see kernel regression as attention, with three bandwidths.

Python · cell 3

A small bandwidth (low temperature) chases the noise, since each query copies its nearest neighbors. A large one (high temperature) over-smooths toward the global mean. The middle one is close to the truth. That is the bias-variance trade-off, and it is the same trade-off a network faces when it decides how sharp each attention head should be.

Core

Kernel regression as attention, without NaN

Implement nadaraya_watson(xq, xs, ys, bandwidth) returning predictions of shape (m,) for queries xq of shape (m,), using training points xs, ys of shape (n,) and a Gaussian kernel. Write it as a softmax over scores. The naive version, np.exp(-(xq - xs)**2 / (2 * bw**2)) divided by its sum, returns 0/0 = NaN for a query far from every training point, because every kernel value underflows to zero. Your version must return the value of the nearest training point in that case, which is the correct limit.

Hidden tests, hints, and AI help, from $1.99 a month.
Research

Find the temperature that gives a target entropy

Given scores s of shape (n,) (not all equal) and a target entropy H_target strictly between 0 and log⁡n\log n, return the temperature tau for which H(softmax⁡(s/τ))=HtargetH(\operatorname{softmax}(\mathbf{s}/\tau)) = H_{\text{target}}, accurate to 10−610^{-6} in entropy. The derivation above proved that entropy increases strictly with τ\tau, so the answer is unique. Calibrating a temperature to a target entropy is a real technique, used for example to set the per-point bandwidths in t-SNE (§1.6), where the target is stated as a perplexity.

Hidden tests, hints, and AI help, from $1.99 a month.

In PyTorch

In PyTorch the soft lookup is three lines, and autograd confirms the covariance formula for the query gradient.

🔥 In PyTorchfor a machine with PyTorch

Read more

Watch next

🎬 Transformers, the tech behind LLMs | Deep Learning Chapter 5 · 3Blue1Brown · starts at 22:22The softmax and temperature segment, with the clearest pictures of how temperature sharpens or flattens a distribution.
🎬 Attention for Neural Networks, Clearly Explained!!! · StatQuest with Josh StarmerA gentle walk through attention as similarity scores turned into weights, in the encoder-decoder setting where it was born.