TAI BUI
Back to curriculum
BuildPython

Multi-Head Attention

Split the embedding across several heads and the parameter count stays put, while each head gets its own attention pattern.

TIME: ~75 minutesPREREQUISITES: Phase 7 · 02 (Self-Attention from Scratch)

Learning Objectives

  • Explain why a single attention head loses signal and how splitting d_model across heads keeps the parameter count fixed.
  • Implement split_heads, per-head scaled dot-product attention, combine_heads, and the W_o output projection in plain Python.
  • Compare MHA, MQA, GQA, and MLA by how many K/V heads each keeps and what that does to KV-cache memory.
  • Choose d_head and n_heads for a given d_model using the rules of thumb from production models.

The Problem

Multi-head attention runs several attention functions in parallel, each with its own Q, K, V projections, and concatenates their outputs. Each head works in a smaller subspace of dimension d_model / n_heads, so total parameters stay the same while the layer can express more.

The motivation is what one head can't do. A single self-attention head computes one attention matrix, which captures one kind of relationship, typically the one that lowers loss on the training signal. If your data has subject-verb agreement, co-reference, long-range discourse, and syntactic chunking tangled together, a single head smears them into one softmax distribution and loses half the signal.

The 2017 Vaswani paper introduced the fix. In 2026 it is the default in transformers, and the live questions are how many heads to use and whether keys and values share projections (Grouped-Query Attention, Multi-Query Attention, Multi-head Latent Attention).

Warm-up0/3
1. You split a 512-wide hidden vector into 8 equal chunks, one per group of neurons. How wide is each chunk?Warm-up
A bar labelled 512 divided by seven accent lines into eight equal sections, each section marked with a question mark.
2. A softmax is applied across one row of attention scores. What does the resulting row look like?Warm-up
A row of five typed scores 2, 1, 0.1, -1 and 0.5, an arrow labelled softmax, and an output row of five boxes each holding a question mark.
Scores are typed for the picture, not captured.
3. A (6, 8) matrix is multiplied by an (8, 8) matrix. What shape is the result?Warm-up
A 6 by 8 matrix times an 8 by 8 matrix equals a dashed box whose shape is marked with question marks.
Check: The Problem0/3
1. What is an attention head in multi-head attention?Define
Diagram of the demo layer: an X matrix of shape 6 by 8 feeds two parallel lanes. Each lane has Q, K, V columns, a 6 by 6 attention matrix and a 6 by 4 output. The two outputs are stacked into a 6 by 8 matrix.
Layout follows main.py with n_heads=2 and d_model=8.
2. A sentence has agreement, co-reference, and phrase structure all at once. Each word must decide what to look at using only one weighted average over the other words. What is the likely consequence?Explain
The typed sentence The keys that Mia lost are in her bag with a blue arc from keys to are, an orange arc from Mia to her, and a green underline under that Mia lost. Below it, nine dashed boxes with question marks stand for one attention row.
Sentence typed for the picture.
3. Why does splitting d_model across n_heads keep the parameter count the same as one big head?Explain
Two panels. Left: one head with W_q of shape 512 by 512, 262,144 parameters. Right: eight heads, each with W_q of shape 512 by a question mark, total parameters a question mark.
d_model = 512 and 8 heads chosen for the picture; only the Q projection is counted.

The Concept

Multi-head attention splits, attends, concatenates

Split. Take X of shape (N, d_model). Project to Q, K, V, each of shape (N, d_model). Reshape to (N, n_heads, d_head) where d_head = d_model / n_heads, then transpose to (n_heads, N, d_head).

Attend in parallel. Run scaled dot-product attention inside each head. Each head produces (N, d_head). The heads work on different subspaces of the embedding and don't exchange information during the attention computation itself.

Concatenate and project. Stack the heads back to (N, d_model) and multiply by a learned output matrix W_o of shape (d_model, d_model). W_o is where the heads mix.

Why it works. Each head can specialize without competing with the others for representational budget. Probing studies from 2019–2024 found distinct head roles: positional heads, heads that attend to the previous token, copy heads, named-entity heads, and induction heads (which underlie in-context learning).

The 2026 lineage of variations:

