The training loop
Read these first if they are new to you
- Minibatches: a few examples per step Intermediate
- Gradient descent Intermediate
- Data loaders: preparing examples in parallel Intermediate
- Checkpoints: saving training to resume it Intermediate
- Held-out data: the validation set Intermediate
- A moving average of the weights (EMA) Intermediate
- Mixed precision: 16-bit numbers (bf16) Intermediate
- Backpropagation: gradients by the chain rule Intermediate
- Automatic gradients in PyTorch Intermediate
- Gradient clipping Intermediate
- AdamW: steps that adapt Intermediate
- Learning-rate schedules: warm-up, decay Intermediate
The idea in one paragraph
Training a neural network is one short cycle repeated many thousands of times: get a batch of examples, compute the loss, compute the gradients, and take a step. A few more tasks ride along on a slower clock: every so often, the loop checks the model on held-out data and saves its progress. This page walks one cycle and links each part to the unit that explains it. Read it first for the map, or last to put the pieces together.
Every step runs the same five parts
- Get a batch. A data loader hands over the next minibatch, already prepared by worker processes, often with random changes such as rotations (data augmentation). The loop moves it to the GPU (tensors on the GPU).
- Forward pass and loss. The model turns the batch into predictions, and the loss scores them with one number. The model's arithmetic often runs in 16-bit numbers to save time and memory (mixed precision). The loss itself is usually kept in 32 bits.
- Backward pass. Backpropagation computes the gradient of the
loss for every weight; in PyTorch,
loss.backward()does it (automatic gradients). With several GPUs, the GPUs average their gradients here. - Take a step. Gradient clipping shrinks the gradients if they are unusually large. The optimizer, often AdamW, updates the weights, a refinement of plain gradient descent. The learning-rate schedule sets the step size for the next step.
- Update the averaged weights. A slowly moving copy of the weights takes a small step toward the new weights (a moving average of the weights).
And now and then, for example every 1,000 steps:
- Check and save. Measure the loss on the validation set, which the model never trains on, and write a checkpoint so that training can resume after a stop.
Steps, epochs and checks run on different clocks
One pass of parts 1 to 5 is a step. One pass through all the training examples is an epoch. With 1,000 examples and batches of 50, an epoch is 20 steps, and 10 epochs are 200 steps. Checks and saves are usually counted in steps: every 50 steps in the code below, every few thousand in a long run.
All six parts fit in about 30 lines
This PyTorch script trains a small network on made-up data on the CPU. The numbered comments match the parts above. Each line is explained in the unit linked from its part.
loop.py
import copy
import torch
from torch.utils.data import DataLoader, TensorDataset
torch.manual_seed(0) # same random numbers on every run
x = torch.randn(1200, 8); y = x.sum(1, keepdim=True) + 0.1 * torch.randn(1200, 1)
train_dl = DataLoader(TensorDataset(x[:1000], y[:1000]), batch_size=50, shuffle=True)
val_x, val_y = x[1000:], y[1000:] # held out: never trained on
model = torch.nn.Sequential(torch.nn.Linear(8, 32), torch.nn.ReLU(), torch.nn.Linear(32, 1))
ema = copy.deepcopy(model)
opt = torch.optim.AdamW(model.parameters(), lr=0.01)
sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=200)
step = 0
for epoch in range(10): # 20 batches per epoch
for xb, yb in train_dl: # 1. get a batch
with torch.autocast("cpu", dtype=torch.bfloat16):
loss = ((model(xb) - yb) ** 2).mean() # 2. forward pass and loss
opt.zero_grad()
loss.backward() # 3. gradients
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
opt.step(); sched.step(); step += 1 # 4. step, schedule
with torch.no_grad(): # 5. averaged copy
for pe, p in zip(ema.parameters(), model.parameters()):
pe.lerp_(p, 0.05) # beta = 0.95
if step % 50 == 0: # 6. now and then
with torch.no_grad():
val = ((ema(val_x) - val_y) ** 2).mean().item()
print(f"step {step}: train loss {loss.item():.3f}, validation loss {val:.3f}")
torch.save({"model": model.state_dict(), "ema": ema.state_dict(), "step": step,
"optimizer": opt.state_dict(), "schedule": sched.state_dict()}, "ckpt.pt")
step 50: train loss 0.067, validation loss 0.594
step 100: train loss 0.027, validation loss 0.048
step 150: train loss 0.027, validation loss 0.030
step 200: train loss 0.019, validation loss 0.026
TensorDataset pairs each row of x with the same row of y.
CosineAnnealingLR lowers the learning rate by cosine
decay, to 0 at step T_max = 200. A semicolon separates two statements on one line. The
% sign gives the remainder of a division, so step % 50 == 0 is true at every 50th
step.
The validation loss uses the averaged copy, because that is the model the run keeps. At step 50 it is
still high, because the copy averages over about the last 20 steps and lags behind the fast early
progress. (The trained weights alone score about 0.056 there.) By step 150 the copy has caught up.
pe.lerp_(p, 0.05) is the averaging rule with β = 0.95, written as "move 5% of the
way toward p".
A real loop also logs its numbers and sets random seeds. Its checkpoints also save the state of the
random-number generators (see checkpoints). It switches the model between
training and evaluation modes (model.train() and model.eval()), which matters for layers
that behave differently in each. On several GPUs it starts one process per GPU before the loop begins.
Inside the autocast block the model's output is bf16. Subtracting the float32 targets turns the result
into float32, so this loss is already computed in float32. Real code often makes that explicit by
computing the loss after the block.
Check yourself
-
A run has 60,000 training examples, a batch size of 100, and trains for 5 epochs. It validates every 500 steps. How many steps does it take, and how many times does it validate?
Answer
60,000 ÷ 100 = 600 steps per epoch, so 5 × 600 = 3,000 steps. It validates at steps 500, 1,000, …, 3,000: 6 times.
-
Why does the loop validate only every 50 steps, not at every step?
Answer
Validation runs the model on a whole held-out set, which costs much more than one step and teaches the model nothing. Checking now and then is enough to see the trend.
-
In which part of the cycle do several GPUs need to talk to each other, and what do they share?
Answer
In part 3, during and right after the backward pass. They average their gradients, so that every GPU takes the same step in part 4.
Go further
Optional background, for readers who want more depth.
-
The basic loop with a training pass and a test pass, without the extras covered here.
-
A Recipe for Training Neural Networks: set up the training and evaluation skeleton Andrej Karpathy Essay Advanced
Practical habits for building and checking a loop, such as first making it overfit a single batch.
Where this shows up
No unit or explanation builds on this one yet.