The setup: two bottlenecks, two speed regimes

We scale to many machines to solve compute (one chip is far below the exaflops of a cluster) and memory (models don't fit in one GPU). The recurring conceptual split is intranode communication (very fast — you can afford expensive patterns) vs internode (slow — you must respect the channel). Everything today is at the level of collective operations, and one identity does a lot of work:

The identity that keeps paying off

all-reduce = reduce-scatter + all-gather. Because the two-step version costs the same as the one-step all-reduce, several memory-saving tricks turn out to be free in communication.

Hardware aside: TPU mesh vs GPU tree

The networking differs by philosophy. A TPU uses a toroidal mesh — each chip wired to neighbors, wrapping around — so it scales indefinitely with a fixed number of neighbors per chip (cheap, beefy neighbor links). A GPU uses a fat tree — fast at the bottom, then pods, then spine switches — an all-to-all philosophy that's more flexible for unpredictable communication. Hence (per Dally/Dean) GPUs suit MoEs (tokens route unpredictably) and TPUs suit dense, predictable partitions (tensor parallel over the mesh).

It's converging, though: newer TPUs moved toward tree/all-to-all (Virgo network) because modern models are MoEs that fling activations around. And the Huawei Ascend 910 shows the opposite extreme — weaker chips but a giant fiber-optic all-to-all rack of 384, at ~4× NVIDIA's power: you can brute-force communication if you'll pay the power (the SRAM/Groq story again). The real takeaway: the new unit of compute is the data center, and we want to control memory and compute losslessly across it.

Naive data parallel — and why memory is the problem

Split the batch B across M machines; each computes gradients on B/M elements; all-reduce to sum. Compute scales perfectly; communication is ~2×params per step. But memory savings: zero — every GPU holds a full copy of everything. And "everything" is a lot:

What you storeNote
Parametersthe model (blue)
Gradientssame size as params (orange)
Optimizer state — Adam's 1st + 2nd moments (often fp32)the big one (green) — most of the memory

Rule of thumb: ~16 bytes per parameter (≈5 copies). The optimizer state dominates — which is exactly what ZeRO attacks.

ZeRO: shard the redundant state

DDP stage 1 stage 2 stage 3 (FSDP) params grads optim all replicated optim ÷ N + grads ÷ N + params ÷ N
Stage 1 — shard optimizer state
Each worker updates one parameter slice. Everyone computes a full gradient → reduce-scatter gradients → each does its slice's update → all-gather updated params. That's reduce-scatter + all-gather = one all-reduce — identical comms to DDP. Free.
Stage 2 — also shard gradients
You can't materialize the whole gradient, but you don't need to: sweep backward and reduce-scatter each layer's gradient to its owner as you go, freeing it immediately. Incremental = same cost. Still free.
Stage 3 (FSDP) — shard params too
Each GPU holds only a slice of params, grads, and state. Gather params on demand while stepping the compute graph: all-gather a layer's weights → forward → free; on backward, all-gather again → compute → reduce-scatter grads → free. That's two all-gathers + one reduce-scatter — one extra all-gather vs DDP.
Why stage 3's extra comms is nearly free

Two ideas: (1) sweep the graph, communicate, then free; (2) overlap communication with computation — all-gather layer i+1's weights while computing layer i. If compute is big and the network fast, the comms hides underneath, so FSDP runs near single-GPU utilization while cutting memory dramatically (an A100 goes from not fitting 7B to fitting 50B+).

StrategyCommunicationMemory
DDP (naive)2×params (1 all-reduce)full replica
ZeRO 1 & 2same as DDP (free)optim (+grads) ÷ N
ZeRO 3 / FSDP+1 all-gather (hidden)everything ÷ N

FSDP is clean, elegant, and you'll implement it in the assignment (wrap a module: all-gather → compute → free, repeat). But it has two limits, which force the rest of the toolbox: it consumes the batch size (you can't have more data-parallel workers than batch elements, and past the critical batch size more batch helps less than more steps), and it doesn't touch activation memory.

Pipeline parallelism — cut the depth

