Self-attention: the sequence looks at itself

Self-attention: the sequence looks at itself

29 min read

The previous lesson left the architecture without a single recurrent piece and with one operation at its centre, attention, which has to be handed three things: queries, keys and values. Nothing is left to produce any of them, because both of the networks that did were recurrent. The only thing that still has a row for every position is the sentence itself, already turned into vectors, so there is one candidate for all three roles and no second one.

Using it as it is does not work, though, and the reason fits in a line: a vector resembles itself more than anything else. Every position would give itself the highest weight, in every row at once, and the layer would hand each one back what it already had, with no parameter to do anything else. And it does need to do something else: in the keys to this car are here, getting are right means having looked at keys, four positions back, and not at car, which sits right next to it and is singular. This lesson builds the layer that can do that, and the price is three learnt matrices, one per role. Multiplying a vector by one of them is projecting it: out comes another vector, usually shorter, whose coordinates are combinations of the original's, and the matrix decides which combinations. Each position is projected three times, giving its query, its key and its value.

The grid below sets a sentence against itself: the row is the position asking, the column is the position answering, and each cell shows how much of that column goes into what that row reads. Type any sentence you like and, above all, switch the projections off and on. With them off, the queries, keys and values are the sentence's vectors as they are, and the highest cell in each row is the one that position gives itself, in all seven rows at once. With them on, the row for are goes looking for keys above everything else.

The same positions on both axes, something that never happened in the previous block. Unprojected, every row is won by its own diagonal; with the three projections, "are" skips "car" and goes straight to "keys".

Three readings of the same matrix

Stack the sentence by rows, the way the previous block stacked the encoder's states:

X∈RT×dmodel,\mathbf{X} \in \mathbb{R}^{T \times d_{\text{model}}},

where row tt is xt⊤\mathbf{x}_t^{\top}, the vector of the token at position tt (transposed, because in this course vectors are columns). That is everything that goes into the layer: one matrix, and no second network.

The queries, keys and values all come out of it, each through its own matrix:

Q=XWQ,K=XWK,V=XWV,\mathbf{Q} = \mathbf{X}\mathbf{W}^Q, \qquad \mathbf{K} = \mathbf{X}\mathbf{W}^K, \qquad \mathbf{V} = \mathbf{X}\mathbf{W}^V,

with WQ,WK∈Rdmodel×dk\mathbf{W}^Q, \mathbf{W}^K \in \mathbb{R}^{d_{\text{model}} \times d_k} and WV∈Rdmodel×dv\mathbf{W}^V \in \mathbb{R}^{d_{\text{model}} \times d_v}, so that Q,K∈RT×dk\mathbf{Q}, \mathbf{K} \in \mathbb{R}^{T \times d_k} and V∈RT×dv\mathbf{V} \in \mathbb{R}^{T \times d_v}. The superscripts on these three matrices are labels for the role each one plays, not layer indices. This is the only place in the course where a superscript does not count layers, and it comes from the paper's notation. There is a single set of these three projections here; a real layer carries several in parallel, which is the subject of a later lesson on multi-head attention.

That is where the self in the name comes from, and it is what separates this lesson from the lesson on attention as query, key and value. There, the query was the state of one network and the keys and values were the states of another: two sources for three roles. Here all three come out of the same X\mathbf{X}. The operation that receives them is the same as in that lesson, with one addition: the divisor the paper puts in the score.

Attention(Q,K,V)=softmax ⁣(QK⊤dk)V∈RT×dv,\text{Attention}\left(\mathbf{Q}, \mathbf{K}, \mathbf{V}\right) = \text{softmax}\!\left(\frac{\mathbf{Q}\mathbf{K}^{\top}}{\sqrt{d_k}}\right)\mathbf{V} \in \mathbb{R}^{T \times d_v},

with the softmax taken by rows. That divisor, dk\sqrt{d_k}, is the only thing in the line nobody has justified yet. For now, take note of it; I will come back to it at the end.

