Train Your Own Small GPT

What You’ll Learn
  • Turn text into input and next-character target batches.
  • Train a small GPT from random weights using PyTorch’s automatic differentiation.
  • Use a simple baseline and held-out validation data to judge the result.
  • Diagnose a failed training run, save its state, and continue it later.
  • Produce a model, generated samples, and an experiment report that someone else can check.

The walkthrough showed how a prediction is computed. Random weights can run through every operation correctly and still produce poor text. This lab connects that forward pass to a loss, a backward pass, and repeated parameter updates.

Here, from scratch means the small GPT starts with random weights and its architecture is implemented in the lab code. PyTorch calculates derivatives for us. The bundled text is a small teaching dataset: completing this lab demonstrates a working training pipeline, not a conversational assistant.

1. Get a Training Run Working

Download the mini GPT lab, unzip it, and open a terminal in the directory containing implementation. If you already have this repository, start at the repository root. Install uv if needed; uv sync sets up the Python environment described by the project.

cd implementation
uv sync
uv run train.py --steps 300 --device cpu --out-dir runs/tiny-gpt

The command trains on the bundled text and writes a checkpoint under runs/tiny-gpt. --steps 300 asks for 300 optimizer updates. It is a starting experiment, not a promise that training will converge after that many updates.

Before editing the code, run the four phases below and match each phase to the script:

Training Step Walkthrough

The cat sat on the mat
Step 1: Forward Pass

Predicted probability distributions at all 5 positions.

Position 4 "The cat sat on the"
predict: mat
mat
13.1%
floor
17.7%
bed
16.8%
cat
13.8%
dog
12.5%
All 5 positions predicted in parallel, not one at a time. Click any position to see its full distribution.
1 / 4

What to look for: The forward pass computes probabilities, the loss scores the targets, backward computes gradients, and the optimizer changes parameters. The next forward pass uses those changed parameters. The animation illustrates the pattern; your terminal reports the measured values for your run.

FileRead it to answer
data.pyWhich characters are inputs, and which are targets?
model.pyHow do embeddings, attention, and the FFN produce vocabulary scores?
train.pyWhere are gradients calculated, parameters updated, and validation measured?
generate.pyHow does a saved model continue a prompt?

First checkpoint: Find the initial validation loss and a later validation loss in the run output. Keep both values even if the later value is worse. That is a result to investigate.

The output directory also contains metrics.csv with step, training loss, and validation loss; loss.svg plots those measurements. samples.json records text from before and after training, and config.json records the run settings. Keep these files with the checkpoint.

A reference CPU run with the bundled corpus, default settings, seed 42, Python 3.12, and PyTorch 2.14 recorded:

Optimizer stepTraining lossValidation loss
03.72813.7389
1001.98272.0850
3001.38371.7254

These measurements show learning on this small corpus. Your values can differ with library versions and devices. The generated text still contains broken words; improving next-character prediction on a few short stories does not produce a general chat model.

2. Check What the Model Is Asked to Predict

Suppose the text is The cat. A four-character training window can use:

input:   T h e [space]
target:  h e [space] c

Each position predicts the character immediately after it. The first target is h, not T. The lab uses characters so you can inspect the whole vocabulary; the same shift applies to the subword tokens from Tokenization.

Think of the input and target as two copies of a sentence, with one copy moved one place left. This describes the alignment, but the model still sees only its allowed prefix at each position.

The code splits the source text into training and validation portions before constructing overlapping windows. Otherwise, nearly identical windows could appear in both splits. The tokenizer learns its vocabulary from the training portion; an unseen validation character maps to the unknown ID.

Quick Check
Why do we split source text before constructing overlapping training windows?
Your turn
Choose an answer to check your understanding.

3. Follow One Update Through the Code

For a batch of 2 sequences, 4 positions each, and a vocabulary of 30 characters, the model receives IDs shaped (2, 4) and produces scores shaped (2, 4, 30). There are 8 predictions, each choosing among 30 characters.

These raw scores are called logits. The loss function handles the probability calculation internally. Pass logits directly to cross-entropy; do not apply softmax first.

