28-08-2026 10 min read
cudagpukernelsagentstrueforgehackathon

the kernel agent that cannot lie about the speedup

Built for the WeMakeDevs / TrueFoundry Agent Harness Hackathon, on a single RTX 4090.

In February 2025, Sakana AI announced an “AI CUDA Engineer” that reported 10–100× kernel speedups. The kernels had not been optimised. The agent had found a way to exploit the benchmark harness, and several figures implied throughput roughly 30× above what the hardware can physically do. Tri Dao pointed out publicly that the numbers were not possible.

That failure is not exotic. It is the default outcome of a specific arrangement: an agent that writes a kernel and also reports its own speedup. Correctness tests do not catch it — a kernel can be numerically perfect and still be timed dishonestly.

So I built an agent inside TrueForge with one property: it submits kernel source and never submits a number.


the arrangement

A fixed harness owns everything that determines a measurement — allocation, the input distribution, the timing loop, the reference implementation, the tolerances. Nine gates adjudicate the result, and publishing requires human approval.

flowchart LR
    A["agent<br/><i>writes kernels</i>"] -->|"source only"| B["preflight_kernel<br/><i>MCP tool</i>"]
    B --> C["container<br/><i>no network, no inherited env</i>"]
    C --> D["9 gates"]
    D -->|"admitted"| E["publish_kernel<br/><i>human approval</i>"]
    D -->|"rejected"| A
    A -.->|"cannot reach"| D

    style A fill:#161022,stroke:#5f4e7d,color:#f5f3ff
    style D fill:#12261a,stroke:#0ca30c,color:#f5f3ff
    style E fill:#2a1f34,stroke:#d03b3b,color:#f5f3ff

The insight is that the documented ways of faking a kernel speedup are all properties of measurement code. Making that code ours does not detect them — it makes them unrepresentable.

Inside the container that is two processes, and the split is the whole guarantee:

flowchart TB
    R["runner<br/><i>outside the container</i>"] -->|"nonce + output path<br/><b>on stdin</b>"| S
    subgraph C["container"]
        S["supervisor.py<br/><i>never loads candidate code</i><br/><i>the only writer of a verdict</i>"]
        W["worker<br/><i>driver.cu / driver.py</i><br/><i>links or imports the candidate</i>"]
        S -->|"argv: op, shape, precision<br/><b>no secrets</b>"| W
        W -->|"numbers, on a descriptor"| S
    end
    S -->|"measurement.json"| R

    style S fill:#12261a,stroke:#0ca30c,color:#f5f3ff
    style W fill:#2a1f34,stroke:#d03b3b,color:#f5f3ff

A single process cannot both execute a candidate and be trusted to report on it. That sentence cost two admitted forgeries to learn.


eighteen operations, sixty-seven cases

One sweep, one GPU, thermal cooldown between cases so the figures are comparable. 51 admitted, 12 of 12 adversarial candidates rejected.

The same operation, the same reference, the same gates — so the spread inside a row is the kernel and nothing else.

Best and worst admitted result per operation, as a percentage of that operation's binding ceiling. The gap in a row is the only thing that varies: same op, same reference, same gates. Three rows are low because of the operation rather than the code — gather is irregular by construction, moe_gemm reads every expert's weights, attention_paged chases a block table.
opbestworstwhat it exercises
matmul91.5% triton/tf3257.8% triton/fp32tf32 tensor cores vs pinned ieee
attention_decode92.9% triton19.6% torchthe only attention shape on the memory side
swiglu91.2% tilelang39.0% torchfusion — 72% of that category fails upstream
rmsnorm90.4% tilelang26.1% torchone reduction, five toolchains
rope89.7% triton28.9% torchelement i needs element i + d/2
quantize88.7% triton14.0% torchquantization — 0 of 30 solved upstream
attention88.5% triton/fp1629.3% torchFlashAttention forward, online softmax
cross_entropy88.0% triton44.8% torchloss; output smaller than input
attention_gqa82.7% triton/bf1628.6% torchindex the shared KV head, don’t expand it
gather78.0% triton38.2% torchindex; irregular, cannot reach peak
attention_backward70.3% triton/bf1611.1% triton/fp32dQ, dK, dV; three kernels
attention_causal67.3% triton/bf1621.1% torchskip tiles, mask only the diagonal
attention_paged44.8% triton13.4% torchblock-table indirection
moe_gemm32.6% triton21.6% torchgrouping, not arithmetic

