Home · ASPLOS 2022 · Machine Learning Systems

Breaking the computation and communication abstraction barrier in distributed machine learning workloads

Abhinav Jangda, Jun Huang, Guodong Liu, Amir Hossein Nodehi Sabet, Saeed Maleki, Youshan Miao and 3 others

Conference
ASPLOS 2022
Published
2022
Pages
402-416
Citations
64

Machine-learning frameworks call one library for math and another for network transfers, so neither can optimize across the boundary — CoCoNet expresses both in one small program, then mechanically splits, reorders, fuses, and overlaps them, turning roughly 2,000 lines of hand-written CUDA into 15 lines of DSL and speeding up BERT, GPT-2, and GPT-3 workloads by 1.2–1.8×.

The argument in one glance

  1. Problem

    Two libraries, one wall between them

    Distributed ML alternates math kernels (matrix multiply from cuBLAS) with network kernels (AllReduce from NCCL). Each library is superbly optimized in isolation — and blind to the other. While the GPU multiplies, the network idles; while gradients cross the network, the GPU idles. At GPT-3 scale, every wasted percent is time, energy, and money.

  2. What people did

    Optimize within each box, or hand-write across the boundary

    Frameworks like PyTorch fuse computation with computation, and NCCL tunes collectives internally. When engineers do cross the wall — overlapping a matrix multiply with the AllReduce that consumes it — they write the orchestration by hand: about 2,000 lines of custom CUDA for one such kernel pair.

  3. Why it fell short

    Hand-written cross-boundary code cannot keep up

    Every model size, GPU count, network topology, and parallelism style (data, model, pipeline) wants a different combination of tricks — and the paper shows no single schedule wins everywhere. Writing and maintaining thousands of lines of CUDA per scenario is unproductive and error-prone, so most deployments simply leave the performance behind.

  4. This paper's idea

    One language for both, four mechanical transformations, an autotuner

    CoCoNet is a small language where tensors carry a distribution layout (sliced, replicated, local) and programs mix MatMul with AllReduce. Four semantics-preserving transformations — split, reorder, fuse, overlap — rewrite the program across the old boundary, and an autotuner searches schedules in seconds. Results: up to 1.68× faster BERT data-parallel training, 1.51× model-parallel inference, 1.77× pipeline-parallel inference on 256 V100 GPUs.

Why this problem is worth solving

Models outgrew single machines years ago — BERT at 340 million parameters, GPT-2 at 1.5 billion, GPT-3 at 175 billion — so every serious training or inference job is distributed, and its inner loop interleaves two kinds of work. Computation: tensor algebra on each GPU, served by libraries like cuBLAS and cuDNN. Communication: exchanging tensors between GPUs, served by collective libraries — NVIDIA’s

NVIDIA Collective Communications Library, which implements operations like AllReduce (sum a tensor across all GPUs so every GPU ends with the total) as CUDA kernels copying directly between GPU memories
.

The two are separate abstractions: a framework calls a computation kernel, waits, calls a communication kernel, waits. The paper’s opening measurement shows what the wall costs. In model-parallel inference of GPT-2, a matrix multiply is followed by an AllReduce of its output. Run sequentially — as every framework does — the network sits idle during the multiply and the GPUs sit largely idle during the reduce. Overlapping the two in a fine-grained way hides 80% of the matrix multiply’s execution time, a 1.36× speedup on 16 V100 GPUs — but the hand-written implementation of that one overlap is about 2,000 lines of CUDA, valid for one operator pair, one topology, one library version.

The paper’s diagnosis: the barrier is an artifact of software packaging, not of hardware. Its bet: a compiler can cross it mechanically.

What people did before, and where it broke

Independent optimization inside each box. Kernel fusion is standard for computation — ML compilers merge chains of element-wise operations into one kernel (see the TensorIR and AStitch explainers elsewhere on this site). NCCL tunes its collectives with topology-aware protocols and channels. Neither touches the other: the output of an AllReduce is always written to memory, then re-read by the next computation kernel.

Fixed cross-boundary strategies baked into systems. ZeRO and GShard distribute optimizer state and computation across ranks — effectively one specific reorder of communication and computation, hard-coded into a runtime. Valuable, but a point in the design space, not a way to explore it: the paper shows these schedules lose to alternatives at small tensor sizes and leave fusion and overlap opportunities unexploited.

Coarse-grained overlap. Frameworks overlap a layer’s gradient AllReduce with the previous layer’s backward pass — scheduling whole kernels concurrently. Fine-grained overlap inside a producer-consumer pair (start communicating a chunk the moment it is computed) requires controlling the order in which the producer kernel emits its output, which no library interface exposes.

