TAI BUI
Back to curriculum
BuildPython

GPT — Causal Language Modeling

BERT sees both sides of a token. GPT sees the tokens before it. The triangle mask is one line of code, and a lot of modern AI rests on it.

TIME: ~75 minutesPREREQUISITES: Phase 7 · 02 (Self-Attention), Phase 7 · 05 (Full Transformer), Phase 7 · 06 (BERT)

Learning Objectives

  • Explain how the causal mask lets one forward pass produce a next-token prediction at every position in parallel.
  • Compute the shift-by-one cross-entropy loss and compare it against the random baseline ln V.
  • Implement greedy, temperature, top-k, top-p and min-p sampling and say when each one fits.
  • Explain why GPT inference is serial even though training is parallel, and what the KV cache saves.

The Problem

A language model is a function that takes the first t-1 tokens and returns a probability distribution over token t. Train on that signal, next-token prediction, and you get a model that generates text one token at a time.

To train on a whole sequence in parallel, each position's prediction has to depend on earlier positions alone. If a position could see later tokens, the model would be reading the answer off the page.

The causal mask enforces that. It is an upper-triangular matrix of -inf values added to the attention scores before softmax. After softmax those positions become 0, so each position attends to itself and earlier positions. Apply the mask once to the whole sequence and one forward pass gives you N parallel next-token predictions.

GPT-1 (2018), GPT-2 (2019), GPT-3 (2020), GPT-4 (2023), GPT-5 (2024), Claude, Llama, Qwen, Mistral, DeepSeek and Kimi are decoder-only causal transformers with the same core loop. What differs is size, data quality, and RLHF.

Warm-up0/3
1. A language model is trained on next-token prediction. What does it output at each position?Warm-up
Five input tokens (The, cat, sat, on, the) feed a box labelled language model, which has one dashed question-mark output under each input position.
Typed example sentence.
2. BERT fills in masked tokens using context on both sides. Why is that setup a poor fit for writing text one token at a time?Warm-up
Top: a six-token sentence The cat [MASK] on the mat with arrows from the words on both sides into the masked slot. Bottom: the two words The cat followed by a dashed slot for the next token.
Typed example sentence.
3. In self-attention, softmax is applied to each row of the score matrix. What does each row sum to afterward?Warm-up
Six raw attention scores for one row of a 6 by 6 score matrix, an arrow labelled softmax, and six dashed question marks for the values afterward, with a question mark for their sum.
Scores are row 5 of the seed-42 Gaussian matrix in main.py's demo_causal_mask.
Check: The Problem0/3
1. What is a causal mask in a decoder-only transformer?Define
Terminal output of the lesson's causal_mask(4): four rows of numbers, with zeros and negative infinity entries, repeated as a 4 by 4 grid labelled by row i and column j.
Printed by the lesson's causal_mask function; the terminal prompt line is typed.
2. What is next-token prediction as a training signal?Define
Six tokens (the, cat, sat, on, the, mat) labelled t1 to t6 in a row, above a dashed box asking what the training signal is.
Typed example sentence.
3. A model learns to predict token t from earlier tokens, with all positions trained in one pass. What must hold for each position?Explain
Five tokens t1 to t5 in a row with t3 highlighted, a bar saying the model predicts the token after each position in one pass, and a dashed box asking what t3 may see.
Schematic.

The Concept

Causal mask creates a triangular attention matrix

The mask

The causal mask is an N × N matrix, for a sequence of length N, that blocks each position from attending to later ones:

M[i, j] = 0       if j <= i
M[i, j] = -inf    if j > i

Add M to the raw attention scores before softmax. Since exp(-inf) = 0, masked positions get zero weight, and each row of the attention matrix is a probability distribution over previous positions (and the current one).

Implementation cost: one line. code/main.py builds it with a nested list comprehension, and in PyTorch it is one torch.tril() call. Time to compute: nanoseconds. Impact on the field: enormous.

Parallel training, serial inference

Training runs the whole (N, d_model) sequence through one forward pass, computes N cross-entropy losses (one per position), averages them, and backpropagates. The work is parallel along the sequence, which is why GPT training scales: a batch of 1M tokens goes through in one GPU pass.

Inference is token by token. Feed [t1, t2, t3], get t4. Feed [t1, t2, t3, t4], get t5. Feed [t1, t2, t3, t4, t5], get t6. The KV cache (Lesson 12) saves the hidden states of t1…tn so you don't recompute them at each step, but serial depth at inference still equals output length. That is the autoregressive tax, and it is why decoding is the latency bottleneck for LLMs.

