Every canonical term. Same full explainers as the in-body tooltips.
- .to() is not in-place
- tensor.to(device) returns a new tensor; you must assign. model.to(device) is different — modules are stateful. His first training bug: the CPU token buffer was never actually moved.
- 0.3068
- Overnight max HellaSwag, completion-scored. Beats OpenAI GPT-2 124M (~0.295), still under GPT-3 124M (~0.335). He jokes that if he had slept for a fifth epoch they might have caught it.
- 0.5M tokens
- GPT-3 124M’s token batch. Will not fit as one step, so gradient accumulation over micro-batches. Scale each loss by 1/n_micro so the summed grad equals that large-batch grad.
- 50257
- GPT-2 vocabulary size: 50,000 BPE merges + 256 byte tokens + 1 EOT. An ugly CUDA dimension. He still trains this many real tokens; 50304 is only a pad of unused logits.
- 50304
- 50257 padded up so the dim has many factors of 2. Extra logits unused; kernels faster; ~93 ms. wte / lm_head grow by the pad rows; weight tying still holds.
- A100
- His GPUs — 8× A100 80GB SXM on a Lambda box. Peak (dense, no sparsity): ~19.5 TF FP32 / ~156 TF TF32 / ~312 TF BF16. 60% utilization is excellent because training is memory-bound. Speed table is this machine at B=16, T=1024 — not a laptop.
- AdamW
- Adam with decoupled weight decay. He treats it as “Adam with a bug fix.” Default early-debug lr 3e-4; the real run uses 6e-4 with warmup + cosine. Keeps first and second moment buffers (m, v).
- all-reduce
- The DDP communication that averages gradients across ranks so every replica takes the same AdamW step. With gradient accumulation you wrap the non-final micro-steps in no_sync() so you all-reduce once, not N times.
- apply()
- nn.Module.apply walks every submodule. He uses it at the end of GPT.__init__ to install N(0, 0.02) / bias 0, then special-cases residual c_proj.
- attention / self-attention
- The reduce: every token emits Q, K, V; QKᵀ says who is interesting; softmax weights a sum of V. Causal mask forbids looking at the future. In GPT-2 this is multi-head, implemented as one fused module with heads as a batch dim. Attention gathers; the MLP thinks.
- Attention Is All You Need
- Vaswani et al. 2017 — the original encoder-decoder Transformer, fixed sinusoidal positions, post-norm. GPT-2 drops the encoder and cross-attention, trains wpe, and moves LayerNorms to pre-norm plus ln_f. Weight tying is already in this paper (and Press & Wolf 2017).
- autocast
- torch.autocast(device_type="cuda", dtype=torch.bfloat16) around forward + loss only. Matmuls run BF16; softmax, LayerNorm, and the loss stay FP32; wte.weight stays FP32. No gradient scaler.
- backward
- loss.backward() deposits gradients into .grad (plus-equals). Overfitting one batch to ~0 is the test that this path works. Under weight tying, wte receives grad contributions from both the embedding lookup and the lm_head.
- batch (B, T, C)
- B independent sequences, T tokens, C = 768 channels. Debug: B=4, T=32. Speed tests: B=16, T=1024. Labels Y are the same stream shifted +1, so he loads B·T+1 tokens. If you OOM, drop B; keep B a nice number.
- batch size schedule
- GPT-3 ramps batch size in the early part of training. He discusses it next to 2-D-only weight decay and FusedAdamW. The logical batch he actually targets is the GPT-3 124M ~0.5M tokens, faked with accumulation.
- BatchNorm
- Another train/eval-behavior layer that this net does not use. GPT-2 normalizes with LayerNorm, not BatchNorm. Listed next to Dropout as the reason eval() might do nothing here.
- BERT
- Also picked up the tanh-approximate GELU after the Hendrycks / slow-erf issue. Historical company for GPT-2’s nonlinearity. He keeps the approx only because he is matching the GPT-2 checkpoint.
- bfloat16 / BF16
- 16-bit float that keeps FP32’s exponent, so no loss-scaling. torch.autocast around forward+loss only. Activations (logits) become BF16; parameters like wte stay FP32. Softmax / LN / loss stay FP32. ~300 ms.
- block size / context
- 1024 for GPT-2. T in (B, T) cannot exceed this. wpe has 1024 rows. Debug batches use T=32; the speed tests and the real run use T=1024.
- BPE / tokenizer
- Byte-pair encoding. GPT-2 vocab 50257. Token 198 is newline. “Hello, I'm a language model,” is 8 tokens. The 50k merges plus 256 byte leaves plus one EOT token are the whole vocabulary.
- build-nanogpt
- github.com/karpathy/build-nanogpt — the artifact of this video, commit per step. By the end ~90% of nanoGPT. Discussions tab / issues / PRs, plus the Zero to Hero Discord.
- c_attn
- The fused QKV projection name, matched to Hugging Face so the checkpoint copies. One Linear emits Q, K, V together; heads become a batch dimension. Algorithmically the same multi-head attention as the previous video.
- c_proj
- The output projection of attention and of the MLP — the residual-stream write. residual init scales these by 1/√(2·n_layer) so variance does not grow with depth.
- causal
- Autoregressive: a token may only attend to itself and the past. The mask (attn.bias in HF) enforces this so you can train on every position in the window at once. That is why GPT-2 is a decoder-only language model.
- causal mask / autoregressive mask
- Zeros out attention to future tokens so the model can be trained on every position at once. Implemented as attn.bias in the HF/OpenAI buffers — he ignores that buffer when loading and rebuilds the mask in the fused attention kernel.
- Cloud compute
- Rent the box. He says you can reproduce 124M in roughly an hour (or less) for about $10 at lecture time. His own timings are the Lambda 8×A100, not that $10 sketch.
- Common Crawl
- The messy web dump in the GPT-3 mix (with books, Wikipedia, etc.). Not what he trains on tonight. FineWeb-EDU is the modern educational-filtered replacement he actually tokenizes to .npy shards.
- completion scoring
- HellaSwag the completion way: condition on the context, sum or average log-probs of each of the four endings, pick the most likely. Not free generation. That is how he gets 0.3068 overnight and how he compares to OpenAI’s ~0.295.
- cosine decay
- After warmup, lr cosine-anneals toward min_lr = 0.1 · max_lr. GPT-3 recipe. max_steps = 19073 ≈ one epoch on FineWeb-EDU at his batch. max_lr = 6e-4 in the final script.
- CPU
- Always available, and the home of the token buffers in DataLoaderLite. A full sample on CPU is ~6s at this tiny size. You cannot mix CPU tensors with CUDA tensors; forward builds positions on idx.device so a CPU idx just works.
- cross entropy
- Training loss = −log p(correct next token). F.cross_entropy wants 2-D logits, so he flattens (B,T,V) → (B·T, V). Random-init uniform guess: −ln(1/50257) ≈ 10.82; he measures ~11. That tells him the init is not confidently wrong.
- CUDA
- The NVIDIA GPU device. model.to(device), tensors on idx.device, autocast device_type="cuda". His box has eight of them (cuda:0 …); DDP is one process per GPU via torchrun.
- cuda.synchronize
- Wait for the GPU before you read the clock. Without it, CUDA is async and your millisecond timings lie. He uses this for the B=16, T=1024 speed table (1000 ms FP32 baseline and friends).
- data_ptr
- PyTorch’s pointer to the storage. wte.weight.data_ptr() == lm_head.weight.data_ptr() in the HF/OpenAI checkpoint — proof of tying, not just equal values. He redirects transformer.wte.weight = lm_head.weight so the same storage is used twice.
- DataLoaderLite
- Walk a 1-D token stream in strides of B·T, fetch B·T+1 so Y is X shifted by one, wrap at EOF. Tokens stay on CPU and ship per batch. 338,025 Tiny Shakespeare tokens; at B=4, T=32 that’s ~2,640 batches per epoch.
- DDP / DistributedDataParallel
- torchrun, one process per GPU, rank + world_size. All-reduce gradients so replicas stay in sync. Only rank 0 logs, samples, and writes checkpoints. This is how the 8×A100 box is actually used.
- dead ReLU
- A ReLU unit stuck in the exact-zero tail, so it gets zero gradient forever. GELU’s smooth tail always contributes a local gradient — one reason GPT-2 (and BERT) picked it over ReLU.
- decoder-only
- GPT-2 drops the encoder stack and the cross-attention that read it. What’s left is a stack of causal self-attention + MLP blocks — a language model that only looks left. That is the skeleton he implements as the GPT nn.Module.
- device (CUDA / MPS / CPU)
- Autodetect. Tensors created in forward use idx.device. You can follow on CPU/Mac until DDP. A CPU override of the tiny sample is ~6s. Serious multi-GPU training is later, on the 8×A100 box.
- Discord
- Zero to Hero Discord, where he says he will hang out on the nanoGPT channel. The other place besides the build-nanogpt discussions tab to talk about the lecture.
- dropout
- A train/eval-behavior layer. This GPT-2 124M net has none, so model.eval() may be a no-op. He still calls eval() before sampling, “just in case” PyTorch internals care.
- embedding
- A glorified lookup table — nn.Embedding wraps a single tensor you index by row. wte is vocab×768; wpe is 1024×768. Token + position embeddings add (with broadcast over batch) before the first block.
- EOT
- End-of-text, the one special token on top of 50,000 BPE merges and 256 byte tokens (vocab 50257). Delimits documents and can start generation. The “ugly” leftover after those three pieces is why he pads to 50304.
- epoch
- One pass over the token stream. Tiny Shakespeare at B=4, T=32 is ~2,640 batches. The ~2-hour FineWeb run is one epoch (19073 steps); the overnight is ×4, ~40B tokens, eight hours while he slept.
- eval mode
- model.eval(). This net has no Dropout/BatchNorm, so it may be a no-op; he does it anyway. Good practice when you are only sampling. Combined with torch.no_grad so autograd does not store activations.
- fan-in
- Number of inputs to a Linear/Embedding. Xavier-style init uses ~1/√fan-in. 0.02 is roughly that at GPT-2 widths (768 → ~0.036, 1600 → ~0.025), which is why OpenAI’s 0.02 is in the right ballpark.
- FineWeb
- The Hugging Face web-crawl family; FineWeb-EDU is the educational slice he trains on. Tokenized once to memory-mapped .npy shards. Replaces WebText, which was never a clean public dump.
- FineWeb-EDU
- Modern educational web crawl. He tokenizes once to .npy shards and memory-maps them. Replaces unavailable WebText. LLMs (he mentions Llama 3 70B) judge which pages are educational. Overnight training is this data, not Tiny Shakespeare.
- Flash Attention
- IO-aware attention. Never writes the T×T matrix to HBM; tiles softmax in SRAM. Same math. ~96 ms. torch.compile cannot invent this one — it is an algorithmic rewrite (online softmax), not a fuse of existing kernels.
- FP16
- Older 16-bit format (Volta). Smaller exponent range, so you need gradient scalers. He refuses it. BF16 keeps FP32’s exponent and only crops mantissa — same range, no scaler — which is why he uses that instead.
- FP32
- Default PyTorch dtype. A100 peak ~19.5 TF dense. His untuned 124M step is ~1000 ms at B=16, T=1024. TF32 keeps the tensors looking like FP32 and only crops the mantissa inside the matmul.
- from_pretrained
- A classmethod that builds his GPT and copies Hugging Face / OpenAI tensors in, transposing a few leftover TF weights. from_pretrained("gpt2") is the 124M confidence check: load, then generate coherent text from the official weights.
- FusedAdamW
- Single CUDA kernel for the AdamW update. ~90 ms. He enables it by inspecting AdamW’s signature for a fused kwarg, then passing fused=True on versions that support it.
- GELU (tanh)
- Smooth ReLU used by GPT-2. They used the tanh approximation because TensorFlow’s erf was slow in 2018 (a Hendrycks issue). Today you’d use exact GELU; he keeps tanh to match the checkpoint. Avoids dead-ReLU (zero gradient in the flat tail).
- GPT-2 1.5B / gpt2-xl
- The largest GPT-2, 1558M in the corrected count. People say “GPT-2” and mean this. Hugging Face loads it as “gpt2-xl”. Not the model he trains tonight — the overnight run and the HellaSwag / val-loss comparisons are the 124M.
- GPT-2 124M
- Smallest model in the GPT-2 miniseries. 12 layers, 12 heads, 768 channels, context 1024. OpenAI’s paper table of parameter counts is wrong (a GitHub note admits a counting error); 124M is the corrected number. Hugging Face name “gpt2” loads this one; “gpt2-xl” is the 1.5B. This whole lecture’s target.
- GPT-2 miniseries
- Four released GPT-2 sizes, 124M through 1558M, so you can put model size on the x-axis and chart downstream metrics. The paper’s parameter-count table is wrong; the GitHub repo notes a counting error and 124M is the corrected small one. Usually the biggest model is the one people call “GPT-2.”
- GPT-3
- The paper he treats as the concrete recipe: AdamW, grad-norm clip, warmup+cosine, ~0.5M token batch, 2-D-only weight decay. Architecture is close to GPT-2. GPT-3 124M HellaSwag ~0.335 is the line he does not quite catch overnight.
- GPT-3 124M
- Same 124M shape, GPT-3 hyperparameters. HellaSwag ~0.335 — still above his overnight 0.3068. The GPT-3 paper is the optimizer/schedule document he copies (AdamW, clip, warmup+cosine, ~0.5M token batch).
- GPT-3 paper
- The concrete optimizer/schedule document. Architecture is not a huge departure from GPT-2; hparams are. He follows it for AdamW, clip, warmup+cosine, and the ~0.5M token batch. GPT-2 the paper is vague; GPT-3 is the recipe.
- gradient accumulation
- GPT-3 124M used ~0.5M tokens/step, which won’t fit. Run n micro-batches, scale each loss by 1/n, step once. Combined with DDP: no_sync() on every micro-step except the last, so you don’t all-reduce N times.
- gradient clipping
- Clip the global grad norm so a bad batch can’t explode a step. GPT-3-style hygiene. He prints the pre-clip norm; early in training it can be “really high.” Architecture is already fast; this is “train like they trained.”
- gradient scaler
- Extra state needed for FP16 so tiny grads don’t underflow. Unnecessary with BF16. He discusses scalers only to explain why he will not use float16 for this training run.
- HBM
- The GPU’s big, slow-ish memory. Tiny unfused ops round-trip here between kernels — that is what torch.compile and Flash Attention are trying to stop. Flash Attention never writes the T×T attention matrix to HBM at all.
- held-out
- Documents the model has not trained on, used for validation loss — the same next-token cross-entropy. Keep sampling RNG separate from training RNG so generating text does not perturb the optimization.
- HellaSwag
- 4-way commonsense ending pick. He scores by completion log-prob (condition on context, pick the most likely ending) — not free generation. Overnight max 0.3068, beating OpenAI GPT-2 124M (~0.295), still under GPT-3 124M (~0.335). use_compile = False in the final run because torch.compile breaks this eval.
- Hello, I'm a language model
- The Hugging Face docs prefix. 8 tokens, stacked ×5 → x of shape (5, 8), then sampled to length 30 with top-k 50. He uses it to compare his class against the HF pipeline, and later to read overnight generations.
- Hendrycks
- Dan Hendrycks, GELU. The tanh approximation exists because at the time TF’s erf was slow — he says this is the GitHub issue as far as he can tell. BERT and GPT-2 picked up the approx; he keeps it only to match the 124M checkpoint.
- Hugging Face Transformers
- How he loads the released TF GPT-2 weights as PyTorch. modeling_gpt2.py is ~2000 lines; his class is ~100. “gpt2” = 124M. They already did the TensorFlow→PyTorch conversion, which is why he starts there instead of openai/gpt2.
- idx
- The (B, T) integer token tensor that enters forward. Positions 0..T−1 are built on idx.device so CPU/GPU never mix. T cannot exceed block size 1024. Output logits are (B, T, vocab).
- Jupyter
- play.ipynb, running as the VS Code notebook extension. Where he plots wpe, walks the state_dict, and first samples the HF pipeline. The training script itself is train_gpt2.py in the terminal.
- kernel
- A single GPU program launch. Unfused GELU-then-Linear is two kernels and two HBM trips; fusion makes one. FusedAdamW is one kernel for the optimizer update. Flash Attention is a kernel that had to be rewritten, not just fused.
- kernel fusion
- Combining several ops into one GPU kernel to cut memory traffic. Flash Attention is a fusion torch.compile cannot discover because it needs an algorithmic rewrite. torch.compile does discover GELU+Linear-style fusions.
- Lambda
- The cloud box he SSH’s into: 8× A100 80GB SXM. About $10 and roughly an hour for a 124M-scale reproduction at the time of the lecture. All of the millisecond timings (1000 → 90 ms) are this machine at B=16, T=1024.
- LayerNorm
- Per-token mean/var normalize, then scale+shift. GPT-2 default init is scale=1, bias=0. Stays in FP32 under autocast because it’s precision-sensitive. In the GPT-2 block it sits before attention and before the MLP (pre-norm), plus a final ln_f.
- learning rate
- Early debug: 3e-4, “a pretty good default.” Final run: max_lr 6e-4, min_lr 0.1·max, warmup 715, cosine over 19073 steps. Not a fixed 3e-4 once he starts “train like they trained.”
- learning rate warmup
- Linear ramp from 0 so step 1 doesn’t smash a random net. Final script: warmup_steps = 715. Copied from the GPT-3 recipe, then cosine-annealed toward min_lr = 0.1 · max_lr.
- Linear
- nn.Linear, the matmul + optional bias that is almost all of GPT compute. Tensor cores exist to do these 4×4 tiles. lm_head is Linear 768→50257 with no bias; the MLP is two Linears with GELU between.
- Llama 3
- Mentioned twice: SwiGLU as the modern MLP nonlinearity, and Llama 3 70B as the judge that filters FineWeb-EDU for educational pages. Not the architecture he trains.
- llm.c
- Karpathy’s from-scratch C/CUDA rewrite of this stack. Same algorithm, faster. PyTorch was for understanding. He runs train_gpt2.py next to the C implementation and the printed losses/norms match, while the C side is quicker.
- lm_head
- Final Linear 768 → vocab, no bias. In GPT-2 this matrix IS wte. The 768→50257 matmul dominates the 124M forward. Output logits are (B, T, 50257), one softmax away from next-token probabilities.
- ln_f
- The extra final LayerNorm GPT-2 adds right before the classifier — the second pre-norm change. Named to match Hugging Face (transformer.ln_f) so the state_dict key copies.
- logits
- Raw next-token scores, shape (B, T, vocab), one softmax away from probabilities. He only uses the last time-step when sampling. Under autocast the logits tensor becomes BF16; the softmax over them stays FP32.
- loss.item
- Turns a 0-dim GPU tensor into a Python float on CPU so you can print it. That hidden device copy is one of the little PyTorch device facts he flags next to .to() not being in-place.
- m and v moments
- AdamW’s first and second moment buffers, one pair per parameter. m looks like momentum; v looks like RMSProp. They are why AdamW is heavier than SGD and why FusedAdamW (one kernel to update params + m + v) is worth it.
- matmul precision
- torch.set_float32_matmul_precision("high") turns on TF32 for FP32 matmuls on Ampere. One line, no dtype changes. 1000→333 ms on his box. “Medium"/"highest" are the other knobs; he wants tensor cores.
- mechanistic interpretability
- Looking at a 300×300 slice of a weight and asking what the structure means. He waves at it over the first-layer imshow and moves on. This lecture is a reproduction, not an interp paper.
- memory-bound
- Tensor cores idle waiting for bytes. Why TF32 didn’t give the full 8×: you still move FP32 traffic. Most well-tuned training sits in this regime; 60% of peak is already excellent.
- micro-batch
- One forward/backward that fits in memory. n of them, each loss scaled by 1/n, make one optimizer step of ~0.5M tokens. DDP: no_sync() on all but the last micro-step.
- min val 3.0726
- Overnight minimum validation loss. Beats the OpenAI GPT-2 124M dashed line on his plot. Min train is 2.922. He also notes a weird FineWeb-EDU periodicity across epochs that he does not fully explain.
- mixed precision
- Some ops in BF16 (matmuls), some in FP32 (norms, softmax, loss). Autocast decides. He wraps only the forward and the loss; the optimizer still steps FP32 parameters. Result after TF32: ~300 ms.
- MLP / feed-forward
- The map: two Linear layers with GELU between, applied to each token alone. No communication. Attention gathers; MLP thinks. Together a block is map-reduce, and twelve of them iteratively refine the residual stream.
- mmap
- Memory-map the token shards so the loader is just pointer arithmetic over uint16/int32 arrays. Tokens live on CPU and ship per batch, same idea as DataLoaderLite, at FineWeb scale.
- MPS
- Apple Silicon GPU backend. Autodetect order: CUDA, else MPS, else CPU. New enough MacBooks can follow the early lecture here; DDP and the 8×A100 overnight are not this path.
- multi-head attention
- Several attention heads in parallel; outputs concatenated. He implements them as one module, heads as a batch dim, not as separate Python modules. Same math as the previous Zero to Hero video, just fewer kernel launches.
- n_embd
- 768 channels for GPT-2 124M — the residual-stream width. wte and wpe rows, the block, and the lm_head input are all this size.
- n_head
- 12 heads for GPT-2 124M. In the fused kernel they become a batch dimension next to B, so the QKᵀ / softmax / V matmuls run over all heads at once.
- n_layer
- 12 for GPT-2 124M. Also the length of transformer.h. Residual init uses N = 2 · n_layer because each layer writes twice (attn + MLP).
- nanoGPT / build-nanogpt
- This video’s repo is build-nanogpt (commit-per-step). By the end it is ~90% of nanoGPT. Walk the commits in order and you walk the video. He also points at the discussions tab and the Zero to Hero Discord.
- NCCL
- NVIDIA’s collective library under DDP all-reduce (ASR often “nickel”). Mentioned again in the llm.c C/CUDA stack (MPI / NCCL / CUDA). You do not call it directly from the teaching PyTorch — DDP does.
- nice numbers
- CUDA likes dims with many factors of 2. Vocab 50257 is ugly (50k BPE + 256 bytes + 1 EOT). He pads to 50304. Extra logits unused. ~93 ms. Weight tying still holds; the pad rows are just unused classifier bins.
- nn.Module
- PyTorch’s network base class. His GPT, Block, CausalSelfAttention, and MLP are modules; apply() walks them for init. The skeleton matches Hugging Face names (transformer.wte, h.0…h.11, ln_f, lm_head) so the state_dict copies.
- no_sync
- DDP context manager that skips the all-reduce. Used on every micro-step except the last under gradient accumulation. Without it you would pay N communications for one logical GPT-3-sized batch.
- occupancy
- How many warps the GPU can keep in flight. Ugly dimensions (50257) waste this; padding vocab to 50304 makes kernels happier. Part of why “nice numbers” show up as a real millisecond win on the A100.
- online softmax
- The numerical trick Flash Attention uses to tile softmax without a full T×T materialization. Proposed earlier; Flash Attention is the IO-aware kernel that relies on it. Same math as the naive implementation.
- OpenAI
- Released GPT-2 in 2019: blog post, paper, and github.com/openai/gpt-2 weights. The paper is thin on training details; the original code is TensorFlow. Weights are public; the recipe is not, which is why he also reads GPT-3.
- overfit a single batch
- First training test. If loss can’t go to ~0 on one batch, backward is broken. Then he switches to the loader and loss only falls to ~6.6 in 50 steps (unused-vocab gains). Early-debug AdamW lr is 3e-4.
- overnight
- ×4 the ~2-hour one-epoch run, ~eight hours, ~40B tokens, while he was sleeping. Min train 2.922, min val 3.0726, max HellaSwag 0.3068. Beats OpenAI GPT-2 124M; GPT-3 124M HellaSwag still above.
- pipeline
- Hugging Face’s generate wrapper. Default top-k 50 is the knob he copies. His loop matches the HF model internals but not the pipeline snippet — leftover pipeline defaults, not a weight bug.
- plus-one token
- Fetch B·T+1 so the last position has a target. X = buf[:-1], Y = buf[1:], both viewed (B, T). DataLoaderLite always advances by B·T but reads one extra token for that shift.
- pre-norm
- GPT-2 moves LayerNorm to BEFORE attention and BEFORE the MLP, and adds a final ln_f before the classifier. Residuals stay clean (x = x + f(LN(x))), so the loss gradient can ride the residual stream unchanged down to the tokens. This is the main diagram change versus Attention Is All You Need.
- Press & Wolf
- 2017 paper arguing you should share the input embedding and the pre-softmax linear. Intuition: tokens that are close as inputs should get similar output logits. GPT-2 (and AIAYN) do this; it is the wte = lm_head tie.
- PyTorch
- The framework he actually writes. Friendlier than the original TensorFlow GPT-2. Default random init of nn.Linear / Embedding is enough to get a garbage-token model; later he overrides that with std 0.02 and residual init.
- Q, K, V
- Query, key, value — the three vectors each token emits in attention. QKᵀ is “how interesting”; softmax weights a sum of V. c_attn produces all three in one Linear, then split/transpose gymnastics make NH a batch dimension.
- rank
- This process’s id in the DDP group, 0 .. world_size−1. Only rank 0 prints, logs, samples, and writes checkpoints. Each rank sees a different data shard; all-reduce keeps the replicas’ gradients in sync.
- rank 0
- The DDP process that is allowed to print, log, sample, and checkpoint. Everyone else trains silently. Necessary once torchrun has started eight replicas on the A100 box.
- RedPajama
- An open attempt to recreate a LLaMA-style web mix. He mentions it among the dataset options people try when WebText is unavailable. He does not train on it tonight — FineWeb-EDU is the pick.
- residual init
- GPT-2 paper: scale residual projection weights by 1/√N, N = number of residual layers. He uses N = 2 · n_layer (attn + MLP per block) so residual-stream variance stays ~1 instead of growing like √depth. std starts at 0.02, then those c_proj weights get the extra 1/√N scale.
- residual stream
- The highway x = x + branch(x). Addition splits the gradient equally, so the top gradient reaches the token embeddings unaltered, and each block only adds its contribution. He wants norms inside the branches, not on the highway. A clean residual pathway is “desirable from an optimization perspective.”
- RMSProp
- The second-moment flavor inside Adam’s v buffer. He does not use RMSProp alone. AdamW keeps m (momentum-like) and v (RMSProp-like) and applies decoupled weight decay.
- scaling laws
- Plot model size (the miniseries) against downstream metrics — translation, summarization, QA — and watch them improve. Why labs release 124M through 1.5B instead of one checkpoint.
- SGD
- The simpler optimizer from earlier Zero to Hero videos. Not what he uses here. AdamW is a bit more involved — it keeps m and v moment buffers and, in his words, just optimizes language models faster than SGD.
- shards
- .npy token files written once after tiktoken, then memory-mapped by the loader. He does not retokenize FineWeb-EDU every step. Multi-epoch, he notes you should permute documents/shards — the overnight loader does not.
- softmax
- Turns logits into a distribution that sums to 1. Used in attention (over keys) and at the classifier (over vocab). Stays FP32 under autocast. Flash Attention tiles this softmax in SRAM instead of writing the full T×T matrix to HBM.
- SRAM
- On-chip memory next to the tensor cores. Flash Attention tiles softmax here so the full T×T matrix never hits HBM. Same math, much less memory traffic; step time ~96 ms after compile.
- state_dict
- Raw name→tensor map of parameters. He copies HF keys into his module, transposing a few leftover TF weights, ignoring buffers like attn.bias. from_pretrained("gpt2") is a classmethod that returns his GPT with those tensors installed.
- std 0.02
- OpenAI model.py inits Linear/Embedding ~ N(0, 0.02), biases 0. Roughly 1/√fan-in at these widths (768 → 0.036, 1600 → 0.025). They used 0.01 for position embeddings; he keeps 0.02.
- SwiGLU
- A later MLP nonlinearity (Llama 3 and friends). GPT-2 does not use it; GPT-2 is tanh-approximate GELU. He mentions the swap only to place GELU in history.
- Tensor Cores
- A100 instruction that does a 4×4 matmul. Almost all GPT work is Linear matmuls; the lm_head dominates at 124M. TF32 and BF16 are both ways of feeding those tensor cores. Peak on his A100: ~156 TF TF32 / ~312 TF BF16 (dense).
- TensorFlow
- Original OpenAI GPT-2 code. “Not used as much anymore,” and some weights are transposed from what PyTorch wants. The tanh GELU exists because TF’s erf was slow in 2018. Hugging Face already converted the checkpoint.
- TF32
- Ampere tensor-core mode. Same 8-bit exponent as FP32, mantissa cropped 23→10 inside the matmul instruction. torch.set_float32_matmul_precision("high"). Tensors still look like FP32. Promised 8×, he got ~3× (1000→333 ms) because the workload is memory-bound.
- tiktoken
- OpenAI’s BPE tokenizer library. gpt2 encoding: 50,000 merges + 256 byte tokens + 1 end-of-text = 50257. Compression on English is ~3:1. “Hello, I'm a language model,” is 8 tokens; token 198 is newline.
- Tiny Shakespeare
- ~1M ASCII characters, ~338k tokens, the debug corpus. Not the real pretrain set. Used to overfit one batch and then to walk a DataLoaderLite. Tokenizer compression ~3:1, so 1000 chars ≈ 300 tokens.
- token 198
- The GPT-2 newline token. Shows up immediately in the Tiny Shakespeare dump (and doubled for blank lines). A useful sanity check that tiktoken is the real gpt2 encoding.
- top-k 50
- Hugging Face pipeline default. Keep the 50 most likely tokens, zero the rest, renormalize. Stops the model from sampling rare junk. His generations match HF internals but not the pipeline snippet because of leftover knobs.
- torch.compile
- PyTorch’s graph compiler. Fuses kernels (GELU+Linear, etc.) so you don’t round-trip to HBM between tiny ops. First step pays compile cost; then ~130 ms. He turns it OFF for the overnight run because it breaks HellaSwag eval / generation.
- torch.no_grad
- Sampling context so autograd doesn’t store activations. Saves memory and some time. The sampling loop grows X one column at a time under this context manager, keeping only last-position logits.
- torchrun
- The launcher that starts one process per GPU and sets RANK / WORLD_SIZE / LOCAL_RANK. That is how DDP is actually started on the 8×A100 box. Only rank 0 should I/O.
- train_gpt2.py
- The script he builds commit-by-commit in build-nanogpt. Starts as a load-and-sample file; ends as ~90% of nanoGPT with DDP, FineWeb, and HellaSwag. He also points at train_gpt2.py inside llm.c as the PyTorch reference next to the C/CUDA file.
- Transformer
- The 2017 Attention Is All You Need architecture. GPT-2 is a decoder-only Transformer: no encoder, no cross-attention. Almost all the compute is matrix multiplies inside Linear layers. He implements the 124M in about 100 lines versus Hugging Face’s ~2000.
- ugly number
- A dimension CUDA hates — primes, or 50257. Nice numbers have many factors of 2. Padding vocab 50257→50304 is the concrete fix; extra logits are unused.
- use_compile
- Flag he sets False for the overnight run. torch.compile is fast (~130 ms) but breaks HellaSwag eval and generation. One of the leftover issues he lists at the end, next to FineWeb periodicity and the unpermuted loader.
- validation data
- Sequences the model has not seen, used to measure next-token loss and to draw the curve he hopes will beat the OpenAI 124M dashed line. Same metric as training; different documents.
- validation loss
- Same next-token cross-entropy on held-out documents. Overnight min 3.0726, which beats the OpenAI GPT-2 124M dashed line on his plot. Min train 2.922. Sampling is revived during the run so you can read generations, not just stare at a curve.
- vocab size
- 50257 for GPT-2 (50k BPE + 256 bytes + 1 EOT). He pads the classifier to 50304 for nice CUDA dims. Uniform random-init cross entropy is −ln(1/50257) ≈ 10.82.
- VS Code
- His single interface: notebook + train_gpt2.py + terminal + browser, all in one editor. The intro plate in the chapter book is this layout next to the GPT-2 paper.
- WebText
- GPT-2’s dataset — outbound Reddit links. Not released as a clean dump he can just wget. That is why the reproduction trains on FineWeb-EDU instead.
- weight decay
- Applied to 2-D parameters (the matrices) only — not biases or LayerNorms. He splits AdamW parameter groups that way. Combined with FusedAdamW this is part of the ~90 ms step.
- weight tying
- wte and lm_head.weight share one storage (same data_ptr in the HF/OpenAI checkpoint). From AIAYN / Press & Wolf 2017: tokens that are close as inputs should get similar output logits. transformer.wte.weight = lm_head.weight; backward grads add from both ends. Saves ~40M params, ~30% of 124M.
- world_size
- Number of DDP processes, usually one per GPU. torchrun sets RANK and WORLD_SIZE. He uses them to detect DDP, to shard data, and to restrict logging/sampling/checkpoints to rank 0.
- wpe
- Learned absolute position table, 1024 × 768. Original Transformer used fixed sinusoids; GPT-2 trains these. His imshow plot shows sinusoidal-ish rows that are a bit jagged — he reads that as slightly undertrained. At init this table is random noise; smoothness falls out of optimization.
- wte
- Token embedding table, 50257 × 768. Also reused as lm_head (weight tying). ~40M params, ~30% of 124M. Each row is the distributed representation that stands in for one BPE token.
- Xavier
- The default-ish Linear init from earlier Zero to Hero videos (sometimes “Javier” in the captions). PyTorch’s constructor uses a variant of this until he overrides everything with N(0, 0.02) plus residual 1/√N on c_proj.
- Zero to Hero
- The playlist this continues. Prior videos: GPT from scratch, tokenization. He assumes that familiarity — “let’s build GPT from scratch” — and spends this lecture on the 124M reproduction, the kernels, and the GPT-3 recipe.
- zero_grad
- Must clear .grad because backward is += (it deposits, it does not overwrite). Forgot this and you silently accumulate steps. First line of every iteration after the data load.