The operations were not picked by taste. Six came from KernelBenchX, which grades 176 tasks across 15 categories and reports where LLM-written kernels actually break — fusion fails 72% of the time, quantization is 0 of 30. Six more came from what a serving stack runs: FlashInfer measures 28–30% latency reduction from fusing RoPE into attention alone.

Three of those numbers are low because of the operation, not the code — gather is irregular by construction, moe_gemm reads every expert’s weights, attention_paged chases a block table. A harness whose every op streams teaches the wrong lesson: it makes 90% look like the pass mark.


what it caught

Physics can disprove a precision claim without a reference. One adversarial candidate declares fp32 and quietly computes in bf16 — roughly 2× the speed for a 2^15 loss of mantissa. Caught three times over: correctness at 594× tolerance, timed_work wrong after timing, and the roofline gate at 100.9 TFLOP/s against an 83.1 ceiling.

That last one needs no reference output at all. It is a statement about what the hardware can do.

Drag the intensity. Below the ridge the memory roof binds; above it, the compute roof does. The forged claim sits above the fp32 compute roof at every intensity, which is why the gate can refuse it without ever comparing against a correct answer.

The same kernel is honest at one precision and dishonest at another. A Triton FlashAttention kernel is rejected as fp32 and admitted at tf32 and bf16. Nothing about the kernel changes — tl.dot silently uses TF32. Reduced precision is a perfectly good engineering choice: it widens the tolerance and lowers the ceiling you are judged against, so it buys nothing you have not earned. Taking it silently is the only thing disallowed.

A toolchain’s config is what gets measured. A Helion config sweep on one kernel spans 4.7×block_sizes=[1] reaches 90% of the bus, [32] reaches 47%.


then it caught me

This is the part I did not plan for.

a candidate could forge the entire verdict — twice

Round one: a C++ static constructor runs before main. A kernel with an empty body whose constructor printed a fabricated measurement was admitted at a plausible 91% of peak. I fixed it with a nonce and observed wall time.

Round two, found by review of the very PR that added the breadth: the fix was a speed bump, not a boundary. The secrets were still in argv, and the candidate was still inside the process that wrote the verdict.

probe, inside the containerresult
/proc/<ppid>/cmdlinereadable
/proc/<ppid>/environreadable
/proc/<ppid>/memrefused, EPERM
ptrace(PTRACE_ATTACH)refused, EPERM

No flag and no environment variable can carry a secret past code running there. A sibling process can. Hence the supervisor split above. Both attacks are committed as regression tests.

an admitted adversary at 89.7%

I claimed the residual was “spend the time without doing the work”, so I wrote it instead of trusting myself: compute correctly through warmup, then serve a cached answer during the timed calls.

Against matmul it was refused at 18.4× the fp32 ceiling. Against rmsnorm it was admitted at 89.7%, replacing an honest 26% kernel. A copy moves exactly the traffic charged, and 4 FLOP/element is below the ridge point — the compute axis had been covering for the memory axis.

Fixed by rotating one input by a single element before every timed sample and checking against the reference for the final input state. Nothing computed earlier stays valid, and no candidate can know which sample is last.

the provenance bound was nearly vacuous

harness_wall_ms was the worker’s whole lifetime — seconds of torch import and float64 reference computation, all of it slack a forgery could spend.

Slack in the provenance bound, as a multiple of the claimed work. Each step moves the clock further from the code being measured.

Both harnesses now write a byte either side of every timing loop on a descriptor the supervisor owns, so the loops are timed on a clock the measured code does not control. The claim side was understated too: median_ms is per launch and each sample batches inner_iters, which omitted understated the claim by up to 40× on small shapes.

my own candidate was misreporting by 12.7×

A CuTe DSL kernel reported 7.0% of the memory bus, and I wrote that down as “CuTe is low-level and I used it badly.” It was not. @cute.jit re-traces on every entry, so the number was JIT dispatch, not the kernel. Hoisting the compile reports 88.6%.

A second CuTe kernel measured 56.4%, which also was not slow. Removing only the second read of the row — same access pattern — moved it to 86%, so it was already at 84.9% of DRAM and simply moving three passes where two are needed. Staging the row through shared memory: 85.9%.


six false accusations

A gate that flags correct work is worse than no gate, because it teaches everyone to ignore the gate. I built that gate six times.

