Skip to content
datarekha

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 Last reviewed

9 min read Advanced Deep Learning Lesson 23 of 39

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.

GPipe schedule — micro-batches fill the pipelineGPU 0GPU 1GPU 2GPU 3time →bubbleF1F2F3F4F1F2F3F4F1F2F3F4F1F2F3F4
Four micro-batches (F1–F4) stream diagonally through four stages. Once the pipeline is full, all four GPUs work at once; the dashed cells are the bubble — the unavoidable ramp-up and ramp-down.

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/P utilization); the idle time is the pipeline bubble.
  • GPipe micro-batching streams M micro-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

0/4
Q1Why can't data parallelism (DDP) train a model that's too big for a single GPU?
Q2What is the 'pipeline bubble'?
Q3How does GPipe shrink the bubble, and what's the trade-off?
Q4What does DualPipe (DeepSeek-V3) add over plain GPipe/1F1B?

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).

Sign in to track your progress

Completed lessons, your XP, level, and streak save to your account — it's free and takes a few seconds.

Practice this in an interview

All questions
How do you optimise GPU utilization for model serving, and what role does dynamic batching play?

GPUs 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.

Why are GPUs used for deep learning instead of CPUs?

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.

What is the difference between DDP and FSDP for distributed training?

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.

How does autoscaling work for ML inference services, and what metrics should drive it?

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.

Related lessons

Explore further