Manual heroics. Where the payoff justified it, engineers wrote custom fused kernels — thousands of lines each, unmaintainable across the matrix of models, sizes, and clusters.

CoCoNet's pipeline, from program to PyTorch operator

  1. DSL program new

    A dozen lines of C++-embedded DSL expressing computation and communication together. Every tensor declares a layout — sliced along a dimension across ranks, replicated on all ranks, or local (same shape, different values per rank) — so the compiler can type-check distribution the way ordinary compilers type-check shapes.

  2. Transformations new

    Four semantics-preserving rewrites: split a collective into finer ones, reorder computation across an AllGather, fuse communication and computation into one kernel, overlap producer-consumer chains. Each has an automatically-checked validity rule; an ordering of them is a schedule — the Halide idea, extended across the communication boundary.

  3. Autotuner new

    Pre-fuses pointwise computations to shrink the space, then breadth-first explores schedules, generates code for each, runs them, and returns the fastest for this hardware, topology, and size — 9 to 11 seconds for the paper’s workloads.

  4. Code generator new

    Emits CUDA: fused-collective kernels built by extending NCCL’s internals (reusing its topology handling, protocols, and channels), overlapped kernel pairs where the MatMul computes chunks in exactly the order the ring AllReduce needs them, and scattered-tensor variants that take lists of non-contiguous buffers.

  5. PyTorch integration

    Generated programs are wrapped as custom PyTorch operators, so Megatron-LM or NVIDIA’s BERT scripts call them like any other function.

The DSL programs in the paper are 12–20 lines; the generated CUDA runs from 140 lines to roughly 2,000 for the overlapped schedules — code nobody has to write or maintain by hand anymore.

The idea

Three moves make the barrier crossable.

Give tensors a distribution type. In CoCoNet a tensor is not just shape and dtype but also layout: sliced (each of N ranks holds 1/N along some dimension), replicated (every rank holds the same full value), or local (per-rank values, same shape). Operations infer their output layouts — a MatMul of a sliced weight produces a local partial result; an AllReduce turns local into replicated; a ReduceScatter turns local into sliced. This bookkeeping, absent from every existing framework, is what lets the compiler prove that a rewrite across the boundary preserves meaning.

Separate algorithm from schedule — Halide’s lesson, imported. The program says what is computed; a schedule — an ordering of four transformations — says how:

  • split breaks one collective into finer ones. The key instance splits AllReduce into a
    sum across ranks where each rank keeps only its 1/N slice of the result
    followed by an
    concatenation of all slices so every rank ends with the full tensor
    . Always valid — and it exposes a seam.
  • reorder slides computation into that seam: operations after the AllGather move before it, becoming sliced computations on 1/N of the data. Valid exactly when the operations can be sliced along the gathered dimension. This one transformation reproduces ZeRO- and GShard-style distribution as a special case.
  • fuse merges a ReduceScatter, the sliced computations, and the AllGather into a single FusedAllReduce kernel: values arriving from the network flow through registers into the computation instead of taking a store-and-reload round trip through GPU memory.
  • overlap runs producer and consumer concurrently at chunk granularity — the matrix multiply is scheduled to produce output chunks in precisely the order the ring AllReduce transmits them, so communication of chunk k rides under computation of chunk k+1, in one kernel launch each.

Let a machine pick, because no schedule always wins. The paper’s own data kills the idea of a universal best: plain AllReduce-then-fused- computation wins for tensors up to about 2¹⁶ elements, while the fully fused-and-distributed schedule wins beyond 2¹⁷ — fusion can even lose by inflating register usage and cutting GPU occupancy. The autotuner turns that judgment call into a ten-second search.

Rewriting Megatron-LM's self-attention tail, one transformation at a time

4 GPUs (N=4). MM = matrix multiply; AR = AllReduce (sum across GPUs, everyone gets the total); RS = ReduceScatter (sum, each GPU keeps 1/N of it); AG = AllGather (share slices so everyone has the whole). The running example is the end of Megatron-LM's model-parallel self-attention block: MatMul, AllReduce the partial results, add bias, Dropout, add the residual connection.

Step 1 of 6

Baseline: four kernels, one at a time

Each GPU multiplies its input by its slice of the weights (a local partial result), AllReduce sums the partials, then every GPU redundantly runs the identical bias-add, Dropout, and residual-add over the full replicated tensor. The AllReduce output is written to memory and re-read by the pointwise kernel; the network is idle during the MatMul, the compute units mostly idle during the AllReduce.

