Original source
---
id: distributed-training
title: DDP, FSDP, ZeRO and Pipeline Parallel
code: DF
subtitle: Pick a parallelism, size the memory, wire the collectives and fix the hangs. PyTorch gloo on CPU.
hue: 280
sources: phases/10-llms-from-scratch/05-scaling-distributed, phases/10-llms-from-scratch/19-dualpipe-parallelism, phases/19-capstone-projects/48-distributed-fsdp-ddp, phases/19-capstone-projects/76-collective-ops-from-scratch, phases/19-capstone-projects/77-data-parallel-ddp, phases/19-capstone-projects/78-zero-parameter-sharding, phases/19-capstone-projects/79-pipeline-parallel
source_hash: a601a24bdbc9c6e0
---
## Four strategies side by side
type: comparison
span: 2
from: phases/10-llms-from-scratch/05-scaling-distributed/docs/en.md, phases/19-capstone-projects/48-distributed-fsdp-ddp/docs/en.md
| Factor | DDP | FSDP or ZeRO-3 | Pipeline | Tensor parallel |
|---|---|---|---|---|
| Splits | the batch | params, grads, optimizer | the model by layers | each weight matrix |
| Per-GPU memory | full copy, no drop | 1/N of each state | one group of layers | a slice of each layer |
| Traffic | all-reduce of gradients | all-gather per layer, reduce-scatter | activations at layer boundaries | all-reduce after each matmul |
| Link | overlaps with compute | more than plain DP | low bandwidth, across nodes | NVLink inside a node |
| Main cost | memory | gather every forward pass | pipeline bubbles | latency after every layer |
## Use each one when
type: concepts
from: phases/19-capstone-projects/48-distributed-fsdp-ddp/docs/en.md, phases/19-capstone-projects/78-zero-parameter-sharding/docs/en.md, phases/10-llms-from-scratch/05-scaling-distributed/docs/en.md, phases/10-llms-from-scratch/19-dualpipe-parallelism/docs/en.md
- **DDP**: the model fits on one device. The dataset does not.
- **FSDP**: the model does not fit on one device either.
- **ZeRO-1**: the default. Same bandwidth as DDP, memory drop linear in N.
- **ZeRO-2 or ZeRO-3**: the parameter shard memory is also a problem.
- **Tensor parallel**: inside a node. It needs fast inter-GPU links.
- **Pipeline**: across nodes. Bandwidth needs are low.
- **DualPipe**: EP-heavy MoE at multi-thousand GPU scale.
- **DualPipeV**: dense or EP-light models. One parameter copy.
## ZeRO stages
type: comparison
span: 2
from: phases/19-capstone-projects/78-zero-parameter-sharding/docs/en.md, phases/10-llms-from-scratch/05-scaling-distributed/docs/en.md
| Factor | DDP | ZeRO-1 | ZeRO-2 | ZeRO-3 |
|---|---|---|---|---|
| Sharded | nothing | optimiser state | optim + grads | optim + grads + params |
| Memory per rank | params + grads + optim | params + grads + optim/N | params + grads/N + optim/N | params/N + grads/N + optim/N |
| Comm per step | 1x allreduce | 1x reduce_scatter + 1x allgather | 1x reduce_scatter + 1x allgather | allgather and reduce_scatter per layer |
| Savings | none | ~4x reduction | ~8x reduction | ~Nx reduction |
| PyTorch FSDP name | not listed | not listed | `ShardingStrategy.SHARD_GRAD_OP` | `FULL_SHARD` |
## Memory numbers
type: concepts
from: phases/10-llms-from-scratch/05-scaling-distributed/docs/en.md, phases/19-capstone-projects/78-zero-parameter-sharding/docs/en.md
- **Adam states**: m and v in FP32. 70B x 4 bytes x 2 = 560GB.
- **Vanilla, mixed precision**: 16P bytes per rank.
- **ZeRO-1**: 4P + 12P/N bytes. At N=8 that is 5.5P, a 65% drop.
- **FSDP, 70B on 8 GPUs**: 105 GB per GPU. Still over 80GB.
- **Mixed precision, 7B**: 112 GB to 84 GB. Optimizer stays FP32.
- **Effective batch size**: per_gpu_batch_size x N.
## Pipeline schedules
type: comparison
from: phases/10-llms-from-scratch/19-dualpipe-parallelism/docs/en.md, phases/10-llms-from-scratch/19-dualpipe-parallelism/code/main.py
| Factor | 1F1B | Zero Bubble | DualPipe | DualPipeV |
|---|---|---|---|---|
| Param copies | 1 | 1 | 2 | 1 |
| Bubble vs micro-batches | grows | grows | constant | small growth |
| Overlap | minimal | partial | full | partial |
| Simulator | `bubble_1f1b` | `bubble_zero_bubble` | `bubble_dualpipe` | `bubble_dualpipev` |
## Collectives
type: comparison
from: phases/19-capstone-projects/76-collective-ops-from-scratch/docs/en.md
| Factor | Ring allreduce | Tree allreduce | Allgather | Reduce_scatter |
|---|---|---|---|---|
| Per-rank bytes | 2T(N-1)/N | T log2(N) | T(N-1)/N | T(N-1)/N |
| Steps | 2(N-1) | 2 log2(N) | N-1 | N-1 |
| Use when | large T | small T, high latency | ZeRO unshard | ZeRO gradient sharding |
| Message size | above ~1 MB | below ~1 MB | any | any |
## Ring allreduce on a queue mesh
type: architecture
from: phases/19-capstone-projects/76-collective-ops-from-scratch/docs/en.md, phases/19-capstone-projects/76-collective-ops-from-scratch/code/main.py
```arch
node | r0 | 1,1 | rank 0 | owns chunk 0 | process
node | r1 | 2,1 | rank 1 | owns chunk 1 | process
node | r2 | 2,2 | rank 2 | owns chunk 2 | process
node | r3 | 1,2 | rank 3 | owns chunk 3 | process
edge | r0 | r1 | queue 0 to 1
edge | r1 | r2 | queue 1 to 2
edge | r2 | r3 | queue 2 to 3
edge | r3 | r0 | queue 3 to 0
```
## DDP worker loop
type: annotated-code
lang: python
from: phases/19-capstone-projects/77-data-parallel-ddp/code/main.py
```python
dist.init_process_group(
backend="gloo",
init_method=f"file://{init_file}", # file rendezvous
rank=rank,
world_size=world_size,
)
torch.manual_seed(SEED) # same init on every rank
ddp = DistributedDataParallel(model, world_size=world_size) # broadcasts params
for step in range(steps):
offset = step * world_size * batch + rank * batch # own slice
optim.zero_grad(set_to_none=True)
loss.backward()
ddp.sync_grads() # all_reduce SUM, then div_ by world_size
optim.step()
```
## Collective calls
type: task-table
span: 2
from: phases/19-capstone-projects/48-distributed-fsdp-ddp/docs/en.md, phases/19-capstone-projects/77-data-parallel-ddp/code/main.py, phases/19-capstone-projects/78-zero-parameter-sharding/code/main.py, phases/19-capstone-projects/79-pipeline-parallel/code/main.py, phases/19-capstone-projects/77-data-parallel-ddp/docs/en.md
| Task | Code |
|---|---|
| Set the rendezvous port | `os.environ["MASTER_PORT"] = str(port)` |
| Start a CPU group | `dist.init_process_group(backend="gloo", rank=rank, world_size=world_size)` |
| Same params on all ranks | `dist.broadcast(p.data, src=0)` |
| Sum gradients | `dist.all_reduce(p.grad.data, op=dist.ReduceOp.SUM)` |
| Mean gradients | `p.grad.data.div_(world_size)` |
| ZeRO-1 gradient shard | `dist.reduce_scatter(local_grad, chunks, op=dist.ReduceOp.SUM)` |
| ZeRO-1 params back | `dist.all_gather(gathered, self.master_shard)` |
| GPipe bubble | `bubble_fraction(num_stages, num_microbatches)` |
## Run the lessons
type: command-table
from: phases/19-capstone-projects/48-distributed-fsdp-ddp/docs/en.md, phases/19-capstone-projects/48-distributed-fsdp-ddp/code/main.py, phases/19-capstone-projects/76-collective-ops-from-scratch/docs/en.md, phases/19-capstone-projects/77-data-parallel-ddp/docs/en.md, phases/19-capstone-projects/78-zero-parameter-sharding/docs/en.md, phases/19-capstone-projects/79-pipeline-parallel/docs/en.md
| Command | What it does |
|---|---|
| `python3 code/main.py` | Lesson 48: two gloo ranks, writes `outputs/ddp-demo.json` |
| `--world-size 4` | Lesson 48: four ranks. Param spread stays under 1e-3 |
| `--no-write` | Lesson 48: skip the JSON file |
| `python3 code/main.py` | Lesson 76: queue mesh against gloo, byte counter |
| `python3 code/main.py` | Lesson 77: single process against DDP on 4 ranks |
| `python3 code/main.py` | Lesson 78: loss and the ZeRO-1 memory table |
| `python3 code/main.py` | Lesson 79: Gantt chart and bubble percentage |
## Defaults in the code
type: defaults
from: phases/19-capstone-projects/48-distributed-fsdp-ddp/code/main.py, phases/19-capstone-projects/48-distributed-fsdp-ddp/docs/en.md, phases/19-capstone-projects/76-collective-ops-from-scratch/code/main.py, phases/19-capstone-projects/76-collective-ops-from-scratch/docs/en.md, phases/19-capstone-projects/79-pipeline-parallel/code/main.py
| Setting | Default | Note |
|---|---|---|
| `--world-size` | 2 | Lesson 48 ranks |
| `--backend` | `gloo` | Use `nccl` on GPUs |
| `--num-steps` | 6 | Lesson 48 training steps |
| `--batch-size` | 8 | Per rank |
| `RECV_TIMEOUT_S` | 30.0 | Queue mesh receive deadline |
| DDP bucket size | 25 MB | One allreduce per bucket |
| `NUM_STAGES` | 4 | Lesson 79 pipeline |
| `NUM_MICROBATCHES` | 8 | Lesson 79 pipeline |
## Troubleshooting
type: troubleshooting
from: phases/19-capstone-projects/48-distributed-fsdp-ddp/docs/en.md, phases/19-capstone-projects/77-data-parallel-ddp/docs/en.md, phases/19-capstone-projects/79-pipeline-parallel/docs/en.md, phases/10-llms-from-scratch/05-scaling-distributed/docs/en.md
| Symptom | Cause | Fix |
|---|---|---|
| Ranks diverge from step one | Each rank uses its own init | `dist.broadcast(tensor, src=0)` at construction |
| All ranks see the same batches | One shared seed for shuffling | `torch.manual_seed(seed + rank)` for shuffling |
| Allreduce deadlocks | Skipped params have no gradient | `find_unused_parameters=True` |
| Accumulation is slow | Allreduce on every microbatch | Wrap microbatches in `no_sync()` |
| LR breaks when N changes | SUM without the division | Divide by world_size |
| Pipeline hangs on the wire | Every stage sends before receive | Even ranks send first, odd ranks receive first |
| One stage gates every cycle | Stages split by parameter count | Equalise FLOPs per stage |
| 70B FSDP over 80GB on 8 GPUs | 105 GB per GPU | 16 GPUs or activation checkpointing |
## Q&A
type: qa
from: phases/19-capstone-projects/77-data-parallel-ddp/docs/en.md, phases/10-llms-from-scratch/05-scaling-distributed/docs/en.md, phases/19-capstone-projects/79-pipeline-parallel/docs/en.md, phases/19-capstone-projects/78-zero-parameter-sharding/docs/en.md, phases/10-llms-from-scratch/19-dualpipe-parallelism/docs/en.md
- **Why mean and not sum?** An LR tuned at one rank then works at four ranks.
- **Does mixed precision halve memory?** No. It saves about 25%.
- **GPipe bubble at M=8, N=4?** 27%. At M=64, N=4 it is 4.5%.
- **GPipe or 1F1B?** Same bubble. 1F1B bounds activation memory.
- **Why reduce_scatter in ZeRO?** Each rank gets only its own shard sum.
- **Is ZeRO-3 the same as FSDP?** Yes. The mechanism is the same.
- **DualPipe under 1k GPUs?** Overkill. Bubbles are a small share of cost.