Multi-Head Attention
Multi-Head Attention (MHA) is the central computational primitive of the Transformer architecture, enabling a model to jointly attend to information from multiple representation subspaces at different positions simultaneously. It matters because virtually every state-of-the-art model in NLP, vision, and multimodal AI — from GPT-4 to Stable Diffusion — is built on it as a foundational operation. The counterintuitive fact: adding more heads does not increase the total computation budget, because each head operates on a proportionally smaller projection of the input, keeping FLOPs roughly constant.
A single attention pass forces the model to blend all relational information into one weighted average, losing the ability to simultaneously track different kinds of relationships (syntactic, semantic, co-referential, positional). Multi-Head Attention sidesteps this by running several independent attention operations in parallel on lower-dimensional projections, then concatenating and re-projecting the results. The "aha moment" is that each head learns a distinct relational inductive bias — one head may track subject-verb agreement while another tracks coreference — without any explicit supervision telling it to do so. The final linear projection then fuses these diverse relationship signals into a single rich representation.
How It Works
Scaled Dot-Product Attention
The building block of MHA is Scaled Dot-Product Attention. Given a matrix of queries , keys , and values :
Explanation: Each query vector dot-products with every key vector to produce a raw compatibility score, which is scaled to prevent vanishing gradients in the softmax, normalized to a probability distribution, and used to form a weighted sum of the value vectors. The scaling factor is critical. Without it, dot products grow with dimensionality, pushing the softmax into regions of near-zero gradient:
Explanation: Assuming query and key components are i.i.d. with zero mean and unit variance, dividing by normalizes the dot-product variance to 1 regardless of dimensionality, stabilizing training.
Projecting Into Multiple Subspaces
Multi-Head Attention applies independent learned linear projections to before computing attention:
Explanation: Each head learns its own projection matrices , , that carve out a different relational subspace of the input.
Concatenation and Output Projection
The head outputs are concatenated and linearly projected back to :
Explanation: Concatenation stacks the value-weighted summaries into a vector of dimension , and the output matrix fuses the multi-perspective information back into the model's working dimension.
Parameter Count
In the standard setting , so total parameters are:
Explanation: Three projection matrices each of size plus the output projection of the same size sum to parameters, independent of the number of heads — confirming that heads are free in terms of parameter count but not in terms of per-head capacity.
PyTorch Implementation
```python import torch import torch.nn as nn import torch.nn.functional as F import math class MultiHeadAttention(nn.Module): def __init__(self, d_model: int, num_heads: int, dropout: float = 0.1): super().__init__() assert d_model % num_heads == 0, "d_model must be divisible by num_heads" self.d_model = d_model self.h = num_heads self.d_k = d_model // num_heads # per-head dimension
# Fused QKV projection for efficiency self.qkv_proj = nn.Linear(d_model, 3 * d_model, bias=False) self.out_proj = nn.Linear(d_model, d_model, bias=False) self.dropout = nn.Dropout(dropout) def forward( self, query: torch.Tensor, # (B, T_q, d_model) key: torch.Tensor, # (B, T_k, d_model) value: torch.Tensor, # (B, T_k, d_model) mask: torch.Tensor | None = None, # (B, 1, T_q, T_k) boolean ) -> tuple[torch.Tensor, torch.Tensor]: B, T_q, _ = query.shape T_k = key.shape[1]
# --- Project Q, K, V ---
# For self-attention query==key==value; for cross-attention they differ. Q = self.qkv_proj(query)[:, :, :self.d_model] K = self.qkv_proj(key) [:, :, self.d_model:2self.d_model] V = self.qkv_proj(value)[:, :, 2self.d_model:]
# --- Reshape to (B, h, T, d_k) --- def split_heads(x, T): return x.view(B, T, self.h, self.d_k).transpose(1, 2) Q = split_heads(Q, T_q) # (B, h, T_q, d_k) K = split_heads(K, T_k) # (B, h, T_k, d_k) V = split_heads(V, T_k) # (B, h, T_k, d_k)
# --- Scaled dot-product attention --- scale = math.sqrt(self.d_k) scores = torch.matmul(Q, K.transpose(-2, -1)) / scale # (B, h, T_q, T_k) if mask is not None: scores = scores.masked_fill(mask == 0, float('-inf')) attn_weights = F.softmax(scores, dim=-1) # (B, h, T_q, T_k) attn_weights = self.dropout(attn_weights)
# --- Weighted sum of values --- context = torch.matmul(attn_weights, V) # (B, h, T_q, d_k)
# --- Merge heads --- context = context.transpose(1, 2).contiguous().view(B, T_q, self.d_model) output = self.out_proj(context) # (B, T_q, d_model) return output, attn_weights # return weights for interpretability
# --- Quick sanity check --- if __name__ == "__main__": B, T, d = 2, 16, 512 mha = MultiHeadAttention(d_model=d, num_heads=8) x = torch.randn(B, T, d) out, attn = mha(x, x, x) # self-attention print(out.shape) # torch.Size([2, 16, 512]) print(attn.shape) # torch.Size([2, 8, 16, 16]) ``` > Note on `torch.nn.MultiheadAttention`: PyTorch ships a fused, optimized implementation. The custom version above matches its semantics exactly and is useful for learning or when you need custom masking logic. For production use `F.scaled_dot_product_attention` (PyTorch ≥ 2.0), which dispatches to FlashAttention kernels automatically.
Ashish Vaswani led the team at Google Brain that published "Attention Is All You Need" (2017), which introduced both the Transformer architecture and Multi-Head Attention as its core operation, replacing recurrence entirely for sequence modeling. His specific contribution was the insight that multiple parallel attention functions over projected subspaces could replace complex recurrent state, enabling massive parallelism during training. Surprisingly, Vaswani left Google shortly after the paper's publication to co-found Adept AI, and the paper — now one of the most cited in computer science history — was nearly rejected by reviewers who felt it lacked sufficient experiments on non-translation tasks.
Intuition
Imagine you are a detective reading a witness statement and you need to understand it from several angles simultaneously: Who is doing what? (subject-verb) Where do pronouns point? (coreference) Which phrases are idioms? (lexical) Which words are negating others? (semantic scope). A single reading pass forces you to smear all these questions into one interpretation. Multi-Head Attention is like assigning a team of specialist analysts, each given a slightly blurred, dimensionally-reduced photocopy of the statement through different-colored filters. Analyst 1 looks only through the blue filter (trained to catch syntactic structure), analyst 2 through amber (trained on co-reference), and so on. They each write a short summary, you staple all summaries together, and a coordinator reads the stapled bundle and writes the final integrated interpretation. No one told each analyst what to specialize in — they discovered their specialty through gradient descent because it minimized prediction error.