Convert a raw-pointer or block_ptr Triton kernel to the tl.make_tensor_descriptor API, producing a tensor_descriptor.py. Backend-agnostic — no Spyre invariants. Use when asked to port a kernel to tensor descriptors or modernize block_ptr/raw-pointer loads.
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.
Convert a raw-pointer or block_ptr Triton kernel to the tl.make_tensor_descriptor API, producing a tensor_descriptor.py. Backend-agnostic — no Spyre invariants. Use when asked to port a kernel to tensor descriptors or modernize block_ptr/raw-pointer loads.
Tensor-Descriptor Conversion Skill
Modernize a GPU-shaped Triton kernel that uses raw pointer arithmetic or the
deprecated tl.make_block_ptr API into one that uses tl.make_tensor_descriptor.
This is a backend-agnostic transformation: it changes how memory is
addressed, not how work is distributed.
Trigger
Use when the user asks to convert a kernel to tensor descriptors, port
block_ptr/raw-pointer loads to make_tensor_descriptor, or produce a
tensor_descriptor.py from an original.py / block_ptr.py.
kernel_name: directory under kernels/ (e.g. rms_norm, matmul)
Source: kernels/<name>/original.py (and block_ptr.py if present)
Output: kernels/<name>/tensor_descriptor.py
Kernel function named _<name>_kernel_td
Procedure
Scan every access and pick its descriptor form. Before writing anything,
list the original's loads/stores and map each to a descriptor op — most are
not the plain desc.load/desc.store (see descriptor-rules.md):
data-dependent gather/scatter — tl.load(ptr + idx) where idx was
loaded at runtime (e.g. token_id = tl.load(...) then
tl.load(row_ptr + token_id)) → desc.gather(indices, offset) /
desc.scatter(value, indices, offset) on a multi-dim descriptor (§5).
This IS portable — do not mistake it for non-portable.
A kernel is non-portable only if an access hits a genuine wall (§4–§5):
a last dim that cannot reach ≥ 16 bytes, or an indirect index that mixes
multiplicatively / fuses dims / is non-delinearizable (hash, f(loaded)).
In that case say so, name the blocking access, and stop — do NOT ship a hybrid
that leaves a raw tl.load/tl.store in and annotates it (violates
checklist item 1). But reach for desc.gather/desc.scatter before declaring
defeat: a per-row/per-program index into a structured tensor is portable.
Replace all pointer arithmetic / block_ptr with descriptors. Follow
../_shared/descriptor-rules.md §1 — one
descriptor per tensor, any rank, strides from the signature or computed for
row-major.
Drop redundant tail masks. The descriptor shape is the boundary; do
not port the original's mask / tl.where / other= machinery. See
descriptor-rules.md §2 — including the non-identity-fill caveat (a product
reduction with other=1.0, a max with -inf etc. still needs a tail fix-up).
Respect the 16-byte last-dim minimum (descriptor-rules.md §4). Resolve
any scalar descriptor per that rule — widen the last dim to ≥ 16 bytes, or
declare the kernel non-portable. Do not emit a [..., 1] descriptor.
Preserve the kernel signature when possible so the wrapper can call both
forms with minimal branching. If the descriptor form needs a value the
original derived from the grid (e.g. a row count), recover it from existing
args first; add a new parameter only as a last resort and update the wrapper.
Keep the grid as-is. TD conversion does not cap or reshape the grid —
one program per work item (e.g. grid = (n_rows,)) is fine.
Output file structure
# SPDX-License-Identifier: <same as original># SPDX-FileCopyrightText: <same as original>## Tensor-descriptor conversion of <kernel_function_name>.# Original: <source path># Changes summarized in kernels/<name>/conversion-notes.md.import triton
import triton.language as tl
@triton.jitdef_<name>_kernel_td(..., BLOCK_...: tl.constexpr):
"""<Brief description.>"""
...
Document the conversion
After writing the kernel, record what changed in
kernels/<name>/conversion-notes.md — a single file shared with spyre-convert
(this skill owns the tensor-descriptor section; spyre-convert appends its own).
Create the file if it does not exist:
Keep it short — the why behind non-obvious choices, not a line-by-line diff.
If the file already exists (e.g. a prior run, or spyre-convert ran first), edit
the ## Tensor-descriptor conversion section in place and leave any
## Spyre-aware conversion section untouched.
Checklist
Every access mapped to a descriptor op — affine → desc.load/store,
data-dependent index → desc.gather/scatter (§5). Kernel declared
non-portable + STOPPED only on a genuine §4/§5 wall (un-widenable scalar,
multiplicative/fused/non-delinearizable index); never a raw-op hybrid.
All memory accesses use descriptors — no raw tl.load/tl.store, no
tl.make_block_ptr, no tl.advance, no order/boundary_check