01 · Why guessing loses
The best block shape isn't a constant — it's a function of your GPU
Day 14 recommended 256 threads per block and day 21 picked BLOCK=4096 on faith. Both numbers are actually functions — of shared memory per SM, register file size (day 09), warp count (day 06), L1 geometry (day 10). Change the GPU and the optimum moves: the block shape that hits 90% of bandwidth on a 4070 can leave 30% on the table on an H100, and vice versa. Hand-tuning per GPU doesn't scale past your own desk — and open-source kernels can't hand-tune for hardware they've never seen.
Triton's answer is refreshingly blunt: if the parameter matters, measure it. Declare a small set of candidate configurations; on first launch, run each and keep the fastest; cache the winner keyed on the arguments you nominate.
02 · The decorator
@triton.autotune, end to end
@triton.autotune(
configs=[
triton.Config({'BLOCK': 256}, num_warps=4),
triton.Config({'BLOCK': 1024}, num_warps=4),
triton.Config({'BLOCK': 4096}, num_warps=8),
],
key=['n'], # re-tune when the problem size changes
reset_to_zero=['out_ptr'], # for kernels that accumulate
)
@triton.jit
def add(x_ptr, y_ptr, out_ptr, n, BLOCK: tl.constexpr):
pid = tl.program_id(0)
offs = pid * BLOCK + tl.arange(0, BLOCK)
mask = offs < n
tl.store(out_ptr + offs,
tl.load(x_ptr + offs, mask=mask)
+ tl.load(y_ptr + offs, mask=mask),
mask=mask)The key list is the part worth staring at. Tuning results are cached per distinct value of the keyed arguments, because the best BLOCK for n = 1,000 is not the best for n = 100,000,000 — different shapes mean different grids, different tail-masking costs, different occupancy. Key on whatever changes the optimal shape; production kernels typically key on tensor shapes, never on raw pointers or batch contents. The first call for each new shape pays the search; every call after is a dictionary lookup.
03 · What's actually being searched
Three knobs, all familiar under other names
BLOCK SIZES
the tile shape — day 15's T, day 20's BLOCK; controls reuse and masking cost
NUM_WARPS
warps per program — day 03's scheduling unit, day 06's occupancy currency
NUM_STAGES
pipeline depth — how many tiles ahead to prefetch; day 15's double buffering, automated
Notice the interactions you already understand: a bigger block wants more shared memory (day 08) and more registers (day 09), which cuts programs-per-SM (day 06), which changes latency hiding (day 01). Deeper pipelining hides memory latency but holds more tiles live. No closed-form model survives all three interacting — which is precisely why search beats theory here, and why day 14 said "default to 256, then measure": autotune is the measuring, industrialized.
04 · Costs, caches, and production practice
Tune once at startup, not inside the hot loop
The search runs real kernel launches with timing around them — for a big GEMM with a wide config list, that's seconds of one-time cost. Three practices keep it sane. Budget the warmup: tune during model loading, before latency matters. Prune the list: eight sensible configs beat fifty random ones. Pre-bake when it matters: inference engines like vLLM snapshot tuned configs per GPU and skip the search entirely — deterministic startup, no surprise timings on the first user request. And never put an autotuned kernel's first call inside a benchmark: you'll measure the search, not the kernel.
- 01The optimal block shape is a function of the GPU, not a constant. Shared memory, registers, and warp limits all move the optimum. Search per hardware instead of hand-tuning per desk.
- 02Key the cache on problem shape. key=['n'] (or the shape tuple) re-tunes when the optimal shape could change — and pays the search only once per shape.
- 03The search space is block sizes × num_warps × num_stages. Reuse, occupancy, and pipelining — every concept from days 03–15 — as three knobs the benchmark explores for you.
- 04Tune at startup; pre-bake in production. The first call pays for real timed launches. Warm up before serving, prune the config list, and snapshot configs where startup latency is sacred.
Next: day 23 — kernel fusion: why frameworks fuse elementwise ops, what it saves in memory traffic, and a calculator to watch the savings stack up.