If these roles feel unfamiliar, revisit Neural Network Basics and the Training Loop. You do not need to derive every attention gradient before running this experiment.

4. Make a Small Test Pass Before a Larger Run

A model that cannot learn one fixed batch is unlikely to learn an entire corpus. Use the diagnostic mode to repeatedly update on the same batch:

uv run train.py --overfit-one-batch --steps 200 --device cpu --out-dir runs/one-batch

The fixed batch loss should fall substantially with enough updates. This deliberately tests memorization; its validation result is not evidence of generalization. If it fails, inspect the target shift, gradients, and learning rate before adding layers or more data.

Next, train the bigram baseline, which predicts the next character using only the current character:

uv run train.py --model bigram --lr 0.03 --steps 300 --device cpu --out-dir runs/bigram

Compare it with the small GPT using the same source text and split. The baseline command uses a larger learning rate because its small lookup table trains differently from the transformer; record both settings in your report. A longer-context model has more information available, but it still needs enough useful data and successful optimization to beat the baseline.

ObservationCheck next
Loss is NaN or infiniteFind the first non-finite value; try a lower learning rate with --lr
Fixed-batch loss barely movesConfirm parameters have gradients and the optimizer receives them
Training loss falls, validation loss risesThe model may be memorizing; compare an earlier checkpoint and inspect the data
Validation loss is suspiciously good immediatelyCheck for duplicate text, incorrect labels, or future-token access
Generated text repeatsInspect loss and training data before changing sampling temperature
Fill in the Blanks
The set updates the model's weights. The set measures prediction error without updating weights. A one-batch overfit test checks whether the training code can .
Fill in every blank, then check your answer.

5. Generate, Stop, and Resume

Load the trained weights in a separate process:

uv run generate.py --checkpoint runs/tiny-gpt/checkpoint.pt --prompt 'The ' --max-new-tokens 100

Read the output alongside the validation loss. Recognizable words can come from memorized training passages; a low loss on a tiny held-out split does not establish broad language ability.

checkpoint.pt contains the latest state. best.pt contains the state with the lowest measured validation loss; pass that path to generate.py to compare an earlier, better validation result with the final one.

Continue the same training run for 100 additional updates:

uv run train.py --resume runs/tiny-gpt/checkpoint.pt --steps 100

A checkpoint stores the model weights, optimizer state, step count, tokenizer, and configuration needed to continue the experiment. The optimizer has its own memory of earlier gradients. Loading weights alone is sufficient for generation, but does not fully resume a training run.

Second checkpoint: Confirm the resumed step count continues from the saved count. Then load the new checkpoint with generate.py again. Keep the data and configuration unchanged when testing continuation.

6. Submit an Experiment Someone Else Can Check

Save these items together:

  1. The training command, seed, data source, and split method.
  2. Initial and final training/validation measurements for the baseline and GPT.
  3. The one-batch test result and one problem you checked while debugging.
  4. Generated text from fixed prompts, with the checkpoint and sampling settings recorded.
  5. The checkpoint and commands for loading it and resuming training.

Write a short explanation of whether the GPT improved on the baseline, and what the validation evidence supports. If it did not improve, describe one change to test next while holding the other settings fixed. This report is the completion task for the core course.

Quick Summary

StepWhat happensIntuition
Prepare dataSplit text, encode it, shift targetsAsk the model the right question
Check one updateForward, loss, backward, optimizerVerify that weights can learn
Test one batchReuse a tiny set of examplesIsolate bugs before scaling the experiment
Compare runsBaseline and GPT use the same held-out dataMeasure what the extra context buys
Save and reloadPreserve model and training stateMake the result usable after the process ends
Learning Checklist
  • I can decode an input/target batch and explain the one-character shift.
  • I can follow tensor shapes from token IDs to logits and loss.
  • I have made one fixed batch learn and checked that parameters change.
  • I compared a baseline and GPT using held-out validation data.
  • I loaded a checkpoint to generate text and resumed its training.
  • My experiment report states what the results show and what they do not establish.

You now have a training experiment to inspect. Next, compare how the same transformer components support different objectives in AI Models →. After GPT, you can reuse this training workflow in the optional open-model fine-tuning lab.