Causal attention and multi-head attention
All context vectors are computed with one matrix multiplication: attn_weights @ inputs.
Why One Multiplication Can Replace a Loop
Earlier, a context vector was built for one input token at a time. That approach works, but it means repeating the same steps for every position in a sequence. This article shows how the attention weights you have already computed can be applied to all the inputs in a single operation, producing a full set of context vectors together.
The whole computation is one line. It takes the attention weight matrix named attn_weights, multiplies it by the inputs using the @ operator, stores the result in all_context_vecs, and prints it. Before reading further, run that line and look at the printed tensor, then predict what it should look like. The rest of this article uses that output to reason backward about the operation that produced it.
Tracing One Matrix Product by Hand
Before trusting the one-line version, trace what it does on a small case. Suppose there are three tokens, each represented by a two-number input vector, and the attention weights for each token are three numbers that sum to 1. The values below are invented for teaching purposes; they are not from a real model.
Computing all three context vectors in one product
Inputs are the rows [1, 0], [0, 1], and [1, 1]. The attention weights matrix has rows [0.5, 0.25, 0.25], [0.2, 0.6, 0.2], and [0.1, 0.1, 0.8]. Compute attn_weights @ inputs.
- 1Row 1 of the result
Take the first weights row [0.5, 0.25, 0.25]. Each weight scales one input row, and the scaled rows are summed: 0.5 times [1, 0] plus 0.25 times [0, 1] plus 0.25 times [1, 1] gives [0.75, 0.5].
- 2Row 2 of the result
Take the second weights row [0.2, 0.6, 0.2]: 0.2 times [1, 0] plus 0.6 times [0, 1] plus 0.2 times [1, 1] gives [0.4, 0.8].
- 3Row 3 of the result
Take the third weights row [0.1, 0.1, 0.8]: 0.1 times [1, 0] plus 0.1 times [0, 1] plus 0.8 times [1, 1] gives [0.9, 0.9].
- 4Assemble the tensor
Stack the three results as rows. The printed output has one row per token, in the same order as the weights rows.
[[0.75, 0.5], [0.4, 0.8], [0.9, 0.9]]. Each row is one context vector.
Reading Rows as Context Vectors
Each row of the output tensor holds one context vector. Row number one belongs to the first input token, row number two belongs to the second input token, and so on. The rows of the result are aligned with the rows of the attention weight matrix, because each row of attention weights describes how much one token attends to every input token.
The key idea is that a single matrix product replaces a loop over tokens. For each row of the attention weights, the product forms a weighted combination of the input vectors. Doing this for all rows at once gives all context vectors together. The diagram below shows how each weights row produces exactly one output row.
Predicting the Shape of the Result
You can predict the output shape before running the program. Let the attention weights be a $T \times T$ matrix, one row and one column per token, and let the inputs be a $T \times d$ matrix, one row per token with $d$ values each. The inner dimensions match, the shared $T$ is consumed by the product, and what remains is the outer pair.
In the 3 by 3 times 3 by 2 example, the middle 3 is the inner dimension and it cancels. The first matrix contributes its row count, 3, and the second contributes its column count, 2, so the result is 3 by 2. In symbols: $(3 \times 3)(3 \times 2) = (3 \times 2)$.
Checking a Row Against an Earlier Vector
A useful check is to compare one row of the batched result with a context vector computed earlier by a different route. Earlier, the second context vector was computed one token at a time. Its second row in the batched result should match that vector. Printing the earlier vector alongside the new tensor gives a direct comparison.
| Row | Batched result | Earlier per-token vector | Match? |
|---|---|---|---|
| Row 2, correct run | [0.4, 0.8] | [0.4, 0.8] | Yes, every value agrees |
| Row 2, faulty run | [0.4, 0.7] | [0.4, 0.8] | No, the second value differs |
Comparing one batched row with a reference vector to find where it diverges
Compare values position by position, starting at the first value. The first position where the two vectors disagree tells you which token and which weight produced the error. In the faulty row, the first value agrees and the second does not, so the problem lies in the weights row for that token or in the product that formed its entries.
The pattern of disagreement also points to the cause. This check is a debugging technique, not just a confirmation. If a single row disagrees, the problem is in the batched path for that token, and you should inspect the attention weights in that row. If every row disagrees, the problem is more likely in the shared inputs or in how the weights were built.
Restricting Each Token to Earlier Positions
Causal attention is the later stage that restricts which tokens each position can draw from. Each token may use itself and the tokens before it, but not tokens that come later in the sequence. In the attention weights matrix, this means the entries that would connect a token to a future position are excluded, so each row only blends the allowed inputs.
Extending the Simplified Mechanism with Trainable Weights
The mechanism you have just run is a simplified version of attention. The next step adds trainable weights, which lets the model learn from data and improve its performance on specific tasks. This extended mechanism is self-attention, also called scaled dot-product attention. It is the self-attention used in the original transformer architecture, in GPT models, and in most other popular large language models.
The source sequence of attention variants is built step by step. The simplified attention mechanism comes first, followed by self-attention with trainable weights. The later stages add causal attention and multi-head attention. Multi-head attention runs several attention computations in parallel and combines their results into one context vector per token; this article does not walk through its internal steps, but it builds directly on the single matrix product described above.
Mistakes When Reading the Batched Result
Common mistake Treating each output row as a weight instead of a context vector
What it looks likeReading [0.75, 0.5] as three attention weights for the first token.
Why it failsThe output has the input width as its number of columns, so its rows hold blended input values, not weights.
Do this insteadRead the weights matrix for the blend coefficients and the output rows for the resulting context vectors.
Common mistake Predicting the output shape from the wrong operand
What it looks likeExpecting a 3 by 3 result when multiplying a 3 by 3 weights matrix by a 3 by 2 inputs matrix.
Why it failsThe result takes its rows from the weights matrix and its columns from the inputs matrix, giving 3 by 2.
Do this insteadWrite the shapes down and cancel the inner dimensions before running the line.
Common mistake Comparing rows in the wrong order
What it looks likeMatching the first output row against the second earlier vector.
Why it failsRows are aligned with tokens, so a mismatch in ordering creates false disagreements.
Do this insteadCompare the batched row with the same token's earlier vector, row by row.
Common mistake Assuming one matching row proves the whole batch is correct, when other rows may still use the wrong weights
Practice: Predict the Result Shape
What do you think happens?
If attn_weights is a 4 by 4 matrix and inputs is a 4 by 3 matrix, what is the shape of all_context_vecs?
Reveal answer
4 by 3, that is $(4 \times 4)(4 \times 3) = (4 \times 3)$
The inner 4 cancels. The rows come from attn_weights and the columns come from inputs, so there is one context vector per token, each of width 3.
Your batched result has rows [0.75, 0.5], [0.4, 0.7], and [0.9, 0.9]. An earlier per-token computation for token 2 gave [0.4, 0.8]. Which row do you inspect first, and which value in that row is the first mismatch?
Hints
- Match the row to the token it belongs to, using the same ordering as the weights rows.
- Compare values position by position, starting from the first value.
Key Takeaways
- All context vectors are computed with one matrix multiplication,
attn_weights@ inputs, which replaces a loop over tokens. - Each output row is one token's context vector and lines up with the matching row of the attention weights.
- The shape of the result follows from the operands: (T x T) times (T x d) gives (T x d), because the inner dimension cancels.
- To locate an error, compare a batched row against an earlier per-token vector and find the first mismatched value.
- Self-attention with trainable weights, also called scaled dot-product attention, extends this mechanism, and causal and multi-head attention are built on top of it.