Transformers from the ground up
Attention by hand
Attention is three projections and a weighted average. Every token asks a question, every token advertises what it knows, and a softmax decides who answers.
Three vectors per token
Each token’s vector is projected three ways. The query is what this position is looking for. The key is what this position can offer. The value is what gets passed on if the offer is accepted. The projections are learned matrices. The same input can therefore ask different questions in different layers.
A sequence of n tokens gives three matrices of n rows. Attention compares every query with every key. That is where the n × n score matrix comes from. Nothing in the operation is sequential. All scores can be computed at once. So the layer runs well on a GPU.
The projections are the only learned parameters in this calculation. The rest is arithmetic: a dot product, a division by the square root of the key width, a softmax and a weighted sum. So an attention block is easy to implement and hard to interpret. The complexity lives in the learned matrices, not in the dozen lines that apply them. A correct implementation tells you nothing about what the weights mean.
Scores, scale, softmax
A score is a dot product between one query and one key. A dot product multiplies matching entries and adds the results. Dot products grow with the dimension of the vectors. A large score makes the softmax nearly one-hot, meaning one weight takes almost all the mass. The other positions then get almost no gradient. Dividing by the square root of the key dimension keeps the variance roughly constant as the width grows.
The scaled scores go through a softmax along the key axis. Each row becomes a distribution over the tokens it is allowed to see. The output for that position is the weighted sum of the value rows. A causal mask sets future scores to negative infinity before the softmax. Those positions then get weight zero, not a small weight.
Softmax is applied to each row on its own. One token’s distribution does not constrain another. That independence is what makes the layer parallel across positions. It also means a row can be sharp while its neighbour is flat. The same head can act like a lookup for one token and an average for the next.
The fused version
import torch
import torch.nn.functional as F
q = torch.randn(2, 8, 16, 64) # batch, heads, tokens, head_dim
k = torch.randn(2, 8, 16, 64)
v = torch.randn(2, 8, 16, 64)
out = F.scaled_dot_product_attention(q, k, v, is_causal=True)
print(out.shape) # (2, 8, 16, 64)One query, three keys
Run the attention calculation, then change the query so it points at the second dimension and watch the weights move from “the” to “cat”.
A weighted average, not a lookup
The output is a weighted average of the value rows. Every weight is between zero and one, and the row sums to one. If the distribution is flat, the result is close to the mean of the values. If one score dominates, the result is close to that single value. Most useful attention sits somewhere between those extremes.
Attention therefore cannot copy information that is not in the values. It can select and blend. The content has to be present somewhere in the sequence. A retrieval layer is a different mechanism. Attention is a routing mechanism inside a fixed set of tokens.
The weights are a soft alignment between positions, not a hard pointer. A token can take most of its value from one neighbour and the rest from another. The mix changes with the query. That flexibility lets a head represent a relation that is not exactly one-to-one. A pronoun that depends on two earlier nouns is one example. The head does not have to choose a single source.
The score matrix is n × n per head. Doubling the context quadruples that matrix, which is why long-context models spend so much of their budget on attention memory rather than on parameters.
What the weights do not mean
A high attention weight is tempting to read as an explanation: this token caused that output. The weights are one factor among many. The value vectors, the output projection and the residual stream all change what reaches the next layer. Different heads and different layers produce different patterns for the same input.
Attention maps are still useful. They show where a head routes information. A mask that leaks the future shows up at once as a suspiciously uniform row. Treat them as instrumentation. They are not a story about why the model answered the way it did.
A mask that hides the future
Run the masked softmax for three tokens, then remove the mask and confirm the first row changes because it can now see the last token.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does the query vector represent?
