Home · ASPLOS 2024 · Accelerators & Hardware Design

DTC-SpMM: Bridging the Gap in Accelerating General Sparse Matrix Multiplication with Tensor Cores

Ruibo Fan, Wei Wang and Xiaowen Chu

Conference
ASPLOS 2024
Published
2024
Pages
253-267
Citations
39

GPUs' fastest silicon — Tensor Cores — only multiplies dense tiles, and the reigning attempt to feed them general sparse matrices ran below 8% pipeline utilization while burning 10× more instructions on coordinate math than on math — DTC-SpMM closes four measured gaps with a byte-indexed compression format, similarity-based row reordering matched to tile size, PTX-level shared-memory bypass with asynchronous double buffering, and a scheduler-simulating load balancer, beating cuSPARSE by 1.5× or more on 79.5% of 414 matrices and speeding end-to-end GNN training up to 1.91×.

The argument in one glance

  1. Problem

    The GPU's fastest units refuse sparse food

    SpMM — a sparse matrix times a dense one — is the workhorse of graph neural networks (the adjacency-times-features step) and scientific computing. Meanwhile GPUs concentrated their FLOPS in Tensor Cores: units that multiply small DENSE tiles (e.g., 16×8) at far higher rates than CUDA cores, in DL-friendly precisions like TF32. Real sparse matrices — millions of rows, over 95% zeros, no structure — cannot feed dense tiles without mostly multiplying zeros.

  2. What people did

    Structure the sparsity, or condense it

    Two lines of attack. DL-pruning methods (Block-SpMM, VectorSparse, Magicube, SparTA, Flash-LLM) constrain WHERE zeros fall so tiles stay dense — fine for pruned weights, useless for arbitrary graphs. For truly general matrices, TC-GNN pioneered Sparse Graph Translation: slide nonzeros leftward within 16-row windows, packing them into fewer "dense" TC blocks, then run those on Tensor Cores.

  3. Why it fell short

    Measured: under 8% of the Tensor Core pipeline actually used

    The paper profiles TC-GNN on an RTX 4090 and finds four compounding gaps. Its format stores three 32-bit arrays per nonzero — 168% more memory than plain CSR, for a memory-bound kernel. Its condensing underdelivers: TC blocks average under 16 nonzeros — under TWO per column — so tiles stay mostly empty and data reuse evaporates. Its kernel stages B tiles through shared memory that irregular sparsity can never reuse, and spends 10× more integer instructions computing coordinates than doing tensor math — pipeline utilization below 8%. And whole SMs sit idle on matrices whose row windows differ in work. Structured-sparsity methods fare worse: OOM or "not supported" on million-row matrices.

  4. This paper's idea

    Fix the format, the layout, the pipeline, and the schedule — each measured

    DTC-SpMM attacks all four gaps: ME-TCF stores each nonzero's position in ONE byte (a 16×8 tile has only 128 slots), ending up smaller than CSR; TCU-Cache-Aware reordering groups Jaccard-similar rows into clusters capped at exactly 16 (the tile height) then re-clusters for L2 locality; the runtime kernel bypasses shared memory entirely with PTX-level mma and vectorized loads straight to registers, double-buffers sparse tiles with async copies, and precomputes indices; a simulation-based Selector predicts — without running — whether a strict load-balanced kernel variant will pay. Result: faster than cuSPARSE by ≥1.5× on 79.5% of 414 SuiteSparse matrices, up to 3.29× on long-row matrices, and 1.22–1.91× end-to-end GNN training speedups.

Why this problem is worth solving

sparse matrix-matrix multiplication — C = A×B where A is sparse and B, C are dense. The kernel of GNN propagation (adjacency × features), sparse solvers, and much of scientific computing; memory-bound, with total work proportional to A’s nonzeros times B’s width
sits at an awkward moment in hardware history. GPU vendors have shifted the overwhelming majority of new FLOPS into
dedicated matrix-multiply units in NVIDIA GPUs since Volta. A warp of 32 threads collectively issues an MMA (matrix multiply-accumulate) over small fixed-shape tiles — for TF32 precision, 16×8×8 or 16×8×4 — at throughput far beyond CUDA cores. Designed for dense deep learning; they neither skip zeros nor accept irregular layouts
, so a kernel that cannot use them is locked out of most of the chip’s arithmetic. For dense workloads this is free money; for sparse ones it is a wall, because a 16×8 tile of an adjacency matrix with 99.9% sparsity is almost entirely zeros — and Tensor Cores multiply the zeros at full price.

