Causal Masking
Force every future position's attention score to negative infinity before the softmax, so a token can only ever attend to itself and whatever came before it.
Why Does This Exist?
Self-attention, left alone, lets every token attend to every other token — including tokens later in the sequence. For a model whose only job is to build a representation of a complete, already-written passage, that's exactly right; there's no "later" to worry about because the whole input is available at once.
But training a model to generate text by predicting the next token creates a very specific problem. If token 3's representation is allowed to attend to token 4, and the model is being trained to predict token 4 from token 3's representation, the model can trivially cheat: just copy whatever it already saw at position 4. Training loss looks fantastic, and the model has learned nothing about actually predicting the future — it learned to read the answer key it was handed by accident. At inference time, token 4 doesn't exist yet when you're generating it, so a model trained this way collapses immediately outside of training.
Think of It Like This
An exam where later pages are stapled shut
Imagine an exam booklet where every page after the one you're currently on is stapled closed — you can flip back and reread anything you've already answered, but you physically cannot see ahead to question 12 while you're working on question 5. That's not a memory limitation; it's an enforced rule of the exam, the same for every question in the booklet.
Causal masking is exactly that staple. It doesn't make future tokens hard to attend to — it makes them unavailable, structurally, for every position, so the model that trains under this constraint learns to actually predict rather than to peek.
How It Actually Works
One mask, added before the softmax
The diagram above shows the mechanism directly: build an mask matrix where position is 0 when (key position is at or before query position ) and negative infinity when (key position is in the future relative to query ). Add this mask to the raw attention scores before the softmax:
Adding negative infinity to a score, then exponentiating inside the softmax, sends that term to exactly zero — . So every blocked position contributes literally nothing to the weighted output, not a small amount, not an approximation. The diagram's × marks are cells that become exact zeros in the attention-weight matrix, every time, for every input.
Why it's a triangle
Draw out which pairs survive — query position is allowed to see key position whenever — and the surviving region is the lower triangle of the grid, including the diagonal. The diagonal always survives: a token always attends to itself, since satisfies . That's why every accented diagonal cell in the diagram is open while everything above it, later in the sequence, is blocked.
The training-time payoff
Because the mask is applied per-position rather than by physically truncating the input, a causally masked model can compute the loss for every position in a training sequence in one forward pass. Position 5's prediction only ever saw positions 1 through 5, so it's a fair, uncheatable next-token prediction — and so is position 500's, computed in the same batched pass. That parallel-training property, alongside the raw prevention of future-leakage, is a large part of why decoder-only causally masked transformers scaled so well: every position in a sequence supplies a training signal, computed together, with none of them cheating off the others.
Show Me the Code
Building the mask and confirming that blocked positions land at exactly zero after the softmax, not merely small.
import numpy as np
def softmax(z: np.ndarray) -> np.ndarray: e = np.exp(z - z.max(axis=-1, keepdims=True)) return e / e.sum(axis=-1, keepdims=True)
def causal_mask(n: int) -> np.ndarray: mask = np.triu(np.ones((n, n)), k=1) # 1s strictly above the diagonal (the future) return np.where(mask == 1, -np.inf, 0.0) # future -> -inf, everything else -> 0
scores = np.array([[2.0, 1.0, 0.5], [1.0, 3.0, 0.2], [0.1, 0.4, 2.5]])masked = scores + causal_mask(3)weights = softmax(masked)print(np.round(weights, 3))# -> [[1. 0. 0. ]# [0.119 0.881 0. ]# [0.066 0.089 0.845]]Row 0 puts all its weight on position 0 — it literally has nothing else available. Row 1 splits weight across positions 0 and 1 only. The upper-right zeros aren't rounding; causal_mask forces them there exactly, before the softmax even runs.
Watch Out For
Masking after the softmax instead of before
Setting attention weights to zero after the softmax, rather than adding negative infinity before it, looks equivalent but isn't. Post-hoc zeroing leaves the softmax's denominator computed as though the blocked positions were still included, so the remaining weights don't actually sum to 1 anymore — the distribution is subtly wrong, and every output is a slightly miscalibrated blend. Masking before the softmax is what guarantees the surviving weights renormalize correctly.
Applying a causal mask to an encoder that doesn't need one
Causal masking is specifically for autoregressive generation — decoder-only models and decoder stacks in encoder-decoder architectures. An encoder that's building a representation of a complete, already-available input (translation source text, an image's patches) has no "future" to hide, and masking it anyway needlessly throws away context the encoder is supposed to have. Confusing which stack needs the mask is a common wiring mistake when assembling a custom transformer from scratch.
The Quick Version
- Causal masking adds negative infinity to every attention score where the key position is later than the query position.
- After the softmax, those blocked positions land at exactly zero weight — not approximately, exactly.
- The surviving pattern is a lower triangle including the diagonal: every position attends to itself and everything before it, nothing after.
- This is what prevents a next-token-prediction model from trivially copying the answer during training.
- It also lets every position's training loss compute in one parallel forward pass, since no position can see ahead to cheat.
What to Read Next
- Self-Attention is the unmasked computation causal masking restricts.
- Multi-Head Attention applies the same causal mask independently within every head.
- Sequence-to-Sequence is the encoder-decoder shape where only the decoder side needs this mask.
- Attention Complexity covers the cost of the full score matrix this page's mask is applied to.