Mutation Analysis for GPU-Kernel Benchmark Oracles
A benchmark’s correctness checker used to be bookkeeping. For GPU-kernel generation it has become infrastructure that carries load: KernelBench-style verdicts
The community has noticed. KernelBench-Verified
Software engineering solved this measurement problem fifty years ago. Mutation analysis
Everything is released as KernelBench-M: the mutant pool, the witnesses, the suites, and the pipeline that regenerates every number.
Two examples set the tone.
A zeros-output softmax passes. One KernelBench problem applies softmax across \(d = 393{,}216\) elements. Under the official torch.rand inputs the reference output averages \(2.5 \times 10^{-6}\) per element, while the check accepts any output within \(\text{atol} = 10^{-2}\), four thousand times the signal. A kernel that returns all zeros passes every official trial. This is not an edge case; the blind band widens systematically with reduction size.
No reseeding can help. The official inputs are drawn from \([0, 1)\). Every element is positive, so deleting a ReLU is the identity on the entire support; \(\exp(x)\) cannot overflow for \(x < 1\), so removing softmax’s max-subtraction stabilizer is unobservable. Survival under such inputs is a property of the distribution, not of sampling luck. More random trials measure the same blindness more confidently.
A benchmark problem supplies a PyTorch reference \(f\) and an input generator; a submission \(\hat f\) passes if allclose(f(x), f̂(x); atol, rtol) holds on a few draws. We want to score the protocol, inputs plus tolerance, by the fraction of faults it detects. Four obstacles separate this domain from classical mutation testing and from test-augmentation work on Python benchmarks such as EvalPlus
blockIdx permutation relabels independent work); some are real faults no numerical oracle can see (an out-of-bounds write landing in allocator slack). Scoring suites against unkillable rows deflates every protocol equally and informs about none. We admit a mutant into the denominator only with a kill witness: a concrete, validity-gated input on which it verifiably fails.The pipeline that survives these obstacles:
flowchart LR
A["<b>Substrate</b><br>188 gate-verified<br>CUDA implementations"] --> B["<b>Mutate</b><br>124 deterministic rules<br>6 fault families<br>10,303 mutants"]
B --> C["<b>Filter</b><br>NVRTC compile, hash images,<br>quarantine crashers<br>8,253 distinct"]
C --> D["<b>Witness</b><br>kill search over<br>validity-gated inputs<br>7,384 witnessed"]
D --> E["<b>Kill matrix</b><br>mutants × inputs"]
E --> F["Score protocols"]
E --> G["Synthesize suites"]
E --> H["Fault taxonomy"]
Mutation needs source, and KernelBench’s references bottom out in closed cuDNN/cuBLAS binaries, so for each problem we maintain a correct CUDA implementation used only as a mutation target. Substrates are LLM-authored and admitted by an automated gate that checks them against the official reference on every suite; the oracle remains the benchmark’s own reference. The 124 rules span six families: classical arithmetic and relational replacements; GPU-specific operators in the lineage of MUTGPU __syncthreads to __syncwarp, ceil-to-floor grid division, bounds-guard deletion, fp16 accumulation, index-axis swaps); and LLM-mined fine-grained families (argmax tie-breaking, perturbed polynomial constants, shifted piecewise thresholds). Hashing compiled images, the GPU analogue of Trivial Compiler Equivalence
| Stage | Operators (levels 1–2) | Architectures (level 3) |
|---|---|---|
| Problems (substrates) | 188 | 48 |
| Mutants generated | 10,303 | 12,589 |
| − non-compiling | 1,594 | 38 |
| − duplicate compiled image | 279 | 155 |
| − equivalent to original | 177 | 35 |
| Distinct mutants | 8,253 | 12,361 |
| of which witnessed | 7,384 | 8,519 |
| Witnessed mutants missed by the official inputs | 1,248 | 1,476 |
| Miss rate | 16.9% | 17.3% |
The kill matrix comprises over 120,000 mutant–input evaluations; the whole campaign used roughly 30 GPU-hours on a single H100.
Headline. The official protocol, each problem’s own get_inputs() with five seeds, detects 6,136 of the 7,384 witnessed faults: 83.1%. One in six provably-detectable faults survives. Bootstrap resampling over problems shows the pooled rate is a population property, not sampling noise: the 90% band contracts from [8, 29]% at ten problems onto 16.9% at the full set. The per-problem distribution is heavily right-tailed. The median problem loses 10% of its witnessed faults, while a tail dominated by transposed-convolution and reduction problems loses 40–73%.
Where the misses live. The gradient across fault families is the paper’s central empirical fact.
{
"title": {"text": "Witnessed faults missed by the official check, by family"},
"color": ["#2a78d6"],
"tooltip": {"trigger": "axis", "axisPointer": {"type": "shadow"}, "formatter": "{b}: {c}% missed"},
"grid": {"left": "3%", "right": "12%", "bottom": "3%", "top": "50px", "containLabel": true},
"xAxis": {"type": "value", "name": "missed (%)", "min": 0, "max": 100},
"yAxis": {"type": "category", "data": ["Arithmetic (n=3,349)", "Indexing (n=749)", "Semantic (n=858)", "Boundary (n=1,735)", "Synchronization (n=400)", "Precision (n=271)"]},
"series": [
{
"name": "Missed",
"type": "bar",
"barWidth": 18,
"itemStyle": {"borderRadius": [0, 4, 4, 0]},
"label": {"show": true, "position": "right", "formatter": "{c}%"},
"data": [8.7, 14.4, 14.9, 22.9, 27.8, 78.6],
"markLine": {
"symbol": "none",
"lineStyle": {"type": "dashed", "color": "#eb6834"},
"label": {"formatter": "all families: 16.9%", "position": "insideEndTop"},
"data": [{"xAxis": 16.9}]
}
}
]
}
| Family | Rules | Witnessed | Missed | Example operators |
|---|---|---|---|---|
| Arithmetic | 5 | 3,349 | 8.7% | + to −, * to /, off-by-one constant |
| Indexing | 13 | 749 | 14.4% | axis swap, stride confusion, transposed access |
| Semantic | 18 | 858 | 14.9% | tie-breaking, fused-op reordering, flag flips |
| Boundary | 27 | 1,735 | 22.9% | guard deletion, ceil to floor grid, tail drop |
| Synchronization | 6 | 400 | 27.8% | barrier removal, syncwarp weakening |
| Precision | 27 | 271 | 78.6% | fp16 accumulators, no-stabilizer, fast-math |
Textbook mutations, the operator swaps that dominate classical mutation testing, are caught at 91%: random dense inputs excite arithmetic everywhere, so arithmetic faults have nowhere to hide. The families that escape are precisely the ones real GPU bugs inhabit. Boundary faults hide because official shapes are aligned and remainder blocks never execute. Synchronization faults hide because small aligned workloads rarely lose the race. Precision faults hide because the tolerance forgives them by construction. A checker validated on hand-seeded arithmetic bugs would look excellent and be blind where it matters. At operator granularity the same two regimes recur: storing through an fp16 temporary escapes the tolerance 92% of the time, while barrier weakenings escape at 26–29% because the official aligned shapes never exercise the code they break.
Mechanisms, quantified. Three regularities organize the survivors.
A metric that can score the official protocol can score its proposed replacements. We re-implement KernelBench-Verified from its released source: four deterministic scalings of each problem’s own inputs (×1, ×3, ×0.01, ×−1; shapes never varied; integer tensors never scaled) plus an fp32 tolerance of \(10^{-3}\). We reconstruct the Correctness-Illusion-style fuzzer from its paper (log-uniform magnitudes spanning six decades; no code is released). All protocols are scored on a unified denominator of 8,215 witnessed mutants across 235 operator- and architecture-level problems, and every protocol includes the original distribution.
{
"title": {"text": "Protocol audit on the unified denominator (8,215 witnessed mutants)"},
"color": ["#2a78d6"],
"tooltip": {"trigger": "axis", "axisPointer": {"type": "shadow"}, "formatter": "{b}: {c}% detected"},
"grid": {"left": "3%", "right": "12%", "bottom": "3%", "top": "50px", "containLabel": true},
"xAxis": {"type": "value", "name": "detected (%)", "min": 70, "max": 100},
"yAxis": {"type": "category", "data": ["Official KernelBench (5 inputs)", "KBV inputs, tol 1e-2 (4 inputs)", "CI-style fuzz (3 inputs, 107 false kills)", "KBV full, tol 1e-3 (4 inputs)", "Ours (2 inputs)", "Ours, all targeted (~8 inputs)"]},
"series": [
{
"name": "Detected",
"type": "bar",
"barWidth": 18,
"itemStyle": {"borderRadius": [0, 4, 4, 0]},
"label": {"show": true, "position": "right", "formatter": "{c}%"},
"data": [80.0, 84.0, 86.2, 88.5, 98.1, 99.9]
}
]
}
Decomposing KBV. Of KernelBench-Verified’s +8.5 points over the official protocol, +4.0 come from the four hidden distributions and +4.5 from the tighter tolerance. The tolerance change outweighs all four designed distributions combined. Their paper reports the same asymmetry from the outside and states that it “does not quantify how many failures were solely attributable to tighter tolerance versus distributional mismatches.” The kill matrix computes precisely this.
Why constant scalings plateau. All four KBV transforms rescale magnitude; none varies shape or structure. Remainder-block boundary faults and index-arithmetic faults are therefore unreachable by construction, independent of how many scaled configurations are added, and these are exactly the families the official check already misses most. This is the kind of blind spot invisible to anecdote-based validation and obvious under a metric. Collapsing the kill matrix to its distinct miss-patterns makes the band’s core visible: 643 mutants that every baseline misses and only our targeted suite detects.
The fuzz baseline crosses the ceiling. The reconstructed fuzzer buys its 86.2% partly with invalid inputs: at the top of its magnitude range it rejected correct kernels 107 times in our audit. Detection bought with invalid inputs is not detection; a deployed benchmark would be rejecting honest submissions.
A cautionary replication note. An early version of our own audit guessed \(\mathcal{N}(0,1) \times 10^{4}\) for KBV’s large-magnitude distribution and observed false rejections of correct kernels. The fault was our guess, not their protocol: their published ×3 cannot cross the ceiling. We keep the episode on record because it argues the thesis better than any experiment we designed. Without a measured validity gate, an input designer, human or model, cannot tell when they have crossed it.
Suites as set cover. Once the kill matrix exists, suite construction is the classical covering problem
{
"title": {"text": "Detection vs. input budget (greedy set cover on the kill matrix)"},
"color": ["#2a78d6", "#eb6834"],
"tooltip": {"trigger": "axis"},
"legend": {"top": "30px", "data": ["Optimized, full pool", "Optimized, held-out mutants"]},
"grid": {"left": "3%", "right": "6%", "bottom": "3%", "top": "70px", "containLabel": true},
"xAxis": {"type": "value", "name": "inputs per problem", "min": 1, "max": 5, "interval": 1},
"yAxis": {"type": "value", "name": "witnessed faults detected (%)", "min": 80, "max": 100},
"series": [
{
"name": "Optimized, full pool",
"type": "line",
"symbolSize": 8,
"lineStyle": {"width": 2},
"data": [[1, 87.1], [2, 98.0], [3, 99.5], [5, 100.0]],
"markLine": {
"symbol": "none",
"lineStyle": {"type": "dashed", "color": "#888888"},
"label": {"formatter": "official, 5 random inputs: 83.1%", "position": "insideEndTop"},
"data": [{"yAxis": 83.1}]
}
},
{
"name": "Optimized, held-out mutants",
"type": "line",
"symbolSize": 8,
"lineStyle": {"width": 2, "type": "dashed"},
"data": [[1, 84.5], [2, 94.8], [3, 96.4], [5, 96.6]]
}
]
}
| Scored on | b = 1 | b = 2 | b = 3 | b = 5 | Official (5 random) |
|---|---|---|---|---|---|
| Full pool | 87.1% | 98.0% | 99.5% | 100.0% | 83.1% |
| Held-out mutants | 84.5% | 94.8% | 96.4% | 96.6% | 82.9% |
To rule out overfitting the selection to the measured pool, we split each problem’s witnessed mutants 50/50 by id-hash, select on the dev half only, and score on the test half. Dev-selected suites detect 94.8% of held-out mutants at budget two, a roughly 3-point generalization gap, indicating the chosen inputs capture fault families, not memorized individuals. At the official five-input budget an optimized suite detects every witnessed mutant. The gain is chosen testing, not more testing.
What must a generator know? Our targeted suites were written by an LLM given three artifacts of the measurement: the blind-spot taxonomy, a per-family attack playbook, and the validity ceiling. They killed 431 previously-unkilled mutants at a 94% suite-validity rate, and the 6% of suites the gate rejected were precisely ceiling violations. To isolate how much of this is the metric’s contribution, we fix a hard target (1,154 mutants that survive all official inputs, across 30 problems) and vary only the generator’s knowledge.
{
"title": {"text": "Knowledge ladder: share of 1,154 official-input survivors killed"},
"color": ["#2a78d6"],
"tooltip": {"trigger": "axis", "axisPointer": {"type": "shadow"}, "formatter": "{b}: {c}% of survivors killed"},
"grid": {"left": "3%", "right": "4%", "bottom": "3%", "top": "50px", "containLabel": true},
"xAxis": {"type": "category", "data": ["R0: random fuzz", "R1: naive prompt", "R2: + taxonomy & ceiling", "R3: + white-box diffs"]},
"yAxis": {"type": "value", "name": "survivors killed (%)", "min": 0, "max": 70},
"series": [
{
"name": "Survivors killed",
"type": "bar",
"barWidth": 36,
"itemStyle": {"borderRadius": [4, 4, 0, 0]},
"label": {"show": true, "position": "top", "formatter": "{c}%"},
"data": [21.6, 42.9, 61.0, 57.3]
}
]
}
| Rung | Generator knows | Survivors killed | Validity |
|---|---|---|---|
| R0 random fuzz | nothing | 21.6% | 107 false kills |
| R1 naive prompt | “tests may miss subtle bugs” | 42.9% | 60/60 |
| R2 + taxonomy & ceiling | fault families, safe magnitudes | 61.0% | 94% (accumulated) |
| R3 + white-box diffs | the mutated source sites | 57.3% | 55/56 |
Two readings matter. First, each increment of measurement-derived knowledge buys detection; the taxonomy nearly triples the fuzz baseline. Second, white-box access to the faults themselves does not beat the taxonomy: shown ten concrete mutants, the generator overfits its inputs to them, while family-level knowledge generalizes to the whole pool. The measurement’s abstraction is worth more than its raw instances, which is what makes these prompted rungs a credible floor for a learned generator whose reward is the kill rate computed here.
Level-3 KernelBench problems are whole architectures. Their substrates are multi-kernel pipelines, one __global__ per layer and 2 to 140 kernels deep, so every mutant carries a layer tag and a question inaccessible at operator scale becomes measurable: does an early fault reach the network output?
Architecture-level checking is weaker on every accounting. Under strict witnessed accounting, 17.3% of architecture-level faults escape the official inputs against 16.9% at operator scale, and this is a floor, since the level-3 witness search is far shallower. The distinct-mutant upper bound is 1.7×: 43.0% survival versus 25.7%.
{
"title": {"text": "Survival by injection depth across 48 architectures (distinct-mutant accounting)"},
"color": ["#2a78d6"],
"tooltip": {"trigger": "axis", "axisPointer": {"type": "shadow"}, "formatter": "{b}: {c}% surviving"},
"grid": {"left": "3%", "right": "4%", "bottom": "3%", "top": "50px", "containLabel": true},
"xAxis": {"type": "category", "name": "injection depth quartile", "data": ["Q1 (n=5,745)", "Q2 (n=2,390)", "Q3 (n=2,489)", "Q4 (n=1,401)"]},
"yAxis": {"type": "value", "name": "mutants surviving (%)", "min": 0, "max": 60},
"series": [
{
"name": "Surviving",
"type": "bar",
"barWidth": 36,
"itemStyle": {"borderRadius": [4, 4, 0, 0]},
"label": {"show": true, "position": "top", "formatter": "{c}%"},
"data": [44, 49, 42, 39],
"markLine": {
"symbol": "none",
"lineStyle": {"type": "dashed", "color": "#eb6834"},
"label": {"formatter": "operator scale: 25.7%", "position": "insideEndTop"},
"data": [{"yAxis": 25.7}]
}
}
]
}
Within networks, faults injected in the front half survive at 44–49%, falling to 39% in the output quartile: a modest but consistent gradient. More telling than either aggregate is the per-architecture structure.
| Architecture | Character | Survival (distinct) |
|---|---|---|
| VGG-19 | deep homogeneous conv chain | 90% |
| SqueezeNet | deep homogeneous | 90% |
| LSTM stacks | long recurrent chains | 77–87% |
| 17-layer MLP | homogeneous | 82% |
| ResNet-18 (51 kernels) | normalization- and branch-dense | 11% |
| SwinMLP (71 kernels) | normalization- and branch-dense | 14% |
Deep homogeneous pipelines are near-opaque to the official check, while architectures dense in normalization and branching stay comparatively transparent even at extreme depth. The per-architecture median of 25.7% matches the operator scale exactly, so the aggregate gap is carried entirely by the homogeneous tail. Depth supplies the opportunity for masking; homogeneity, long chains without renormalization, realizes it. The direction of travel for the field, from single operators toward end-to-end generated models, is precisely the direction in which its oracles weaken.
Two problems no oracle can referee. The two level-3 problems our admission gate could never pass turn out to be unpassable in principle. For 48_Mamba2ReturnY, whose reference exponentiates cumulative sums of unbounded random parameters, outputs reach \(10^{20}\) and the official fp32 forward violates the benchmark tolerance against its own fp64 evaluation at 352 positions. For 45_UNetSoftmax, whose blocks chain softmax into batch normalization, a variance amplifier, the official fp32 forward deviates from fp64 by up to 0.74 (9,379 violations), farther than our rejected candidate sits from the reference. No fp32 implementation, including the reference itself, can be adjudicated on these problems. No prior audit noticed: KernelBench-Verified ships hidden tests for both (its validity filter catches only NaN/Inf, and finite \(10^{20}\) outputs pass), robust-kbench’s filters never touch level 3, and the instability of the naive Mamba segment-sum is acknowledged upstream but had not been connected to the benchmark. Scores reported on these two problems are noise, a concrete instance of what patching without measuring cannot see.
float4, warp shuffles; no taxonomy shown) for 60 random problems and gated them against the official reference. Three were incorrect: a compile error; a misaligned-address crash from an unguarded vectorized load, squarely in our boundary/guard family; and a value error in a fused GEMM–GroupNorm, matching our accumulation/semantic families. The bug sample is small, but both runtime bugs fall inside the taxonomy. The exercise also exposed a harness blind spot that only realistic style triggers (__launch_bounds__-qualified kernels defeating naive extern "C" injection), which is the checker-measurement lesson applied to our own tooling.Benchmarks for generated GPU kernels have been patching their correctness checkers blind. Mutation analysis, adapted to graded numerical oracles with a witnessed denominator, a measured validity ceiling, and tractable compilation, turns checker quality into a number, explains the number through two mechanisms and a six-family taxonomy, prices existing patches, and converts suite design into optimization that generalizes to held-out faults.
If you maintain a kernel benchmark, or train a model against one, three things carry over directly:
We release the pool, witnesses, suites, and pipeline as KernelBench-M, so that the next patch to a kernel benchmark can ship with its coverage measured rather than asserted. The full paper is available here.