MM → AR → (bias + Dropout + residual)
every GPU: pointwise work on N/N of the data
AR output: stored, then reloaded

Why it works

  • Tensors carry distribution layouts → the compiler can statically verify that a rewrite across the computation/communication boundary preserves semantics → transformations that were one-off engineering become reusable, composable, and safe.
  • AllReduce is exactly ReduceScatter plus AllGather → splitting it creates a seam where computation can run on 1/N of the data → work and (for the optimizer) state distribute across ranks for free.
  • Fusing communication with computation keeps intermediates in registers → the memory round trip between kernels disappears → bandwidth-bound pointwise stages accelerate and kernel-launch overhead drops.
  • The producer’s output order is compiler-controlled → chunks are computed in the ring’s transmission order → communication hides under computation at fine grain, using compute and network (and NVLink and InfiniBand simultaneously, in the pipeline case) instead of alternating them.
  • The best combination depends on size and topology → an autotuner that executes real candidates picks it in seconds → users get hand-tuned performance from 15-line programs, on every configuration.

What it costs

  • A new language to adopt. Workloads must be expressed in the DSL (or wrapped by someone who does); the paper’s programs are 12–20 lines, but they sit outside the everyday PyTorch idiom.
  • NCCL surgery. Fused collectives are built by modifying NCCL’s internal kernels — powerful, but coupled to one communication library’s architecture and evolution.
  • Autotuning per configuration. Schedules are chosen by measuring real runs for each hardware, topology, and size; cheap (seconds) but repeated whenever the setup changes.
  • Fusion can backfire. Fused kernels raise register usage and can lower occupancy — the paper’s own small-tensor results show the fused schedule losing, which is precisely why the autotuner exists.
  • Scope. Transformations center on collectives with clean algebraic structure (AllReduce, AllGather, point-to-point); the evaluation is NVIDIA-only and inference/training loops of transformer workloads.

The evaluation

All experiments ran on 16 NVIDIA DGX-2 nodes — 256 Tesla V100 GPUs total, NVSwitch/NVLink inside nodes and InfiniBand between them. CoCoNet was applied to all three parallelism styles, each against the strongest available hand-optimized baseline.

Three parallelism styles, one compiler

PropertyHand-optimized libraries (NCCL + Apex / Megatron-LM)Fixed distribution strategies (ZeRO / GShard-style)CoCoNet autotuned schedules
Data-parallel optimizer step (Adam / LAMB, tensors 2¹⁰–2³⁰)
End-to-end BERT training (336M / 1.2B / 3.9B parameters)
Model-parallel inference (BERT 3.9B, GPT-2 8.3B)
Pipeline-parallel inference (GPT-2 8.3B, GPT-3 175B)
Programming effort
16 DGX-2 nodes = 256 V100-32GB GPUs; NVSwitch/NVLink intra-node, InfiniBand inter-node. Data-parallel standalone numbers are best-of-1000-iteration averages per tensor size; BERT trained with mixed precision (batch 8192 Adam / 65536 LAMB); pipeline standalone uses GPT-3 dimensions (sequence 2048, hidden 12288), one transformer layer per node.Underlined values explain themselves when clicked.

Two details deserve emphasis. First, the interface optimization — letting kernels take a list of scattered per-layer buffers instead of one contiguous tensor — is mundane but real: NVIDIA’s own BERT script spends time each iteration copying every layer’s gradients into a single buffer just to satisfy AllReduce’s calling convention, then copying results back. Second, the memory effect of the reorder transformation is a capability change, not just a speedup: distributing Adam’s momentum and variance state let a 3.9-billion-parameter BERT train with plain data parallelism, which NV BERT and PyTorch DDP simply cannot do.

Check your understanding

  1. 1. To overlap a matrix multiply with the ring AllReduce that consumes it, CoCoNet must change something about the MatMul kernel itself. Predict what.

  2. 2. The reorder transformation moves Dropout and the residual-add from after an AllGather to before it. Why does this reduce work rather than just reshuffle it?

  3. 3. CoCoNet’s fully-fused schedule is its most aggressive — yet the autotuner refuses it for tensors below about 2¹⁷ elements. What goes wrong?

  4. 4. In Megatron-LM’s pipeline parallelism, the baseline AllReduces within a stage group and then has every rank send its (identical) result to the next stage. Where does CoCoNet’s 12× standalone improvement mainly come from?

Related papers on Machine Learning Systems

See all 212 →