An accelerator has a fixed number of processing units, so a large matrix multiplication runs in WAVES — and if the work does not divide evenly, the final wave is nearly empty while still costing a full wave, which is why the same vendor library reaches 84 percent of an A100's peak on one shape and 7 percent on another; the shapes that matter are now decided at runtime by sentence length or image resolution, far too late for the hours-long search that compilers use to pick tile sizes; this paper (Chinese Academy of Sciences and UNSW Sydney) tunes about forty fixed-size tiles offline, then at runtime CUTS the output into regions and gives each region a DIFFERENT tile size, so the ragged remainder is made of cheap work rather than expensive work — a choice made in roughly two microseconds that lands within 4 percent of an exhaustive search taking 1.6 seconds.
The argument in one glance
-
Problem
The same library is twelve times more efficient on one shape than another
The heavy operations in a neural network are matrix multiplications and convolutions, and how fast they run depends enormously on their SHAPE — how many rows, columns and accumulation steps. On an NVIDIA A100 whose peak is 312 trillion operations per second, the vendor library reaches 262.2 on one shape and only 22.3 on another, even though BOTH are limited by arithmetic rather than memory. That is 84 percent of the machine against 7 percent. Meanwhile shapes have stopped being fixed: a language model sees whatever sentence length arrives, a vision model whatever image resolution, a server whatever batch size it can assemble. The shape is known only at runtime.
-
What people did
Search for good tile sizes, and pre-tune for a declared range of shapes
The standard answer is a tensor compiler. Given a shape, it explores a vast space of ways to break the loops into tiles, measures candidates, and produces an implementation tuned to that exact shape. This works extremely well and is why such compilers are widely deployed. To extend it to varying shapes, systems such as DietCode and Nimble ask the developer to declare a RANGE for each dynamic dimension, then pre-generate a set of programs offline, each covering part of that range, and select among them at runtime.
-
Why it fell short
The search is too slow to run online, and a declared range is a promise you cannot keep
A single search takes roughly 0.33 CPU hours. That is entirely acceptable offline for a fixed shape, and entirely impossible while a request is waiting. Range-based pre-tuning avoids the online cost but inherits two problems. Outside the declared range it does not merely run slowly — it fails. Given 8,192 shapes and a range declared as 1 to 128, only 254 runs completed at all, the rest hitting out-of-bounds errors or unavailable resources. And inside the range it degrades as the range widens, because one program covering more shapes fits each of them less well: measured on the same 128 shapes, the gap grew from 1.68 times to 2.37 times as the declared range grew.
-
This paper's idea
Tune fixed-size tiles offline, then cut the shape into regions that use different tiles
This paper (Chinese Academy of Sciences and UNSW Sydney — Yu, Li, Zhao, Cui, Feng, Xue) splits the loop nest in two. The inner loops become a MICRO-KERNEL, a fixed-size tile tuned offline to fit the accelerator local memory; the outer loops are left open until the shape arrives. About forty micro-kernels are tuned and kept. At runtime the output is carved into regions by one of nine patterns, and each region is given a DIFFERENT micro-kernel — a large one for the bulk, a small one for the remainder — so the ragged tail is made of cheap work instead of expensive work. Nothing is compiled online; the micro-kernels are pre-built binaries whose addresses and trip counts are parameters. Choosing takes about 2 microseconds and lands within 4 percent of an exhaustive search that takes 1.6 seconds.
Why this problem is worth solving
The gap between what a shape should cost and what it does cost is far larger
than most people expect.
A TENSOR
OPERATOR is one heavy step of a neural network, typically a matrix
multiplication or a convolution. A matrix multiplication is described by three
numbers — how many rows, how many columns, and how many accumulation steps —
and running it fast means TILING: breaking the loops into blocks sized so each
block’s data fits in fast on-chip memory and is reused there instead of being
re-fetched. The best tile sizes depend on the shape, which is why compilers
search for them. The consequences of getting this wrong are severe. On an NVIDIA
A100 with a peak of 312 trillion operations per second, the vendor library hits
262.2 on one shape and 22.3 on another, both limited by arithmetic rather than
memory — 84 percent of the machine against 7 percent, a factor of nearly twelve
from shape alone. A hand-written library holds a handful of implementations
chosen for common shapes, and no single implementation suits them all
The mechanism behind those collapses is worth understanding precisely, because
it is what the paper attacks.
An accelerator
has a fixed number of processing units — an A100 has 108 streaming
multiprocessors — and a large operation is divided into many independent blocks
of work that are handed to them. Because the units are finite, the blocks run in
WAVES: one round fills every unit, the next round starts when units free up. If
the work divides into 1,024 blocks and a wave holds 864, then two waves are
needed and the second contains only 160 blocks, leaving over 80 percent of the
machine idle for the whole of it. The cost is charged in full because the
operation is not finished until the last block is. This is why performance falls
off a cliff at particular shapes rather than degrading smoothly. In one
measurement here, growing one dimension from 3,328 to 3,584 — about 8 percent
more work — took the runtime from 0.11 milliseconds to 0.21, almost double,
purely because the extra work started a new wave
What people did before, and where it broke
- Hand-written vendor libraries. Every vendor ships highly tuned routines, and they are excellent on the shapes they were tuned for. But a routine holds a few implementations, and prior work established that no single carefully designed implementation suits all shapes.
- The measured consequence. The same library delivers 84 percent of an A100’s peak on one compute-bound shape and 7 percent on another. Being slow on unusual shapes is not a rare corner case once shapes are chosen by the input.
- Search for the tile sizes. Tensor compilers explore an enormous space of tiling choices for a given shape, measure candidates on real hardware, and emit an implementation tuned to it. This is the strongest known approach and routinely beats hand-written code.
- But the search costs about a third of a CPU hour. That is a fine price to pay once, offline, for a shape you will run millions of times. It is not a price you can pay while a user waits for a sentence to be processed.
- Pre-tune across a declared range. DietCode and Nimble ask the developer to state the range of each dynamic dimension, generate a family of programs offline each covering part of it, and pick one at runtime. This removes the online cost and is a sensible engineering answer.
- But a declared range is a promise about the future. Outside it the system fails rather than degrading: with a range of 1 to 128 and 8,192 shapes presented, only 254 runs completed, the others hitting out-of-bounds errors or unavailable resources. Widening the range fixes that and costs performance inside it, since one program covering more shapes fits each less well.
- The gap. Everyone was choosing between tuning offline for shapes you must know, or paying an unaffordable search online. Nobody had built a system where the expensive part is done offline WITHOUT knowing the shape, leaving only a cheap selection online.
How the work splits between offline and online
Tune fixed-size tiles when there is time, then assemble them when the shape arrives
-
Tile the loops and pick sizes for the shape
The classic approach breaks the loop nest into tiles so each block of data fits in fast memory, and searches for the tile sizes that suit this particular shape. It produces excellent code and takes roughly 0.33 CPU hours, which requires knowing the shape in advance.
-
Cut the loop nest into an offline half and an online half new
The innermost loops become a micro-kernel template, sized to fit the local memory of one processing unit. The outer loops, which decide how many micro-kernel instances run and where, are left unresolved. Only the inner half needs the tuning, and it does not depend on the operator overall shape.
-
Tune a library of fixed-size micro-kernels offline new
Generate candidates with tile sizes stepping in multiples of 16, score them on synthetic workloads across a wide range of dimension sizes, and keep the best forty. For each, fit a piecewise-linear model predicting how long a pipeline of that many instances takes on one processing unit.
-
When the shape arrives, carve the output into regions new
Match the operator against nine representative patterns, derived from a skeleton that divides the output into seven blocks. One pattern covers the whole output with a single micro-kernel; others split it into two or more regions that can each be handled differently.
-
Assign a different micro-kernel to each region new
Try the fixed-size micro-kernels in each region and score the combination with a cost model that multiplies the number of waves needed by the predicted cost of one pipelined task. Abandon any partial assignment already worse than the best complete one. Pad locally so any shape is covered.
-
Dispatch pre-built binaries, compiling nothing new
The micro-kernels were compiled offline with fixed tile sizes, taking tensor start addresses and loop trip counts as parameters. So the online step selects and parameterises rather than generating code, which is why it costs about 2 microseconds rather than hours.
The idea
Move the expensive work to where the shape is not needed. The deadlock in this field was that tuning requires knowing the shape and the shape arrives too late. The escape is to notice that not every part of the tuning depends on the overall shape. How to arrange the innermost loops so a tile of data lands in local memory and is reused there is a question about the TILE and the HARDWARE, not about how many tiles there will eventually be. So that half can be searched exhaustively offline, exactly as a static compiler would, producing a set of tuned fixed-size micro-kernels. What genuinely depends on the shape is only the outer structure — how many instances, arranged how — and that turns out to be answerable by arithmetic rather than search. Splitting a problem so the costly analysis lands on the shape-independent part is the same separate-what-varies-from-what-does-not discipline covered elsewhere on this site where an expensive offline analysis is done once against stable structure and only a cheap lookup remains on the request path.
Make the leftover work cheap instead of trying to eliminate it. The mechanism is more surprising than “pick a better tile size”. Because processing units are finite, work runs in waves, and a shape that overflows a wave boundary by a little pays for a whole wave that is nearly empty. The obvious response is to choose tiles that divide the shape evenly, minimising waves. This paper does something better: it cuts the output into REGIONS and gives each a different micro-kernel, using a large one for the bulk and a small one for the remainder. In the worked example this produces THREE waves where the single-kernel version needed two — and runs 1.21 times faster, because the extra wave is made of short tasks rather than long ones. The tail was not removed; it was made cheap. That inversion, where the design deliberately does worse on the metric everyone watches because that metric had stopped predicting time, is the same lesson covered elsewhere on this site where a system accepts more of the quantity being minimised and wins decisively on the quantity that matters.
Predict instead of measure, and check how much you gave up. Selection online must be nearly free, so measurement is impossible and a cost model is required. Theirs is deliberately simple: for each region, multiply the number of waves needed by the predicted cost of one pipelined task, and add up the regions. Both halves matter, which the paper demonstrates by breaking them. A version using only the wave count picks large micro-kernels and reaches 81 percent of an exhaustive search; a version using only the pipeline cost picks small ones and reaches 72 percent; the combination reaches 96 percent. The comparison against exhaustive search is the honest way to report this, because it separates “our model is good” from “our search space is good”. Two microseconds for 96 percent of what 1.6 seconds achieves is a clean statement of the trade. Replacing an unaffordable measurement with a cheap estimate and then quantifying the loss is the same bound-what-you-cannot-count discipline covered elsewhere on this site where an approximation is chosen precisely because it keeps the decision tractable, and its accuracy is reported rather than assumed.
Cutting a matrix multiplication in two
The paper's own case study, on an NVIDIA A100
Multiplying a 4096-by-4096 matrix by a 4096-by-1024 one. All numbers measured or derived in the paper.
Step 1 of 5
The machine runs work in waves
The A100 has 108 streaming multiprocessors. The chosen micro-kernel uses thread blocks of 8 warps and reaches only 12.5 percent occupancy, so 8 warps are active per multiprocessor. A full wave therefore holds 108 times 8, which is 864 warps. Anything beyond that starts a second wave.
occupancy is limited because each block holds a large tile of data in on-chip memory, which restricts how many blocks fit per multiprocessor the operation is not finished until its last block is, so a wave costs full price however few blocks are in it
Why it works
- Tuning the innermost loops depends on the tile and the hardware, not on the operator’s overall shape → that half of the search can be done offline without knowing anything → so the 0.33 CPU hours are paid once, in advance, instead of on the request path.
- Micro-kernels are compiled offline into binaries whose addresses and trip counts are parameters → the online step selects and parameterises rather than generating code → costing about 2 microseconds per shape rather than hours.
- Finite processing units mean work runs in waves, and a nearly empty final wave costs nearly a full one → giving the remainder a smaller, faster micro-kernel makes that wave cheap → so three waves of mixed work beat two waves of uniform work by 1.21 times.
- The cost model multiplies waves needed by predicted cost per pipelined task → neither factor alone is sufficient, since one favours large tiles and one small → and combining them reaches 96 percent of exhaustive search against 81 and 72 percent for each half alone.
- Local padding guarantees a valid micro-kernel combination for every shape → no shape range has to be declared in advance → producing 8,192 valid runs out of 8,192 where a range-based system managed 254.
What it costs (trade-offs and scope)
- The benefit depends on the shape being awkward. Gains are largest for small shapes and for operators whose shapes vary frequently over a wide range, because that is when the wave imbalance is pronounced. On a large language model the operator gains are 1.08 to 1.24 times and end-to-end gains fall from 1.05 to 1.01 times as batch size grows from 1 to 8, since bigger batches make shapes rounder.
- Knowing the range would still help. The authors note that when shape ranges ARE known at compile time, a better-matched micro-kernel set and a refined cost model would do better. Giving up that knowledge is a real cost, paid for robustness.
- The online set is fixed and pruned. Forty micro-kernels are kept out of a much larger generated space, chosen by average performance on synthetic workloads. A shape poorly served by all forty has no recourse, and the hyperparameters were set empirically per platform.
- It optimises operators, not graphs. Operator fusion and other graph-level optimisations are listed as future work, so this composes with rather than replaces those gains.
- Convolution goes through matrix multiplication only. Alternative algorithms such as Winograd are not implemented, which the authors flag; for fairness they also force the baseline libraries to use the same route.
The evaluation
Testing covered two quite different accelerators — an NVIDIA A100 with Ampere Tensor Cores, and a Huawei Ascend 910 with Da Vinci Cores — using each vendor’s own library as the baseline. The shape sets were large: 1,433 matrix multiplication shapes from DeepBench and from real models, and 5,485 convolution shapes. End-to-end tests replaced the operators inside eight models, driven by 150 sentences of length 5 to 500 for the language models and by eight batch sizes and ten resolutions for the vision models. Importantly, the reported end-to-end times INCLUDE the cost of running the selection model, not only the kernels it picks.
Running a tensor operator whose shape arrives at runtime
| Property | Hand-tuned vendor library | Pre-tune across a declared range | Offline tiles, online composition |
|---|---|---|---|
| Shapes handled | |||
| Cost paid at runtime | |||
| Tiles used per operator | |||
| What happens to the ragged tail | |||
| Measured speed |
Check yourself
Check your understanding
1. A vendor library reaches 262.2 trillion operations per second on one matrix multiplication and 22.3 on another, and BOTH are limited by arithmetic rather than memory bandwidth. Predict what explains a gap that large.
2. Splitting the output between two tile sizes produces THREE waves where a single tile size needed two, and yet it runs 1.21 times faster. Predict why more waves is faster.
3. Compilers such as DietCode pre-tune programs for a range of shapes declared by the developer. Predict why widening that declared range makes them slower even for shapes INSIDE it.
4. The cost model multiplies the number of waves needed by the predicted cost of one pipelined task. The authors also test each factor alone. Predict what each half does wrong on its own.
Sources
- Optimizing Dynamic-Shape Neural Networks on Accelerators via On-the-Fly Micro-Kernel Polymerization (ACM Digital Library, DOI 10.1145/3620665.3640390) — ASPLOS 2024; the full 16-page paper, read in full for this explainer. Published open access under a Creative Commons licence.