Put different layers on different GPUs; pass activations forward, partial gradients backward. Done naively, utilization is catastrophic — one GPU active at a time, the rest idle in a bubble:

naive: big bubble grey = idle micro-batched: bubble shrinks colors = micro-batches flowing

The fix is micro-batches: chop the batch so a stage finishes a piece and passes it on, keeping the pipeline full. Utilization ≈ stages / micro-batches, so the bubble shrinks as 1/microbatches — another use for the precious batch-size resource. Pipeline parallel's virtues: it saves memory, composes with data parallel, and communicates only activations (b·s·h, small) point-to-point — so it tolerates slow links and goes on the slowest interconnects (cross-pod, cross-datacenter, decentralized).

Zero-bubble pipelining

The backward pass does two things per node: B = propagate partials backward (the critical path — the next stage can't start without it) and W = compute weight gradients (a leaf, do it whenever). Separate them: run all the B's as fast as possible, defer the W's to fill gaps — and the bubble nearly vanishes. (Interleaved scheduling, e.g. DeepSeek's, also helps.)

Tensor parallelism — cut the width

Split each matmul into smaller matmuls and recombine (the tiling idea again). For an MLP z = GeLU(xA)·B: split A by columns and B by rows, run in parallel, combine at the end.

forward:   f = copy(x)        →  parallel matmuls  →  g = all-reduce
backward:  g = identity       ←  (partials flow)   ←  f = all-reduce
# column-cut at inputs (MLP up-proj, attention QKV);
# row-cut at outputs (MLP down-proj, attention out);
# layernorm / nonlinearity / MoE router → replicated (don't bother cutting)

