AsmEvo tunes AMD GPU kernels at the assembly level

The news. On August 21, 2026, a paper introduced AsmEvo, which studies kernel optimization in the setting where the deployed AMDGPU binary — not the source — is the thing you have to work with. It reconstructs a reassemblable representation of the code object, has a long-horizon agent propose low-level edits, rebuilds an ABI-preserving object, and accepts a candidate only after differential verification against the original under identical launches. The reported results cover a selected KernelBench subset on MI308X plus AMD's AITer binaries and vLLM/SGLang Triton assembly kernels on MI300X. Read the paper →

Picture the tuner again, standing in front of an engine that is bolted shut. There are no blueprints, and the drawings that do exist describe something several revisions upstream of the metal in front of them. In that situation the engine itself stops being a copy of the design and becomes the design — the only object whose behaviour counts, and therefore the only object worth editing. That is the premise AsmEvo starts from, and the paper states it plainly: "the deployed binary is the only behavioral oracle."

Most of the tooling a GPU programmer reaches for sits above that level. The abstraction ladder runs from eager PyTorch down through torch.compile, then Triton, then CUDA C++ — each rung trading away convenience for control, and each one still trusting a compiler to translate your intent into machine code faithfully.

GPU Abstraction Stack

PyTorch EagerL4

Write Python ops — runs immediately, one kernel per op

torch.compileL3

Annotate with @torch.compile — compiler fuses & optimizes for you

TritonL2

Write tile-level kernels in Python — full control, no C++ needed

CUDA C++L1

Write thread-level kernels — maximum control, maximum complexity

↑ easier↓ more control

AsmEvo works one rung below the bottom of that ladder. The trade it makes is the same one the ladder has been making all along, just pushed one step further: give up the readable source, get the last slice of control. That is exactly the choice the curriculum frames as the CUDA expertise gap and as picking which rung to work on — AsmEvo simply adds a rung underneath and asks whether an agent can stand on it.

The obvious objection is that editing assembly by hand-waving is how you get a fast kernel that quietly computes the wrong answer. This is where the bench test earns its place. AsmEvo rebuilds each candidate as an ABI-preserving object so the caller still fits, then runs differential verification: edited kernel and original, identical launches, results compared. Only a candidate that survives that comparison is timed at all. The stopwatch does not start until that comparison agrees — which turns the whole thing into an evaluator-optimizer loop whose evaluator is not a score at all, but a pass/fail gate. Speed is a ranking; correctness is admission. And because a whole binary is far too large a search space, profiling narrows the edits to the hot window — the region that is actually limiting the kernel — with a conservative in-place patch available as a fallback.

What the measurements show is a wide spread, and the spread tracks which family of binaries was being optimized.

Binary familyHardwareGeometric-mean speedupMax speedupCoverage
Selected KernelBench kernelsMI308X1.35×3.88×29 of 30 improved
AITer binaries (AMD)MI300X1.09×1.31×reported, count not stated
vLLM / SGLang Triton assembly kernelsMI300X1.18×1.34×reported, count not stated

Put those ratios into the form you actually care about — the share of a kernel’s time that disappears — and the spread becomes readable, because the arithmetic is just 1 − 1/speedup. A 1.35× geometric mean (the average of the per-kernel speedups, computed multiplicatively) removes about 26% of the time; the 3.88× best case removes about 74%. Now hold the hardware fixed and compare only the two MI300X families, which is the one comparison the reported numbers license: 1.18× on the vLLM/SGLang Triton assembly kernels removes about 15%, while 1.09× on the AITer binaries removes about 8%roughly half as much on AMD’s own inference-kernel library. Made concrete on a kernel that costs 4.00 ms per launch (illustrative — the paper reports ratios, not absolute times), that is 4.00 / 1.18 = 3.39 ms, a saving of 0.61 ms, against 4.00 / 1.09 = 3.67 ms, a saving of 0.33 ms. The paper does not claim a cause for that gap, and neither should you — but the reading worth keeping is that a speedup number here says as much about which binary you pointed the optimizer at as it says about the optimizer.

Which is what makes this worth reading even if you will never touch an AMD code object. The transferable part is the shape of the loop, not the target: when an agent is turned loose on something where a plausible-looking change can be silently wrong, the useful design move is to make correctness the admission gate and performance only the ranking among what is already admitted. A kernel binary is an unusually clean case of that, because agreement can be checked by execution rather than argued about — but the same shape carries over to other optimizers whose search space contains a great many fast, wrong answers.

Goes deeper in: GPU & CUDA → Triton & torch.compile → When to Use What

Related explainers

Frequently Asked Questions

Check what you knowMap your AI & GPU knowledge across every track — free, role-based