This is an automated email from the ASF dual-hosted git repository.

tqchen 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 4b86188893 [REFACTOR][CUDA] Store tcgen05 descriptor options in call 
attributes (#20596)
4b86188893 is described below

commit 4b86188893aeb5480d5c725dd2e179fc1a487313
Author: Tianqi Chen <[email protected]>
AuthorDate: Wed Oct 7 13:34:29 2026 -0400

    [REFACTOR][CUDA] Store tcgen05 descriptor options in call attributes 
(#20596)
    
    Move tcgen05 descriptor configuration into typed CUDA call attributes,
    preserving the in-place destination and fixed void result. Use shared
    keyword construction and reflected printing, and pass attributes to
    descriptor code generation.
    
    Remove unused scale-address operands while retaining runtime scale-ID
    patching and the descriptor bit layout.
---
 include/tvm/tirx/op_attr_types.h                   |   9 ++
 python/tvm/backend/cuda/codegen/registry.py        |   7 +-
 python/tvm/backend/cuda/cpp/descriptors.py         |  97 ++++++------------
 python/tvm/backend/cuda/op.py                      |  82 ++++++++--------
 .../cuda/tile_primitive/gemm_async/tcgen05.py      |  18 ----
 python/tvm/ir/op.py                                |  36 +++++--
 python/tvm/tirx/script/ir_builder/op.py            |  15 +--
 src/backend/cuda/codegen/codegen_cuda.cc           |   2 +-
 src/backend/cuda/op/attrs.h                        | 108 +++++++++++++++++++++
 src/backend/cuda/op/target_builtin.cc              |  21 ++--
 src/relax/script/printer/call.cc                   |  79 +++++++++++++++
 src/tirx/script/printer/expr.cc                    |  52 ----------
 .../codegen/test_tcgen05_descriptor_encoders.py    |   2 -
 .../cuda/gemm_async/test_gemm_async.py             |   9 +-
 .../python/tirx/script/test_tirx_script_printer.py |   8 +-
 15 files changed, 328 insertions(+), 217 deletions(-)

diff --git a/include/tvm/tirx/op_attr_types.h b/include/tvm/tirx/op_attr_types.h
index 08f977ffdc..aab39d4ca1 100644
--- a/include/tvm/tirx/op_attr_types.h
+++ b/include/tvm/tirx/op_attr_types.h
@@ -75,6 +75,15 @@ using FLegalize = ffi::TypedFunction<PrimExpr(PrimExpr)>;
  */
 using TScriptPrinterName = ffi::String;
 
+/*!
+ * \brief The published script callable accepts the shared Op construction 
contract.
+ *
+ * Published by Python construction namespaces, not by Op definitions: a 
printer
+ * name alone does not establish a legacy wrapper's positional/keyword 
contract.
+ * Semantic syntax hooks retain precedence over ordinary named construction.
+ */
+using TScriptStandardCall = bool;
+
 /*!
  * \brief Specifies that TVMScript printer prints the dtype as the first/last 
argument.
           If not specified, dtype will not be printed.
diff --git a/python/tvm/backend/cuda/codegen/registry.py 
b/python/tvm/backend/cuda/codegen/registry.py
index 7ae1235185..94acd6ff18 100644
--- a/python/tvm/backend/cuda/codegen/registry.py
+++ b/python/tvm/backend/cuda/codegen/registry.py
@@ -66,8 +66,11 @@ def register_codegen(op, backend="cuda"):
         op_names = {full_op_name, canonical_op_name}
 
         @functools.wraps(func)
-        def wrapper(arg_list):
-            res = func(*arg_list)  # pylint: disable=not-callable
+        def wrapper(arg_list, attrs=None):
+            # Keep operand-only helpers callable directly while forwarding the
+            # typed static options of an attributed Call to its lowering.
+            kwargs = {} if attrs is None else {"attrs": attrs}
+            res = func(*arg_list, **kwargs)  # pylint: disable=not-callable
             if isinstance(res, tuple):
                 return res[0], res[1]
             return res, list()
diff --git a/python/tvm/backend/cuda/cpp/descriptors.py 
b/python/tvm/backend/cuda/cpp/descriptors.py
index bf30b54e0b..3e246d1e07 100644
--- a/python/tvm/backend/cuda/cpp/descriptors.py
+++ b/python/tvm/backend/cuda/cpp/descriptors.py
@@ -30,7 +30,7 @@ same constraints into its dispatch.
 from ..codegen.registry import CODEGEN_REGISTRY, register_codegen
 from ..codegen.schema import device_intrinsic
 from ..codegen.types import PTXDataType
-from ..codegen.utils import parse_str, validate_cta_group
+from ..codegen.utils import validate_cta_group
 
 # =============================================================================
 # wgmma_encode_matrix_descriptor — pure-C bitfield struct fill (no asm).
@@ -321,37 +321,21 @@ device_intrinsic(
 
 
 @register_codegen("cuda_tcgen05_encode_instr_descriptor")
-def codegen_cuda_tcgen05_encode_instr_descriptor(
-    desc,
-    d_dtype,
-    a_dtype,
-    b_dtype,
-    M,
-    N,
-    K,
-    trans_a,
-    trans_b,
-    n_cta_group,
-    neg_a,
-    neg_b,
-    sat_d,
-    is_sparse,
-):
-    """Validate dtype combinations and shape, translate dtypes to PTX format
-    integers, then forward to the schema-driven impl."""
-    a_dtype = parse_str(a_dtype)
-    b_dtype = parse_str(b_dtype)
-    d_dtype = parse_str(d_dtype)
-    M = int(M)
-    N = int(N)
-    K = int(K)
-    n_cta_group = validate_cta_group(n_cta_group)
-    trans_a = bool(trans_a)
-    trans_b = bool(trans_b)
-    neg_a = bool(neg_a)
-    neg_b = bool(neg_b)
-    sat_d = bool(sat_d)
-    is_sparse = bool(is_sparse)
+def codegen_cuda_tcgen05_encode_instr_descriptor(desc, *, attrs):
+    """Validate static Call.attrs and lower the in-place descriptor fill."""
+    d_dtype = attrs.d_dtype
+    a_dtype = attrs.a_dtype
+    b_dtype = attrs.b_dtype
+    M = attrs.M
+    N = attrs.N
+    K = attrs.K
+    trans_a = attrs.trans_a
+    trans_b = attrs.trans_b
+    n_cta_group = validate_cta_group(attrs.n_cta_groups)
+    neg_a = attrs.neg_a
+    neg_b = attrs.neg_b
+    sat_d = attrs.sat_d
+    is_sparse = attrs.is_sparse
 
     kind = _get_tcgen05_mma_kind(d_dtype, a_dtype, b_dtype)
     if kind not in ["f16", "tf32", "f8f6f4", "i8"]:
@@ -436,39 +420,22 @@ device_intrinsic(
 
 
 @register_codegen("cuda_tcgen05_encode_instr_descriptor_block_scaled")
-def codegen_cuda_tcgen05_encode_instr_descriptor_block_scaled(
-    desc,
-    d_dtype,
-    a_dtype,
-    b_dtype,
-    sfa_dtype,
-    sfb_dtype,
-    sfa_tmem_addr,
-    sfb_tmem_addr,
-    M,
-    N,
-    K,
-    trans_a,
-    trans_b,
-    n_cta_group,
-    neg_a,
-    neg_b,
-    is_sparse,
-):
-    a_dtype = parse_str(a_dtype)
-    b_dtype = parse_str(b_dtype)
-    d_dtype = parse_str(d_dtype)
-    sfa_dtype = parse_str(sfa_dtype)
-    sfb_dtype = parse_str(sfb_dtype)
-    M = int(M)
-    N = int(N)
-    K = int(K)
-    n_cta_group = validate_cta_group(n_cta_group)
-    trans_a = bool(trans_a)
-    trans_b = bool(trans_b)
-    neg_a = bool(neg_a)
-    neg_b = bool(neg_b)
-    is_sparse = bool(is_sparse)
+def codegen_cuda_tcgen05_encode_instr_descriptor_block_scaled(desc, *, attrs):
+    """Validate static Call.attrs and lower the in-place descriptor fill."""
+    d_dtype = attrs.d_dtype
+    a_dtype = attrs.a_dtype
+    b_dtype = attrs.b_dtype
+    sfa_dtype = attrs.sfa_dtype
+    sfb_dtype = attrs.sfb_dtype
+    M = attrs.M
+    N = attrs.N
+    K = attrs.K
+    trans_a = attrs.trans_a
+    trans_b = attrs.trans_b
+    n_cta_group = validate_cta_group(attrs.n_cta_groups)
+    neg_a = attrs.neg_a
+    neg_b = attrs.neg_b
+    is_sparse = attrs.is_sparse
 
     kind = _get_tcgen05_mma_kind(d_dtype, a_dtype, b_dtype, sfa_dtype, 
sfb_dtype)
     valid_kinds = {"mxf8f6f4", "mxf4", "mxf4nvf4"}
diff --git a/python/tvm/backend/cuda/op.py b/python/tvm/backend/cuda/op.py
index 63793ebf50..4218095fc8 100644
--- a/python/tvm/backend/cuda/op.py
+++ b/python/tvm/backend/cuda/op.py
@@ -21,7 +21,7 @@ from __future__ import annotations
 
 from tvm import tirx
 from tvm.ir import Call, Op, StringImm
-from tvm.ir.op import _init_op_api
+from tvm.ir.op import _init_op_api, _make_op_api
 from tvm.ir.type import PointerType, PrimType
 from tvm.runtime import const
 from tvm.tirx.op import bitwise_and, call_intrin, tvm_access_ptr
@@ -603,6 +603,14 @@ def ptx_legacy_ldmatrix(*all_args):
     )
 
 
+_encode_instr_descriptor = _make_op_api(
+    Op.get("tirx.cuda.tcgen05_encode_instr_descriptor"), __name__
+)
+_encode_instr_descriptor_block_scaled = _make_op_api(
+    Op.get("tirx.cuda.tcgen05_encode_instr_descriptor_block_scaled"), __name__
+)
+
+
 def cuda_tcgen05_encode_instr_descriptor(
     desc,
     *,
@@ -669,23 +677,21 @@ def cuda_tcgen05_encode_instr_descriptor(
         Whether the MMA operation is sparse.
     """
     _choice("n_cta_groups", n_cta_groups, _TCGEN05_CTA_GROUP)
-    return call_intrin(
-        "",
-        "tirx.cuda.tcgen05_encode_instr_descriptor",
+    return _encode_instr_descriptor(
         desc,
-        d_dtype,
-        a_dtype,
-        b_dtype,
-        M,
-        N,
-        K,
-        trans_a,
-        trans_b,
-        n_cta_groups,
-        neg_a,
-        neg_b,
-        sat_d,
-        is_sparse,
+        d_dtype=d_dtype,
+        a_dtype=a_dtype,
+        b_dtype=b_dtype,
+        M=M,
+        N=N,
+        K=K,
+        trans_a=trans_a,
+        trans_b=trans_b,
+        n_cta_groups=n_cta_groups,
+        neg_a=neg_a,
+        neg_b=neg_b,
+        sat_d=sat_d,
+        is_sparse=is_sparse,
     )
 
 
@@ -697,8 +703,6 @@ def cuda_tcgen05_encode_instr_descriptor_block_scaled(
     b_dtype,
     sfa_dtype,
     sfb_dtype,
-    sfa_tmem_addr,
-    sfb_tmem_addr,
     M,
     N,
     K,
@@ -731,12 +735,6 @@ def cuda_tcgen05_encode_instr_descriptor_block_scaled(
     sfb_dtype : str
         The datatype of scale factor matrix B.
 
-    sfa_tmem_addr : Expr
-        The address of the scale factor matrix A in tensor memory, should be 
uint32_t.
-
-    sfb_tmem_addr : Expr
-        The address of the scale factor matrix B in tensor memory, should be 
uint32_t.
-
     M : int
         The size of non-reduction dimension of Matrix A.
 
@@ -767,26 +765,22 @@ def cuda_tcgen05_encode_instr_descriptor_block_scaled(
         Whether the MMA operation is sparse.
     """
     _choice("n_cta_groups", n_cta_groups, _TCGEN05_CTA_GROUP)
-    return call_intrin(
-        "",
-        "tirx.cuda.tcgen05_encode_instr_descriptor_block_scaled",
+    return _encode_instr_descriptor_block_scaled(
         desc,
-        d_dtype,
-        a_dtype,
-        b_dtype,
-        sfa_dtype,
-        sfb_dtype,
-        sfa_tmem_addr,
-        sfb_tmem_addr,
-        M,
-        N,
-        K,
-        trans_a,
-        trans_b,
-        n_cta_groups,
-        neg_a,
-        neg_b,
-        is_sparse,
+        d_dtype=d_dtype,
+        a_dtype=a_dtype,
+        b_dtype=b_dtype,
+        sfa_dtype=sfa_dtype,
+        sfb_dtype=sfb_dtype,
+        M=M,
+        N=N,
+        K=K,
+        trans_a=trans_a,
+        trans_b=trans_b,
+        n_cta_groups=n_cta_groups,
+        neg_a=neg_a,
+        neg_b=neg_b,
+        is_sparse=is_sparse,
     )
 
 
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 acb3b7c90c..f3e43707bc 100644
--- a/python/tvm/backend/cuda/tile_primitive/gemm_async/tcgen05.py
+++ b/python/tvm/backend/cuda/tile_primitive/gemm_async/tcgen05.py
@@ -1279,23 +1279,6 @@ def gemm_async_tcgen05_impl(op_call: TilePrimitiveCall, 
sctx: DispatchContext) -
         sfa_base = SFA_buffer.allocated_addr[0]
         sfb_base = SFB_buffer.allocated_addr[0]
 
-        # Compute initial SFA/SFB addresses (for ki=0). Both physical axes are
-        # part of a TMEM address: TLane occupies the high half-word and TCol
-        # the low half-word. Dropping TLane silently redirects an explicitly
-        # banded scale view to row zero.
-        sfa_coord_0 = SFA_slice_layout.apply(0)
-        sfb_coord_0 = SFB_slice_layout.apply(0)
-        sfa_tlane_0 = sfa_coord_0.get("TLane", 0)
-        sfb_tlane_0 = sfb_coord_0.get("TLane", 0)
-        sfa_tcol_0 = sfa_coord_0.get("TCol", 0)
-        sfb_tcol_0 = sfb_coord_0.get("TCol", 0)
-        SFA_init_addr = _get_tmem_addr_fast(
-            sfa_base, sfa_tlane_0, tvm.tirx.floordiv(sfa_tcol_0, 
SFA_elem_per_col)
-        )
-        SFB_init_addr = _get_tmem_addr_fast(
-            sfb_base, sfb_tlane_0, tvm.tirx.floordiv(sfb_tcol_0, 
SFB_elem_per_col)
-        )
-
         # Rotate sf_id per ki when multiple ki share one SF column.
         needs_sf_id = sfa_sf_mma_k < SFA_elem_per_col and sfa_elems_per_ki > 0
 
@@ -1503,7 +1486,6 @@ def gemm_async_tcgen05_impl(op_call: TilePrimitiveCall, 
sctx: DispatchContext) -
         def impl():
             descI_local: T.uint32
             
T.cuda.tcgen05.encode_instr_descriptor_block_scaled(T.address_of(descI_local), 
d_dtype=C_type, a_dtype=A_type, b_dtype=B_type, sfa_dtype=SFA_type, 
sfb_dtype=SFB_type,  # noqa: E501, F821
-                                                               
sfa_tmem_addr=SFA_init_addr, sfb_tmem_addr=SFB_init_addr,  # noqa: E501
                                                                M=M_mma * 
cta_group, N=N_mma, K=MMA_K, trans_a=a_mn_major, trans_b=b_mn_major, 
n_cta_groups=cta_group)  # noqa: E501
             call_main(descI_local)  # noqa: F821
     else:
diff --git a/python/tvm/ir/op.py b/python/tvm/ir/op.py
index 927b8edac7..0b406b556f 100644
--- a/python/tvm/ir/op.py
+++ b/python/tvm/ir/op.py
@@ -27,12 +27,23 @@ import tvm_ffi
 from . import _ffi_api
 from .expr import Expr
 
+_RESULT_TYPE_UNSET = object()
+
 
 def _make_op_api(op, module_name):
     """Build a callable whose operands and result are governed by its Op."""
+    from .attrs import make_node  # pylint: disable=import-outside-toplevel
     from .expr import Call, reinfer_type  # pylint: 
disable=import-outside-toplevel
 
-    def call(*args, attrs=None, ty_args=None, span=None, ret_ty=None, 
**kwargs):
+    def call(
+        *args,
+        attrs=None,
+        ty_args=None,
+        span=None,
+        ty=_RESULT_TYPE_UNSET,
+        ret_ty=_RESULT_TYPE_UNSET,
+        **kwargs,
+    ):
         # Bind named operands using the registered signature, without inventing
         # defaults or interpreting arbitrary Python wrapper signatures.
         operands = list(args)
@@ -45,18 +56,29 @@ def _make_op_api(op, module_name):
             else:
                 raise TypeError(f"{op.name}: missing operand {info.name!r}")
         if kwargs:
-            raise TypeError(f"{op.name}: unexpected keyword operands 
{tuple(kwargs)}")
-        if ret_ty is None and (
+            if not op.attrs_type_key:
+                raise TypeError(f"{op.name}: unexpected keyword operands 
{tuple(kwargs)}")
+            if attrs is not None:
+                raise TypeError(f"{op.name}: cannot mix attrs with attribute 
keywords")
+            attrs = make_node(op.attrs_type_key, **kwargs)
+        if ty is not _RESULT_TYPE_UNSET:
+            if ret_ty is not _RESULT_TYPE_UNSET:
+                raise TypeError(f"{op.name}: ty and ret_ty cannot both be 
specified")
+            ret_ty = ty
+        elif (ret_ty is _RESULT_TYPE_UNSET or ret_ty is None) and (
             op.get_attr("TFixedReturnType") is not None or 
op.get_attr("FInferType") is not None
         ):
             provisional = Call.unchecked(op, operands, attrs=attrs, 
ty_args=ty_args, span=span)
             ret_ty = reinfer_type(provisional)
+        if ret_ty is _RESULT_TYPE_UNSET:
+            ret_ty = None
         return Call(op, operands, attrs=attrs, ty_args=ty_args, span=span, 
ty=ret_ty)
 
     call.__name__ = op.name.rsplit(".", 1)[-1]
     call.__module__ = module_name
     call.__doc__ = op.doc or f"Construct a call to {op.name}."
     call.__tvm_op__ = op
+    call.__tvm_standard_call__ = True
     return call
 
 
@@ -69,9 +91,11 @@ def _init_op_api(namespace, target_module_name=None):
     module or SimpleNamespace containers; underscores are ordinary name parts.
 
     Generated functions accept registered positional/named operands plus
-    ``attrs``, ``ty_args``, ``span`` and ``ret_ty``. Omitting the result 
invokes
-    an available Op inference hook; without one, Call retains a missing type.
-    Explicit results and inference/validation errors are preserved.
+    ``attrs``, ``ty_args``, ``span`` and ``ty`` (also spelled ``ret_ty``).
+    Omitting the result invokes an available Op inference hook; without one,
+    Call retains a missing type.
+    Explicit results and inference/validation errors are preserved. Attribute
+    keywords construct the registered attrs schema, when one is declared.
 
     Existing generated functions are reused. A deliberate wrapper may declare
     ``__tvm_op__ = Op.get(name)`` to retain ownership of that name. This 
declares
diff --git a/python/tvm/tirx/script/ir_builder/op.py 
b/python/tvm/tirx/script/ir_builder/op.py
index 8bd107da37..3d189e7281 100644
--- a/python/tvm/tirx/script/ir_builder/op.py
+++ b/python/tvm/tirx/script/ir_builder/op.py
@@ -335,13 +335,14 @@ def register_script_namespace(
                 current = getattr(current, part, None)
             op = _ir.Op.get(op_name)
             identity = getattr(current, "__tvm_op__", None)
-            if (
-                callable(current)
-                and isinstance(identity, _ir.Op)
-                and identity.same_as(op)
-                and op.get_attr("TScriptPrinterName") is None
-            ):
-                op.set_attr("TScriptPrinterName", op_name)
+            if callable(current) and isinstance(identity, _ir.Op) and 
identity.same_as(op):
+                op.set_attr(
+                    "TScriptStandardCall",
+                    bool(getattr(current, "__tvm_standard_call__", False)),
+                    override=True,
+                )
+                if op.get_attr("TScriptPrinterName") is None:
+                    op.set_attr("TScriptPrinterName", op_name)
     else:
         _register_script_namespace_printer_names(namespace, f"tirx.{name}", 
override)
     return namespace
diff --git a/src/backend/cuda/codegen/codegen_cuda.cc 
b/src/backend/cuda/codegen/codegen_cuda.cc
index 66598b90ab..fbfe880efd 100644
--- a/src/backend/cuda/codegen/codegen_cuda.cc
+++ b/src/backend/cuda/codegen/codegen_cuda.cc
@@ -1065,7 +1065,7 @@ void CodeGenCUDA::Dispatch_(const CallNode* op, 
std::ostream& os) {
     auto codegen = 
codegen_getter.value()(call_op->name).cast<ffi::Optional<tvm::ffi::Function>>();
     if (codegen.has_value()) {
       // codegen is registered, it should return a Call to cuda_func_call
-      auto func_call = codegen.value()(op->args);
+      auto func_call = codegen.value()(op->args, op->attrs);
       auto res = func_call.cast<ffi::Tuple<Call, ffi::Array<ffi::String>>>();
       print_cuda_func_call(res.get<0>().get(), os);
       for (const auto& tag : res.get<1>()) {
diff --git a/src/backend/cuda/op/attrs.h b/src/backend/cuda/op/attrs.h
new file mode 100644
index 0000000000..93fa8d3fa3
--- /dev/null
+++ b/src/backend/cuda/op/attrs.h
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+#ifndef TVM_BACKEND_CUDA_OP_ATTRS_H_
+#define TVM_BACKEND_CUDA_OP_ATTRS_H_
+
+#include <tvm/ir/attrs.h>
+
+namespace tvm {
+namespace tirx {
+
+/*! \brief Static options for the dense tcgen05 instruction descriptor. */
+struct TCGen05InstrDescriptorAttrs : public AttrsNode {
+  ffi::String d_dtype;
+  ffi::String a_dtype;
+  ffi::String b_dtype;
+  int64_t M;
+  int64_t N;
+  int64_t K;
+  bool trans_a;
+  bool trans_b;
+  int64_t n_cta_groups = 1;
+  bool neg_a = false;
+  bool neg_b = false;
+  bool sat_d = false;
+  bool is_sparse = false;
+
+  static void RegisterReflection() {
+    namespace refl = ffi::reflection;
+    refl::ObjectDef<TCGen05InstrDescriptorAttrs>()
+        .def_ro("d_dtype", &TCGen05InstrDescriptorAttrs::d_dtype)
+        .def_ro("a_dtype", &TCGen05InstrDescriptorAttrs::a_dtype)
+        .def_ro("b_dtype", &TCGen05InstrDescriptorAttrs::b_dtype)
+        .def_ro("M", &TCGen05InstrDescriptorAttrs::M)
+        .def_ro("N", &TCGen05InstrDescriptorAttrs::N)
+        .def_ro("K", &TCGen05InstrDescriptorAttrs::K)
+        .def_ro("trans_a", &TCGen05InstrDescriptorAttrs::trans_a)
+        .def_ro("trans_b", &TCGen05InstrDescriptorAttrs::trans_b)
+        .def_ro("n_cta_groups", &TCGen05InstrDescriptorAttrs::n_cta_groups, 
refl::DefaultValue(1))
+        .def_ro("neg_a", &TCGen05InstrDescriptorAttrs::neg_a, 
refl::DefaultValue(false))
+        .def_ro("neg_b", &TCGen05InstrDescriptorAttrs::neg_b, 
refl::DefaultValue(false))
+        .def_ro("sat_d", &TCGen05InstrDescriptorAttrs::sat_d, 
refl::DefaultValue(false))
+        .def_ro("is_sparse", &TCGen05InstrDescriptorAttrs::is_sparse, 
refl::DefaultValue(false));
+  }
+  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("tirx.cuda.TCGen05InstrDescriptorAttrs",
+                                    TCGen05InstrDescriptorAttrs, AttrsNode);
+};
+
+/*! \brief Static options for the block-scaled tcgen05 instruction descriptor. 
*/
+struct TCGen05InstrDescriptorBlockScaledAttrs : public AttrsNode {
+  ffi::String d_dtype;
+  ffi::String a_dtype;
+  ffi::String b_dtype;
+  ffi::String sfa_dtype;
+  ffi::String sfb_dtype;
+  int64_t M;
+  int64_t N;
+  int64_t K;
+  bool trans_a;
+  bool trans_b;
+  int64_t n_cta_groups = 1;
+  bool neg_a = false;
+  bool neg_b = false;
+  bool is_sparse = false;
+
+  static void RegisterReflection() {
+    namespace refl = ffi::reflection;
+    refl::ObjectDef<TCGen05InstrDescriptorBlockScaledAttrs>()
+        .def_ro("d_dtype", &TCGen05InstrDescriptorBlockScaledAttrs::d_dtype)
+        .def_ro("a_dtype", &TCGen05InstrDescriptorBlockScaledAttrs::a_dtype)
+        .def_ro("b_dtype", &TCGen05InstrDescriptorBlockScaledAttrs::b_dtype)
+        .def_ro("sfa_dtype", 
&TCGen05InstrDescriptorBlockScaledAttrs::sfa_dtype)
+        .def_ro("sfb_dtype", 
&TCGen05InstrDescriptorBlockScaledAttrs::sfb_dtype)
+        .def_ro("M", &TCGen05InstrDescriptorBlockScaledAttrs::M)
+        .def_ro("N", &TCGen05InstrDescriptorBlockScaledAttrs::N)
+        .def_ro("K", &TCGen05InstrDescriptorBlockScaledAttrs::K)
+        .def_ro("trans_a", &TCGen05InstrDescriptorBlockScaledAttrs::trans_a)
+        .def_ro("trans_b", &TCGen05InstrDescriptorBlockScaledAttrs::trans_b)
+        .def_ro("n_cta_groups", 
&TCGen05InstrDescriptorBlockScaledAttrs::n_cta_groups,
+                refl::DefaultValue(1))
+        .def_ro("neg_a", &TCGen05InstrDescriptorBlockScaledAttrs::neg_a, 
refl::DefaultValue(false))
+        .def_ro("neg_b", &TCGen05InstrDescriptorBlockScaledAttrs::neg_b, 
refl::DefaultValue(false))
+        .def_ro("is_sparse", 
&TCGen05InstrDescriptorBlockScaledAttrs::is_sparse,
+                refl::DefaultValue(false));
+  }
+  
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("tirx.cuda.TCGen05InstrDescriptorBlockScaledAttrs",
+                                    TCGen05InstrDescriptorBlockScaledAttrs, 
AttrsNode);
+};
+
+}  // namespace tirx
+}  // namespace tvm
+
+#endif  // TVM_BACKEND_CUDA_OP_ATTRS_H_
diff --git a/src/backend/cuda/op/target_builtin.cc 
b/src/backend/cuda/op/target_builtin.cc
index 0306dfb38f..2ef90b9f13 100644
--- a/src/backend/cuda/op/target_builtin.cc
+++ b/src/backend/cuda/op/target_builtin.cc
@@ -31,6 +31,8 @@
 
 #include <string>
 
