Thomas Piacentini
← Workshop

Cheat sheet

Train and Load a GPT from Scratch

Original text · English

Build the 124M GPT, train it with AdamW and cosine warmup, load GPT-2 weights, gate the pipeline.

Run the lessons

CommandWhat it does
python3 code/main.py (35)Counts 124M params, generates with a tiny config
python3 code/main.py (36)Trains a tiny model, writes outputs/losses.jsonl
python3 code/main.py (37)Writes outputs/gpt2-stub.safetensors, loads it
python3 code/main.py (44)20 steps, per step lr and grad norm, text plot
python code/main.py planValidates manifest, cost estimate, prints DAG
python code/main.py runExecutes stages, writes manifest.out.yaml
python code/main.py gateApplies eval gates. Prints SHIP or HOLD
build_param_groups(model, weight_decay)AdamW groups, decay split
Sources

GPTConfig defaults (124M)

SettingDefaultNote
vocab_size50257Rows of the token table
context_length1024Rows of the position table
d_model768Embedding width
num_heads12d_model must divide by it
num_layers12Twelve TransformerBlock modules
mlp_expansion4MLP hidden is 4 times d_model
dropout0.1Tiny demo sets 0.0
weight_tyingTruelm_head shares tok_embed
Sources

GPTModel forward

ids0 to T-1tied weightToken idsshape B, Tclienttok_embed50257, 768storepos_embed1024, 768 learnedstoreAddembed_dropoutprocess12 blockspre-LN TransformerBlockmodelfinal_lnLayerNormprocesslm_headlogits B, T, 50257model
Nodes and connections
NodeRoleKind
Token idsshape B, Tclient
tok_embed50257, 768store
pos_embed1024, 768 learnedstore
Addembed_dropoutprocess
12 blockspre-LN TransformerBlockmodel
final_lnLayerNormprocess
lm_headlogits B, T, 50257model
FromToConnection
Token idstok_embedids
Token idspos_embed0 to T-1
tok_embedAdd
pos_embedAdd
Add12 blocks
12 blocksfinal_ln
final_lnlm_head
lm_headtok_embedtied weight
Sources

Build, count and sample

ref_model = GPTModel(GPTConfig())124M reference
count_parameters(ref_model)unique params, tied once
GPTModel(GPTConfig(weight_tying=False))adds vocab_size * d_model
self.lm_head.weight = self.tok_embed.weightshare, never copy
generate(tiny_model, prompt, max_new_tokens=12,
temperature=0.8, top_k=20, seed=42)temperature must be positive
top_k_filter(logits, top_k)others to -inf
window = tokens[:, -model.cfg.context_length:]sliding window
Sources

One training step

  1. make_batches target is the input shifted by one position.
  2. Schedule cosine_with_warmup sets group["lr"] in each param group.
  3. calc_loss_batch forward, flatten to (batch * seq, vocab), cross entropy.
  4. Backward loss.backward(), then clip_grad_norm_ at max_norm=1.0.
  5. AdamW optimizer.step() with betas=(0.9, 0.95).
  6. Log append {"step", "train_loss", "lr"} to losses.jsonl.
  7. Probe every eval_every steps run evaluate_model and generate_and_print_sample.
Sources

Training loop core

optimizer = torch.optim.AdamW(
build_param_groups(model, cfg.weight_decay),decay split
lr=cfg.max_lr, betas=(0.9, 0.95))
for step in range(cfg.num_steps):
lr = cosine_with_warmup(step, cfg.warmup_steps, cfg.num_steps, cfg.max_lr, cfg.min_lr)
for group in optimizer.param_groups:
group["lr"] = lr
inputs, targets = next(train_loader)
optimizer.zero_grad(set_to_none=True)
loss = calc_loss_batch(model, inputs, targets)flat cross entropy
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=cfg.grad_clip)
optimizer.step()
Sources

Schedule and size numbers

Warmup
lr_max * step / warmup_steps up to warmup_steps.
Cosine
lr_min + 0.5 * (lr_max - lr_min) * (1 + cos(pi * progress)).
Floor
past total_steps the rate stays at lr_min.
Warmup size
a fraction of total steps. Typical is 1 to 3 percent.
Floor size
lr_min at 10 percent of lr_max keeps the optimizer learning.
Weight tying
saves 50257 times 768 = 38 million parameters.
Residual init
scale out projections by 1 / sqrt(2 * num_layers).
Pre-training FLOPs
6 x params x tokens.
Sources

