You now have every piece of a language model and every piece of an optimizer. Training is the loop that joins them, repeated hundreds of thousands of times. This page is about that loop:
- how batches are cut from a stream of tokens;
- how a large batch is built from small ones;
- how training fits in 16-bit numbers;
- how much compute a model of a given size needs.
A note on scale: the exercises run in your browser, where NumPy has no optimized matrix library and a matrix product takes most of a second. So they train bigram-sized models in a few hundred steps. The loop is exactly the one used for a 7-billion-parameter model; only the model inside it is smaller.
The training loop
Every step does the same six things:
- Sample a batch of token sequences.
- Forward: compute the logits and the loss.
- Backward: compute the gradient of the loss with respect to every parameter.
- Clip the gradient's global norm (section 3).
- Step: update the parameters with AdamW (Adam with decoupled weight decay), at the learning rate the schedule gives for this step.
- Every few hundred steps, evaluate the loss on held-out data, never used for training.
Example. A bigram model (a table of logits, all zeros at the start) trained on the repeating text
0 1 2 0 1 2 ..., with plain gradient descent. The training loss at a few steps:
step loss 0 1.099 10 0.235 20 0.115 40 0.055 59 0.037 At step 0 every token has probability , so the loss is : the uniform baseline of section 2. Then it falls towards 0, because in this text the next token is always certain.
The training loss going down only shows that the model fits what it has seen. The validation loss, measured on the held-out data, shows whether it generalizes to text it has never seen. When the two separate (training loss still falling, validation loss flat or rising), the model is memorizing its training data.
Batches from a token stream
The training corpus is tokenized once, into one long array of ids. A batch is random windows of consecutive tokens. The first tokens of a window are the input ; the same window shifted by one is the target , so that : at every position, the target is the next token.
Example. With the data
[10, 11, 12, 13, 14], and a window starting at position 1: the input is[11, 12, 13]and the target is[12, 13, 14]. The model learns to predict 12 after 11, 13 after 11 12, and 14 after 11 12 13: three training examples from one window.
One window gives training examples at once, one per position, which is why causal masking matters (section 7). This is how the get_batch function of nanoGPT works.
Gradient accumulation
LLMs (large language models) are trained with batches of millions of tokens, far more than fit in the memory of a GPU (graphics processing unit) at once. Gradient accumulation splits a batch into micro-batches, runs forward and backward on each, adds up the gradients, and takes one optimizer step at the end.
Because the loss is a mean, the gradient of the full batch is the mean of the micro-batch gradients when the micro-batches have the same number of tokens. Otherwise each one must be weighted by its share of the tokens.
Example. Two micro-batches with 3 and 1 targets have mean losses and . The mean over all 4 targets is . A plain average of the two means would give , as if the small micro-batch counted as much as the big one.
Done correctly, accumulation gives the same update as one big batch, only slower. The same idea, spread over many GPUs instead of over time, is data parallelism (section 9).
Mixed precision
How a computer stores a real number
A floating-point number is stored like scientific notation, , but in base 2. It has three parts:
- a sign: plus or minus;
- an exponent (the in the example), which sets the range: how large or how small the number can be;
- a mantissa (the ), which sets the precision: how many significant digits are kept.
With more exponent bits a format can store huger and tinier numbers; with more mantissa bits it can tell closer numbers apart.
Three formats
Every number in the loop can be stored in 32 bits, the FP32 (32-bit floating point) format. But 16-bit formats halve the memory and run several times faster on modern accelerators. The catch is range and precision. The three formats in use are FP32, FP16 (16-bit floating point) and BF16 (brain floating point, 16 bits; also written bfloat16):
| Format | Exp. | Mant. | Max | Min normal |
|---|---|---|---|---|
| FP32 | 8 | 23 | ||
| FP16 | 5 | 10 | 65504 | |
| BF16 | 8 | 7 |
(Exp. and Mant. are the numbers of exponent and mantissa bits; one more bit is the sign. "Min normal" is the smallest number stored at full precision; smaller ones lose digits.)
FP16: precise, but a small range
FP16 has precision but little range: its largest number is 65504, and gradients are often smaller than ; below about they round to exactly zero. Micikevicius et al. (2018) made FP16 training work with two techniques.
An FP32 master copy of the weights, to which the updates are applied. A small update added to a large weight in FP16 is lost to rounding.
Example. In FP16, the number right after is : nothing in between can be stored. So rounds back to exactly , and a weight of 1 receiving updates of would never change. In FP32 the update is kept.
Loss scaling: multiply the loss by a large factor before the backward pass, so all gradients are times larger and stay representable, then divide them by (in FP32) before the update.
Example. A gradient of becomes exactly 0 in FP16. Multiplied first by , it is about , which FP16 stores without trouble; dividing by in FP32 gives back about .
But a gradient of multiplied by the same is , above FP16's maximum of 65504: it becomes
inf. The scale must be large enough for the small gradients and small enough for the large ones.
With dynamic loss scaling, starts high. It is halved whenever a gradient overflows to inf (and that step is skipped), and doubled again after a long run of good steps. PyTorch's GradScaler uses exactly this, with defaults , factors 2 and 0.5, and 2000 good steps before growing.
BF16: the range of FP32, less precision
BF16 keeps FP32's 8 exponent bits and drops mantissa bits. It has FP32's range, so gradients do not underflow and loss scaling is unnecessary (Kalamkar et al., 2019), at the cost of only 2 to 3 significant decimal digits: the number right after is . With BF16 one can compute in 16 bits and keep the master weights and optimizer states in FP32, with no loss scaling at all.
How much compute:
The forward pass costs about 2 FLOPs (floating-point operations) per parameter per token, one multiply and one add, and the backward pass about twice that. So training a model with parameters on tokens costs
where is the total compute, the number of parameters and the number of training tokens (Kaplan et al., 2020, section 2.1). The 6 is 2 for the forward pass plus 4 for the backward pass.
Example. A 1-billion-parameter model trained on 20 billion tokens needs about FLOPs.
MFU (model FLOPs utilization; Chowdhery et al., 2022) measures how well the hardware is used: the FLOPs the model needs per second, that is times the tokens processed per second, divided by the peak FLOPs per second of the hardware. PaLM (Pathways Language Model) reports an MFU of 46.2%. The rest is lost to memory transfers, communication between chips and operations that do not use the hardware fully.
Example. Imagine 8 accelerators, each with a peak of FLOPs per second, running at an MFU of 40%: together they do useful FLOPs per second. The FLOPs of the run above then take seconds, about 10.4 hours.
Scaling laws: how big, for how long
For a fixed compute budget , should you train a larger model on fewer tokens, or a smaller one on more? Kaplan et al. (2020) found that the loss falls as a smooth power law in , and : double the compute, and the loss drops by a predictable amount.
Hoffmann et al. (2022) redid the analysis with learning-rate schedules matched to the length of each run, and found that and should grow in equal proportion: about 20 training tokens per parameter (their Table 3 gives 20.2 billion tokens for a 1-billion-parameter model). Their 70-billion-parameter Chinchilla, trained on 1.4 trillion tokens, outperformed the much larger Gopher (280 billion parameters) at the same compute.
Combining the two rules, and , gives , so the compute-optimal size for a budget is .
Example. A budget of FLOPs gives billion parameters, trained on billion tokens.
Training far beyond 20 tokens per parameter can still pay off: a smaller model costs more to train than the optimum, but less every time it is used. LLaMA (Large Language Model Meta AI), for example, notes that its 7B model keeps improving even after 1 trillion tokens, about 140 per parameter.
References
- J. Kaplan et al., Scaling Laws for Neural Language Models, arXiv, 2020. Section 2.1: FLOPs per training token.
- J. Hoffmann et al., Training Compute-Optimal Large Language Models, arXiv, 2022 (NeurIPS 2022). Chinchilla; Table 3: compute-optimal tokens per model size.
- P. Micikevicius et al., Mixed Precision Training, ICLR, 2018. FP32 master weights and loss scaling.
- D. Kalamkar et al., A Study of BFLOAT16 for Deep Learning Training, arXiv, 2019. Table 1: FP32, FP16 and BF16 formats.
- A. Chowdhery et al., PaLM: Scaling Language Modeling with Pathways, arXiv, 2022. Section 4.1 and appendix B: model FLOPs utilization.
- PyTorch source,
torch.amp.GradScaler: the dynamic loss-scaling rule and its defaults. - A. Karpathy, nanoGPT:
get_batchand the training loop intrain.py.