Given Q of shape (B, n_heads, T, d_head), the per-head attention output before concatenation has shape ___.
Same topic, related formats. Practice these next.
Same topic, related formats. Practice these next.
The per-head output is (B, n_heads, T, d_head). The softmax produces a (T, T) attention matrix per head, then multiplying by V of shape (T, d_head) recovers d_head columns.
Picture a librarian handling T books at once. For each book the librarian asks 'how related are you to every other book in the room?' and gets back a list of T relatedness scores, one per other book. The librarian then makes one summary per book by mixing the contents of every book according to those scores. The summary for each book is a single page exactly as wide as one book's original page count. The librarian does this for every book (T books) and ends up with T summaries, each the same width. Now imagine a whole roomful of librarians doing this in parallel, each focused on a different angle, for several different stories at once. The full pile of summaries has the shape (B, n_heads, T, d_head).
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example.
Everything important, quickly.
Trace shapes from Q, K, V through QK^T, softmax, and the final matmul against V. Distinguish the attention-weight matrix shape (T, T) from the output shape (d_head). Connect to the concat step that recovers d_model, and end with how FlashAttention preserves the output shape while avoiding materializing the weight matrix.
import torch
import torch.nn.functional as F
B, n_heads, T, d_head = 2, 8, 16, 64
Q = torch.randn(B, n_heads, T, d_head)
K = torch.randn(B, n_heads, T, d_head)
V = torch.randn(B, n_heads, T, d_head)
scores = Q @ K.transpose(-2, -1) / (d_head ** 0.5)
print(scores.shape) # (B, n_heads, T, T)
weights = F.softmax(scores, dim=-1)
print(weights.shape) # (B, n_heads, T, T) -- unchanged by softmax
per_head_output = weights @ V
print(per_head_output.shape) # (B, n_heads, T, d_head)
# Concatenate heads: (B, n_heads, T, d_head) -> (B, T, d_model)
d_model = n_heads * d_head
concatenated = per_head_output.transpose(1, 2).contiguous().view(B, T, d_model)
print(concatenated.shape) # (B, T, d_model)Real products, models, and research that use this idea.
What an interviewer would ask next. Try answering before peeking at the approach.
Red flags and common mistakes that signal junior thinking. Click to expand.
Confusing the shape of the attention-weight matrix (B, n_heads, T, T) with the shape of the attention output (B, n_heads, T, d_head). The matmul against V brings the trailing dimension back to d_head.
The night-before-the-interview bullets. Scan these on the way to the call.
Primary sources. Skim if you want the original framing.