The loss: shift by one

Given tokens [t1, t2, t3, t4]:

  • Input: [t1, t2, t3]
  • Targets: [t2, t3, t4]

At each position i, compute -log P(target_i | inputs[:i+1]). Average these and you have the cross-entropy for the whole sequence.

Pre-training, fine-tuning and SFT all use this loss. The data changes, the loss stays the same.

Decoding strategies

After training, the sampling choice matters more than most people expect.

MethodWhat it doesWhen to use
GreedyArgmax every stepDeterministic tasks, code completion
TemperatureDivide logits by T, sampleCreative tasks, higher T = more diversity
Top-kSample from top-k tokens onlyKills low-probability tails
Top-p (nucleus)Sample from smallest set with cumulative prob ≥ p2020+ default; adapts to distribution shape
Min-pKeep tokens with p >= min_p * max_p2024+; better at rejecting long tails than top-p
Speculative decodingDraft model proposes N tokens, big model verifies2–3× latency reduction at same quality

code/main.py implements the first five rows (greedy, temperature, top-k, top-p, min-p). Speculative decoding is described here but not implemented in the file, and neither is beam search (Exercise 2).

In 2026, min-p with temperature 0.7 is a reasonable default for open-weights models. Speculative decoding is table stakes for a production inference stack.

What made the "GPT recipe" work

  1. Decoder-only. No encoder overhead. One pass of attention + FFN per layer.
  2. Scaling. 124M → 1.5B → 175B → trillions. Chinchilla scaling laws (Lesson 13) tell you how to spend compute.
  3. In-context learning. Emerged around 6B–13B. The model can follow few-shot examples without fine-tuning.
  4. RLHF. Post-training on human preferences turned raw pretrained text into chat assistants.
  5. Pre-norm + RoPE + SwiGLU. Stable training at scale.

The core architecture has changed little since GPT-2. The interesting progress has come from data, scale, and post-training.