That Q\mathbf{Q} comes whole out of X\mathbf{X} does not contradict the previous lesson's caveat that, when generating text, the decoder still writes one token per step. Here X\mathbf{X} is a sentence that is already complete (the source sentence, or the target one during training), so all the queries come out at once. When generating, the decoder's X\mathbf{X} is what it has written so far and gains a row at each step, and Q\mathbf{Q} gains one with it. But the new query is that row multiplied by WQ\mathbf{W}^Q, not the state of a network that needs the previous step's: the only thing forcing the decoder to go one token at a time is that the next token has not been written yet. How that decoder is put together is the subject of the lesson on masks.

Now look at the shapes, which is where you can see that the input and the output are the same sentence. QK⊤\mathbf{Q}\mathbf{K}^{\top} multiplies a T×dkT \times d_k matrix by a dk×Td_k \times T one, so the product is square, T×TT \times T: one row and one column per position. The softmax does not change that shape, so the weight map A\mathbf{A} that comes out of it is T×TT \times T as well, with the weight αij\alpha_{ij} in cell (i,j)(i, j): how much of position jj goes into what position ii reads. This is the grid from the start of the lesson, and the literature calls it the attention weights or the attention map. In the previous block it was Ty×TxT_y \times T_x and its two axes were two different sentences; here both axes are the same one, which is why the cell αii\alpha_{ii} exists and means something: how much of itself a position keeps. Each row still sums to 11, because the softmax still normalises one row at a time, and no position is left out of any row: every position looks at every position.

And count the parameters, which is the other property of shape that decides what follows in the block. The three matrices hold between them

dmodel(2dk+dv)parameters,d_{\text{model}}\left(2d_k + d_v\right) \quad \text{parameters},

and TT does not appear in that count. They multiply rows of X\mathbf{X}, and it makes no difference to them how many rows there are. Compare that with the lesson on why the MLP fails on sequences: there, each position had its own block of columns Wt(1)\mathbf{W}^{(1)}_t, so lengthening the input meant adding parameters, and each position learnt on its own. These three matrices are the same for every position and every length.

Without projections, the layer copies itself

What remains to be defended is what looks like a detour: if Q\mathbf{Q}, K\mathbf{K} and V\mathbf{V} are readings of X\mathbf{X}, why not use it as it is and save the three projections? Set Q=K=V=X\mathbf{Q} = \mathbf{K} = \mathbf{V} = \mathbf{X} and work it through. The score of the pair (i,j)(i,j) becomes the dot product of the two token vectors,

eij=xi⊤xjdmodel,e_{ij} = \frac{\mathbf{x}_i^{\top}\mathbf{x}_j}{\sqrt{d_{\text{model}}}},

and two things follow, neither of them good. First, xi⊤xj=xj⊤xi\mathbf{x}_i^{\top}\mathbf{x}_j = \mathbf{x}_j^{\top}\mathbf{x}_i, so the grid of scores is symmetric. The weights are not (each row is divided by its own sum, and those sums have no reason to match), but the ordering within each row is fixed by a number that cannot tell the position asking from the position answering. A language can: are needs keys far more than keys needs are.

The second is worse. Suppose all the token vectors have the same length, ∥xt∥=r\lVert \mathbf{x}_t \rVert = r. I am assuming this so that the calculation comes out exact, and the Cauchy–Schwarz inequality does the rest:

xi⊤xj≤∥xi∥∥xj∥=r2=xi⊤xi,\mathbf{x}_i^{\top}\mathbf{x}_j \le \lVert \mathbf{x}_i \rVert \lVert \mathbf{x}_j \rVert = r^{2} = \mathbf{x}_i^{\top}\mathbf{x}_i,

with equality only when xj\mathbf{x}_j points in the direction of xi\mathbf{x}_i. The diagonal cell is the largest in its row, always, in every row at once, so the softmax gives the highest weight to the position itself and the mixture leans towards xi\mathbf{x}_i: the layer hands back a faded version of what came in. How far it leans depends on how long the vectors are: the larger rr is, the more tightly the row closes on its diagonal. If the vectors have different lengths, the conclusion keeps its shape: the row then goes to whichever position has the longest vector, which is still a property of the vectors and not of what the sentence needs.