GPT-2 names to local names

FactorGPT-2 fileLocal model
Token embeddingwte.weighttok_embed.weight
Position embeddingwpe.weightpos_embed.weight
LayerNorm 1 scaleh.N.ln_1.weightblocks.N.ln1.scale
Fused QKV, transposedh.N.attn.c_attn.weightblocks.N.attn.qkv.weight
Attention out, transposedh.N.attn.c_proj.weightblocks.N.attn.out_proj.weight
MLP fc1, transposedh.N.mlp.c_fc.weightblocks.N.mlp.fc1.weight
MLP fc2, transposedh.N.mlp.c_proj.weightblocks.N.mlp.fc2.weight
Final LayerNormln_f.weight, ln_f.biasfinal_ln.scale, final_ln.shift
Sources

Load safetensors weights

make_stub_safetensors(STUB_PATH, cfg, seed=42)offline fixture
model = GPTModel(cfg)
report = load_safetensors(model, STUB_PATH, verbose=False)
report.summary()loaded= missing= unexpected= shape_mismatch=
report.ok()no missing, no shape_mismatch
model.lm_head.weight.data_ptr() == model.tok_embed.weight.data_ptr()tied
Sources

Troubleshooting

SymptomCauseFix
Loss near zero, model learns nothingNo shift by oneTarget at positions 1..T
LayerNorm scale goes to zeroWeight decay on the scaleScale and bias in the no decay group
One bad batch wipes hoursHuge gradientclip_grad_norm_ before step
Same samples before and after loadName map missed every tensorRead the LoadReport
shape_mismatch in the reportDims differ from the configLoader assigns nothing. Fix the config
Off by one LR late in the runGlued three part scheduleOne function over step, test the edges
Sources

Pipeline eval gates

SettingDefaultNote
mmlu65.0Must be at least this
humaneval40.0Must be at least this
truthfulqa50.0Must be at least this
safety_refusal_rate0.05Must be at most this
kl_from_reference25.0Must be at most this
cost_total_usd50000.0Must be at most this
budget_usd50000.0Run halts over budget
seed42Manifest field
Sources

Q&A

Why transpose on load?
c_attn, c_proj and c_fc use conv1d layout. nn.Linear wants the transpose.
Why print a sample during training?
Loss is a scalar. A sample shows a model stuck on one token.
Why slice eval batches once?
Identical batches make eval loss comparable across runs.
Where does the schedule read its step?
From global_step in the checkpoint, not a local counter.
Sources
Original source
---
id: gpt-from-scratch-training
title: Train and Load a GPT from Scratch
code: TL
subtitle: Build the 124M GPT, train it with AdamW and cosine warmup, load GPT-2 weights, gate the pipeline.
hue: 28
sources: phases/19-capstone-projects/35-gpt-model-assembly, phases/19-capstone-projects/36-training-loop-eval, phases/19-capstone-projects/37-loading-pretrained-weights, phases/19-capstone-projects/44-cosine-lr-warmup, phases/10-llms-from-scratch/13-building-complete-llm-pipeline
source_hash: 06e61dd1d80fe8c1
---

## Run the lessons
type: command-table
from: phases/19-capstone-projects/35-gpt-model-assembly/docs/en.md, phases/19-capstone-projects/36-training-loop-eval/docs/en.md, phases/19-capstone-projects/37-loading-pretrained-weights/docs/en.md, phases/19-capstone-projects/44-cosine-lr-warmup/docs/en.md, phases/10-llms-from-scratch/13-building-complete-llm-pipeline/docs/en.md

| Command | What it does |
|---|---|
| `python3 code/main.py` (35) | Counts 124M params, generates with a tiny config |
| `python3 code/main.py` (36) | Trains a tiny model, writes `outputs/losses.jsonl` |
| `python3 code/main.py` (37) | Writes `outputs/gpt2-stub.safetensors`, loads it |
| `python3 code/main.py` (44) | 20 steps, per step lr and grad norm, text plot |
| `python code/main.py plan` | Validates manifest, cost estimate, prints DAG |
| `python code/main.py run` | Executes stages, writes `manifest.out.yaml` |
| `python code/main.py gate` | Applies eval gates. Prints `SHIP` or `HOLD` |
| `build_param_groups(model, weight_decay)` | AdamW groups, decay split |

## GPTConfig defaults (124M)
type: defaults
from: phases/19-capstone-projects/35-gpt-model-assembly/code/main.py, phases/19-capstone-projects/35-gpt-model-assembly/docs/en.md

