Overlapping Communication and Computation
Why a large training run's collectives are mostly free when overlapped and catastrophic when exposed, and the bucketing, prefetch and scheduling tricks that hide them.
At 1,000 GPUs, a training step moves a substantial fraction of the model's parameters and gradients across the network every iteration. If that traffic waits for compute to finish and compute waits for traffic, throughput is the sum of the two. If they run concurrently, throughput is the larger of the two. The difference between a run at 50 percent model FLOPs utilisation and one at 25 percent is usually not the kernels; it is whether the collectives were hidden.
The arithmetic that decides it
For one layer, overlap is possible when
Compute time per layer scales with batch size and hidden dimension; communication time scales with parameter or activation bytes divided by link bandwidth. Two consequences follow. Small batches expose communication, because there is not enough compute per layer to hide it. Slow links expose it for the same reason from the other side, which is why the same code achieves very different utilisation on NVLink and on Ethernet.
The four places overlap is engineered
Gradient reduce in data parallelism. Gradients become available layer by layer during the backward pass, and the all-reduce for layer \(L\) can start as soon as its gradient exists rather than waiting for layer 1. PyTorch DDP does this with gradient bucketing: gradients accumulate into fixed-size buckets (25 MB by default) and each bucket launches its all-reduce as it fills. Too small and the collective is latency-bound with poor bandwidth utilisation; too large and the last bucket has no compute left to hide behind.
Parameter gather in FSDP/ZeRO-3. Sharded parameters must be all-gathered before a layer runs and freed afterwards. Prefetching the gather for layer \(L+1\) during layer \(L\)'s compute is what makes ZeRO-3 viable; without it, every layer is a synchronous stall. The same applies in reverse for reduce-scatter of gradients.
Tensor-parallel collectives. These are the hardest to hide because the all-reduce sits directly between two dependent matrix multiplications inside a layer. The techniques that work decompose the matmul so partial results can be communicated while the remaining tiles compute, which requires kernel-level fusion of the collective into the GEMM rather than a library call between them.
Pipeline activation transfer. Sending stage boundary activations on a separate CUDA stream lets the next micro-batch's compute proceed while the previous one's activations are in flight.
Diagnosing exposure
The useful measurement is not bandwidth but the fraction of step time in which a collective is running with no concurrent compute. A profiler timeline showing NCCL kernels with a gap in the compute stream beneath them is exposed communication, and the fix depends on which of the four cases above it is.
A cheaper first check: compare measured step time against the compute-only estimate from model FLOPs and device peak. A model FLOPs utilisation of 35 to 50 percent is typical for a well-tuned large run; 15 percent usually means something is exposed rather than that the kernels are slow.
When it breaks
- Gradient accumulation changes the picture. With accumulation, most micro-steps do not communicate at all, so the collective lands on one step in \(k\) and has that step's compute alone to hide behind. Overlap tuned for the common case fails on the reducing step.
- Bucket sizes are workload-dependent. The default 25 MB was chosen for models much smaller than current ones. A model with very large individual weight tensors can put one tensor in one bucket and lose the pipelining entirely.
- Overlap consumes memory. Prefetched parameters and in-flight buckets are live allocations. Aggressive prefetch depth trades memory for hiding, and on a memory-tight configuration it triggers allocator thrash that costs more than the exposure it removed.
- Non-determinism. Overlapping changes the order in which reductions complete, so results are not bit-reproducible across runs even with fixed seeds. See numerical computation gotchas.
10 flashcards for this concept
Click a card to reveal the answer.