The forward/backward duality matters: the f and g operators swap roles (identity ↔ all-reduce) between passes. Tensor parallel is very communication-hungry — an activation-sized all-reduce at every matmul — so it lives only on the fastest interconnects (within a node, ≤8 GPUs on NVLink); cross-node it falls off a cliff. (TPUs' big mesh is the exception — they tensor-parallel over far more chips.)

Pipeline parallelTensor parallel
Cutsdepth (layers)width (matrices)
Commspoint-to-point, b·s·hall-reduce, activation-sized, every matmul
Interconnecttolerates slowneeds very fast (intranode)
Downsidebubbles (need big batch)low complexity, but heavy comms

Activation memory — the part everyone forgets

Memory isn't just parameters. Profiling shows huge activation humps, and at scale activations dwarf parameter memory. Storing everything costs roughly:

activation_memory ≈ 34·s·b·h  +  5·(a·s/h)·s·b·h
#                  └ the fundamental s·b·h term ┘   └ quadratic attention+dropout ┘
#  (the second term is droppable via FlashAttention recomputation)

The s·b·h dependence is fundamental — you store something per sequence position, per batch element, per hidden dim. How each strategy reduces it:

StrategyActivation memory
No parallelism34·sbh + attention term
Tensor parallel (size t)24/t·sbh + 10·sbh + attn/t
+ Sequence parallelfully ÷ t (splits the leftover 10·sbh)
+ Activation recomputation34·sbh / t

Tensor parallel divides the matmul parts (24 of the 34) and attention by t, but the lightweight ops it replicates — layernorm, dropout, the residual inputs you must keep — leave a stubborn 10·sbh penalty. Sequence parallel fixes that: split those leftover ops along the sequence axis (all-gather / reduce-scatter on demand — FSDP-like, with the same forward/backward duality). Add recomputation and you reach 34·sbh/t — the practical lower bound to remember when checking whether a model fits.

Expert parallelism (and why it's preferred for MoE)

Shard the experts (the FFNs) across devices — analogous to tensor parallel (high-bandwidth, reduces activation), but you route sparse tokens instead of slicing dense matmuls. For MoE, prefer EP over TP (per Megatron's guidelines): tensor parallel makes matmuls too small (hurting GPU utilization), whereas routing sparse token activations is cheaper, lets you send tokens exactly where needed, and skips overhead. But EP is genuinely hard — heavy all-to-all dispatch that's latency-sensitive (compute waits for tokens). DeepSeek's DeepEP and NVIDIA's hybrid-EP libraries optimize this at the lowest level — DeepSeek even found undocumented PTX instructions to squeeze out networking performance.

⚠️ EP couples with DP, and conflicts with TP

Naively, EP is a subset of the data-parallel split (experts sharded within DP replicas), which bounds how far EP can go. And since MoEs only change the MLPs (not attention), EP doesn't parallelize attention — you'd want high TP for attention but low TP for the (now-tiny) MLP matmuls. Modern systems decouple the tensor-parallel degree of the attention vs MLP layers to resolve this.

Context parallel (ring attention) splits a long sequence's activations across accelerators, passing them ring-like along the mesh — used for long-context extension and serving.

No strategy dominates — so stack them

StrategyCutsMain drawback
Data parallel (DDP)batchno memory savings
FSDP / ZeRO-3batch + all stateconsumes batch size; no activation savings
Tensor parallelwidthneeds fast interconnect
Pipeline paralleldepthbubbles (need big batch)
Expert parallelexpertscomplex all-to-all; DP/TP coupling
Sequence / context parallelsequence lengthadd-on for activation / long context

You can do the math per layer (compute vs communication time) and plot utilization: as long as compute time exceeds comms time, comms hides underneath. With a big batch, FSDP alone is compute-bound and great; as batch shrinks it becomes comms-bound, so you add tensor parallel to push the efficient region out, then more strategies — that stacking is 3D/4D parallelism.

The simple prescription

(1) Until the model fits in memory, cut it up: use tensor/expert parallel on the fast interconnect (≈8 per box), then pipeline parallel / ZeRO-3 the rest of the way to fit. (2) Once it fits, data-parallel with all remaining GPUs. (3) If the batch is too small, use gradient accumulation. Megatron's guideline, in order: maximize DP; keep TP/EP within NVLink (one box); pipeline across nodes; prefer EP for MoE; context parallel for long sequences.

The classic Narayanan/Megatron sweeps bear this out: DP maxed, TP rising to 8 then capped, pipeline parallel filling the rest, DP shrinking only at extreme scale — and utilization stays flat and high even at enormous GPU counts. (A counterintuitive lever: do more activation recomputation — it saves memory, which buys a bigger batch, which buys better utilization.)

Real training runs

ModelParallelism
OLMo (7B, dense)pure FSDP — scales surprisingly well for small models
DeepSeek V1ZeRO-1 + tensor + sequence + pipeline
DeepSeek V3 (MoE)pipeline + expert parallel ×64 (8 machines grouped), with pipelining tricks to keep EP utilized
YiZeRO-1 + tensor + pipeline (→ EP when MoE)
Llama 3 405B (dense)TP 8 · CP 1 · PP 16 · DP 128; long-context stage cranks CP, lowers DP. (GPUs failed 148× → needs redundancy.)
Gemma 2 (TPU)FSDP + tensor + sequence — no pipeline (tensor-parallel over the big mesh)
Mixtral 8×22BEP 8 · PP 4 · TP 4 (TP for attention)
Qwen 3 (MoE)EP 32 · PP 8 · TP 2

Common thread: everyone maximizes data parallel, keeps tensor parallel ≤ 8, and — thanks to DeepSeek-V3-era infrastructure — expert parallel can now be large. NVIDIA's Megatron-Bridge repo publishes recommended configs per model.

Golden rules & takeaways

Next: scaling laws.

References

Primary source is the lecture itself. Key works named:

  1. Rajbhandari et al. (2019) — ZeRO
  2. Zhao et al. (2023) — PyTorch FSDP
  3. Shoeybi et al. (2019) — Megatron-LM (tensor parallelism)
  4. Narayanan et al. (2021) — Efficient Large-Scale Training on GPU Clusters (3D parallelism)
  5. Korthikanti et al. (2022) — Reducing Activation Recomputation (sequence parallel)
  6. Qi et al. (2024) — Zero Bubble Pipeline Parallelism
  7. Liu et al. (2023) — Ring Attention (context parallelism)
  8. NVIDIA Megatron-Core / Megatron-Bridge — parallelism guides & configs