The missing square root in attention
28 min read
Self-attention is fully assembled except for one symbol. The three projections , and have their argument, the weight matrix is square () because the sentence looks at itself, and the softmax by rows shares one unit out among the positions of each row. But the in the denominator was written into the formula without any explanation of why it is there. It is the only loan still outstanding, and this lesson pays it off.
The question is sharper than it looks. Why a root, and not itself? And why of , and not of or of the length of the sentence? Both are answered at once, because both depend on finding out how far a sum moves away from zero as terms are added to it. That takes no elaborate probability theory, only averaging, and the calculation starts by averaging over a finite list that can be walked through in full.
Take the crudest possible version of a query and a key, that is, of a row of and a row of : all their coordinates are or . The score is then a count of agreements, because each term contributes if the two coordinates share a sign and if they do not. With four coordinates, three agreements and one disagreement give . With four hundred, the same proportion (three hundred against one hundred) gives .
What is worth noticing is not that the terms have grown, since they are still , but that there are a hundred times as many of them. A small deviation from an even split, repeated over more terms, turns into a large number, and the softmax is about to exponentiate that number.
With random signs the proportion does not stay fixed at three to one, and the score grows more slowly: its typical size goes with , as the next section derives. The figure takes one row of six scores at that size in three widths and tries three divisors before the softmax: (that is, not dividing), and itself. Each group of bars is the row's share-out, one weight per position, and the number above it is what the winning position takes. Look for the row that does not change from one column to the next.
A score grows with the root of the width
Each cell of is the dot product of a row of (the query of one position) with a row of (the key of another). Both live in , and I write them and without a position index, because the calculation is the same for any pair. The score is
a sum of terms. The size that comes out depends on the size that goes in, so let's fix the exact case first: the coordinates are or , as in the count of agreements above. Call the -th term, which is then or , and .
Squaring separates the diagonal from the rest:
because whatever the sign. The first term no longer depends on anything. The second is the one that has to be averaged, and averaging here means something perfectly finite: running through the sign combinations can take, with fixed, and taking the mean. We write it , the mean of a quantity over the randomness it is assumed to have. The cross terms all disappear.
See why each cross term averages to zero
Fix two different indices and look at the term . Pair each sign combination with the one you get by flipping the sign of and leaving the other coordinates where they are. That pairs up the combinations perfectly into pairs: each combination has exactly one partner, and it is never itself.
Within a pair, , and have not moved and has changed sign, so the two values of are opposites and sum to zero. Adding zero times and dividing by leaves
The same pairing gives : in each term changes sign along with its own coordinate, so each one averages zero on its own.
So of the square, only the first term survives:
With , that is all it takes. The score is spread around zero, and what measures how far it typically lands from it (the standard deviation) is the root of that mean:
which answers half the question. Dividing by leaves , and does not appear in that : the same scale for queries of eight coordinates and for queries of a thousand.
With the divisor in place, the whole operation (product, division, softmax and mixture) gets a name of its own: the scaled dot-product, which is what the paper calls it. The dot product is the ordinary one, the one in the line above; what is scaled is this division and nothing more.
The signs were there to make the calculation exact, but notice which properties it really needed.
Two: that and that . Any centred
initialisation whose coordinates are independent and of typical size meets them, and with them
the same cancellation leaves the same . The Gaussians from
rng.normal that the course has used since the lesson on
implementing an MLP are one such initialisation, and the first cell below measures it with
them.
It is worth saying out loud what that assumption is about, because it is not about the model you will end up using. It is about a freshly initialised network: nothing guarantees that after thousands of steps the coordinates of are still independent or still of size . That is why the divisor is fixed when the layer is designed rather than measured on the fly: it fixes the start, which is where the problem halts training before it has even begun.
That leaves the part usually taken for granted: why the root and not . The divisor has to take away from the score exactly what the width put into it, and what the width put in is a factor of . Dividing by would leave the scores at a typical size of , closer and closer to zero the wider the model; and scores that tend to zero give a softmax that tends to the even share, in every cell, so the row stops choosing anything. Those are two failures, one on each side, and the root is the only exponent that falls into neither.
A closed row returns no gradient
If the scores measure , the differences between the scores in a row measure that too, and what the softmax looks at is the differences:
with running over the positions of row . Two scores apart (an ordinary gap when the typical size is , which is what gives) end up a factor from each other, and with that the share-out is over: one position takes almost the whole unit.
A row that chooses firmly does not sound like a defect. The defect is in the derivative.
Differentiate with respect to the scores of its own row. It is the same calculation that the lesson on loss functions did with the softmax of the output layer, and the same two cases come out, depending on whether the indices match:
Now take the closed row, with one weight at almost and the rest at almost . The diagonal case gives times ; the cross case, in any cell, carries at least one factor that is almost zero. Both derivatives switch off at once, and the whole row with them.
What that costs becomes clear when you look at what comes before the scores: only and , since the scores come out of and nothing else goes in. If the gradient does not get through the softmax, those two matrices receive nothing from that row, and they are precisely the ones that would have to learn which positions each position should look at. The layer goes on computing (a saturated output is a perfectly well-defined output); what it can no longer do is change its mind.
It is worth pausing on why Block 2 never ran into this, because the difference is one line. There the softmax was followed by the cross-entropy, and the of the loss supplies the exact inverse factor: together the two gave , which is in the correct class and does not vanish while the network is wrong, however sure of itself it is. Here there is no logarithm behind the softmax, only the multiplication by , the matrix of values. The Jacobian cancels against nothing: it stays whole, zeros included.
And so two things the course had closed, in different blocks, turn up again. The curve is the same as the derivative of the sigmoid, with its maximum of in the middle and its two flat tails: the saturation from the lesson on activation functions, in a layer with no sigmoid in it. And a gradient that arrives switched off at the matrices that were meant to learn is the whole of Block 3, in an architecture without a single recurrence. What joined them was never the sigmoid or the recurrent state: it was that in both there was a function flattening out.
A whole row comes down to one number, which comes from adding the diagonal case over the positions:
where the last equality uses that the row sums to . With the row shared out evenly it is ; with the whole row on one position it is . It is the sum of the diagonal of the Jacobian, and it measures the same thing from both sides: how much that row shares out, and how much it still has left to learn.
The divisor, measured in NumPy
The first cell takes the calculation done for signs and checks it where it actually applies: Gaussian coordinates, five widths, with the measured typical size set beside the column.
rng = np.random.default_rng(7)
N = 8000 # pairs of vectors per width
print(f"{'d_k':>5} {'mean':>7} {'typical size':>13} {'sqrt(d_k)':>9} {'divided':>12}")
for d_k in (4, 16, 64, 256, 1024):
Q = rng.normal(size=(N, d_k)) # centred coordinates, of typical size 1
K = rng.normal(size=(N, d_k))
e = np.sum(Q * K, axis=1) # one score per pair
typical = np.sqrt(np.mean(e ** 2))
print(f"{d_k:5d} {e.mean():7.3f} {typical:13.2f} {np.sqrt(d_k):9.2f} {typical / np.sqrt(d_k):12.2f}")
The first run downloads the Python interpreter (~15 MB). After that it stays in the browser cache and is reused across every lesson.
The two middle columns agree as far as a sample of eight thousand pairs allows, and the one on the right stays pinned at : dividing puts the size in the same place whatever the width. The mean column is small next to the typical size on its row (at , against ), which is what says when it is measured instead of proved.
The second cell moves from the scores to what comes out of them. Pay particular attention to the last column, which decides whether the layer can learn.
rng = np.random.default_rng(11)
T, N = 8, 4000 # 8 positions per row, 4000 rows measured
def softmax_rows(E):
Z = np.exp(E - E.max(axis=1, keepdims=True))
return Z / Z.sum(axis=1, keepdims=True)
print(f"{'':<11} {'d_k':>5} {'top weight':>10} {'share-out':>9} {'max a(1-a)':>12}")
for d_k in (8, 64, 512):
Q = rng.normal(size=(N, d_k)) # N queries against the same T keys
K = rng.normal(size=(T, d_k))
E = Q @ K.T
for name, Esc in (("undivided", E), ("divided", E / np.sqrt(d_k))):
A = softmax_rows(Esc)
print(f"{name:<11} {d_k:5d} {A.max(axis=1).mean():10.3f} "
f"{(1 - (A ** 2).sum(axis=1)).mean():9.3f} {(A * (1 - A)).max(axis=1).mean():12.4f}")
The first run downloads the Python interpreter (~15 MB). After that it stays in the browser cache and is reused across every lesson.
Read it in pairs of lines. Undivided, the top weight climbs from to and the share-out sinks from to (the most it can be with eight positions is ), while the largest cell on the diagonal of the Jacobian falls from to : more than five times less gradient for no reason other than having widened the model. Divided, the three lines stop moving: of top weight, of share-out and of derivative at all three widths, included. That is the property the divisor buys. It does not make the numbers better; it makes them stop depending on .
Test your intuition
Five questions: how big a score is, why the root and nothing else, what a closed row loses, why Block 2 escaped this, and what dividing does not touch.
The rows of and in a layer have coordinates, centred and independent, each of typical size . Undivided, how big is a score , typically?
A margin of ±0 is accepted.
Why is the divisor and not ?
A row of the map has gone almost entirely to one position: and all the rest . Mark what is true.
Select every correct option. This is graded all-or-nothing: there is no partial credit.
Block 2 also put a softmax in the network and never mentioned it saturating. Why did this not happen there?
Dividing the scores of a row by can change which position gets the largest weight.
Write score_gradient(A, G), the backward pass of the softmax by rows. It takes the weight
map already normalised, A, of shape with each row summing to , and the gradient
coming down from above, G, of the same shape, with .
It returns , of shape .
The two cases of the derivative are worked out above; chained together, they give, cell by cell, , where runs over row .
Write it with array operations (no looping over cells, and no building of any Jacobian) and do not write into the arrays you receive.
The first run downloads the Python interpreter (~15 MB); after that it stays in the browser cache. This challenge is much easier to solve on a physical keyboard: on a phone, read it and come back later.
With the divisor in place, the scale of the scores stops depending on the width, and the share-out and the gradient stop depending on it by the same calculation. But look at what the divisor takes for granted: it receives already decided. Nothing worked out so far says how long a query should be (only how to compensate for whatever length it has), and until now has been a number that turned up because the projections of the previous lesson had to come out at some width.
There is a second thing this layer cannot do, and it points to the same place. A row of is a share-out, and only one: in the keys to this car are here, the position of are has to decide, with a single set of weights, whether to look at keys, which is what it agrees with, or at to this car, which is what stands between them; and with one row it cannot do both. The answer to both questions is the same and takes up the next lesson, on multi-head attention: several pairs of projections working in parallel, each with its own share-out and its own width, carved out of . There, stops being a given number and becomes a decision with a reason.
Further reading1 source · 1 paper
Where this lesson comes from, and where to go next. None of it is needed to carry on with the course.
- Attention Is All You Need
Its §3.2.1 and footnote 4 contain this lesson in two sentences: with q and k of mean 0 and variance 1, q·k has variance d_k, hence √d_k. It never gets to the saturated softmax or the vanishing gradient; that part is yours.