Autotuning a cuTile kernel#
This walkthrough uses CompileIQ to autotune a
cuTile (CudaTile) matrix multiplication
kernel. The complete example is available in
examples/compilers/cutile_example.
CompileIQ is normally used to tune NVIDIA internal compiler parameters, but it can also tune user-level parameters. This example focuses on user-level cuTile parameters. Compiler-parameter tuning for cuTile will be showcased in a future version.
What the example searches#
The example searches four kinds of cuTile configuration:
Parameter |
cuTile surface |
Effect |
|---|---|---|
|
Compile-time constants |
Selects a known-valid |
|
|
Sets the function-level CTA hint. |
|
|
Sets the function-level occupancy hint. |
|
|
Sets the latency hint on every load. |
|
|
Allows or forbids TMA on every load. |
The fixed problem shape is 1024 x 1024 x 1024. The tile configurations are
curated so that every shape divides the problem dimensions evenly:
TILE_CONFIGS = [
{"tm": 64, "tn": 64, "tk": 32},
{"tm": 128, "tn": 64, "tk": 32},
{"tm": 128, "tn": 128, "tk": 32},
{"tm": 128, "tn": 128, "tk": 64},
]
SEARCH_SPACE = {
"tile_idx": ss.choice(list(range(len(TILE_CONFIGS)))),
"num_ctas": ss.choice([1, 2]),
"occupancy": ss.choice([1, 2, 3, 4]),
"latency": ss.choice([1, 4, 8]),
"allow_tma": ss.choice([0, 1]),
}
Using choice for the tile index tells CompileIQ that the configurations are
categorical. It also prevents the search from constructing unsupported
combinations by varying the three tile dimensions independently.
Applying each candidate#
The objective applies the function-level hints by creating a kernel variant for the sampled candidate:
def build_kernel(config):
return matmul_kernel.replace_hints(
num_ctas=config["num_ctas"],
occupancy=config["occupancy"],
)
The launch passes the selected tile dimensions and per-load hints as compile-time constants. Each candidate therefore causes cuTile to JIT-compile the corresponding kernel variant.
Correctness and benchmarking#
Every candidate is checked against torch.matmul before it is timed. A compile
failure, launch failure, or numerical mismatch returns INVALID_SCORE instead
of terminating the search:
try:
kernel = build_kernel(config)
out = run_matmul(kernel, a, b, c, config)
ref = torch.matmul(a.float(), b.float()).to(torch.float16)
torch.cuda.synchronize()
if not torch.allclose(out, ref, atol=1e-1, rtol=1e-1):
return INVALID_SCORE
return bench_ms(lambda: run_matmul(kernel, a, b, c, config))
except Exception:
return INVALID_SCORE
The benchmark uses CUDA events and reports the mean kernel runtime in
milliseconds. Passing --clock-mhz also uses gpu_benchmark_mode to stabilize
GPU clocks for the duration of the search. gpu_benchmark_mode also accepts a
power_watts argument to cap the GPU power limit — see the
Normalization guide for an example.
Running the example#
Install CompileIQ with the examples dependency group, then run the script on a CUDA 13.3 or newer system with a supported NVIDIA GPU:
poetry install --with examples
cd examples/compilers/cutile_example
poetry run python cutile_autotune.py --generations 5 --pool-size 16
To lock GPU clocks during the run, add --clock-mhz with a clock supported by
your GPU:
poetry run python cutile_autotune.py \
--generations 5 --pool-size 16 --clock-mhz 1965
The example prints the best runtime, tile configuration, and hint assignment:
Best runtime: 0.0421 ms
Best tile config: {'tm': 128, 'tn': 128, 'tk': 64}
Best hints: num_ctas=2 occupancy=2 latency=4 allow_tma=True
The result is specific to the kernel, input shape, software stack, and GPU used for the search. Re-run correctness and performance validation in the intended deployment environment before using a configuration in production.