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 7e27712065 [TIRx][CUDA] Configure kernel constraints with CUDA leaf 
ops (#20565)
7e27712065 is described below

commit 7e27712065b5c6ce9e16dcdad84ad32623798e88
Author: Tianqi Chen <[email protected]>
AuthorDate: Tue Oct 6 13:25:56 2026 -0400

    [TIRx][CUDA] Configure kernel constraints with CUDA leaf ops (#20565)
    
    Represent minimum blocks per SM, maximum blocks per cluster, per-thread
    register limits, and required block/cluster dimensions as CUDA-owned
    leaf declarations. Preserve the declarations through lowering and
    collect their settings during body generation before emitting matching
    forward and definition signatures.
    
    Recognize required block dimensions directly when collecting host launch
    metadata.
---
 docs/tirx/native_basics/cuda/functions.rst         |  28 ++--
 include/tvm/tirx/function.h                        |  37 -----
 src/backend/cuda/codegen/codegen_cuda.cc           | 184 ++++++++++++++-------
 src/backend/cuda/codegen/codegen_cuda.h            |  13 ++
 src/backend/cuda/op/target_builtin.cc              |  15 ++
 src/target/source/codegen_c.cc                     |  19 ++-
 src/target/source/codegen_c.h                      |   6 +
 src/tirx/transform/split_host_device.cc            | 116 +------------
 .../test_tir_transform_split_host_device.py        |  16 +-
 tests/python/tirx/codegen/test_codegen_cuda.py     |  46 ++----
 10 files changed, 218 insertions(+), 262 deletions(-)

diff --git a/docs/tirx/native_basics/cuda/functions.rst 
b/docs/tirx/native_basics/cuda/functions.rst
index c09ec847a3..4debc34cf3 100644
--- a/docs/tirx/native_basics/cuda/functions.rst
+++ b/docs/tirx/native_basics/cuda/functions.rst
@@ -287,13 +287,12 @@ By default, the block size also drives the kernel's 
``__launch_bounds__``. The
 first argument (max threads per block) is set automatically from the thread
 extent. To also set the second argument — the minimum blocks per SM, an
 occupancy hint — add
-``Tx.attr({"tirx.launch_bounds_min_blocks_per_sm": N})`` in the device region 
(note:
-``Tx.attr``, not ``func_attr``):
+``Tx.cuda.launch_bounds_min_blocks_per_sm(N)`` in the device region:
 
 .. code-block:: python
 
     Tx.device_entry()
-    Tx.attr({"tirx.launch_bounds_min_blocks_per_sm": 2})   # second 
launch-bounds arg
+    Tx.cuda.launch_bounds_min_blocks_per_sm(2)   # second launch-bounds arg
     bx = Tx.cta_id([1]); tx = Tx.thread_id([256])
     ...
 
@@ -301,16 +300,23 @@ occupancy hint — add
 
     extern "C" __global__ void __launch_bounds__(256, 2) scale_kernel(...) { 
... }
 
-Without the attr the second argument is omitted (just 
``__launch_bounds__(256)``).
+Without this declaration the second argument is omitted (just 
``__launch_bounds__(256)``).
+
+``Tx.cuda.launch_bounds_max_blocks_per_cluster(N)`` supplies the third operand
+and requires a minimum-blocks declaration. 
``Tx.cuda.max_registers_per_thread(N)``
+emits ``__maxnreg__(N)`` and cannot accompany launch bounds. These declarations
+accept positive integer constants. Matching repetitions are allowed; 
conflicting
+values are rejected. They configure the containing kernel and produce no 
runtime
+instructions at their textual position.
 
 Some kernels require an exact block and cluster shape instead of an advisory
-maximum. Set ``tirx.required_block_size`` to ``1`` to make the thread and
-cluster extents a compile-time launch contract:
+maximum. Use ``Tx.cuda.required_block_size`` with the three thread dimensions 
followed
+by the three cluster dimensions to declare a compile-time launch contract:
 
 .. code-block:: python
 
     Tx.device_entry()
-    Tx.attr({"tirx.required_block_size": 1})
+    Tx.cuda.required_block_size(128, 1, 1, 1, 2, 1)
     bx, by = Tx.cta_id([4, 2])
     _, cy = Tx.cta_id_in_cluster([1, 2])
     tx = Tx.thread_id([128])
@@ -321,15 +327,15 @@ cluster extents a compile-time launch contract:
     extern "C" __global__ void __block_size__((128, 1, 1), (1, 2, 1)) 
kernel(...) { ... }
 
 This requires CUDA Toolkit 13 or newer. All thread and cluster dimensions must
-be static; CUDA lowers ``__block_size__`` to PTX ``.reqntid`` and checks the
+be positive constants matching the declared launch extents; CUDA lowers 
``__block_size__`` to PTX ``.reqntid`` and checks the
 same dimensions at launch. A preferred cluster dimension must be absent or
 equal to the required cluster dimension, and each logical block-grid dimension
 must be divisible by its cluster dimension.
 
-``tirx.required_block_size`` can be combined with the launch-bounds attributes
+``Tx.cuda.required_block_size`` can be combined with the launch-bounds 
declarations
 when an occupancy hint is also needed; code generation then emits both
 ``__block_size__`` and ``__launch_bounds__``. It cannot be combined with
-``tirx.max_registers``.
+``Tx.cuda.max_registers_per_thread``.
 
 At run time the kernel is launched through the **CUDA Driver API**. TVM's CUDA
 runtime loads the module (``cuModuleLoadData``), fetches the function
@@ -338,7 +344,7 @@ runtime loads the module (``cuModuleLoadData``), fetches 
the function
 the config carries a list of launch *attributes* — the thread-block **cluster
 dimension** and **preferred cluster dimension** (Hopper/Blackwell), plus 
optional
 programmatic-dependent-launch and cooperative-launch flags. Kernels with
-``tirx.required_block_size`` instead use CUDA's required-block sentinel; their
+``Tx.cuda.required_block_size`` instead use CUDA's required-block sentinel; 
their
 compile-time cluster shape replaces the ordinary runtime cluster attribute. In
 outline, ``src/backend/cuda/runtime/cuda_module.cc`` follows this path:
 
diff --git a/include/tvm/tirx/function.h b/include/tvm/tirx/function.h
index 29204fe7a0..e6e68a0954 100644
--- a/include/tvm/tirx/function.h
+++ b/include/tvm/tirx/function.h
@@ -225,43 +225,6 @@ namespace attr {
  */
 constexpr const char* kKernelLaunchParams = "tirx.kernel_launch_params";
 
-/*!
- * \brief CUDA launch bound minimum CTAs per SM.
- *
- * Type: IntImm
- */
-constexpr const char* kLaunchBoundsMinBlocksPerSM = 
"tirx.launch_bounds_min_blocks_per_sm";
-
-/*!
- * \brief CUDA launch bound maximum CTAs per cluster.
- *
- * Type: IntImm
- */
-constexpr const char* kLaunchBoundsMaxBlocksPerCluster =
-    "tirx.launch_bounds_max_blocks_per_cluster";
-
-/*!
- * \brief CUDA maximum registers per thread.
- *
- * Emits the CUDA 13 ``__maxnreg__`` kernel qualifier.  This attribute is
- * mutually exclusive with the launch-bounds attributes.
- *
- * Type: IntImm
- */
-constexpr const char* kMaxRegisters = "tirx.max_registers";
-
-/*!
- * \brief Require CUDA to use the statically-declared block and cluster 
dimensions.
- *
- * Emits the CUDA 13 ``__block_size__`` kernel qualifier.  Unlike
- * ``__launch_bounds__``, this is an exact launch contract: CUDA derives the
- * PTX ``.reqntid`` directive from the thread extents, and interprets the
- * launch grid in clusters using the cluster-CTA extents.
- *
- * Type: IntImm (must be 1)
- */
-constexpr const char* kRequiredBlockSize = "tirx.required_block_size";
-
 /*!
  * \brief Whether to set noalias rule on the function arguments.
  *
diff --git a/src/backend/cuda/codegen/codegen_cuda.cc 
b/src/backend/cuda/codegen/codegen_cuda.cc
index e68687cd67..01c1511fee 100644
--- a/src/backend/cuda/codegen/codegen_cuda.cc
+++ b/src/backend/cuda/codegen/codegen_cuda.cc
@@ -175,6 +175,11 @@ CodeGenCUDA::CodeGenCUDA(Target target) : target(target) { 
restrict_keyword_ = "
 
 void CodeGenCUDA::PrintFunctionSignature(const ffi::String& function_name, 
const PrimFunc& func,
                                          std::ostream& os) {
+  PrintFunctionPrefix(func, os);
+  CodeGenC::PrintFunctionSignature(function_name, func, os);
+}
+
+void CodeGenCUDA::PrintFunctionPrefix(const PrimFunc& func, std::ostream& os) {
   CallingConv calling_conv =
       func->GetAttr<CallingConv>(tvm::attr::kCallingConv, 
CallingConv::kDefault).value();
   in_kernel_launch_ = (calling_conv == CallingConv::kDeviceKernelLaunch);
@@ -186,7 +191,6 @@ void CodeGenCUDA::PrintFunctionSignature(const ffi::String& 
function_name, const
     TVM_FFI_THROW(InternalError) << "Unsupported calling convention for cuda 
codegen: "
                                  << static_cast<int>(calling_conv);
   }
-  CodeGenC::PrintFunctionSignature(function_name, func, os);
 }
 
 class ThreadIdxExtractor : public tirx::StmtExprVisitor {
@@ -239,71 +243,98 @@ class ThreadIdxExtractor : public tirx::StmtExprVisitor {
   PrimExpr clusterCtaIdx_z_ext = IntImm::Int32(1);
 };
 
-void CodeGenCUDA::PrintExtraAttrs(const PrimFunc& f, std::ostream& os) {
+void CodeGenCUDA::InitFuncState(const PrimFunc& func) {
+  CodeGenC::InitFuncState(func);
+  min_blocks_per_sm_.reset();
+  max_blocks_per_cluster_.reset();
+  max_registers_per_thread_.reset();
+  required_block_size_.reset();
+  in_kernel_launch_ =
+      func->GetAttr<CallingConv>(tvm::attr::kCallingConv, 
CallingConv::kDefault).value() ==
+      CallingConv::kDeviceKernelLaunch;
+  // Thread metadata is also needed while emitting cluster index accesses.
   auto extractor = ffi::make_object<ThreadIdxExtractor>();
-  extractor->Visit(f->body);
+  extractor->Visit(func->body);
+  launch_dimensions_ = {extractor->threadIdx_x_ext,     
extractor->threadIdx_y_ext,
+                        extractor->threadIdx_z_ext,     
extractor->clusterCtaIdx_x_ext,
+                        extractor->clusterCtaIdx_y_ext, 
extractor->clusterCtaIdx_z_ext};
   sym::Analyzer analyzer;
-  PrimExpr threadIdx_ext = analyzer->Simplify(
-      extractor->threadIdx_x_ext * extractor->threadIdx_y_ext * 
extractor->threadIdx_z_ext);
-  PrimExpr cluster_cta_yz_ext =
-      analyzer->Simplify(extractor->clusterCtaIdx_y_ext * 
extractor->clusterCtaIdx_z_ext);
-  if (const IntImmNode* const cluster_cta_yz_ext_int = 
cluster_cta_yz_ext.as<IntImmNode>()) {
-    cluster_cta_x_is_linear_rank_ = cluster_cta_yz_ext_int->value == 1;
-  } else {
-    cluster_cta_x_is_linear_rank_ = false;
-  }
-  auto max_registers = f->GetAttr<int64_t>(tirx::attr::kMaxRegisters);
-  auto required_block_size = 
f->GetAttr<int64_t>(tirx::attr::kRequiredBlockSize);
-  if (required_block_size.has_value()) {
-    TVM_FFI_ICHECK_EQ(required_block_size.value(), 1);
-    TVM_FFI_ICHECK(!max_registers.has_value())
-        << tirx::attr::kRequiredBlockSize << " cannot be combined with maximum 
registers";
-    const auto* tx = extractor->threadIdx_x_ext.as<IntImmNode>();
-    const auto* ty = extractor->threadIdx_y_ext.as<IntImmNode>();
-    const auto* tz = extractor->threadIdx_z_ext.as<IntImmNode>();
-    const auto* cx = extractor->clusterCtaIdx_x_ext.as<IntImmNode>();
-    const auto* cy = extractor->clusterCtaIdx_y_ext.as<IntImmNode>();
-    const auto* cz = extractor->clusterCtaIdx_z_ext.as<IntImmNode>();
-    TVM_FFI_ICHECK(tx && ty && tz && cx && cy && cz)
-        << tirx::attr::kRequiredBlockSize << " requires static thread and 
cluster dimensions";
-    os << " __block_size__((" << tx->value << ", " << ty->value << ", " << 
tz->value << "), ("
-       << cx->value << ", " << cy->value << ", " << cz->value << "))";
-    if 
(!f->GetAttr<int64_t>(tirx::attr::kLaunchBoundsMinBlocksPerSM).has_value()) {
-      
TVM_FFI_ICHECK(!f->GetAttr<int64_t>(tirx::attr::kLaunchBoundsMaxBlocksPerCluster).has_value())
-          << tirx::attr::kLaunchBoundsMaxBlocksPerCluster << " requires "
-          << tirx::attr::kLaunchBoundsMinBlocksPerSM;
-      return;
-    }
+  cluster_cta_x_is_linear_rank_ =
+      is_one(analyzer->Simplify(launch_dimensions_[4] * 
launch_dimensions_[5]));
+}
+
+void CodeGenCUDA::DeclareFunction(const GlobalVar& gvar, const PrimFunc& func) 
{
+  if (!RegisterFunctionName(gvar, func)) return;
+  // Definitions supply their body-derived qualifiers in AddFunction.
+  if (!func->body.has_value()) {
+    InitFuncState(func);
+    PrintFunctionSignature(GetFunctionName(gvar), func, fwd_decl_stream);
+    fwd_decl_stream << ";\n";
   }
-  if (max_registers.has_value()) {
-    TVM_FFI_ICHECK_GT(max_registers.value(), 0);
-    
TVM_FFI_ICHECK(!f->GetAttr<int64_t>(tirx::attr::kLaunchBoundsMinBlocksPerSM).has_value()
 &&
-                   
!f->GetAttr<int64_t>(tirx::attr::kLaunchBoundsMaxBlocksPerCluster).has_value())
-        << tirx::attr::kMaxRegisters << " cannot be combined with CUDA launch 
bounds";
-    os << " __maxnreg__(" << max_registers.value() << ")";
+}
+
+void CodeGenCUDA::AddFunction(const GlobalVar& gvar, const PrimFunc& func) {
+  DeclareFunction(gvar, func);
+  if (!func->body.has_value()) return;
+  InitFuncState(func);
+  std::ostringstream parameters;
+  PrintFunctionParameters(func, parameters);
+
+  // Generate the body once, retaining the settings declared by its leaf ops.
+  std::ostringstream previous_functions;
+  stream.swap(previous_functions);
+  stream << " {\n";
+  PreFunctionBody(func);
+  int func_scope = BeginScope();
+  PrintStmt(func->body.value());
+  EndScope(func_scope);
+  PrintIndent();
+  stream << "}\n\n";
+  std::string body = stream.str();
+  stream.swap(previous_functions);
+
+  std::ostringstream signature;
+  PrintFunctionPrefix(func, signature);
+  PrintType(func->ret_type, signature);
+  PrintExtraAttrs(func, signature);
+  signature << " " << GetFunctionName(gvar) << parameters.str();
+  fwd_decl_stream << signature.str() << ";\n";
+  stream << signature.str() << body;
+}
+
+void CodeGenCUDA::PrintExtraAttrs(const PrimFunc& f, std::ostream& os) {
+  TVM_FFI_ICHECK(!max_blocks_per_cluster_ || min_blocks_per_sm_)
+      << "CUDA maximum blocks per cluster requires minimum blocks per SM";
+  TVM_FFI_ICHECK(!max_registers_per_thread_ || (!min_blocks_per_sm_ && 
!max_blocks_per_cluster_))
+      << "Maximum registers per thread cannot be combined with CUDA launch 
bounds";
+  TVM_FFI_ICHECK(!max_registers_per_thread_ || !required_block_size_)
+      << "Required block size cannot be combined with maximum registers";
+  if (required_block_size_) {
+    for (size_t i = 0; i < launch_dimensions_.size(); ++i) {
+      const auto* dim = launch_dimensions_[i].as<IntImmNode>();
+      TVM_FFI_ICHECK(dim) << "Required block size requires static thread and 
cluster dimensions";
+      TVM_FFI_ICHECK_EQ(dim->value, (*required_block_size_)[i])
+          << "Required block size must agree with declared launch extents";
+    }
+    const auto& dims = *required_block_size_;
+    os << " __block_size__((" << dims[0] << ", " << dims[1] << ", " << dims[2] 
<< "), (" << dims[3]
+       << ", " << dims[4] << ", " << dims[5] << "))";
+    if (!min_blocks_per_sm_) return;
+  }
+  if (max_registers_per_thread_) {
+    os << " __maxnreg__(" << *max_registers_per_thread_ << ")";
     return;
   }
-  if (const IntImmNode* const threadIdx_ext_int = 
threadIdx_ext.as<IntImmNode>()) {
-    if (threadIdx_ext_int->value == 1) {
-      // unable to extract the number of threads per block, hence directly 
return
-      return;
-    }
-    auto min_blocks_per_sm = 
f->GetAttr<int64_t>(tirx::attr::kLaunchBoundsMinBlocksPerSM);
-    auto max_blocks_per_cluster = 
f->GetAttr<int64_t>(tirx::attr::kLaunchBoundsMaxBlocksPerCluster);
-    if (min_blocks_per_sm.has_value()) {
-      TVM_FFI_ICHECK_GT(min_blocks_per_sm.value(), 0);
-      os << " __launch_bounds__(" << threadIdx_ext_int->value << ", " << 
min_blocks_per_sm.value();
-      if (max_blocks_per_cluster.has_value()) {
-        TVM_FFI_ICHECK_GT(max_blocks_per_cluster.value(), 0);
-        os << ", " << max_blocks_per_cluster.value();
-      }
-      os << ")";
-    } else {
-      TVM_FFI_ICHECK(!max_blocks_per_cluster.has_value())
-          << tirx::attr::kLaunchBoundsMaxBlocksPerCluster << " requires "
-          << tirx::attr::kLaunchBoundsMinBlocksPerSM;
-      os << " __launch_bounds__(" << threadIdx_ext_int->value << ")";
+  sym::Analyzer analyzer;
+  PrimExpr threads =
+      analyzer->Simplify(launch_dimensions_[0] * launch_dimensions_[1] * 
launch_dimensions_[2]);
+  if (const auto* count = threads.as<IntImmNode>(); count && count->value != 
1) {
+    os << " __launch_bounds__(" << count->value;
+    if (min_blocks_per_sm_) {
+      os << ", " << *min_blocks_per_sm_;
+      if (max_blocks_per_cluster_) os << ", " << *max_blocks_per_cluster_;
     }
+    os << ")";
   }
 }
 
@@ -1645,6 +1676,39 @@ void CodeGenCUDA::DispatchAllocTensor(const BindNode* 
op, const CallNode* buffer
 }
 
 void CodeGenCUDA::Dispatch_(const EvaluateNode* op) {
+  if (const auto* call = op->value.as<CallNode>()) {
+    static const Op min_blocks = 
Op::Get("tirx.cuda.launch_bounds_min_blocks_per_sm");
+    static const Op max_blocks = 
Op::Get("tirx.cuda.launch_bounds_max_blocks_per_cluster");
+    static const Op max_registers = 
Op::Get("tirx.cuda.max_registers_per_thread");
+    static const Op required_block = Op::Get("tirx.cuda.required_block_size");
+    std::optional<int64_t>* setting = nullptr;
+    if (call->op.same_as(min_blocks)) {
+      setting = &min_blocks_per_sm_;
+    } else if (call->op.same_as(max_blocks)) {
+      setting = &max_blocks_per_cluster_;
+    } else if (call->op.same_as(max_registers)) {
+      setting = &max_registers_per_thread_;
+    }
+    if (setting) {
+      int64_t value = 
static_cast<int64_t>(call->args[0].as_or_throw<IntImm>()->value);
+      TVM_FFI_ICHECK_GT(value, 0) << call->op << " must be positive";
+      TVM_FFI_ICHECK(!setting->has_value() || setting->value() == value)
+          << "Conflicting " << call->op << " values";
+      *setting = value;
+      return;
+    }
+    if (call->op.same_as(required_block)) {
+      std::array<int64_t, 6> dimensions;
+      for (size_t i = 0; i < dimensions.size(); ++i) {
+        dimensions[i] = 
static_cast<int64_t>(call->args[i].as_or_throw<IntImm>()->value);
+        TVM_FFI_ICHECK_GT(dimensions[i], 0) << "Required block dimensions must 
be positive";
+      }
+      TVM_FFI_ICHECK(!required_block_size_ || *required_block_size_ == 
dimensions)
+          << "Conflicting required block size values";
+      required_block_size_ = dimensions;
+      return;
+    }
+  }
   if (auto value = op->value.as<PrimExpr>(); value && 
is_const_int(value.value())) return;
   CodeGenC::Dispatch_(op);
 }
diff --git a/src/backend/cuda/codegen/codegen_cuda.h 
b/src/backend/cuda/codegen/codegen_cuda.h
index 907eff672a..9b86446a40 100644
--- a/src/backend/cuda/codegen/codegen_cuda.h
+++ b/src/backend/cuda/codegen/codegen_cuda.h
@@ -28,6 +28,8 @@
 #include <tvm/target/codegen.h>
 #include <tvm/tirx/op.h>
 
+#include <array>
+#include <optional>
 #include <string>
 #include <unordered_map>
 
@@ -48,6 +50,9 @@ class CodeGenCUDA final : public CodeGenC {
     });
   }
   // override behavior
+  void DeclareFunction(const GlobalVar& gvar, const PrimFunc& func) final;
+  void AddFunction(const GlobalVar& gvar, const PrimFunc& func) final;
+  void InitFuncState(const PrimFunc& func) final;
   void PrintFunctionSignature(const ffi::String& function_name, const 
PrimFunc& func,
                               std::ostream& os) final;
   void PrintExtraAttrs(const PrimFunc& f, std::ostream& os) final;  // 
NOLINT(*)
@@ -89,6 +94,14 @@ class CodeGenCUDA final : public CodeGenC {
                        bool skip_first_arg, std::ostream& os) final;  // 
NOLINT(*)
 
  private:
+  void PrintFunctionPrefix(const PrimFunc& func, std::ostream& os);
+  std::array<PrimExpr, 6> launch_dimensions_{IntImm::Int32(1), 
IntImm::Int32(1), IntImm::Int32(1),
+                                             IntImm::Int32(1), 
IntImm::Int32(1), IntImm::Int32(1)};
+  std::optional<int64_t> min_blocks_per_sm_;
+  std::optional<int64_t> max_blocks_per_cluster_;
+  std::optional<int64_t> max_registers_per_thread_;
+  std::optional<std::array<int64_t, 6>> required_block_size_;
+
   // Handle volatile loads
   void HandleVolatileLoads(const std::string& value, const TensorLoadNode* op,
                            std::ostream& os) final;
diff --git a/src/backend/cuda/op/target_builtin.cc 
b/src/backend/cuda/op/target_builtin.cc
index 0c33c27bb5..0306dfb38f 100644
--- a/src/backend/cuda/op/target_builtin.cc
+++ b/src/backend/cuda/op/target_builtin.cc
@@ -260,6 +260,21 @@ OpDef& RegisterDeviceIntrinsic(OpDef&& def, const char* 
op_namespace, CallEffect
 }
 
 void RegisterDeviceIntrinsics() {
+  // Kernel configuration declarations survive lowering until CUDA body 
generation.
+  RegisterDeviceIntrinsic(OpDef("tirx.cuda.launch_bounds_min_blocks_per_sm"), 
"cuda",
+                          CallEffectKind::kEmbedInfo, 
sig::arg<IntImm>("value"))
+      .set_attr<TFixedReturnType>("TFixedReturnType", PrimType::Void());
+  
RegisterDeviceIntrinsic(OpDef("tirx.cuda.launch_bounds_max_blocks_per_cluster"),
 "cuda",
+                          CallEffectKind::kEmbedInfo, 
sig::arg<IntImm>("value"))
+      .set_attr<TFixedReturnType>("TFixedReturnType", PrimType::Void());
+  RegisterDeviceIntrinsic(OpDef("tirx.cuda.max_registers_per_thread"), "cuda",
+                          CallEffectKind::kEmbedInfo, 
sig::arg<IntImm>("value"))
+      .set_attr<TFixedReturnType>("TFixedReturnType", PrimType::Void());
+  RegisterDeviceIntrinsic(
+      OpDef("tirx.cuda.required_block_size"), "cuda", 
CallEffectKind::kEmbedInfo,
+      sig::arg<IntImm>("thread_x"), sig::arg<IntImm>("thread_y"), 
sig::arg<IntImm>("thread_z"),
+      sig::arg<IntImm>("cluster_x"), sig::arg<IntImm>("cluster_y"), 
sig::arg<IntImm>("cluster_z"))
+      .set_attr<TFixedReturnType>("TFixedReturnType", PrimType::Void());
   RegisterDeviceIntrinsic(OpDef("tirx.cuda.any_sync"), "cuda", 
CallEffectKind::kPure,
                           sig::arg<IntExpr>("mask"), sig::arg("pred"))
       .set_attr<TFixedReturnType>("TFixedReturnType", PrimType::Int(32));
diff --git a/src/target/source/codegen_c.cc b/src/target/source/codegen_c.cc
index 7e693f5c24..68d40b5107 100644
--- a/src/target/source/codegen_c.cc
+++ b/src/target/source/codegen_c.cc
@@ -89,7 +89,12 @@ void CodeGenC::PrintFunctionSignature(const ffi::String& 
function_name, const Pr
   PrintFuncPrefix(os);
   PrintType(func->ret_type, os);
   PrintExtraAttrs(func, os);
-  os << " " << function_name << "(";
+  os << " " << function_name;
+  PrintFunctionParameters(func, os);
+}
+
+void CodeGenC::PrintFunctionParameters(const PrimFunc& func, std::ostream& os) 
{
+  os << "(";
   for (size_t i = 0; i < func->params.size(); ++i) {
     tirx::Var v = func->params[i];
 
@@ -139,10 +144,8 @@ void CodeGenC::PrintFunctionSignature(const ffi::String& 
function_name, const Pr
   }
 }
 
-void CodeGenC::DeclareFunction(const GlobalVar& gvar, const PrimFunc& func) {
-  if (internal_functions_.count(gvar)) {
-    return;
-  }
+bool CodeGenC::RegisterFunctionName(const GlobalVar& gvar, const PrimFunc& 
func) {
+  if (internal_functions_.count(gvar)) return false;
 
   auto function_name = [&]() -> ffi::String {
     if (auto global_symbol = 
func->GetAttr<ffi::String>(tvm::attr::kGlobalSymbol)) {
@@ -161,9 +164,13 @@ void CodeGenC::DeclareFunction(const GlobalVar& gvar, 
const PrimFunc& func) {
     has_tvm_ffi_main_func_ = true;
   }
   internal_functions_.insert({gvar, function_name});
+  return true;
+}
 
+void CodeGenC::DeclareFunction(const GlobalVar& gvar, const PrimFunc& func) {
+  if (!RegisterFunctionName(gvar, func)) return;
   InitFuncState(func);
-  PrintFunctionSignature(function_name, func, fwd_decl_stream);
+  PrintFunctionSignature(GetFunctionName(gvar), func, fwd_decl_stream);
   fwd_decl_stream << ";\n";
 }
 
diff --git a/src/target/source/codegen_c.h b/src/target/source/codegen_c.h
index c33782706b..bf96b0a2c5 100644
--- a/src/target/source/codegen_c.h
+++ b/src/target/source/codegen_c.h
@@ -167,6 +167,12 @@ class CodeGenC : public tirx::ExprFunctor<void(const 
Expr&, std::ostream&)>,
    * \param f The function to be compiled.
    */
   virtual void InitFuncState(const PrimFunc& f);
+
+  // Register names independently of emitting declarations for body-derived 
qualifiers.
+  bool RegisterFunctionName(const GlobalVar& gvar, const PrimFunc& func);
+  // Prints parameters and initializes their variable IDs and handle types 
once.
+  void PrintFunctionParameters(const PrimFunc& func, std::ostream& os);
+
   // expression
   void Dispatch_(const VarNode* op, std::ostream& os) override;               
// NOLINT(*)
   void Dispatch_(const TensorLoadNode* op, std::ostream& os) override;        
// NOLINT(*)
diff --git a/src/tirx/transform/split_host_device.cc 
b/src/tirx/transform/split_host_device.cc
index dd009492d2..cb8ebb7bb1 100644
--- a/src/tirx/transform/split_host_device.cc
+++ b/src/tirx/transform/split_host_device.cc
@@ -104,95 +104,6 @@ PrimFunc AnnotateDeviceRegionsForSplit(PrimFunc func) {
 
 // Host/device function extraction
 
-class LaunchBoundsAttrExtractor : public StmtExprMutator {
- public:
-  using StmtExprMutator::Mutate;
-  using StmtExprMutator::Mutate_;
-  UnchangedOr<ffi::Any> Mutate(ffi::AnyView input, InplaceMode inplace_mode) 
override {
-    if (input.as<ExprNode>()) return ffi::Unchanged();
-    return StmtExprMutator::Mutate(input, inplace_mode);
-  }
-  Stmt Extract(Stmt stmt) {
-    min_blocks_per_sm_.reset();
-    max_blocks_per_cluster_.reset();
-    max_registers_.reset();
-    required_block_size_.reset();
-    Stmt result = Mutate(stmt, InplaceMode::kAllow).ValueOrUnchanged(stmt);
-    TVM_FFI_ICHECK(!max_blocks_per_cluster_.has_value() || 
min_blocks_per_sm_.has_value())
-        << tirx::attr::kLaunchBoundsMaxBlocksPerCluster << " requires "
-        << tirx::attr::kLaunchBoundsMinBlocksPerSM;
-    TVM_FFI_ICHECK(!max_registers_.has_value() ||
-                   (!min_blocks_per_sm_.has_value() && 
!max_blocks_per_cluster_.has_value()))
-        << tirx::attr::kMaxRegisters << " cannot be combined with CUDA launch 
bounds";
-    TVM_FFI_ICHECK(!required_block_size_.has_value() || 
!max_registers_.has_value())
-        << tirx::attr::kRequiredBlockSize << " cannot be combined with maximum 
registers";
-    return result;
-  }
-
-  std::optional<int64_t> min_blocks_per_sm() const { return 
min_blocks_per_sm_; }
-  std::optional<int64_t> max_blocks_per_cluster() const { return 
max_blocks_per_cluster_; }
-  std::optional<int64_t> max_registers() const { return max_registers_; }
-  std::optional<int64_t> required_block_size() const { return 
required_block_size_; }
-
- private:
-  UnchangedOr<Stmt> Mutate_(const AttrStmtNode* op, InplaceMode inplace_mode) 
final {
-    if (op->attr_key == tirx::attr::kLaunchBoundsMinBlocksPerSM) {
-      const auto* min_blocks_per_sm = op->value.as<IntImmNode>();
-      TVM_FFI_ICHECK(min_blocks_per_sm)
-          << tirx::attr::kLaunchBoundsMinBlocksPerSM << " expects an integer 
value";
-      TVM_FFI_ICHECK_GT(min_blocks_per_sm->value, 0)
-          << tirx::attr::kLaunchBoundsMinBlocksPerSM << " must be positive";
-      if (min_blocks_per_sm_.has_value()) {
-        TVM_FFI_ICHECK_EQ(min_blocks_per_sm_.value(), min_blocks_per_sm->value)
-            << "Conflicting " << tirx::attr::kLaunchBoundsMinBlocksPerSM << " 
values";
-      }
-      min_blocks_per_sm_ = static_cast<int64_t>(min_blocks_per_sm->value);
-      return Mutate(op->body, inplace_mode).ValueOrUnchanged(op->body);
-    } else if (op->attr_key == tirx::attr::kLaunchBoundsMaxBlocksPerCluster) {
-      const auto* max_blocks_per_cluster = op->value.as<IntImmNode>();
-      TVM_FFI_ICHECK(max_blocks_per_cluster)
-          << tirx::attr::kLaunchBoundsMaxBlocksPerCluster << " expects an 
integer value";
-      TVM_FFI_ICHECK_GT(max_blocks_per_cluster->value, 0)
-          << tirx::attr::kLaunchBoundsMaxBlocksPerCluster << " must be 
positive";
-      if (max_blocks_per_cluster_.has_value()) {
-        TVM_FFI_ICHECK_EQ(max_blocks_per_cluster_.value(), 
max_blocks_per_cluster->value)
-            << "Conflicting " << tirx::attr::kLaunchBoundsMaxBlocksPerCluster 
<< " values";
-      }
-      max_blocks_per_cluster_ = 
static_cast<int64_t>(max_blocks_per_cluster->value);
-      return Mutate(op->body, inplace_mode).ValueOrUnchanged(op->body);
-    } else if (op->attr_key == tirx::attr::kMaxRegisters) {
-      const auto* max_registers = op->value.as<IntImmNode>();
-      TVM_FFI_ICHECK(max_registers) << tirx::attr::kMaxRegisters << " expects 
an integer value";
-      TVM_FFI_ICHECK_GT(max_registers->value, 0)
-          << tirx::attr::kMaxRegisters << " must be positive";
-      if (max_registers_.has_value()) {
-        TVM_FFI_ICHECK_EQ(max_registers_.value(), max_registers->value)
-            << "Conflicting " << tirx::attr::kMaxRegisters << " values";
-      }
-      max_registers_ = static_cast<int64_t>(max_registers->value);
-      return Mutate(op->body, inplace_mode).ValueOrUnchanged(op->body);
-    } else if (op->attr_key == tirx::attr::kRequiredBlockSize) {
-      const auto* required_block_size = op->value.as<IntImmNode>();
-      TVM_FFI_ICHECK(required_block_size)
-          << tirx::attr::kRequiredBlockSize << " expects an integer value";
-      TVM_FFI_ICHECK_EQ(required_block_size->value, 1)
-          << tirx::attr::kRequiredBlockSize << " must be 1";
-      if (required_block_size_.has_value()) {
-        TVM_FFI_ICHECK_EQ(required_block_size_.value(), 
required_block_size->value)
-            << "Conflicting " << tirx::attr::kRequiredBlockSize << " values";
-      }
-      required_block_size_ = static_cast<int64_t>(required_block_size->value);
-      return Mutate(op->body, inplace_mode).ValueOrUnchanged(op->body);
-    }
-    return StmtExprMutator::Mutate_(op, inplace_mode);
-  }
-
-  std::optional<int64_t> min_blocks_per_sm_;
-  std::optional<int64_t> max_blocks_per_cluster_;
-  std::optional<int64_t> max_registers_;
-  std::optional<int64_t> required_block_size_;
-};
-
 class HostDeviceSplitter : public StmtExprMutator {
  public:
   using StmtExprMutator::Mutate;
@@ -317,8 +228,6 @@ class HostDeviceSplitter : public StmtExprMutator {
                     {})),
           std::move(body));
     }
-    auto launch_bounds_attr = ffi::make_object<LaunchBoundsAttrExtractor>();
-    body = launch_bounds_attr->Extract(std::move(body));
     PrimFunc device_func(kernel_params, body, kernel_ret_type);
     device_func = WithAttrs(std::move(device_func), {{tvm::attr::kTarget, 
device_target},
                                                      {tirx::attr::kNoAlias, 
true},
@@ -332,24 +241,6 @@ class HostDeviceSplitter : public StmtExprMutator {
       device_func =
           WithAttr(std::move(device_func), tirx::attr::kKernelLaunchParams, 
launch_params.value());
     }
-    if (device_target->kind->name == "cuda") {
-      if (launch_bounds_attr->min_blocks_per_sm().has_value()) {
-        device_func = WithAttr(std::move(device_func), 
tirx::attr::kLaunchBoundsMinBlocksPerSM,
-                               
launch_bounds_attr->min_blocks_per_sm().value());
-      }
-      if (launch_bounds_attr->max_blocks_per_cluster().has_value()) {
-        device_func = WithAttr(std::move(device_func), 
tirx::attr::kLaunchBoundsMaxBlocksPerCluster,
-                               
launch_bounds_attr->max_blocks_per_cluster().value());
-      }
-      if (launch_bounds_attr->max_registers().has_value()) {
-        device_func = WithAttr(std::move(device_func), 
tirx::attr::kMaxRegisters,
-                               launch_bounds_attr->max_registers().value());
-      }
-      if (launch_bounds_attr->required_block_size().has_value()) {
-        device_func = WithAttr(std::move(device_func), 
tirx::attr::kRequiredBlockSize,
-                               
launch_bounds_attr->required_block_size().value());
-      }
-    }
     auto num_inputs = cur_func_->GetAttr<int64_t>(tvm::attr::kNumInputs);
     if (num_inputs.has_value()) {
       device_func = WithAttr(std::move(device_func), tvm::attr::kNumInputs, 
num_inputs);
@@ -441,8 +332,6 @@ class DeviceInfoCollector : public StmtExprVisitor {
         }
       }
     }
-    collector->use_required_block_dimension_ =
-        func->GetAttr<int64_t>(tirx::attr::kRequiredBlockSize).value_or(0) == 
1;
 
     collector->Visit(func->body);
 
@@ -533,7 +422,12 @@ class DeviceInfoCollector : public StmtExprVisitor {
   }
 
   ffi::Optional<VisitInterrupt> Visit_(const EvaluateNode* op) final {
+    static const Op required_block_size = 
Op::Get("tirx.cuda.required_block_size");
     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(required_block_size)) {
+      use_required_block_dimension_ = true;
+    }
     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.
diff --git 
a/tests/python/tirx-transform/test_tir_transform_split_host_device.py 
b/tests/python/tirx-transform/test_tir_transform_split_host_device.py
index bd95398420..2c90bf735e 100644
--- a/tests/python/tirx-transform/test_tir_transform_split_host_device.py
+++ b/tests/python/tirx-transform/test_tir_transform_split_host_device.py
@@ -471,17 +471,17 @@ def 
test_cuda_required_block_size_coexists_with_launch_bounds():
         def main(A: T.Tensor(4, "float32")):
             T.func_attr({"target": T.target("cuda", host="llvm")})
             T.region("tirx.device_scope", [], attrs={"target": 
T.target("cuda")})
-            T.attr(0, "tirx.required_block_size", 1)
-            with T.attr(0, "tirx.launch_bounds_min_blocks_per_sm", 1):
-                bx = T.launch_thread("blockIdx.x", 4)
-                tx = T.launch_thread("threadIdx.x", 128)
-                if tx == 0:
-                    A[bx] = 0.0
+            T.cuda.required_block_size(128, 1, 1, 1, 1, 1)
+            T.cuda.launch_bounds_min_blocks_per_sm(1)
+            bx = T.launch_thread("blockIdx.x", 4)
+            tx = T.launch_thread("threadIdx.x", 128)
+            if tx == 0:
+                A[bx] = 0.0
 
     after = tvm.tirx.transform.SplitHostDevice()(Before)
     kernel = after["main_kernel"]
-    assert int(kernel.attrs["tirx.required_block_size"]) == 1
-    assert int(kernel.attrs["tirx.launch_bounds_min_blocks_per_sm"]) == 1
+    assert "T.cuda.required_block_size(128, 1, 1, 1, 1, 1)" in kernel.script()
+    assert "T.cuda.launch_bounds_min_blocks_per_sm(1)" in kernel.script()
     assert list(kernel.attrs["tirx.kernel_launch_params"]) == [
         "blockIdx.x",
         "threadIdx.x",
diff --git a/tests/python/tirx/codegen/test_codegen_cuda.py 
b/tests/python/tirx/codegen/test_codegen_cuda.py
index 6d64053777..c84790f48e 100644
--- a/tests/python/tirx/codegen/test_codegen_cuda.py
+++ b/tests/python/tirx/codegen/test_codegen_cuda.py
@@ -253,11 +253,11 @@ def 
test_tirx_launch_bounds_omits_min_blocks_without_persistent_schedule():
     assert "__launch_bounds__(128, 1)" not in src
 
 
-def test_tirx_launch_bounds_min_blocks_attr_sets_one_block_per_sm():
+def test_tirx_launch_bounds_min_blocks_sets_one_block_per_sm():
     @T.prim_func
     def main(A: T.Tensor((4,), "int32")):
         T.device_entry()
-        T.attr({"tirx.launch_bounds_min_blocks_per_sm": 1})
+        T.cuda.launch_bounds_min_blocks_per_sm(1)
         bx = T.cta_id([4])
         tx = T.thread_id([128])
         if tx == 0:
@@ -265,19 +265,15 @@ def 
test_tirx_launch_bounds_min_blocks_attr_sets_one_block_per_sm():
 
     src, _ = _get_source(main)
     assert 'extern "C" __global__ void __launch_bounds__(128, 1) main_kernel' 
in src
-    assert "tirx.launch_bounds_min_blocks_per_sm" not in src
+    assert "tirx.cuda.launch_bounds_min_blocks_per_sm" not in src
 
 
 def test_tirx_launch_bounds_max_blocks_per_cluster_emits_third_operand():
     @T.prim_func
     def main(A: T.Tensor((4,), "int32")):
         T.device_entry()
-        T.attr(
-            {
-                "tirx.launch_bounds_min_blocks_per_sm": 1,
-                "tirx.launch_bounds_max_blocks_per_cluster": 1,
-            }
-        )
+        T.cuda.launch_bounds_min_blocks_per_sm(1)
+        T.cuda.launch_bounds_max_blocks_per_cluster(1)
         bx = T.cta_id([4])
         tx = T.thread_id([384])
         if tx == 0:
@@ -285,14 +281,14 @@ def 
test_tirx_launch_bounds_max_blocks_per_cluster_emits_third_operand():
 
     src, _ = _get_source(main)
     assert 'extern "C" __global__ void __launch_bounds__(384, 1, 1) 
main_kernel' in src
-    assert "tirx.launch_bounds_max_blocks_per_cluster" not in src
+    assert "tirx.cuda.launch_bounds_max_blocks_per_cluster" not in src
 
 
-def test_tirx_max_registers_attr_emits_cuda_maxnreg():
+def test_tirx_max_registers_emits_cuda_maxnreg():
     @T.prim_func
     def main(A: T.Tensor((4,), "int32")):
         T.device_entry()
-        T.attr({"tirx.max_registers": 92})
+        T.cuda.max_registers_per_thread(92)
         bx = T.cta_id([4])
         tx = T.thread_id([128])
         if tx == 0:
@@ -301,19 +297,15 @@ def test_tirx_max_registers_attr_emits_cuda_maxnreg():
     src, _ = _get_source(main)
     assert 'extern "C" __global__ void __maxnreg__(92) main_kernel' in src
     assert "__launch_bounds__" not in src
-    assert "tirx.max_registers" not in src
+    assert "tirx.cuda.max_registers_per_thread" not in src
 
 
 def test_tirx_max_registers_rejects_launch_bounds():
     @T.prim_func
     def main(A: T.Tensor((4,), "int32")):
         T.device_entry()
-        T.attr(
-            {
-                "tirx.max_registers": 92,
-                "tirx.launch_bounds_min_blocks_per_sm": 1,
-            }
-        )
+        T.cuda.max_registers_per_thread(92)
+        T.cuda.launch_bounds_min_blocks_per_sm(1)
         bx = T.cta_id([4])
         tx = T.thread_id([128])
         if tx == 0:
@@ -327,7 +319,7 @@ def test_tirx_required_block_size_emits_cuda_block_size():
     @T.prim_func
     def main(A: T.Tensor((8,), "int32")):
         T.device_entry()
-        T.attr({"tirx.required_block_size": 1})
+        T.cuda.required_block_size(128, 1, 1, 1, 2, 1)
         bx, by = T.cta_id([4, 2])
         _, cy = T.cta_id_in_cluster([1, 2])
         tx = T.thread_id([128])
@@ -337,19 +329,15 @@ def test_tirx_required_block_size_emits_cuda_block_size():
     src, _ = _get_source(main)
     assert 'extern "C" __global__ void __block_size__((128, 1, 1), (1, 2, 1)) 
main_kernel' in src
     assert "__launch_bounds__" not in src
-    assert "tirx.required_block_size" not in src
+    assert "tirx.cuda.required_block_size" not in src
 
 
 def test_tirx_required_block_size_emits_launch_bounds_when_requested():
     @T.prim_func
     def main(A: T.Tensor((4,), "int32")):
         T.device_entry()
-        T.attr(
-            {
-                "tirx.required_block_size": 1,
-                "tirx.launch_bounds_min_blocks_per_sm": 1,
-            }
-        )
+        T.cuda.required_block_size(128, 1, 1, 1, 1, 1)
+        T.cuda.launch_bounds_min_blocks_per_sm(1)
         bx = T.cta_id([4])
         tx = T.thread_id([128])
         if tx == 0:
@@ -360,8 +348,8 @@ def 
test_tirx_required_block_size_emits_launch_bounds_when_requested():
         'extern "C" __global__ void __block_size__((128, 1, 1), (1, 1, 1)) '
         "__launch_bounds__(128, 1) main_kernel" in src
     )
-    assert "tirx.required_block_size" not in src
-    assert "tirx.launch_bounds_min_blocks_per_sm" not in src
+    assert "tirx.cuda.required_block_size" not in src
+    assert "tirx.cuda.launch_bounds_min_blocks_per_sm" not in src
 
 
 def test_tirx_cuda_kernel_return_zero_codegen_is_void_early_return():

Reply via email to