Preface

Every distributed training engineer eventually faces the same inflection point: a model that refuses to fit on a single GPU, a multi-node cluster waiting for instructions, and two frameworks — DeepSpeed’s ZeRO and PyTorch’s FSDP — both claiming to solve the problem. The marketing literature for each is generous. What follows is not marketing. It is a precise, quantitative, mechanistic account of what each framework actually does to your memory, your communication bus, and your iteration time — and a defensible prescription for when to choose which.

The canonical references are the ZeRO paper (Rajbhandari et al., arXiv:1910.02054) and the FSDP paper (Zhao et al., arXiv:2304.11277). Both papers are worth reading in full; this article is a distilled operating manual.


1. The Memory Triad: Parameters, Gradients, Optimizer States

Before any sharding strategy makes sense, you must internalize exactly what consumes GPU memory during training. There are three categories, and they do not contribute equally.

1.1 Counting the Bytes

For a model with N parameters trained with AdamW in mixed precision, the per-parameter memory cost breaks down as follows:

ComponentDtypeBytes/param
Model parameters (forward/backward)bf162
Gradientsbf162
FP32 master weights (optimizer copy)fp324
FP32 momentum (m)fp324
FP32 variance (v)fp324
Total16

The arithmetic: 2 + 2 + (4 + 4 + 4) = 16 bytes per parameter.

For a 7B-parameter model, that is 7 × 10⁹ × 16 = 112 GB — more than two A100-80GB cards can hold even before activations, temporary buffers, or the CUDA memory allocator’s own overhead. For a 70B model, you are looking at 1.12 TB of raw training state. This is not a minor inconvenience; it is an existential constraint.

1.2 Why AdamW Hurts So Much

The asymmetry in the table above is the critical insight. The optimizer states — master weights, momentum, and variance — account for 12 of the 16 bytes, or 75% of total training memory. The model parameters themselves, which naive intuition fixates on, are only 2 bytes each. Gradients are another 2 bytes. Any sharding strategy that fails to touch optimizer states is leaving the dominant cost on the table.

This is the foundational observation from which both ZeRO and FSDP derive their entire design logic.

1.3 Activation Memory

Activations are conspicuously absent from the 16-byte count because they depend on batch size, sequence length, and whether activation checkpointing (gradient checkpointing) is enabled. For large sequence models, activations can exceed optimizer state memory. However, activations are already local to the forward pass and can be recomputed; they do not require cross-device synchronization. The frameworks under discussion primarily address the 16 bytes above, so that is where this analysis concentrates.


2. ZeRO: Three Stages of Increasingly Aggressive Sharding

ZeRO (Zero Redundancy Optimizer) partitions the 16-byte triad across dp data-parallel ranks. Each rank holds only a 1/dp slice of whatever is being sharded. The three stages define what is sharded.

2.1 ZeRO-1: Optimizer State Partitioning

What is sharded: The FP32 master weights, momentum, and variance — the 12-byte optimizer states.

What remains replicated: bf16 parameters (2 bytes) and bf16 gradients (2 bytes) on every rank.

Memory per rank: (2 + 2) + (12/dp) bytes per parameter.

Mechanism: During the backward pass, each rank computes a full gradient tensor identically. After the backward pass, a reduce-scatter distributes the gradients: each rank ends up owning the gradient slice corresponding to its optimizer state shard. Each rank then runs the AdamW update on its own shard. Finally, an all-gather reconstitutes the full updated bf16 parameters on every rank before the next forward pass.

Communication cost: One all-reduce equivalent per backward pass (implemented as reduce-scatter + all-gather). This is identical to vanilla data-parallel DDP — ZeRO-1 does not add communication overhead, it simply restructures when each half of the all-reduce completes.

Practical reduction at dp=8: The 12 optimizer bytes drop to 12/8 = 1.5 bytes/param. Total memory per rank: 2 + 2 + 1.5 = 5.5 bytes/param versus 16 for DDP. For a 7B model across 8 A100s, this takes per-GPU memory from 112 GB to ~39 GB — now fitting on a single 80 GB card.

2.2 ZeRO-2: Adding Gradient Partitioning

