ZeRO Optimization › ZeRO Stage 3: Parameter Partitioning

Michael BrenndoerferJanuary 21, 20266 min read
Language AI HandbookAll chapters
Part 31 of 67Training Infrastructure
Chapter 225ZeRO Optimization › ZeRO Stage 3: Parameter Partitioning
Reading section 5 of 14 · ZeRO Stage 3: Parameter Partitioning · 6 minSection 5 of 14 · 6 min

Book contents

Section 5 of 14 · 6 min read

ZeRO Optimization › ZeRO Stage 3: Parameter PartitioningOne section · Sections in this chapter5 / 14 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

ZeRO Stage 3 completes the partitioning by distributing the parameters themselves across the GPUs. In Stages 1 and 2, each GPU still holds a full 16-bit replica of all parameters for the forward and backward passes. This 2Ψ2\Psi bytes is the last significant replicated component. Stage 3 eliminates it, reducing every component of model state to 1/Nd1/N_d per GPU.

On-Demand Parameter Fetching

The challenge with partitioning parameters is that the forward and backward passes need parameters for every layer. If GPU ii only permanently stores a 1/Nd1/N_d shard, how does it execute the forward pass through layers whose parameters live on other GPUs?

Stage 3 exploits the sequential structure of the forward pass: a transformer processes layers one at a time, and at any given moment, only the parameters for the current layer are actively needed. Stage 3 uses this property to fetch parameters on demand:

  1. Each GPU permanently owns only 1/Nd1/N_d of the total parameters, distributed across all layers.
  2. Before layer ll's computation begins, all GPUs participate in an All-Gather to collect the complete parameter set for layer ll from whichever GPUs own those shards.
  3. The computation for layer ll proceeds normally, using the fully gathered parameters.
  4. Once layer ll's contribution to the activations (forward) or gradients (backward) has been computed, the gathered parameters are discarded. The GPU reverts to holding only its permanent shard.

The backward pass mirrors this: backpropagation traverses layers in reverse, fetching each layer's parameters via All-Gather before computing gradients, then immediately discarding the fetched parameters. A Reduce-Scatter ships the local gradient contribution to the GPU that owns each parameter shard, which then performs the optimizer update.

This "just-in-time" parameter fetching means that at any moment, a GPU holds:

  1. Its permanently-owned parameter shard, which is 1/Nd1/N_d of all parameters
  2. The parameters for the layer currently being computed, fetched transiently via All-Gather
  3. Its permanently-owned gradient shard and optimizer state shard

The transient parameter buffer for the current layer is bounded by the size of the largest single layer, which is typically much smaller than Ψ\Psi. For a transformer model, the largest layer (usually the MLP FFN) has roughly 4×dmodel24 \times d_\text{model}^2 parameters. For a 7B model with dmodel=4096d_\text{model} = 4096, that is about 4×40962=67 million4 \times 4096^2 = 67 \text{ million} parameters, or 0.13 GB at fp16, which is negligible compared to the savings.

Memory Per GPU Under Stage 3

When all components are partitioned, the per-GPU memory for model state becomes:

MStage 3=16ΨNdM_{\text{Stage 3}} = \frac{16\Psi}{N_d}

where 16Ψ16\Psi is the total model state (fp16 params + fp16 grads + fp32 master + fp32 Adam moments). Every component scales with NdN_d. This is a perfect linear reduction in per-GPU memory with the number of GPUs.

For a 7B model with 64 GPUs:

16×7×109×1 byte64=112 GB64=1.75 GB\frac{16 \times 7 \times 10^9 \times 1 \text{ byte}}{64} = \frac{112 \text{ GB}}{64} = 1.75 \text{ GB}

Stage 3 makes it theoretically possible to train arbitrarily large models given enough GPUs, since the memory per GPU decreases linearly with NdN_d. Doubling the GPU count halves the per-GPU model state memory. This is a fundamentally different scaling property than Stages 1 and 2, where the 2Ψ2\Psi parameter replica creates a floor that does not decrease with more GPUs.

The Communication Cost

Stage 3's memory efficiency comes at a communication price. The total communication volume relative to standard data parallelism is:

  • Baseline (Standard DP): 1 All-Reduce equivalent, total volume 2Ψ2\Psi bytes per step
  • Stage 1: 1 All-Reduce equivalent, total volume 2Ψ2\Psi bytes (Reduce-Scatter + All-Gather for gradients/parameters)
  • Stage 2: 1 All-Reduce equivalent, total volume 2Ψ2\Psi bytes (same operations, gradient memory freed earlier)
  • Stage 3: 3 All-Reduce equivalents, total volume 6Ψ6\Psi bytes per step

Stage 3's forward pass requires one All-Gather to fetch parameters layer by layer (total volume 2Ψ2\Psi). The backward pass requires a second All-Gather for parameters (another 2Ψ2\Psi) plus a Reduce-Scatter for gradients (2Ψ2\Psi). The full step therefore transfers 6Ψ6\Psi bytes versus 2Ψ2\Psi for the baseline. This 3x communication overhead is manageable on high-bandwidth interconnects such as NVLink (600 GB/s bidirectional on A100 nodes) or InfiniBand (200-400 Gb/s between nodes), but can become a significant bottleneck on slower networks.

The communication-compute overlap is critical to Stage 3's practical performance. Modern implementations prefetch the parameters for the next layer while computing the current layer, so the All-Gather for layer l+1l+1 proceeds in parallel with the computation for layer ll. If the network is fast enough to transfer a layer's parameters before the GPU finishes computing with the previous layer, the communication overhead is effectively hidden. This prefetching only works for sequential architectures where the execution order is predictable at scheduling time.

In[8]:
Code
def communication_volume(params_billions, stage, num_gpus):
    """
    Estimated total communication volume per training step in gigabytes.
    Stage 1 and 2 match standard DP all-reduce volume.
    Stage 3 triples it due to forward/backward parameter gathers.
    """
    params = params_billions * 1e9
    bytes_per_param = 2  # fp16

    if stage in (1, 2):
        # Equivalent to one all-reduce: 2 * params * bytes_per_param
        volume = 2 * params * bytes_per_param
    else:
        # Stage 3: forward gather + backward gather + backward reduce-scatter
        volume = 3 * 2 * params * bytes_per_param

    return volume / 1e9


stages = [1, 2, 3]
gpu_counts_comm = [8, 16, 32, 64]
params_7b = 7
comm_table = {
    stage: {
        nd: communication_volume(params_7b, stage, nd) for nd in gpu_counts_comm
    }
    for stage in stages
}
Out[9]:
Console
Communication volume per step (GB) for 7B parameters
  Stage       8GPUs      16GPUs      32GPUs      64GPUs
------------------------------------------------
Stage 1        28.0G      28.0G      28.0G      28.0G
Stage 2        28.0G      28.0G      28.0G      28.0G
Stage 3        84.0G      84.0G      84.0G      84.0G

Notice that the communication volume for Stages 1 and 2 is independent of the number of GPUs in this model, because the total data transferred in a ring-based all-reduce scales as 2×(N−1)/N×Ψ≈2Ψ2 \times (N-1)/N \times \Psi \approx 2\Psi for large NN. Stage 3's volume is also constant across GPU counts here because all three phases each transfer 2Ψ2\Psi bytes in total, regardless of how many GPUs share the work. The communication volume for Stage 3 matches standard data parallelism practitioners already accept, just tripled. This is why it requires fast interconnects to achieve good hardware utilization.