One Head Isn't Enough
One attention head is a single weighted average — it can track meaning or grammar, not both. So we split the model width into parallel heads. Then two twists the formula hides: self vs cross attention (same math, different source for K and V), and the causal mask that blindfolds a decoder from the future — plus the silent leakage bug that keeps train loss looking perfect while the model quietly cheats.
HYPOTHESIS
H₀METHOD
Last experiment we built one attention head: softmax(QKᵀ/√d_k)·V, and watched “bank” reach across a sentence and become river-bank. That single head felt like the whole story. It isn’t. This experiment attacks a two-part hypothesis I walked in believing:
“One head is enough, and a word may look anywhere in the sentence it likes.”
Both halves are wrong, and they fail for opposite reasons — one is a capacity problem, the other a cheating problem. We fix the first with multi-head attention, the second with a causal mask, and along the way meet cross-attention and one of the nastiest bugs in the whole architecture. Verdict up front: FAR FROM IT.
ONE AVERAGE CAN’T POINT TWO PLACES
interactiveA single head produces exactly one attention distribution — one set of weights, one weighted average of the values. But look at what “bank” actually needs in “the river bank was steep”: it has to attend to river to fix its meaning, and to was to satisfy subject–verb grammar, at the same time. One average can’t peak in two places. Force it and you get a blurry compromise that’s good at neither.
↑ Drag the heads dial. At 1 head, one tangled fan of arcs tries to serve all three relations at once — mush. Add heads and each relation peels off into its own clean specialist. Click a head chip to isolate it.
The fix is almost embarrassingly simple: don’t make one big head, make h small ones. Split the model width d_model into h slices of d_model/h, give each its own W_Q, W_K, W_V, run attention in parallel, then concatenate the outputs and mix them with one more matrix W_O:
Note the accounting: we didn’t add width, we sliced it. Eight heads over a 512-dim model are eight 64-dim attentions, not eight 512-dim ones. Same parameter budget — spent on specialists instead of one generalist.
SAME MATH, DIFFERENT SOURCE — SELF VS CROSS
interactiveSo far every word has queried its own sentence — self-attention. But think about translation: to produce the French word “rivière”, the decoder needs to look back at the English “river” it came from. The query lives in one sequence, the keys and values in another. That’s cross-attention — and here’s the punchline: it’s the exact same operation. The only thing that changes is where K and V come from.
↑ Press ▶ generate and watch the decoder write French one token at a time. In cross mode each new word reaches up into the English source it came from; toggle to self and it attends only to the French already written. One formula, two wirings.
Self-attention lets a sequence enrich itself; cross-attention lets one sequence read another. Encoders use self-attention to understand the input; a translation decoder uses both — self-attention over what it’s written so far, then cross-attention into the source. Same softmax(QKᵀ/√d_k)·V, different plumbing.
THE BLINDFOLD — WHY A DECODER NEEDS A MASK
interactiveNow the second half of the hypothesis — that a word may look anywhere. For a model generating text one token at a time, that’s fatal. If word 3 is allowed to attend to word 5 during training, it can just copy the answer it’s supposed to predict. At inference word 5 doesn’t exist yet, so the model face-plants. A generator must only ever look backward.
We enforce it by adding a mask M to the scores before softmax, where every future position (column j > row i) is set to −∞:
Why −∞ and not just 0? This is the trap. Softmax exponentiates: e^0 = 1, so a “blocked” score of 0 still hands the future token a full unit of weight. Only e^−∞ = 0 makes it truly vanish. Zeroing the score looks masked and isn’t.
↑ Toggle the three modes. “no mask” leaks ~40% of attention into the future. ”−∞” drives future leakage to a true 0%. “×0” looks masked but still leaks — because softmax(0)=1.
THE LEAKAGE TRAP
Here’s the mock question that caught me. “Your language model trains beautifully — train and validation loss both drop and stay low — but generated text is garbage. What happened?” My first instinct was overfitting. Wrong, and the giveaway is that validation loss looks fine too.
During training we use teacher forcing: at every position the model is fed the true previous tokens. If the mask leaks, the model can peek one step ahead at the real next token — so it learns the trivial “copy what you were shown” shortcut, and its loss on both train and validation drops to near-zero. Nothing in the loss curve can reveal the cheat, because validation is scored the same leaky way. The illusion only shatters at autoregressive inference, where the model must feed on its own outputs and the crib sheet is gone.
So you can’t debug this with a metric — you debug it structurally. Print the attention matrix (or its gradients) and look at the strict upper triangle: it must be exactly zero. Any non-zero weight there — the classic cause being someone wrote scores.masked_fill(mask, 0) instead of −inf — is the leak. It’s a bug you find by staring at the triangle, not at the curve.
- ①Both train and val loss look healthy → rules out ordinary overfitting (which spreads the two apart). Healthy-everything + broken generation is the fingerprint of train/inference mismatch.
- ②The diagnostic is a heatmap, not a number: strict-upper-triangle attention (or gradient) must be 0. Non-zero = leakage.
THE WHOLE PICTURE
Three placements of one operation, distinguished only by where the query and key/value sequences live and whether the future is masked:
| Placement | Query from | Key / Value from | Sees future? | Lives in |
|---|---|---|---|---|
| Encoder self-attn | source | source | yes (full) | BERT / encoder |
| ★ Decoder self-attn | target so far | target so far | no — masked | GPT / decoder |
| Cross-attn | target (decoder) | source (encoder) | n/a | translation decoder |
And each of those runs multi-head underneath — a stack of parallel specialists, concatenated and mixed by W_O. Capacity from the heads; honesty from the mask.
WHERE THIS LIVES IN THE TRANSFORMER
interactiveTime to place everything on the original blueprint. This is Figure 1 of Attention Is All You Need (Vaswani et al., 2017) — the whole machine. The three attention blocks we’ve now built by hand are lit in coral; hover any block to see its job, and click a lit one to jump to the experiment that derives it.
↑ Three attention blocks, one operation. The only dashed block left in our path is positional encoding — the subject of the next experiment.
CONCLUSION
One head is not enough: a single distribution is a single weighted average, and it buckles the instant a word needs two relations — meaning and grammar — at once. Real attention runs h heads in parallel, each free to specialise (thanks only to different random starts), then concatenates them through W_O. Same width, spent on specialists.
A word may not look anywhere: a decoder generating text must be blindfolded from the future with an additive −∞ mask — not a multiply-by-0, because softmax(0)=1 leaks. Get it subtly wrong and teacher forcing will hide the bug behind a perfect-looking loss curve until the day you actually generate. Multi-head for capacity, the causal mask for honesty.
WHAT NEXT
One thing we’ve quietly assumed the whole time: that attention knows the order of the words. It doesn’t. A dot product doesn’t care whether “river” came before or after “bank” — shuffle the sentence and self-attention returns the same answer, just permuted. It’s permutation-invariant. So how does a transformer tell “dog bites man” from “man bites dog”? Next experiment: positional encoding — how order gets injected back in (EXP-015).