The stakes are concrete: every GCN training step multiplies the graph’s adjacency matrix by a feature matrix, and frameworks like DGL and PyG spend much of their epoch time there. If the “leftover” CUDA-core silicon remains the only home for SpMM, sparse workloads fall further behind with each hardware generation. TC-GNN proved the direction viable — condense the sparsity until tiles are dense enough — but the measured reality (sub-8% pipeline utilization, slower than cuSPARSE on long-row matrices) shows that a viable direction and a working system are separated by exactly the kind of systematic, layer-by-layer engineering this paper supplies. Its opening move — measuring why the state of the art underperforms, with profiler evidence per gap — is as instructive as the fixes.

What people did before, and where it broke

  • CUDA-core SpMM (cuSPARSE, Sputnik, GE-SpMM, ASpT, SparseTIR): mature, well-balanced, format-savvy — and capped by CUDA-core throughput. These are the incumbents to beat, and cuSPARSE remains genuinely hard to beat.
  • Structured-sparsity Tensor Core methods (Block-SpMM, VectorSparse, CLASP, Magicube, SparTA, Flash-LLM): co-design the sparsity pattern with pruning so nonzeros cluster into blocks or vectors. They excel at their target — pruned weight matrices, thousands of rows, 60–90% sparsity — and collapse outside it: BELL-format padding OOMs on large matrices, SparTA caps at 50K rows, Flash-LLM converts from dense storage and OOMs on YeastH. Graphs don’t prune to order.
  • TC-GNN, the general-SpMM pioneer: Sparse Graph Translation compresses each 16-row window leftward into condensed TC blocks. Four measured gaps: (1) its TCF format keeps three 32-bit arrays per nonzero — +168% memory vs
    compressed sparse row — the standard sparse format: one row-offset array plus one column index and one value per nonzero; the memory baseline any new format must justify itself against
    — on a memory-bound kernel; (2) condensing depends on luck — without reordering, MeanNnzTC (average nonzeros per 16×8 block) stays below 16, i.e., under two per column, wasting both compute and the chance to reuse loaded B rows; (3) the kernel inherits dense GEMM’s shared-memory staging although compressed-irregular A makes B-tile reuse impossible, and computes access coordinates on the fly — 10× more integer multiply-add instructions than tensor instructions; (4) work per row window varies wildly, and nothing balances it.

How DTC-SpMM works

DTC-SpMM's pipeline: two offline passes, one decision, one kernel

  1. TCU-Cache-Aware reordering (offline, optional) new

    Hierarchy I: rows with similar column patterns (Jaccard similarity via MinHash/LSH) merge greedily into clusters CAPPED AT 16 ROWS — exactly the TC-block height, so similarity is spent where it densifies tiles. Hierarchy II: clusters re-cluster (cap = SM count) so concurrently-scheduled thread blocks touch overlapping B rows, restoring the L2 locality the small caps sacrificed. GPU-accelerated MinHash cuts hours to minutes.

  2. ME-TCF format conversion new

    Four arrays: row-window offsets, TC-block offsets, per-nonzero LOCAL indices — one byte each, since a 16×8 tile has only 128 positions — and the original column ids per block column. Net: ~6% smaller than CSR as-is, ~30% smaller after reordering (fewer blocks), vs TC-GNN's +168%. Conversion runs as GPU kernels, 72–101× faster than TC-GNN's CPU path.

  3. Simulation-based Selector new

    Estimates — without executing — whether the load-balanced kernel variant will pay, by simulating the GPU's thread-block scheduler (a validated round-robin model plus occupancy) to compute the makespan of the unbalanced schedule vs the perfectly-balanced one. If the ratio exceeds 1.2, launch the balanced kernel; otherwise the base kernel (strict balancing costs ~22% on already-uniform matrices).

  4. Runtime kernel: bypass, buffer, precompute new

    Per iteration: asynchronously prefetch the NEXT sparse A tile into a shared-memory double buffer (cp.async) while Tensor Cores compute the current one; fetch B data with vectorized 128-bit loads STRAIGHT into registers — no shared-memory staging — using PTX-level mma (16×8×4 TF32) instead of the C-level WMMA API that mandates contiguous loads; precompute indices to strip coordinate arithmetic from the loop; fix the register-layout mismatch from vectorized loads with a one-time remapping when writing C.

  5. Balanced variant (when selected) new

    Instead of one thread block per row window, every thread block takes exactly 32 TC blocks — wherever they come from — with atomic accumulation across windows. Perfect balance, bought with atomics: +54% on the pathologically imbalanced ddi graph, a net loss on uniform matrices; hence the Selector.

