Triton Kernel Programming
Overview
This skill provides a hands-on reference for building production Triton kernels. It covers the triton.language API, autotune decorators, the @triton.jit compilation model, debugging/interpreter workflows, and triton.testing benchmarks.
When to Use
Use this skill when:
- Implementing any custom GPU compute kernel in Triton
- Optimizing inference latency for small-batch transformer operations
- Fusing operations (e.g., matmul + activation, attention with softmax)
- Porting CUDA kernels to Triton for easier maintenance
Do not use for:
- Standard PyTorch operations that already run fast (use
torch.compile)
- Distributed or multi-GPU parallelism patterns
- CPU-bound workloads
Installation
# Triton ships with PyTorch ≥2.0. Install latest:
pip install -U triton
# Or build from source for latest features:
git clone https://github.com/triton-lang/triton.git
cd triton
pip install -r python/requirements.txt
pip install .
# For profiling:
pip install nvitools # NVIDIA profiling helpers
pip install torch_tb_profiler # PyTorch profiling
Core API Reference
@triton.jit Decorator
Compiles a Python function into a GPU kernel. All code inside must be valid Triton (subset of Python + triton.language ops).
@triton.jit
def kernel( # ← compiled kernel
ptr, # runtime arguments: pointers, scalars
BLOCK: tl.constexpr, # constexpr: baked in at compile time
):
pid = tl.program_id(axis=0) # SPMD program index
...
triton.language (tl) — Key Operations
| Category |
Operation |
Description |
| Indexing |
tl.program_id(axis) |
SPMD program index along axis 0, 1, or 2 |
| Ranges |
tl.arange(start, end) |
1D range tensor for vectorized addressing |
| Arithmetic |
tl.sum, tl.max, tl.min, tl.argmax |
Block reduction along axis |
| Arithmetic |
tl.dot(a, b) |
Block matrix multiply (triggers tensor cores) |
| Activation |
tl.exp, tl.log, tl.sigmoid, tl.tanh |
Element-wise math |
| Activation |
tl.sqrt, tl.abs, tl.where |
Element-wise ops |
| Memory |
tl.load(ptr, mask=, other=) |
Vector load from global memory |
| Memory |
tl.store(ptr, val, mask=) |
Vector store to global memory |
| Memory |
tl.atomic_add(ptr, val) |
Atomic add (for reductions) |
| Cast |
tensor.to(tl.float16) |
Type conversion |
| Cast |
tl.cast(tensor, tl.float32) |
Explicit type conversion |
| Debug |
tl.device_print("x:", x) |
Runtime print |
| Debug |
tl.device_assert(cond, "msg") |
Runtime assertion |
| Debug |
tl.static_print(x) |
Compile-time print |
| Debug |
tl.static_assert(cond, "msg") |
Compile-time assert |
Memory Operations — Masking Best Practice
# Always mask loads/stores for safety:
mask = offsets < n_elements
x = tl.load(ptr + offsets, mask=mask, other=0.0)
# 'other' provides a safe default for out-of-bounds positions
# For matmul inner loop, use other=0.0 for partial tiles:
a = tl.load(a_ptrs, mask=offsets_k[None, :] < K - k, other=0.0)
b = tl.load(b_ptrs, mask=offsets_k[:, None] < K - k, other=0.0)
Complete Kernel Templates
Template 1: Element-wise Fusion (e.g., LayerNorm)
@triton.jit
def layernorm_kernel(
input_ptr, output_ptr, weight_ptr, bias_ptr,
row_stride, n_cols, eps,
BLOCK_SIZE: tl.constexpr,
):
pid = tl.program_id(0)
row_start = pid * row_stride
offsets = row_start + tl.arange(0, BLOCK_SIZE)
mask = tl.arange(0, BLOCK_SIZE) < n_cols
x = tl.load(input_ptr + offsets, mask=mask, other=0.0)
# Mean
mean = tl.sum(x, axis=0) / n_cols
# Variance
x_shifted = x - mean
var = tl.sum(x_shifted * x_shifted, axis=0) / n_cols
# Normalize
x_norm = x_shifted / tl.sqrt(var + eps)
# Scale + shift
w = tl.load(weight_ptr + tl.arange(0, BLOCK_SIZE), mask=mask)
b = tl.load(bias_ptr + tl.arange(0, BLOCK_SIZE), mask=mask)
y = x_norm * w + b
tl.store(output_ptr + offsets, y, mask=mask)
Template 2: Flash Attention-Style Softmax with Online Safe Computation
@triton.jit
def fused_attention_kernel(
q_ptr, k_ptr, v_ptr, output_ptr,
stride_qh, stride_qd,
stride_kh, stride_kd,
stride_vh, stride_vd,
stride_oh, stride_od,
H, D,
BLOCK_D: tl.constexpr,
BLOCK_N: tl.constexpr,
):
pid_h = tl.program_id(0) # head index
offs_d = tl.arange(0, BLOCK_D)
offs_n = tl.arange(0, BLOCK_N)
# Load Q block for this head
q_ptrs = q_ptr + pid_h * stride_qh + offs_d[:, None] * stride_qd
q = tl.load(q_ptrs) # (BLOCK_D, 1)
# Online safe softmax over KV sequence
m_i = tl.full([BLOCK_N], -float('inf'), dtype=tl.float32)
z_i = tl.zeros([BLOCK_N], dtype=tl.float32)
acc = tl.zeros([BLOCK_D, BLOCK_N], dtype=tl.float32)
for start_n in range(0, N, BLOCK_N):
k_ptrs = k_ptr + pid_h * stride_kh + offs_n[None, :] * stride_kd + start_n * stride_kd
k = tl.load(k_ptrs, mask=offs_n[None, :] < N - start_n, other=0.0)
# S = Q @ K^T
s = tl.dot(q.T, k) # (1, BLOCK_N)
# Online safe softmax
m_ij = tl.maximum(m_i, s)
p = tl.exp(s - m_ij)
alpha = tl.exp(m_i - m_ij)
acc = acc * alpha + p * k.T # weighted accumulate
z_i = z_i * alpha + p
m_i = m_i * 0 + m_ij # broadcast update
output = acc / z_i
# Store
out_ptrs = output_ptr + pid_h * stride_oh + offs_d[:, None] * stride_od
tl.store(out_ptrs, output)
Template 3: FP8 GEMM with Split-K (Inference-Optimized)
@triton.autotune(
configs=[
triton.Config({'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 128, 'BLOCK_SIZE_K': 64, 'SPLIT_K': 4}, num_warps=4),
triton.Config({'BLOCK_SIZE_M': 32, 'BLOCK_SIZE_N': 128, 'BLOCK_SIZE_K': 64, 'SPLIT_K': 8}, num_warps=4),
triton.Config({'BLOCK_SIZE_M': 16, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 128, 'SPLIT_K': 16}, num_warps=8),
],
key=['M', 'N', 'K'],
prune_configs_by={
'early_config_prune': lambda configs, named_args: [
c for c in configs if c.kwargs['BLOCK_SIZE_M'] * c.kwargs['SPLIT_K'] <= 128
],
},
)
@triton.jit
def fp8_gemm_splitk_kernel(
a_ptr, b_ptr, c_ptr, partial_ptr,
M, N, K,
stride_am, stride_ak,
stride_bk, stride_bn,
stride_cm, stride_cn,
BLOCK_SIZE_M: tl.constexpr,
BLOCK_SIZE_N: tl.constexpr,
BLOCK_SIZE_K: tl.constexpr,
SPLIT_K: tl.constexpr,
):
pid = tl.program_id(0)
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
k_block_id = pid // num_pid_m
pid_m = pid % num_pid_m
offs_m = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
offs_n = tl.arange(0, BLOCK_SIZE_N)
offs_k = k_block_id * BLOCK_SIZE_K + tl.arange(0, BLOCK_SIZE_K)
a_ptrs = a_ptr + (offs_m[:, None] * stride_am + offs_k[None, :] * stride_ak)
b_ptrs = b_ptr + (offs_k[:, None] * stride_bk + offs_n[None, :] * stride_bn)
acc = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
for k in range(0, K // SPLIT_K, BLOCK_SIZE_K):
a = tl.load(a_ptrs, mask=offs_k[None, :] < K // SPLIT_K - k, other=0.0)
b = tl.load(b_ptrs, mask=offs_k[:, None] < K // SPLIT_K - k, other=0.0)
acc += tl.dot(a, b)
a_ptrs += BLOCK_SIZE_K * stride_ak
b_ptrs += BLOCK_SIZE_K * stride_bk
# Write partial sum
partial_idx = k_block_id * M + pid_m * BLOCK_SIZE_M
partial_ptrs = partial_ptr + partial_idx
tl.store(partial_ptrs, tl.sum(acc, axis=1)[:, None])
Autotuning Strategy
When Autotuning Is Essential
| Scenario |
Autotune Impact |
| Variable input shapes (VLLM, serving) |
Critical — cache per shape |
| Fixed production shapes |
Run once, freeze config |
| Memory-bound ops (softmax, norms) |
Less critical — memory access pattern dominates |
| Compute-bound ops (GEMM) |
Critical — 2–5x perf difference between configs |
Config Design Heuristics
# Rule of thumb: product of tile dimensions should fit in registers
# BLOCK_SIZE_M * BLOCK_SIZE_N * element_size <= register_budget
# For NVIDIA A100/H100 (fp16 matmul):
configs = [
# Balanced: good all-around
triton.Config({'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 128, 'BLOCK_SIZE_K': 32}, num_warps=4, num_stages=3),
# Throughput: large tiles for compute-bound
triton.Config({'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 256, 'BLOCK_SIZE_K': 64}, num_warps=8, num_stages=4),
# Latency: small tiles for memory-bound / small M
triton.Config({'BLOCK_SIZE_M': 32, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 32}, num_warps=4, num_stages=2),
# AMD MI300X: use fewer warps, may need waves_per_eu
triton.Config({'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 32}, num_warps=4, num_stages=0),
]
Profiling Workflow
Step-by-Step: Profile and Optimize
# 1. Warmup: run once to trigger JIT compilation
output_triton = my_kernel(x, y)
# 2. Benchmark with triton.testing
import triton.testing
ms, min_ms, max_ms = triton.testing.do_bench(
lambda: my_kernel(x, y),
quantiles=[0.5, 0.2, 0.8],
warmup=100, # iterations
rep=100, # measurement iterations
)
# 3. Compare to reference
ms_torch, _, _ = triton.testing.do_bench(lambda: torch.matmul(a, b))
# 4. Compute TFLOPS
tflops = lambda ms: 2 * M * N * K * 1e-12 / (ms * 1e-3)
print(f"Triton: {tflops(ms):.2f} TFLOPS | Torch: {tflops(ms_torch):.2f} TFLOPS")
CUDA Graph Integration (Production)
# After autotuning has selected the best config, capture a CUDA graph:
import torch
def capture_gemm_graph(a, b):
# Warm up with the production shape
_ = triton_matmul(a, b)
torch.cuda.synchronize()
# Capture graph
graph = torch.cuda.CUDAGraph()
with torch.cuda.graph(graph):
c = triton_matmul(a, b)
return graph, c
# Replay for inference — eliminates 1-2ms JIT overhead per launch
graph.replay()
Debugging Cheatsheet
| Problem |
Symptom |
Fix |
| Wrong output |
Off-by-one in offsets |
Check mask logic, use % modulo for boundaries |
| NaN output |
Numerical instability |
Subtract max before exp; check division by zero |
| Slow kernel (memory-bound) |
Low bandwidth util |
Increase tile sizes, check _b128 in ISA |
| Slow kernel (compute-bound) |
Low TFLOPS |
Check tensor core usage in PTX; try num_stages tuning |
| Compilation error |
@triton.jit function issue |
Check for unsupported Python constructs (no dictionaries, no dynamic indexing) |
compute-sanitizer errors |
Out-of-bounds access |
Check mask coverage for partial tiles |
| High launch overhead |
CPU-side latency |
Use CUDA Graphs for production inference |
Quality Gates
| Gate |
Command/Check |
Expected |
| Correctness |
torch.max(torch.abs(ref - triton_out)) |
< 0.01 (fp16) or < 0.5 (fp8) |
| Autotuning |
TRITON_PRINT_AUTOTUNING=1 env var |
Best config printed |
| Tensor core usage |
Check PTX for wgmma/mma |
Present for matmul kernels |
| Memory coalescing |
Check ISA for global_load_dwordx4 |
Present in hot loop |
| LDS usage |
grep "triton_gpu.shared" from MLIR dump |
< 64 KB |
| Occupancy |
Compute from VGPR/LDS counts |
> 50% for compute-bound |
| Speedup |
triton.testing.do_bench |
> 1.5x over naive PyTorch |
Cross-References
triton-kernel-build-design guideline — full design patterns, memory hierarchy, and optimization reference
- Official tutorials: https://triton-lang.org/main/getting-started/tutorials/
dataset-curation-manifest — when building data-loading kernels
embedding-analysis — for understanding embedding compute patterns
References
1---2name: triton-kernel-programming3description: Hands-on implementation template and API reference for writing, tuning, debugging, and benchmarking Triton GPU kernels. Covers the full triton.language API surface, autotuning patterns, profiling workflows, and production integration.4---56# Triton Kernel Programming78## Overview910This skill provides a hands-on reference for building production Triton kernels. It covers the `triton.language` API, autotune decorators, the `@triton.jit` compilation model, debugging/interpreter workflows, and `triton.testing` benchmarks.1112## When to Use1314Use this skill when:15- Implementing any custom GPU compute kernel in Triton16- Optimizing inference latency for small-batch transformer operations17- Fusing operations (e.g., matmul + activation, attention with softmax)18- Porting CUDA kernels to Triton for easier maintenance1920Do not use for:21- Standard PyTorch operations that already run fast (use `torch.compile`)22- Distributed or multi-GPU parallelism patterns23- CPU-bound workloads2425## Installation2627```bash28# Triton ships with PyTorch ≥2.0. Install latest:29pip install -U triton3031# Or build from source for latest features:32git clone https://github.com/triton-lang/triton.git33cd triton34pip install -r python/requirements.txt35pip install .3637# For profiling:38pip install nvitools # NVIDIA profiling helpers39pip install torch_tb_profiler # PyTorch profiling40```4142## Core API Reference4344### `@triton.jit` Decorator4546Compiles a Python function into a GPU kernel. All code inside must be valid Triton (subset of Python + `triton.language` ops).4748```python49@triton.jit50def kernel( # ← compiled kernel51 ptr, # runtime arguments: pointers, scalars52 BLOCK: tl.constexpr, # constexpr: baked in at compile time53):54 pid = tl.program_id(axis=0) # SPMD program index55 ...56```5758### `triton.language` (tl) — Key Operations5960| Category | Operation | Description |61|----------|-----------|-------------|62| **Indexing** | `tl.program_id(axis)` | SPMD program index along axis 0, 1, or 2 |63| **Ranges** | `tl.arange(start, end)` | 1D range tensor for vectorized addressing |64| **Arithmetic** | `tl.sum`, `tl.max`, `tl.min`, `tl.argmax` | Block reduction along axis |65| **Arithmetic** | `tl.dot(a, b)` | Block matrix multiply (triggers tensor cores) |66| **Activation** | `tl.exp`, `tl.log`, `tl.sigmoid`, `tl.tanh` | Element-wise math |67| **Activation** | `tl.sqrt`, `tl.abs`, `tl.where` | Element-wise ops |68| **Memory** | `tl.load(ptr, mask=, other=)` | Vector load from global memory |69| **Memory** | `tl.store(ptr, val, mask=)` | Vector store to global memory |70| **Memory** | `tl.atomic_add(ptr, val)` | Atomic add (for reductions) |71| **Cast** | `tensor.to(tl.float16)` | Type conversion |72| **Cast** | `tl.cast(tensor, tl.float32)` | Explicit type conversion |73| **Debug** | `tl.device_print("x:", x)` | Runtime print |74| **Debug** | `tl.device_assert(cond, "msg")` | Runtime assertion |75| **Debug** | `tl.static_print(x)` | Compile-time print |76| **Debug** | `tl.static_assert(cond, "msg")` | Compile-time assert |7778### Memory Operations — Masking Best Practice7980```python81# Always mask loads/stores for safety:82mask = offsets < n_elements83x = tl.load(ptr + offsets, mask=mask, other=0.0)84# 'other' provides a safe default for out-of-bounds positions8586# For matmul inner loop, use other=0.0 for partial tiles:87a = tl.load(a_ptrs, mask=offsets_k[None, :] < K - k, other=0.0)88b = tl.load(b_ptrs, mask=offsets_k[:, None] < K - k, other=0.0)89```9091## Complete Kernel Templates9293### Template 1: Element-wise Fusion (e.g., LayerNorm)9495```python96@triton.jit97def layernorm_kernel(98 input_ptr, output_ptr, weight_ptr, bias_ptr,99 row_stride, n_cols, eps,100 BLOCK_SIZE: tl.constexpr,101):102 pid = tl.program_id(0)103 row_start = pid * row_stride104 offsets = row_start + tl.arange(0, BLOCK_SIZE)105 mask = tl.arange(0, BLOCK_SIZE) < n_cols106107 x = tl.load(input_ptr + offsets, mask=mask, other=0.0)108 109 # Mean110 mean = tl.sum(x, axis=0) / n_cols111 # Variance112 x_shifted = x - mean113 var = tl.sum(x_shifted * x_shifted, axis=0) / n_cols114 # Normalize115 x_norm = x_shifted / tl.sqrt(var + eps)116 # Scale + shift117 w = tl.load(weight_ptr + tl.arange(0, BLOCK_SIZE), mask=mask)118 b = tl.load(bias_ptr + tl.arange(0, BLOCK_SIZE), mask=mask)119 y = x_norm * w + b120 121 tl.store(output_ptr + offsets, y, mask=mask)122```123124### Template 2: Flash Attention-Style Softmax with Online Safe Computation125126```python127@triton.jit128def fused_attention_kernel(129 q_ptr, k_ptr, v_ptr, output_ptr,130 stride_qh, stride_qd,131 stride_kh, stride_kd,132 stride_vh, stride_vd,133 stride_oh, stride_od,134 H, D,135 BLOCK_D: tl.constexpr,136 BLOCK_N: tl.constexpr,137):138 pid_h = tl.program_id(0) # head index139 140 offs_d = tl.arange(0, BLOCK_D)141 offs_n = tl.arange(0, BLOCK_N)142 143 # Load Q block for this head144 q_ptrs = q_ptr + pid_h * stride_qh + offs_d[:, None] * stride_qd145 q = tl.load(q_ptrs) # (BLOCK_D, 1)146 147 # Online safe softmax over KV sequence148 m_i = tl.full([BLOCK_N], -float('inf'), dtype=tl.float32)149 z_i = tl.zeros([BLOCK_N], dtype=tl.float32)150 acc = tl.zeros([BLOCK_D, BLOCK_N], dtype=tl.float32)151 152 for start_n in range(0, N, BLOCK_N):153 k_ptrs = k_ptr + pid_h * stride_kh + offs_n[None, :] * stride_kd + start_n * stride_kd154 k = tl.load(k_ptrs, mask=offs_n[None, :] < N - start_n, other=0.0)155 156 # S = Q @ K^T157 s = tl.dot(q.T, k) # (1, BLOCK_N)158 159 # Online safe softmax160 m_ij = tl.maximum(m_i, s)161 p = tl.exp(s - m_ij)162 alpha = tl.exp(m_i - m_ij)163 acc = acc * alpha + p * k.T # weighted accumulate164 z_i = z_i * alpha + p165 m_i = m_i * 0 + m_ij # broadcast update166 167 output = acc / z_i168 169 # Store170 out_ptrs = output_ptr + pid_h * stride_oh + offs_d[:, None] * stride_od171 tl.store(out_ptrs, output)172```173174### Template 3: FP8 GEMM with Split-K (Inference-Optimized)175176```python177@triton.autotune(178 configs=[179 triton.Config({'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 128, 'BLOCK_SIZE_K': 64, 'SPLIT_K': 4}, num_warps=4),180 triton.Config({'BLOCK_SIZE_M': 32, 'BLOCK_SIZE_N': 128, 'BLOCK_SIZE_K': 64, 'SPLIT_K': 8}, num_warps=4),181 triton.Config({'BLOCK_SIZE_M': 16, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 128, 'SPLIT_K': 16}, num_warps=8),182 ],183 key=['M', 'N', 'K'],184 prune_configs_by={185 'early_config_prune': lambda configs, named_args: [186 c for c in configs if c.kwargs['BLOCK_SIZE_M'] * c.kwargs['SPLIT_K'] <= 128187 ],188 },189)190@triton.jit191def fp8_gemm_splitk_kernel(192 a_ptr, b_ptr, c_ptr, partial_ptr,193 M, N, K,194 stride_am, stride_ak,195 stride_bk, stride_bn,196 stride_cm, stride_cn,197 BLOCK_SIZE_M: tl.constexpr,198 BLOCK_SIZE_N: tl.constexpr,199 BLOCK_SIZE_K: tl.constexpr,200 SPLIT_K: tl.constexpr,201):202 pid = tl.program_id(0)203 num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)204 k_block_id = pid // num_pid_m205 pid_m = pid % num_pid_m206 207 offs_m = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)208 offs_n = tl.arange(0, BLOCK_SIZE_N)209 offs_k = k_block_id * BLOCK_SIZE_K + tl.arange(0, BLOCK_SIZE_K)210 211 a_ptrs = a_ptr + (offs_m[:, None] * stride_am + offs_k[None, :] * stride_ak)212 b_ptrs = b_ptr + (offs_k[:, None] * stride_bk + offs_n[None, :] * stride_bn)213 214 acc = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)215 216 for k in range(0, K // SPLIT_K, BLOCK_SIZE_K):217 a = tl.load(a_ptrs, mask=offs_k[None, :] < K // SPLIT_K - k, other=0.0)218 b = tl.load(b_ptrs, mask=offs_k[:, None] < K // SPLIT_K - k, other=0.0)219 acc += tl.dot(a, b)220 a_ptrs += BLOCK_SIZE_K * stride_ak221 b_ptrs += BLOCK_SIZE_K * stride_bk222 223 # Write partial sum224 partial_idx = k_block_id * M + pid_m * BLOCK_SIZE_M225 partial_ptrs = partial_ptr + partial_idx226 tl.store(partial_ptrs, tl.sum(acc, axis=1)[:, None])227```228229## Autotuning Strategy230231### When Autotuning Is Essential232233| Scenario | Autotune Impact |234|----------|----------------|235| Variable input shapes (VLLM, serving) | Critical — cache per shape |236| Fixed production shapes | Run once, freeze config |237| Memory-bound ops (softmax, norms) | Less critical — memory access pattern dominates |238| Compute-bound ops (GEMM) | Critical — 2–5x perf difference between configs |239240### Config Design Heuristics241242```python243# Rule of thumb: product of tile dimensions should fit in registers244# BLOCK_SIZE_M * BLOCK_SIZE_N * element_size <= register_budget245246# For NVIDIA A100/H100 (fp16 matmul):247configs = [248 # Balanced: good all-around249 triton.Config({'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 128, 'BLOCK_SIZE_K': 32}, num_warps=4, num_stages=3),250 # Throughput: large tiles for compute-bound251 triton.Config({'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 256, 'BLOCK_SIZE_K': 64}, num_warps=8, num_stages=4),252 # Latency: small tiles for memory-bound / small M253 triton.Config({'BLOCK_SIZE_M': 32, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 32}, num_warps=4, num_stages=2),254 # AMD MI300X: use fewer warps, may need waves_per_eu255 triton.Config({'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 32}, num_warps=4, num_stages=0),256]257```258259## Profiling Workflow260261### Step-by-Step: Profile and Optimize262263```python264# 1. Warmup: run once to trigger JIT compilation265output_triton = my_kernel(x, y)266267# 2. Benchmark with triton.testing268import triton.testing269ms, min_ms, max_ms = triton.testing.do_bench(270 lambda: my_kernel(x, y),271 quantiles=[0.5, 0.2, 0.8],272 warmup=100, # iterations273 rep=100, # measurement iterations274)275276# 3. Compare to reference277ms_torch, _, _ = triton.testing.do_bench(lambda: torch.matmul(a, b))278279# 4. Compute TFLOPS280tflops = lambda ms: 2 * M * N * K * 1e-12 / (ms * 1e-3)281print(f"Triton: {tflops(ms):.2f} TFLOPS | Torch: {tflops(ms_torch):.2f} TFLOPS")282```283284### CUDA Graph Integration (Production)285286```python287# After autotuning has selected the best config, capture a CUDA graph:288import torch289290def capture_gemm_graph(a, b):291 # Warm up with the production shape292 _ = triton_matmul(a, b)293 torch.cuda.synchronize()294 295 # Capture graph296 graph = torch.cuda.CUDAGraph()297 with torch.cuda.graph(graph):298 c = triton_matmul(a, b)299 300 return graph, c301302# Replay for inference — eliminates 1-2ms JIT overhead per launch303graph.replay()304```305306## Debugging Cheatsheet307308| Problem | Symptom | Fix |309|---------|---------|-----|310| Wrong output | Off-by-one in offsets | Check `mask` logic, use `%` modulo for boundaries |311| NaN output | Numerical instability | Subtract max before exp; check division by zero |312| Slow kernel (memory-bound) | Low bandwidth util | Increase tile sizes, check `_b128` in ISA |313| Slow kernel (compute-bound) | Low TFLOPS | Check tensor core usage in PTX; try `num_stages` tuning |314| Compilation error | `@triton.jit` function issue | Check for unsupported Python constructs (no dictionaries, no dynamic indexing) |315| `compute-sanitizer` errors | Out-of-bounds access | Check mask coverage for partial tiles |316| High launch overhead | CPU-side latency | Use CUDA Graphs for production inference |317318## Quality Gates319320| Gate | Command/Check | Expected |321|------|--------------|----------|322| Correctness | `torch.max(torch.abs(ref - triton_out))` | `< 0.01` (fp16) or `< 0.5` (fp8) |323| Autotuning | `TRITON_PRINT_AUTOTUNING=1` env var | Best config printed |324| Tensor core usage | Check PTX for `wgmma`/`mma` | Present for matmul kernels |325| Memory coalescing | Check ISA for `global_load_dwordx4` | Present in hot loop |326| LDS usage | `grep "triton_gpu.shared"` from MLIR dump | `< 64 KB` |327| Occupancy | Compute from VGPR/LDS counts | `> 50%` for compute-bound |328| Speedup | `triton.testing.do_bench` | `> 1.5x` over naive PyTorch |329330## Cross-References331332- `triton-kernel-build-design` guideline — full design patterns, memory hierarchy, and optimization reference333- Official tutorials: https://triton-lang.org/main/getting-started/tutorials/334- `dataset-curation-manifest` — when building data-loading kernels335- `embedding-analysis` — for understanding embedding compute patterns336337## References338339| Resource | Link |340|----------|------|341| Triton Python API | https://triton-lang.org/main/python-api/ |342| Triton Autotune | https://triton-lang.org/main/python-api/generated/triton.autotune.html |343| Triton Tutorials | https://triton-lang.org/main/getting-started/tutorials/ |344| PyTorch User-Defined Triton | https://docs.pytorch.org/tutorials/recipes/torch_compile_user_defined_triton_kernel_tutorial.html |345| Triton Exercises | https://lweitkamp.github.io/triton_exercises/print.html |346| TK-GEMM (Llama3 FP8) | https://pytorch.org/blog/accelerating-llama3 |