§3.3Multi-Headed Attention

Part I NLPT pp. 11–14 · ~6 min read

  • multi-head attention
  • attention head
  • query
  • key
  • value

At the end of the last section we hit a wall: with query = key = value, a token attends mostly to itself. The escape is to give the layer room to specialize — and then to run several such specialists in parallel. That is multi-headed attention .

One head, three projections

A single attention head applies three independent learned linear projections to each embedding, producing distinct query , key , and value vectors instead of reusing the embedding three times:

class AttentionHead(nn.Module):
    def __init__(self, embed_dim, head_dim):
        super().__init__()
        self.q = nn.Linear(embed_dim, head_dim)
        self.k = nn.Linear(embed_dim, head_dim)
        self.v = nn.Linear(embed_dim, head_dim)

    def forward(self, hidden_state):
        attn_outputs = scaled_dot_product_attention(
            self.q(hidden_state), self.k(hidden_state), self.v(hidden_state))
        return attn_outputs

Each nn.Linear maps the 768-dim embedding down to head_dim dimensions. Now the query and key of a token can differ, so a token can look for complementary information rather than a copy of itself.

Why more than one head?

The softmax of a single head tends to focus on one aspect of similarity — often the current token itself. But language has many kinds of relationships at once, so we run several heads in parallel and let each learn its own:

Intuition — heads are like CNN filters

In a convolutional vision model, one filter learns to detect faces while another finds car wheels — different filters, different features, all learned from data. Attention heads are the same idea for sequences: one head might track subject–verb agreement, another nearby adjectives, another coreference. We don’t hand-design these roles; they emerge from training.

Try it. The lab below has two hand-built heads for “time flies like an arrow”. The semantic head routes “flies” to “arrow”; the verb-centric head makes every token attend to the verb “flies”. Same sentence, completely different attention — that is the value of multiple heads.

Head:each head has its own learned Q/K/V projections
Query token:

The output is the value vectors averaged by those weights.

q(flies)[0, 3, 0, 0]
key token jk(j)q·k÷√4weight wⱼv(j)
time000.009
flies6.003.000.178
like000.009
an000.009
arrow9.004.500.796

weights sum to 1.00. “flies” attends most to arrow.

x′(flies) =[1.80, 2.13, 0.83, 0.04]← the context-updated embedding
Two heads on the same sentence. Switch heads: “semantic” routes flies→arrow; “verb-centric” routes every token→flies. Real models learn 12 such heads.

Concatenate the heads

QKVLinearLinearLinearScaled dot-productattentionConcatLinearoutputstacked boxes = h heads in parallel
Figure 3-5 (recreated). Multi-head attention: each of Q, K, V has its own per-head Linear layers; the heads run scaled dot-product attention in parallel, then their outputs are concatenated and mixed by a final Linear.

The full multi-head layer concatenates every head’s output and passes the result through one more linear layer:

class MultiHeadAttention(nn.Module):
    def __init__(self, config):
        super().__init__()
        embed_dim = config.hidden_size          # 768
        num_heads = config.num_attention_heads  # 12
        head_dim = embed_dim // num_heads        # 64
        self.heads = nn.ModuleList(
            [AttentionHead(embed_dim, head_dim) for _ in range(num_heads)]
        )
        self.output_linear = nn.Linear(embed_dim, embed_dim)

    def forward(self, hidden_state):
        x = torch.cat([h(hidden_state) for h in self.heads], dim=-1)
        x = self.output_linear(x)
        return x

In matrix notation, with hh heads:

MultiHead(Q,K,V)=Concat(head1,,headh)WO,headi=Attention(QWiQ,KWiK,VWiV)\operatorname{MultiHead}(Q,K,V) = \operatorname{Concat}(\text{head}_1, \dots, \text{head}_h)\, W^O, \quad \text{head}_i = \operatorname{Attention}(Q W_i^Q,\, K W_i^K,\, V W_i^V)
  • WiQ,WiK,WiVW_i^Q, W_i^K, W_i^V — the ii-th head’s projection matrices (its own learnable parameters).
  • headi\text{head}_i — the §3.2 scaled dot-product attention run inside head ii.
  • WOW^O — the final output projection (output_linear) mixing the heads.

Note — head_dim is chosen so the total stays constant

head_dim need not be smaller than embed_dim, but it’s chosen as embed_dim / num_heads so the compute per layer is independent of the head count. BERT: 768 / 12 = 64. Twelve heads of width 64 concatenate back to exactly 768.

Follow the shapes through the whole layer — note how the hidden dimension splits into heads and then re-merges:

x (embeddings)[1,5,768]
[1,5,64]
[1,5,64]
[1,5,768]
[1,5,768]

Click an operation to see what it does. A highlighted dimension is one that just changed.

Multi-head attention shapes, for BERT’s 12 heads. A highlighted dimension just changed.

Passing BERT’s config through this layer confirms the shape round-trips:

multihead_attn = MultiHeadAttention(config)
attn_output = multihead_attn(inputs_embeds)
attn_output.size()          # torch.Size([1, 5, 768])

Reading real heads

With a pretrained checkpoint you can visualize genuine learned attention (the book uses BertViz’s head_view). Feeding it two sentences — “time flies like an arrow” and “fruit flies like a banana” — and inspecting head 8 shows two things:

  • Attention is strongest between words in the same sentence: the model has learned that the [SEP]-separated sentences are distinct.
  • For the ambiguous word “flies”, the head attends to “arrow” in the first sentence and to “fruit” and “banana” in the second — exactly the context needed to tell the verb from the noun.

That single head has learned a slice of syntax and word-sense disambiguation, purely from data. Multiply by twelve, stack across layers, and you get the rich contextual representations that make transformers work.

With attention fully assembled, the encoder layer still needs its second sublayer and the glue that holds a deep stack together: the feed-forward network, layer normalization, and positional embeddings.

Check yourself: multi-headed attention

1.What does a single attention head do differently from the simplified §3.2 version?

2.Why use multiple attention heads instead of one big one?

3.BERT has embed_dim = 768 and 12 heads. What is head_dim, and why?

4.Predict the shape: 12 heads each output [1, 5, 64]. After `torch.cat(..., dim=-1)` and the output Linear, what is the shape?

5.In the two-head lab, switching from the “semantic” head to the “verb-centric” head changes the attention pattern completely. What does that illustrate?

5 questions