VariantQ headsK/V headsUsed by
Multi-head (MHA)NNGPT-2, BERT, T5
Multi-query (MQA)N1PaLM, Falcon
Grouped-query (GQA)NG (e.g. N/8)Llama 2 70B, Llama 3+, Qwen 2+, Mistral
Multi-head latent (MLA)Ncompressed to low-rankDeepSeek-V2, V3

GQA is the modern default because it cuts KV-cache memory by a factor of N/G while keeping nearly full quality. MLA goes further: it compresses K/V into a latent space and projects back at compute time. That costs extra FLOPs and saves considerably more memory.

Check: The Concept0/13
1. What does d_head measure?Define
Two cards. The first shows d_model 8, n_heads 2 and d_head with a question mark; the second shows d_model 768, n_heads 12 and d_head with a question mark.
Values from the lesson's demo and its Use It table.
2. What is Multi-Query Attention (MQA)?Define
Eight blue query-head boxes Q0 to Q7 above a wide dashed box marked with a question mark for the K/V heads. A note says MQA is used by PaLM and Falcon.
N = 8 is chosen for the picture.
3. What is Grouped-Query Attention (GQA)?Define
Eight blue query-head boxes Q0 to Q7 above a wide dashed box marked with a question mark for the K/V heads. A note lists Llama 2 70B, Llama 3+, Qwen 2+ and Mistral as users.
N = 8 is chosen for the picture.
4. What is Multi-head Latent Attention (MLA)?Define
A token's hidden state feeds a dashed box labelled stored in cache with a question mark, which feeds an attend step that uses K and V. A note says MLA is used by DeepSeek-V2 and V3.
5. What is an induction head?Define
A row of nine typed token boxes: Ava, Kim, sat, a full stop, Bo, Lee, ran, a full stop, Ava, followed by an arrow to a box with a question mark for the next token.
Typed for the picture, not captured from a model.
6. What is W_o in multi-head attention?Define
Two head outputs of shape 6 by 4 are concatenated into a 6 by 8 matrix, which passes into a dashed box labelled W_o with an unknown shape, then to the next layer.
Dashed box: the shape of W_o is what the question asks. Sizes from the lesson's demo.
7. Probing studies cited in The Concept found distinct head roles. Which of these is one of them?Define
A grid of attention heads, four layers by eight heads, each drawn as a circle with a question mark inside.
Grid size is schematic, not a real model.
8. Why does the layer need W_o after the heads are concatenated?Explain
Two head outputs of shape 6 by 4 are concatenated into a dashed unknown shape, which passes into a dashed box labelled W_o and then to the next layer.
Dashed boxes mark what is not shown.
9. What distinguishes Grouped-Query Attention (GQA) from Multi-Query Attention (MQA) in the variant table?Explain
A four-row table of attention variants MHA, MQA, GQA and MLA with N query heads each and the K/V heads column showing question marks.
Rows from the lesson's variant table; the K/V column is hidden.
10. What does MLA (Multi-head Latent Attention), used by DeepSeek-V2/V3, trade to save cache memory beyond GQA?Explain
Two rows, GQA and MLA, each a write-to-cache box followed by an attend-step box. Below, two dashed boxes with question marks for the cache memory and compute comparison of MLA against GQA.
11. A model has N=32 query heads. Compared with full MHA, what is its KV cache if it uses MQA?Apply
A two-row table. MHA: 32 query heads, 32 K/V heads, KV cache 1x as baseline. MQA: 32 query heads, K/V heads and KV cache both question marks.
Head count from the stem.
12. A teammate concatenates the head outputs and feeds them straight to the next layer, with no W_o. What is lost?Analyze
Two 6 by 4 head outputs are concatenated into a 6 by 8 matrix that goes straight to the next layer, with no W_o step.
Sizes from the lesson's demo.
13. You are configuring a new 70B model and have not yet measured a KV-cache problem. Which attention variant fits best?Design
A situation card: model size 70B, status new and being configured, KV-cache problem not measured yet, attention variant a question mark.

Build It

Step 1: split heads from the single-head attention we already have