what it flaggedwhy it was wrong
a baseline at 1820 GB/s on a 1008 GB/s bus34 MB fits in 72 MB of L2; DRAM does not bound cache-resident traffic
fp32 attention diverging from fp64the anti-narrow-range input distribution spanned 2^24 in the softmax tail
an honest kernel’s timing varianceat repeats=10, (10*9)//10 indexes the last sample, so p90 was max
torch’s own matmul at 2.8e-2pure relative error explodes where cancellation puts the reference near zero
a kernel at a third of its throughputmeasured after 900 s of autotuning, against a ceiling assuming peak clock
a correct TF32 FlashAttention kernelthe cached-serve fix first offset the input, shifting the softmax regime

The last one shipped inside another fix, which is the sharper lesson: the dangerous moment is not writing a new check, it is changing what the harness feeds the kernel.

Two cost models were wrong in the same family. gather charged every row read, but indices are drawn with replacement so ~1−1/e are distinct — overstating traffic by a fifth and pushing a correct kernel to 95.5% of the bus, refused as pointing at the measurement. It was right. And attention_backward charged 10 units of 2·b·h·s²·d, which assumes the forward hands over O and the logsumexp; they are not inputs, so recovering them is part of the work.


what it still does not claim

  • Hopper compute ceilings are absent on purpose. The published figures do not divide into a per-SM-per-clock constant I can defend, so the gate returns UNVERIFIABLE_COMPUTE rather than guessing.
  • Tensor-core ceilings are a floor, not a limit. Within one compute capability NVIDIA ships parts differing by exactly 2× — RTX 3090 at 35.6 dense TF32 TFLOPS against the A40 at 74.8, both sm_86 — and states the reason is unpublished.
  • The worker’s numbers are bounded, not proven. The bound is now tight — 1.05–1.27× of an externally observed interval — but removing the residual entirely means the supervisor owning allocation and verification too, plausibly over CUDA IPC. Not done.

One honest negative: TrueForge’s context management cost 49% more tokens on this workload (87,311 against 58,504), both arms completing. Wrong regime for the feature, not a broken feature.


why a harness, and not a script

Three things had to be true at once.

The sandbox has to hold the GPU, because a performance claim measured somewhere other than where the kernel runs is not a measurement — and it has to pin the toolchain, because a kernel measured against a different CUDA version is a different kernel. TrueForge shipped neither: daytona is the only exposed provider and went closed source in June 2026. So this contributes a container sandbox provider, submitted upstream as truefoundry/trueforge#467.

The adjudicator has to sit outside the agent’s reach. As an MCP server it is not a library the agent can monkeypatch — and publish_kernel carries destructiveHint, so the harness gates it behind human approval rather than trusting the agent to ask nicely.

And the knowledge should not be mine to invent. The agent reads Hugging Face’s own cuda-kernels and triton-kernels skills, pinned to a git SHA.


the shape of the thing

The design decision that paid for itself repeatedly: the gates adjudicate a measurement schema, not a toolchain.

flowchart LR
    subgraph H["harnesses"]
        H1["driver.cu"]
        H2["driver.py"]
    end
    H1 --> S["one measurement schema"]
    H2 --> S
    S --> G["nine gates"]
    G --> O["18 ops × 6 toolchains × 4 precisions"]

    style S fill:#12261a,stroke:#0ca30c,color:#f5f3ff

Adding a backend means writing a harness that emits that schema. Five toolchains — CUDA, Triton, Helion, CuTe DSL, TileLang — went in with zero changes to any gate.

The agent, asked for “the fastest fp32 matmul you can in Triton”, pinned input_precision="ieee" rather than letting tl.dot quietly hand it TF32 — and was admitted at 60.9%, ahead of the hand-written baseline in this repo at 58.2%. Taking the default would have been faster and would have been rejected.

The project’s own premise was falsified twice by review, and once more by an attack I wrote against it. That is the strongest thing I can say for it. A verification harness whose claims have never been tested by someone actively trying to break them is not a verification harness. It is a hope.


Code: github.com/rycerzes/kernel-preflight — MIT, including all twelve adversarial candidates as regression tests. The repo also has a 2m45s demo video: four of its beats are a recorded session replayed verbatim, and a fifth is a live run against a kernel that serves a cached answer while it is being timed.

comments

Reactions and discussion are powered by giscus

Command Palette
Search for a command to run