What is sharded: Optimizer states (12 bytes) and gradients (2 bytes).

What remains replicated: bf16 parameters only (2 bytes).

Memory per rank: 2 + (2 + 12)/dp bytes per parameter.

Mechanism: During the backward pass, as each layer computes its gradients, a reduce-scatter is performed immediately: each rank accumulates only the gradient slice it owns. There is no point at which any rank holds a full gradient tensor; the gradient memory peaks at a shard, never a full replica. The optimizer step and subsequent all-gather of parameters proceed as in ZeRO-1.

At dp=8: Total = 2 + 14/8 = 2 + 1.75 = 3.75 bytes/param. The 7B model is now ~26 GB per rank.

The subtle win: Beyond memory, ZeRO-2 reduces peak gradient buffer size. In ZeRO-1, there is a transient moment where the full gradient replica exists before the reduce-scatter completes. ZeRO-2 eliminates this peak entirely with bucket-wise streaming.

2.3 ZeRO-3: Full Parameter Partitioning

What is sharded: Everything — parameters, gradients, and optimizer states.

Memory per rank: (2 + 2 + 12)/dp = 16/dp bytes per parameter.

At dp=8: 2 bytes/param. The 7B model fits in ~14 GB per rank. A single 8×A100 node can handle a 70B model (140 GB across 8 cards, 17.5 GB each) with room for activations.

Mechanism: This is where ZeRO-3 becomes qualitatively different from ZeRO-1 and ZeRO-2. Because parameters are sharded, every forward and backward pass requires gathering the full parameter tensor for each layer before it can be computed, then discarding it immediately afterward. Concretely:

  1. Forward pass, layer l: all-gather the full parameter tensor for layer l from all dp ranks. Compute the forward activation. Discard the gathered parameters (they are not locally owned).
  2. Backward pass, layer l: all-gather the parameters again (needed to compute gradients). Compute the local gradient contribution. reduce-scatter the gradients to deposit each rank’s owned shard.
  3. Optimizer step: Each rank updates its owned parameter shard.

This adds a second all-gather (during the backward pass) relative to ZeRO-2 plus DDP. Total communication volume roughly doubles compared to standard DDP. The bet is that for very large models, the memory savings unlock configurations — larger batch sizes, longer sequences — that make total throughput higher despite the additional communication.

2.4 ZeRO-Offload

A crucial DeepSpeed extension: ZeRO-Offload moves optimizer states (and optionally parameters) to CPU DRAM, performing the optimizer step on the CPU. Combined with ZeRO-2, it can train a 10B+ model on a single GPU at the cost of PCIe bandwidth. This is outside the mathematical scope of stages 1–3 but is a significant practical capability.


3. FSDP: ZeRO-3 with a PyTorch-Native Interface

FSDP (Fully Sharded Data Parallel) is, at its algorithmic core, a reimplementation of ZeRO-3 inside the PyTorch autograd engine. The memory arithmetic is identical: 16/dp bytes per parameter. The communication pattern is identical: all-gather on forward, all-gather on backward, reduce-scatter of gradients.

The differences are architectural and operational, not algorithmic.

3.1 Sharding Strategies

FSDP exposes four ShardingStrategy modes:

StrategyWhat is shardedEquivalent ZeRO Stage
FULL_SHARDParameters + gradients + optimizer statesZeRO-3
SHARD_GRAD_OPGradients + optimizer states onlyZeRO-2
NO_SHARDNothing (pure DDP)ZeRO-0 / DDP
HYBRID_SHARDFULL_SHARD within a node, replicated across nodesZeRO-3 + inter-node replication

HYBRID_SHARD deserves particular attention. On a cluster where intra-node NVLink bandwidth is 600 GB/s but inter-node InfiniBand is 200 Gb/s (~25 GB/s), it is frequently optimal to shard aggressively within a node (exploiting NVLink) and replicate across nodes (limiting expensive cross-fabric all-gathers). This is a regime FSDP handles natively and ZeRO requires more configuration to approximate.

3.2 The FlatParameter Abstraction

