TAI BUI
Back to curriculum
CapstonePython

Pipeline Parallel and Bubble Analysis

Pipeline parallelism splits the model across ranks, one stage per rank, and microbatches flow through the stages in sequence. The idle time while the pipeline fills and drains is the bubble, and shrinking it is most of the craft. Tensor parallelism splits the matrix multiply instead.

TIME: ~90 minPREREQUISITES: Phase 19 Track C lessons 42-49

Learning Objectives

  • Split a sequential model into N stages and simulate a forward pipeline across N ranks.
  • Schedule M microbatches through the pipeline using the GPipe schedule (forward-only fill, then backward) and compute the bubble fraction.
  • Explain how the 1F1B schedule used in Megatron-LM and PipeDream differs from GPipe in bubble size and activation memory.
  • Defend stage assignment: equal compute per stage matters more than equal parameter count per stage.

The Problem

A 70B-parameter model in fp16 needs 140 GB for parameters alone, which no consumer GPU holds. ZeRO-3 shards the parameters across ranks, but each rank still has to allgather the full layer for each forward step, paying log(N) hops per layer. Pipeline parallel takes a different route. Cut the model into N stages and put one stage on each rank. Layer 1's forward finishes on rank 0 and hands the activation tensor to rank 1, rank 1 runs layer 2 and hands off to rank 2, and so on. Backward flows in reverse. Memory drops linearly because each rank holds one stage. Compute is sequential, and that is where the bubble comes from.

The bubble is idle time at the start of the pipeline (waiting for the first microbatch to reach the last stage) and at the end (waiting for the last microbatch to drain back through). With M microbatches and N stages, the per-stage bubble fraction is (N-1)/(M+N-1). At M=8, N=4 that is 27%. At M=64, N=4 it is 4.5%. More microbatches per step shrink the bubble, which means smaller per-microbatch batch sizes, and that constraint drives microbatch design.

Warm-up0/3
1. A 70B-parameter model stores each parameter in fp16 (2 bytes). Roughly how much memory do the parameters alone need?Warm-up
2. An assembly line has four stations in a row. Before the first item reaches station 4, what are stations 2 through 4 doing?Warm-up
3. Two ranks each call a blocking send to the other before either calls receive. What happens?Warm-up
Check: The Problem0/6
1. What does pipeline parallelism do with a model that is too large for one GPU?Define
2. What is the pipeline bubble?Define
3. How does the data a pipeline moves between ranks differ from what ZeRO-3 moves?Explain
4. With N=4 stages you raise the microbatch count from M=8 to M=64. What happens to the bubble fraction?Apply
5. Which is the smallest of these microbatch counts that brings an 8-stage GPipe bubble below 10%?Apply
6. A team uses N=4 stages and splits each batch into M=2 microbatches. Stages sit idle about 60% of the time. Which change most directly cuts that idle share?Analyze

The Concept

Mermaid diagram

GPipe schedule

GPipe fills the pipeline forward with all M microbatches before starting any backward pass, then drains backward in reverse. Activations from each microbatch are held until its backward runs, so memory grows linearly with M. Forward takes M+N-1 cycles and backward takes another M+N-1. Per-stage useful work is 2M cycles and the per-stage bubble is 2(N-1) cycles. When each forward and backward takes one unit of time, the bubble fraction is (N-1)/(M+N-1). Picking M much greater than N hides the bubble.

1F1B schedule

1F1B interleaves the two passes: as soon as a microbatch's forward reaches the last stage, its backward starts and streams back. Each stage alternates one forward and one backward. The bubble is still N-1, but activation memory is bounded by the pipeline depth instead of the microbatch count. Megatron and PipeDream both use 1F1B. GPipe comes first here because it is simpler, and 1F1B is left as an exercise.

Why equal compute per stage matters