| Setting | Default | Note |
|---|---|---|
| `vocab_size` | 50257 | Rows of the token table |
| `context_length` | 1024 | Rows of the position table |
| `d_model` | 768 | Embedding width |
| `num_heads` | 12 | `d_model` must divide by it |
| `num_layers` | 12 | Twelve `TransformerBlock` modules |
| `mlp_expansion` | 4 | MLP hidden is 4 times `d_model` |
| `dropout` | 0.1 | Tiny demo sets 0.0 |
| `weight_tying` | True | `lm_head` shares `tok_embed` |

## GPTModel forward
type: architecture
from: phases/19-capstone-projects/35-gpt-model-assembly/code/main.py, phases/19-capstone-projects/35-gpt-model-assembly/docs/en.md

```arch
node | ids | 1,1 | Token ids | shape B, T | client
node | tok | 2,1 | tok_embed | 50257, 768 | store
node | pos | 2,2 | pos_embed | 1024, 768 learned | store
node | add | 3,1 | Add | embed_dropout | process
node | blk | 4,1 | 12 blocks | pre-LN TransformerBlock | model
node | ln | 4,2 | final_ln | LayerNorm | process
node | head | 3,2 | lm_head | logits B, T, 50257 | model
edge | ids | tok | ids
edge | ids | pos | 0 to T-1
edge | tok | add |
edge | pos | add |
edge | add | blk |
edge | blk | ln |
edge | ln | head |
edge | head | tok | tied weight
```

## Build, count and sample
type: annotated-code
lang: python
from: phases/19-capstone-projects/35-gpt-model-assembly/code/main.py

```python
ref_model = GPTModel(GPTConfig())  # 124M reference
count_parameters(ref_model)  # unique params, tied once
GPTModel(GPTConfig(weight_tying=False))  # adds vocab_size * d_model
self.lm_head.weight = self.tok_embed.weight  # share, never copy
generate(tiny_model, prompt, max_new_tokens=12,
         temperature=0.8, top_k=20, seed=42)  # temperature must be positive
top_k_filter(logits, top_k)  # others to -inf
window = tokens[:, -model.cfg.context_length:]  # sliding window
```

## One training step
type: flow
from: phases/19-capstone-projects/36-training-loop-eval/code/main.py, phases/19-capstone-projects/36-training-loop-eval/docs/en.md

1. **make_batches**: target is the input shifted by one position.
2. **Schedule**: `cosine_with_warmup` sets `group["lr"]` in each param group.
3. **calc_loss_batch**: forward, flatten to `(batch * seq, vocab)`, cross entropy.
4. **Backward**: `loss.backward()`, then `clip_grad_norm_` at `max_norm=1.0`.
5. **AdamW**: `optimizer.step()` with `betas=(0.9, 0.95)`.
6. **Log**: append `{"step", "train_loss", "lr"}` to `losses.jsonl`.
7. **Probe**: every `eval_every` steps run `evaluate_model` and `generate_and_print_sample`.

## Training loop core
type: annotated-code
lang: python
from: phases/19-capstone-projects/36-training-loop-eval/code/main.py

```python
optimizer = torch.optim.AdamW(
    build_param_groups(model, cfg.weight_decay),  # decay split
    lr=cfg.max_lr, betas=(0.9, 0.95))
for step in range(cfg.num_steps):
    lr = cosine_with_warmup(step, cfg.warmup_steps, cfg.num_steps, cfg.max_lr, cfg.min_lr)
    for group in optimizer.param_groups:
        group["lr"] = lr
    inputs, targets = next(train_loader)
    optimizer.zero_grad(set_to_none=True)
    loss = calc_loss_batch(model, inputs, targets)  # flat cross entropy
    loss.backward()
    torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=cfg.grad_clip)
    optimizer.step()
```

## Schedule and size numbers
type: concepts
from: phases/19-capstone-projects/44-cosine-lr-warmup/docs/en.md, phases/19-capstone-projects/35-gpt-model-assembly/docs/en.md, phases/10-llms-from-scratch/13-building-complete-llm-pipeline/docs/en.md

- **Warmup**: `lr_max * step / warmup_steps` up to `warmup_steps`.
- **Cosine**: `lr_min + 0.5 * (lr_max - lr_min) * (1 + cos(pi * progress))`.
- **Floor**: past `total_steps` the rate stays at `lr_min`.
- **Warmup size**: a fraction of total steps. Typical is 1 to 3 percent.
- **Floor size**: `lr_min` at 10 percent of `lr_max` keeps the optimizer learning.
- **Weight tying**: saves 50257 times 768 = 38 million parameters.
- **Residual init**: scale out projections by `1 / sqrt(2 * num_layers)`.
- **Pre-training FLOPs**: 6 x params x tokens.

