Home / Fundamentals / Transformer Architecture
Explain scaled dot-product attention, implement it, and justify dividing by √d_k.
30-second answer
Project inputs to queries, keys and values; scores are QKᵀ/√d_k; apply the causal mask with −∞; softmax each row; multiply by V. Each output is a convex combination of value vectors weighted by query–key similarity. With unit-variance components, q·k has variance d_k, so unscaled scores grow with dimension, softmax saturates to near one-hot, and its gradient vanishes; dividing by √d_k restores unit variance. Multi-head attention runs this in parallel subspaces and projects the concatenation with W_O.
Try answering out loud before reading on. The walkthrough below is paced for a 40-minute interview round.
0–5 min Clarify and frame
Confirm whether they want derivation, implementation, or both. Framing: "I'll write the computation with shapes, implement causal multi-head attention, justify √d_k with a variance argument, then cover numerical stability, cost and the backward pass."
5–15 min Core explanation
For X ∈ ℝ^{T×d} and head size d_k:
Q = X W_Q, K = X W_K, V = X W_V (T × d_k)
S = Q Kᵀ / √d_k (T × T)
P = softmax(S + M) row-wise; M = 0 or −∞ (causal mask)
O = P V (T × d_k)
Each row of P is a distribution over positions; each output row is a weighted average of value vectors. Multi-head attention runs H heads of size d/H in parallel, concatenates, and applies W_O.
import torch, math
def causal_attention(x, Wq, Wk, Wv, Wo, n_heads):
B, T, d = x.shape
dk = d // n_heads
def heads(t): # (B, T, d) -> (B, H, T, dk)
return t.view(B, T, n_heads, dk).transpose(1, 2)
q, k, v = heads(x @ Wq), heads(x @ Wk), heads(x @ Wv)
s = (q @ k.transpose(-2, -1)) / math.sqrt(dk) # (B, H, T, T)
mask = torch.triu(torch.ones(T, T, dtype=torch.bool, device=x.device), 1)
s = s.masked_fill(mask, float("-inf"))
p = s.softmax(dim=-1)
o = (p @ v).transpose(1, 2).reshape(B, T, d)
return o @ Wo
15–25 min Deep dive
Why √d_k. Let components of q and k be independent, mean 0, variance 1. Then Var(q·k) = Σ Var(q_i k_i) = d_k, so the standard deviation is √d_k. With d_k = 128, scores have std ≈ 11; softmax of such logits is nearly one-hot, and its Jacobian diag(p) − ppᵀ is nearly zero, so gradients vanish. Dividing by √d_k keeps logits at unit scale at initialization.
Numerical stability. Softmax is computed as exp(s − max(s)). In fp16, use a large negative constant instead of −∞ where kernels require it, and compute softmax in fp32. A fully masked row (e.g. padding queries) produces NaN with −∞ masks, so handle it explicitly.
Backward pass, briefly. With dO given:
dV = Pᵀ dO
dP = dO Vᵀ
dS = P ⊙ (dP − rowsum(dP ⊙ P)) # softmax backward
dQ = dS K / √d_k, dK = dSᵀ Q / √d_k
Storing P (T × T per head) for the backward pass is what makes naive attention memory-hungry; FlashAttention recomputes it block by block instead.
Cost per token per layer: projections 8d² FLOPs, scores and weighted sum 4·T·d. The quadratic term matches the parameter matmuls (including the MLP's 16d²) at T ≈ 6d.
25–32 min Trade-offs and variants
- Additive attention (a small MLP scoring each pair) predates dot-product attention; dot-product wins on hardware because it's a matmul.
- Separate Q and K projections allow asymmetric relevance; tying them forces
score(i, j) = score(j, i). - QK-norm (normalizing queries and keys) bounds logit growth and stabilizes large-scale training.
- Softmax alternatives (sigmoid attention, linear kernels) trade the normalization for efficiency or other properties.
32–37 min Production example
A team saw NaNs in fp16 fine-tuning only on padded batches: some query rows were fully masked, producing softmax(−inf…). Fixing the mask to leave at least one valid key (or zeroing those rows after softmax) and computing softmax in fp32 resolved it. Using a fused attention kernel (FlashAttention-style) also cut activation memory enough to double the batch size.
37–40 min Likely follow-ups
- What does
W_Odo? Mixes information across heads and maps back to the model dimension. - What changes in cross-attention? Queries come from one sequence, keys and values from another; there's no causal mask over the source.
- Is the attention map an explanation of the model's decision? Not reliably; it shows routing in one layer, not causal importance.
What interviewers listen for
- Correct shapes, masking before softmax, and per-row softmax.
- The variance argument and the vanishing-gradient consequence.
- Awareness of memory cost from storing
Pand how fused kernels avoid it.
Red flags
- Applying the mask after softmax.
- "We divide by √d_k to normalize" without the saturation argument.