Say stage 0 takes 50 ms and stage 1 takes 100 ms. Each cycle is gated on stage 1, and the other stages sit idle for 50 ms per cycle waiting for it to release. Equal parameter count is the wrong axis. A transformer's compute is dominated by attention plus MLP in each layer, while embedding layers have many parameters and little compute. Assign stages to equalise FLOPs per stage, not weights per stage.

Microbatch versus batch

A pipeline runs M microbatches of size B each, so the effective batch size is MB. The gradient at the end of a pipeline step is the gradient over those MB examples combined. The bubble fraction depends on M, while the optimiser sees M*B. Tuning M trades bubble (lower with high M) against per-microbatch memory (under GPipe, activation memory grows with M).

Check: The Concept0/14
1. What is a microbatch in pipeline training?Define
2. What does the GPipe schedule do?Define
3. What does a stage do under the 1F1B schedule?Define
4. Why is GPipe's activation memory proportional to M?Explain
5. Why can splitting stages by equal parameter count leave a pipeline unbalanced?Explain
6. Under GPipe, what does raising the microbatch count M trade away?Explain
7. What lets 1F1B bound activation memory by pipeline depth instead of by M?Explain
8. If a pipeline runs M microbatches of size B each, what effective batch size does the optimiser see?Explain
9. How does 1F1B improve over GPipe?Explain
10. With N=4 stages and M=8 microbatches under GPipe, how many idle cycles does each stage have, counting forward and backward together?Apply
11. Stages were split by equal parameter count, and the stage holding the embedding table finishes far sooner than the rest. What is the right fix?Analyze
12. A two-stage pipeline has stage 0 at 50 ms and stage 1 at 100 ms. The closed-form bubble fraction predicts less idle time than a profiler shows. Why?Analyze
13. A colleague says moving from GPipe to 1F1B at N=4, M=8 will cut the 27% bubble to nearly zero. What is wrong with that claim?Analyze
14. An 8-stage GPipe run needs M=64 to keep the bubble under 10%, but activation memory no longer fits. Which change fits the lesson's ideas?Design

Build It

code/main.py implements:

  • StageMLP: a small nn.Module that holds one stage's two linear layers and exposes forward(x).
  • gpipe_schedule(num_stages, num_microbatches): lays out the GPipe schedule as (cycle, stage, microbatch, phase) tuples in integer cycles, with every forward and backward costing one cycle. (FORWARD_UNITS and BACKWARD_UNITS are defined at the top of the file but the schedule does not use them.)
  • render_gantt(...): draws that schedule as a stage-by-cycle grid with microbatch labels such as F3 and B3.
  • bubble_fraction(num_stages, num_microbatches): the closed form (N-1)/(M+N-1).
  • measure_bubble(...): counts the idle slots in the schedule, which is the measured fraction.
  • run_pipeline(...): a second demo, a real two-rank pipeline over torch.distributed with the gloo backend. Rank 0 runs stage 0 and sends the activation to rank 1, rank 1 runs stage 1, computes the loss and sends the gradient back, and both ranks train a small MLP for a few steps.

Run it:

bash
python3 code/main.py

The output is a 4-stage, 8-microbatch Gantt grid, the measured bubble percentage next to the closed-form prediction, a table of bubble versus M, and then a short summary of the two-rank gloo run (microbatch losses seen by rank 1 and the final parameter norms).

Check: Build It0/6
1. In the two-rank run in main.py, what does rank 1 send back to rank 0 after computing the loss?Apply
2. In the schedule gpipe_schedule(3, 4), the last stage runs F3 at cycle 5. What does it run at cycle 6?Apply
3. What does bubble_fraction(8, 8) return?Apply
4. measure_bubble(3, 6) counts idle slots in the schedule. What does it return?Apply
5. In main.py, BACKWARD_UNITS is 2. If you set it to 4, what happens to the output of gpipe_schedule?Apply
6. run_pipeline(steps=3, microbatches=4) runs the two-rank gloo pipeline. How many microbatch losses does rank 1 record?Apply

Production patterns in the wild

Three patterns harden pipeline parallel enough to ship.

