triton-kernels
Skill783 starsChanged 20 days agoApache-2.0View on GitHub
This skill provides patterns and guidance for developing portable, optimized Triton kernels that run on NVIDIA and AMD GPUs without modification. For backend-specific tuning, see rocm-kernels (AMD) and xpu-kernels (Intel). Use this skill when: 1. **BLOCK_SIZE for reductions must cover the full reduction dimension.** Use triton.next_power_of_2(dim) in the Python wrapper. Never autotune BLOCK_SIZE when it controls the reduction axis — partial rows give wrong results silently. 2. Masked loads need a safe other value. Use other=0.0 for additive contexts (sum,…
1 other project uses a version of this.
Adopted by
Most recently taken up by
- anthony-maio/triton-skillstheir own version, first seen Mar 5, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- anthony-maio/triton-skills★ 16same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository5 other files it gives its agents.
- AGENTS.mdAGENTS.md
- cpu-kernelsSkill
- cuda-kernelsSkill
- rocm-kernelsSkill
- xpu-kernelsSkill
The file
We split this skill into the rules it gives an agent
9 rules. About 0 are common, 3 are distinctive, and 6 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 18
Use this skill when:
- Very specific: line 34
Masked loads need a safe
othervalue: Use other=0.0 for additive contexts (sum, dot product). - Very specific: line 55
Every Triton kernel is launched as a grid of programs.
- Very specific: line 85
Always mask when BLOCK_SIZE may exceed the actual dimension:
- Very specific: line 163
When NOT to autotune: Never autotune a parameter that controls the reduction dimension (BLOCK_D for rmsnorm, BLOCK_SIZE for softmax row …
- Distinctive: line 167
Grid as lambda: When block sizes are autotuned, use a lambda for the grid since dimensions aren't known until a config is selected:
- Very specific: line 359
Use triton.testing.do_bench for accurate GPU timing:
- Distinctive: line 413
Always validate against PyTorch before benchmarking:
- Very specific: line 491
Use type(module).__name__ not isinstance() (catches all RMSNorm variants)
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
| name | triton-kernels |
| description | Provides guidance for writing and benchmarking portable Triton kernels targeting NVIDIA and AMD GPUs. Covers core DSL patterns, @triton.autotune, numerics (fp16/bf16/fp8), masked loads, reductions, tiling, benchmarking harness, correctness testing, and integration with HuggingFace Kernels Hub (get_kernel). Vendor-neutral: points to rocm-kernels and xpu-kernels for backend-specific tuning. |
| disable-model-invocation | false |
| user-invocable | true |
| allowed-tools | Read, Grep, Glob, Bash |
| argument-hint | kernel type: softmax, matmul, rmsnorm, layernorm, activation, reduction, element-wise, autotune, benchmark, correctness, get_kernel, transformers, diffusers |
Portable Triton Kernels
This skill provides patterns and guidance for developing portable, optimized Triton kernels that run on NVIDIA and AMD GPUs without modification. For backend-specific tuning, see rocm-kernels (AMD) and xpu-kernels (Intel).
When This Skill Applies
Use this skill when:
- Writing new Triton kernels for normalization, activation, attention, or linear algebra ops
- Deciding block sizes, num_warps, num_stages, and autotune configs
- Handling numerics (fp32 accumulation, bf16/fp16 input/output, masked values)
- Setting up correctness tests against a PyTorch reference
- Benchmarking kernel throughput (GB/s or TFLOPS)
- Publishing a Triton kernel to the HuggingFace Kernels Hub via get_kernel
- Fusing multiple ops into a single kernel to reduce DRAM round-trips
Hard Constraints
- BLOCK_SIZE for reductions must cover the full reduction dimension. Use
triton.next_power_of_2(dim)in the Python wrapper. Never autotune BLOCK_SIZE when it controls the reduction axis — partial rows give wrong results silently. - Masked loads need a safe
othervalue. Useother=0.0for additive contexts (sum, dot product). Useother=float('-inf')for max-based reductions (softmax numerator). Using the wrong fill value is the #1 cause of subtle numerical bugs. - Accumulate in fp32. Cast inputs to
tl.float32before reductions. Cast back to the input dtype only at the finaltl.store. Half-precision accumulation compounds rounding errors across hundreds of additions. - All tensors must be contiguous. Assert
x.is_contiguous()in the Python wrapper, or call.contiguous()before computing pointers. Non-contiguous tensors break flat-offset pointer arithmetic. - tl.constexpr parameters must be known at compile time. BLOCK_SIZE,
num_warps, num_stages are compile-time constants. Pass them as kernel
arguments with the
: tl.constexprannotation or via autotune configs.
Core DSL Patterns
Program IDs and Grid Launch
Every Triton kernel is launched as a grid of programs. Each program gets a unique
ID via tl.program_id(axis).
@triton.jit
def my_kernel(x_ptr, output_ptr, n_elements, BLOCK_SIZE: tl.constexpr):
pid = tl.program_id(axis=0)
block_start = pid * BLOCK_SIZE
offsets = block_start + tl.arange(0, BLOCK_SIZE)
mask = offsets < n_elements
x = tl.load(x_ptr + offsets, mask=mask, other=0.0)
# ... compute ...
tl.store(output_ptr + offsets, result, mask=mask)Grid sizing:
grid = (triton.cdiv(n_elements, BLOCK_SIZE),)
my_kernel[grid](x, output, n_elements, BLOCK_SIZE=1024)For 2D grids (e.g. matmul):
grid = (triton.cdiv(M, BLOCK_M), triton.cdiv(N, BLOCK_N))
# program_id(0) = row block, program_id(1) = col blockMasked Loads and Stores
Always mask when BLOCK_SIZE may exceed the actual dimension:
offsets = tl.arange(0, BLOCK_SIZE)
mask = offsets < n_cols
x = tl.load(ptr + offsets, mask=mask, other=0.0)
tl.store(out_ptr + offsets, result, mask=mask)2D Pointer Arithmetic (Tiling)
For loading 2D tiles (matmul, attention):
row_offsets = row_start * BLOCK_M + tl.arange(0, BLOCK_M)
col_offsets = col_start * BLOCK_N + tl.arange(0, BLOCK_N)
# Broadcasting: [:, None] makes column vector, [None, :] makes row vector
ptrs = base_ptr + row_offsets[:, None] * stride_row + col_offsets[None, :]
mask = (row_offsets[:, None] < M) & (col_offsets[None, :] < N)
tile = tl.load(ptrs, mask=mask, other=0.0)Reductions
Row-wise reduction (softmax, layernorm, rmsnorm):
# One program per row. BLOCK_SIZE >= n_cols (next power of 2).
pid = tl.program_id(0)
row_start = pid * stride_row
offsets = tl.arange(0, BLOCK_SIZE)
mask = offsets < n_cols
x = tl.load(x_ptr + row_start + offsets, mask=mask, other=0.0).to(tl.float32)
row_sum = tl.sum(x, axis=0)
row_max = tl.max(x, axis=0)The dot Product (Tile-Level Matmul)
tl.dot(a, b) performs a tile-level matrix multiply that maps to tensor cores.
Requires both operands to have their K dimension >= 16.
# a_tile: (BLOCK_M, BLOCK_K), b_tile: (BLOCK_K, BLOCK_N)
acc += tl.dot(a_tile, b_tile) # acc: (BLOCK_M, BLOCK_N)Autotune
@triton.autotune benchmarks multiple kernel configurations at runtime and
caches the fastest one per problem shape.
@triton.autotune(
configs=[
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 256, 'BLOCK_K': 64}, num_stages=3, num_warps=8),
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 32}, num_stages=4, num_warps=4),
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 128, 'BLOCK_K': 32}, num_stages=4, num_warps=4),
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 64, 'BLOCK_K': 32}, num_stages=5, num_warps=2),
],
key=['M', 'N', 'K'], # re-tune when these values change
)
@triton.jit
def matmul_kernel(..., BLOCK_M: tl.constexpr, BLOCK_N: tl.constexpr, BLOCK_K: tl.constexpr):
...Guidelines:
- key= lists the runtime values that affect which config is fastest. Usually the matrix dimensions.
- num_warps: More warps = more threads per program. Large tiles (128x256) need 8-16 warps. Small tiles (32x64) need 2-4.
- num_stages: Software pipelining. With N stages, N-1 tiles are being prefetched while 1 is being computed. More stages need more SRAM. Use 2-3 for memory-bound ops, 3-5 for compute-bound.
- When NOT to autotune: Never autotune a parameter that controls the reduction dimension (BLOCK_D for rmsnorm, BLOCK_SIZE for softmax row width). The kernel silently produces wrong results if the autotuner picks a value smaller than the actual dimension.
- Grid as lambda: When block sizes are autotuned, use a lambda for the grid
since dimensions aren't known until a config is selected:
grid = lambda meta: (triton.cdiv(M, meta['BLOCK_M']) * triton.cdiv(N, meta['BLOCK_N']),)
num_warps Heuristic
num_warps = 4
if BLOCK_SIZE >= 2048:
num_warps = 8
if BLOCK_SIZE >= 4096:
num_warps = 16num_stages Heuristic
# Query SRAM per SM if available, otherwise default to 2
num_stages = 4 if sram_per_sm > 200_000 else 2Numerics
fp32 Accumulation (Critical)
# Load in native dtype, cast to fp32 for compute, cast back for store
x = tl.load(x_ptr + offsets, mask=mask, other=0.0).to(tl.float32)
# ... reductions and arithmetic in fp32 ...
tl.store(out_ptr + offsets, result.to(tl.float16), mask=mask)Without fp32 accumulation, a sum over 512+ elements in fp16 accumulates visible rounding error (max_abs_diff > 1e-2 vs torch reference).
Safe Softmax Pattern
x = tl.load(x_ptr + offsets, mask=mask, other=float('-inf')).to(tl.float32)
x_max = tl.max(x, axis=0)
x = x - x_max # shift for numerical stability
numerator = tl.exp(x) # exp(-inf) = 0, harmless
denominator = tl.sum(numerator, axis=0)
result = numerator / denominatorThe other=float('-inf') ensures masked positions don't affect the max or the
sum (since exp(-inf) = 0).
Dtype Handling
# Detect input dtype and cast back at the end
input_dtype = x.dtype # only works outside @triton.jit; inside, use tl.float16 etc.
# Inside kernel: always accumulate in fp32
x_fp32 = x.to(tl.float32)
# ... compute ...
out = result.to(x_fp32.dtype) # won't work — use explicit dtype
out = result.to(tl.bfloat16) # explicit castReference Kernel: Softmax
Complete fused softmax — one DRAM read, one DRAM write per row:
@triton.jit
def softmax_kernel(x_ptr, output_ptr, n_cols, stride_row, BLOCK_SIZE: tl.constexpr):
pid = tl.program_id(0)
row_start = pid * stride_row
offsets = tl.arange(0, BLOCK_SIZE)
mask = offsets < n_cols
x = tl.load(x_ptr + row_start + offsets, mask=mask, other=float('-inf')).to(tl.float32)
x_max = tl.max(x, axis=0)
x = x - x_max
numerator = tl.exp(x)
denominator = tl.sum(numerator, axis=0)
result = numerator / denominator
tl.store(output_ptr + row_start + offsets, result.to(tl.float16), mask=mask)
def softmax(x: torch.Tensor) -> torch.Tensor:
assert x.is_contiguous()
n_rows, n_cols = x.shape
output = torch.empty_like(x)
BLOCK_SIZE = triton.next_power_of_2(n_cols)
num_warps = 4 if BLOCK_SIZE <= 1024 else (8 if BLOCK_SIZE <= 4096 else 16)
softmax_kernel[(n_rows,)](
x, output, n_cols, x.stride(0),
BLOCK_SIZE=BLOCK_SIZE, num_warps=num_warps,
)
return outputWhy this beats PyTorch: naive softmax does ~8MN memory ops (max, subtract, exp, sum, divide — each round-trips through DRAM). This kernel does 2MN (one read, one write). ~4x reduction in memory traffic.
Reference Kernel: Matmul with Tiling
Blocked matrix multiplication with accumulation loop:
@triton.jit
def matmul_kernel(
a_ptr, b_ptr, c_ptr,
M, N, K,
stride_am, stride_ak, stride_bk, stride_bn, stride_cm, stride_cn,
BLOCK_M: tl.constexpr, BLOCK_N: tl.constexpr, BLOCK_K: tl.constexpr,
):
pid_m = tl.program_id(0)
pid_n = tl.program_id(1)
rm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
rn = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
for k in range(0, K, BLOCK_K):
rk = k + tl.arange(0, BLOCK_K)
a = tl.load(a_ptr + rm[:, None] * stride_am + rk[None, :] * stride_ak,
mask=(rm[:, None] < M) & (rk[None, :] < K), other=0.0)
b = tl.load(b_ptr + rk[:, None] * stride_bk + rn[None, :] * stride_bn,
mask=(rk[:, None] < K) & (rn[None, :] < N), other=0.0)
acc += tl.dot(a, b)
c = acc.to(tl.float16)
tl.store(c_ptr + rm[:, None] * stride_cm + rn[None, :] * stride_cn, c,
mask=(rm[:, None] < M) & (rn[None, :] < N))Key points:
- Accumulate in fp32, store in target dtype
other=0.0for masked positions — zeros don't affect additiontl.dotrequires K dimension >= 16
Reference Kernel: RMSNorm
@triton.jit
def rmsnorm_kernel(
x_ptr, weight_ptr, out_ptr,
stride_row, D,
eps: tl.constexpr,
HAS_WEIGHT: tl.constexpr,
BLOCK_D: tl.constexpr,
):
row = tl.program_id(0)
offsets = tl.arange(0, BLOCK_D)
mask = offsets < D
x = tl.load(x_ptr + row * stride_row + offsets, mask=mask, other=0.0).to(tl.float32)
variance = tl.sum(x * x, axis=0) / D
rms_inv = tl.rsqrt(variance + eps)
if HAS_WEIGHT:
w = tl.load(weight_ptr + offsets, mask=mask, other=1.0).to(tl.float32)
out = x * rms_inv * w
else:
out = x * rms_inv
tl.store(out_ptr + row * stride_row + offsets, out.to(x.dtype), mask=mask)
def triton_rmsnorm(x, weight=None, eps=1e-6):
assert x.is_contiguous()
x_2d = x.view(-1, x.shape[-1])
out = torch.empty_like(x_2d)
M, D = x_2d.shape
has_weight = weight is not None
if not has_weight:
weight = torch.empty(0, device=x.device)
BLOCK_D = triton.next_power_of_2(D)
num_warps = 4 if BLOCK_D <= 1024 else (8 if BLOCK_D <= 4096 else 16)
rmsnorm_kernel[(M,)](
x_2d, weight, out, x_2d.stride(0), D, eps, has_weight,
BLOCK_D=BLOCK_D, num_warps=num_warps, num_stages=2,
)
return out.view_as(x)Benchmarking
Throughput Measurement
Use triton.testing.do_bench for accurate GPU timing:
ms = triton.testing.do_bench(lambda: my_kernel_wrapper(x))GB/s Calculation
For memory-bound kernels (softmax, rmsnorm, activations):
# Total bytes moved = bytes_read + bytes_written
# For in-place-like ops: 2 * numel * element_size (one read + one write)
gbps = 2 * x.numel() * x.element_size() * 1e-9 / (ms * 1e-3)TFLOPS Calculation
For compute-bound kernels (matmul):
# Matmul: 2*M*N*K FLOPs (multiply + add for each output element)
tflops = 2 * M * N * K * 1e-12 / (ms * 1e-3)Benchmark Harness Template
@triton.testing.perf_report(
triton.testing.Benchmark(
x_names=['N'],
x_vals=[128 * i for i in range(2, 100)],
line_arg='provider',
line_vals=['triton', 'torch'],
line_names=['Triton', 'PyTorch'],
styles=[('blue', '-'), ('green', '-')],
ylabel='GB/s',
plot_name='kernel-performance',
args={'M': 4096},
))
def benchmark(M, N, provider):
x = torch.randn(M, N, device='cuda', dtype=torch.float32)
if provider == 'torch':
ms = triton.testing.do_bench(lambda: torch.softmax(x, dim=-1))
elif provider == 'triton':
ms = triton.testing.do_bench(lambda: my_softmax(x))
gbps = 2 * x.numel() * x.element_size() * 1e-9 / (ms * 1e-3)
return gbps
benchmark.run(save_path='.', print_data=True)Correctness Testing
Always validate against PyTorch before benchmarking:
def test_kernel(size, atol=1e-3, rtol=1e-3):
torch.manual_seed(0)
x = torch.randn(size, device='cuda')
y_triton = my_kernel(x)
y_ref = torch_reference(x)
torch.testing.assert_close(y_triton, y_ref, atol=atol, rtol=rtol)
print("PASSED")
# Test with irregular shapes to verify masking
test_kernel((1823, 781))
test_kernel((1, 1))
test_kernel((4096, 4096))HuggingFace Kernels Hub Integration
Load pre-compiled kernels from the Hub (no local compilation):
from kernels import get_kernel, has_kernel
# Check availability and load
if has_kernel("kernels-community/activation", version=1):
activation = get_kernel("kernels-community/activation", version=1)
y = torch.empty_like(x)
activation.gelu_fast(y, x)For Triton kernels specifically, the kernel is packaged as a noarch build (no
per-CUDA-version compilation needed). The build.toml uses backends = ["triton"].
Publishing Your Own Triton Kernel
# build.toml
[general]
name = "my-triton-kernel"
backends = ["triton"]
version = 1
license = "Apache-2.0"
[general.hub]
repo-id = "your-username/my-triton-kernel"Build and upload:
kernel-builder build-and-uploadTransformers Integration
Patch model modules with your custom kernel:
from transformers import AutoModelForCausalLM
def patch_rmsnorm(model):
for name, module in model.named_modules():
if type(module).__name__ == 'RMSNorm':
eps = getattr(module, 'variance_epsilon', None) or getattr(module, 'eps', 1e-6)
has_weight = hasattr(module, 'weight') and module.weight is not None
if has_weight:
def make_forward(mod, epsilon):
def forward(x):
return triton_rmsnorm(x, mod.weight, eps=epsilon)
return forward
module.forward = make_forward(module, eps)
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf", torch_dtype=torch.bfloat16)
model.to("cuda")
patch_rmsnorm(model)Key points:
- Use
type(module).__name__notisinstance()(catches all RMSNorm variants) - Weight may be None if
elementwise_affine=False - Inject before
enable_model_cpu_offload()
Common Pitfalls
| Symptom | Cause | Fix |
| Silent wrong results | Autotuned BLOCK_SIZE < reduction dim | Compute BLOCK_SIZE = triton.next_power_of_2(dim) in wrapper, never autotune |
| NaN / huge values | fp16 accumulation overflow | Cast to tl.float32 before reductions |
| Wrong softmax output | other=0.0 in masked load | Use other=float('-inf') for max-reduction inputs |
| Crash on non-contiguous input | Flat offset math assumes contiguity | Assert is_contiguous() or call .contiguous() |
| "K >= 16" error from tl.dot | BLOCK_K too small | Minimum BLOCK_K = 16 (tensor core constraint) |
| Autotune runs every call | Missing key= parameter | Add key=['M', 'N', 'K'] to cache across calls |
| Performance worse than PyTorch at small N | Kernel launch overhead dominates | Triton kernels shine at large N; small N is launch-bound |
Performance Profiling
# NVIDIA
nsys profile -o profile python your_kernel.py
ncu --set full -o metrics python your_kernel.py
# AMD (ROCm) — see rocm-kernels skill
rocprof --stats python your_kernel.pySee Also
Scripts
- benchmark_template.py — Reusable benchmark harness for any Triton kernel
- correctness_template.py — Correctness test template
References
- autotune-guide.md — Deep dive on autotune configs, key parameter, and pitfalls
- kernel-patterns.md — Additional kernel patterns (dropout, fused add+norm, element-wise)
- benchmarking-guide.md — GB/s vs TFLOPS, roofline analysis, when to expect gains
Backend-Specific Skills
- rocm-kernels — AMD MI355X / R9700 tuning, ROCm constraints
- xpu-kernels — Intel XPU Triton tuning
External Resources
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

