Most kernel speedups are measurement bugs

Lesson 9 · warmup, synchronization, L2, and quantiles · how a benchmark becomes evidence

This is the lesson that makes the other eight count. Your mission says lessons teach but proof comes from measurement — and a measurement you cannot defend is not proof, it is a story. Every trap below has produced a confident, wrong, publicly-posted speedup number.

Trap 1: the GPU has not finished

CUDA launches are asynchronous. Your Python call queues the kernel and returns immediately. If you wrap it in time.time() you are timing the launch, not the work.

The wrong way and the right way to put a clock around a GPU kernel.

Listen first: the broken version records a start time, calls the kernel, and records an end time. Because the launch returns before the GPU has done anything, the elapsed time is microseconds of queueing, and you conclude your kernel is a hundred times faster than it is. The correct version calls torch.cuda.synchronize before the first timestamp — to drain anything already queued — and again before the second, which blocks until the GPU has actually finished. Only then does the elapsed time describe the kernel.

Trap 2: the first call is not the kernel

Triton is a JIT. The first invocation of a @triton.jit function compiles it, and if you used @triton.autotune it also benchmarks every config in your search space. That first call can be hundreds of milliseconds against a kernel that runs in one. Discard it — and discard a few more, because the caching allocator, clocks and cuDNN heuristics all settle over the first several calls.

Trap 3: you are measuring the L2 cache

This is the subtle one, and it is the one that inflates bandwidth claims specifically. Run the same kernel on the same buffers in a tight loop and after the first iteration the input is already sitting in L2 — 40 MB of it on an A100. Your kernel is now reading from cache at several times HBM bandwidth, and you report a number the memory system cannot actually sustain.

This matters enormously for everything in this lab, because per Lesson 2 your kernels are memory-bound — so a benchmark that accidentally removes the memory bottleneck is measuring nothing you care about. triton.testing.do_bench writes over a cache-sized buffer between repetitions for exactly this reason.

Trap 4: the mean of a skewed distribution

GPU timings are noisy and right-skewed — an OS hiccup or a clock dip adds time, nothing ever subtracts it. One 3 ms outlier in a hundred 1 ms runs drags the mean but not the median. Report the median with quantiles, which is what do_bench's quantiles argument gives you. Minimum is not the answer either: it is the best case you ever saw under ideal cache and clock conditions, and production never runs there.

The tool that does all four for you

The standard harness, with the four traps handled.

Listen first: triton.testing.do_bench takes a zero-argument callable. Warmup and rep are durations in milliseconds — it estimates how many iterations fit in each window rather than making you pick a count. Passing a list of quantiles returns the median and the bounds you asked for instead of a single number. It synchronises, discards the warmup window, and clears the L2 cache between repetitions by default. The line above it is the one people skip: assert that your kernel agrees with the reference before you time it, because a wrong kernel can be arbitrarily fast.

And always against a real baseline. "2.1× faster than PyTorch eager on an A100 at this shape, median of 100 reps" is evidence. "Very fast" is not, and neither is a speedup over a naive implementation you wrote yourself to lose.

Micro-world: measure a kernel wrong, then right

A kernel whose true runtime is exactly 1.000 ms

You know the truth here — that is the point. Turn the safeguards off one at a time and watch what you would have reported. The PyTorch baseline for this shape is 2.000 ms, so the honest speedup is 2.0×.

Per-repetition timings (after warmup)

Table view

Check yourself

Three questions on measurement

1. You wrap a Triton call in time.time() with no synchronize(). What did you measure?
2. Why must you discard the first several calls?
3. Why does do_bench clear the L2 cache between repetitions?
Cold-recall defense (one breath):
Four traps. Async — CUDA launches return immediately, so synchronize() on both sides or you time the queue. Warmup — Triton JIT-compiles and autotunes on the first call; discard the early ones. L2 — repeat the same buffers and you benchmark a 40 MB cache instead of HBM, which destroys any memory-bound result. Statistics — timings are right-skewed, so report the median with quantiles, never the mean and never the min. triton.testing.do_bench does all four (warmup/rep are durations in ms). And check correctness before you time anything, against a real baseline — a wrong kernel is arbitrarily fast.

The reporting checklist

A benchmark in this lab is only evidence if it states all of these. Copy this into every experiment README:

FieldWhy it is required
GPU model and clocksA T4 result does not transfer to an A100. Boost vs sustained clocks differ.
Exact shapes and dtypesKernel performance is shape-dependent; one shape is an anecdote.
Baseline, named and versioned"Faster than PyTorch 2.x eager" — not faster than nothing.
Correctness check with tolerancesStated atol/rtol, run before timing.
Warmup and rep settingsSo the reader knows the traps above were handled.
Median + spread, not a single numberA point estimate hides the distribution.
GB/s for memory-bound, TFLOP/s for compute-boundPer Lesson 2 — quoting the wrong one hides the real limit.
What you did not testHonesty about scope is what makes the rest believable.

Primary source

Read the source of triton.testing.do_bench (~50 lines, 10 min). It is short, and reading it is worth more than any blog post: you can see the cache flush, the synchronize calls, the warmup estimation and the quantile handling in code. Every one of this lesson's four traps is a line in that file.

💬 I'm your teacher for this — ask me followups any time. When Experiment 0001 produces numbers, bring them here and we will audit them against the checklist together — that is the highest-value thing this lesson can do for you.

Read next

Lesson 9 · Zain's AI Inference Lab · source: triton.testing.do_bench