Pipeline parallelism
When a model is too big to fit on one GPU, you split it across GPUs by layer — an assembly line of stages. The catch is the 'pipeline bubble' of idle GPUs; micro-batching, 1F1B, and DeepSeek's DualPipe are how you shrink it.
Written and reviewed by Shreyash Prashu Last reviewed
What you'll learn
- Why huge models need model parallelism, and how pipeline parallelism splits by layer
- The pipeline bubble — why a naive pipeline leaves most GPUs idle
- How micro-batching (GPipe) shrinks the bubble, and the exact bubble formula
- 1F1B for memory, DualPipe for overlap, and where pipeline fits the parallelism zoo
Before you start
Data parallelism (DDP) puts a full copy of the model on every GPU and splits the data — which is useless the moment the model itself no longer fits on one GPU. A frontier model’s weights, gradients, and optimizer state run to terabytes; you have to split the model. Pipeline parallelism is the most intuitive way to do it: cut the network into consecutive stages and put each stage on a different GPU, like an assembly line.
GPU 0 holds the first chunk of layers, GPU 1 the next, and so on. A batch flows through stage 0, whose output is handed to stage 1, then stage 2… That works — but done naively it wastes almost everything.
The pipeline bubble
Run a single batch through the line and watch the GPUs: while stage 0 computes,
stages 1–3 sit idle waiting for its output. When the work reaches stage 1, stage 0
has nothing to do. At any instant only one stage is busy — so with P stages
you get 1/P utilization. Four GPUs running at 25% is a catastrophic way to spend a
cluster. That idle time is the pipeline bubble.
The fix is to keep all stages busy at once by feeding the pipeline a stream of
work instead of one batch. Split the batch into M micro-batches and pump them
in back-to-back (this is GPipe): as micro-batch 1 advances to stage 1, micro-batch
2 enters stage 0 behind it, and soon every stage is working on a different
micro-batch.
How much bubble is left?
The bubble never fully vanishes — you still pay one ramp-up and one ramp-down — but
its fraction shrinks as you add micro-batches. For P stages and M
micro-batches the bubble fraction is (P − 1) / (M + P − 1):
def bubble_fraction(P, M):
return (P - 1) / (M + P - 1)
P = 4 # four pipeline stages
for M in [1, 2, 4, 8, 16, 32]:
print(f"micro-batches M={M:>2}: bubble = {bubble_fraction(P, M):.1%} idle")
micro-batches M= 1: bubble = 75.0% idle
micro-batches M= 2: bubble = 60.0% idle
micro-batches M= 4: bubble = 42.9% idle
micro-batches M= 8: bubble = 27.3% idle
micro-batches M=16: bubble = 15.8% idle
micro-batches M=32: bubble = 8.6% idle
M = 1 is the naive case — 75% of a 4-GPU cluster idle. Push 32 micro-batches
through and the bubble falls under 10%. The rule of thumb: keep M several times
larger than P. You can’t shrink it forever, though — more micro-batches means more
in-flight activations to hold in memory, which is the next problem.
1F1B and DualPipe
1F1B (“one-forward-one-backward”, from PipeDream) tackles that memory cost.
Plain GPipe runs all M forward passes, then all M backward passes — so it must
store activations for every in-flight micro-batch at the peak. 1F1B instead
interleaves: as soon as a micro-batch finishes its forward pass through the last
stage, it starts flowing backward, freeing its activations early. Same bubble, far
lower peak activation memory — which pairs naturally with
activation checkpointing.
DualPipe (DeepSeek-V3) goes further: it runs the pipeline bidirectionally and deliberately overlaps the inter-stage communication with computation, so the GPU is computing one micro-batch while it ships another’s activations across the network. That overlap hides almost all of the remaining bubble — a big part of how DeepSeek-V3 trained so cheaply.
In one breath
- When a model is too big for one GPU, data parallelism can’t help (it replicates the whole model); you must split the model, and pipeline parallelism splits it by layer into stages, one per GPU — an assembly line.
- Naively only one stage works at a time (
1/Putilization); the idle time is the pipeline bubble. - GPipe micro-batching streams
Mmicro-batches so all stages stay busy; the bubble fraction is(P−1)/(M+P−1)— ~75% at M=1 but under 10% at M=32 for 4 stages. - 1F1B interleaves forward/backward to cut peak activation memory; DualPipe (DeepSeek-V3) overlaps communication with compute to hide almost all of the remaining bubble.
- Pipeline is one axis of 4D parallelism — data, tensor, pipeline, and expert splits combined to train the largest models.
Quick check
Quick check
Next
Pipeline parallelism is one piece of how frontier models are trained; see how it and the rest of the modern stack come together in the frontier LLM walkthrough, and revisit Multi-GPU training for the data-parallel half (DDP & FSDP).
Practice this in an interview
All questionsGPUs execute tensor operations efficiently only when the batch dimension is large enough to saturate all CUDA cores. Dynamic batching collects individual requests arriving within a short window and fuses them into a single GPU call, dramatically improving throughput and cost efficiency without sacrificing per-request latency beyond the configured wait threshold.
Neural network training is dominated by large matrix multiplications that are embarrassingly parallel. GPUs have thousands of small cores optimised for this exact operation, whereas CPUs have tens of powerful cores optimised for low-latency sequential logic. The throughput difference is 10–100x for typical DL workloads.
DDP keeps a complete model, gradient set, and optimizer state on every GPU and synchronizes gradients, while FSDP shards those states across GPUs and temporarily gathers the parameters needed by each wrapped module. DDP is simpler and often faster when the model fits; FSDP trades more communication and checkpointing complexity for much lower per-GPU memory.
Autoscale ML inference on a leading workload signal such as per-pod queue wait or backlog, with GPU or model-specific throughput as supporting signals; CPU alone misses accelerator saturation. Kubernetes HPA supports custom or external metrics, while strict online SLAs usually require warm replicas and scale-to-zero only for workloads that tolerate measured cold starts.