Chapter 26 β Distributed Training
βNo single GPU can train a frontier model. The art of distributed training is splitting computation across thousands of GPUs while minimizing communication overhead.β
GPT-4, LLaMA-3 405B, and DeepSeek-V3 were each trained on thousands of GPUs for months. This chapter covers the parallelism strategies that make this possible.
Why Distribute?
The largest models today have hundreds of billions of parameters. A single GPU canβt hold the model, optimizer states, gradients, and activations simultaneously:
| Component | LLaMA-3 70B (BF16) | LLaMA-3 405B (BF16) |
|---|---|---|
| Model weights | 140 GB | 810 GB |
| Adam optimizer states | 560 GB | 3,240 GB |
| Gradients | 140 GB | 810 GB |
| Activations (B=1, T=8192) | ~30 GB | ~170 GB |
| Total | ~870 GB | ~5,030 GB |
| H100 80GB GPUs needed | minimum 11 | minimum 63 |
The Three Dimensions of Parallelism
Frontier training combines all three β 3D parallelism β plus additional techniques like sequence parallelism and expert parallelism (for MoE models).
Data Parallelism (DDP)
The simplest strategy: each GPU has a full copy of the model and processes different data.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# PyTorch DDP
import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel as DDP
dist.init_process_group("nccl") # NVIDIA Collective Communications Library
local_rank = int(os.environ["LOCAL_RANK"])
model = MyTransformer().to(local_rank)
model = DDP(model, device_ids=[local_rank])
# Training loop is identical β DDP handles gradient sync automatically
for batch in dataloader:
loss = model(batch)
loss.backward() # gradients synced via all-reduce
optimizer.step()
optimizer.zero_grad()
# Launch: torchrun --nproc_per_node=8 train.py
Limitation: every GPU must hold the full model + optimizer states. For a 70B model, thatβs ~870 GB β impossible with DDP alone.
FSDP / ZeRO: Sharded Data Parallelism
Fully Sharded Data Parallel (FSDP) β PyTorchβs implementation of DeepSpeed ZeRO β shards not just data but optimizer states, gradients, and even parameters across GPUs:
| ZeRO Stage | What is sharded | Memory per GPU (70B, 8 GPUs) |
|---|---|---|
| Stage 0 (DDP) | Nothing β full replicas | ~870 GB |
| Stage 1 | Optimizer states | ~210 GB |
| Stage 2 | + Gradients | ~158 GB |
| Stage 3 / FSDP Full | + Parameters | ~109 GB |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# PyTorch FSDP (Fully Sharded Data Parallel)
from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
from torch.distributed.fsdp import MixedPrecision
mp_policy = MixedPrecision(
param_dtype=torch.bfloat16,
reduce_dtype=torch.bfloat16,
buffer_dtype=torch.bfloat16,
)
model = FSDP(
model,
mixed_precision=mp_policy,
sharding_strategy=ShardingStrategy.FULL_SHARD, # ZeRO-3
auto_wrap_policy=transformer_auto_wrap_policy,
)
# Training loop remains the same
# FSDP all-gathers parameters before forward, shards after backward
The tradeoff: FSDP introduces more communication (all-gather before forward, reduce-scatter after backward) but dramatically reduces per-GPU memory.
Tensor Parallelism (TP)
Tensor parallelism splits individual weight matrices across GPUs. For a linear layer Y = XW:
In a transformer, TP is applied to:
- Attention: Q, K, V projections split column-wise across heads; output projection split row-wise
- FFN: first linear split column-wise, second split row-wise
Constraint: TP requires fast interconnect (NVLink/NVSwitch) since it communicates within every layer. Typically used within a single node (8 GPUs).
Pipeline Parallelism (PP)
Pipeline parallelism assigns different layers to different GPUs:
The pipeline bubble (idle time) is reduced by splitting a batch into micro-batches. Schedules like 1F1B (one forward, one backward) and interleaved PP minimize the bubble.
- Bubble fraction β (PP_size - 1) / n_microbatches
- With 4 pipeline stages and 32 micro-batches: ~9% bubble overhead
Communication Patterns
| Operation | When Used | Data Volume | Requires |
|---|---|---|---|
| All-Reduce | DDP gradient sync | 2 Γ model_size | Inter-node OK |
| All-Gather | FSDP param collection, TP column-parallel | model_size / world_size | Fast interconnect preferred |
| Reduce-Scatter | FSDP gradient sharding | model_size / world_size | Inter-node OK |
| Point-to-Point | Pipeline parallelism activations | activation_size | Between adjacent stages |
| All-to-All | MoE expert routing | tokens Γ d | Needs careful scheduling |
Putting It All Together: 3D Parallelism
A practical configuration for training LLaMA-3 405B on 1024 H100 GPUs:
DeepSpeed Integration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# DeepSpeed ZeRO-3 config
ds_config = {
"train_batch_size": 2048,
"train_micro_batch_size_per_gpu": 2,
"gradient_accumulation_steps": 128,
"bf16": {"enabled": True},
"zero_optimization": {
"stage": 3,
"offload_param": {"device": "none"},
"offload_optimizer": {"device": "none"},
"overlap_comm": True,
"contiguous_gradients": True,
"reduce_bucket_size": 5e8,
},
}
# Initialize with HuggingFace Accelerate
from accelerate import Accelerator
accelerator = Accelerator(deepspeed_plugin=ds_config)
model, optimizer, dataloader = accelerator.prepare(model, optimizer, dataloader)
Whatβs Next
Not all accelerators are NVIDIA GPUs. The next chapter surveys ASICs and alternative accelerators β TPUs, Gaudi, Groq LPUs, and more.
β Previous: Chapter 25 β CUDA & Kernel Development Β· Next: Chapter 27 β ASICs & Accelerators β
Last updated: April 2026