ZeRO Sharding
Data parallelism replicates the optimiser state N times for no reason. ZeRO shards it, then the gradients, then the parameters themselves.
Mixed-precision Adam holds, per parameter: an fp16 weight and gradient (2 + 2 bytes) plus fp32 weight, momentum and variance (12 bytes) — sixteen bytes where the model itself needs two. Replicating that across ranks wastes bytes.
Stages 1 and 2 keep the communication volume of plain data parallelism: a reduce-scatter of gradients plus an all-gather of updated weights is exactly one all-reduce in disguise. Stage 3 additionally all-gathers each layer’s parameters immediately before use and frees them after, adding one further of traffic per step.
import torch
from torch.distributed.fsdp import fully_shard, MixedPrecisionPolicy
policy = MixedPrecisionPolicy(
param_dtype=torch.bfloat16,
reduce_dtype=torch.float32, # reduce grads in fp32
)
# Shard each block separately so parameters are gathered
# per-block and freed again, not held for the whole model.
for block in model.blocks:
fully_shard(block, mp_policy=policy)
fully_shard(model, mp_policy=policy)Reducing gradients in bf16 to save bandwidth is a false economy at scale: the
reduction is a sum over ranks, and bf16 has eight mantissa bits. Keep
reduce_dtype at fp32. Wrapping only the top-level model rather than each block
is the other common mistake — it collapses stage 3 back into a single enormous
all-gather.