+#include "attrs.h"
+
 namespace tvm {
 namespace tirx {
 namespace builtin {
@@ -58,6 +60,9 @@ void RegisterCudaTargetBuiltins() {
   if (registered) return;
   registered = true;
 
+  TCGen05InstrDescriptorAttrs::RegisterReflection();
+  TCGen05InstrDescriptorBlockScaledAttrs::RegisterReflection();
+
   OpDef("tirx.tvm_load_matrix_sync")
       .signature(
           sig::arg("fragment", "The matrix fragment."), sig::arg<IntExpr>("m", 
"The M dimension."),
@@ -407,20 +412,12 @@ void RegisterDeviceIntrinsics() {
                           sig::arg("cond"))
       .set_attr<TFixedReturnType>("TFixedReturnType", PrimType::Int(64));
   RegisterDeviceIntrinsic(OpDef("tirx.cuda.tcgen05_encode_instr_descriptor"), 
"cuda",
-                          CallEffectKind::kOpaque, sig::arg("desc"), 
sig::arg("d_dtype"),
-                          sig::arg("a_dtype"), sig::arg("b_dtype"), 
sig::arg<IntExpr>("M"),
-                          sig::arg<IntExpr>("N"), sig::arg<IntExpr>("K"), 
sig::arg("trans_a"),
-                          sig::arg("trans_b"), 
sig::arg<IntExpr>("n_cta_groups"), sig::arg("neg_a"),
-                          sig::arg("neg_b"), sig::arg("sat_d"), 
sig::arg("is_sparse"))
+                          CallEffectKind::kOpaque, sig::arg("desc"),
+                          sig::call_attrs<TCGen05InstrDescriptorAttrs>())
       .set_attr<TFixedReturnType>("TFixedReturnType", PrimType::Void());
   
RegisterDeviceIntrinsic(OpDef("tirx.cuda.tcgen05_encode_instr_descriptor_block_scaled"),
 "cuda",
-                          CallEffectKind::kOpaque, sig::arg("desc"), 
sig::arg("d_dtype"),
-                          sig::arg("a_dtype"), sig::arg("b_dtype"), 
sig::arg("sfa_dtype"),
-                          sig::arg("sfb_dtype"), sig::arg("sfa_tmem_addr"),
-                          sig::arg("sfb_tmem_addr"), sig::arg<IntExpr>("M"), 
sig::arg<IntExpr>("N"),
-                          sig::arg<IntExpr>("K"), sig::arg("trans_a"), 
sig::arg("trans_b"),
-                          sig::arg<IntExpr>("n_cta_groups"), 
sig::arg("neg_a"), sig::arg("neg_b"),
-                          sig::arg("is_sparse"))
+                          CallEffectKind::kOpaque, sig::arg("desc"),
+                          
sig::call_attrs<TCGen05InstrDescriptorBlockScaledAttrs>())
       .set_attr<TFixedReturnType>("TFixedReturnType", PrimType::Void());
   RegisterDeviceIntrinsic(OpDef("tirx.cuda.tcgen05_encode_matrix_descriptor"), 
"cuda",
                           CallEffectKind::kOpaque, sig::arg("desc"), 
sig::arg("addr"),
diff --git a/src/relax/script/printer/call.cc b/src/relax/script/printer/call.cc
index a9f1f6a812..103757c025 100644
--- a/src/relax/script/printer/call.cc
+++ b/src/relax/script/printer/call.cc
@@ -242,10 +242,89 @@ TVM_FFI_STATIC_INIT_BLOCK() {
   }
 }
 
+// The name is published only for a callable implementing the shared Op 
contract.
+// Legacy/semantic wrappers retain their existing translators below.
+ffi::Optional<ExprDoc> StandardCallDocTranslate(DocTranslatorObj* d, const 
CallNode* call) {
+  auto maybe_op = call->op.as<Op>();
+  if (!maybe_op || !Op::HasAttrMap("TScriptStandardCall")) return std::nullopt;
+  const Op& op = maybe_op.value();
+  if (op->attrs_type_key.empty()) return std::nullopt;
+  static const auto& standard = 
Op::GetAttrMap<tirx::TScriptStandardCall>("TScriptStandardCall");
+  static const auto& names = 
Op::GetAttrMap<tirx::TScriptPrinterName>("TScriptPrinterName");
+  if (!standard.get(op, false) || !names.count(op) || names[op].empty()) 
return std::nullopt;
+  if (call->attrs.defined() ? op->attrs_type_key != call->attrs->GetTypeKey()
+                            : !op->attrs_type_key.empty()) {
+    return RawCall(d, call);
+  }
+  try {
+    op.Validate(call);
+  } catch (const ffi::Error&) {
+    return RawCall(d, call);
+  }
+  ffi::Array<ExprDoc> args;
+  for (const Expr& arg : call->args) {
+    args.push_back(MaterializeCallArgument(d, arg, d->Translate(arg).value()));
+  }
+  ffi::Array<ffi::String> keys;
+  ffi::Array<ExprDoc> values;
+  if (!call->ty_args.empty()) {
+    ffi::Array<ExprDoc> types;
+    for (const Type& type : call->ty_args) types.push_back(TypeValue(d, type, 
false));
+    keys.push_back("ty_args");
+    values.push_back(ListDoc(types));
+  }
+  if (call->attrs.defined()) {
+    std::vector<std::pair<ffi::String, ffi::Any>> fields;
+    ffi::reflection::ForEachFieldInfo(
+        TVMFFIGetTypeInfo(call->attrs->type_index()), [&](const 
TVMFFIFieldInfo* field) {
+          ffi::Any value = ffi::reflection::FieldGetter(field)(call->attrs);
+          // Factory defaults can have effects or change between constructions.
+          // Only a reflected literal default is safe to omit here.
+          if ((field->flags & kTVMFFIFieldFlagBitMaskHasDefault) &&
+              !(field->flags & kTVMFFIFieldFlagBitMaskDefaultFromFactory) &&
+              ffi::StructuralEqual()(
+                  value, 
ffi::AnyView::CopyFromTVMFFIAny(field->default_value_or_factory))) {
+            return;
+          }
+          fields.emplace_back(ffi::String(field->name), std::move(value));
+        });
+    // Without attribute keywords the named builder does not construct attrs.
+    // Preserve even an empty or entirely default-valued schema explicitly.
+    if (fields.empty()) return RawCall(d, call);
+    std::sort(fields.begin(), fields.end(),
+              [](const auto& a, const auto& b) { return a.first < b.first; });
+    for (const auto& [key, value] : fields) {
+      if (key == "ty" || key == "ret_ty" || key == "attrs" || key == "ty_args" 
|| key == "span" ||
+          key == "type_key" ||
+          std::any_of(op->args_info.begin(), op->args_info.end(),
+                      [&](const ArgumentInfo& info) { return info->name == 
key; })) {
+        return RawCall(d, call);
+      }
+      keys.push_back(key);
+      values.push_back(AnyValue(d, value));
+    }
+  }
+  bool omit_result = false;
+  try {
+    // Match construction without ty, rather than let inference read the 
stored result.
+    Call provisional =
+        Call::Unchecked(Type::Missing(), call->op, call->args, call->attrs, 
call->ty_args);
+    omit_result = ffi::StructuralEqual()(Call::ReinferType(provisional.get()), 
call->ty);
+  } catch (const ffi::Error&) {
+    // Missing information, unavailable inference, or errors require the exact 
stored type.
+  }
+  if (!omit_result) {
+    keys.push_back("ty");
+    values.push_back(TypeValue(d, call->ty));
+  }
+  return NamedCallCallee(names[op])->Call(args, keys, values);
+}
+
 ffi::Optional<ExprDoc> CallDefaultDocTranslate(DocTranslatorObj* d, 
ffi::AnyView input,
                                                const ffi::Object* destination) 
{
   const auto* call =
       ffi::details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
CallNode>(input);
+  if (auto doc = StandardCallDocTranslate(d, call)) return doc;
   if (auto op = call->op.as<Op>(); op && op.value()->name.find("relax.") == 0) 
{
     if (HasRelaxCallResult(call, destination)) {
       ffi::Array<ExprDoc> args;
diff --git a/src/tirx/script/printer/expr.cc b/src/tirx/script/printer/expr.cc
index 15a16e02c2..5403f771b7 100644
--- a/src/tirx/script/printer/expr.cc
+++ b/src/tirx/script/printer/expr.cc
@@ -212,58 +212,6 @@ TVM_FFI_STATIC_INIT_BLOCK() {
                                
FDocTranslate::FromNative<&CUDAFuncCallDocTranslate>());
 }
 
-template <bool block_scaled>
-ffi::Optional<ExprDoc> CUDAInstructionDescriptorDocTranslate(DocTranslatorObj* 
d,
-                                                             ffi::AnyView 
input,
-                                                             const 
ffi::Object*) {
-  const auto* call =
-      ffi::details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
CallNode>(input);
-  if (!CanTranslateExplicitResultCall(call) || call->args.size() != 
(block_scaled ? 17 : 14) ||
-      !ffi::StructuralEqual()(call->ty, PrimType::Void())) {
-    return RawCall(d, call);
-  }
-  Op op = call->op.as_or_throw<Op>();
-  if (op->args_info.size() != call->args.size()) return RawCall(d, call);
-  constexpr size_t optional_begin = block_scaled ? 13 : 9;
-  ffi::Array<ffi::String> keys;
-  ffi::Array<ExprDoc> values;
-  ExprDoc descriptor = d->Translate(call->args[0]).value();
-  for (size_t i = 1; i < call->args.size(); ++i) {
-    const Expr& arg = call->args[i];
-    if (i >= optional_begin) {
-      const auto* value = arg.as<IntImmNode>();
-      PrimType default_type = i == optional_begin ? PrimType::Int(32) : 
PrimType::Bool();
-      int64_t default_value = i == optional_begin ? 1 : 0;
-      if (value && value->value == default_value &&
-          ffi::StructuralEqual()(value->ty, default_type)) {
-        continue;
-      }
-    }
-    const auto* string = arg.as<StringImmNode>();
-    ExprDoc value = string ? LiteralDoc::Str(string->value, std::nullopt)
-                           : MaterializeCallArgument(d, arg, 
d->Translate(arg).value());
-    d->RecordOrigin(value, arg);
-    keys.push_back(op->args_info[i]->name);
-    values.push_back(value);
-  }
-  return NamespaceDoc("tirx")
-      ->Attr("cuda")
-      ->Attr("tcgen05")
-      ->Attr(block_scaled ? "encode_instr_descriptor_block_scaled" : 
"encode_instr_descriptor")
-      ->Call({descriptor}, keys, values);
-}
-
-TVM_FFI_STATIC_INIT_BLOCK() {
-  OpDef("tirx.cuda.tcgen05_encode_instr_descriptor")
-      .set_attr<FDocTranslate>(
-          kOpCallDocTranslate,
-          
FDocTranslate::FromNative<&CUDAInstructionDescriptorDocTranslate<false>>());
-  OpDef("tirx.cuda.tcgen05_encode_instr_descriptor_block_scaled")
-      .set_attr<FDocTranslate>(
-          kOpCallDocTranslate,
-          
FDocTranslate::FromNative<&CUDAInstructionDescriptorDocTranslate<true>>());
-}
-
 ffi::Optional<ExprDoc> LLVMIntrinsicDocTranslate(DocTranslatorObj* d, 
ffi::AnyView input,
                                                  const ffi::Object*) {
   const auto* call =
diff --git a/tests/python/tirx/codegen/test_tcgen05_descriptor_encoders.py 
b/tests/python/tirx/codegen/test_tcgen05_descriptor_encoders.py
index cd80b898f8..1162b143c8 100644
--- a/tests/python/tirx/codegen/test_tcgen05_descriptor_encoders.py
+++ b/tests/python/tirx/codegen/test_tcgen05_descriptor_encoders.py
@@ -125,8 +125,6 @@ def 
test_instr_descriptor_block_scaled_matches_runtime_encoder(
                 b_dtype=b_dtype,
                 sfa_dtype="float8_e8m0fnu",
                 sfb_dtype="float8_e8m0fnu",
-                sfa_tmem_addr=0,
-                sfb_tmem_addr=0,
                 M=m,
                 N=n,
                 K=k,
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 ac427a0dad..0215926190 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
@@ -3834,8 +3834,13 @@ def 
test_gemm_tcgen05_preserves_block_scale_tmem_lane_bases():
     sctx = DispatchContext(target, ExecScope("thread"), {}, {}, 
scope_kind="thread")
     script = gemm_async_tcgen05_impl(call, sctx).script()
 
-    assert "T.cuda.get_tmem_addr(T.uint32(256), 1, 0)" in script
-    assert "T.cuda.get_tmem_addr(T.uint32(320), 2, 0)" in script
+    # Check the addresses consumed by MMA, rather than the former unused
+    # initial-address operands of the instruction descriptor encoder.
+    assert "sfa_linear % 128 // 4 + 1" in script
+    assert "sfb_linear % 128 // 4 + 2" in script
+    assert "T.cuda.get_tmem_addr(T.uint32(256), sfa_tlane, sfa_tcol // 4)" in 
script
+    assert "T.cuda.get_tmem_addr(T.uint32(320), sfb_tlane, sfb_tcol // 4)" in 
script
+    assert "descI_local, sfa_addr, sfb_addr, should_accum" in script
 
 
 @pytest.mark.parametrize(
diff --git a/tests/python/tirx/script/test_tirx_script_printer.py 
b/tests/python/tirx/script/test_tirx_script_printer.py
index 3504ed03c0..3e541832f3 100644
--- a/tests/python/tirx/script/test_tirx_script_printer.py
+++ b/tests/python/tirx/script/test_tirx_script_printer.py
@@ -785,7 +785,7 @@ def test_printer_ptx_more():
             sat_d=False,
             is_sparse=False,
         ),
-        'd: T.handle = T.handle()\nT.cuda.tcgen05.encode_instr_descriptor(d, 
d_dtype="f16", a_dtype="f16", b_dtype="f16", M=16, N=16, K=16, 
trans_a=T.bool(True), trans_b=T.bool(False))',  # noqa: E501
+        'd: T.handle = T.handle()\nT.cuda.tcgen05_encode_instr_descriptor(d, 
K=16, M=16, N=16, a_dtype="f16", b_dtype="f16", d_dtype="f16", trans_a=True, 
trans_b=False)',  # noqa: E501
     )
     _assert_namespace_print(
         cuda_op.cuda_tcgen05_encode_instr_descriptor_block_scaled(
@@ -795,8 +795,6 @@ def test_printer_ptx_more():
             b_dtype="f16",
             sfa_dtype="f16",
             sfb_dtype="f16",
-            sfa_tmem_addr=a,
-            sfb_tmem_addr=b,
             M=16,
             N=16,
             K=16,
@@ -808,9 +806,7 @@ def test_printer_ptx_more():
             neg_b=False,
         ),
         "d: T.handle = T.handle()\n"
-        "a: T.handle = T.handle()\n"
-        "b: T.handle = T.handle()\n"
-        'T.cuda.tcgen05.encode_instr_descriptor_block_scaled(d, d_dtype="f16", 
a_dtype="f16", b_dtype="f16", sfa_dtype="f16", sfb_dtype="f16", 
sfa_tmem_addr=a, sfb_tmem_addr=b, M=16, N=16, K=16, trans_a=T.bool(True), 
trans_b=T.bool(False), is_sparse=T.bool(True))',  # noqa: E501
+        'T.cuda.tcgen05_encode_instr_descriptor_block_scaled(d, K=16, M=16, 
N=16, a_dtype="f16", b_dtype="f16", d_dtype="f16", is_sparse=True, 
sfa_dtype="f16", sfb_dtype="f16", trans_a=True, trans_b=False)',  # noqa: E501
     )
 
 

Reply via email to