Check: The Concept0/24
1. What does autoregressive mean for a GPT-style model?Define
Code panel with the Use It example: a prompt string and a model.generate call with max_new_tokens 64, temperature 0.7, top_p 0.9 and do_sample True, above a dashed question box about what happens between tokens.
Code copied from the lesson's Use It section.
2. What does temperature do in sampling?Define
Ten tokens with their logits from main.py in two rows, and below them three boxes labelled T = 0.5, T = 1.0 and T = 2.0, each asking what changes.
Logits from main.py's demo_sampling; the T values are illustrative.
3. What does top-p (nucleus) sampling keep before it samples?Define
Horizontal bars for the ten demo tokens sorted by probability, the largest 0.380, beside a note top-p sampling with p = 0.9 and a dashed question box for the tokens kept.
Probabilities printed by main.py's demo_sampling.
4. Which rule does min-p sampling use to decide which tokens survive?Define
Horizontal bars for the ten demo tokens sorted by probability beside a note min-p sampling with min_p = 0.1 and a dashed question box for the tokens kept.
Probabilities printed by main.py's demo_sampling.
5. How does speculative decoding speed up generation?Define
A large model box labelled accurate and slow with a note that output comes one token at a time, a goal of cutting latency without lowering quality, and a dashed box for the speed-up method.
Schematic.
6. What is teacher forcing?Define
A six-token data row, then a model input of the, cat and a dashed question slot feeding a model box to predict the token after sat.
Typed example sentence.
7. What does the KV cache do during autoregressive inference?Define
Three generation steps: [t1 t2 t3] gives t4, [t1 to t4] gives t5, [t1 to t5] gives t6, beside a dashed box KV cache that stores a question mark.
Steps follow the lesson's inference example.
8. The Concept lists five ingredients of the GPT recipe. Which of these is NOT one of them?Define
A row of GPT-1 (2018), GPT-2 (2019), GPT-3 (2020), GPT-4 (2023), GPT-5 (2024) boxes, above five numbered dashed slots for the recipe's ingredients.
Model names and years from the lesson's Problem section.
9. Why does adding -inf to the scores where j > i still leave each row as a valid distribution over earlier positions?Explain
For position 2, three stacked rows of six boxes: raw scores, a mask row with zeros in columns 0 to 2 and negative infinity in columns 3 to 5, and the masked scores; below, six dashed question marks for the softmax output.
Scores are row 2 of the seed-42 matrix from main.py's demo_causal_mask.
10. Why does GPT training scale well even though generation is autoregressive?Explain
Training row: tokens t1 to t5 enter one forward pass that gives a dashed loss question; generation rows: [t1,t2,t3] gives t4, [t1..t4] gives t5, [t1..t5] gives t6, one pass per new token.
Generation rows follow the lesson's inference example.
11. Why is ln V the random baseline for next-token loss on a vocabulary of size V?Explain
A plot of loss equal to minus ln p against the probability p given to the true token, falling from large values at small p to 0 at p = 1, with a dashed horizontal line at ln 10 = 2.303.
Curve computed; ln 10 = 2.303 is the value main.py prints.
12. How does the training loss for SFT differ from the loss used in pre-training?Explain
Two panels side by side, pre-training data with plain text and SFT data with an instruction and a reply, each pointing to a dashed box asking for its training loss.
Typed examples, not from a real dataset.
13. Why does the min-p cutoff adapt to how sharp the distribution is?Explain
Two bar charts of ten token probabilities, one with a tall top bar (a sharp step) and one with a lower top bar (a flat step), each with the label min_p = 0.1 (the bar heights are the probabilities) and a dashed question box for the cutoff.
Made from the demo logits at T = 0.5 and T = 2.0 with main.py's softmax; bars in vocabulary order.
14. What happens to sampling as temperature T approaches 0, and as T grows very large?Explain
Ten tokens with their logits from main.py in two rows, and below them two dashed boxes asking what sampling becomes as T goes to 0 and as T becomes very large.
Logits from main.py's demo_sampling.
15. Why does the lesson call serial generation the autoregressive tax?Explain
Three boxes forward pass, sample, append in a row with a return arrow from append to forward pass, and a dashed box asking what the autoregressive tax is.
Schematic of the loop described in the lesson's Use It section.
16. For tokens [t1, t2, t3, t4, t5] in the shift-by-one setup, which target is scored against the prediction made at input position t3?Apply
Five tokens t1 to t5 in a row with t3 highlighted and an arrow from it to a dashed box asking which target its prediction is scored against.
Schematic.
17. You train on a 50,000-token vocabulary and the loss still sits near 10.8 after many steps. What should you suspect?Analyze
A loss-versus-steps plot on a 0 to 12 axis where the curve stays flat near 10.8 across many steps, for a model with a 50,000-token vocabulary.
Drawn from the stem's numbers (flat near 10.8), not from a real run.
18. After a refactor, the printed attention weights are nonzero above the diagonal, and training loss drops to nearly zero within a few steps. Generation is garbage. What is the most likely cause?Analyze
A 6 by 6 schematic attention grid with cells above the diagonal shaded red as nonzero, beside notes that training loss drops to nearly zero within a few steps and generation is garbage, and a dashed question box.
Schematic from the stem's description; only the above-diagonal cells are stated by the stem.
19. A developer applies the causal mask by setting future attention weights to 0 after softmax, rather than adding -inf before it. What goes wrong?Analyze
Position 1 of the demo: raw scores, their softmax over all six columns, an arrow labelled set columns 2 to 5 to 0, and six dashed question marks for the resulting row and its sum.
Scores from row 1 of the seed-42 matrix in main.py's demo_causal_mask.
20. A service uses a KV cache, yet a 500-token answer still has high latency. What explains it?Analyze
A request box into a model box marked KV cache enabled, into an answer box of 500 tokens, with a note that latency stays high and a dashed question box.
The stem's situation, schematic.
21. Samples with top-p 0.95 occasionally include odd, very low-probability tokens from a long tail. Which change targets this directly?Analyze
Sixteen bars of a made-up next-token distribution with one tall bar and a long tail of small ones; the first 8 bars are highlighted as the set kept by top-p 0.95.
Illustrative numbers, not from a real model. The kept set is computed from them.
22. A code-completion feature must return identical output for the same prompt every time. Which decoding choice fits?Design
A typed prompt def add(a, b): feeding a model box with two output slots, run 1 and run 2, both dashed question marks, and the requirement that they be identical every time.
Typed example prompt.
23. You are shipping a chat feature on an open-weights model and want a sensible sampling default before tuning. Which choice fits?Design
A chat panel with a user bubble and a model reply bubble, and a sampling settings panel with temperature and truncation fields both shown as dashed question marks.
Layout mock-up, no real values.
24. Your decoding is the latency bottleneck and quality must not drop. A much smaller model is available. Which design fits?Design
A large box for the big model noting that decoding sets the latency and quality must not drop, and a much smaller box for an available small model.
The stem's situation, schematic.