From HKUST (Guangzhou) and HKUST (ASPLOS 2024). TF32 precision throughout (preferred over FP16 for GNN/scientific numerics); evaluated on RTX 4090 (Ada) and RTX 3090 (Ampere); open-source.

The idea

A tile is an address space of 128 — index it with a byte. The format insight is almost embarrassing in retrospect: once nonzeros are assigned to 16×8 TC blocks, their position within the block needs only 7 bits, yet TC-GNN spent three 32-bit integers per nonzero on global coordinates. ME-TCF stores the one-byte local index plus per-block metadata, making the TC-oriented representation smaller than CSR — which matters doubly for a memory-bound kernel: less footprint AND higher arithmetic density per byte moved. The general principle: when you impose a tiling, re-derive your coordinates relative to the tiles.

Reorder for the tile, then re-reorder for the cache. Condensing quality — MeanNnzTC — is the single number that decides whether Tensor Cores see work or zeros, and it depends on which rows share a window. Classic reordering (METIS, Louvain) optimizes cache behavior, not tile density; LSH-based grouping with large clusters dilutes similarity. DTC-SpMM’s two-level design derives both caps from hardware constants: clusters of at-most-16 similar rows (the tile height — merging beyond it admits dissimilar rows and thins every tile), then clusters-of-clusters sized to the SM count so the thread blocks running concurrently touch similar B rows and the L2 cache sees locality again. Each level fixes what the other breaks: alone, the 16-row cap loses to LSH64 on L2 hit rate; together they beat it while lifting MeanNnzTC 1.13–1.72×.

Delete the staging that dense GEMM needs and sparse GEMM doesn’t. Dense GEMM stages tiles in shared memory because both operands get reused across the tile — but with compressed, irregular A, each loaded B fragment serves exactly one TC block, so staging is pure overhead standing between global memory and the Tensor Core. The C-level WMMA API structurally forces that staging (its fragment loader requires contiguous, regularly-strided memory); dropping to PTX-level mma frees the kernel to issue vectorized 128-bit global loads directly into the registers the MMA expects. Two careful sub-decisions complete the move: strided (rather than sequential) thread access, because the warp-transpose shuffle sequential access requires costs 10.7 cycles against the MMA’s own 16; and a register layout mismatch created by vectorization is resolved not per-iteration but ONCE, by remapping when C is finally written. The remaining latency — sparse A tile loads — hides behind computation via cp.async double buffering, the only async path the hardware offers (global-to-register prefetch doesn’t exist before Hopper).

Predict the schedule instead of guessing the strategy. Strict balancing helps imbalanced inputs (+54% on ddi) and hurts uniform ones (−22%), so the right choice is input-dependent — and DTC-SpMM makes it by simulating the GPU’s thread-block scheduler (a community-validated model of NVIDIA’s round-robin placement plus measured occupancy) to estimate the unbalanced makespan analytically. The approximation ratio against perfect balance, thresholded at 1.2, picks the kernel. No profiling run, no autotuning sweep — a scheduling-theory calculation.

One SpMM through DTC-SpMM

Multiplying a graph's adjacency matrix by its feature matrix

A = sparse adjacency (millions of rows, >95% sparse); B = dense features (N = 128–512 columns); TC block = 16×8 tile consumed by one MMA sequence; MeanNnzTC = average nonzeros per TC block; row window = 16 consecutive rows sharing TC blocks. Numbers from the RTX 4090 evaluation.

Step 1 of 7

Offline: similar rows move in together

MinHash-LSH proposes row pairs with high column-set overlap; a priority queue greedily merges the most similar pairs into clusters, freezing each cluster at 16 rows — tile height. A second pass re-clusters the clusters (cap: SM count) so concurrent thread blocks will want the same B rows. On long-row matrices this lifts tile density 1.72×.

density gain beats METIS, Louvain, and LSH64
GPU MinHash: minutes, not hours; amortized over thousands of SpMMs