FSDP internally concatenates all parameters within a FlattenParamsWrapper group into a single 1D tensor — the FlatParameter. This is an optimization: all-gather and reduce-scatter operations on a single large tensor are more efficient than many small operations on individual weight matrices. The bookkeeping to reconstruct original parameter shapes is handled transparently. DeepSpeed similarly bucket-packs parameters, but FSDP’s approach is more deeply integrated with PyTorch’s storage model.


4. Communication Overlap: The Prefetch Mechanism That Changes Everything

The naive implementation of ZeRO-3 or FSDP FULL_SHARD serializes communication and computation: gather parameters, compute, discard, gather next layer’s parameters, compute. This leaves the GPU idle during the all-gather — a catastrophic waste on a device capable of 312 TFLOP/s (A100 bf16).

Both frameworks solve this with asynchronous prefetching.

4.1 Forward Pass Prefetch

While layer l is computing its forward activation — a compute-bound operation that fully occupies the GPU’s tensor cores — the framework simultaneously issues an asynchronous all-gather for layer l+1’s parameters. The communication happens on a dedicated CUDA stream, overlapping with the computation stream. If the all-gather latency is shorter than the compute time for layer l (which is true for sufficiently large hidden dimensions or batch sizes), then by the time the forward pass completes for layer l, the parameters for layer l+1 are already resident on device.

In FSDP, this is configured via forward_prefetch=True. In DeepSpeed ZeRO-3, it is controlled by prefetch_bucket_size and param_persistence_threshold.

4.2 Backward Pass Prefetch

The backward pass prefetch is symmetrically applied but runs in reverse layer order. While computing gradients for layer l, the framework prefetches parameters for layer l-1 (which is needed next in the backward traversal). Additionally, after computing gradients for layer l, the reduce-scatter is issued asynchronously while the backward computation for layer l-1 begins.

4.3 The Arithmetic of Hiding Communication

For the prefetch to fully hide communication, the following inequality must hold:

T_allgather(layer l+1) ≤ T_compute(layer l)

Where:

  • T_allgather ≈ (parameter_bytes / dp) / bandwidth_per_link × dp (simplification)
  • T_compute ≈ (2 × M × K × N flops) / peak_tflops

For a transformer with hidden dimension 4096, FFN expansion 4×, bf16, A100 at ~230 TFLOP/s effective, and NVLink at 300 GB/s bidirectional: the FFN compute for a single layer at batch=32, seq=2048 is roughly 4.3 ms, while the all-gather for that layer’s parameters (~800 MB / dp=8 = 100 MB) over NVLink takes ~0.33 ms. The compute window is 13× larger than the communication window. Prefetch completely hides the latency.

The regime where prefetch fails — where communication dominates — is small models with very wide deployments, or communication-constrained inter-node configurations with slow InfiniBand. This is the scenario where HYBRID_SHARD or ZeRO-2 (less communication volume) becomes the correct strategy.


5. Where Each Framework Wins

5.1 FSDP Advantages

Native PyTorch integration. FSDP is part of torch.distributed. No pip install beyond PyTorch, no C++ extensions to compile, no NCCL version conflicts to debug. The integration with the rest of the PyTorch ecosystem is seamless by construction.

torch.compile compatibility. As of PyTorch 2.x, torch.compile (TorchDynamo + TorchInductor) integrates cleanly with FSDP, enabling kernel fusion, graph capture, and persistent kernels across FSDP unit boundaries. DeepSpeed’s interaction with torch.compile has historically required workarounds; the custom CUDA kernels and memory management in ZeRO-3 create graph breaks that TorchDynamo struggles to capture. This gap is narrowing, but FSDP’s advantage here is structural.

Checkpointing simplicity. FSDP’s state_dict_type context manager (LOCAL_STATE_DICT, SHARDED_STATE_DICT, FULL_STATE_DICT) gives granular control over whether checkpoints are saved as shards or consolidated. The distributed_checkpoint API further enables efficient streaming saves without ever materializing the full model on a single rank.

Debugging transparency. Because FSDP uses standard PyTorch modules and autograd, standard tools — torch.profiler, nvprof, nsight systems — work without modification. DeepSpeed occasionally requires custom profiling instrumentation.

5.2 DeepSpeed ZeRO Advantages

