04

Multi-Head Attention

Why one attention pass is not enough, and how splitting into heads lets a layer track several relationships at once.

Key terms

TermMeaning
HeadOne independent attention computation with its own small Q, K, V slice
d_modelThe width of the model's vectors (here 4)
d_kThe width of each head's key/query vectors (here 2)
SplitReshaping the full projection into per-head slices (no new numbers)
ConcatGluing the heads' outputs back into one d_model vector
W_OThe output projection that mixes the concatenated heads

One head sees one relationship

On Day 03 you built a single attention pass: scores from Q·Kᵀ, scaled by √d_k, softmaxed into weights, then a weighted sum of the value vectors. That is exactly one head.

The problem: one head can only average the values one way. But language has many kinds of relationship at once. In "the cat that chased the mouse slept," a word needs to look back at its subject, at nearby adjectives, and at the verb it belongs to. A single averaging pass cannot serve all three at the same time.

Multi-head attention runs several of these passes in parallel. Each head gets its own slice of the query, key, and value space, so each head is free to learn a different relationship. One head might learn to look at the previous word, another at the sentence's subject. They never share weights, so they never collapse into the same behaviour.

Split, attend, concat, W_O

Here is the whole mechanism with tiny numbers. We use d_model = 4 and 2 heads, so each head has width d_k = 2. Take two tokens whose projected Q, K, V rows are already computed (each is 4 wide):

TokenQKV
t1[1, 0, 0, 1][1, 0, 1, 0][2, 0, 1, 1]
t2[0, 1, 1, 0][0, 1, 0, 1][0, 2, 1, 1]

Split. Head 1 takes columns 1-2, head 2 takes columns 3-4. Splitting only re-groups existing numbers; it creates nothing new.

Head 1 (cols 1-2)Head 2 (cols 3-4)
Q t1[1, 0][0, 1]
Q t2[0, 1][1, 0]
K t1[1, 0][1, 0]
K t2[0, 1][0, 1]
V t1[2, 0][1, 1]
V t2[0, 2][1, 1]

Attend (head 1). Run the Day 03 recipe with d_k = 2, so √d_k = √2 ≈ 1.414. For the t1 query the raw scores are Q·Kᵀ: against K-t1 [1,0]·[1,0] = 1, against K-t2 [1,0]·[0,1] = 0. Scale by 1/1.414, then softmax.

Queryscaled scoressoftmax weightsweighted sum of V
head1 t1[0.707, 0][0.670, 0.330][1.340, 0.660]
head1 t2[0, 0.707][0.330, 0.670][0.660, 1.340]

For head1-t1: 0.670·[2,0] + 0.330·[0,2] = [1.340, 0.660].

Attend (head 2). Same recipe on head 2's slices. Here both value rows are [1,1], so every weighted sum lands on [1,1] — a reminder that the output depends on the values a head attends over.

Queryscaled scoressoftmax weightsweighted sum of V
head2 t1[0, 0.707][0.330, 0.670][1, 1]
head2 t2[0.707, 0][0.670, 0.330][1, 1]

Concat. For each token, glue head 1's output next to head 2's output to rebuild a 4-wide vector:

Tokenconcat = head1 ‖ head2
t1[1.340, 0.660, 1, 1]
t2[0.660, 1.340, 1, 1]

W_O. The concatenated vector is mixed by one more matrix, W_O (4×4), so the heads can talk to each other before leaving the layer. Using

W_O rows:
[1, 0, 1, 0]
[0, 1, 0, 1]
[1, 0, 0, 1]
[0, 1, 1, 0]

each output is concat · W_O. For t1: out[0] = 1.340·1 + 0.660·0 + 1·1 + 1·0 = 2.340, and continuing across the columns:

Tokenoutput = concat · W_O
t1[2.340, 1.660, 2.340, 1.660]
t2[1.660, 2.340, 1.660, 2.340]

That 4-wide result is the multi-head attention output for each token — same width as the input, ready to add back into the residual stream (Day 05).

Counting the parameters

A common surprise: splitting into more heads does not add parameters. The projections W_Q, W_K, W_V are each d_model × d_model whether you use 1 head or 8 — the split is just a reshape of their output. Only four weight matrices carry parameters.

MatrixShapeParams
W_Q4×416
W_K4×416
W_V4×416
W_O4×416
Total64

The general rule is 4 · d_model² weights for a multi-head attention block (ignoring biases). For our d_model = 4 that is 4 · 16 = 64. Doubling the heads from 2 to 4 while keeping d_model = 4 would leave this count unchanged — you would just have four heads of width 1 instead of two of width 2.

Key takeaways

  • Multi-head attention runs several independent attention passes so a layer can track several relationships at once.
  • The flow is split -> attend per head -> concat -> W_O mix, and the output keeps the original d_model width.
  • Splitting is a reshape, not new math: heads carve up the existing projected vectors.
  • Parameters come only from W_Q, W_K, W_V, W_O — about 4 · d_model² weights, independent of head count.

Checklist

  • Can you split a 4-dim Q/K/V row into two 2-dim heads by hand?
  • Can you run the Day 03 attention recipe separately inside each head?
  • Can you concatenate two head outputs and apply W_O to get the final vector?
  • Can you explain why adding heads (at fixed d_model) does not change the parameter count?