Navigation
🏋️ Training Systems· 12 min read

ZeRO Stage 1/2/3

Partitioning optimizer states, gradients, and parameters across data-parallel ranks — how ZeRO eliminates the memory wall without changing model semantics.

zerodeepspeedmemory-optimizationshardingdistributed

ZeRO (Zero Redundancy Optimizer) is the single most important memory optimization for training. It eliminates redundant memory across data-parallel ranks by sharding optimizer states, gradients, and parameters — without changing model semantics or convergence.


1. The Redundancy Problem

In standard data parallelism, every GPU holds a complete copy of everything:

ComponentPer GPU8 GPUs totalRedundancy
Parameters (BF16)14 GB112 GB8×
Gradients (BF16)14 GB112 GB8×
Optimizer (FP32)84 GB672 GB8×
Total112 GB896 GB8×

For a 7B model on 8 GPUs: 896 GB of aggregate memory, but only 112 GB of unique data. 784 GB is pure waste.


ZeRO Stages

2. ZeRO Stage 1: Shard Optimizer States

What’s sharded: Optimizer states (mm, vv, master weights) — each GPU holds 1/N of the optimizer.

Communication added: AllGather the updated parameters after each optimizer step.

Before (standard DP)After (ZeRO-1)
Per GPU: 2Ψ + 2Ψ + 12Ψ = 16ΨPer GPU: 2Ψ + 2Ψ + 12Ψ/N
7B, 8 GPUs: 112 GB per GPU7B, 8 GPUs: 14 + 14 + 10.5 = 38.5 GB

Memory savings: 112 → 38.5 GB per GPU (2.9× reduction). Extra communication: One AllGather of parameters per step (~14 GB).

Why Optimizer First?

Optimizer states are 75% of training memory but are only needed during the optimizer step (once per training step). During forward and backward, they’re idle. Sharding them is high-ROI with minimal communication cost.

Worked Example: 13B Model on 8 GPUs

Parameters: 13B. Static memory = 16 \ 13B = 208 GB.

ComponentStage 0 (DDP)Stage 1Stage 2Stage 3
Params (BF16)26 GB26 GB26 GB26/8 = 3.25 GB
Grads (BF16)26 GB26 GB26/8 = 3.25 GB3.25 GB
Optimizer (FP32)156 GB156/8 = 19.5 GB19.5 GB19.5 GB
Per GPU208 GB71.5 GB48.75 GB26 GB
Fits on 80 GB?NoYes (tight)YesYes (plenty)
  • Stage 0: 208 GB per GPU — doesn’t fit. Need at least 3 GPUs just for static memory.
  • Stage 1: 71.5 GB — fits on 80 GB, with 8.5 GB left for activations.
  • Stage 2: 48.75 GB — comfortable, room for larger batch size.
  • Stage 3: 26 GB — maximum flexibility for batch size and sequence length.

3. ZeRO Stage 2: + Shard Gradients

What’s sharded: Optimizer states AND gradients.

Each GPU accumulates gradients only for its assigned parameter partition. After backward, a ReduceScatter (instead of AllReduce) distributes the averaged gradient shards.

ZeRO-1ZeRO-2
Per GPU2Ψ + 2Ψ + 12Ψ/N2Ψ + 2Ψ/N + 12Ψ/N
7B, 8 GPUs38.5 GB14 + 1.75 + 10.5 = 26.25 GB

Communication: Same as standard DP (ReduceScatter + AllGather ≈ AllReduce). No extra communication vs ZeRO-1!


4. ZeRO Stage 3: + Shard Parameters

What’s sharded: Everything — optimizer states, gradients, AND parameters.

Each GPU holds only 1/N of the parameters. Before each layer’s forward/backward, the full parameters are gathered (AllGather), used, and discarded.

ZeRO-2ZeRO-3
Per GPU2Ψ + 2Ψ/N + 12Ψ/N2Ψ/N + 2Ψ/N + 12Ψ/N = 16Ψ/N
7B, 8 GPUs26.25 GB112/8 = 14 GB

Communication: AllGather parameters before each layer (forward AND backward) + ReduceScatter gradients. This is 1.5× the communication of standard DP.

Breaking it down:

  • DDP (standard DP): AllReduce = 2Ψ bytes per step
  • ZeRO-3: Forward AllGather (Ψ) + Backward AllGather (Ψ) + ReduceScatter (Ψ) = 3Ψ bytes per step
  • Ratio: 3Ψ / 2Ψ = 1.5×

The ZeRO Stages Summary

StageShardsMemory per GPUCommunication vs DPBest for
0 (DP)Nothing16Ψ1× (AllReduce)Small models that fit
1Optimizer4Ψ + 12Ψ/N~1×Most training (low overhead)
2+ Gradients2Ψ + 14Ψ/N1×Same comm as DP, less memory
3+ Parameters16Ψ/N~1.5×Large models that don’t fit otherwise

5. DeepSpeed ZeRO Configuration

# ZeRO Stage 1 (most common)
ds_config = {
    "zero_optimization": {
        "stage": 1,
    },
    "bf16": {"enabled": True},
    "optimizer": {
        "type": "AdamW",
        "params": {"lr": 1e-4, "weight_decay": 0.01}
    }
}

# ZeRO Stage 2
ds_config["zero_optimization"] = {
    "stage": 2,
    "allgather_bucket_size": 5e8,       # bucket size for AllGather
    "reduce_scatter": True,              # use ReduceScatter (always True)
    "reduce_bucket_size": 5e8,
}

# ZeRO Stage 3
ds_config["zero_optimization"] = {
    "stage": 3,
    "param_persistence_threshold": 1e5,  # params < this stay replicated
    "prefetch_bucket_size": 5e7,         # prefetch next layer's params
    "sub_group_size": 1e9,               # partition granularity
}

Stage Selection Guide

ScenarioRecommendedWhy
Model fits with standard DPZeRO-0 or 1Minimal overhead
OOM with standard DP, fits with optimizer shardingZeRO-1Best memory/comm tradeoff
Still OOM, or want larger batch sizeZeRO-2Same comm as DP, less memory
Model doesn’t fit even with sharded optimizerZeRO-3Only option, 3× comm cost
Extremely large model (>100B per GPU)ZeRO-3 + offloadingSee CPU & NVMe Offloading

6. ZeRO vs Tensor Parallelism

Both reduce per-GPU memory, but they work differently:

ZeRO-3Tensor Parallelism
What’s splitFull copies, gathered on-demandActual matrix dimensions
CommunicationAllGather per layer (parameter size)AllReduce per layer (activation size)
Compute efficiencyEach GPU does full layer computeEach GPU does partial layer compute
Network requirementCan use InfiniBand (lower BW)Needs NVLink (high BW, low latency)
Max practical degreeHundreds of GPUs8 GPUs (NVLink group)

In practice: Use TP within a node (NVLink) and ZeRO across nodes (InfiniBand). They’re complementary, not competing.

For PyTorch’s native ZeRO-3 implementation, see FSDP.


Self-Check

  • Calculate per-GPU memory for each ZeRO stage for a 13B model on 16 GPUs
  • Explain why sharding optimizer states first is highest-ROI
  • Compare communication overhead of ZeRO stages 1, 2, 3 vs standard DP
  • Choose the right ZeRO stage for a given model/cluster configuration
  • Explain why ZeRO-3 and TP are complementary (not competing)

References