What settles the argument is neither of these, but what is missing from both. That layer has not a single parameter. There is nothing to adjust, so there is no way for it to learn that a plural verb has to go and find a plural noun. The three matrices are what separate the three roles (asking is not being asked, and neither is contributing content) and, on top of that, they are the only thing the layer can learn.

The sequence in all three roles, in NumPy

The whole layer is five lines. Read them with the shapes in front of you, which is where you see the sentence go in once and come out as three different readings.

import numpy as np

T, d_model, d_k, d_v = 6, 8, 4, 5
rng = np.random.default_rng(5)
X = rng.normal(size=(T, d_model)) # row t: the vector of the token at position t
Wq = rng.normal(size=(d_model, d_k)) * 0.6
Wk = rng.normal(size=(d_model, d_k)) * 0.6
Wv = rng.normal(size=(d_model, d_v)) * 0.6

Q, K, V = X @ Wq, X @ Wk, X @ Wv # three readings of the SAME X

E = Q @ K.T / np.sqrt(d_k) # (T, T): one score per pair of positions
Z = np.exp(E - E.max(axis=1, keepdims=True))
A = Z / Z.sum(axis=1, keepdims=True) # softmax by rows
Y = A @ V

print("X", X.shape, " Q", Q.shape, " K", K.shape, " V", V.shape)
print("A", A.shape, " square:", A.shape[0] == A.shape[1])
print("every row sums to 1:", bool(np.allclose(A.sum(axis=1), 1.0)))
print("A symmetric:", bool(np.allclose(A, A.T)))
print("output", Y.shape, "->", d_v, "coordinates per position")
print("parameters:", d_model * (2 * d_k + d_v), " and T does not appear in the count")
print("row 0 of A:", np.round(A[0], 3))
numpy

The first run downloads the Python interpreter (~15 MB). After that it stays in the browser cache and is reused across every lesson.

Change T to 99 and run it again: the map becomes 9×99 \times 9, the output gains three rows and the parameter line does not move. That is the layer's shape property in a nutshell.

Now the check that backs up the previous section. The cell below removes the projections, measures what happens, and then puts them back.

import numpy as np

rng = np.random.default_rng(5)
T, d_model = 6, 8
X = rng.normal(size=(T, d_model))
X = X / np.linalg.norm(X, axis=1, keepdims=True) # all of length 1


def softmax_rows(E):
Z = np.exp(E - E.max(axis=1, keepdims=True))
return Z / Z.sum(axis=1, keepdims=True)


def attention(Q, K, V):
A = softmax_rows(Q @ K.T / np.sqrt(Q.shape[1]))
return A @ V, A


# 1. Unprojected: Q, K and V are X.
_, A0 = attention(X, X, X)
print("symmetric scores:", bool(np.allclose(X @ X.T, (X @ X.T).T)))
print("the diagonal wins its row:", bool(np.all(A0.argmax(axis=1) == np.arange(T))))
print("weight on itself: ", np.round(np.diag(A0), 3))
print("largest of the rest: ", np.round((A0 - np.eye(T)).max(axis=1), 3)) # diagonal left out
_, A0_long = attention(3 * X, 3 * X, 3 * X) # the same vectors, longer
print("and with vectors 3 times longer:", np.round(np.diag(A0_long), 3))

# 2. With projections: two different readings of the same X.
d_k = 4
Wq, Wk = rng.normal(size=(d_model, d_k)), rng.normal(size=(d_model, d_k))
_, A1 = attention(X @ Wq, X @ Wk, X)
print("now the diagonal wins its row:", bool(np.all(A1.argmax(axis=1) == np.arange(T))))
print("weight on itself: ", np.round(np.diag(A1), 3))
print("row 0 in full: ", np.round(A1[0], 3))

# 3. The formula's divisor, measured and still unexplained.
for d in (8, 64, 512):
Qd, Kd = rng.normal(size=(T, d)), rng.normal(size=(T, d))
undivided = softmax_rows(Qd @ Kd.T).max(axis=1).mean()
divided = softmax_rows(Qd @ Kd.T / np.sqrt(d)).max(axis=1).mean()
print(f"d_k = {d:3d} largest weight undivided: {undivided:.3f} divided: {divided:.3f}")
numpy

