Which loop order wins at sparse-times-sparse matrix multiply depends on the matrix — and even on the region inside the matrix — so Spada makes the dataflow a dial, sliding a reshapeable window over the compressed rows and letting a few profiled passes per region choose the window's shape.
The argument in one glance
-
Problem
One kernel, wildly different customers
Sparse-times-sparse matrix multiplication (SpGEMM) powers graph analytics, scientific solvers, economic models, and compressed neural networks. The inputs could hardly differ more: graph matrices reach 10¹³ elements at 0.0001% density, while pruned network layers are small but 10–90% dense. Irregular structure starves both compute units and memory bandwidth on CPUs and GPUs.
-
What people did
Build an accelerator around one dataflow
SIGMA and others hard-wire inner product; OuterSPACE and SpArch hard-wire outer product; MatRaptor and GAMMA hard-wire the row-based (Gustavson) scheme. Each loop order optimizes one kind of data reuse — outputs, inputs, or a balance — and each shines on the sparse patterns that reward that reuse.
-
Why it fell short
The winning dataflow follows the data, not the chip
Across 27 real workloads, no fixed design wins consistently: the outer-product machine leads on compressed fully-connected layers, the row-based machine on convolutions and attention, and the inner-product machine collapses on very sparse inputs (up to 38× slower). Worse, patterns change within a single matrix — region by region — so even a per-matrix choice is too coarse.
-
This paper's idea
A reshapeable window makes the dataflow continuous
Spada blocks the row-based loop nest into a 2D window over the compressed rows of A. The window’s shape α×β is a dial: 1×8 behaves like row-based (output reuse), 8×1 like outer-product (input reuse), with mixed shapes in between. Hardware lanes regroup in a few cycles to match any shape, and a profiling pass over the first rows of each matrix region picks the shape for the rest. Spada matches or beats the best fixed design almost everywhere — 1.44× geomean over the strongest baselines.
Why this problem is worth solving
sparse matrix-matrix multiplication, C = A×B where
both inputs are stored compressed — typically CSR (compressed sparse
rows: per row, a list of (column, value) pairs) or CSC (the
column-major mirror)
Accelerators answered, but each froze one dataflow — one ordering of the three loops over M, N, and the reduction dimension K — into silicon:
- Inner product (reduction innermost): each output element is a dot product. Perfect output reuse, but the two operands must be intersected — most fetched pairs share no index in sparse data, so bandwidth is wasted on misses.
- Outer product (reduction outermost): each column of A times
each row of B yields a partial matrix. Each B row is fetched
exactly once — best input reuse — but the flood of
can outgrow on-chip memory and spill.
partial sums — intermediate products that must later be merged into final output values
- Row-based / Gustavson (reduction in the middle): build one output row at a time from the B rows matching that A row’s nonzeros. Psums stay small and on-chip, everything stays in CSR — but B rows are gathered irregularly, with reuse only if nearby A rows happen to share column indices.
The paper’s measurements make the consequence concrete: the inner-product design trails by 38× on average across the suite; the outer-product design wins compressed fully-connected layers while the row-based design wins convolutions and attention; and within single matrices, different regions prefer different shapes. A fixed dataflow is a standing bet against half your workload.
What people did before, and where it broke
SIGMA, OuterSPACE, SpArch, MatRaptor, GAMMA — each a strong point design, each rigid. (This site’s Flexagon explainer covers the closest prior spirit: supporting all three dataflows on one substrate, switched per DNN layer by an offline mapper.) Three gaps remained. First, the three classic dataflows have drastically different execution behaviors — inner product walks A by rows and B by columns, outer product the opposite — so naively unioning them in hardware means either replicated datapaths or heavyweight reconfiguration. Second, choosing a dataflow requires knowing the sparse pattern, which for arbitrary matrices (unlike DNN layers with stable statistics) is unknown until runtime — and even then, patterns shift within a matrix. Third, whatever mechanism adapts must not cost much: SpGEMM is memory-bound, and area belongs to the cache.
Spada: one template, many shapes
-
Window-based adaptive dataflow (WA) new
Blocks the row-based loop nest (order mo-ko-ki-mi-n) so the inner loops form an α×β window over A’s compressed nonzeros — α consecutive rows, β elements along the reduction dimension. α×β is fixed at the lane count (8); the SHAPE is the dial: 1×8 reduces to row-based, 8×1 to outer-product-style column processing, 2×4 and 4×2 sit between.
-
Multiply PEs (MPEs) new
Two MPEs of 8 lanes each: per lane a B-row fetcher, a multiplier, and a P queue buffering products; a sort array per lane pair dynamically rebalances load; a bitonic network plus flexible reduction tree merges each group of β lanes into one psum row. Regrouping lanes to a new window shape touches only queue thresholds, the network’s output level, and the tree — a few stall cycles between passes.
-
Add PEs (APEs) + psum tracker new
16 APEs (radix-8 comparator trees with accumulators) merge psum rows into final CSR output rows; a scheduler table tracks every output row’s pending psum addresses in circular buffers and spawns merge tasks as they accumulate.
-
Global cache with RidxLRU new
A shared 1.5 MB FiberCache-style cache holds B rows and psums. Replacement is RidxLRU: each cached B row remembers the most recent A row that used it, and rows tied to older A rows are evicted first — keeping whole rows useful for the multi-row window where plain LRU would shred them.
-
Window adapter (profiling-guided) new
Partitions A into bands — runs of contiguous rows with similar row lengths (thresholds on length jumps) — then, per large band, profiles one pass with each shape (1×8, 2×4, 4×2, 8×1) and locks the winner for the rest; small bands use early-stopping cost descent. No analytical model, no offline knowledge.
The idea
Don’t union the dataflows — parametrize one. A naive multi-dataflow machine would implement inner-, outer-, and row-based execution separately and switch — expensive, slow, and mostly redundant. Spada instead starts from the row-based scheme (already the best all-rounder, with consistent CSR formats and small psums) and applies the oldest trick in dense linear algebra: loop blocking. Blocking the row and reduction loops turns the per-element traversal into an α×β window over A’s compressed nonzeros. The endpoints of the shape space are the classic dataflows: α=1 recovers row-based behavior (many psums merged immediately — output reuse), β=1 recovers outer-product behavior (a column of A elements sharing fetched B rows — input reuse). The inner-product scheme is deliberately left out: its ineffectual index intersections are a pure liability on sparse data.
Make reconfiguration nearly free by making modes similar. Because every shape runs the same template — fetch window, fetch matching B rows, multiply, merge groups of lanes — switching shapes only regroups lanes: P-queue thresholds, the bitonic sorting network’s output level, and the flexible reduction tree change; fetchers and multipliers don’t. Reconfiguration happens between passes (a few stall cycles, hidden behind B prefetching), not per element.
Balance load where sparsity makes it uneven. Lanes process B rows of wildly different lengths. Each lane pair shares a sort array that each cycle picks the two globally smallest column indices from either lane’s stream — so a lane that finished early transparently helps its neighbor, keeping merge order intact. Empty A rows are skipped outright via the CSR offsets.
Let the matrix teach you its shape — cheaply. Analytical prediction of reuse would require scanning A’s index structure (as expensive as doing the work) and modeling cache interference. Spada instead exploits two empirical regularities: within a band of contiguous rows, index overlap between neighbors is stable, and band boundaries correlate with jumps in row length — detectable from the CSR offsets array alone. So the window adapter partitions rows into bands by row-length discontinuities, profiles the first rows of each large band under all four shapes, and applies the winner to the band’s remainder. Small bands use cost-descent: stop profiling the moment a shape performs worse than its predecessor.
One matrix, two personalities, one adaptive pass
A×B in CSR; window α×β = (rows of A) × (elements along reduction dim), total fixed at 8 lanes. MPE = multiply PE; APE = add/merge PE; psum row = partial result for one output row. Band = contiguous A rows with similar row lengths. The matrix is illustrative: a graph-like region of short overlapping rows followed by a denser block region.
Step 1 of 6
Band detection from the offsets array
The window adapter reads only A’s CSR offsets — row lengths — and cuts bands where consecutive lengths jump by more than 5 (absolute) or 2× (relative). Rows 0–799 have 4–6 nonzeros each: band 1. Rows 800+ carry hundreds: band 2. No values or column indices are touched; this costs almost nothing.
band 1: rows 0–799 (short, uniform) band 2: rows 800+ (long) thresholds: T_abs=5, T_rel=2, T_band=128 rows
Why it works
- The classic dataflows differ, at core, in loop order → blocking the row-based nest exposes that difference as two block sizes → one hardware template spans the reuse spectrum, with the classic schemes as endpoints of a dial rather than separate machines.
- All shapes share the fetch-multiply-merge pipeline → mode switches are lane regroupings, not datapath changes → reconfiguration costs cycles, not area, so adapting per region is affordable.
- Sparse patterns are locally stable (bands) and band boundaries announce themselves in row lengths → profiling a band’s first rows predicts its remainder → the system learns the right shape from the matrix itself, with no offline analysis and no model of cache behavior.
- Load imbalance is the tax on parallel sparsity → pairwise sort arrays and task-aware P queues absorb row-length variance at O(n²) cost only within pairs → multipliers, the scarce resource, stay busy.
- Memory dominates SpGEMM → the shared cache plus RidxLRU keeps both B rows and psums useful across window shapes → adaptivity comes at GAMMA-equivalent area (6.32 vs 6.13 mm²) instead of SpArch’s 2×.
What it costs
- Profiling passes run suboptimal shapes. Each large band spends its first rows trying all four shapes; small bands may settle on a suboptimal shape via early-stopped cost descent.
- Reconfiguration stalls. Draining P queues between passes of different shapes costs cycles, partly hidden by prefetching.
- The window is small. Shapes are limited to the 8-lane budget (1×8…8×1); patterns that would reward much larger windows are out of reach, and sort-array balancing only spans lane pairs.
- Two FC layers still lose. On compressed fully-connected layers the gap to SpArch’s massive merger remains — adaptation inherits the row-based template’s limits rather than transcending them.
- Simulation + partial RTL. Performance comes from a cycle-accurate simulator with synthesized key components, not a full chip.
The evaluation
Twenty-seven workloads stress the diversity claim: 18 SuiteSparse matrices spanning densities from 2.5×10⁻⁷ to 0.49 and 4 to 5,162 nonzeros per row (squared, or multiplied by their transpose, following GAMMA), plus compressed ResNet-50, AlexNet, and pruned BERT layers. Spada is compared against re-implementations of the three fixed-dataflow flagships — SIGMA (inner product), SpArch (outer product), GAMMA (row-based) — with equal multiplier counts.
Fixed dataflows vs the adaptive window
| Property | SIGMA (inner product) | SpArch (outer product) | GAMMA (row-based) | Spada (adaptive window) |
|---|---|---|---|---|
| Reuse bet built into the hardware | ||||
| Geomean performance (27 workloads, equal multipliers) | ||||
| Area (scaled to 28 nm) and efficiency | Excluded (reported under different conditions) | |||
| What it needs to know in advance | Nothing — and pays for it | Nothing | Nothing |
Check your understanding
1. A band of A has short rows whose column indices heavily overlap between consecutive rows. Predict the window shape Spada’s profiler will select.
2. Why does Spada derive its adaptive dataflow by blocking the row-based scheme, rather than building hardware that implements inner-, outer-, and row-based dataflows and switching among them?
3. With a tall window active, Spada replaces plain LRU with RidxLRU, which evicts B rows associated with the OLDEST A row first. What failure of LRU does this fix?
4. Why does Spada choose window shapes by profiling actual execution instead of computing the best shape from the matrix’s structure?