← Contents Volume II · Lecture 4

Volume II · Splitting the Work

4

Stages, Bubbles, and Redundant Memory

Tensor parallelism cuts inside a layer. This lecture covers the other way to slice a model that does not fit on one GPU: cut between layers instead, and give each GPU a contiguous stretch of the model to own outright. That trade buys back memory at the cost of idle time — and a second, orthogonal idea, ZeRO, buys back a different kind of memory that was never doing useful work in the first place.

The mental model

Pipeline parallelism and ZeRO are not competing answers to "we don't have enough memory" — they are two different answers to two different memory problems. Pipeline parallelism trades memory for pipeline-bubble idle time; ZeRO trades a little extra communication for eliminating memory that was purely redundant. Real training runs use both at once, alongside tensor parallelism.

Pipeline parallelism: splitting by layer

Where tensor parallelism splits the weight matrix inside one layer across GPUs, pipeline parallelism splits the model by layer: GPU 0 owns layers 1 through k, GPU 1 owns layers k+1 through 2k, and so on, with activations physically passed from one GPU to the next as data flows through the network. Huang et al.'s GPipe (arXiv:1811.06965) is the paper that made this practical at scale: it splits each training batch into smaller micro-batches and pipelines them synchronously through the stages, which lets near-linear speedup emerge from adding more stages — but it pays for that synchrony with "bubble" time, the idle GPU cycles that occur while the pipeline is filling up at the start of a batch and draining at the end, because early stages have nothing left to do once their micro-batches have all moved downstream and late stages have nothing to do until the first micro-batch arrives.

Harlap et al.'s PipeDream (arXiv:1806.03377) attacks exactly this idle time with an asynchronous schedule: a "one-forward-one-backward" pattern that keeps every stage doing useful work far more of the time than GPipe's synchronous flush allows, at a real cost — because the schedule is asynchronous, some weight updates are computed using slightly stale versions of the weights rather than the very latest ones. The trade is explicit: GPipe accepts idle bubble time to keep every update exactly correct; PipeDream accepts slightly stale updates to keep GPUs almost always busy.

GPIPE: FILL AND DRAIN BUBBLES IN A NAIVE PIPELINE S0 S1 S2 S3 fill steady state shaded = idle bubble
Figure 4a The shaded corner is bubble time: stage 3 waits for the pipeline to fill before it has any work, and the mirror image happens at drain when early stages run out of micro-batches before the batch ends. More stages means a bigger fraction of the batch spent filling and draining.
Plate 4.1 The fill/drain bubble from Figure 4a, made adjustable. Push pipeline stages up without adding more micro-batches and the idle fraction climbs — this is the arithmetic behind "more stages means a bigger fraction of the batch spent filling and draining."

ZeRO: memory that was redundant, not shared

Data parallelism (Lecture 3) has a quieter cost than the bubble time above: every GPU in a data-parallel group holds a full copy of the optimizer state, the gradients, and the parameters — not because each GPU needs a private copy to do useful work, but simply because that is what naive replication does. Rajbhandari et al.'s ZeRO (arXiv:1910.02054, Microsoft, 2019/2020) names this plainly: that replicated memory is redundant, not shared in any useful sense, and it can be partitioned across the data-parallel GPUs instead of copied onto every one of them.

ZeRO does this in three increasingly aggressive stages. Stage 1 partitions only the optimizer state across GPUs — each GPU keeps its full copy of gradients and parameters, but the (often large, e.g. Adam's two moment buffers per parameter) optimizer state lives on only one GPU per shard. Stage 2 additionally partitions the gradients. Stage 3 goes further still and partitions the parameters themselves, so no single GPU ever holds a complete copy of the model's weights at rest — only reconstructing the piece it needs, communicating to fetch the rest, when a computation actually requires it. The paper's own claim is that this could in principle scale model size roughly linearly with the number of GPUs, rather than being capped by whatever fits in one GPU's memory, at a communication overhead the paper argues stays low relative to the memory it frees. PyTorch's Fully Sharded Data Parallel (FSDP) is the widely-used implementation of this same idea, most directly corresponding to ZeRO stage 3.

ZeRO: PARTITIONING WHAT WAS PURELY REDUNDANT Plain DP every GPU: full O + G + P ZeRO-1 O partitioned; G, P full ZeRO-2 O, G partitioned; P full ZeRO-3 O, G, P all partitioned O = optimizer state · G = gradients · P = parameters shaded boxes = data held redundantly on every GPU; outlined = held on one shard only FSDP (PyTorch) is the common realization of ZeRO stage 3
Figure 4b Plain data parallelism replicates optimizer state, gradients, and parameters on every GPU in full. Each ZeRO stage partitions one more of the three across the data-parallel group instead, trading a little added communication for memory that scales with GPU count rather than being capped per-GPU.

Two orthogonal answers, not competing ones

Pipeline parallelism answers "the model's weights don't fit on one GPU" by giving each GPU only some layers, at the cost of bubble idle time. ZeRO answers "data parallelism is wasting memory on redundant copies" by partitioning what never needed to be copied. Neither replaces the other, and production 3D parallelism (Lecture 3's Narayanan et al. follow-up) runs tensor, pipeline, and ZeRO-partitioned data parallelism together, each solving the piece of the memory and communication problem it is suited to.

Read the primary sources