Write numerical equivalence tests for a tensor-descriptor kernel (tensor_descriptor.py), comparing it against the original kernel. Produces tests/triton/test_<name>_td.py. Use when asked to test a _td kernel.
Installation
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Write numerical equivalence tests for a tensor-descriptor kernel (tensor_descriptor.py), comparing it against the original kernel. Produces tests/triton/test_<name>_td.py. Use when asked to test a _td kernel.
Tensor-Descriptor Test Skill
Write numerical-equivalence tests for a _td kernel against the original, using
random inputs. Output goes to tests/triton/test_<name>_td.py.
This is for the descriptor-API form.
Trigger
Use when asked to write/add tests for a tensor_descriptor.py (_td) kernel.
Read kernels/<name>/tensor_descriptor.py for the signature.
Read kernels/<name>/wrapper.py for launch shapes/dtypes.
Read tests/triton/test_<name>.py if present, to match style.
Launching the kernel — reuse wrapper.py
Use the existing kernels/<name>/wrapper.py to launch both kernels. It
already dispatches to the _td kernel (typically via a kernel_fn= argument)
and owns the grid, allocator, and signature — call it for the _td form and the
original form alike. Do not copy its launch logic into the test as a private
helper: a forked launch path drifts from production and has to be updated twice
(exactly the duplication that bites when the kernel signature changes).
If the wrapper does not expose a parameter a test needs (e.g. a tile-batching
constexpr, or a stride you want to vary), extend wrapper.py to accept it
so the test and the production path stay on the same code — only fall back to a
direct in-test launch when changing the wrapper is genuinely not appropriate.
# SPDX-License-Identifier: Apache-2.0"""Numerical tests for the tensor-descriptor <name> kernel."""import pytest
import torch
from kernels.<name>.tensor_descriptor import _<name>_kernel_td
from kernels.<name>.wrapper import <wrapper_fn>
# Reference vs _td both go through the wrapper:# out_ref = <wrapper_fn>(x, ...) # original kernel# out_td = <wrapper_fn>(x, ..., kernel_fn=_<name>_kernel_td) # _td kernel@pytest.fixturedefdevice():
ifnot torch.cuda.is_available():
pytest.skip("CUDA not available")
return torch.device("cuda")
Test categories
1. Correctness vs original
Parametrize over representative shapes (include non-divisible), seed with
torch.manual_seed(42), random inputs, compare with
torch.testing.assert_close.
Tolerances — size to the output dtype's ULP, not a round number. When the
_td kernel and the original differ only in float op order (a reduction
accumulated across tiles vs reduced per-tile), output differs by ≤ ~1 ULP of the
output dtype. Define a per-dtype TOL dict once:
Loosen up only with a stated reason in a comment — e.g. the divergence is not
pure reordering (a precomputed inv_rms that multiplies, a tl.dot whose K
accumulates in a different order than the reference), or the KB documents a
precision difference. f32 outputs should be near-exact (reductions
accumulate in f32) — keep 1e-5.
Single-tile cases are bitwise-identical. When a row/problem fits one tile
(size <= BLOCK_SIZE), there is no reduction-order difference — assert
atol=0, rtol=0. A loose tolerance there hides regressions.
2. Edge cases
Non-divisible shapes (dimensions not a multiple of BLOCK).
Minimum size (smaller than one tile).
Asymmetric shapes, zero input (eps handling), large values.
Any kernel-specific tile-batching parameter (one controlling how many work
items a program processes per tile) — parametrize over its valid values,
including ones that do not divide the work-item count, to exercise OOB
padding. Respect any power-of-2 constraint the descriptor imposes on a block
dimension.
Checklist
Test at tests/triton/test_<name>_td.py
Both kernels launched via wrapper.py (no forked launch path; extend
the wrapper if a needed param is missing); correctness parametrized over shapes
Edge cases: non-divisible, minimum size, asymmetric, zero, large
Compare with torch.testing.assert_close against the original only
Per-dtype TOL dict; loosening has a stated reason
Single-tile cases assert atol=0, rtol=0
torch.manual_seed(42) everywhere; random (not handcrafted) inputs