Training at scale: memory, parallelism, rooflines and infrastructure design
Before this lesson: lesson 07 (the one-GPU training step, 16 bytes per parameter, gradient accumulation), lesson 08 (the transformer layer that tensor parallelism splits) and lesson 11 (). The full theory is in the ML Systems track: training memory, collectives, data parallelism and ZeRO, tensor and pipeline parallelism and the roofline model. Time: about 105 minutes, plus the notebook.
Why this matters #
"Will a 13B model train on 8 GPUs with 80 GB?" and "How would you train this model on 1,000 GPUs?"
are standard questions in research engineer and ML engineer interviews at LLM labs, and in
ML-for-science groups that train foundation models. Some rounds ask for code: a data-parallel step
with all_reduce, a simulated ring all-reduce, a column-parallel linear layer, or a pipeline
schedule. Design rounds ask for a full training plan: GPU-hours, a parallel layout, a check that the
network and the data loader keep up, what happens when a node dies, and how you know the model is
getting better. The answers are a few formulas and a few hardware numbers. This lesson gives you
both, with proofs, so that you can derive them on a whiteboard in minutes.
1. Predict first #
2. The picture #
One GPU first. A training step keeps four kinds of tensors in GPU memory:
- parameters , used by the forward and the backward pass;
- gradients , made by the backward pass and used by the optimizer;
- optimizer states: for AdamW the first and second moments and , and in mixed precision also an fp32 master copy of the weights;
- activations: the values made by the forward pass and kept until the backward pass has used them.
The first three are the model states. Their size depends only on the number of parameters . The activations depend on the number of tokens processed at once, on the width and on the depth.
Make a 7B model fit on 80 GB GPUs. With DDP (stage 0) the model states alone take 112 GB: the bar crosses the 80 GB line. Switch to ZeRO stages 1, 2 and 3 and watch the optimizer states, then the gradients, then the parameters shrink by the number of GPUs. Then try "standard attention" at : the activations explode, and ZeRO does not help them. Tensor parallelism () and full checkpointing do.
Parallelism is a choice of what to split.
| Method | What is split | What is sent | How often |
|---|---|---|---|
| data parallelism (DDP) | the batch | all-reduce of the gradients | once per step, during the backward pass |
| ZeRO-1, ZeRO-2 | the batch + optimizer states (+ gradients) | reduce-scatter of gradients, all-gather of parameters | once per step |
| ZeRO-3 / FSDP | the batch + all model states | all-gather of parameters (forward and backward), reduce-scatter of gradients | layer by layer |
| tensor parallelism (TP) | every weight matrix | all-reduce of activations | 2 per layer forward, 2 backward |
| pipeline parallelism (PP) | the layers | activations and their gradients, to the next stage | per micro-batch, at each stage boundary |
Three collectives do almost all the communication. A collective is an operation in which all GPUs of a group take part. Take GPUs, each with a vector of bytes:
- all-reduce: every GPU ends with the sum of all vectors;
- reduce-scatter: GPU ends with chunk (of size ) of the sum;
- all-gather: every GPU starts with one chunk and ends with all chunks.
An all-reduce is a reduce-scatter followed by an all-gather. The ring algorithm arranges the GPUs in a ring. In each step every GPU sends one chunk of bytes to its neighbour. Each phase takes steps, so an all-reduce sends
almost independent of the number of GPUs. This is why data parallelism scales: more GPUs do not mean more bytes per GPU.
Step through a ring all-reduce on 4 GPUs. Press Next step. In the first 3 steps (reduce-scatter) each GPU sends one chunk (orange outline) to the next GPU, which adds it: the small counts the GPUs already in the partial sum. After step 3 each GPU holds one complete chunk. The next 3 steps (all-gather) copy the complete chunks around the ring. Watch the counter reach times the vector size.
8 GPUs run a ring all-reduce on 2 GB of bf16 gradients each. About how many bytes does each GPU send?
Each GPU sends GB: chunks of in the reduce-scatter and in the all-gather. It does not grow with . 14 GB would be "send the whole vector to each of the 7 other GPUs", the naive algorithm that the ring avoids.
3. Definitions and main results #
Notation (as in the Megatron and ZeRO papers). number of parameters; layers; hidden size; attention heads; sequence length; micro-batch size (sequences per GPU per forward pass); data-parallel GPUs; tensor-parallel degree; pipeline stages; micro-batches per step; GPUs in total; training tokens; FLOP/s per GPU; bandwidth in bytes/s. Hardware (dense, bf16): A100-80GB SXM 312 TFLOP/s and 2.04 TB/s HBM; H100 SXM 989 TFLOP/s and 3.35 TB/s HBM, NVLink 450 GB/s per direction inside a node, about 50 GB/s per GPU between nodes (one 400 Gb/s network card per GPU). 1 GB bytes.
3.1 The memory of one training step #
Model states. Mixed-precision AdamW keeps, per parameter (ZeRO paper, §3.1):
| bf16 weight | bf16 gradient | fp32 master weight | fp32 Adam | fp32 Adam | total |
|---|---|---|---|---|---|
| 2 | 2 | 4 | 4 | 4 | 16 bytes |
The last three are the optimizer states: bytes per parameter. PyTorch autocast keeps fp32 weights instead (4 for the weight, 4 for the gradient, 8 for Adam): also 16 bytes, split differently. A 7B model needs GB before any activation.
Parameter count. A GPT-style layer (MLP width ) has parameters: attention , MLP , two LayerNorms . So , plus the embedding.
Activations. For the weight gradient is , so the backward pass needs the input . Every matmul input, every nonlinearity input and every dropout mask is stored. Korthikanti et al. (2022, §4.1) count, per layer, with 16-bit activations and 1-byte dropout masks:
- covers the matmul inputs, the GeLU input, the LayerNorm inputs and the dropout masks.
- is the attention core: the softmax output, its dropout mask and the dropout output, for each of the heads. It grows with .
- FlashAttention never stores the matrices: about per layer.
- Full activation checkpointing stores only the input of each layer ( bytes) and recomputes the rest in the backward pass. This costs one extra forward pass: about instead of FLOPs per token, 33% more compute.
- Tensor parallelism with sequence parallelism of degree divides all of it by .
Example: , , , (a 7B model). and . Without FlashAttention: GB per layer, 104 GB for 32 layers. With FlashAttention: GB per layer, 18.3 GB in total. With full checkpointing: GB, plus one layer during recomputation.
3.2 Data parallelism #
Theorem (data parallelism = one large batch). Let the loss be the mean over a batch of samples, where each sample's loss depends only on that sample and (no BatchNorm statistics across the batch). Split the batch into shards of equal size. Then the mean of the shard gradients equals the full-batch gradient.
Proof.
- , where is the loss of sample .
- Let be shard and its mean loss. Each sample is in exactly one shard, so .
- The gradient is linear: .
- GPU computes . An all-reduce (sum), then a division by , gives on every GPU.
- All GPUs start from the same and apply the same update to the same gradient, so the copies stay identical.
With unequal shards (a ragged last batch, or a different number of tokens per GPU), weight each by its count; section 4(a) shows the error of the plain mean. Gradient accumulation is the same proof in time instead of space: run micro-batches one after another, add up their gradients, then take one optimizer step. The global batch is sequences.
How DDP hides the all-reduce. PyTorch DDP groups the gradients into buckets (25 MiB by
default). When a bucket is complete, its all-reduce starts while the backward pass continues with
earlier layers. Inside no_sync() (the accumulation steps) DDP skips the all-reduce.
When is it hidden? With tokens per GPU per step, the backward pass costs about FLOPs (4 of the FLOPs per token), and the bf16 ring all-reduce sends about bytes per GPU. Communication hides behind the backward pass when
The model size cancels. With TFLOP/s achieved and GB/s between nodes, each GPU needs at least 8,000 tokens per step. With gradient accumulation, only the last micro-batch's backward pass overlaps with the all-reduce, so count the tokens of that micro-batch.
3.3 ZeRO and FSDP #
ZeRO (Zero Redundancy Optimizer) is data parallelism without copies of the model states: each GPU keeps a slice and collects the rest only when it needs it.
Theorem (ZeRO memory and communication). With parameters, data-parallel GPUs and , the model states per GPU and the values each GPU sends per step are (times for ring collectives; 2 bytes per value in bf16):
| Stage | What is sharded | Model states per GPU (bytes) | Sent per GPU per step |
|---|---|---|---|
| DDP (stage 0) | nothing | ||
| ZeRO-1 | optimizer states | ||
| ZeRO-2 | + gradients | ||
| ZeRO-3 / FSDP | + parameters |
Proof.
- Memory. Each stage keeps the unsharded parts in full and the sharded parts as a slice. Add the bytes per parameter: 2 (weights), 2 (gradients), (optimizer).
- DDP. A ring all-reduce of gradient values is a reduce-scatter () plus an all-gather (): .
- ZeRO-1 and ZeRO-2. GPU updates only its of the parameters, so it needs only its of the summed gradient: a reduce-scatter (). After the update, every GPU needs all the new parameters: an all-gather (). This is the DDP all-reduce cut in two, with the optimizer step in the middle: , no extra traffic.
- ZeRO-3. Parameters are not kept between uses. Before each layer's forward pass, all-gather its weights, then free them: over the whole model. The backward pass needs them again: a second all-gather (). Then reduce-scatter the gradients (). No all-gather after the update, because the next forward pass does it. Total , which is DDP.
FSDP is PyTorch's ZeRO-3. It wraps the model in units (usually one per transformer block), prefetches the all-gather of the next unit while the current unit computes, and frees the gathered weights after use. HSDP (hybrid sharding) shards inside a node and replicates across nodes, so that the all-gathers stay on NVLink.
What ZeRO does not shard: activations. Each GPU still holds the activations of its own micro-batch. For those you need checkpointing, a smaller micro-batch, or TP with sequence parallelism.
Which is the first ZeRO stage at which no GPU holds a full copy of the bf16 weights?
ZeRO-1 shards the optimizer states, ZeRO-2 adds the gradients, and only ZeRO-3 shards the parameters themselves. That is why only ZeRO-3 must all-gather the weights before each layer, in the forward and again in the backward pass, which costs the extra of communication ( instead of ).
3.4 Tensor parallelism (Megatron) #
Simplest case: one matmul on two GPUs.
- Split by columns, . Then : each GPU computes its own columns. No communication, if is on both GPUs.
- Split by rows, and split to match. Then : each GPU has a partial sum, and an all-reduce adds them.
The MLP block , with of size and of size (Shoeybi et al. 2019, §3):
- Split by columns. GPU computes . GeLU acts on each element, so no communication is needed.
- Split by rows. GPU computes , a partial sum. One all-reduce gives .
- Why not split by rows? Then GPU would hold , and : a second all-reduce before the GeLU.
- In the backward pass, every GPU used the same , so is a sum over GPUs: one more all-reduce.
Attention is split by heads: the Q, K, V projections are column-parallel (each GPU gets heads), attention runs locally, and the output projection is row-parallel. So each layer needs 2 all-reduces in the forward pass and 2 in the backward pass, each on values.
Cost. TP divides weights, gradients, optimizer states and (with sequence parallelism) the activations by . But its communication sits on the critical path of every layer. In the forward pass a GPU computes FLOPs per layer and sends bytes, so
For , and TFLOP/s this is about 0.63 on NVLink (450 GB/s), but about 5.6 between nodes (50 GB/s). So TP stays inside a node.
3.5 Pipeline parallelism #
Split the layers into stages, one per GPU. Between stages only point-to-point messages travel: the activations of a micro-batch forward, their gradients backward ( values each). This is little data, so PP works well across nodes. The naive pipeline sends the whole batch from stage to stage, so only one GPU works at a time. GPipe cuts the batch into micro-batches, so stage 2 works on micro-batch 1 while stage 1 works on micro-batch 2.
Theorem (pipeline bubble). With stages, micro-batches, and times , for the forward and backward pass of one micro-batch on one stage (equal on all stages, communication ignored), a GPipe step takes . The idle ("bubble") fraction of the step is
Proof.
- Micro-batch () can run on stage () only after it left stage , and after micro-batch left stage . By induction on , its forward pass on stage ends at time .
- So the last forward pass (, ) ends at .
- The backward phase is the mirror image and takes .
- Each stage does useful work for , so it is idle for : a fraction of the step.
Relative to the ideal time , the bubble is ; the Ultra-Scale Playbook and Narayanan et al. use this form. Say which one you use.
Shrink the bubble. Each row is a stage; blue boxes are forward passes, orange boxes backward passes (twice as long), numbered by micro-batch; the grey gaps are the bubble. With , the bubble is . Raise to 16: it falls to . Then switch to 1F1B: the step time stays the same, but stage 0 now holds the activations of at most micro-batches instead of .
- More micro-batches shrink the bubble. With the bubble is below 20% of the step, because .
- But GPipe stores the activations of all micro-batches, because every forward pass runs before the first backward pass.
- 1F1B (one forward, one backward) starts each backward pass as early as possible. Stage runs warm-up forward passes, then alternates. The bubble is the same, but stage holds at most micro-batches of activations (Narayanan et al. 2021, §2.2).
- Interleaved 1F1B gives each GPU non-adjacent chunks of layers. The bubble drops to , at the cost of times more point-to-point messages.
A pipeline has stages and micro-batches (GPipe, ). What fraction of the step is the bubble?
The bubble is . The step takes time units, and each stage works for . is with the wrong denominator meaning: that form compares the idle time with the ideal time, not with the step.
3.6 Putting them together #
Large runs combine the methods: total GPUs DP TP PP (and more, see below). The order of decisions (Ultra-Scale Playbook, "Finding the Best Training Configuration"):
- Fit one step in memory. Below about 10B parameters, ZeRO with FlashAttention (and, if needed, activation checkpointing) is usually enough. Larger models add TP inside a node (at most its GPU count, 8 on an H100 node) and PP or ZeRO-3 across nodes.
- Reach the target global batch with DP and gradient accumulation.
- Map to the hardware. Put the method with the most traffic (TP) on the fastest links (NVLink), and PP and DP across nodes.
Context parallelism (CP) splits the tokens of one long sequence over GPUs. Ring attention passes key and value blocks around a ring of GPUs; DeepSpeed-Ulysses uses an all-to-all to switch from a split over tokens to a split over heads. CP divides the activation memory by its degree; use it when one sample's activations do not fit (ML Systems 10). Expert parallelism puts the experts of a mixture-of-experts layer on different GPUs, and tokens travel with an all-to-all (Deep Learning 15).
Example: Llama 3 405B trained on 16,384 H100s with TP 8, PP 16 and FSDP data parallelism 128 (DP is outermost, because it tolerates the longest network latency); the long-context stage added CP 16 (Llama 3 paper, §3.3.2 and Table 4).
3.7 Rooflines for compute, network and storage #
Arithmetic intensity = FLOPs / bytes moved. For any resource with rate (bytes/s), the roofline bound is
The ridge point is : an operation with is compute-bound, one with is memory-bound. On an H100, FLOP/byte; on an A100, .
Result 1 (when a matmul is compute-bound). A bf16 matmul that reads its inputs and writes its output once has , which tends to when .
- FLOPs: outputs, each a length- dot product: .
- Bytes: read and values, write values, 2 bytes each: .
- Divide. For the weight term dominates the denominator, so .
Move a matmul along the roofline. The blue roof is the best possible FLOP/s at each intensity. The orange point is a matmul. With it sits at , far left: memory-bound, less than 1% of peak. Slide up: the point climbs the slanted part and reaches the flat roof between and on an H100 (the dashed ridge, ). Compare A100 and H100: the H100 needs a larger .
What this means for training. In training, is the number of tokens in a micro-batch,
, usually thousands. So every weight matmul is compute-bound. The memory-bound parts are
elementwise operations (LayerNorm, GeLU, residual adds) and an unfused attention that writes the
scores to HBM. The fixes are kernel fusion (torch.compile) and FlashAttention. Small
matmuls are a serving problem (lesson 13).
Network and storage rooflines use the same max rule. Network: compare the bytes each GPU sends per step with its link bandwidth, and the result with the compute time (section 3.2 did this for DP). Storage: the data loader must deliver (bytes per sample) (samples per step) / (step time). Compare it with what the shared file system sustains for your job, not with its peak. If the work on different resources overlaps, the step takes about the maximum of the times; if nothing overlaps, the sum.
3.8 Compute budget and MFU #
Result 2 (training FLOPs per token). With weight parameters, training costs per token
- A weight matrix of shape applied to one token costs FLOPs. Over all weight matrices: in the forward pass.
- The backward pass has two matmuls per forward matmul (gradient with respect to the input and to the weight), each of the same size: . Total .
- The attention scores have no weights. For one query token in one layer, costs FLOPs (a dot product with keys over the total head width ), and another . Forward , with backward per layer: .
- With a causal mask a token sees on average keys: .
The ratio attention / weights is for full attention. It is small for an LLM (, , causal: about 8%) but large for long sequences.
MFU (model FLOPs utilization) (model FLOPs per token tokens per second) / (peak FLOP/s of all GPUs). It counts only the FLOPs the model needs; recomputation does not count. Llama 3 405B reached 38–43% BF16 MFU on 8k–16k H100s (Table 4). Plan with 35–45% for a dense model, and say so. Then
Goodput is the fraction of the elapsed time spent on useful training. Llama 3 reports more than 90% on 16k GPUs. MFU measures how well one step uses the GPUs; goodput measures how much of the calendar is made of useful steps.
3.9 Checkpointing and failures #
Job failure rate. A synchronous job stops when any of its GPUs (or their hosts, links or software) fails. If each GPU fails independently at rate (exponential lifetimes), the time to the first failure satisfies . So the job's mean time between failures (MTBF) is
Llama 3 405B saw 419 unexpected interruptions in 54 days on 16,384 GPUs, about 78% from hardware (§3.3.4, Table 5). That is hours for the job, and days years per GPU.
Result 3 (Young's checkpoint interval). Save a checkpoint after every of useful work. Each checkpoint blocks training for ; failures come at random with mean time ; a restart costs . If , the extra time per unit of useful time is
Why this is true, in one sentence: frequent checkpoints waste time writing, rare checkpoints lose more work at each failure, and the best interval balances the two.
- Writing: each of work pays , so per unit of work.
- A failure hits a random point of a segment, so on average half a segment, , of work is lost, plus the restart .
- Failures come at rate , so this costs per unit of time.
- Add: overhead (first order in , , ).
- Set the derivative to zero: , so .
- At the first two terms are equal, each; their sum is .
Find the best checkpoint interval. The writing cost falls with , the lost work grows with ; the total has its minimum where they cross, at the violet dot . Press Simulate failures: the magenta dots come from a Monte Carlo run with random failures and follow the formula. Now move from 1,024 to 16,384 GPUs (): the part of the overhead grows by , the square root of 16 (the restart part grows ).
The square root is the key point: 100 times more GPUs make 100 times shorter and the overhead 10 times larger. This is why large runs cut with asynchronous checkpoints: copy the state to CPU memory in seconds, then write it to storage in the background. Young (1974) gave this first-order formula; Daly (2006) gave corrections for close to .
What a checkpoint must contain, so that a resumed run continues the same run:
- the model weights (the fp32 master copy) and the optimizer states (AdamW , and its step counter): 12–16 bytes per parameter;
- the learning-rate scheduler state, the global step and the tokens seen;
- the random number generator (RNG) states of every rank (dropout and data sampling use them);
- the data position. Simplest: make the data order a pure function of (seed, epoch, step), so the step counter is enough;
- the gradient-scaler state (fp16 only), the config and the code version (git commit).
How to write it. Sharded: every rank writes its own shard in parallel (PyTorch Distributed Checkpoint, DCP), which can also reload into a different number of GPUs. Atomic: write to a new directory and mark it complete only at the end, so that a crash during the write leaves the previous checkpoint valid. Retention: keep the last 2–3, plus a permanent one every few thousand steps for analysis and for rollback after a loss spike (PaLM restarted about 100 steps before each spike and skipped 200–500 batches).
The restart path. Detect failures (collective timeouts, heartbeats, a jump in step time: one
slow GPU slows every rank); restart without a human (for example Slurm --requeue); exclude bad
nodes and keep spare ones; load the latest complete checkpoint. And test it: kill the job on
purpose and check that the resumed loss curve matches the uninterrupted one.
With years per GPU, about how often is a 16,384-GPU synchronous job interrupted?
hours, as in the Llama 3 run (419 interruptions in 54 days). The job stops when any one of its GPUs fails, so the failure rates add up. At 64 GPUs the same formula gives about 33 days: failures are rare at small scale and shape the whole design at large scale.
3.10 The eval pipeline #
Two loops.
- Inside the training loop (cheap, every few hundred steps): training loss, loss on a small fixed validation set, gradient norm, learning rate, tokens/s and MFU, memory, and the time the loader makes the GPUs wait.
- Outside the loop (asynchronous): an eval runner watches the checkpoint directory and starts eval jobs on other GPUs. Training never waits for them. Each result is stored with its key: run, step, eval-set version, eval-code version and settings (for example decoding parameters).
Eval data. Freeze the eval sets and store their hashes. For time-dependent data (weather, finance, logs), split by time, never at random; choose checkpoints on the validation period and open the test period once, for the final report. For LLMs, check contamination (n-gram overlap between the training data and the eval sets).
Eval cost. Count it like training: about forward FLOPs per token (plus attention), times the tokens of the eval suite. Keep it to a few percent of the training compute, and check that one eval finishes before the next checkpoint arrives.
Answers to section 1. (1) DDP: GB per GPU, more than one 80 GB GPU. ZeRO-3: GB (ZeRO paper, Figure 1; ZeRO-1 gives 31.4 GB and ZeRO-2 16.6 GB). (2) Only . Each all-gather moves every parameter once, like each half of the DDP all-reduce. Two all-gathers and one reduce-scatter make , against for DDP.
A 30B model trains on 64 GPUs with 8 per node (NVLink inside, 50 GB/s per GPU between nodes). You add tensor parallelism . Where should each TP group live?
TP sends 4 all-reduces of values per layer, on the critical path of every layer. The ratio of communication to compute is : below 1 on NVLink but about 9 times larger between nodes. Data parallelism and pipeline parallelism send less often (once per step, or per micro-batch at stage boundaries), so they go across nodes.
4. Worked example #
(a) Data parallelism by hand #
A model with one weight , squared loss, mean over 4 samples: , . The gradient of is .
- Full batch. Residuals ; times : . So .
- Two GPUs. GPU 0 gets samples 1–2: . GPU 1 gets samples 3–4: .
- All-reduce. The sum is ; divided by it is ✓. Without the division the gradient is twice too large (for SGD, a doubled learning rate).
- Uneven shards. GPU 0 gets sample 1, GPU 1 gets samples 2–4: gradients and . Their plain mean is : wrong. Weighted by the counts: ✓.
(b) The design round: pretrain a 7B model on 1T tokens #
Prompt. "Pretrain a 7B decoder (, , , , causal) on 1 trillion tokens within two weeks. You have H100 nodes with 8 GPUs (80 GB, NVLink), 50 GB/s per GPU between nodes, and a shared file system. Global batch about 4M tokens."
Step 1 — compute budget.
- FLOPs per token: (attention about 7%).
- Total: FLOPs.
- At 40% MFU, TFLOP/s per GPU: GPU-seconds 31,750 GPU-hours.
- Two weeks with 90% goodput: . Take 128 GPUs (16 nodes): 10.3 days of compute, about 11.5 days of wall-clock time.
Step 2 — memory per GPU.
- Global batch: tokens sequences, so each of 128 GPUs processes 8 sequences per step: micro-batch , accumulation steps.
- Model states with DDP: GB. Does not fit.
- ZeRO-1 over : GB.
- Activations (, FlashAttention): 18.3 GB. Without FlashAttention: 104 GB, impossible.
- Total about 47 GB of 80 GB, with room for the logits and the communication buffers.
- Layout: ZeRO-1 data parallelism over 128 GPUs. No TP, no PP, no activation checkpointing. Why ZeRO-1 and not ZeRO-3? ZeRO-1 communicates once per optimizer step, even with accumulation. ZeRO-3 would all-gather the weights for each of the 8 micro-batches: about 8 times the traffic.
Step 3 — roofline checks.
- HBM: the MLP matmul has
FLOP/byte, far above 295.
Compute-bound. Fuse the elementwise operations with
torch.compile. - Network: one optimizer step is 8 micro-batches of s, so 3.75 s. The gradient reduce-scatter + all-gather (bf16, GB) sends GB per GPU. Worst case, every byte crosses the 50 GB/s link and nothing overlaps: 0.56 s, 15% of the step. A hierarchical all-reduce (reduce inside the node over NVLink, all-reduce of each GPU's across nodes, gather inside the node) sends only GB across nodes: 66 ms, under 2%. Note the overlap rule: the last micro-batch has 4,096 tokens, less than , so a flat ring would not hide fully.
- Storage: 4.2M tokens per step at 4 bytes per token ID is 17 MB per 3.75 s: about 5 MB/s. Not a risk.
Step 4 — checkpointing and failures.
- Content: fp32 weights + + GB, plus RNG states, step, scheduler, data position and config. Sharded: 0.66 GB per GPU. Assume a blocking save of s.
- days s. Young: s h; overhead . Expect less than one failure in the whole run.
- Choose every 2,000 steps (2.1 h). Keep the last 3 and every 20,000th. Do a kill test first.
- Contrast with 16,384 GPUs: h, min and 7.3% overhead with s; with asynchronous saves ( s), min and 3%.
Step 5 — eval pipeline.
- In-loop: validation loss every 500 steps on a fixed held-out set.
- Asynchronous, at every checkpoint (about 120 over the run): a benchmark suite of about 50M tokens. Forward cost FLOPs, 0.5 GPU-hours per checkpoint, about 63 GPU-hours in total: 0.2% of training.
Step 6 — monitoring and trade-offs. Dashboards: loss, gradient norm, tokens/s, MFU per rank, loader wait, exposed communication time. Alerts: NaN or a loss spike, MFU 10% below normal (a slow node), checkpoint older than 4 hours. At 10 times the model (70B): model states 1.12 TB, so TP 8 inside the node plus PP or ZeRO-3 across nodes (check yourself, question 6). At 10 times the GPUs: failures every 1–2 days, so asynchronous checkpoints and automatic node exclusion.
(c) A different case: a long-sequence ViT #
A vision transformer for global weather fields: , , patch tokens per sample, full (non-causal) attention.
- Attention share: , so attention is of the FLOPs. The rule would underestimate the compute 2.3 times.
- Activations with FlashAttention: GB per layer, 27 GB per sample: the activations, not the model states ( GB), fill the GPU.
- Storage: one sample (input and target state, 70 channels on a grid, 16-bit) is about 290 MB. At 64 samples per 0.7-second step this is about 27 GB/s from the file system: here the data path, not the network, is the main risk. The same three rooflines, a different answer.
5. In code #
NumPy checks the numbers above: the data-parallel gradients of 4(a), the ZeRO table, the Megatron split, the bubble and Young's interval. The notebook has these cells and the exercises.
import numpy as np
# (a) data parallelism = one large batch: y_hat = w x, squared loss, mean over the batch
x, y, w = np.array([1., 2., 3., 4.]), np.array([2., 4., 5., 9.]), 1.0
grad = lambda xs, ys: 2 * np.mean((w * xs - ys) * xs)
full = grad(x, y)
g0, g1 = grad(x[:2], y[:2]), grad(x[2:], y[2:]) # two GPUs, two samples each
print(full, g0, g1, (g0 + g1) / 2) # -15.5 -5.0 -26.0 -15.5
u0, u1 = grad(x[:1], y[:1]), grad(x[1:], y[1:]) # uneven shards: 1 and 3 samples
print((u0 + u1) / 2, (1 * u0 + 3 * u1) / 4) # -11.0 (wrong) -15.5 (count-weighted)
# (b) model-state memory per GPU (GB) for mixed-precision AdamW, K = 12
def zero_gb(psi, n):
return [16 * psi, 4 * psi + 12 * psi / n, 2 * psi + 14 * psi / n, 16 * psi / n]
print([round(v / 1e9, 2) for v in zero_gb(7.5e9, 64)]) # [120.0, 31.41, 16.64, 1.88]
# (c) Megatron: split the first MLP weight by columns, the second by rows
gelu = lambda z: 0.5 * z * (1 + np.tanh(np.sqrt(2 / np.pi) * (z + 0.044715 * z**3)))
rng = np.random.default_rng(0)
X, A, B = rng.normal(size=(4, 8)), rng.normal(size=(8, 32)), rng.normal(size=(32, 8))
Z = gelu(X @ A) @ B # the unsplit MLP
Z1 = gelu(X @ A[:, :16]) @ B[:16] # GPU 0: its 16 columns of A, 16 rows of B
Z2 = gelu(X @ A[:, 16:]) @ B[16:] # GPU 1
print(np.allclose(Z, Z1 + Z2)) # True: one all-reduce (a sum) at the end
P1, P2 = X[:, :4] @ A[:4], X[:, 4:] @ A[4:] # row split of A instead
print(np.allclose(gelu(X @ A), gelu(P1) + gelu(P2))) # False: GeLU of a sum is not a sum of GeLUs
# (d) pipeline bubble and (e) Young's interval
p, m = 4, 8
print(round((p - 1) / (m + p - 1), 4)) # 0.2727
M_gpu = 54 * 16384 / 419 # days per GPU between failures (Llama 3)
for n in (128, 16384):
M = M_gpu * 86400 / n # job MTBF in seconds
for delta in (30, 5):
tau = np.sqrt(2 * delta * M)
print(n, delta, round(M / 3600, 1), round(tau / 60, 1), round(100 * np.sqrt(2 * delta / M), 2))
# 128 30 395.9 154.1 0.65 (GPUs, delta in s, M in h, tau* in min, overhead in %)
# 128 5 395.9 62.9 0.26
# 16384 30 3.1 13.6 7.34
# 16384 5 3.1 5.6 3.0
A Monte Carlo check of Young's formula, with random failures (time in hours):
import numpy as np
M, delta, R = 24.0, 0.05, 0.1 # hours: job MTBF, checkpoint cost, restart cost
work = 2000.0 # hours of useful training
def simulate(tau, rng):
"""Elapsed hours to finish `work`, with a checkpoint after every tau hours of work."""
t, done, next_fail = 0.0, 0.0, rng.exponential(M)
while done < work:
seg = min(tau, work - done) + delta # work, then write a checkpoint
if t + seg <= next_fail: # the segment finishes: progress is saved
t, done = t + seg, done + seg - delta
else: # failure: lose the segment, pay the restart
t = next_fail + R
next_fail = t + rng.exponential(M)
return t
rng = np.random.default_rng(0)
taus = np.array([0.25, 0.5, 1.0, 1.55, 2.5, 4.0, 8.0])
sim = np.array([np.mean([simulate(tau, rng) / work - 1 for _ in range(40)]) for tau in taus])
formula = delta / taus + taus / (2 * M) + R / M
print(np.round(100 * sim, 1)) # [21.2 11.8 7.8 7.2 8.2 10.9 19.8]
print(np.round(100 * formula, 1)) # [20.9 11.5 7.5 6.9 7.6 10. 17.7]
print(round(np.sqrt(2 * delta * M), 2), taus[np.argmin(sim)]) # 1.55 1.55
The minimum is at h in both. The simulation is a little above the first-order formula, and more so for large , where holds less well.
6. Where it shows up #
- LLM pretraining. Every large run is 3-D or 4-D parallel, with an MFU around 40% and failures every few hours at the largest scale (Llama 3, §3.3). The data-parallel size sets the global batch, and the global batch sets the learning rate.
- Fine-tuning. FSDP with LoRA or QLoRA: the frozen base model is sharded, and only the small adapters have gradients and Adam states (lesson 11).
- ML for science. Weather, climate and other scientific foundation models often have moderate parameter counts but very long token sequences and large samples. Attention FLOPs, activations and the input pipeline then matter more than ZeRO (section 4(c)). HPC clusters add node-hour allocations, job time limits and a shared parallel file system.
- RL post-training. An inference engine runs inside the training loop to generate rollouts, so the design mixes the training numbers of this lesson with the serving numbers of lesson 13.
7. Common mistakes #
- "2 bytes per parameter" for training. That is bf16 inference. Training with AdamW needs 16 bytes per parameter, plus the activations.
- "ZeRO-3 shards activations." ZeRO-1/2/3 shard model states only. Activations need checkpointing, a smaller micro-batch, TP with sequence parallelism, or context parallelism.
- "ZeRO-3 costs times the communication." It is against .
- Gradient accumulation with ZeRO-2/3. ZeRO-2 reduce-scatters after every micro-batch, and ZeRO-3 also all-gathers the weights for every micro-batch: about times the traffic of ZeRO-1.
- Sum instead of mean. Without the division by the gradient is times too large. With uneven shards, weight by the counts.
- TP across nodes. Its per-layer all-reduces need NVLink-class bandwidth.
- Too few micro-batches in a pipeline. With most stages wait; aim for and use 1F1B so that the activations do not grow with .
- at long sequence lengths. It drops the attention term; for it can be off by a factor of 2 or more.
- Planning at peak FLOP/s. Use 35–45% MFU for a dense model, and say so.
- Weights-only checkpoints, written in place, never tested. Without optimizer state, RNG states and data position a "resume" is a different run; a crash during an in-place write destroys the only copy; an untested restart fails when you need it.
8. Check yourself #
- A 70B model, mixed-precision AdamW, 64 GPUs with 80 GB each. Model-state memory per GPU with
ZeRO-1, ZeRO-2 and ZeRO-3? Which ones fit?
Answer
ZeRO-1: GB. ZeRO-2: GB. ZeRO-3: GB. Only ZeRO-3 fits (or add TP/PP); the activations come on top. - Why does ZeRO-2 send no more data than DDP?
Answer
A ring all-reduce already is a reduce-scatter plus an all-gather. ZeRO-2 keeps the two halves and puts the optimizer step on the shard between them: reduce-scatter the gradients (), update the own shard, all-gather the parameters (). Total , the same as DDP. - stages, micro-batches. What is the bubble with GPipe and with 1F1B, and with interleaving ()? How many micro-batches of activations does stage 0 hold?
Answer
GPipe and 1F1B: of the step. Interleaved: . Stage 0 holds 32 micro-batches with GPipe and at most 8 with 1F1B. - DDP across nodes with TFLOP/s achieved per GPU and GB/s per GPU. How many tokens per GPU per step hide the all-reduce? Does the answer change for a model 10 times larger?
Answer
tokens. No: both the backward FLOPs () and the all-reduce bytes () are proportional to , so cancels. - A job on 1,024 GPUs ( days), blocking checkpoint s, restart min. Give , and the overhead.
Answer
s h. s min. Overhead plus : about 2.9%. - Pretrain a 70B decoder (, , ) on 1,024 H100s (128 nodes of 8). Why can it not use ZeRO-1 alone, and what layout would you propose?
Answer
ZeRO-1 keeps full bf16 weights and gradients: GB per GPU. Propose TP 8 inside each node (states and activations divided by 8), PP 4 across nodes and DP 32 with ZeRO-1. Each GPU holds parameters, so its model states are GB. The TP communication/compute ratio is about 0.63 on NVLink; the pipeline bubble with is .
How many bytes per parameter does mixed-precision AdamW keep, and how are they split?
16 bytes: bf16 weight (2) + bf16 gradient (2) + fp32 master weight, Adam and Adam (4 + 4 + 4).
How many bytes does each GPU send in a ring all-reduce of bytes over GPUs?
: chunks of in the reduce-scatter and in the all-gather.
Why does data parallelism with equal shards give the full-batch gradient?
The full loss is the mean of the shard losses, and the gradient is linear, so the mean of the shard gradients is the full gradient.
When does the DDP all-reduce hide behind the backward pass?
When each GPU processes at least tokens per step; the model size cancels.
What are the model states per GPU with ZeRO-3 on GPUs?
bytes: weights, gradients and optimizer states are all sharded.
Why does ZeRO-3 send the data of DDP?
It all-gathers the weights twice (forward and backward, ) and reduce-scatters the gradients once (): against .
How many all-reduces does a Megatron TP layer (attention + MLP) need per training step?
4: two in the forward pass and two in the backward pass, each on values.
What is the pipeline bubble fraction with stages and micro-batches?
of the step (GPipe and 1F1B); compute it for , : .
What is the mean time between failures of a job on GPUs?
, because the job stops at the first failure of any GPU and the rates add.
Give Young's checkpoint interval and its overhead.
, overhead (plus ). 100× more GPUs give 10× more overhead.
9. Interview angle #
Recognize the task from the wording.
| The prompt says | Start with |
|---|---|
| "will it fit?", "out of memory" | the memory table: , activations per layer, then ZeRO stage, TP, checkpointing |
| "how would you train this on N GPUs?" | DP (with ZeRO) first; TP inside a node; PP across nodes only if needed |
| "the step is slow", "low GPU utilization" | rooflines per resource: HBM, network (exposed communication), data loader |
| "communication is the bottleneck" | ; hierarchical collectives; ZeRO-1 instead of ZeRO-3 with accumulation |
| "how long will training take?", "how many GPUs?" | FLOPs per token , MFU 35–45%, goodput 90% |
| "nodes fail", "a three-month run" | , Young's , asynchronous sharded checkpoints, restart path |
| "very long sequences", "images or fields as tokens" | attention FLOPs , FlashAttention, context parallelism, the data path |
The routine to say aloud (about 30 seconds): "Let me first get the numbers: parameters, layers, width, sequence length, tokens, GPUs per node, memory, link bandwidths and the deadline. Then I compute the FLOPs and GPU-hours, then the memory per GPU, then I choose the smallest layout that fits, and I check compute, network and storage with rooflines. Last, failures and evals."
A plan for a 45-minute design round.
| Minutes | Step | What you write down |
|---|---|---|
| 0–5 | Clarify | model shape, data, token budget, deadline, hardware, what "done" means |
| 5–10 | Compute budget | FLOPs per token, total FLOPs, GPU-hours at an assumed MFU, number of GPUs |
| 10–15 | Memory | model states, activations, what must be sharded |
| 15–22 | Layout | DP / ZeRO / TP / PP / CP degrees and their mapping to nodes |
| 22–27 | Roofline checks | HBM, network (is communication hidden?), storage (is the loader fast enough?) |
| 27–33 | Reliability | checkpoint content and size, interval, restart path |
| 33–39 | Eval pipeline | in-loop signals, asynchronous checkpoint evals, eval sets, cost |
| 39–45 | Monitoring, trade-offs | dashboards and alerts; what changes at 10× the model, data or cluster |
Typical questions and short model answers.
- "Will a 13B model train on one node of 8 × 80 GB?" — "The model states are GB, so DDP does not fit. ZeRO-3 over 8 GPUs gives 26 GB per GPU; ZeRO-2 gives GB. Activations with FlashAttention at , , 40 layers: GB per layer, 28.5 GB per sequence. So ZeRO-3 with one sequence per GPU fits at about 55 GB; with ZeRO-2 I would add activation checkpointing. NVLink makes the ZeRO-3 all-gathers cheap inside the node."
- "Explain ZeRO stages 1, 2 and 3." — "All three are data parallel. Stage 1 shards the optimizer states, stage 2 also the gradients, stage 3 also the parameters. Stages 1 and 2 send the same as DDP, because they split the all-reduce into a reduce-scatter before the update and an all-gather after it. Stage 3 gathers the weights twice per step: , 1.5 times DDP, in exchange for memory."
- "Why does tensor parallelism stay inside a node?" — "It needs 4 all-reduces of the activations per layer, on the critical path. The ratio of communication to compute is : about 0.6 on NVLink for , but about 5.6 at 50 GB/s between nodes."
- "What is the pipeline bubble and how do you reduce it?" — "With stages and micro-batches, the first stage waits slots for the pipeline to fill and the last waits to drain: a fraction . More micro-batches shrink it, gives under 20%. 1F1B keeps the activations at micro-batches instead of ; interleaving divides the bubble by ."
- "How often should you checkpoint a 16k-GPU job?" — "The job fails about every 3 hours. Young's interval is : with a 30-second blocking save, about 14 minutes and 7% overhead. That is too much, so I make the save asynchronous (copy to CPU, write in the background): with 5 seconds, about 6 minutes and 3%."
- "MFU dropped from 42% to 30% after a restart. What do you check?" — "A slow node first: compare per-rank step times, run GPU and network health checks, exclude the node. Then the data path after the restart (a cold cache), and exposed communication in a profile."
Typical follow-ups. "How does FSDP overlap communication?" (it prefetches the next unit's all-gather and runs reduce-scatters while the backward pass continues). "What happens when you restart on a different number of GPUs?" (sharded checkpoints are resharded at load time; the global batch must stay the same, so change the accumulation steps). "The loss spikes at step 80k?" (roll back to a permanent checkpoint before the spike, skip the batches around it, then lower the learning rate or tighten clipping if it repeats).
10. Practice #
The notebook for this lesson runs in Colab, in NumPy. Each exercise has tests against a reference:
ring_allreduce: simulate the ring on GPUs (a list of arrays); check the sum and that each GPU sends values.data_parallel_grad: the count-weighted mean of shard gradients equals the full-batch gradient of a linear model, also for uneven shards.zero_memory: the per-GPU model states of DDP and ZeRO-1/2/3, checked against the ZeRO paper's 7.5B example.pipeline_schedule: simulate GPipe and 1F1B and return the step time, the bubble and the peak in-flight micro-batches; compare with .young_interval: find the best interval numerically and by Monte Carlo, and compare with .
Timed practice (25 minutes each): Sasha Rush's LLM-Training-Puzzles, in order: "Standard Training", "Gradient Accumulation", "Distributed Data Parallel", "Weight-Sharded Data Parallel", "Fully-Sharded Data Parallel", "Pipeline Parallel", "GPipe Schedule", "Pipeline + FSDP". Predict the time and peak memory of each puzzle before you code it. Then two design drills of 45 minutes: the worked example of section 4(b) from memory, and question 6 of section 8 as a full design.
Say it aloud drill. Without notes, explain in 2 minutes why ZeRO-1 and ZeRO-2 send as much as DDP and ZeRO-3 sends 1.5 times as much. Then derive Young's interval in 1 minute.
11. Watch and read #
- Stanford CS336 (Spring 2025), Lecture 7: Parallelism 1 and Lecture 8: Parallelism 2 (videos on YouTube; embedding is disabled): collectives, data, tensor and pipeline parallelism, with real numbers.
- Hugging Face, The Ultra-Scale Playbook (free web book): "First Steps: Training on One GPU", "Data Parallelism" (ZeRO-1/2/3), "Tensor Parallelism", "Pipeline Parallelism", "Finding the Best Training Configuration".
- Austin et al., How to Scale Your Model (free web book): "All About Rooflines" and "How to Parallelize a Transformer for Training".
- Sasha Rush, LLM-Training-Puzzles: eight puzzles that simulate one training step on several GPUs.
- Rajbhandari et al. (2020), ZeRO: Memory Optimizations Toward Training Trillion Parameter Models: §3.1 (16 bytes per parameter), §5 (the three stages), §7 (communication volume).
- Shoeybi et al. (2019), Megatron-LM, §3 (the column/row split); Korthikanti et al. (2022), Reducing Activation Recomputation in Large Transformer Models, §4 (the activation formula).
- Huang et al. (2019), GPipe; Narayanan et al. (2021), Efficient Large-Scale Language Model Training on GPU Clusters Using Megatron-LM, §2 (1F1B, interleaving, the bubble).
- Llama 3 team (2024), The Llama 3 Herd of Models: §3.3.2 and Table 4 (4-D parallelism, MFU), §3.3.4 and Table 5 (interruptions and their causes).
- PyTorch, Asynchronous Saving with Distributed Checkpoint (DCP).
- Young (1974), "A first order approximation to the optimum checkpoint interval", Communications of the ACM 17(9), 530–531: the original derivation of .
Runs in your browser with nothing to install. The notebook repeats the key demos and has exercises with checks; solutions are inside, folded.
No Google account needed: Python runs inside your browser. The first start downloads about 40 MB; your changes are saved in this browser only.