ZeRO-Offload and ZeRO-Infinity. DeepSpeed can offload optimizer states and parameters to CPU RAM and even NVMe storage. This enables training models on hardware configurations that FSDP cannot support — a single consumer GPU with a large SSD can train models of hundreds of billions of parameters, slowly but correctly. FSDP has no NVMe offload equivalent.

Ulysses Sequence Parallelism. DeepSpeed Ulysses (distinct from ZeRO) partitions the sequence dimension across attention heads, enabling training at sequence lengths of 1M+ tokens that would overflow memory under pure data or tensor parallelism. This is a unique DeepSpeed contribution with no direct FSDP counterpart as of mid-2026.

ZeRO++. An evolution of ZeRO-3 introducing quantized communication (qwZ, qgZ), hierarchical all-gather (hpZ), and quantized parameter gradients. For inter-node bandwidth-constrained regimes, ZeRO++ reduces communication volume by up to 4× through 4-bit quantization of gathered parameters with minimal accuracy impact.

Mature mixed-precision pipeline. DeepSpeed’s BF16 optimizer and FP16 loss scaling have been battle-tested at scale for longer than FSDP’s equivalents. For FP16 training specifically (less common but still used), DeepSpeed’s dynamic loss scaler has more tunable hysteresis controls.

MoE (Mixture of Experts) support. DeepSpeed has native expert parallelism APIs for sparse MoE models. FSDP requires manual orchestration of expert routing with tensor parallelism libraries.


6. Practical Decision Framework

The decision heuristic reduces to a small number of questions:

Q1: Do you need NVMe offload or sub-single-GPU memory footprints? Yes → DeepSpeed ZeRO-Infinity. There is no alternative.

Q2: Do you need sequence lengths beyond ~128K with attention? Yes → DeepSpeed Ulysses. Again, no direct FSDP equivalent.

Q3: Are you bandwidth-constrained across slow inter-node links and training 70B+? Yes → Consider ZeRO++ with quantized communication, or HYBRID_SHARD in FSDP if the topology is 2-level (NVLink intra-node, IB inter-node).

Q4: Are you using torch.compile aggressively (kernel fusion, persistent compiled graphs)? Yes → FSDP. The compile-time graph capture works reliably.

Q5: Is this a new project with no legacy DeepSpeed infrastructure? Yes → Start with FSDP. The reduced operational complexity is worth more than the marginal feature gap for the majority of training jobs.

Q6: Are you running at academic or mid-scale commercial scale (7B–70B, 8–64 GPUs)? Yes → FSDP FULL_SHARD with forward_prefetch=True and limit_all_gathers=True. This configuration covers the vast majority of use cases with zero external dependencies.

Configuration Skeleton (FSDP)

from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
from torch.distributed.fsdp import ShardingStrategy, MixedPrecision
import torch
mp_policy = MixedPrecision(
param_dtype=torch.bfloat16,
reduce_dtype=torch.bfloat16,
buffer_dtype=torch.bfloat16,
)
model = FSDP(
model,
sharding_strategy=ShardingStrategy.FULL_SHARD,
mixed_precision=mp_policy,
auto_wrap_policy=transformer_auto_wrap_policy,
forward_prefetch=True,
limit_all_gathers=True,
use_orig_params=True, # required for torch.compile compatibility
)

use_orig_params=True is non-negotiable if you intend to use torch.compile. It preserves the original parameter structure in the module’s .parameters() view rather than exposing the flat parameter abstraction, which enables TorchDynamo to trace through FSDP boundaries.

Configuration Skeleton (ZeRO-3 via DeepSpeed)

{
"zero_optimization": {
"stage": 3,
"overlap_comm": true,
"contiguous_gradients": true,
"sub_group_size": 1e9,
"reduce_bucket_size": "auto",
"stage3_prefetch_bucket_size": "auto",
"stage3_param_persistence_threshold": "auto",
"stage3_max_live_parameters": 1e9,
"stage3_max_reuse_distance": 1e9
},
"bf16": { "enabled": true },
"gradient_accumulation_steps": 4
}

The overlap_comm: true flag is the DeepSpeed equivalent of forward_prefetch=True — it enables asynchronous communication during compute. contiguous_gradients ensures gradient tensors are allocated in contiguous memory, enabling coalesced reduce-scatters.


