LIVE · BENGALURU EST. 2024
EXP #014 At At Q-04 · Transformers from Scratch · step 2 ⚠ Learning

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.

2026-07-14 12 MIN READ COMPLETE
01

HYPOTHESIS

H₀
H₀ One attention head is enough, and a word may look anywhere in the sentence it likes.
02

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.

03

ONE AVERAGE CAN’T POINT TWO PLACES

interactive

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

query = "bank" · arcs = who it attends to · drag the dial to add heads
theriverbankwassteep
heads = 1
💀 one head, three jobs — it averages semantics + grammar + determiner into a single tangled arc-fan. It can't point at "river" AND "was" cleanly.
head 1: semantics + grammar + determiner

↑ 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:

MultiHead(Q,K,V)=Concat(head1,,headh)WO\text{MultiHead}(Q,K,V) = \text{Concat}(\text{head}_1, \dots, \text{head}_h)\,W_O

headi=Attention(QWQi,  KWKi,  VWVi)\text{head}_i = \text{Attention}(QW_Q^i,\; KW_K^i,\; VW_V^i)

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.

SPECIMEN · why heads don't all learn the same thing 100×

Nothing in the math forces heads to differ — the formula is identical for every head. They diverge for one humble reason: each starts from a different random initialisation, and gradient descent nudges them down different valleys. Empirically, distinct jobs emerge — a head that tracks the previous word, a head that does coreference, a head that watches punctuation. But it’s encouraged, not enforced: heads overlap, and some end up redundant. Diversity here is a happy statistical accident of random starts, not a guarantee.

04

SAME MATH, DIFFERENT SOURCE — SELF VS CROSS

interactive

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

SOURCE · English (encoder)TARGET · French (decoder)theriverflowslarivièrecoule
generating "la" (1/3) — it reaches into the English source and leans on "the". Q from the decoder, K & V from the encoder.

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

05

THE BLINDFOLD — WHY A DECODER NEEDS A MASK

interactive

Now 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 −∞:

Attention=softmax ⁣(QKdk+M)V,Mij={0jij>i\text{Attention} = \text{softmax}\!\left(\frac{QK^{\top}}{\sqrt{d_k}} + M\right)V, \qquad M_{ij} = \begin{cases} 0 & j \le i \\ -\infty & j > i \end{cases}

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.

IsatonthebankIsatonthebank0.350.370.180.060.040.220.370.260.100.050.120.290.320.180.090.070.190.290.260.190.060.130.220.270.33
attention leaking to future tokens (upper triangle): 38%
no mask → every word reads ahead. Useless for generation — it can just copy the answer.

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

06

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.

why a healthy loss curve lies

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

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:

DATA TABLE n=5
PlacementQuery fromKey / Value fromSees future?Lives in
Encoder self-attnsourcesourceyes (full)BERT / encoder
★ Decoder self-attntarget so fartarget so farno — maskedGPT / decoder
Cross-attntarget (decoder)source (encoder)n/atranslation 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.

08

WHERE THIS LIVES IN THE TRANSFORMER

interactive

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

Attention Is All You Need — the whole machine · click any lit block to jump to the experiment that builds it
ENCODER ×N
Add & Norm
Feed Forward
Add & Norm
Multi-Head AttentionEXP-013/14
⊕ Positional EncodingEXP-015 · soon
Input Embedding
Inputs
Output Probabilities
Softmax
Linear
DECODER ×N
Add & Norm
Feed Forward
Add & Norm
Multi-Head AttentionEXP-014
Add & Norm
Masked Multi-Head AttentionEXP-014
⊕ Positional EncodingEXP-015 · soon
Output Embedding
Outputs (shifted right)
↑ encoder K, V flow → into the decoder's cross-attention
built in this quest coming next (EXP-015) foundational
Encoder self-attention, the decoder's masked self-attention, and the encoder→decoder cross-attention — all three are the same softmax(QKᵀ/√d_k)·V, run multi-head. Positional encoding (dashed) is the one piece still missing.

↑ Three attention blocks, one operation. The only dashed block left in our path is positional encoding — the subject of the next experiment.

09

CONCLUSION

⚠ FAR FROM IT
Both halves of the hypothesis fall — for opposite reasons.

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.

10

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