Why it works

  • A 16×8 tile has 128 positions → local indices fit one byte → the TC-oriented format drops below CSR’s footprint, and a memory-bound kernel converts saved bytes directly into speed.
  • Tile density (MeanNnzTC) determines both wasted MACs and B-row reuse → reordering with the cluster cap set to the tile height spends similarity exactly where tiles form → 1.13–1.72× denser tiles; and re-clustering to SM count restores the L2 locality the small cap costs — each hierarchy covering the other’s blind spot.
  • Irregular compressed A makes cross-block B reuse impossible → shared-memory staging is pure latency, and the WMMA API that mandates it is the real obstacle → PTX-level mma plus vectorized direct-to-register loads deletes the staging, and deferring layout repair to the C writeback keeps the fix off the critical path.
  • Coordinate arithmetic competed with tensor math for issue slots (10× IMAD over HMMA) → precomputation moves it offline → the instruction stream refills with MMAs, and utilization on long-row matrices rises 17×.
  • Whether strict balancing pays depends on the input’s window skew → a validated scheduler model computes the makespan analytically → the 1.2-threshold Selector captures ddi’s +54% without paying uniform matrices’ −22%.

What it costs

  • Preprocessing that must amortize. Format conversion costs 1.5–14.5 SpMM executions; the Selector costs 25–42% of one; reordering (optional) costs minutes. Iterative workloads — GNN training runs thousands of SpMMs on a fixed matrix — amortize all of it; workloads whose sparse matrix changes every call (graph-sampling GNN training) should stay on cuSPARSE/HP-SpMM, as the authors say plainly.
  • PTX-level fragility. The kernel is welded to NVIDIA’s instruction-level details — mma shapes, cp.async, register layouts, a reverse-engineered scheduler model, occupancy constants. Portability across vendors (or even architecture generations) is real engineering, though the authors argue the principles transfer.
  • TF32 only (for now). Chosen deliberately for GNN/HPC numerics; other precisions are future work.
  • Reordering permutes the matrix. Downstream consumers must work in (or map back from) the reordered space — standard for the technique, but a real integration step.
  • Selector is a heuristic. The 1.2 threshold comes from offline calibration on synthetic matrices; the scheduler model is community-validated but unofficial.

The evaluation

Two GPUs (RTX 4090/Ada, RTX 3090/Ampere), 414 SuiteSparse matrices with ≥1M nonzeros plus 8 representative GNN matrices split into short-row (Type I) and long-row (Type II) classes; B widths of 128–512; nine baselines spanning CUDA-core (cuSPARSE, Sputnik, SparseTIR), TC-based general (TC-GNN), and TC-based structured (Block-SpMM, VectorSparse, Flash-LLM, SparTA) approaches. Breakdown studies attribute gains to each technique, and a GCN case study closes the loop end-to-end.

General SpMM on modern GPUs: three ways

PropertycuSPARSE (CUDA cores)TC-GNN (prior TC state of the art)DTC-SpMM
Where the FLOPS come from
Sparse format footprintCSR baseline
Tile density (MeanNnzTC)N/A — no tiles
Load balanceMature row-level strategies
Overall performanceThe bar to clear — TC-GNN missed it on long-row matricesCompetitive only on short-row (Type I) matrices
RTX 4090, CUDA 12.1, TF32, N ∈ {128, 256, 512}, 1000-run averages; 414 SuiteSparse matrices (≥1M nonzeros) + 8 representative GNN matrices (Type I short-row / Type II long-row). RTX 3090 shows the same ordering with slightly smaller margins. GCN case study: 2-layer model, 200 epochs, including format-conversion time for DTC-SpMM.Underlined values explain themselves when clicked.

Check yourself

Check your understanding

  1. 1. TC-GNN already condensed sparse matrices into 16×8 TC blocks, yet its measured MeanNnzTC stayed below 16 — under two nonzeros per column. Predict the two distinct ways this low density hurts performance.

  2. 2. Dense GEMM kernels stage tiles in shared memory, and TC-GNN copied this pattern. Why does DTC-SpMM DELETE the staging for B — and what made deleting it require dropping from CUDA's WMMA API to PTX?

  3. 3. DTC-SpMM ships TWO kernels — base and strictly-balanced — and picks per input matrix using a "simulation-based Selector." Why not always run the balanced kernel, and how does the Selector decide without running anything?

  4. 4. The authors state plainly that DTC-SpMM is the WRONG choice for graph-sampling GNN training, recommending cuSPARSE or HP-SpMM there. What boundary does this draw?

Sources

Related papers on Accelerators & Hardware Design

See all 118 →