Activation checkpointing pairs with pipeline. With M microbatches in flight on GPipe, activation memory is M times one microbatch. Activation checkpointing recomputes the forward at backward time, trading compute for memory, and the combination is what makes pipeline tractable for long sequences.

Stage balance is measured, not assumed. Production teams run a profiling pass that measures actual per-layer compute (FLOPs and wall-clock) on the target hardware, then partition by that measurement. The Megatron-LM --num-layers-per-stage flag accepts a list, which allows uneven layer counts when stages have different per-layer cost.

The send-recv schedule has to avoid deadlock. If each stage sends before it receives, the pipeline deadlocks on the wire. The standard fix is to interleave: even-rank stages send first and then recv, odd-rank stages recv first and then send. code/main.py only has two ranks, so the pattern shows up in its smallest form: rank 0 sends the activation and then receives the gradient, while rank 1 receives first and then sends.

Check: Production patterns in the wild0/4
1. What does activation checkpointing do?Define
2. In the two-rank run in main.py, rank 0 sends the activation and then receives the gradient. Why does rank 1 receive first and then send?Apply
3. A four-stage pipeline hangs on its first microbatch because every rank calls send before recv. What is the standard fix?Analyze
4. A model has a huge embedding table and many attention and MLP layers, to be split over 4 ranks. Which plan fits what the lesson teaches?Design

Use It

Production patterns:

  • Megatron-LM. The reference for pipeline parallel at scale. It uses 1F1B and supports tensor, pipeline, and data parallel combined.
  • DeepSpeed Pipeline. Integrates with ZeRO. ZeRO-1 plus pipeline is a common combination for the largest open models.
  • PyTorch Pipe. The PyTorch-native pipeline wrapper, built on torch.distributed.pipeline.sync.Pipe.
Check: Use It0/1
1. ZeRO is described as orthogonal to pipeline parallel and often combined with it. What does orthogonal mean here?Explain

Ship It

Lesson 80 stores the per-stage parameter shards in the sharded checkpoint. Lesson 81 composes DDP, ZeRO, and pipeline in the end-to-end demo, in spirit: the demo keeps the pipeline simulated to keep runtime short.

Exercises

  1. Implement 1F1B and verify the bubble fraction matches GPipe but activation memory is bounded.
  2. Profile real per-stage time on a deeper model and rebalance stages by measured wall-clock.
  3. Add gradient accumulation across pipeline microbatches and check the gradient equals the gradient of the equivalent full-batch forward.
  4. Pair the pipeline with activation checkpointing and measure the memory drop versus compute cost.
  5. Combine pipeline with DDP (each pipeline rank is replicated across a data-parallel group) and reason through the 2D schedule.

Key Terms

TermWhat people sayWhat it actually means
Pipeline"Model parallel along depth"One stage per rank, activations flow stage to stage
Bubble"Pipeline idle time"(N-1) steps at start + end where some stages have no work
Microbatch"Slice of the batch"One forward/backward unit; bubble shrinks as M grows
GPipe"Fill then drain"All M forwards before any backward; high activation memory
1F1B"Interleaved schedule"One forward one backward per stage; bounded activation memory
Author's Note

My bet is that the stage-balance argument is the one to internalise before the schedule names. The bubble formula counts the idle slots from filling and draining, and it assumes each forward and backward takes one unit of time. If stage 1 takes twice as long as stage 0, the (N-1)/(M+N-1) figure understates the idle time, so I'd read it as a floor.

I'd treat the closed form and the measured fraction as one check, not two. The Gantt grid in code/main.py gives a count of idle slots, and agreement with (N-1)/(M+N-1) tells you the schedule is laid out as the formula assumes. It does not tell you the stages are balanced or that the wire works, so I'd keep those questions separate.

If I were extending it, I'd start with exercise 1, because that is where the GPipe versus 1F1B difference stops being a claim in prose and gets checked against a schedule you built.

Further Reading

Code