## GPT-2 names to local names
type: comparison
span: 2
from: phases/19-capstone-projects/37-loading-pretrained-weights/docs/en.md, phases/19-capstone-projects/37-loading-pretrained-weights/code/main.py

| Factor | GPT-2 file | Local model |
|---|---|---|
| Token embedding | `wte.weight` | `tok_embed.weight` |
| Position embedding | `wpe.weight` | `pos_embed.weight` |
| LayerNorm 1 scale | `h.N.ln_1.weight` | `blocks.N.ln1.scale` |
| Fused QKV, transposed | `h.N.attn.c_attn.weight` | `blocks.N.attn.qkv.weight` |
| Attention out, transposed | `h.N.attn.c_proj.weight` | `blocks.N.attn.out_proj.weight` |
| MLP fc1, transposed | `h.N.mlp.c_fc.weight` | `blocks.N.mlp.fc1.weight` |
| MLP fc2, transposed | `h.N.mlp.c_proj.weight` | `blocks.N.mlp.fc2.weight` |
| Final LayerNorm | `ln_f.weight`, `ln_f.bias` | `final_ln.scale`, `final_ln.shift` |

## Load safetensors weights
type: annotated-code
lang: python
from: phases/19-capstone-projects/37-loading-pretrained-weights/code/main.py

```python
make_stub_safetensors(STUB_PATH, cfg, seed=42)  # offline fixture
model = GPTModel(cfg)
report = load_safetensors(model, STUB_PATH, verbose=False)
report.summary()  # loaded= missing= unexpected= shape_mismatch=
report.ok()  # no missing, no shape_mismatch
model.lm_head.weight.data_ptr() == model.tok_embed.weight.data_ptr()  # tied
```

## Troubleshooting
type: troubleshooting
from: phases/19-capstone-projects/36-training-loop-eval/docs/en.md, phases/19-capstone-projects/37-loading-pretrained-weights/docs/en.md, phases/19-capstone-projects/44-cosine-lr-warmup/docs/en.md

| Symptom | Cause | Fix |
|---|---|---|
| Loss near zero, model learns nothing | No shift by one | Target at positions 1..T |
| LayerNorm scale goes to zero | Weight decay on the scale | Scale and bias in the no decay group |
| One bad batch wipes hours | Huge gradient | `clip_grad_norm_` before `step` |
| Same samples before and after load | Name map missed every tensor | Read the `LoadReport` |
| `shape_mismatch` in the report | Dims differ from the config | Loader assigns nothing. Fix the config |
| Off by one LR late in the run | Glued three part schedule | One function over `step`, test the edges |

## Pipeline eval gates
type: defaults
from: phases/10-llms-from-scratch/13-building-complete-llm-pipeline/code/main.py

| Setting | Default | Note |
|---|---|---|
| `mmlu` | 65.0 | Must be at least this |
| `humaneval` | 40.0 | Must be at least this |
| `truthfulqa` | 50.0 | Must be at least this |
| `safety_refusal_rate` | 0.05 | Must be at most this |
| `kl_from_reference` | 25.0 | Must be at most this |
| `cost_total_usd` | 50000.0 | Must be at most this |
| `budget_usd` | 50000.0 | Run halts over budget |
| `seed` | 42 | Manifest field |

## Q&A
type: qa
from: phases/19-capstone-projects/37-loading-pretrained-weights/docs/en.md, phases/19-capstone-projects/36-training-loop-eval/docs/en.md, phases/19-capstone-projects/44-cosine-lr-warmup/docs/en.md

- **Why transpose on load?** `c_attn`, `c_proj` and `c_fc` use conv1d layout. `nn.Linear` wants the transpose.
- **Why print a sample during training?** Loss is a scalar. A sample shows a model stuck on one token.
- **Why slice eval batches once?** Identical batches make eval loss comparable across runs.
- **Where does the schedule read its step?** From `global_step` in the checkpoint, not a local counter.
Code and artifacts 1
What you take away

Saved in your notebook on this device.

Sources and limits

Reading checkpoints are self-reported. Test results are recorded separately and apply to the executed cases.

AI Engineering from Scratch · 3f9715c · MIT

Download this file ↓