06 · Attention
Self-Attention
When the queries, the keys, and the values are all learned projections of the same sequence.
What a word looks like to the model
Five chapters of vectors, and nothing has said where they come from. To the model a word is a row of numbers it learns to understand. Every word in the vocabulary gets its own vector of length
Stack the embeddings of the five words as rows and the result is the sentence matrix
So
Where Q, K and V come from
Each of the three matrices every formula so far has taken for granted is made by multiplying
That is where the self in self-attention comes from: the queries, the keys and the values are all built out of the same sequence.
The shapes follow from what each one has to do:
is , one row per word. and are , so and come out . is , so comes out .
The projections are row-wise. Row
Three matrices, and what training changes
The whole mechanism fits on one page, with one thing marked that no earlier figure has shown: which parts are learned and which are recomputed at every step.
The dictionary chapter said a key and a value are allowed to be different things, without much reason to care. Here is the reason.
There is a sharper argument. Suppose
is symmetric: entry
The questions being asked
The projections also change the width.
Which raises the question this chapter is named after. If the sizes are chosen and training chooses the contents, what does a word end up asking? A query is a vector, and
Take The cat chased the mouse. Something has to work out that the thing doing the chasing is the cat and the thing being chased is the mouse, rather than the reverse. Nobody writes that rule down. The query belonging to chased is whatever
Seeing it run
The four steps of the breakdown are four lines of a function.
def attention(X, W_q, W_k, W_v):
"""One head of self-attention. Returns the output and the weights."""
Q, K, V = X @ W_q, X @ W_k, X @ W_v # project the same X three ways
scores = Q @ K.transpose(-2, -1) # every query against every key
d_k = Q.shape[-1]
weights = torch.softmax(scores / d_k**0.5, dim=-1) # scale, then rows
return weights @ V, weights # blend the values
The interesting experiment is a task that can only be solved by looking something up, given to a single head that is never told how. Each training example is a tiny store: four entries, each a pair of small integers, a tag and a value; one query tag; and a loss that mentions only the final answer. The store is different in every example, so nothing can be memorised.
The detail that matters is how an entry reaches the model. Each entry is a single token whose embedding is the tag's embedding plus the value's embedding, added together. The query is a token holding only a tag embedding. Nothing marks which part of a token is a tag and which is a value. For the task to be solved,
Watching one fixed example during training, with the store tags [0, 2, 1, 3], values [3, 5, 5, 1] and the query tag 2, so the answer lives in entry 1:
| step | loss | acc | e0 | e1 | e2 | e3 |
|---|---|---|---|---|---|---|
| 0 | 1.800 | 0.20 | .29 | .25 | .20 | .09 |
| 25 | 1.047 | 0.80 | .00 | .73 | .00 | .00 |
| 100 | 0.002 | 1.00 | .00 | .95 | .00 | .00 |
| 400 | 0.000 | 1.00 | .00 | .98 | .00 | .00 |
At step 0 the attention is close to uniform: the head has no idea where to look, which is what random
One head asks one question per word. The next chapter runs several at once, so that different heads can ask different things about the same sentence.