Scaling laws & distributed training
Modern LLMs have 8B-1T parameters and train on trillions of tokens, far beyond a single GPU. This chapter covers two things: how to allocate a fixed compute budget between model size and dataset size (Chinchilla scaling laws), and how to actually train large models across many GPUs (data parallelism, FSDP, tensor parallelism, pipeline parallelism).
Chapter 8 ended with a working training loop on a small model, a 50M-parameter transformer learning on a single GPU in an afternoon. The math of that chapter is the math of modern LLM training: same cross-entropy, same AdamW, same warmup-plus-cosine schedule. What is not the same at frontier scale is the engineering. Llama-3 has 8B to 405B parameters. GPT-4 is reportedly trillion-plus. The training corpora are trillions of tokens. A single H100 holds at most a few billion parameters in mixed precision, and training needs additional memory for gradients, optimizer state, and activations. The Chapter 8 loop, as written, runs out of memory before the first forward pass on these models, and would take years if it didn’t.
This chapter answers two questions about that gap. The first is what should you train? Given a fixed FLOP budget, say, , should you train a small model for a long time or a large model briefly? The Chinchilla scaling laws (Hoffmann et al. 2022) give a surprising answer: roughly tokens per parameter, with model size and dataset size scaling together. Pre-2022 models (GPT-3, Gopher, PaLM) violated this rule and were significantly undertrained. Modern frontier models fix it, and then intentionally over-train smaller architectures past Chinchilla-optimal for the inference economics.
The second question is how do you actually train at scale? Four parallelism strategies (data parallelism, tensor parallelism, pipeline parallelism, and FSDP) distribute a training run across many GPUs. At the very largest scale, all three of DP, TP, and PP combine into “3D parallelism”: Megatron-Turing’s 530B model used 8-way TP × 35-way PP × 64-way DP across GPUs. After this chapter, the system diagrams of GPT-4-scale training runs are readable. Chapter 10 continues with the practical infrastructure that runs those clusters.
The setup: single GPU isn’t enough
A B-parameter model in BF16 weighs about GB just for the parameters themselves. Training needs more memory than that, by a lot. Gradients add another GB (one per parameter). AdamW’s optimizer state, the first moment and second moment , typically kept in FP32 even when the model runs in BF16, adds another GB ( B). Activations for the backward pass, depending on sequence length and batch size, add to GB more. Total training-state memory for a B model: GB.
That number does not fit on any single GPU. An A100 has or GB; an H100 has GB. A B model is borderline on a single H100 with careful memory management; a B model is not even close: its training state is closer to GB.
Time is the second wall. Even if memory fit, training wall-clock scales with FLOPs divided by GPU throughput. An H100 peaks around TFLOPS at BF16. The standard estimate for transformer training is FLOPs total, where is parameters and is training tokens. A B model on T tokens is FLOPs, about years on a single H100 running at peak throughput, which they never do. A B model on T tokens is closer to years. Single-GPU training of a frontier model is, literally, infeasible.
These are two distinct problems. Memory says the model and its training state do not fit on one GPU. Time says even if they did, the run would take years. The solutions overlap but require different techniques. Sections 2 and 3 first handle a prior question, what to train, given the FLOP budget. Sections 4 through 7 cover the engineering of distributing that training across many GPUs.
The first scaling laws: Kaplan 2020
Kaplan et al. 2020 (arxiv.org/abs/2001.08361) was the first systematic study of how loss varies with model size, dataset size, and compute for transformer language models. They trained transformers across a wide range of sizes and dataset sizes and discovered that test loss followed clean power laws in each variable. Doubling the parameter count reduced loss by a predictable amount; doubling the data reduced it by a (different) predictable amount; doubling the compute, holding the right things constant, reduced it by yet another amount. The pattern was robust enough that it became the field’s standard vocabulary, “scaling laws” as a noun, “the slope of the scaling curve” as a way of talking, all started here.
The paper’s most-cited claim was the compute-optimal allocation. Given a fixed compute budget , Kaplan recommended putting most of the compute into model size and comparatively little into more training data, specifically, and . The reading at the time was: scale parameters aggressively; data is a secondary concern.
The labs that built the next generation of models followed this recommendation. GPT-3 (Brown et al. 2020) was B parameters trained on B tokens, a ratio of roughly tokens per parameter. Gopher (Rae et al. 2021): B parameters, B tokens, a ratio near . Megatron-Turing NLG (Smith et al. 2022): B parameters, B tokens, a ratio of . PaLM (Chowdhery et al. 2022): B parameters, B tokens, a ratio of . Every flagship model from 2020 through early 2022 sat in the same regime: very large parameters, comparatively few training tokens, following the Kaplan recipe.
The problem was that the Kaplan experimental design under-trained the larger models. Each model in their grid was trained for a fixed number of optimizer steps, regardless of size. Large models, which have more parameters to fit, would have needed more steps to converge; they were stopped early. The headline allocation that fell out was an artifact of the experimental setup, not of the underlying scaling behavior. When the experiments were re-run with each model trained to convergence, done two years later in the Chinchilla paper, the optimal allocation shifted dramatically. Most of the 2020-2022 frontier models, it turned out, were significantly undertrained for their size.
The Chinchilla correction: Hoffmann 2022
The new experiment
Hoffmann et al. 2022 (arxiv.org/abs/2203.15556) redid the scaling analysis with corrected methodology. Where Kaplan trained each model for a fixed step budget, Hoffmann trained each of + language models to convergence across a careful grid of model sizes and token counts. Three complementary analyses, fitting a parametric loss function, isolating compute-vs-loss curves at fixed model size, and the iso-FLOPs-curve method, all pointed to the same answer.
The empirical demonstration was the most visible part of the paper. The authors trained Chinchilla, a B-parameter model on T tokens, using the same compute budget as DeepMind’s earlier Gopher (B parameters, B tokens). Chinchilla, despite being smaller, outperformed Gopher on essentially every benchmark. Same training compute, better-trained smaller model, better results. The Kaplan-era practice of “scale parameters first” was empirically wrong.
The scaling-law equation
The paper’s central fitted equation is a parametric form for the test loss:
With fitted values , , , , . The three terms decompose the loss into three sources: the irreducible loss (the entropy of natural language, no language model can do better than this), the model-capacity error (loss from too few parameters; decays as a power law in ), and the data-limitation error (loss from too few training tokens; decays as a power law in ). Both error terms vanish in the limit of infinite size or infinite data; both decay slowly, as small powers of their inputs.
The shapes of the exponents matter. With and , doubling reduces the model-capacity error by a factor of , about a reduction, and doubling reduces the data-limitation error by , about an reduction. Neither term dominates by much. They are close enough in slope that, for compute-optimal training, both terms should be reduced together.
Compute-optimal allocation
Training a model of size on tokens costs approximately FLOPs. (The is the empirical estimate: for the forward pass, for the backward pass. The backward is roughly twice the forward because it computes gradients with respect to both the activations and the parameters.) The compute-optimal allocation is the solution to a constrained minimization:
Skipping the Lagrangian derivation, the answer is a pair of power laws in :
Plug in Chinchilla’s fitted : . So and . Both scale together as compute increases, neither dominates. Compare to Kaplan’s and the difference is stark: Chinchilla puts substantially more weight on tokens.
The 20-tokens-per-parameter rule
The headline result everyone remembers from Chinchilla is , about training tokens per parameter. It is exactly the ratio the Chinchilla model itself used: B parameters, T tokens. Want to train a B-parameter model compute-optimally by this rule? Use B training tokens.
Worth being precise about where "" comes from. It is the result of Hoffmann et al.’s empirical iso-FLOP curves (training a grid of models at fixed compute and reading off the loss-minimizing size directly), not a number produced by solving the parametric equation above with its fitted constants. Plugging into the constrained minimization instead gives a ratio that is compute-dependent and, at Chinchilla’s own training budget, noticeably above , drifting from roughly at to over at . The two Chinchilla-paper analyses (the parametric fit and the iso-FLOP profiles) agree on the qualitative story, N and D should scale together, but do not reproduce the same numeric ratio from the fitted constants alone. ” tokens per parameter” is the empirical rule of thumb; treat it as that, not as an algebraic consequence of (9.chinchilla) .
Practical use: given a compute budget , the -tokens-per-parameter rule of thumb is a reasonable starting allocation. Tune from there based on what your downstream evaluation actually cares about. The Chinchilla ratio is a guide, not a contract, and as the Llama-3 example below will show, modern practice often intentionally departs from it.
The numerical version is short: the Chinchilla loss function is a few lines of numpy, and the compute-optimal allocation falls out of a brute-force sweep along the constraint. Running it across a range of compute budgets shows the true optimal ratio implied by this parametric fit, and that ratio rises with compute rather than sitting fixed at .
The allocation math is settled. The engineering question is what comes next: even if you know what you want to train, how do you actually run a B-parameter model that does not fit on one GPU?
Why distribute: three reasons
The reasons to distribute training across many GPUs come in three flavors. They overlap in motivation but call for different techniques.
Memory. As Section 1 spelled out, a B model with AdamW state in mixed precision runs about GB of training-state memory. An H100 has GB. The model cannot fit on one GPU at any setting. Sharding the parameters, gradients, and optimizer state across many GPUs, so each GPU holds only a fraction, is the only way the run starts at all.
Speed. Even when memory fits, a single-GPU run takes prohibitively long. The standard FLOP estimate for a B model on T tokens is FLOPs, roughly years of H100-equivalent compute at peak throughput. Splitting that work across many GPUs in parallel is what brings wall-clock down from years to weeks.
Batch size. Language modeling benefits from large effective batches, the per-step gradient is a better estimate of the true population gradient when averaged over more tokens, and many of the optimizer hyperparameters (especially the AdamW second moment) work better with bigger batches. Achieving, say, an effective batch of M tokens per step on a B model means many GPUs processing in parallel, since the per-GPU batch is constrained by activation memory.
Three reasons, three distinct parallelism strategies that can be combined. Data parallelism (DP) addresses speed and batch size: same model on every GPU, different micro-batches. Model parallelism, tensor parallelism (TP) and pipeline parallelism (PP), addresses memory by splitting the model itself. FSDP (PyTorch’s name for ZeRO-3) gives DP-like simplicity with model-parallel-like memory savings. Sections 5 through 7 cover each in turn; for very large models, all three are combined into 3D parallelism.
Data parallelism: the simple case
How DP works
Data parallelism is the simplest scheme and the right starting point. Each GPU holds a complete copy of the model parameters, every weight matrix, every bias, every layer norm scale, all replicated identically. The training batch is sharded across GPUs: with GPUs and a global batch of sequences, each GPU sees sequences. Each GPU runs its forward pass on its shard, computes the loss, and runs the backward pass independently. At that point, every GPU has a gradient, but a different gradient, because each one saw different data.
The synchronization step is to average the gradients across all GPUs, using an all-reduce. Every GPU comes out the other side with the same averaged gradient, applies the same AdamW step, and ends the iteration with identical parameters, bit-identical, modulo floating-point reduction order. The model stays in sync across the whole cluster, automatically. The next batch arrives sharded, the process repeats. DP is conceptually clean: take Chapter 8’s loop, wrap the model in DistributedDataParallel, and the training proceeds.
The all-reduce
The collective operation that makes DP work is all-reduce: every GPU contributes a tensor, the system computes the element-wise sum (or mean), and the result is delivered back to every GPU. For gradient averaging, every GPU contributes its full gradient and gets back the mean.
The efficient implementation is the ring all-reduce. Conceptually, the GPUs form a ring; the gradient is split into chunks; each step of the ring shuffles one chunk forward, accumulating partial sums. After ring steps, every GPU has the full sum. The total bytes each GPU sends and receives works out to approximately , for large , very close to . Crucially, the communication cost per GPU is independent of for large : adding more GPUs does not slow each individual GPU’s communication.
That sounds like DP scales arbitrarily, and it almost does. The gradient all-reduce happens every step, covers the entire model, and stays roughly constant in per-GPU cost regardless of GPU count. Doubling the GPU count doubles the throughput linearly, until a different bottleneck shows up.
Scaling limits
The first scaling limit of DP is memory: DP does not help with the memory problem at all. Every GPU still holds the full model, the full gradient, the full optimizer state. A model that does not fit on a single GPU does not fit under DP either. DP is for speed; it is not a memory-savings technique.
The second scaling limit is communication. The “constant per-GPU cost” assumption holds when the cluster’s network can keep up. At a few hundred GPUs in a single rack with NVLink and high-bandwidth InfiniBand between nodes, the all-reduce overlaps cleanly with compute and the run scales linearly. Past roughly GPUs, the all-reduce time per step starts to dominate compute time, and adding more GPUs delivers diminishing returns. The exact crossover depends on model size, batch size, and network topology, but the qualitative pattern is robust: DP alone is not enough for frontier-scale training.
If DP cannot break the per-GPU memory ceiling, the next step is to actually split the model itself. There are two ways to do that.
Model parallelism: tensor and pipeline
Tensor parallelism (Megatron)
Tensor parallelism splits individual layer operations across GPUs. Each GPU holds a slice of a layer’s parameters and computes a slice of that layer’s output. The pioneering recipe is Megatron-LM (Shoeybi et al. 2019, arxiv.org/abs/1909.08053), which works out specific dimension choices for the transformer’s FFN and attention so that communication between GPUs is required only at carefully chosen points.
For the FFN, the recipe is a column-then-row split. The first linear projects , Megatron splits this along the output (column) dimension, so each TP rank computes its own slice of the -dimensional hidden activations independently, with no communication needed. The nonlinearity, GELU (element-wise) or the gated SwiGLU variant, is applied within each rank’s own activation slice and needs no communication. The second linear projects , Megatron splits this along the input (row) dimension, so each rank multiplies its own activation slice by its own weight slice, producing a partial sum. A single all-reduce across TP ranks then combines the partial sums into the final FFN output. Two big linear operations; one communication point.
For attention, the recipe is similar: QKV projections split along the head dimension (different attention heads on different GPUs), each rank computes attention independently on its heads, the output projection splits along the input dimension, and a single all-reduce across TP ranks at the end of attention recovers the full output. Each transformer block ends up with two all-reduces, one for attention, one for FFN.
The communication cost per block is two all-reduces of the activation, proportional to batch × sequence-length × , not to model size. This is high-volume communication that happens on every layer of every forward and backward pass. It needs very high bandwidth and very low latency, which is why TP is almost always confined to within a node: typical TP ranks are , , or , matching the number of GPUs on one server connected by NVLink.
Pipeline parallelism (GPipe)
Pipeline parallelism splits the model between layers, not within them. Different layers live on different GPUs. GPU holds layers through ; GPU holds layers through ; and so on, dividing an -layer model into pipeline stages. The pioneering paper is GPipe (Huang et al. 2018, arxiv.org/abs/1811.06965).
The forward pass flows down the pipeline: a batch enters GPU , its layer--through- activations are computed, the result is sent to GPU , which runs layers through , and so on. The backward pass flows in reverse. Communication happens only at the stage boundaries, one activation send between consecutive GPUs per stage transition. That is dramatically less communication than TP. PP is bandwidth-friendly and runs comfortably across nodes over InfiniBand.
The cost of PP is the pipeline bubble. At the very start of training, GPU is busy on the first batch while GPUs through wait for activations to arrive. At the end of each backward pass, only the later GPUs are busy while the earlier ones idle. The idle time is the bubble; it makes the pipeline less efficient than a fully-utilized DP cluster of equivalent size.
The mitigation is micro-batching: split each batch into many smaller micro-batches and feed them through the pipeline one after another. While GPU is processing micro-batch , GPU is already processing micro-batch , and the pipeline fills up. With stages and micro-batches per batch, the bubble fraction is approximately . To get the bubble small, has to be much larger than . Modern 1F1B and interleaved-1F1B schedules push the bubble down further by interleaving forward and backward passes carefully.
The tradeoff
The two model-parallel strategies trade off communication volume against scheduling complexity. TP has low memory per GPU and high per-layer activation communication, fastest within a node, worst across nodes. PP has low communication (only at stage boundaries) but introduces the pipeline bubble, best across nodes, requires careful micro-batch scheduling.
For very large models, both are used together: TP within nodes (NVLink bandwidth) and PP across nodes (InfiniBand). Combining the two with DP on top gives the 3D parallelism that frontier-scale training runs use. The Megatron-Turing B model (Smith et al. 2022, arxiv.org/abs/2201.11990) is the canonical demonstration: TP within each node, PP across nodes, DP across replicas, totaling GPUs. The dimensions are chosen so each form of communication lands on a network layer that can sustain it.
The third way to think about parallelism keeps DP’s simplicity while sharding the state, and has become the standard default for everything in the B-B range.
FSDP: the modern default
The motivation
Vanilla DP wastes memory by replicating everything. Every GPU stores a full copy of the parameters, the gradients, and the AdamW optimizer state ( and ). For a B model in BF16 with AdamW: GB params + GB gradients + GB FP32 optimizer state = GB per GPU just for state, before any activations. That barely fits on an H100, and entirely will not fit on cheaper GPUs.
The insight from ZeRO (Rajbhandari et al. 2020, arxiv.org/abs/1910.02054) is that if the work is already sharded across DP ranks, there is no good reason to keep the state replicated. Shard the state too. ZeRO defines three stages of increasingly aggressive sharding: ZeRO-1 shards optimizer state, ZeRO-2 shards gradients too, ZeRO-3 shards parameters as well. PyTorch’s FSDP (Fully Sharded Data Parallel) is essentially ZeRO-3 with a clean wrapper API, and it has become the modern default for any model larger than a few hundred million parameters.
How FSDP works
Under ZeRO-3 / FSDP, each GPU stores only of each layer’s parameters, where is the number of DP ranks. The rest live on other GPUs. When the forward pass needs layer :
- All-gather layer ‘s parameters from all DP ranks. Each rank contributes its shard; every rank ends up with the full layer params temporarily in memory.
- Compute the forward pass for layer using those gathered parameters.
- Discard the gathered parameters as soon as the layer is done. The local shard is kept; the rest is freed.
Backward works symmetrically: gather the parameters for layer , compute the gradient, then reduce-scatter the gradient back to the appropriate shards. After the full backward pass, each GPU holds the gradient for its shard of every layer’s parameters. The optimizer step happens locally on each shard.
The end result is that each GPU stores only of the model + of the gradients + of the optimizer state. For a B model on GPUs, the per-GPU state drops from GB to GB, comfortably fitting on much cheaper hardware. The cost is extra communication: every forward and backward pass now needs all-gathers and reduce-scatters. In practice, those costs overlap with compute (FSDP pre-fetches the next layer’s parameters while the current layer is computing), so the wall-clock cost is often small. For most modern training, FSDP’s memory savings dramatically outweigh its communication overhead.
When to use what
The parallelism strategy depends on model size and cluster scale.
- Small model (B), few GPUs: vanilla DP. Simple, low overhead, no reason to add complexity.
- Medium model (B-B), many GPUs: FSDP. Memory savings open up cheaper hardware; the communication overhead is manageable; the API stays nearly identical to vanilla DP.
- Large model (B+): FSDP plus tensor parallelism within nodes. FSDP handles the DP-axis sharding; TP shrinks each layer’s compute footprint enough to fit big layers in NVLink-bandwidth communication.
- Frontier scale (B+): full 3D parallelism, DP TP PP, often with FSDP on the DP axis. Megatron-Turing B’s GPUs is the canonical example.
What we’ve built: and what’s next
Nine chapters in, the training-side mental model is complete. Chapters 1 through 6 specified the architecture. Chapter 7 specified the data pipeline. Chapter 8 specified the training loop and its mathematics, the cross-entropy loss, AdamW, the warmup-plus-cosine schedule, gradient clipping. This chapter answered the two questions of scale: what to train, given a compute budget (Chinchilla scaling laws, tokens per parameter, with the modern caveat that frontier models intentionally over-train for inference economics), and how to train it across many GPUs (DP for speed, TP within nodes for memory, PP across nodes for memory, FSDP as the modern unified default, 3D parallelism for the largest runs). The system diagrams of GPT-4-scale training runs, Llama-3’s -GPU H100 cluster, Megatron-Turing’s -GPU 3D parallel setup, are now readable from end to end.
What is still missing is the systems engineering of running those clusters. Cluster orchestration, NCCL configuration, CUDA streams, Triton kernel development, debugging training runs that go bad at the -GPU scale, recovering from inevitable hardware failures over weeks-long jobs. Chapter 10 covers that material. It is practical depth rather than core conceptual content: the math and the parallelism strategies of this chapter are the conceptual core; the kernel-level details of Chapter 10 are what frontier labs actually wrestle with in production.
Exercises
Four exercises that build on the chapter’s machinery. Each is a self-contained problem with a starting template; hints are collapsed by default; try the problem first.
Exercise 1 (easy): Chinchilla loss calculator
Implement the Chinchilla scaling law equation and verify two properties: (a) loss decreases as increases (model size); (b) loss decreases as increases (training tokens). Both monotonically.
Hint
The Chinchilla loss equation is with Hoffmann et al.’s fitted constants , , , , . The monotonicity check is straightforward: as grows, shrinks; as grows, shrinks. The loss is bounded below by , the irreducible entropy of natural language.
Solution
Just sum the three terms directly:
E, A, B = 1.69, 406.0, 410.0
ALPHA, BETA = 0.34, 0.28
def chinchilla_loss(N, D):
return E + A / N ** ALPHA + B / D ** BETA
chinchilla_loss(175e9, 300e9) # GPT-3: 2.002
chinchilla_loss(70e9, 1.4e12) # Chinchilla: 1.936 -- lower despite 2.5x fewer paramsMonotonicity in (D = 1T fixed): 2.2226, 2.0306, 1.9429, 1.9027 for = 1e9…1e12 — strictly decreasing. Monotonicity in (N = 7B fixed): 3.1107, 2.5223, 2.2135, 2.0514 for = 1e9…1e12 — strictly decreasing. At , loss = 1.6940, matching to four decimal places.
Exercise 2 (medium): Compute-optimal allocation
Given a compute budget (in FLOPs), find the model size and dataset size that minimize the Chinchilla loss subject to . Use a brute-force search along the iso-compute curve.
Hint
Parameterize the constraint by the tokens-per-parameter ratio . From and , you get and . Sweep over a wide range (log scale; say 0.1 to 2000) and pick the that minimizes the loss. With these fitted constants, the optimum is not fixed at : it drifts with , from roughly at to over at . That’s higher than the famous “20 tokens per parameter” rule of thumb, which comes from Chinchilla’s separate iso-FLOP experiments rather than from this parametric equation directly (see the note in Section 3 above). Your brute-force search here should reproduce the drifting, above-20 values, not a fixed 20.
Solution
Loop over ratios, compute and from the constraint, track the minimum-loss triple:
import numpy as np
def compute_optimal(C, ratios=None):
if ratios is None:
ratios = np.logspace(-1, 3.3, 200)
best = None
for r in ratios:
N = np.sqrt(C / (6 * r))
D = r * N
loss = chinchilla_loss(N, D)
if best is None or loss < best[3]:
best = (N, D, r, loss)
return bestResults:
| Compute | N_opt | D_opt | D/N | Loss |
|---|---|---|---|---|
| 1.0e22 | 5.21e9 | 3.20e11 | 61.3 | 2.138 |
| 1.0e23 | 1.46e10 | 1.14e12 | 78.6 | 2.005 |
| 6.0e23 | 3.31e10 | 3.02e12 | 91.3 | 1.929 |
| 1.0e24 | 4.17e10 | 4.00e12 | 95.9 | 1.911 |
| 1.0e25 | 1.16e11 | 1.43e13 | 123.0 | 1.845 |
The ratio drifts from 61 to 123 as grows two orders of magnitude — it never sits at 20. This parametric equation’s own optimum is well above the “20 tokens per parameter” rule of thumb, which is a separate empirical finding from Chinchilla’s iso-FLOP sweeps, not a consequence of this fitted formula.
Exercise 3 (medium): Parallelism memory footprint
For each of DP, TP, PP, and FSDP, compute the memory per GPU for a given model size and number of GPUs. Account for parameters, gradients, and optimizer state. Verify FSDP gives proportional reduction.
Hint
For a model of parameters trained in BF16 with AdamW (matching Section 1’s accounting: GB params + GB gradients + GB FP32 optimizer state for a B model):
- Parameters: BF16 (2 bytes/param) = bytes
- Gradients: BF16 (2 bytes/param) = bytes
- Optimizer state: AdamW stores in FP32 = bytes total
- Activations: depends on batch size; ignore for this exercise
Total: bytes per GPU under vanilla DP. (This is the same bytes/param used throughout the chapter body, e.g. the GB figure for a B model and the GB post-FSDP figure.)
For each strategy, divide the appropriate state by the number of GPUs:
- DP: nothing sharded → bytes/GPU regardless of GPU count
- TP: params + grads + optimizer state divided by TP-rank → bytes/GPU
- PP: params + grads + optimizer state divided by PP-rank → bytes/GPU
- FSDP: same as TP/PP for state, divided by DP-rank → bytes/GPU
Solution
DP doesn’t shard anything; TP, PP, and FSDP each divide the full bytes by GPU count:
def memory_per_gpu(model_params, strategy, num_gpus):
BYTES_PER_PARAM_FULL = 12
N = model_params
if strategy == 'dp':
return BYTES_PER_PARAM_FULL * N
else: # 'tp', 'pp', 'fsdp'
return BYTES_PER_PARAM_FULL * N / num_gpus7B model, 8 GPUs: DP 84.0 GB, TP/PP/FSDP 10.5 GB each. 7B model, 64 GPUs: DP still 84.0 GB (unsharded), TP/PP/FSDP 1.3 GB each — FSDP at 64 GPUs uses exactly 1/8 the memory it used at 8 GPUs, matching the 8x increase in GPU count. 70B model, 8 GPUs: DP 840.0 GB (exceeds 80GB), and even sharded strategies hit 105.0 GB per GPU — still exceeds an 80GB card, so this setup needs more than 8 GPUs regardless of strategy.
Exercise 4 (hard): Communication cost in 3D parallelism
Estimate per-step communication cost for a 3D-parallel training setup. Inputs: model size, DP-rank, TP-rank, PP-rank, network bandwidth (TB/s). Outputs: communication time per step.
Hint
Each parallelism dimension has its own communication pattern:
- DP all-reduce: once per step; volume ≈ bytes (per GPU), via Ring all-reduce on DP ranks: for large (independent of DP-rank, per Section 5). Under 3D parallelism each DP rank only holds parameters to begin with, so the simplified scaffold below, which skips that division, overcounts DP communication by a factor of TP-rank and should be read as an upper bound.
- TP all-reduce: per layer; volume ≈ bytes per layer. Activation size depends on hidden dim, batch size, seq len.
- PP peer-to-peer: at stage boundaries; volume ≈ bytes per micro-batch boundary.
- FSDP all-gather + reduce-scatter: per layer; volume ≈ bytes total (similar to DP but communicated piecewise).
For this exercise, use a simplified model: just account for DP and TP costs (PP costs are typically smaller). The communication time is total_volume / bandwidth.
Solution
Sum the DP gradient all-reduce volume and the per-layer TP activation all-reduce volume, then divide by bandwidth:
def communication_time_per_step(
model_params, dp_rank, tp_rank, num_layers,
seq_len, batch_size, d_model, bandwidth_tb_s,
):
bandwidth_bytes_s = bandwidth_tb_s * 1e12
dp_volume = 2 * (2 * model_params) # FP16 grads, all-reduce factor 2
tp_per_layer = 2 * (2 * batch_size * seq_len * d_model) # FP16 activations
tp_volume = num_layers * tp_per_layer
total_volume = dp_volume + tp_volume
return total_volume / bandwidth_bytes_sFor the 70B-class config (80 layers, seq_len 2048, batch 4, d_model 8192): NVLink (0.6 TB/s) gives 502.46 ms/step, InfiniBand (0.025 TB/s) gives 12058.99 ms/step — a 24x slowdown from the bandwidth drop alone. This is why tensor parallelism, which needs an all-reduce every layer, stays confined to the NVLink domain within a node, while DP’s once-per-step all-reduce can tolerate the slower cross-node InfiniBand links.
Note this scaffold’s dp_volume uses the full model_params rather than model_params / tp_rank, so it’s an upper bound on DP communication (see the hint) — the qualitative conclusion (NVLink >> InfiniBand for TP-heavy setups) holds either way.
The full pre-training story is Chapters 7 through 10: data, training loop, scaling and parallelism, infrastructure. After this chapter, the reader has the conceptual core. After Chapter 10, the reader has the practical depth. After both, the tutorial moves on to post-training (supervised fine-tuning, alignment) in Chapter 13 and onward, and inference (KV caching, quantization, decoding) in Chapter 17 and onward.