Architecture · Causal self-attention
Transformers
Self-attention builds token representations by letting tokens interact directly based on their content, while a causal language model masks future token positions.

Evidence & limits
- What the further reading establishes
- An attention-based architecture can beat recurrent translation systems while taking better advantage of parallel training.
- What this lab runs
- You'll run the exact causal masking and scaled dot-product mixing steps on the given token representations.
- What it does not prove
- This lesson runs an untrained attention block. It doesn't recreate WMT training or suggest that random attention weights explain language.
Dataset
Causal Sequence Set
- Source
- Course-authored synthetic examples
- License
- Not separately licensed
- Size
- 1 fixed six-token sequence
Further reading
- Attention Is All You Need
Primary · Ashish Vaswani et al. · 2017
Lays out the Transformer, including multi-head attention, positional encoding, and residual blocks.
- The Annotated Transformer
Implementation · Harvard NLP · 2018
Walks through the implementation line by line and turns the paper's equations into working modules.
- Improving Language Understanding by Generative Pre-Training
Paper · Alec Radford et al. · 2018
Uses a masked decoder-only Transformer for language-model pretraining and transfer learning.
Lesson progressRestoring progress…
- CodeRestoring
- ExperimentRestoring
- CheckRestoring
Summary
Keep track of the shapes. Start with n token representations X ∈ ℝⁿˣᵈmodel. Learned projections produce Q and K ∈ ℝⁿˣᵈk and V ∈ ℝⁿˣᵈv. That means QKᵀ has one compatibility score for every query row and key column, giving it shape n × n.
Scale the match scores. A query asks what this position needs. A key says what a position can match, and a value carries the information that may get mixed in. Divide each query-key dot product by √dₖ so the scores don't grow with the projection width and push softmax into saturated probabilities with tiny gradients.
Mask first, then normalize. For query row i, keep key columns j ≤ i and set every future score j > i to −Infinity. Then apply softmax across that row. Future positions get exactly zero probability, and the probabilities that remain add up to one and weight the rows of V.
- 1Project
XW → Q,K[3×d_k] · V[3×d_v] - 2Score
QKᵀ[3×3] / √d_k - 3Normalize
mask j>i to −∞ → softmax per row - 4Mix values
P[3×3]V[3×d_v] → C[3×d_v]
P = softmax(mask(QKᵀ / √d_k))| query ↓ / key → | the | receiver | decoded | ‖context‖ |
|---|---|---|---|---|
| the | 1.00 | 0 | 0 | 1.00 |
| receiver | 0.20 | 0.80 | 0 | 0.82 |
| decoded | 0.20 | 0.33 | 0.46 | 0.60 |
Before softmax every cell above the diagonal is −Infinity. In this small example the values use unit basis rows, so each probability row, rounded to two decimals, is also its context vector and gives the norm shown here.
attention output → projection → residual + norm → MLP → residual + normAttention isn't the whole block. The context C = softmax(mask(QKᵀ/√dₖ))V is only the attention sublayer. A working decoder block also needs output and multi-head projections, residual paths, normalization, a position-wise MLP, and another residual path. Stacked blocks operate on representations that include both token and position information. The exercise below builds only the non-affine normalization core. Full affine layer normalization also applies a learned gain gamma and bias beta to each feature; this exercise leaves those two parameters out on purpose.
Knowledge check
Implementation
Keep the diagonal and earlier positions, and replace only scores where column > row with -Infinity before softmax.
- Signature
def causal_mask(scores):- Inputs
- scores square matrix [sequence × sequence]
- Returns
- nested list with shape [sequence × sequence]
- Rule
keep score[i,j] when j ≤ i; otherwise set it to −Infinity- Example
[[1,2],[3,4]] → [[1,−Infinity],[3,4]]
Reference solution
Approach Keep the diagonal and earlier positions, and replace only scores where column > row with -Infinity before softmax.
import numpy as np def causal_mask(scores): masked = np.asarray(scores, dtype=float).copy() if masked.ndim != 2 or masked.shape[0] != masked.shape[1]: raise ValueError("maskCausal needs a square rank-2 tensor") future_rows, future_columns = np.triu_indices(masked.shape[0], k=1) masked[future_rows, future_columns] = -np.inf return masked.tolist()Use Python and NumPy to build the exact steps that decide which token positions can share information inside a causal attention block.
Saved results
Results created after your saved code passes its checks
Saved results
Results created after your saved code passes its checks
The replay is course data. The validation result is tied to the code you saved and checked.
Loading…