- Key idea
- Q = XW_Q, K = XW_K, V = XW_V; scores QKᵀ/√d; causal mask; softmax by row; output AV
- Real world
- A room of people at a networking event. Each person holds a card saying what they’re looking for (query) and wears a badge saying what they offer (key). Everyone walks towards the badges that match their card, and listens to those people (values) in proportion.
- In an LLM
- In “The cat sat on the”, the last “the” has a query that looks for a noun. The key of “cat” matches it best, so the output for “the” is mostly cat’s value: the model has carried information about the cat forward to the position that will predict the next word.
The recipe
The previous page described attention as a soft lookup. Here is the exact computation one attention head does. The input is a matrix with one row per token (5 tokens × 4 numbers in our toy model; thousands of numbers in a real one).
- Project. Multiply by three learned matrices to get three new matrices, each with one row per token:
- : each token’s query, what it is looking for.
- : each token’s key, what it contains.
- : each token’s value, what it passes on.
- Score. : the dot product of every query with every key. Entry says how well token ’s query matches token ’s key.
- Scale. Divide by , where is the length of a query.
- Mask. Set every score where comes after to , so tokens can’t see the future.
- Softmax each row, turning scores into weights that are positive and add up to 1.
- Mix. : each token’s output is the weighted average of the value rows.
where is the mask: 0 where looking is allowed, above the diagonal.
Try it
This is a real attention head with hand-picked weights so you can read it. The four embedding numbers mean thing, action, small word and first token. Press play, then click a token to follow its row, and hover any cell of a result to see exactly how it was computed.
Reading the matrices
Follow the last “the” through the steps:
- Its embedding is mostly small word. maps small word onto the first query dimension, so its query says “I’m looking for a thing”.
- maps thing onto that same first dimension, so “cat” has a large first key number.
- Their dot product is the largest score in the row. After scaling, masking and softmax, “the” gives about half of its attention to “cat”.
- The output row for “the” is mostly cat’s value: a vector that says thing.
Nobody wrote these rules into a real model. , and start random and are learned by gradient descent, because moving the right information to the right place lowers the next-token loss. Real heads learn patterns like “look at the previous token”, “look at the subject of the sentence” or “look at what followed this token last time”: see real attention patterns.
Everyone at the event has a card in their hand saying what they are looking for (“investors”), and a badge saying what they are (“founder”). Each person compares their card with every badge in the room, walks towards the best matches, and listens to them, more to the better matches. What they hear is the value: not the badge itself, but what that person has to say. The badge (key) decides who you listen to; the value decides what you learn.
In “The cat sat on the mat because it was tired”, the token “it” needs to know what it refers to. In GPT-2 small, head 3 of layer 4 gives 84% of the attention from “it” to “cat”, more than to “mat” or anything else. Its query for “it” matches the key of “cat”, so cat’s value, information about the cat, is added at the position of “it”. Later layers can then predict words that suit a cat.
Every token at once
The widget follows one row, but the matrices compute every token’s attention in one go. That is why attention runs fast on GPUs, which are built for big matrix multiplications, and why training can process all positions of a sequence in parallel. The mask is what makes this safe: every row only uses the rows above it.
Shapes
With tokens, model width and head size :
| Matrix | Shape | In the widget | GPT-2 small, 1 head |
|---|---|---|---|
| 5 × 4 | T × 768 | ||
| 4 × 3 | 768 × 64 | ||
| 5 × 3 | T × 64 | ||
| 5 × 5 | T × T | ||
| 5 × 3 | T × 64 |
The score matrix is why attention gets expensive for long texts: double the length and it has four times as many entries. See what a forward pass costs.
Per-row form
For a single query position :
In code
import torch, math
def attention(X, Wq, Wk, Wv):
Q, K, V = X @ Wq, X @ Wk, X @ Wv # (T, d) each
S = Q @ K.T / math.sqrt(Q.shape[-1]) # (T, T)
T = X.shape[0]
mask = torch.triu(torch.ones(T, T, dtype=torch.bool), diagonal=1)
S = S.masked_fill(mask, float("-inf"))
A = S.softmax(dim=-1) # each row sums to 1
return A @ V # (T, d)In practice you call torch.nn.functional.scaled_dot_product_attention(Q, K, V, is_causal=True), which computes the same thing with a fused kernel (FlashAttention) that never stores the full matrix.
In papers
- Scaled dot-product attention: Vaswani et al., 2017, section 3.2.
- The query–key–value view and its history (attention for translation): Bahdanau et al., 2014.