This is XPU-only. Never add triton.set_allocator or any host-launch change
— device-side descriptors need no global-memory workspace on XPU (that hook is
NVIDIA-TMA-specific). The migration is a pure kernel-body rewrite unless Rule 12
applies.
-
tl.make_block_ptr(base, shape, strides, offsets, block_shape, order) →
tl.make_tensor_descriptor(base, shape, strides, block_shape). Drop
offsets and order from the constructor. The initial offsets values
become the starting load/store offsets (Rules 2 & 7). Hoist the descriptor to
the same scope as the old make_block_ptr — it is loop-invariant, create it
once before the loop.
-
tl.advance(ptr, delta) → integer offset increment. Delete the
tl.advance. Introduce a running integer for each advancing dimension,
initialized to that dim's initial offset (off_k = 0 typically), and add the
delta at the loop tail (off_k += BLOCK_K). Pass the running offset into
desc.load([...]) / desc.store([...]).
-
tl.load(ptr, boundary_check=...) → desc.load([d0, d1]). Drop
boundary_check — the descriptor's shape already masks out-of-bounds
elements to the pad value, which is exactly what boundary_check did.
Padding caveat: padding_option is set at descriptor-creation time, not
at load time. If the original load used padding_option="nan", carry it onto
the constructor: tl.make_tensor_descriptor(..., padding_option="nan").
"zero"/"" is the default and is dropped.
-
tl.store(ptr, val, boundary_check=...) → desc.store([d0, d1], val).
Drop boundary_check; out-of-bounds writes are ignored automatically. Store
has no padding_option.
-
Block-pointer loads/stores never carry mask=/other= (the API forbids
it). Their only masking is boundary_check, handled by Rules 3–4. A mask=
on the input only appears on legacy tensor-of-pointer loads — see Rule 9.
(The skill itself only emits a mask= tensor-of-pointer load in the single
sanctioned case of a non-unit-stride rank-1 block pointer that has no legal
descriptor — see Rule 11.)
-
Transpose (order=(0, 1) in the block pointer). Do NOT emit a descriptor
whose last stride ≠ 1 — that drops off the fast path. Instead describe the
tensor in its native row-major layout (so the last stride is 1) and apply
.T to the loaded block. Concretely, if B is stored (N, K) row-major but
the dot wants (K, N):
b_desc = tl.make_tensor_descriptor(base=b_ptr, shape=(N, K), strides=(stride_bn, stride_bk), block_shape=(BLOCK_N, BLOCK_K))
then b = b_desc.load([pid_n*BLOCK_N, off_k]).T.
-
Static offsets (a dim that never advances, e.g. pid_m * BLOCK_M) → pass
the expression directly in .load() / .store(); no running variable needed.
-
Batched / 3D → pre-add the batch offset to base
(base = a_ptr + bid.to(tl.int64) * stride_az) and keep the descriptor 2D
over the M×K (or K×N) slice. This is the rank-3 special case of Rule 11.
-
Masked tensor-of-pointer loads (tl.load(ptrs, mask=..., other=...), not
block pointers — may be interleaved in the same kernel). Classify the mask:
- Boundary mask — a range check vs a dimension limit, e.g.
offs_k[None,:] < K - k*BLOCK_K or offs_m[:,None] < M. This is a bounds
check: fold it into the descriptor shape (e.g. shape=(M, K)), drop the
mask, and let the descriptor zero-pad.
- Data-dependent / non-boundary mask — causal masks, value-dependent
predicates. Cannot be a rectangular
shape. Load the full block with
desc.load([...]), then re-apply in registers: v = tl.where(mask, v, other).
- Mixed — split: boundary part →
shape, residual predicate → tl.where.
-
Type-annotation hygiene → if the last stride is annotated
tl.int64/tl.int32, flag it (best practice: never annotate the last
stride; prefer tl.constexpr or no annotation). Do not silently change
a signature.
-
Rank normalization → always aim for 2D (XPU optimizes only 2D; block
pointers may be any rank):
-
Block pointer received as a helper argument. A block pointer can never
arrive from host launch (there is no host-side block-pointer object); it can
only be a @triton.jit → @triton.jit parameter — and arriving that way
already defeats the fast path (a descriptor passed across tt.call loses its
identity). If the kernel body uses a block pointer that was not created
locally, the translation is not body-only:
- Change the helper's signature to take raw
base, shape, strides, offsets
(and block_shape/order if not already tl.constexpr there).
- Build the descriptor inside the helper with
tl.make_tensor_descriptor.
- Update every call site to pass the raw ingredients.
Report this as a caller-affecting change and list the touched call sites.
No-op for the common create-and-use-in-the-same-kernel case.