Build It

Step 1: the causal mask

See code/main.py. It fits in one line:

python
def causal_mask(n):
    return [[0.0 if j <= i else float("-inf") for j in range(n)] for i in range(n)]

Add it to the attention scores before softmax. That is the mechanism.

Step 2: a 2-layer GPT-ish model (design sketch, not in main.py)

code/main.py has no model at all. It stands in random Gaussian scores for the attention matrix and random Gaussian logits for the output, which is enough to demonstrate the mask, the loss and the samplers. A real version would stack two decoder blocks (masked self-attention + FFN, no cross-attention), with a token embedding, a positional encoding, and an unembedding tied to the token embedding matrix (a standard trick since GPT-2).

Step 3: next-token prediction, end to end

On a 10-token toy vocab, take random Gaussian logits at each position, boost the true next token by +2.0 to mimic a slightly trained model, and compute cross-entropy loss against the shift-by-one target. There is no gradient step here. Treat it as a forward-pass sanity check.

Step 4: sampling

Implement greedy, temperature, top-k, top-p and min-p. Run each on a fixed prompt and compare the outputs. Each sampling function in code/main.py is short (top-k is about 8 lines, top-p and min-p 15 to 20 each).

Check: Build It0/8
1. What are logits in a language model?Define
A chain of boxes: tokens, embed, blocks, LM head, a dashed question mark, then token, showing where in a forward pass the unknown vector sits, with a prompt asking what it is.
Schematic.
2. What does Build It describe for the unembedding layer of a GPT-style model?Explain
A vertical stack: input token ids, a token embedding matrix V by d, two decoder blocks with masked attention and FFN, a dashed unembedding box with a question mark, and logits over V tokens.
Schematic of the lesson's design sketch; main.py itself has no model.
3. How does cross_entropy_shifted in main.py combine the per-position losses?Apply
A five-row table of position, target token id and negative log probability of the target for the 6-token demo sequence, beside a dashed box for the loss that cross_entropy_shifted returns.
Computed with the lesson's softmax on main.py's demo_ce_loss logits (seed 7, +2.0 boost).
4. In the post-softmax causal attention matrix, what does row 0 look like?Apply
A 6 by 6 grid of raw attention scores before any mask or softmax, and a dashed box asking what row 0 looks like after the mask and softmax.
Raw scores from the seed-42 matrix in main.py's demo_causal_mask.
5. A 6 x 6 causal attention matrix is printed after softmax. Counting rows from 0, how many nonzero weights are in row 3?Apply
An empty 6 by 6 grid with rows and columns numbered 0 to 5 and row 3 outlined, beside a dashed box asking how many nonzero weights row 3 has.
Layout only; cell values are not drawn.
6. cross_entropy_shifted receives logits for a 6-token sequence. How many next-token losses does it average?Apply
Two columns for a 6-token sequence: six rows of ten logits at positions 0 to 5, and the target ids 3, 1, 7, 0, 4, 9, beside a dashed box asking how many losses are averaged.
Shapes and ids from main.py's demo_ce_loss.
7. main.py's demo uses these logits: the 3.2, cat 1.1, dog 2.8, sat 0.4, ran 0.9, jumped 1.5, on -0.2, mat 2.1, floor 0.7, period 0.1. Which tokens can top-k with k=3 return?Apply
Horizontal bars of the ten demo logits in vocabulary order (the 3.2, cat 1.1, dog 2.8, sat 0.4, ran 0.9, jumped 1.5, on -0.2, mat 2.1, floor 0.7, period 0.1) beside a top-k, k = 3 label and a dashed question box.
Logits from main.py's demo_sampling.
8. For the same demo distribution, the sorted probabilities are the 0.380, dog 0.255, mat 0.127, jumped 0.069 and so on. Which tokens survive top-p with p=0.6?Apply
The ten demo tokens sorted by probability as horizontal bars (largest 0.380, then 0.255, 0.127, 0.069) beside a top-p, p = 0.6 label and a dashed box for the survivors.
Probabilities printed by main.py's demo_sampling.

Use It

PyTorch, 2026 idiom:

python
from transformers import AutoModelForCausalLM, AutoTokenizer
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.2-3B-Instruct")
tok = AutoTokenizer.from_pretrained("meta-llama/Llama-3.2-3B-Instruct")
 
