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 5e49b34304 [CUDA][TIRx] Allow collector-A-only SM107 block-scale MMA
and open cp.async wait_group immediates (#20271)
5e49b34304 is described below
commit 5e49b343042335e32d61fdcba5d3916efb7d07ed
Author: Bohan Hou <[email protected]>
AuthorDate: Fri Sep 4 21:27:52 2026 -0400
[CUDA][TIRx] Allow collector-A-only SM107 block-scale MMA and open cp.async
wait_group immediates (#20271)
## Summary
Two `T.ptx` table follow-ups to #20266
(`python/tvm/backend/cuda/ptx/table.py`).
- **Collector-A-only SM107 block-scale MMA.** On the SM107 `tcgen05.mma`
block-scale entries `collector_b` becomes optional, so the
activation-stationary FP8 form with collector A alone is accepted
alongside the A+B form.
- **Open `cp.async` wait-group immediates.** `cp.async.wait_group N` and
`cp.async.bulk.wait_group[.read] N` no longer restrict `N` to `0..7`.
PTX ISA 9.7.10.28.3.3 / 9.7.10.28.6.2 define `N` only as an integer
constant with no value domain, and CUDA 13.4 ptxas at sm_107a accepts
values beyond 7 (measured: 8, 9, 16, 255; the bulk `.read` form also
2147483647 and -1). Each call-site constant still becomes its own
helper; enumeration and full-table certification sample the open operand
at 0 and certify the instruction shape.
## Tests (`tests/python/tirx/codegen/test_ptx_dialect.py`)
- New `test_ptx_tcgen05_mma_block_scale_collector_a_without_block_size`:
SM107 activation-stationary FP8 accepts collector A without `.block*`.
- Wait-group tests updated for the open immediate.
## Downstream
Needed by the Rubin masked grouped GEMM and block-scaled fused-MoE
SwiGLU ports in mlc-ai/tirx-kernels.
---
python/tvm/backend/cuda/ptx/table.py | 18 +++++++++-----
tests/python/tirx/codegen/test_ptx_dialect.py | 36 ++++++++++++++++++++++++++-
2 files changed, 47 insertions(+), 7 deletions(-)
diff --git a/python/tvm/backend/cuda/ptx/table.py
b/python/tvm/backend/cuda/ptx/table.py
index 1aeb7dda50..94e0121f82 100644
--- a/python/tvm/backend/cuda/ptx/table.py
+++ b/python/tvm/backend/cuda/ptx/table.py
@@ -5328,7 +5328,7 @@ _PTX_94_ENTRIES = [
),
ModifierSlot("block_scale", ("block_scale",)),
ModifierSlot("collector_a", _TCGEN05_COLLECTOR_A),
- ModifierSlot("collector_b", _TCGEN05_COLLECTOR_B),
+ ModifierSlot("collector_b", _TCGEN05_COLLECTOR_B,
optional=True),
),
cert_arch="sm_107f",
operands=(
@@ -8516,6 +8516,12 @@ _ENTRIES = [
OperandSlot("b", dtype="u32"),
),
),
+ # PTX ISA 9.7.10.28.3.3 / 9.7.10.28.6.2 defines N only as an integer
+ # constant and declares no value domain. Keep it OPEN: callers may use any
+ # compile-time integer, while certification samples the instruction shape.
+ # MEASURED on CUDA 13.4 ptxas at sm_107a: ordinary and bulk forms accept
+ # values beyond 7 (8, 9, 16, 255), and the bulk `.read` form also accepts
+ # 2147483647 and -1.
*[
InstructionEntry( # cp.async{.bulk}.wait_group{.read} N;
name=f"cp_async{'_bulk' if bulk else ''}_wait_group",
@@ -8527,7 +8533,7 @@ _ENTRIES = [
*((ModifierSlot("read", ("read",), optional=True),) if bulk
else ()),
),
orders_memory=True,
- operands=(OperandSlot("group", kind="imm", choices=tuple(str(n)
for n in range(8))),),
+ operands=(OperandSlot("group", kind="imm"),),
)
for bulk in (False, True)
],
@@ -10179,10 +10185,10 @@ _ENTRIES = [
# and cp.async.wait_all per 9.7.10.28.3.3, cp.async.bulk.commit_group /
# .wait_group per 9.7.10.28.6.1 / 9.7.10.28.6.2.
#
- # The wait_group counts are caller-chosen immediates: the ISA gives N no
- # register form, so each value is its own helper, and the closed `choices`
- # set is what makes every one of them certifiable. 0..7 covers every call
- # site (pipeline depths); widen the tuple if a deeper pipeline appears.
+ # The wait_group counts are caller-chosen OPEN immediates: the ISA gives N
+ # no register form or value domain. Each call-site constant becomes its own
+ # helper; enumeration and full-table certification sample the open operand
+ # at 0 and therefore certify the instruction shape rather than every value.
#
# (The `cp.async` ca/cg copy lines this note once excluded are registered
# in the 9.7.10 group above, ignore-src operand and all.)
diff --git a/tests/python/tirx/codegen/test_ptx_dialect.py
b/tests/python/tirx/codegen/test_ptx_dialect.py
index 4290125f50..835979b6f2 100644
--- a/tests/python/tirx/codegen/test_ptx_dialect.py
+++ b/tests/python/tirx/codegen/test_ptx_dialect.py
@@ -1977,6 +1977,10 @@ def test_ptx_data_movement_dispatch():
T.ptx.applypriority.global_.L2__evict_normal(A.ptr_to([6]))
T.ptx.discard.global_.L2(A.ptr_to([7]))
T.ptx.prefetchu.L1(A.ptr_to([0]))
+ T.ptx.cp.async_.wait_group(255)
+ T.ptx.cp.async_.bulk.wait_group(255)
+ T.ptx.cp.async_.bulk.wait_group.read(8)
+ T.ptx.cp.async_.bulk.wait_group.read(-1)
T.ptx.multimem_ld_reduce.add.u32(v, A.ptr_to([0]))
T.ptx.multimem_red.relaxed.gpu.add.u32(A.ptr_to([0]), v)
smem[tx % 4] = d + p + v
@@ -2000,6 +2004,10 @@ def test_ptx_data_movement_dispatch():
"applypriority.global.L2::evict_normal [%0], 128;",
"discard.global.L2 [%0], 128;",
"prefetchu.L1 [%0];",
+ "cp.async.wait_group 255;",
+ "cp.async.bulk.wait_group 255;",
+ "cp.async.bulk.wait_group.read 8;",
+ "cp.async.bulk.wait_group.read -1;",
"multimem.ld_reduce.add.u32 %0, [%1];",
"multimem.red.relaxed.gpu.add.u32 [%0], %1;",
):
@@ -2411,6 +2419,32 @@ def test_ptx_tcgen05_mma_block_size_collector_form():
_assert_ptxas_ok(collector_src, arch="sm_107f")
+@requires_nvcc
+def test_ptx_tcgen05_mma_block_scale_collector_a_without_block_size():
+ """SM107 activation-stationary FP8 accepts collector A without
`.block*`."""
+
+ @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::mxf8f6f4.block_scale.collector::a::discard"](
+ tmem, desc, desc, idesc, tmem, tmem, T.ptx.pred(flag)
+ )
+ A[tx] = A[tx]
+
+ src = _cuda_source(kernel)
+ opcode =
"tcgen05.mma.cta_group::1.kind::mxf8f6f4.block_scale.collector::a::discard"
+ assert opcode in src
+ _assert_ptxas_ok(src, arch="sm_107a")
+
+
def test_ptx_tcgen05_mma_block_size_collector_legality():
from tvm.backend.cuda.ptx.table import TABLE, tokens_for
@@ -4106,7 +4140,7 @@ def test_ptx_all_variants_render_unique():
_, helper, _ = render_variant(entry, *args,
addr_offsets=addr_offsets)
assert helper not in names, f"address-offset helper name
collision: {helper}"
names.add(helper)
- assert total == 762023 # update when the table grows or a ptxas gap
narrows it
+ assert total == 762050 # update when the table grows or a ptxas gap
narrows it
def test_ptx_no_instruction_registered_twice():