A batch packs a causal LM with right-padded sequences and applies only the causal mask. Spot the mistake.
Same topic, related formats. Practice these next.
Same topic, related formats. Practice these next.
Click any words you think contain an error. Click again to unmark.
Causal mask blocks futures, padding mask blocks pad. Neither is a superset of the other. With right-padding you need both, applied together before softmax. Loss masking on outputs does not fix attention contamination.
Picture a study group where some students arrived late and have empty notebook pages for the early part of the lecture. The class rule is 'only look at notes from earlier in the lecture' (causal mask). That rule says nothing about ignoring the empty pages, so a student trying to review their own notes from later in the lecture will happily read the empty pages from the late-arriving student next to them and try to study from blank paper. You need two rules together: 'only look at earlier notes' AND 'skip empty pages'. Right-padding plus causal mask only is one rule when you need both, and the model trains on blank-paper context.
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.
Walk through what each mask blocks, show that the two constraints are orthogonal, demonstrate the bug with a worked example of a short right-padded sequence, explain why loss masking is a separate concern, and close with the FlashAttention-2 cu_seqlens replacement and a unit-test recipe.
import torch
import torch.nn.functional as F
B, T, n_heads, d_head = 2, 8, 4, 16
real_lengths = torch.tensor([3, 8]) # Seq 0: 3 real + 5 pad. Seq 1: all real.
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)
# Padding mask: -inf at pad columns, 0 at real columns. Shape (B, 1, 1, T).
is_real = torch.arange(T)[None, :] < real_lengths[:, None] # (B, T)
pad_mask = torch.where(is_real, 0.0, float('-inf'))[:, None, None, :]
# Causal mask: -inf above the diagonal, 0 on and below. Shape (1, 1, T, T).
causal_mask = torch.triu(torch.full((T, T), float('-inf')), diagonal=1)
causal_mask = causal_mask[None, None, :, :]
# Combine BOTH masks before softmax. This is the correct approach.
scores = Q @ K.transpose(-2, -1) / (d_head ** 0.5)
scores = scores + causal_mask + pad_mask # any position future OR pad becomes -inf
weights = F.softmax(scores, dim=-1)
output = weights @ V
# Loss masking is a SEPARATE step on the LM head output, not a replacement
# for the attention-level padding mask.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.
Believing the causal mask subsumes the padding mask because 'pad tokens are in the past'. The causal mask permits the past; it does not exclude padding from the past.
The night-before-the-interview bullets. Scan these on the way to the call.
Primary sources. Skim if you want the original framing.