prompt = "Attention is all you need because"
inputs = tok(prompt, return_tensors="pt")
out = model.generate(
    **inputs,
    max_new_tokens=64,
    temperature=0.7,
    top_p=0.9,
    do_sample=True,
)
print(tok.decode(out[0]))

Under the hood, generate() runs the forward pass, pulls the final-position logits, samples the next token, appends it, and repeats. Production LLM inference stacks (vLLM, TensorRT-LLM, llama.cpp, Ollama, MLX) run the same loop with heavy optimization: batched prefill, continuous batching, KV cache paging, speculative decoding.

GPT vs BERT, one line each: GPT predicts P(x_t | x_{<t}). BERT predicts P(x_masked | x_unmasked). The loss determines whether the model can generate.

Check: Use It0/2
1. What one-line distinction does the lesson draw between GPT and BERT?Explain
Two panels. GPT receives the tokens The cat sat and has a dashed predicts box; BERT receives The [MASK] sat and has a dashed predicts box.
Typed example sentence.
2. What optimizations do production stacks like vLLM and llama.cpp add on top of the basic generate() loop?Explain
The generate() loop of forward pass, sample, append with a return arrow, beside the names vLLM, TensorRT-LLM, llama.cpp, Ollama and MLX and a dashed box for what is added on top.
Names and the heavy optimization phrase from the lesson's Use It section.

Ship It

The sampling-tuner skill lives in outputs/skill-sampling-tuner.md. It picks sampling parameters for a new generation task and flags when deterministic decoding is required.

Check: Ship It0/1
1. What does this lesson's Ship It skill, sampling-tuner, do?Define
A file card outputs/skill-sampling-tuner.md, then a flow from a new generation task into the sampling-tuner skill into a dashed box for its output.
File name and skill name from the lesson's Ship It section.
Author's Note

I'd run main.py and read the post-softmax matrix first. The mask is one line, but what to look at is the zeros above the diagonal and the fact that each row is still a distribution over positions 0..i. Predict row 0 before you print it: it has only one unmasked position, so all of its weight lands on position 0.

My bet is that the loss demo is the most useful output in the file. It prints the loss for slightly boosted logits, the loss for random logits, and the random baseline ln V for a 10-token vocab. A loss at ln V means the model is doing no better than guessing uniformly, so I'd compare a training loss against that number first.

For sampling, I'd start with min-p and temperature 0.7, the default suggested under the decoding table, then run the sampling demo to see where the strategies disagree on one fixed distribution. Get a feel for that on the toy before tuning on a real model.

Exercises

  1. Easy. Run code/main.py and check by eye that the printed causal attention matrix is lower-triangular after softmax (the demo only prints it, so add an assert if you want a hard check). Spot-check: row 3 should have weights only in columns 0–3.
  2. Medium. Implement beam search with width 4 (not in main.py, you write it). Compare perplexity of beam-4 vs greedy on 10 short prompts. Does beam win? (Hint: it tends to for translation, not for open-ended chat.)
  3. Hard. Implement speculative decoding (also not in main.py): use a tiny 2-layer model as the draft and a 6-layer model as the verifier. Measure wall-clock speedup on 100 completions of length 64. Confirm the outputs match greedy decoding of the verifier.

Key Terms

TermWhat people sayWhat it actually means
Causal mask"The triangle"Upper-triangular -inf matrix added to attention scores so position i sees positions up to and including i.
Next-token prediction"The loss"Cross-entropy of the model's distribution against the true next token at each position.
Autoregressive"Generate one at a time"Feed output back in as input. Training is parallel across positions, generation is serial.
Logits"Pre-softmax scores"Raw output of the LM head before softmax. Sampling happens on these.
Temperature"Creativity knob"Divide logits by T. T→0 approaches greedy, T→∞ approaches uniform.
Top-p"Nucleus sampling"Truncate the distribution to the smallest set summing to ≥p, then sample from what remains.
Min-p"Better than top-p"Keep tokens where p ≥ min_p × max_p, so the cutoff adapts to how sharp the distribution is.
Speculative decoding"Draft + verify"A cheap model proposes N tokens and the big model verifies them in parallel. Described in this lesson, not implemented in main.py.
Teacher forcing"Training trick"During training, feed the true previous token instead of the model's prediction. Standard for seq2seq LMs.

Further Reading

Code

Ships With