The first run downloads the Python interpreter (~15 MB). After that it stays in the browser cache and is reused across every lesson.

The first two parts are the previous section in numbers. Unprojected, all six rows put their maximum on their own diagonal, and the line with the vectors three times longer shows what decides whether that maximum is a preference or a monopoly: at length 11 the row shares its weight out almost evenly, and stretching the same vectors makes the weight a position gives itself shoot up. With two different readings of the same matrix, that comes to an end and the positions start to look at one another. The third part is something else, and it is worth reading slowly, because it is what the next lesson is about.

Test your intuition

Five questions: where Q\mathbf{Q}, K\mathbf{K} and V\mathbf{V} come from, how big the map is, what the layer computes without its projections, what is symmetric and what is not, and what changes when the sentence gets longer.

In a self-attention layer, where do Q\mathbf{Q}, K\mathbf{K} and V\mathbf{V} come from?

A sentence of T=12T = 12 tokens goes into a self-attention layer with dmodel=512d_{\text{model}} = 512 and dk=dv=64d_k = d_v = 64. How many numbers does the weight map A\mathbf{A} hold?

numbers

A margin of ±0 is accepted.

You remove the three projections (Q=K=V=X\mathbf{Q} = \mathbf{K} = \mathbf{V} = \mathbf{X}), and all the token vectors have the same length. What does the layer compute?

With Q=K=X\mathbf{Q} = \mathbf{K} = \mathbf{X}, what is symmetric and what is not?

The same layer receives first a sentence of T=20T = 20 tokens and then one of T=200T = 200. Mark what is true.

Select every correct option. This is graded all-or-nothing: there is no partial credit.

Write self_attention(X, Wq, Wk, Wv). It takes the sentence already turned into vectors, X, of shape (T,dmodel)(T, d_{\text{model}}) with one row per position, and the three projections: Wq and Wk, each of shape (dmodel,dk)(d_{\text{model}}, d_k), and Wv, of shape (dmodel,dv)(d_{\text{model}}, d_v). It returns a pair, (output, A), in that order: the output, of shape (T,dv)(T, d_v), and the weight map A\mathbf{A}, of shape (T,T)(T, T), already normalised by rows.

Each score is the dot product of a query with a key, divided by dk\sqrt{d_k}, where dkd_k is read from the shape of Wq. Write it with matrix products rather than looping over pairs, 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.


That leaves the divisor, and it cannot be put off any longer. The third part of the last cell measures it without touching anything else: with queries and keys of dk=8d_k = 8, the largest weight in a row averages 0.5580.558, and with random vectors drawn the same way at dk=512d_k = 512 it climbs to 0.9830.983. A row like that has stopped being a mixture: it keeps one position and throws away the other five. This has had a name since the lesson on activation functions: the row is saturated. Dividing by dk\sqrt{d_k}, the maximum grows too, but slowly, from 0.3300.330 to 0.4860.486, and the row goes on sharing its weight out.

That root is not a constant someone kept tweaking until things came out right: it is calculated, and what has to be calculated is how much a dot product grows as coordinates are added to it. That calculation takes up the whole of the next lesson, on the scaled dot-product, and it deserves to be done slowly, because it is where two things this course left in different blocks meet again: the saturation that switches off a share-out and the gradient that vanishes on its way through one. Neither of them needed a sigmoid or a recurrence to appear.

Was this helpful?
Further reading2 sources · 1 paper, 1 article

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
    paperVaswani, Shazeer, Parmar and others, 2017arXiv:1706.03762EN

    The operation is its equation (1), and §3.2.3 says in one sentence that Q, K and V come from the same sequence. The paper never defends the projections; why the layer would copy itself without them is for you to work out.

  • The Illustrated Transformer
    articleJay Alammar, 2018jalammar.github.ioEN

    Draws the computation of Q, K and V and the weight grid, first vector by vector and then as matrices. It stays with the mechanics and leaves alone the symmetry and diagonal arguments used here to justify the three matrices.