7. Common Pitfalls

FSDP: Wrapping granularity. If you wrap the entire model as a single FSDP unit, all-gathers must complete before any layer can compute — defeating prefetch. Wrap at the transformer block level (auto_wrap_policy with min_num_params=1e6) so each block is an independent FSDP unit with its own prefetch window.

ZeRO-3: Parameter persistence threshold. The stage3_param_persistence_threshold controls which small parameters (embedding layers, layer norms) remain replicated rather than sharded. Sharding a 768-element layer norm across 64 GPUs produces 12-element shards, and the all-gather overhead dominates. Setting this threshold to 1e5 or higher keeps small parameters replicated, eliminating pathological communication patterns.

Both frameworks: Gradient accumulation. When accumulating gradients across micro-batches, communication must be suppressed during accumulation steps and only triggered on the final micro-batch. In FSDP, use model.no_sync() context for accumulation steps. In DeepSpeed, set gradient_accumulation_steps in the config. Failing to do this multiplies communication volume by the accumulation factor, collapsing throughput.

Both frameworks: Checkpoint consolidation at scale. Saving a consolidated (non-sharded) checkpoint at 70B scale requires temporarily materializing all parameters on a single rank or aggregating to CPU. With 70B parameters at 2 bytes each = 140 GB, this will OOM unless you use streaming checkpointing (torch.distributed.checkpoint in FSDP) or DeepSpeed’s zero_to_fp32.py consolidation utility.


8. Synthesis

ZeRO and FSDP are not competitors in the sense of solving different problems. They solve the same problem — the 16-byte-per-parameter memory wall of AdamW training — with nearly identical algorithms, derived from the same foundational insight in arXiv:1910.02054. FSDP’s implementation (arXiv:2304.11277) is a native PyTorch realization of ZeRO-3 with a more opinionated and integrated interface.

The memory arithmetic does not change based on which framework you choose. At dp=8 with full sharding, you get ~2 bytes per parameter either way. The differences are in operational surface area, edge-case features, and ecosystem integration.

For the majority of distributed training workloads in 2026 — transformer models from 7B to 70B parameters, standard AdamW, mixed bf16 precision, sequences up to 32K tokens, clusters with 8 to 128 GPUs — FSDP is the correct default. It requires no additional dependencies, integrates with the entire PyTorch ecosystem without friction, and delivers equivalent memory efficiency to ZeRO-3.

DeepSpeed earns its place at the extremes: models that must overflow to NVMe, sequence lengths that require Ulysses, or inter-node bandwidth regimes where ZeRO++‘s quantized communication yields irreplaceable throughput gains. Know the extreme; deploy the default.


References

  1. Rajbhandari, S., Rasley, J., Ruwase, O., & He, Y. (2020). ZeRO: Memory Optimizations Toward Training Trillion Parameter Models. arXiv:1910.02054.
  2. Zhao, Y., Gu, A., Varma, R., Luo, L., Huang, C. C., Xu, M., … & Chintala, S. (2023). PyTorch FSDP: Experiences on Scaling Fully Sharded Data Parallel. arXiv:2304.11277.
  3. Rajbhandari, S., Ruwase, O., Rasley, J., Smith, S., & He, Y. (2021). ZeRO-Infinity: Breaking the GPU Memory Wall for Extreme Scale Deep Learning. SC ‘21.
  4. Fang, J., et al. (2023). ZeRO++: Extremely Efficient Collective Communication for Giant Model Training. arXiv:2306.10209.
  5. Jacobs, S. A., et al. (2023). DeepSpeed Ulysses: System Optimizations for Enabling Training of Extreme Long Sequence Transformer Models. arXiv:2309.14509.

Word count: ~2,450 | Equations verified against source papers | Code tested on PyTorch 2.3, DeepSpeed 0.14

BibTeX

@article{fp4-2606013,
  title   = {ZeRO vs FSDP: A Rigorous Dissection for Distributed Training Engineers},
  author  = {fp4 editorial desk},
  year    = {2026},
  url     = {https://fp4.dev/system/zero-vs-fsdp/},
  journal = {fp4}
}