Concepts / Causal attention and multi-head attention

Causal attention and multi-head attention

All context vectors are computed with one matrix multiplication: attn_weights @ inputs.

  • Programming

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.

python

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.

Worked example

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.

  1. 1
    Row 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].

  2. 2
    Row 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].

  3. 3
    Row 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].

  4. 4
    Assemble the tensor

    Stack the three results as rows. The printed output has one row per token, in the same order as the weights rows.

Result

[[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.

weights row 10.5, 0.25, 0.25context row 10.75, 0.5weights row 20.2, 0.6, 0.2context row 20.4, 0.8weights row 30.1, 0.1, 0.8context row 30.9, 0.9weighted sum of inputsweighted sum of inputsweighted sum of inputs
Which row of the attention-weights matrix combines the input rows, and how does each row of the output become one token's context vector?

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.

attn_weights3 rows, 3 columnsall_context_vecs3 rows, 2 columns@matrix productinputs3 rows, 2 columnsrows keptcolumns kept
Given a 3 x 3 weights matrix and a 3 x 2 inputs matrix, how do the inner dimensions cancel so the output has shape 3 x 2?

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.

RowBatched resultEarlier per-token vectorMatch?
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.

token 1sees token 1 onlytoken 2sees tokens 1 and 2token 3sees tokens 1 to 3earlier positionearlier position
For a three-token sequence, which input positions can each token draw from when attention is causal?

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 like

    Reading [0.75, 0.5] as three attention weights for the first token.

    Why it fails

    The output has the input width as its number of columns, so its rows hold blended input values, not weights.

    Do this instead

    Read 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 like

    Expecting a 3 by 3 result when multiplying a 3 by 3 weights matrix by a 3 by 2 inputs matrix.

    Why it fails

    The result takes its rows from the weights matrix and its columns from the inputs matrix, giving 3 by 2.

    Do this instead

    Write the shapes down and cancel the inner dimensions before running the line.

  • Common mistake

    Comparing rows in the wrong order

    What it looks like

    Matching the first output row against the second earlier vector.

    Why it fails

    Rows are aligned with tokens, so a mismatch in ordering creates false disagreements.

    Do this instead

    Compare 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
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.

MEDIUM

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.

Related concepts

More concepts starting with C