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

tlopex 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 e1d85f64c0 [REFACTOR][TIRx] Declare dynamic shared memory with a CUDA 
leaf op (#20563)
e1d85f64c0 is described below

commit e1d85f64c0d8027da42713eede5574755fc7e2ed
Author: Tianqi Chen <[email protected]>
AuthorDate: Tue Oct 6 10:42:24 2026 -0400

    [REFACTOR][TIRx] Declare dynamic shared memory with a CUDA leaf op (#20563)
    
    Replace the dynamic shared-memory size attribute with
    `T.cuda.dyn_smem_bytes(bytes)`, a CUDA declaration whose signature
    requires an integer constant. `SMEMPool.commit()` emits the declaration,
    and host/device splitting collects its byte count for the runtime launch
    before removing it from the device body.
    
    Preserve pool high-water accounting, explicit-size precedence,
    allocation-extent fallback, and dynamic shared-memory launch argument
    ordering. Migrate direct declaration sites to the CUDA spelling.
---
 python/tvm/backend/cuda/lang/alloc_pool.py         |  9 ++---
 src/backend/cuda/op/target_builtin.cc              |  9 +++++
 src/tirx/transform/split_host_device.cc            | 40 +++++++++++-----------
 .../tile_primitive/cuda/copy_async/test_tma.py     |  8 ++---
 4 files changed, 36 insertions(+), 30 deletions(-)

diff --git a/python/tvm/backend/cuda/lang/alloc_pool.py 
b/python/tvm/backend/cuda/lang/alloc_pool.py
index 119b4000d7..6f305f731b 100644
--- a/python/tvm/backend/cuda/lang/alloc_pool.py
+++ b/python/tvm/backend/cuda/lang/alloc_pool.py
@@ -477,7 +477,7 @@ class SMEMPool:
             self.max_offset = max(self.max_offset, self.offset)
 
     def commit(self, size=None):
-        """Emit pool size annotation into the IR.
+        """Emit the dynamic shared memory byte-count declaration into the IR.
 
         Must be called after all ``alloc()`` / ``move_base_to()`` calls.
 
@@ -495,10 +495,7 @@ class SMEMPool:
             f"the pool high-water mark ({self.max_offset})"
         )
         import tvm.tirx
+        from tvm.backend.cuda.op import dyn_smem_bytes
         from tvm.tirx.script.ir_builder.parser_protocol import add_to_parent
 
-        add_to_parent(
-            tvm.tirx.AttrStmt(
-                0, "tirx.dyn_smem_bytes", tvm.tirx.IntImm("int64", resolved), 
tvm.tirx.Evaluate(0)
-            )
-        )
+        
add_to_parent(tvm.tirx.Evaluate(dyn_smem_bytes(tvm.tirx.IntImm("int64", 
resolved))))
diff --git a/src/backend/cuda/op/target_builtin.cc 
b/src/backend/cuda/op/target_builtin.cc
index 7eaa19106e..0c33c27bb5 100644
--- a/src/backend/cuda/op/target_builtin.cc
+++ b/src/backend/cuda/op/target_builtin.cc
@@ -219,6 +219,15 @@ void RegisterCudaTargetBuiltins() {
       .set_attr<TIRxOpCategory>("TIRxOpCategory", ffi::String("builtin"))
       .set_attr<TCallEffectKind>("TCallEffectKind", 
static_cast<int64_t>(CallEffectKind::kOpaque));
 
+  OpDef("tirx.cuda.dyn_smem_bytes",
+        "Declare the dynamic shared memory size in bytes for the kernel 
launch.")
+      .signature(sig::arg<IntImm>("bytes", "The constant byte count."))
+      .set_attr<TFixedReturnType>("TFixedReturnType", PrimType::Void())
+      .set_attr<TScriptPrinterName>("TScriptPrinterName", 
ffi::String("tirx.cuda.dyn_smem_bytes"))
+      .set_attr<TIRxOpCategory>("TIRxOpCategory", ffi::String("builtin"))
+      .set_attr<TCallEffectKind>("TCallEffectKind",
+                                 
static_cast<int64_t>(CallEffectKind::kEmbedInfo));
+
   RegisterDeviceIntrinsics();
 }
 
diff --git a/src/tirx/transform/split_host_device.cc 
b/src/tirx/transform/split_host_device.cc
index 5858c380f7..f65c79ba63 100644
--- a/src/tirx/transform/split_host_device.cc
+++ b/src/tirx/transform/split_host_device.cc
@@ -444,7 +444,7 @@ class DeviceInfoCollector : public StmtExprVisitor {
           tvm::runtime::launch_param::kUseRequiredBlockDimension);
     }
     // The dynamic shared memory is required to be the last of the kernel
-    // launch parameters. An explicit tirx.dyn_smem_bytes declaration wins;
+    // launch parameters. An explicit tirx.cuda.dyn_smem_bytes declaration 
wins;
     // otherwise fall back to the size inferred from the allocation extent.
     // A zero-extent allocation is a pool-style extern placeholder, so having
     // neither a declaration nor a usable extent is an authoring error.
@@ -453,7 +453,7 @@ class DeviceInfoCollector : public StmtExprVisitor {
       TVM_FFI_ICHECK(!(inferred && inferred->value == 0))
           << "PrimFunc " << gvar->name_hint
           << " allocates dynamic shared memory with a placeholder extent but 
does not declare "
-             "its size; annotate the kernel with tirx.dyn_smem_bytes 
(SMEMPool.commit() emits "
+             "its size; annotate the kernel with tirx.cuda.dyn_smem_bytes 
(SMEMPool.commit() emits "
              "it).";
       collector->dyn_shmem_size = collector->inferred_shmem_size_;
     }
@@ -482,7 +482,7 @@ class DeviceInfoCollector : public StmtExprVisitor {
     if (launch_param == 
tvm::runtime::launch_param::kUseDynamicSharedMemoryTag) {
       TVM_FFI_ICHECK(dyn_shmem_size.has_value())
           << "Compute kernel requires launch parameter \"" << launch_param
-          << "\", but PrimFunc did not declare tirx.dyn_smem_bytes.";
+          << "\", but PrimFunc did not declare tirx.cuda.dyn_smem_bytes.";
       return dyn_shmem_size.value();
     }
 
@@ -518,17 +518,15 @@ class DeviceInfoCollector : public StmtExprVisitor {
     return StmtExprVisitor::Visit_(op);
   }
 
-  ffi::Optional<VisitInterrupt> Visit_(const AttrStmtNode* op) final {
-    if (op->attr_key == "tirx.dyn_smem_bytes") {
-      // Kernel-level declaration of the dynamic shared memory launch size.
-      // The backing shared.dyn allocation is an extern placeholder; this
-      // attribute is the single source of truth for the launch parameter.
+  ffi::Optional<VisitInterrupt> Visit_(const EvaluateNode* op) final {
+    static const Op dyn_smem_bytes = Op::Get("tirx.cuda.dyn_smem_bytes");
+    if (const auto* call = op->value.as<CallNode>(); call && 
call->op.same_as(dyn_smem_bytes)) {
+      // The declaration supplies the launch size even when the backing
+      // shared.dyn allocation is an extern placeholder.
       TVM_FFI_ICHECK(!dyn_shmem_size.has_value())
-          << "Only one tirx.dyn_smem_bytes declaration is allowed per kernel.";
-      TVM_FFI_ICHECK(op->value.as<IntImmNode>()) << "tirx.dyn_smem_bytes must 
be an IntImm";
-      dyn_shmem_size = op->value.as_or_throw<PrimExpr>();
+          << "Only one tirx.cuda.dyn_smem_bytes declaration is allowed per 
kernel.";
+      dyn_shmem_size = call->args[0].as_or_throw<IntImm>();
     }
-
     return StmtExprVisitor::Visit_(op);
   }
 
@@ -569,7 +567,7 @@ class DeviceInfoCollector : public StmtExprVisitor {
       saw_dyn_shared_alloc_ = true;
 
       // Fallback launch size inferred from the allocation extent, used when
-      // no tirx.dyn_smem_bytes declaration is present (e.g. s_tir schedules
+      // no tirx.cuda.dyn_smem_bytes declaration is present (e.g. s_tir 
schedules
       // allocate shared.dyn with a concrete extent). A zero extent is a
       // pool-style extern placeholder and carries no size information.
       tvm::Tuple shape = call->args[0].as_or_throw<tvm::Tuple>();
@@ -603,7 +601,7 @@ class DeviceInfoCollector : public StmtExprVisitor {
   // Whether a shared.dyn allocation was seen.
   bool saw_dyn_shared_alloc_{false};
   // Launch size inferred from the allocation extent (fallback when no
-  // tirx.dyn_smem_bytes declaration is present).
+  // tirx.cuda.dyn_smem_bytes declaration is present).
   ffi::Optional<PrimExpr> inferred_shmem_size_{std::nullopt};
   // Flag-only launch attributes requested by the original PrimFunc.
   bool use_programmatic_dependent_launch_{false};
@@ -729,7 +727,7 @@ class DeviceKernelMutator : public StmtExprMutator {
         write_ptr->body = ReturnRemover::Apply(write_ptr->body.value(), 
!preserve_early_returns);
         // The dyn-smem size declaration was consumed by DeviceInfoCollector;
         // it has no meaning inside the kernel body.
-        class StripDynSmemAttr : public StmtExprMutator {
+        class StripDynSmemDeclaration : public StmtExprMutator {
          public:
           using StmtExprMutator::Mutate;
           using StmtExprMutator::Mutate_;
@@ -738,14 +736,16 @@ class DeviceKernelMutator : public StmtExprMutator {
             return StmtExprMutator::Mutate(input, inplace_mode);
           }
 
-          UnchangedOr<Stmt> Mutate_(const AttrStmtNode* op, InplaceMode 
inplace_mode) final {
-            if (op->attr_key == "tirx.dyn_smem_bytes") {
-              return Mutate(op->body, inplace_mode).ValueOrUnchanged(op->body);
+          UnchangedOr<Stmt> Mutate_(const EvaluateNode* op, InplaceMode 
inplace_mode) final {
+            static const Op dyn_smem_bytes = 
Op::Get("tirx.cuda.dyn_smem_bytes");
+            if (const auto* call = op->value.as<CallNode>();
+                call && call->op.same_as(dyn_smem_bytes)) {
+              return Evaluate(0);
             }
-            return StmtExprMutator::Mutate_(op, inplace_mode);
+            return ffi::Unchanged();
           }
         };
-        write_ptr->body = ffi::make_object<StripDynSmemAttr>()
+        write_ptr->body = ffi::make_object<StripDynSmemDeclaration>()
                               ->Mutate(write_ptr->body, InplaceMode::kAllow)
                               .ValueOrUnchanged(write_ptr->body);
       }
diff --git 
a/tests/python/tirx/operator/tile_primitive/cuda/copy_async/test_tma.py 
b/tests/python/tirx/operator/tile_primitive/cuda/copy_async/test_tma.py
index 1836719c1c..86636c20dd 100644
--- a/tests/python/tirx/operator/tile_primitive/cuda/copy_async/test_tma.py
+++ b/tests/python/tirx/operator/tile_primitive/cuda/copy_async/test_tma.py
@@ -1532,7 +1532,7 @@ def rank_change(A: T.Tensor((8, 8), 'float16')):
     T.cta_id([1])
     tid = T.thread_id([1])
     dyn = T.alloc_tensor((65,), "uint64", scope="shared.dyn")
-    T.attr({"tirx.dyn_smem_bytes": 65 * 8})
+    T.cuda.dyn_smem_bytes(65 * 8)
     A_smem = T.decl_tensor((64,), "float16", dyn.data, 
layout=T.TileLayout(T.S[64]))
     mbar = T.decl_tensor((1,), "uint64", dyn.data, elem_offset=16)
     if tid == 0:
@@ -1559,7 +1559,7 @@ def selector_gather(
     T.cta_id([1])
     tid = T.thread_id([128])
     dyn = T.alloc_tensor((520,), "uint64", scope="shared.dyn")
-    T.attr({"tirx.dyn_smem_bytes": 520 * 8})
+    T.cuda.dyn_smem_bytes(520 * 8)
     A_smem = T.decl_tensor(
         (4, 64), "bfloat16", dyn.data, layout=T.TileLayout(T.S[4, 64])
     )
@@ -1730,7 +1730,7 @@ def _build_sparse_decode_qo_tma_regression():
         T.cta_id([1])
         tid = T.thread_id([128])
         dyn = T.alloc_tensor((shared_bytes + 8,), "uint8", scope="shared.dyn")
-        T.attr({"tirx.dyn_smem_bytes": shared_bytes + 8})
+        T.cuda.dyn_smem_bytes(shared_bytes + 8)
         q_smem = T.decl_tensor(
             (64, 512), "bfloat16", dyn.data, scope="shared.dyn", 
layout=q_layout
         )
@@ -2004,7 +2004,7 @@ def _build_selector_gather_gpu_kernel(dtype="float16"):
         T.cta_id([1])
         tid = T.thread_id([128])
         dyn = T.alloc_tensor((shared_bytes + 64,), "uint8", scope="shared.dyn")
-        T.attr({"tirx.dyn_smem_bytes": shared_bytes + 64})
+        T.cuda.dyn_smem_bytes(shared_bytes + 64)
         A_smem = T.decl_tensor(
             (4, cols), dtype, dyn.data, layout=T.TileLayout(T.S[4, cols])
         )

Reply via email to