Start from the standalone scaled_dot_product_attention function from Lesson 02 and wrap it with a split/combine pair. code/main.py is pure stdlib: a small Matrix class (row-major floats) carries matmul, transpose, and softmax_rows, so there is no numpy. Splitting copies each head's contiguous block of columns into its own Matrix:

python
def split_heads(X: Matrix, n_heads: int) -> List[Matrix]:
    d_head = X.cols // n_heads
    heads = []
    for h in range(n_heads):
        H = Matrix(X.rows, d_head)
        for i in range(X.rows):
            for j in range(d_head):
                H.set(i, j, X.get(i, h * d_head + j))
        heads.append(H)
    return heads
 
def combine_heads(heads: List[Matrix]) -> Matrix:
    n, d_head = heads[0].rows, heads[0].cols
    out = Matrix(n, d_head * len(heads))
    for h, H in enumerate(heads):
        for i in range(n):
            for j in range(d_head):
                out.set(i, h * d_head + j, H.get(i, j))
    return out

A head is a column slice of width d_head, and combining writes the slices back side by side. These are nested loops over a List[Matrix]. NumPy or PyTorch does the same split with one reshape and one transpose, which is the shape bookkeeping The Concept describes.

Step 2: run scaled-dot-product attention per head

Each head gets its own slice of Q, K, V, so attention runs once per head in a plain Python loop:

python
def multi_head_attention(X, Wq, Wk, Wv, Wo, n_heads):
    Q = matmul(X, Wq)
    K = matmul(X, Wk)
    V = matmul(X, Wv)
    Qh = split_heads(Q, n_heads)
    Kh = split_heads(K, n_heads)
    Vh = split_heads(V, n_heads)
    head_outs, per_head_weights = [], []
    for q, k, v in zip(Qh, Kh, Vh):
        o, w = scaled_dot_product_attention(q, k, v)
        head_outs.append(o)
        per_head_weights.append(w)
    concat = combine_heads(head_outs)
    return matmul(concat, Wo), per_head_weights

In main.py the heads run one after another. On real hardware the same computation is one batched matmul of shape (heads, N, d_head) × (heads, d_head, N) -> (heads, N, N), so adding heads doesn't add a matmul. The demo uses n_heads=2 and d_model=8, so d_head=4.

Step 3: Grouped-Query Attention variant

Only the key and value projections change. Q keeps n_heads groups. K and V get n_kv_heads < n_heads groups, which are repeated to match Q by indexing into the smaller list:

python
Qh = split_heads(Q, n_heads)
Kh_small = split_heads(K, n_kv_heads)
Vh_small = split_heads(V, n_kv_heads)
repeat = n_heads // n_kv_heads
Kh = [Kh_small[i // repeat] for i in range(n_heads)]
Vh = [Vh_small[i // repeat] for i in range(n_heads)]

At inference this saves memory because only n_kv_heads copies live in the KV cache, not n_heads. Llama 3 70B uses 64 query heads with 8 KV heads, an 8× cache shrink. The GQA demo in main.py uses 4 query heads and 2 KV heads and prints the KV-cache element counts for both layouts, a 2× shrink.

Step 4: probe what each head learned

Run MHA on a short sentence and print the (N, N) attention matrix for each head. The main.py demo does this for the cat sat on the mat with 2 heads at d_model=8. The GQA demo prints no attention matrices, only the output shape and cache sizes. Different heads pick out different structure even with random initialization. Part of that is signal, and part is rotational symmetry in the subspaces.

Check: Build It0/11
1. What do split and combine do around the attention computation?Define
A left-to-right pipeline: an N by d_model matrix, a split step, a dashed unknown box, an attention box, another dashed unknown box, and a combine step.
Dashed boxes are the shapes the question asks about.
2. On real hardware, why does adding heads not add a separate matmul?Explain
A single-head attention product: Q of shape 6 by 4 times K transpose 4 by 6 gives a 6 by 6 score matrix with the note 1 matmul. Text below asks how many separate matmuls cover all heads.
Sizes from the lesson's demo.
3. In the `main.py` code from Build It Step 1, what does `split_heads` do to an `(N, 8)` matrix when `n_heads=2`?Apply
An N by 8 matrix with columns indexed 0 to 7, the call split_heads of X with n_heads equal to 2, and a dashed box marked returns with a question mark.
N = 6 chosen for the picture (the demo's sentence length).
4. How does `multi_head_attention` in `main.py` run attention across the heads?Apply
A code excerpt from multi_head_attention: three split_heads lines, an empty head_outs list, a commented gap marked with a question mark, then combine_heads and the return line.
Excerpt of the lesson's main.py with the middle lines removed.
5. In the main.py demo with d_model=8 and n_heads=2, what shape is one head's attention-weight matrix for 'the cat sat on the mat'?Apply
Six token boxes: the cat sat on the mat. Below, text stating N is 6 and d_head is 4, and a dashed box asking the shape of head 0's attention matrix with two question marks.
Sizes from the lesson's demo.
6. In main.py, two heads each produce a (6, 4) output. What shape does combine_heads return?Apply
Two 6 by 4 matrices, head 0 in blue and head 1 in orange, go through combine_heads into a dashed box with an unknown shape.
Sizes from the stem.
7. In the GQA code from Build It Step 3, there are 4 query heads, 2 KV heads, and `repeat = 2`. Which KV head does query head 3 read via `Kh_small[i // repeat]`?Apply
Four query-head boxes Q0 to Q3 above two KV-head boxes KV0 and KV1, the indexing line Kh equals Kh_small at i floor-divided by repeat, and a dashed box asking which KV head Q3 reads.
Sizes from the stem. No arrows are drawn.
8. A serving team moves from 64 KV heads to Llama 3 70B's layout of 64 query heads and 8 KV heads. What happens to KV-cache size, all else equal?Apply
A two-row table. Before: 64 query heads, 64 KV heads, KV cache 1x as baseline. Llama 3 70B style: 64 query heads, 8 KV heads, KV cache a question mark.
Head counts from the stem.
9. The GQA demo in `main.py` has 4 query heads and 2 KV heads. What cache ratio does it print?Apply
A terminal printout showing the GQA header with 4 Q heads and 2 KV heads, an output shape of 6 by 8, the MHA cache element count 96, and a GQA count shown as a question mark.
Captured from main.py, with the last line replaced.
10. A GQA implementation sets n_heads=4 and n_kv_heads=3, with repeat = n_heads // n_kv_heads. What goes wrong in the indexing Kh_small[i // repeat]?Analyze
Four query-head boxes Q0 to Q3 and three K/V-head boxes KV0 to KV2, the two code lines setting n_heads 4, n_kv_heads 3, repeat and the Kh list comprehension, and a dashed box marked indexing result with a question mark.
Sizes from the stem. No arrows are drawn.
11. In the main.py demo with random weights, one head shows a sharp, structured attention pattern. What can you conclude?Analyze
Two 6 by 6 heat-maps of attention weights over the cat sat on the mat from random untrained weights. Head 0 is spread out; head 1 has one bright cell in most rows.
Real run of the lesson's multi_head_attention with random weights, seed 358 (the lesson's own demo uses seed 42).

Use It

In PyTorch, MHA is one line:

python
import torch.nn as nn
 
mha = nn.MultiheadAttention(embed_dim=512, num_heads=8, batch_first=True)

GQA as of PyTorch 2.5+:

python
from torch.nn.functional import scaled_dot_product_attention
 
# scaled_dot_product_attention auto-dispatches Flash Attention on CUDA.
# For GQA, pass Q of shape (B, n_heads, N, d_head) and K,V of shape
# (B, n_kv_heads, N, d_head). PyTorch handles the repeat.
out = scaled_dot_product_attention(q, k, v, is_causal=True, enable_gqa=True)

How many heads? Rules of thumb from production models in 2026:

Model sized_modeln_headsd_head
Small (~125M)7681264
Base (~350M)10241664
Large (~1B)204816128
Frontier (~70B)819264128

In the table, d_head lands at 64 or 128. It sets how much one head can "see." Drop below 32 and heads start fighting the scaling factor sqrt(d_head). Go above 256 and you lose the "many small specialists" benefit.

Check: Use It0/6
1. What values does d_head almost always land at in the production-model table?Define
A four-row table of model sizes from Small (about 125M) to Frontier (about 70B) with their d_model and n_heads, and the d_head column showing question marks.
Rows from the lesson's Use It table; d_head is hidden.
2. Why does Use It warn against d_head below 32 and above 256?Explain
A log-scale axis of d_head from 8 to 512 with orange dots at 64 and 128 for the table rows, dashed red lines at 32 and 256, and dashed boxes marked with a question mark for what goes wrong below 32 and above 256.
Positions of 64 and 128 come from the Use It table.
3. You fix d_head at 128 for a model with d_model=2048. How many heads does that give you?Apply
A bar for d_model 2048 whose first segment is labelled 128, with text that the rest is heads of the same width, and a dashed box asking for n_heads.
The first segment is drawn to scale (128 of 2048).
4. How does PyTorch's `scaled_dot_product_attention` handle GQA in Use It?Apply
A code excerpt: q has shape (B, n_heads, N, d_head), k and v have (B, n_kv_heads, N, d_head) with n_kv_heads less than n_heads, and a call to scaled_dot_product_attention whose last argument is a question mark.
Shapes and call as in the lesson's Use It snippet, last argument hidden.
5. A config has d_model=512 and n_heads=32, and training looks unstable. Which rule of thumb does it break?Analyze
A config card with d_model 512, n_heads 32, d_head a question mark, and training looks unstable.
6. You have d_model=4096, want 128-wide heads, and plan GQA with 8 K/V heads. Which configuration results?Design
A config card with d_model 4096, d_head 128 and n_kv_heads 8, plus n_heads and the cache comparison with MHA as question marks.

Ship It

outputs/skill-mha-configurator.md is a skill that recommends head count, kv-head count, and projection strategy for a new transformer. Give it your parameter budget, sequence length, and deployment target.

Author's Note

If I were configuring a new model today, I'd start from GQA and justify going anywhere else. The cache shrink is the reason: with 64 query heads and 8 KV heads you hold an eighth of the K/V in memory, and The Concept puts quality at nearly full. I'd reach for MLA only after the KV cache was the measured problem, since it trades extra FLOPs for the memory.

I'd keep d_head at 64 or 128 and let n_heads fall out of d_model from there. Every row in the Use It table lands on one of those two, and the warning about the sqrt(d_head) scaling factor below 32 is enough for me not to experiment in that direction without a reason.

I'd treat Step 4 as a shape check. main.py prints per-head attention matrices on random weights, so a head that looks like it found structure there only tells you that your split and per-head attention run, not what a trained head does. For that, read the induction-head paper in Further Reading.

Exercises

  1. Easy. code/main.py has no training loop, so this one is an extension you write. Build a tiny one-layer model around the MHA in main.py, train it on a synthetic copy task, and sweep n_heads from 1 to 16 with d_model=64 fixed. Plot the loss. Do more heads help, plateau, or hurt?
  2. Medium. Implement MQA (one KV head shared across all query heads). Measure how much parameter count drops vs full MHA. Compute how much the KV-cache size shrinks at inference for N=2048.
  3. Hard. Implement a tiny version of Multi-head Latent Attention: compress K,V to a rank-r latent, store the latent in the KV cache, decompress at attention time. At what r does cache memory cross below 1/8 of full MHA while quality stays within 1 bit of validation ppl?

Key Terms

TermWhat people sayWhat it actually means
Head"A single attention circuit"One Q/K/V projection of dimension d_head = d_model / n_heads with its own attention matrix.
d_head"Head dimension"Per-head hidden width; 64 or 128 in the production models above.
Split / combine"Reshape tricks"Moves between (N, d_model) and (n_heads, N, d_head) around attention: column slices in main.py, reshape+transpose in numpy or PyTorch.
W_o"Output projection"(d_model, d_model) matrix applied after concatenating heads; where heads mix.
MQA"One KV head"Multi-Query Attention: single shared K/V projection. Smallest KV cache, some quality loss.
GQA"The default since Llama 2"Grouped-Query Attention with n_kv_heads < n_heads; repeats to match Q.
MLA"DeepSeek's trick"Multi-head Latent Attention: K,V compressed to low-rank latent, decompressed at attend time.
Induction head"The circuit behind in-context learning"A pair of heads that detect previous occurrences and copy what followed them.

Further Reading

Code

Ships With