Causal Masking
No looking ahead
Decoder models must never peek at the future: a causal mask zeroes attention to later positions so each token sees only itself and its predecessors. It's the grammar license of next-token training and streaming generation.
What you will learn
- Explain the causal mask's geometry
- Implement it in a few lines
- Connect it to training correctness and streaming
What is this?
A causal mask sets attention scores from token i to all tokens j > i to −∞ (softer weights go to zero), making every position attend only to the past and itself. This makes the model's distribution decomposable — P(token sequence) = Π P(token_i | past) — which is exactly what legitimate next-token training and streaming generation require.
Why do you need it?
- Training on future-peeking models silently overfits to lookahead and misgeneralizes.
- The chain-rule factorization depends on it.
- Streaming decode at inference follows the same causal order.
- Leaking the mask (even in fine-tuning) corrupts quality everywhere.
Mental model
A one-way street in class
The causal mask
You may read everything that came before the current note but nothing after — notes must be written left to right. A student who cheats by reading the answer section learns the test, not the subject; that's exactly what a lookahead 'training leak' does to a model.
How it works
- Attention score matrix is N×N; for every (i, j) with j > i, set score to −∞.
- Softmax turns −∞ into ~0 weight; the future may not influence the present.
- Each token's output depends only on ≤-itself tokens.
- At inference, decode tokens are appended as they're generated — same causal rule.
- Encoder models omit it (bidirectional); decoder models require it.
The mask is a lower triangle.
Examples
Causal mask in numpy
import numpy as np n = 5scores = np.ones((n, n)) * 0.1mask = np.tril(np.ones((n, n)), k=0) # lower trianglescores = np.where(mask == 1, scores, -np.inf) # future -> -infweights = np.exp(scores - scores.max(axis=1, keepdims=True))weights = weights / weights.sum(axis=1, keepdims=True)print(np.round(weights, 2))Row i has mass only on columns 0..i — the model can't see beyond itself.
In production
- Every decoder training step embeds a causal mask; removing it quietly breaks training.
- Speculative decoding uses the same causal view for draft and verify.
- Streaming a long answer is literally walking the mask's diagonal.
Best practices
- Use framework causal options; never hand-roll unless profiling says otherwise.
- Verify the mask when re-using position offsets larger than zero.
- Guard fine-tune contracts against masked-attention regressions.
Common mistakes
- Hand-implementing attention without the mask — the model sees the future and looks great, then falls apart.
- Applying causal masks in encoder-like passages where bidirectional reading is wanted.
- Asymmetric masks in batched decoding → cache/order bugs.
Common issues
Very low train loss, clearly bad generated textFuture leakage — mask missing or wrong
Confirm the triangular mask and retrain/retune; drop leaky checkpoint.
Decode duplicates tokens during streamingKV cache and mask mis-indexed during append
Assert cache length == position; rebuild cache on jump.
Debugging tips
- Suspiciously high train loss reduction with poor test → likely lookahead leak across accidental positions.
- Streaming jumps ahead (generates the same words twice) → mask/cache indices out of sync.
- Strange behavior on position-shifted prompts → verify mask orientation with the position offset.
Interview checkpoint
Hands-on lab
- Compute self-attention with and without a causal mask
- Verify the mask zeroes future influence
- Confirm masked output equals the equivalent causal-slice run
Implement Causal Attention from Scratch
40 min · intermediate
Quick quiz
A causal mask guarantees each token attends only…
Encoder-style models usually use causal masks.