End-to-End Costmodel Example#
This example shows the basic costmodel backend flow:
generate TTIR from a Triton frontend kernel;
build
costmodel_benchitems for multiple candidate configs;call
costmodel_benchonce per item and collect the predicted latency for each config.
This flow is useful for filtering out slow configs before autotuning. The example uses only a vector add kernel so that the focus stays on the costmodel input and return value.
Complete Example#
Save the following code as costmodel_example.py and run it:
from __future__ import annotations
import triton
import triton.language as tl
from triton.backends.ascend.runtime.costmodel_runtime import costmodel_bench
from triton.backends.compiler import GPUTarget
from triton.compiler import ASTSource
from triton.compiler.code_generator import ast_to_ttir
from triton.compiler.compiler import make_backend
from triton._C.libtriton import ir
from triton._C.libtriton.ascend import ir as ascend_ir
@triton.jit
def add_kernel(x_ptr, y_ptr, output_ptr, n_elements, BLOCK_SIZE: tl.constexpr):
pid = tl.program_id(axis=0)
offsets = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
mask = offsets < n_elements
x = tl.load(x_ptr + offsets, mask=mask)
y = tl.load(y_ptr + offsets, mask=mask)
tl.store(output_ptr + offsets, x + y, mask=mask)
def make_ttir(kernel, signature, constants):
source = ASTSource(kernel, signature, constants, attrs=None)
target = GPUTarget("npu", "", 32)
backend = make_backend(target)
options = backend.parse_options(
{
"num_warps": 8,
"num_stages": 2,
"debug": False,
"multibuffer": False,
"compile_mode": "simd",
**source.parse_options(),
}
)
context = ir.context()
ir.load_dialects(context)
ascend_ir.load_dialects(context)
return str(ast_to_ttir(kernel, source, context, options, {}, {}))
signature = {
"x_ptr": "*fp32",
"y_ptr": "*fp32",
"output_ptr": "*fp32",
"n_elements": "i32",
}
n_elements = 98432
configs = [
{"name": "block256", "BLOCK_SIZE": 256},
{"name": "block1024", "BLOCK_SIZE": 1024},
{"name": "block2048", "BLOCK_SIZE": 2048},
]
items = []
for cfg in configs:
ttir = make_ttir(add_kernel, signature, {"BLOCK_SIZE": cfg["BLOCK_SIZE"]})
items.append(
{
"config": cfg["name"],
"ttir": ttir,
# n_elements is the fourth argument in the signature, so it maps
# to %arg3 in TTIR. pid_x gives tl.program_id(0) a static value.
"arg_bindings": f"arg3={n_elements},pid_x=0",
}
)
latencies = {}
for item in items:
config, latency_us = costmodel_bench(item)
if config is not None:
latencies[config] = latency_us
for config, latency_us in sorted(latencies.items(), key=lambda item: item[1]):
print(f"{config}: {latency_us:.3f} us")
Example Output#
The exact numbers may vary with costmodel parameters, but the output shape should look like this:
block256: 0.098 us
block1024: 0.110 us
block2048: 0.126 us
costmodel_bench evaluates one item and returns (config, latency_us). An autotuning layer can call it once per item, aggregate the returned pairs into a dictionary, sort by latency, and keep the configs predicted to be faster.
Key Points#
ASTSource + ast_to_ttironly generates TTIR. It does not compile or launch the kernel.configaffectstl.constexprvalues such asBLOCK_SIZE, so each candidate config needs its own TTIR.Each
costmodel_benchcall handles exactly one item. The item should contain at leastconfigandttir, and may also includearg_bindings.arg_bindingsbinds runtime integer values to TTIR%argNarguments. In this example,n_elements=98432maps toarg3=98432.If the kernel uses
tl.program_id(0), usually passpid_x=0. If it also usestl.num_programs(0), passnum_programs_x=...as well.