This is an automated email from the ASF dual-hosted git repository.
spectrometerHBH pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new 93098fbbbe [TIRx][CUDA] Replace source helpers with typed PTX forms
(#20140)
93098fbbbe is described below
commit 93098fbbbe95aea6663d64fd038f1d566582b2b1
Author: Hongyi Jin <[email protected]>
AuthorDate: Mon Aug 17 15:40:40 2026 -0400
[TIRx][CUDA] Replace source helpers with typed PTX forms (#20140)
## Motivation
TIRx low-level kernels need to reject `tirx.cuda.func_call`: it embeds
arbitrary CUDA source outside the typed IR and PTX dialect, so the
compiler cannot validate operand types, destination liveness,
instruction legality, or target placement.
The downstream audit in mlc-ai/TIRx-kernels#62 found four missing
general capabilities: inactive-path liveness for predicated
destinations, the MXF4 block32 MMA spelling, typed SMEM descriptor
operations, and correct target binding for private PrimFunc helpers
called from a `T.device_entry` region. This PR adds those capabilities
without kernel-specific hooks.
## Change-by-change rationale
### 1. One `preserve_dst` flag for predicated destinations
Files: `python/tvm/backend/cuda/ptx/engine.py`, `render.py`, and
`table.py`.
- Change: PTX calls use one Boolean choice, `preserve_dst=False` by
default and `preserve_dst=True` when the old destination must remain
live.
- Why one flag is sufficient: a false PTX predicate always suppresses
the instruction write. The only compiler-level choice is whether the
inline-asm destination also needs its previous C++ value as an input.
`False` means it does not; `True` means it does.
- `preserve_dst=False`: use the normal write-only `"="` constraint. The
inactive value is undefined to the caller and must be merged or guarded
before use.
- `preserve_dst=True`: use a read-write `"+"` constraint and seed any
carrier bridge from the old destination. The inactive path therefore
retains that value.
- Why not two flags: `undefined_dst` was exactly the complement of
`preserve_dst`, so exposing both created contradictory and invalid
Boolean combinations. The renderer now derives the default undefined
policy from `entry.has_dst` plus `preserve_dst=False`.
- Validation: `preserve_dst=True` requires both `pred=...` and an
ordinary written destination. Accumulators already have `rw="rw"` and
need no extra flag; stores and other destination-free instructions
reject it.
- IR/codegen identity: the marker is now only `pred` or `pred,keep`. The
renderer still emits distinct `_pred_undef` and `_pred_keep` helper
names, preserving helper identity and the exact `"="` versus `"+"` ABI.
Hardware behavior is unchanged in both cases: predicate false means the
PTX instruction does not execute. The flag only states whether the
surrounding compiler must keep the previous destination value live.
The downstream GDN path needs both choices in one expression chain: its
predicated shared loads use the default undefined destination until a
`selp`, while the later predicated `ex2` uses `preserve_dst=True` to
retain the zero selected for inactive lanes.
### 2. Explicit MXF4 block32 `tcgen05.mma` form
File: `python/tvm/backend/cuda/ptx/table.py`.
- Change: add SS and TS entries for
`tcgen05.mma.*.block_scale.block16/block32` with a dedicated
`block_size` modifier.
- Why: the paged MQA kernel needs
`tcgen05.mma.cta_group::1.kind::mxf4.block_scale.block32`. The table
previously covered the scale-vector spelling but not the explicit
scale-block-size spelling, forcing the kernel to hide a valid
instruction in a CUDA source helper.
- ISA constraint: a validator encodes the legal matrix instead of
accepting every token combination: `mxf8f6f4` and `mxf4` allow only
block32; `mxf4nvf4` allows block16 and block32. Keeping this as a
separate table form preserves exact instruction spelling and rejects
invalid `mxf4.block16` at IR construction time.
### 3. Typed SMEM descriptor address updates
File: `python/tvm/backend/cuda/tile_primitive/common.py`.
- Change: introduce `smem_desc_replace_lo` and implement
`smem_desc_add_16B_offset` by reinterpreting the descriptor as
`uint32x2`, updating only lane 0, and repacking it.
- Why: the low 32 bits are the address lane and the high 32 bits contain
descriptor control fields. A generic 64-bit add could carry into those
control fields. The previous implementation used a CUDA union inside
`func_call`; the typed implementation makes the no-carry uint32 behavior
explicit and gives descriptor address replacement one reusable
authority.
### 4. Typed descriptor warp uniformization
Files: `python/tvm/backend/cuda/lang/smem_desc.py` and
`python/tvm/backend/cuda/tile_primitive/gemm_async/tcgen05.py`.
- Change: replace `smem_desc_make_lo_uniform` CUDA helpers with
`mov.b64` unpacking, `shfl.sync.idx.b32` of the low lane, and `mov.b64`
repacking while preserving the high lane.
- Why: only the address lane needs to be broadcast from lane 0; the
descriptor metadata must remain unchanged. Expressing the sequence in
typed PTX removes an arbitrary source boundary and lets normal
operand/type validation see the operation.
- Lowering detail: the tile-primitive implementation emits explicit
local buffers and a `SeqStmt` because it constructs IR nodes below the
script layer. The script-level `SmemDescriptor` method emits the same
typed operation sequence at its abstraction layer.
### 5. `T.device_entry` as the device-side target boundary
File: `src/tirx/transform/bind_target.cc`.
- Change: both the function classifier and call substitutor treat
`attr::kDeviceEntry` as entering GPU scope, alongside thread extents and
virtual-thread attributes.
- Why: a TIRx PrimFunc may contain host preparation followed by a device
body marked by `T.device_entry`. A private typed helper called from that
device body must receive the CUDA target. Without recognizing the
marker, BindTarget can classify or clone the helper as host-side,
producing a cross-target call instead of a device helper.
- Scope: this does not globally classify the whole function as device
code; it changes classification only while visiting the body dominated
by the existing canonical device-entry marker.
This enables the downstream weighted-ReLU reduction and acquire polling
loop to remain typed private PrimFuncs rather than CUDA source helpers.
### 6. Generated PTX typing surface
Files: `python/tvm/backend/cuda/ptx/gen_stubs.py` and
`python/tvm/script/tirx.pyi`.
- Change: generated PTX chain signatures expose `pred` and, for families
with an ordinary written destination, the single `preserve_dst: bool =
False` flag.
- Why: the static typing surface must match the runtime dialect API. The
stub remains generated from the instruction table rather than becoming a
second hand-maintained source of truth.
- Generator fix: line wrapping now checks the length of the final
indented stub line directly. Removing the redundant flag exposed an
off-by-one boundary in the previous indirect threshold.
### 7. Regression tests
Files: `tests/python/tirx/codegen/test_ptx_dialect.py`,
`tests/python/tirx-transform/test_tir_transform_helpers.py`, and
`tests/python/tirx/operator/tile_primitive/cuda/gemm_async/test_gemm_async.py`.
- Predicated destinations: verify default write-only versus opted-in
read-write constraints, `_pred_undef` versus `_pred_keep` helper
identity, IR markers and round trips, invalid flag placement, and ptxas
acceptance.
- MXF4 block size: verify the exact block32 opcode reaches generated
CUDA/PTX and ptxas, and verify invalid `mxf4.block16` is rejected.
- BindTarget: use one private helper from both host code and the region
after `T.device_entry`, then verify separate LLVM and CUDA-targeted
functions are produced.
- Descriptor lowering: verify all descriptor modes still build and that
warp-scoped uniformization appears as typed `T.ptx.shfl_sync` before the
kernel replacement point instead of checking for source-helper names.
## Downstream evidence
The downstream migration is mlc-ai/TIRx-kernels#62.
- GDN predication produced byte-identical SASS; fixed-config
baseline/candidate ratios were 0.9998x, 1.0001x, and 1.0098x. After
simplifying to one flag, all three configs passed again against the
frozen FlashInfer oracle.
- The typed private weighted-ReLU helper retained 168 registers, zero
stack/local memory, and the intended packed arithmetic; ratios were
1.0011x and 1.0116x.
- The typed acquire polling helper retained the acquire load/control
structure and completed TP1 and TP4 correctness; ratios were about
1.0003x and 0.9955x.
- The affected downstream correctness matrix passed, and the low-level
IR registry audit no longer needs source helpers for any of these cases.
## Validation
- `python -m pytest -q tests/python/tirx/codegen/test_ptx_dialect.py`:
47 passed, 32 skipped
- BindTarget regression selection: 1 passed
- descriptor lowering regression selection: 6 passed
- downstream GDN frozen-oracle correctness: 3 configs passed
- downstream low-level IR checker: 2 passed
- changed-file pre-commit: passed in both repositories
- generated `tirx.pyi`: byte-identical to `python -m
tvm.backend.cuda.ptx.gen_stubs` output
## Related
Downstream kernel migration:
https://github.com/mlc-ai/TIRx-kernels/pull/62
---
python/tvm/backend/cuda/lang/smem_desc.py | 19 +-
python/tvm/backend/cuda/ptx/engine.py | 49 ++-
python/tvm/backend/cuda/ptx/gen_stubs.py | 10 +-
python/tvm/backend/cuda/ptx/render.py | 30 +-
python/tvm/backend/cuda/ptx/table.py | 59 ++-
python/tvm/backend/cuda/tile_primitive/common.py | 26 +-
.../cuda/tile_primitive/gemm_async/tcgen05.py | 32 +-
python/tvm/script/tirx.pyi | 487 ++++++++++++++++-----
src/tirx/transform/bind_target.cc | 6 +-
.../tirx-transform/test_tir_transform_helpers.py | 55 +++
tests/python/tirx/codegen/test_ptx_dialect.py | 104 ++++-
.../cuda/gemm_async/test_gemm_async.py | 17 +-
12 files changed, 699 insertions(+), 195 deletions(-)
diff --git a/python/tvm/backend/cuda/lang/smem_desc.py
b/python/tvm/backend/cuda/lang/smem_desc.py
index b1d850abd2..d6244fd73a 100644
--- a/python/tvm/backend/cuda/lang/smem_desc.py
+++ b/python/tvm/backend/cuda/lang/smem_desc.py
@@ -43,13 +43,14 @@ class SmemDescriptor:
def make_lo_uniform(self):
"""Broadcast the lower 32 bits to all warp lanes via
``__shfl_sync``."""
- func_name = "smem_desc_make_lo_uniform"
- source_code = f"""
-__forceinline__ __device__ void {func_name}(uint64_t* desc) {{
- SmemDescriptor* d = reinterpret_cast<SmemDescriptor*>(desc);
- d->lo = __shfl_sync(0xffffffff, d->lo, 0);
-}}
-"""
- return T.cuda.func_call(
- func_name, T.address_of(self._buf[0]), source_code=source_code,
return_type="void"
+ desc_lo = T.alloc_local([1], "uint32")
+ desc_hi = T.alloc_local([1], "uint32")
+ T.ptx.mov.b64(desc_lo[0], desc_hi[0], self._buf[0])
+ T.ptx.shfl_sync.idx.b32(
+ desc_lo[0],
+ desc_lo[0],
+ T.uint32(0),
+ T.uint32(0x1F),
+ T.uint32(0xFFFFFFFF),
)
+ T.ptx.mov.b64(self._buf[0], desc_lo[0], desc_hi[0])
diff --git a/python/tvm/backend/cuda/ptx/engine.py
b/python/tvm/backend/cuda/ptx/engine.py
index 43a9d4d7a7..3e29523af2 100644
--- a/python/tvm/backend/cuda/ptx/engine.py
+++ b/python/tvm/backend/cuda/ptx/engine.py
@@ -118,11 +118,13 @@ def _make_codegen(entry: InstructionEntry):
# operand layout -- which may depend on them, a register group's length
# being a function of the modifiers -- is looked up from them (memoized
# per token combination in the table).
- # The marker is a comma-joined flag set ("pred" and/or "p<i>"); codegen
- # only cares about @p, since the register classes are already in the
- # table. See the arg-layout note in `_emit`.
+ # The marker is a comma-joined flag set ("pred", destination policy,
+ # and/or "p<i>"); codegen only cares about @p and the destination
+ # binding policy, since the register classes are already in the table.
+ # See the arg-layout note in `_emit`.
flags = parse_str(args[-1]).split(",")
predicated = "pred" in flags
+ preserve_dst = "keep" in flags
tokens = [parse_str(a) for a in args[len(args) - n_slots - 1 : -1]]
rest = args[: len(args) - n_slots - 1] # operands, plus pred when
present
mod_map = mods(entry, tokens)
@@ -167,7 +169,15 @@ def _make_codegen(entry: InstructionEntry):
# the helper (which has no parameter for them).
imm_at = {i for slot, i, _ in layout if slot.kind == "imm"}
imms = tuple(str(int(rest[at[i]])) for i in sorted(imm_at))
- _, helper, source = render_variant(entry, tokens, predicated, dtypes,
imms, sinks)
+ _, helper, source = render_variant(
+ entry,
+ tokens,
+ predicated,
+ dtypes,
+ imms,
+ sinks,
+ preserve_dst=preserve_dst,
+ )
# Every helper is void; a destination is an ordinary argument, printed
# by the C codegen as the lvalue it binds the reference parameter to.
# A predicate rides after the operands, so everything past n_operands
@@ -458,7 +468,7 @@ def _coerce_pred(entry, pred):
raise ValueError(f"{entry.name}: pred must be a bool/uint32/int32
expression")
-def _emit(entry, filled, operands, pred=None):
+def _emit(entry, filled, operands, pred=None, preserve_dst=False):
# Modifiers resolve before the operands are looked at: the attribute chain
# is parsed before the call happens, and a register group's length may be a
# function of the modifiers, so the expected arity needs the modifier map.
@@ -477,14 +487,12 @@ def _emit(entry, filled, operands, pred=None):
if len(operands) != n_args:
names = ", ".join(f"{slot.name}[{n}]" if n > 1 else slot.name for
slot, _, n in layout)
raise ValueError(f"{entry.name} expects {n_args} operand(s) ({names}),
got {len(operands)}")
+ if preserve_dst and not entry.has_dst:
+ raise ValueError(f"{entry.name}: preserve_dst=True requires a written
destination")
if pred is not None:
- if entry.has_dst:
- raise ValueError(
- f"{entry.name}: @p predication is only supported on
instructions without a "
- f"destination (a false predicate leaves the destination
unwritten, and the "
- f'"=" output constraint would discard its prior value)'
- )
pred = _coerce_pred(entry, pred)
+ elif preserve_dst:
+ raise ValueError(f"{entry.name}: preserve_dst=True requires pred=...")
# The sink symbol `_`: a lane the caller discards. It is checked here
# rather than in `_coerce_operand` because it is not a value at all -- the
# instruction names the symbol, so the lane leaves no Call argument, and
@@ -528,6 +536,8 @@ def _emit(entry, filled, operands, pred=None):
# indistinguishable from the integer that shares its carrier. The marker
# makes the round trip exact; a call with neither prints "" as before.
flags = ["pred"] if pred is not None else []
+ if preserve_dst:
+ flags.append("keep")
flags += [
f"p{i}"
for slot, i, lanes in layout
@@ -587,7 +597,7 @@ class _InstrChain:
raise AttributeError(name)
return _InstrChain(_narrow(self._cands, unescape_token(name)))
- def __call__(self, *args, pred=None):
+ def __call__(self, *args, pred=None, preserve_dst=False):
# Also accepts the printed round-trip form: trailing modifier-token
# strings in slot order ("" = omitted slot) followed by the pred
# marker, and, when the marker says "pred", the predicate as the last
@@ -603,6 +613,8 @@ class _InstrChain:
flags = marker.split(",") if marker else []
if "pred" in flags and pred is None and args:
args, pred = args[:-1], args[-1]
+ if "keep" in flags:
+ preserve_dst = True
# Put back what the printed text cannot carry: the sunk lanes
# (which left no argument at all, so they are re-inserted first, in
# ascending order, to restore the operand positions) and then the
@@ -631,7 +643,18 @@ class _InstrChain:
# what lets optional trailing operands dispatch by arity.
for entry, filled in cands:
try:
- hits.append((entry, _emit(entry, filled, operands, pred=pred)))
+ hits.append(
+ (
+ entry,
+ _emit(
+ entry,
+ filled,
+ operands,
+ pred=pred,
+ preserve_dst=preserve_dst,
+ ),
+ )
+ )
except ValueError as err:
# Keep the exception; a lone candidate re-raises it untouched,
# and only the aggregate view needs entry names in front.
diff --git a/python/tvm/backend/cuda/ptx/gen_stubs.py
b/python/tvm/backend/cuda/ptx/gen_stubs.py
index 9d31cb723b..2d8c22222c 100644
--- a/python/tvm/backend/cuda/ptx/gen_stubs.py
+++ b/python/tvm/backend/cuda/ptx/gen_stubs.py
@@ -78,7 +78,8 @@ def _chain_class(family: str, entries:
list[InstructionEntry]) -> str:
f"{s.name}∈{{{','.join(s.choices)}}}{' (opt)' if s.optional else ''}"
for s in entry.slots
)
if entry.check is not None and entry.check.__doc__:
- doc = f"{doc} — {entry.check.__doc__.strip()}" if doc else
entry.check.__doc__.strip()
+ check_doc = " ".join(entry.check.__doc__.split())
+ doc = f"{doc} — {check_doc}" if doc else check_doc
if len(entries) > 1:
shapes = dict.fromkeys(
"(" + ", ".join(_operand_params(e)).replace(": Any", "") + ")" for
e in entries
@@ -98,8 +99,9 @@ def _chain_class(family: str, entries:
list[InstructionEntry]) -> str:
# spells its operands as the catch-all; a second one would not even
# parse ("Only one '*' parameter allowed").
params.append("*args: Any")
- if not any(e.has_dst for e in entries):
- params.append("pred: Any = None")
+ params.append("pred: Any = None")
+ if any(e.has_dst for e in entries):
+ params.append("preserve_dst: bool = False")
signature = f"def __call__({', '.join(params)}) -> None"
# Emit the shape ruff format would produce, so the generated text needs no
# formatter to be canonical: a docstring that fits on one line closes on
@@ -121,7 +123,7 @@ def _chain_class(family: str, entries:
list[InstructionEntry]) -> str:
# `T.ptx["tcgen05.ld.sync.aligned.16x64b.x4.b32"](...)`.
continue
lines.append(f" {attr}: {cls}")
- if len(signature) > 92: # keep the generated stub within the repo line
limit
+ if len(f" {signature}: ...") > 100:
joined = ",\n ".join(params)
ret = signature[signature.rindex(")") + 1 :]
signature = f"def __call__(\n {joined},\n ){ret}"
diff --git a/python/tvm/backend/cuda/ptx/render.py
b/python/tvm/backend/cuda/ptx/render.py
index 6a57fd0fbc..96bdab0a91 100644
--- a/python/tvm/backend/cuda/ptx/render.py
+++ b/python/tvm/backend/cuda/ptx/render.py
@@ -196,7 +196,13 @@ def _helper_name(
def render_variant(
- entry: InstructionEntry, tokens, predicated=False, dtypes=None, imms=None,
sinks=frozenset()
+ entry: InstructionEntry,
+ tokens,
+ predicated=False,
+ dtypes=None,
+ imms=None,
+ sinks=frozenset(),
+ preserve_dst=False,
):
"""Render one variant: ``(opcode, helper_name, helper_source)``.
@@ -218,8 +224,11 @@ def render_variant(
``predicated`` is a framework-level axis (never in the table): the helper
gains a trailing ``uint32_t __pred`` operand, and the instruction is
- guarded with ``.reg .pred p; setp.ne.b32 p, %N, 0; @p ...``. Only valid
- for instructions without a destination — see ``InstructionEntry.has_dst``.
+ guarded with ``.reg .pred p; setp.ne.b32 p, %N, 0; @p ...``. A written
+ destination uses the normal write-only binding by default, which states
+ that the caller will not consume the inactive value before selecting or
+ guarding it. ``preserve_dst`` instead binds read-write and retains the old
+ value on the inactive path.
"""
mod_map = mods(entry, tokens)
written = [tok for tok in tokens if tok]
@@ -242,9 +251,14 @@ def render_variant(
# non-canonical (atom's d and b do exactly that).
imm_of = dict(zip(imm_slots(entry), imms or (), strict=True))
helper = _helper_name(entry, written, imms, dtypes, canonical, mod_map,
sinks)
+ assert not preserve_dst or entry.has_dst, "preserve_dst requires a written
destination"
if predicated:
- assert not entry.has_dst, "@p is only supported on instructions
without a destination"
- helper += "_pred"
+ if entry.has_dst:
+ helper += "_pred_keep" if preserve_dst else "_pred_undef"
+ else:
+ helper += "_pred"
+ else:
+ assert not preserve_dst, "preserve_dst requires predication"
params, inputs, outputs, rendered = [], [], [], []
pre, post = [], [] # C-side carrier declarations / bit puns
@@ -300,7 +314,7 @@ def render_variant(
elif slot.kind == "ptr":
params.append(f"const void* {lname}")
inputs.append(f'"l"({lname})')
- elif slot.rw == "rw":
+ elif slot.rw == "rw" or (preserve_dst and slot.rw == "w"):
# A register the instruction both reads and writes -- an
# in-place accumulator. "+" tells the compiler the prior value
# is live, which "=" would declare dead; it is also what makes
@@ -311,7 +325,7 @@ def render_variant(
# which has no coherent read-modify-write story; the dtypes
# that need one are refused rather than half-supported.
raise ValueError(
- f"{entry.name}: accumulator '{slot.name}' cannot take
dtype "
+ f"{entry.name}: read-write destination '{slot.name}'
cannot take dtype "
f"{dtype_of[slot]} (it binds through a carrier)"
)
params.append(f"{cb.c_type}& {lname}")
@@ -347,7 +361,7 @@ def render_variant(
reg = template.format(slot=slot.name,
n=bridge_counts[template])
bridge_counts[template] += 1
bridge_decls.append(f".reg {bridge.reg_class} {reg};")
- if slot.rw in ("r", "rw"):
+ if slot.rw in ("r", "rw") or (preserve_dst and slot.rw == "w"):
asm_pre.append(bridge.into.format(reg=reg, idx=idx))
if slot.rw in ("w", "rw"):
asm_post.append(bridge.out_of.format(reg=reg, idx=idx))
diff --git a/python/tvm/backend/cuda/ptx/table.py
b/python/tvm/backend/cuda/ptx/table.py
index 71c299a919..e55c4b095b 100644
--- a/python/tvm/backend/cuda/ptx/table.py
+++ b/python/tvm/backend/cuda/ptx/table.py
@@ -420,13 +420,12 @@ class InstructionEntry:
def has_dst(self) -> bool:
"""Whether the instruction writes a destination operand.
- Gates ``@p``: a false predicate leaves destinations unwritten, and the
- ``"="`` output constraint tells nvcc the prior value is dead, so a
- predicated destination silently loses it. An accumulator (``rw="rw"``)
- binds "+" instead, which keeps the old value live -- so it does not
- count here and @p remains available on it. A ``.pred`` result is a
- ``rw="w"`` register like any other, written through "=" the same way,
- so it counts without needing a case of its own.
+ A false predicate leaves destinations unwritten. The default ``"="``
+ output constraint means the inactive value is undefined to the caller;
+ ``preserve_dst=True`` explicitly requests a read-write binding instead.
+ An accumulator (``rw="rw"``) already binds "+", so it does not count
+ here. A ``.pred`` result is a ``rw="w"`` register like any other and
+ counts without needing a case of its own.
"""
return any(s.kind == "reg" and s.rw == "w" for s in self.operands)
@@ -2482,6 +2481,19 @@ def _check_tcgen05_mma_block_scale(m):
return None
+def _check_tcgen05_mma_block_scale_block(m):
+ """Valid block sizes per kind: mxf8f6f4/mxf4 use block32, while
+ mxf4nvf4 supports block16 and block32."""
+ valid = {
+ "kind::mxf8f6f4": ("block32",),
+ "kind::mxf4": ("block32",),
+ "kind::mxf4nvf4": ("block16", "block32"),
+ }[m["kind"]]
+ if m["block_size"] not in valid:
+ return f"{m['kind']} supports {'/'.join(valid)}"
+ return None
+
+
# `{, byteMask}` exists exactly when `.cp_mask` is written.
_cp_mask_lanes = _present_lanes("cp_mask")
@@ -7647,8 +7659,8 @@ _ENTRIES = [
# only differ by those qualifiers: no call sites.
# - .ws without the zero-column-mask-desc operand: every caller passes
# the mask (as literal zero).
- # - block_scale's .block16/.block32 vector sizes and its
- # scale_vec-omitted spelling: the library always writes .scale_vec::NX.
+ # - block_scale's scale_vec-omitted spelling without a .block16/.block32
+ # size: no call site uses that form.
*[
InstructionEntry(
name=f"tcgen05_mma_{form}",
@@ -7707,6 +7719,35 @@ _ENTRIES = [
)
for form in ("ss", "ts")
],
+ *[
+ InstructionEntry( # block-scaled with an explicit scale block size
+ name=f"tcgen05_mma_block_scale_block_{form}",
+ mnemonic="tcgen05",
+ slots=(
+ ModifierSlot("action", ("mma",)),
+ ModifierSlot("cta_group", ("cta_group::1", "cta_group::2")),
+ ModifierSlot("kind", ("kind::mxf8f6f4", "kind::mxf4",
"kind::mxf4nvf4")),
+ ModifierSlot("block_scale", ("block_scale",)),
+ ModifierSlot("block_size", ("block16", "block32")),
+ ),
+ cert_arch="sm_100a",
+ check=_check_tcgen05_mma_block_scale_block,
+ operands=(
+ OperandSlot("d_tmem", kind="addr", space="tmem"),
+ *(
+ (OperandSlot("a_desc", dtype="u64"),)
+ if form == "ss"
+ else (OperandSlot("a_tmem", kind="addr", space="tmem"),)
+ ),
+ OperandSlot("b_desc", dtype="u64"),
+ OperandSlot("idesc", dtype="u32"),
+ OperandSlot("sfa_tmem", kind="addr", space="tmem"),
+ OperandSlot("sfb_tmem", kind="addr", space="tmem"),
+ OperandSlot("enable_input_d", dtype="pred"),
+ ),
+ )
+ for form in ("ss", "ts")
+ ],
*[
InstructionEntry( # weight-stationary: no mask vector, a zero-column
desc
name=f"tcgen05_mma_ws_{form}",
diff --git a/python/tvm/backend/cuda/tile_primitive/common.py
b/python/tvm/backend/cuda/tile_primitive/common.py
index 157753d2fe..d229809631 100644
--- a/python/tvm/backend/cuda/tile_primitive/common.py
+++ b/python/tvm/backend/cuda/tile_primitive/common.py
@@ -55,24 +55,22 @@ def get_indices(nth, start, extent):
return [r + s for r, s in zip(reversed(relative), start)]
+def smem_desc_replace_lo(desc_val, desc_lo):
+ """Replace the lower address lane of a 64-bit SMEM descriptor."""
+ desc_halves = T.reinterpret("uint32x2", desc_val)
+ desc_hi = T.Shuffle([desc_halves], [1])
+ return T.reinterpret("uint64", T.Shuffle([T.cast(desc_lo, "uint32"),
desc_hi], [0, 1]))
+
+
def smem_desc_add_16B_offset(desc_val, offset):
"""Add a 16B-aligned byte offset to the lower 32 bits of a SMEM descriptor.
- Uses the SmemDescriptor union defined in the CUDA header (header.py).
- All callers must share a single implementation to avoid codegen conflicts.
+ The address lane wraps as uint32 without carrying into the descriptor's
+ upper control bits.
"""
- func_name = "tvm_builtin_smem_desc_add_16B_offset"
- source_code = f"""
-__forceinline__ __device__ uint64_t {func_name}(uint64_t desc_base, int32_t
offset) {{
- SmemDescriptor desc;
- desc.desc_ = desc_base;
- desc.lo += static_cast<uint32_t>(offset);
- return desc.desc_;
-}}
-"""
- return T.cuda.func_call(
- func_name, desc_val, offset, source_code=source_code,
return_type="uint64"
- )
+ desc_halves = T.reinterpret("uint32x2", desc_val)
+ desc_lo = T.Shuffle([desc_halves], [0]) + T.cast(offset, "uint32")
+ return smem_desc_replace_lo(desc_val, desc_lo)
class CopyInstType(Enum):
diff --git a/python/tvm/backend/cuda/tile_primitive/gemm_async/tcgen05.py
b/python/tvm/backend/cuda/tile_primitive/gemm_async/tcgen05.py
index 619385c732..8ea5f29166 100644
--- a/python/tvm/backend/cuda/tile_primitive/gemm_async/tcgen05.py
+++ b/python/tvm/backend/cuda/tile_primitive/gemm_async/tcgen05.py
@@ -1074,16 +1074,26 @@ def gemm_async_tcgen05_impl(op_call: TilePrimitiveCall,
sctx: DispatchContext) -
_SWIZZLE_TO_LAYOUT = {0: 0, 1: 6, 2: 4, 3: 2, 4: 1}
_krp = Evaluate(tirx_op.tvm_kernel_replace_point())
- def _make_lo_uniform(desc):
- func_name = "smem_desc_make_lo_uniform_"
- source_code = f"""
- __forceinline__ __device__ void {func_name}(uint64_t* desc) {{
- SmemDescriptor* d = reinterpret_cast<SmemDescriptor*>(desc);
- d->lo = __shfl_sync(0xffffffff, d->lo, 0);
- }}
- """
- return T.cuda.func_call(
- func_name, T.address_of(desc), source_code=source_code,
return_type="void"
+ def _make_lo_uniform(desc_buf):
+ desc_lo = tvm.tirx.decl_buffer((1,), "uint32",
name=f"{desc_buf.name}_lo", scope="local")
+ desc_hi = tvm.tirx.decl_buffer((1,), "uint32",
name=f"{desc_buf.name}_hi", scope="local")
+ unpack = T.ptx.mov.b64(desc_lo[0], desc_hi[0], desc_buf[0])
+ shuffle = T.ptx.shfl_sync.idx.b32(
+ desc_lo[0],
+ desc_lo[0],
+ T.uint32(0),
+ T.uint32(0x1F),
+ T.uint32(0xFFFFFFFF),
+ )
+ pack = T.ptx.mov.b64(desc_buf[0], desc_lo[0], desc_hi[0])
+ return SeqStmt(
+ [
+ AllocBuffer(desc_lo),
+ AllocBuffer(desc_hi),
+ Evaluate(unpack),
+ Evaluate(shuffle),
+ Evaluate(pack),
+ ]
)
def _make_desc(smem_buf, ldo, sdo, swizzle_val, name):
@@ -1103,7 +1113,7 @@ def gemm_async_tcgen05_impl(op_call: TilePrimitiveCall,
sctx: DispatchContext) -
)
wrap_stmts = [AllocBuffer(desc_buf), Evaluate(encode_call)]
if warp_scope:
- wrap_stmts.append(Evaluate(_make_lo_uniform(desc_buf[0])))
+ wrap_stmts.append(_make_lo_uniform(desc_buf))
wrap_stmts.append(_krp)
wrap = SeqStmt(wrap_stmts)
sctx.add_post_buffer_def_stmt(smem_buf, wrap)
diff --git a/python/tvm/script/tirx.pyi b/python/tvm/script/tirx.pyi
index 5bf4865f2b..ac9e092c12 100644
--- a/python/tvm/script/tirx.pyi
+++ b/python/tvm/script/tirx.pyi
@@ -37,13 +37,19 @@ class _Chain_abs:
s16: _Chain_abs
s32: _Chain_abs
s64: _Chain_abs
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_activemask:
"""`activemask` — type∈{b32}"""
b32: _Chain_activemask
- def __call__(self, d: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_add:
"""`add` — 3 entries sharing this mnemonic; PTX puts their difference in
the operand list,
@@ -71,7 +77,7 @@ class _Chain_add:
u16x2: _Chain_add
u32: _Chain_add
u64: _Chain_add
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_and:
"""`and` — type∈{pred,b16,b32,b64}"""
@@ -80,7 +86,15 @@ class _Chain_and:
b32: _Chain_and
b64: _Chain_and
pred: _Chain_and
- def __call__(self, d: Any, a: Any, b: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ b: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_applypriority:
"""`applypriority` — space∈{global} (opt); level∈{L2::evict_normal}"""
@@ -135,7 +149,7 @@ class _Chain_atom:
v4: _Chain_atom
v8: _Chain_atom
xor: _Chain_atom
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_bar:
"""`bar` — 8 entries sharing this mnemonic; PTX puts their difference in
the operand list,
@@ -152,7 +166,7 @@ class _Chain_bar:
sync: _Chain_bar
u32: _Chain_bar
warp: _Chain_bar
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_barrier:
"""`barrier` — 9 entries sharing this mnemonic; PTX puts their difference
in the operand
@@ -174,7 +188,7 @@ class _Chain_barrier:
sync: _Chain_barrier
u32: _Chain_barrier
wait: _Chain_barrier
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_bfe:
"""`bfe` — type∈{u32,u64,s32,s64}"""
@@ -183,14 +197,33 @@ class _Chain_bfe:
s64: _Chain_bfe
u32: _Chain_bfe
u64: _Chain_bfe
- def __call__(self, d: Any, a: Any, b: Any, c: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ b: Any,
+ c: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_bfi:
"""`bfi` — type∈{b32,b64}"""
b32: _Chain_bfi
b64: _Chain_bfi
- def __call__(self, f: Any, a: Any, b: Any, c: Any, d: Any, *args: Any) ->
None: ...
+ def __call__(
+ self,
+ f: Any,
+ a: Any,
+ b: Any,
+ c: Any,
+ d: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_bfind:
"""`bfind` — shiftamt∈{shiftamt} (opt); type∈{u32,u64,s32,s64}"""
@@ -200,7 +233,14 @@ class _Chain_bfind:
shiftamt: _Chain_bfind
u32: _Chain_bfind
u64: _Chain_bfind
- def __call__(self, d: Any, a: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_bmsk:
"""`bmsk` — mode∈{clamp,wrap}; type∈{b32}"""
@@ -208,14 +248,29 @@ class _Chain_bmsk:
b32: _Chain_bmsk
clamp: _Chain_bmsk
wrap: _Chain_bmsk
- def __call__(self, d: Any, a: Any, b: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ b: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_brev:
"""`brev` — type∈{b32,b64}"""
b32: _Chain_brev
b64: _Chain_brev
- def __call__(self, d: Any, a: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_clusterlaunchcontrol:
"""`clusterlaunchcontrol` — 4 entries sharing this mnemonic; PTX puts
their difference in
@@ -238,14 +293,21 @@ class _Chain_clusterlaunchcontrol:
shared__cta: _Chain_clusterlaunchcontrol
try_cancel: _Chain_clusterlaunchcontrol
v4: _Chain_clusterlaunchcontrol
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_clz:
"""`clz` — type∈{b32,b64}"""
b32: _Chain_clz
b64: _Chain_clz
- def __call__(self, d: Any, a: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_cnot:
"""`cnot` — type∈{b16,b32,b64}"""
@@ -253,14 +315,29 @@ class _Chain_cnot:
b16: _Chain_cnot
b32: _Chain_cnot
b64: _Chain_cnot
- def __call__(self, d: Any, a: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_copysign:
"""`copysign` — type∈{f32,f64}"""
f32: _Chain_copysign
f64: _Chain_copysign
- def __call__(self, d: Any, a: Any, b: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ b: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_cos:
"""`cos` — mode∈{approx}; ftz∈{ftz} (opt); type∈{f32}"""
@@ -268,7 +345,14 @@ class _Chain_cos:
approx: _Chain_cos
f32: _Chain_cos
ftz: _Chain_cos
- def __call__(self, d: Any, value: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ value: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_cp:
"""`cp` — 22 entries sharing this mnemonic; PTX puts their difference in
the operand list,
@@ -336,7 +420,7 @@ class _Chain_createpolicy:
fractional: _Chain_createpolicy
global_: _Chain_createpolicy
range: _Chain_createpolicy
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_cvt:
"""`cvt` — 27 entries sharing this mnemonic; PTX puts their difference in
the operand list,
@@ -386,7 +470,7 @@ class _Chain_cvt:
u64: _Chain_cvt
u8: _Chain_cvt
ue8m0x2: _Chain_cvt
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_cvt_pack:
"""`cvt_pack` — 2 entries sharing this mnemonic; PTX puts their difference
in the operand
@@ -404,7 +488,7 @@ class _Chain_cvt_pack:
u2: _Chain_cvt_pack
u4: _Chain_cvt_pack
u8: _Chain_cvt_pack
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_cvta:
"""`cvta` — 2 entries sharing this mnemonic; PTX puts their difference in
the operand list,
@@ -421,7 +505,7 @@ class _Chain_cvta:
shared__cta: _Chain_cvta
to: _Chain_cvta
u64: _Chain_cvta
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_discard:
"""`discard` — space∈{global} (opt); level∈{L2}"""
@@ -450,7 +534,7 @@ class _Chain_div:
u16: _Chain_div
u32: _Chain_div
u64: _Chain_div
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_dp2a:
"""`dp2a` — mode∈{lo,hi}; atype∈{u32,s32}; btype∈{u32,s32}"""
@@ -459,19 +543,45 @@ class _Chain_dp2a:
lo: _Chain_dp2a
s32: _Chain_dp2a
u32: _Chain_dp2a
- def __call__(self, d: Any, a: Any, b: Any, c: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ b: Any,
+ c: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_dp4a:
"""`dp4a` — atype∈{u32,s32}; btype∈{u32,s32}"""
s32: _Chain_dp4a
u32: _Chain_dp4a
- def __call__(self, d: Any, a: Any, b: Any, c: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ b: Any,
+ c: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_elect_sync:
"""`elect_sync` — (no modifiers)"""
- def __call__(self, d: Any, p: Any, membermask: Any, *args: Any) -> None:
...
+ def __call__(
+ self,
+ d: Any,
+ p: Any,
+ membermask: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_ex2:
"""`ex2` — 2 entries sharing this mnemonic; PTX puts their difference in
the operand list,
@@ -485,7 +595,7 @@ class _Chain_ex2:
f16x2: _Chain_ex2
f32: _Chain_ex2
ftz: _Chain_ex2
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_fence:
"""`fence` — 5 entries sharing this mnemonic; PTX puts their difference in
the operand
@@ -530,13 +640,22 @@ class _Chain_fma:
rp: _Chain_fma
rz: _Chain_fma
sat: _Chain_fma
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_fns:
"""`fns` — type∈{b32}"""
b32: _Chain_fns
- def __call__(self, d: Any, mask: Any, base: Any, offset: Any, *args: Any)
-> None: ...
+ def __call__(
+ self,
+ d: Any,
+ mask: Any,
+ base: Any,
+ offset: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_getctarank:
"""`getctarank` — 2 entries sharing this mnemonic; PTX puts their
difference in the operand
@@ -546,7 +665,7 @@ class _Chain_getctarank:
shared__cluster: _Chain_getctarank
u32: _Chain_getctarank
u64: _Chain_getctarank
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_griddepcontrol:
"""`griddepcontrol` — action∈{launch_dependents,wait}"""
@@ -568,7 +687,14 @@ class _Chain_isspacep:
shared: _Chain_isspacep
shared__cluster: _Chain_isspacep
shared__cta: _Chain_isspacep
- def __call__(self, p: Any, a: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ p: Any,
+ a: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_ld:
"""`ld` — 3 entries sharing this mnemonic; PTX puts their difference in
the operand list,
@@ -625,7 +751,7 @@ class _Chain_ld:
v8: _Chain_ld
volatile: _Chain_ld
weak: _Chain_ld
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_ldmatrix:
"""`ldmatrix` — 3 entries sharing this mnemonic; PTX puts their difference
in the operand
@@ -648,7 +774,7 @@ class _Chain_ldmatrix:
x1: _Chain_ldmatrix
x2: _Chain_ldmatrix
x4: _Chain_ldmatrix
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_ldu:
"""`ldu` — 2 entries sharing this mnemonic; PTX puts their difference in
the operand list,
@@ -673,7 +799,7 @@ class _Chain_ldu:
u8: _Chain_ldu
v2: _Chain_ldu
v4: _Chain_ldu
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_lg2:
"""`lg2` — mode∈{approx}; ftz∈{ftz} (opt); type∈{f32}"""
@@ -681,7 +807,14 @@ class _Chain_lg2:
approx: _Chain_lg2
f32: _Chain_lg2
ftz: _Chain_lg2
- def __call__(self, d: Any, value: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ value: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_lop3:
"""`lop3` — 2 entries sharing this mnemonic; PTX puts their difference in
the operand list,
@@ -691,7 +824,7 @@ class _Chain_lop3:
and_: _Chain_lop3
b32: _Chain_lop3
or_: _Chain_lop3
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_mad:
"""`mad` — 3 entries sharing this mnemonic; PTX puts their difference in
the operand list,
@@ -715,14 +848,13 @@ class _Chain_mad:
u32: _Chain_mad
u64: _Chain_mad
wide: _Chain_mad
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_mad24:
"""`mad24` — mode∈{hi,lo}; sat∈{sat} (opt); type∈{u32,s32} — `.sat` on the
multiply-add
- lines: `.hi` mode, `.s32` type, nothing else. Both lines spell it as
a syntax line
- of its own -- `mad.hi.sat.s32 d, a, b, c;` (ISA 9.7.1.4) and
`mad24.hi.sat.s32 d, a,
- b, c;` (9.7.1.7) -- with the Notes repeating "Applies only to .s32
type in .hi
- mode".
+ lines: `.hi` mode, `.s32` type, nothing else. Both lines spell it as a
syntax line of
+ its own -- `mad.hi.sat.s32 d, a, b, c;` (ISA 9.7.1.4) and
`mad24.hi.sat.s32 d, a, b, c;`
+ (9.7.1.7) -- with the Notes repeating "Applies only to .s32 type in .hi
mode".
"""
hi: _Chain_mad24
@@ -730,7 +862,16 @@ class _Chain_mad24:
s32: _Chain_mad24
sat: _Chain_mad24
u32: _Chain_mad24
- def __call__(self, d: Any, a: Any, b: Any, c: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ b: Any,
+ c: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_mapa:
"""`mapa` — 2 entries sharing this mnemonic; PTX puts their difference in
the operand list,
@@ -740,7 +881,7 @@ class _Chain_mapa:
shared__cluster: _Chain_mapa
u32: _Chain_mapa
u64: _Chain_mapa
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_match:
"""`match` — 3 entries sharing this mnemonic; PTX puts their difference in
the operand
@@ -752,7 +893,7 @@ class _Chain_match:
b32: _Chain_match
b64: _Chain_match
sync: _Chain_match
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_max:
"""`max` — 2 entries sharing this mnemonic; PTX puts their difference in
the operand list,
@@ -778,7 +919,7 @@ class _Chain_max:
u32: _Chain_max
u64: _Chain_max
xorsign: _Chain_max
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_mbarrier:
"""`mbarrier` — 25 entries sharing this mnemonic; PTX puts their
difference in the operand
@@ -808,7 +949,7 @@ class _Chain_mbarrier:
shared__cta: _Chain_mbarrier
test_wait: _Chain_mbarrier
try_wait: _Chain_mbarrier
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_min:
"""`min` — 2 entries sharing this mnemonic; PTX puts their difference in
the operand list,
@@ -834,7 +975,7 @@ class _Chain_min:
u32: _Chain_min
u64: _Chain_min
xorsign: _Chain_min
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_mma:
"""`mma` — 12 entries sharing this mnemonic; PTX puts their difference in
the operand list,
@@ -879,7 +1020,7 @@ class _Chain_mma:
u4: _Chain_mma
u8: _Chain_mma
xor: _Chain_mma
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_mov:
"""`mov` — 11 entries sharing this mnemonic; PTX puts their difference in
the operand list,
@@ -900,7 +1041,7 @@ class _Chain_mov:
u16: _Chain_mov
u32: _Chain_mov
u64: _Chain_mov
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_mul:
"""`mul` — 4 entries sharing this mnemonic; PTX puts their difference in
the operand list,
@@ -929,7 +1070,7 @@ class _Chain_mul:
u32: _Chain_mul
u64: _Chain_mul
wide: _Chain_mul
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_mul24:
"""`mul24` — mode∈{hi,lo}; type∈{u32,s32}"""
@@ -938,7 +1079,15 @@ class _Chain_mul24:
lo: _Chain_mul24
s32: _Chain_mul24
u32: _Chain_mul24
- def __call__(self, d: Any, a: Any, b: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ b: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_multimem_ld_reduce:
"""`multimem_ld_reduce` — 3 entries sharing this mnemonic; PTX puts their
difference in the
@@ -975,7 +1124,7 @@ class _Chain_multimem_ld_reduce:
v8: _Chain_multimem_ld_reduce
weak: _Chain_multimem_ld_reduce
xor: _Chain_multimem_ld_reduce
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_multimem_red:
"""`multimem_red` — 3 entries sharing this mnemonic; PTX puts their
difference in the
@@ -1057,7 +1206,7 @@ class _Chain_neg:
s16: _Chain_neg
s32: _Chain_neg
s64: _Chain_neg
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_not:
"""`not` — type∈{pred,b16,b32,b64}"""
@@ -1066,7 +1215,14 @@ class _Chain_not:
b32: _Chain_not
b64: _Chain_not
pred: _Chain_not
- def __call__(self, d: Any, a: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_or:
"""`or` — type∈{pred,b16,b32,b64}"""
@@ -1075,23 +1231,38 @@ class _Chain_or:
b32: _Chain_or
b64: _Chain_or
pred: _Chain_or
- def __call__(self, d: Any, a: Any, b: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ b: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_popc:
"""`popc` — type∈{b32,b64}"""
b32: _Chain_popc
b64: _Chain_popc
- def __call__(self, d: Any, a: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_prefetch:
"""`prefetch` — space∈{global,local,const,param} (opt); level∈{L1,L2}
(opt);
evict∈{L2::evict_last,L2::evict_normal} (opt); tensormap∈{tensormap} (opt)
— Each
prefetch syntax line names exactly one target (PTX ISA 9.7.9.16).
- `.level::eviction_priority` stays bound to `.global` on purpose: its
syntax line is
- `prefetch.global.level::eviction_priority`, with `.global` written in
rather than
- the `{.ss}` that the `ld` lines carry. Generic addressing is not
offered there, so
- neither is it here.
+ `.level::eviction_priority` stays bound to `.global` on purpose: its
syntax line is
+ `prefetch.global.level::eviction_priority`, with `.global` written in
rather than the
+ `{.ss}` that the `ld` lines carry. Generic addressing is not offered
there, so neither
+ is it here.
"""
L1: _Chain_prefetch
@@ -1121,18 +1292,26 @@ class _Chain_prmt:
f4e: _Chain_prmt
rc16: _Chain_prmt
rc8: _Chain_prmt
- def __call__(self, d: Any, a: Any, b: Any, c: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ b: Any,
+ c: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_rcp:
"""`rcp` — mode∈{approx,rn,rz,rm,rp}; ftz∈{ftz} (opt); type∈{f32,f64} —
rcp's four syntax
- lines, across two ISA subsections. rcp.approx{.ftz}.f32 d, a;
- rcp.rnd{.ftz}.f32 d, a; (9.7.3.13) rcp.rnd.f64 d, a;
- rcp.approx.ftz.f64 d, a; (9.7.3.14)
The ISA gives
- the last one a subsection of its own because it is a different
*computation* -- a
- gross approximation off the top 20 mantissa bits, with its own
corner-case table --
- but its syntax is one more cell of this grid, and the shape (`d, a`)
is unchanged.
- So it lives here, with the mandatory `.ftz` of its syntax line
enforced below rather
- than by a second entry that would render identically.
+ lines, across two ISA subsections. rcp.approx{.ftz}.f32 d, a;
rcp.rnd{.ftz}.f32 d, a;
+ (9.7.3.13) rcp.rnd.f64 d, a; rcp.approx.ftz.f64 d, a; (9.7.3.14) The ISA
gives the last
+ one a subsection of its own because it is a different *computation* -- a
gross
+ approximation off the top 20 mantissa bits, with its own corner-case table
-- but its
+ syntax is one more cell of this grid, and the shape (`d, a`) is unchanged.
So it lives
+ here, with the mandatory `.ftz` of its syntax line enforced below rather
than by a
+ second entry that would render identically.
"""
approx: _Chain_rcp
@@ -1143,7 +1322,14 @@ class _Chain_rcp:
rn: _Chain_rcp
rp: _Chain_rcp
rz: _Chain_rcp
- def __call__(self, d: Any, value: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ value: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_red:
"""`red` — 2 entries sharing this mnemonic; PTX puts their difference in
the operand list,
@@ -1230,7 +1416,7 @@ class _Chain_redux_sync:
s32: _Chain_redux_sync
u32: _Chain_redux_sync
xor: _Chain_redux_sync
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_rem:
"""`rem` — type∈{u16,u32,u64,s16,s32,s64}"""
@@ -1241,7 +1427,15 @@ class _Chain_rem:
u16: _Chain_rem
u32: _Chain_rem
u64: _Chain_rem
- def __call__(self, d: Any, a: Any, b: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ b: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_rsqrt:
"""`rsqrt` — mode∈{approx}; ftz∈{ftz} (opt); type∈{f32,f64}"""
@@ -1250,7 +1444,14 @@ class _Chain_rsqrt:
f32: _Chain_rsqrt
f64: _Chain_rsqrt
ftz: _Chain_rsqrt
- def __call__(self, d: Any, value: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ value: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_sad:
"""`sad` — type∈{u16,u32,u64,s16,s32,s64}"""
@@ -1261,7 +1462,16 @@ class _Chain_sad:
u16: _Chain_sad
u32: _Chain_sad
u64: _Chain_sad
- def __call__(self, d: Any, a: Any, b: Any, c: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ b: Any,
+ c: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_selp:
"""`selp` — type∈{b16,b32,b64,u16,u32,u64,s16,s32,s64,f32,f64}"""
@@ -1277,7 +1487,16 @@ class _Chain_selp:
u16: _Chain_selp
u32: _Chain_selp
u64: _Chain_selp
- def __call__(self, d: Any, a: Any, b: Any, c: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ b: Any,
+ c: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_set:
"""`set` — 4 entries sharing this mnemonic; PTX puts their difference in
the operand list,
@@ -1321,7 +1540,7 @@ class _Chain_set:
u32: _Chain_set
u64: _Chain_set
xor: _Chain_set
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_setmaxnreg:
"""`setmaxnreg` — action∈{inc,dec}; sync∈{sync}; aligned∈{aligned};
type∈{u32}"""
@@ -1375,7 +1594,7 @@ class _Chain_setp:
u32: _Chain_setp
u64: _Chain_setp
xor: _Chain_setp
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_shf:
"""`shf` — dir∈{l,r}; mode∈{clamp,wrap}; type∈{b32}"""
@@ -1385,7 +1604,16 @@ class _Chain_shf:
l: _Chain_shf
r: _Chain_shf
wrap: _Chain_shf
- def __call__(self, d: Any, a: Any, b: Any, c: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ b: Any,
+ c: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_shfl_sync:
"""`shfl_sync` — 2 entries sharing this mnemonic; PTX puts their
difference in the operand
@@ -1398,7 +1626,7 @@ class _Chain_shfl_sync:
down: _Chain_shfl_sync
idx: _Chain_shfl_sync
up: _Chain_shfl_sync
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_shl:
"""`shl` — type∈{b16,b32,b64}"""
@@ -1406,7 +1634,15 @@ class _Chain_shl:
b16: _Chain_shl
b32: _Chain_shl
b64: _Chain_shl
- def __call__(self, d: Any, a: Any, b: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ b: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_shr:
"""`shr` — type∈{b16,b32,b64,u16,u32,u64,s16,s32,s64}"""
@@ -1420,7 +1656,15 @@ class _Chain_shr:
u16: _Chain_shr
u32: _Chain_shr
u64: _Chain_shr
- def __call__(self, d: Any, a: Any, b: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ b: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_sin:
"""`sin` — mode∈{approx}; ftz∈{ftz} (opt); type∈{f32}"""
@@ -1428,14 +1672,21 @@ class _Chain_sin:
approx: _Chain_sin
f32: _Chain_sin
ftz: _Chain_sin
- def __call__(self, d: Any, value: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ value: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_slct:
"""`slct` — ftz∈{ftz} (opt);
dtype∈{b16,b32,b64,u16,u32,u64,s16,s32,s64,f32,f64};
ctype∈{s32,f32} — slct's two lines (ISA 9.7.6.4), which differ only in the
selector
- type. slct.dtype.s32 d, a, b, c;
slct{.ftz}.dtype.f32 d, a, b,
- c; `.ftz` is spelled on the .f32 selector line alone -- there is
nothing to
- flush when the sign being tested is an integer's.
+ type. slct.dtype.s32 d, a, b, c; slct{.ftz}.dtype.f32 d, a, b, c; `.ftz`
is spelled on
+ the .f32 selector line alone -- there is nothing to flush when the sign
being tested is
+ an integer's.
"""
b16: _Chain_slct
@@ -1450,14 +1701,22 @@ class _Chain_slct:
u16: _Chain_slct
u32: _Chain_slct
u64: _Chain_slct
- def __call__(self, d: Any, a: Any, b: Any, c: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ b: Any,
+ c: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_sqrt:
"""`sqrt` — mode∈{approx,rn,rz,rm,rp}; ftz∈{ftz} (opt); type∈{f32,f64} —
sqrt's three lines
- (PTX ISA 9.7.3.15). sqrt.approx{.ftz}.f32 d, a;
sqrt.rnd{.ftz}.f32 d, a;
- sqrt.rnd.f64 d, a; Unlike rcp, there is no f64
approximation at any
- spelling -- 9.7.3.15 is the whole of sqrt, and it offers `.approx` on
the .f32 line
- only.
+ (PTX ISA 9.7.3.15). sqrt.approx{.ftz}.f32 d, a; sqrt.rnd{.ftz}.f32 d, a;
sqrt.rnd.f64 d,
+ a; Unlike rcp, there is no f64 approximation at any spelling -- 9.7.3.15
is the whole of
+ sqrt, and it offers `.approx` on the .f32 line only.
"""
approx: _Chain_sqrt
@@ -1468,7 +1727,14 @@ class _Chain_sqrt:
rn: _Chain_sqrt
rp: _Chain_sqrt
rz: _Chain_sqrt
- def __call__(self, d: Any, value: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ value: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_st:
"""`st` — 3 entries sharing this mnemonic; PTX puts their difference in
the operand list,
@@ -1601,7 +1867,7 @@ class _Chain_sub:
u16: _Chain_sub
u32: _Chain_sub
u64: _Chain_sub
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_szext:
"""`szext` — mode∈{clamp,wrap}; type∈{u32,s32}"""
@@ -1610,7 +1876,15 @@ class _Chain_szext:
s32: _Chain_szext
u32: _Chain_szext
wrap: _Chain_szext
- def __call__(self, d: Any, a: Any, b: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ b: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_tanh:
"""`tanh` — 2 entries sharing this mnemonic; PTX puts their difference in
the operand list,
@@ -1623,10 +1897,10 @@ class _Chain_tanh:
f16: _Chain_tanh
f16x2: _Chain_tanh
f32: _Chain_tanh
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_tcgen05:
- """`tcgen05` — 18 entries sharing this mnemonic; PTX puts their difference
in the operand
+ """`tcgen05` — 20 entries sharing this mnemonic; PTX puts their difference
in the operand
list, so the call selects one. Shapes: (dst, ncols); (taddr, ncols); ();
(*__operands);
(taddr, s_desc); (d_tmem, a_desc, b_desc, idesc, sfa_tmem, sfb_tmem,
enable_input_d);
(d_tmem, a_tmem, b_desc, idesc, sfa_tmem, sfb_tmem, enable_input_d);
(d_tmem, a_desc,
@@ -1641,6 +1915,8 @@ class _Chain_tcgen05:
b64: _Chain_tcgen05
b6x16_p32: _Chain_tcgen05
b8x16: _Chain_tcgen05
+ block16: _Chain_tcgen05
+ block32: _Chain_tcgen05
block_scale: _Chain_tcgen05
commit: _Chain_tcgen05
cp: _Chain_tcgen05
@@ -1684,7 +1960,7 @@ class _Chain_tcgen05:
x4: _Chain_tcgen05
x64: _Chain_tcgen05
x8: _Chain_tcgen05
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_tensormap_cp_fenceproxy:
"""`tensormap_cp_fenceproxy` — dst∈{global}; src∈{shared::cta};
proxy∈{tensormap::generic};
@@ -1737,7 +2013,14 @@ class _Chain_testp:
notanumber: _Chain_testp
number: _Chain_testp
subnormal: _Chain_testp
- def __call__(self, p: Any, a: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ p: Any,
+ a: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _Chain_vote_sync:
"""`vote_sync` — 2 entries sharing this mnemonic; PTX puts their
difference in the operand
@@ -1750,7 +2033,7 @@ class _Chain_vote_sync:
ballot: _Chain_vote_sync
pred: _Chain_vote_sync
uni: _Chain_vote_sync
- def __call__(self, *args: Any) -> None: ...
+ def __call__(self, *args: Any, pred: Any = None, preserve_dst: bool =
False) -> None: ...
class _Chain_wgmma:
"""`wgmma` — 19 entries sharing this mnemonic; PTX puts their difference
in the operand
@@ -1899,7 +2182,15 @@ class _Chain_xor:
b32: _Chain_xor
b64: _Chain_xor
pred: _Chain_xor
- def __call__(self, d: Any, a: Any, b: Any, *args: Any) -> None: ...
+ def __call__(
+ self,
+ d: Any,
+ a: Any,
+ b: Any,
+ *args: Any,
+ pred: Any = None,
+ preserve_dst: bool = False,
+ ) -> None: ...
class _PTX:
abs: _Chain_abs
diff --git a/src/tirx/transform/bind_target.cc
b/src/tirx/transform/bind_target.cc
index a03b8a252d..e64670b451 100644
--- a/src/tirx/transform/bind_target.cc
+++ b/src/tirx/transform/bind_target.cc
@@ -114,7 +114,8 @@ class FunctionClassifierVisitor : public StmtExprVisitor {
}
void VisitStmt_(const AttrStmtNode* op) final {
- if (op->attr_key == attr::thread_extent || op->attr_key ==
s_tir::attr::virtual_thread) {
+ if (op->attr_key == attr::thread_extent || op->attr_key ==
s_tir::attr::virtual_thread ||
+ op->attr_key == attr::kDeviceEntry) {
// Enter GPU scope for thread extent and virtual thread attributes
bool last_is_under_gpu_scope = is_under_gpu_scope_;
is_under_gpu_scope_ = true;
@@ -199,7 +200,8 @@ class CallSubstitutor : public StmtExprMutator {
}
Stmt VisitStmt_(const AttrStmtNode* op) final {
- if (op->attr_key == attr::thread_extent || op->attr_key ==
s_tir::attr::virtual_thread) {
+ if (op->attr_key == attr::thread_extent || op->attr_key ==
s_tir::attr::virtual_thread ||
+ op->attr_key == attr::kDeviceEntry) {
// Enter GPU scope for thread extent and virtual thread attributes
bool last_is_under_gpu_scope = is_under_gpu_scope_;
is_under_gpu_scope_ = true;
diff --git a/tests/python/tirx-transform/test_tir_transform_helpers.py
b/tests/python/tirx-transform/test_tir_transform_helpers.py
index 1932098c55..982c13a513 100644
--- a/tests/python/tirx-transform/test_tir_transform_helpers.py
+++ b/tests/python/tirx-transform/test_tir_transform_helpers.py
@@ -284,6 +284,61 @@ def test_bind_target_with_device_host_call_same_func():
tvm.ir.assert_structural_equal(After, Expected)
+def test_bind_target_with_tirx_device_entry():
+ """BindTarget classifies calls after T.device_entry as device-side."""
+
+ @I.ir_module
+ class Before:
+ @T.prim_func(private=True)
+ def add(a: T.int32, b: T.int32) -> T.int32:
+ return a + b
+
+ @T.prim_func
+ def main(A: T.Buffer((1,), "int32")):
+ T.func_attr({"global_symbol": "main"})
+ host_value: T.let[T.int32] = Before.add(1, 2)
+ T.device_entry()
+ tx = T.thread_id([1])
+ A[tx] = Before.add(host_value, 3)
+
+ @I.ir_module
+ class Expected:
+ @T.prim_func(private=True)
+ def add(a: T.int32, b: T.int32) -> T.int32:
+ T.func_attr({"target": T.target({"arch": "sm_100a", "kind":
"cuda"})})
+ return a + b
+
+ @T.prim_func(private=True)
+ def add_host(a: T.int32, b: T.int32) -> T.int32:
+ T.func_attr({"target": T.target({"kind": "llvm", "opt-level": 0})})
+ return a + b
+
+ @T.prim_func
+ def main(A: T.Buffer((1,), "int32")):
+ T.func_attr(
+ {
+ "global_symbol": "main",
+ "target": T.target(
+ {
+ "arch": "sm_100a",
+ "host": {"kind": "llvm", "opt-level": 0},
+ "kind": "cuda",
+ }
+ ),
+ }
+ )
+ host_value: T.let[T.int32] = Expected.add_host(1, 2)
+ T.device_entry()
+ tx = T.thread_id([1])
+ A[tx] = Expected.add(host_value, 3)
+
+ target = tvm.target.Target(
+ {"kind": "cuda", "arch": "sm_100a"}, host={"kind": "llvm",
"opt-level": 0}
+ )
+ After = tvm.tirx.transform.BindTarget(target)(Before)
+ tvm.ir.assert_structural_equal(After, Expected)
+
+
def test_filter_primfunc():
mod = MockModule
assert mod
diff --git a/tests/python/tirx/codegen/test_ptx_dialect.py
b/tests/python/tirx/codegen/test_ptx_dialect.py
index 76449d571a..f08d6ea419 100644
--- a/tests/python/tirx/codegen/test_ptx_dialect.py
+++ b/tests/python/tirx/codegen/test_ptx_dialect.py
@@ -201,6 +201,49 @@ def test_ptx_predication_codegen():
assert "setp.ne.b32 p, %2, 0; @p red.relaxed.gpu.global.add.u32 [%0], %1;"
in src
+@requires_nvcc
+def test_ptx_predicated_destination_preserves_old_value():
+ @T.prim_func
+ def kernel(a_ptr: T.handle, out_ptr: T.handle):
+ A = T.match_buffer(a_ptr, (1,), "float32")
+ Out = T.match_buffer(out_ptr, (32,), "float32")
+ T.device_entry()
+ T.cta_id([1])
+ tx = T.thread_id([32])
+ value: T.float32 = T.float32(0)
+ pred: T.uint32 = T.cast(tx == 0, "uint32")
+ T.ptx.ld.global_.f32(value, A.ptr_to([0]), pred=pred,
preserve_dst=True)
+ T.ptx.ex2.approx.ftz.f32(value, value, pred=pred, preserve_dst=True)
+ Out[tx] = value
+
+ src = _cuda_source(kernel)
+ assert "tvm_builtin_ptx_ld_global_f32_pred_keep" in src
+ assert "tvm_builtin_ptx_ex2_approx_ftz_f32_pred_keep" in src
+ assert '"+f"(__d)' in src
+ _assert_ptxas_ok(src)
+
+
+@requires_nvcc
+def test_ptx_predicated_destination_is_undefined_by_default():
+ @T.prim_func
+ def kernel(a_ptr: T.handle, out_ptr: T.handle):
+ A = T.match_buffer(a_ptr, (1,), "float32")
+ Out = T.match_buffer(out_ptr, (32,), "float32")
+ T.device_entry()
+ T.cta_id([1])
+ tx = T.thread_id([32])
+ value = T.local_scalar("float32")
+ pred: T.uint32 = T.cast(tx == 0, "uint32")
+ T.ptx.ld.global_.f32(value, A.ptr_to([0]), pred=pred)
+ Out[tx] = T.if_then_else(pred != 0, value, T.float32(0))
+
+ src = _cuda_source(kernel)
+ assert "tvm_builtin_ptx_ld_global_f32_pred_undef" in src
+ assert '"=f"(__d)' in src
+ assert '"+f"(__d)' not in src
+ _assert_ptxas_ok(src)
+
+
def test_ptx_string_form_matches_chain():
chain_call = T.ptx.ld.global_.acquire.gpu.b32
string_call = T.ptx["ld.acquire.gpu.global.b32"]
@@ -308,15 +351,6 @@ def test_ptx_destination_errors():
T.device_entry()
T.ptx.ld.global_.b32(T.uint32(0), A.ptr_to([0]))
- # @p is rejected on any instruction that writes a destination.
- with pytest.raises((ValueError, tvm.error.DiagnosticError), match="without
a destination"):
-
- @T.prim_func
- def predicated_destination(out: T.Buffer((1,), "uint32"), a_ptr:
T.handle):
- A = T.match_buffer(a_ptr, (1,), "uint32")
- T.device_entry()
- T.ptx.ld.global_.b32(out[0], A.ptr_to([0]), pred=T.uint32(1))
-
def test_ptx_register_group_codegen():
"""A `.lanes > 1` operand renders as braces in the asm, flat params in C.
@@ -1377,6 +1411,42 @@ def test_ptx_pred_operand_roundtrip():
tvm.ir.assert_structural_equal(kernel, reparsed)
+@requires_nvcc
+def test_ptx_tcgen05_mma_block_size_form():
+ @T.prim_func
+ def kernel(a_ptr: T.handle):
+ A = T.match_buffer(a_ptr, (32,), "uint32")
+ T.device_entry()
+ T.cta_id([1])
+ tx = T.thread_id([32])
+ if tx == 0:
+ tmem = T.local_scalar("uint32")
+ desc = T.local_scalar("uint64")
+ idesc = T.local_scalar("uint32")
+ flag = T.local_scalar("uint32")
+ T.ptx["tcgen05.mma.cta_group::1.kind::mxf4.block_scale.block32"](
+ tmem, desc, desc, idesc, tmem, tmem, T.ptx.pred(flag)
+ )
+ A[tx] = A[tx]
+
+ src = _cuda_source(kernel)
+ assert "tcgen05.mma.cta_group::1.kind::mxf4.block_scale.block32" in src
+ _assert_ptxas_ok(src, arch="sm_100a")
+
+ with pytest.raises((ValueError, tvm.error.DiagnosticError),
match="mxf4.*block32"):
+
+ @T.prim_func
+ def invalid_mxf4_block16():
+ T.device_entry()
+ tmem = T.local_scalar("uint32")
+ desc = T.local_scalar("uint64")
+ idesc = T.local_scalar("uint32")
+ flag = T.local_scalar("uint32")
+ T.ptx["tcgen05.mma.cta_group::1.kind::mxf4.block_scale.block16"](
+ tmem, desc, desc, idesc, tmem, tmem, T.ptx.pred(flag)
+ )
+
+
def test_ptx_pred_operand_rejects_untagged_integer():
"""An untagged integer at a `.pred` position is refused, by name.
@@ -2007,11 +2077,15 @@ def test_ptx_coercion_ir_forms():
call = T.ptx.st.release.gpu.global_.b32(global_ptr, val, pred=flag)
assert call.args[2].same_as(flag)
assert len(call.args) == 2 + 1 + 8 + 1 # operands + pred + slot tokens +
marker
- # @p on an instruction with a destination is rejected: a false predicate
- # leaves it unwritten while "=" tells nvcc its prior value is dead.
- dst = tvm.tirx.Var("d", "uint32")
- with pytest.raises(ValueError, match="without a destination"):
- T.ptx.ld.global_.b32(dst, global_ptr, pred=flag)
+ out = tvm.tirx.decl_buffer((1,), "uint32", name="out", scope="local")
+ call = T.ptx.ld.global_.b32(out[0], global_ptr, pred=flag)
+ assert str(call.args[-1]).strip('"') == "pred"
+ call = T.ptx.ld.global_.b32(out[0], global_ptr, pred=flag,
preserve_dst=True)
+ assert str(call.args[-1]).strip('"') == "pred,keep"
+ with pytest.raises(ValueError, match="requires pred"):
+ T.ptx.ld.global_.b32(out[0], global_ptr, preserve_dst=True)
+ with pytest.raises(ValueError, match="requires a written destination"):
+ T.ptx.st.release.gpu.global_.b32(global_ptr, val, pred=flag,
preserve_dst=True)
# fp16/bf16 dtypes bring in __half / __nv_bfloat16 and their bit-cast helpers.
@@ -2201,7 +2275,7 @@ def test_ptx_all_variants_render_unique():
or f"; {opcode};" in source
)
total += not predicated # a @p twin is not a separate variant
- assert total == 200002 # update when the table grows
+ assert total == 200018 # update when the table grows
def test_ptx_no_instruction_registered_twice():
diff --git
a/tests/python/tirx/operator/tile_primitive/cuda/gemm_async/test_gemm_async.py
b/tests/python/tirx/operator/tile_primitive/cuda/gemm_async/test_gemm_async.py
index ad5942aa90..02d972ce58 100644
---
a/tests/python/tirx/operator/tile_primitive/cuda/gemm_async/test_gemm_async.py
+++
b/tests/python/tirx/operator/tile_primitive/cuda/gemm_async/test_gemm_async.py
@@ -3057,10 +3057,9 @@ def test_gemm_smem_desc_modes_codegen(smem_desc):
base address, selected by the ``smem_desc`` config.
- ``hoist`` (default): allocate + encode one descriptor per operand
- (``descA`` / ``descB``), then add the per-MMA 16B offset via
- ``smem_desc_add_16B_offset``. This builder uses a single-thread scope,
- where the encoding thread is also the consumer and no warp shuffle is
- valid or needed.
+ (``descA`` / ``descB``), then update the descriptor address lane for each
+ MMA. This builder uses a single-thread scope, where the encoding thread
+ is also the consumer and no warp shuffle is valid or needed.
- ``recompute``: build the full descriptor inline per MMA
(``_uniform_desc``)
with no allocated/encoded descriptor cell — trades a few ALU ops for one
fewer live register on the hot path.
@@ -3084,18 +3083,12 @@ def test_gemm_smem_desc_modes_codegen(smem_desc):
if smem_desc == "hoist":
assert "encode_matrix_descriptor" in src, "hoist mode must encode a
descriptor"
- assert "smem_desc_make_lo_uniform" not in src, "hoist mode must not
warp-shuffle"
- assert "smem_desc_add_16B_offset" in src, "hoist mode must add the
per-MMA 16B offset"
elif smem_desc == "local_hoist":
assert "descA_local" in src and "descB_local" in src
- assert "smem_desc_add_16B_offset" in src
assert "encode_matrix_descriptor" in src
elif smem_desc == "encode":
assert "encode_matrix_descriptor" in src
- assert "smem_desc_add_16B_offset" not in src
else:
- assert "smem_desc_make_lo_uniform" not in src, "recompute mode must
not hoist a descriptor"
- assert "smem_desc_add_16B_offset" not in src, "recompute mode must not
add a 16B offset"
assert "encode_matrix_descriptor" not in src, "recompute mode must not
encode a descriptor"
@@ -3736,10 +3729,10 @@ def
test_gemm_tcgen05_hoisted_descriptor_uniformization(scope_kind, expect_unifo
callback_text = str(sctx.callbacks["post_buffer_def_stmt"])
assert "encode_matrix_descriptor" in callback_text
- assert ("smem_desc_make_lo_uniform" in callback_text) == expect_uniform
+ assert ("T.ptx.shfl_sync" in callback_text) == expect_uniform
assert ("elect_sync" in impl.script()) == expect_uniform
if expect_uniform:
- assert callback_text.index("smem_desc_make_lo_uniform") <
callback_text.index(
+ assert callback_text.index("T.ptx.shfl_sync") < callback_text.index(
"tvm_kernel_replace_point"
)