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

jinhongyii 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 73e38d3f44 [TIRx][CUDA] Add register and cluster launch controls 
(#20159)
73e38d3f44 is described below

commit 73e38d3f4447aa507cf795542aeffcc0948368b5
Author: Bohan Hou <[email protected]>
AuthorDate: Thu Aug 20 15:56:59 2026 -0400

    [TIRx][CUDA] Add register and cluster launch controls (#20159)
    
    ## Summary
    
    - add a `tirx.max_registers` PrimFunc attribute and lower it to CUDA 13
    `__maxnreg__`
    - reject combinations of the register cap with CUDA launch-bounds
    attributes
    - enable non-portable CUDA cluster sizes when a launch requests more
    than eight CTAs
    - keep launch attributes in fixed local storage and add CUDA codegen
    coverage
    
    ## Motivation
    
    SM100 TIRx kernels need an explicit register cap to preserve
    source-level occupancy and instruction scheduling. Large reduction
    kernels can also require 16-CTA clusters, which the CUDA driver rejects
    unless the non-portable cluster-size attribute is enabled.
    
    ## Impact
    
    TIRx kernels can opt into an exact CUDA register budget while retaining
    the existing launch-bounds path for other specializations. CUDA launches
    with cluster dimensions above eight are enabled only for kernels that
    request them; existing launch behavior remains unchanged.
    
    ## Validation
    
    - `cmake --build build --parallel`
    - `python -m pytest tests/python/tirx/codegen/test_codegen_cuda.py -q`
    (`201 passed`)
    - `pre-commit run --files include/tvm/tirx/function.h
    src/backend/cuda/codegen/codegen_cuda.cc
    src/backend/cuda/runtime/cuda_module.cc
    src/tirx/transform/split_host_device.cc
    tests/python/tirx/codegen/test_codegen_cuda.py`
---
 include/tvm/tirx/function.h                    | 10 ++++++++
 src/backend/cuda/codegen/codegen_cuda.cc       |  9 +++++++
 src/backend/cuda/runtime/cuda_module.cc        | 24 ++++++++++++------
 src/tirx/transform/split_host_device.cc        | 21 ++++++++++++++++
 tests/python/tirx/codegen/test_codegen_cuda.py | 35 ++++++++++++++++++++++++++
 5 files changed, 92 insertions(+), 7 deletions(-)

diff --git a/include/tvm/tirx/function.h b/include/tvm/tirx/function.h
index d335ee9515..58912aefe4 100644
--- a/include/tvm/tirx/function.h
+++ b/include/tvm/tirx/function.h
@@ -293,6 +293,16 @@ constexpr const char* kLaunchBoundsMinBlocksPerSM = 
"tirx.launch_bounds_min_bloc
 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 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 8b6f2fa2d5..15484391ca 100644
--- a/src/backend/cuda/codegen/codegen_cuda.cc
+++ b/src/backend/cuda/codegen/codegen_cuda.cc
@@ -243,6 +243,15 @@ void CodeGenCUDA::PrintExtraAttrs(const PrimFunc& f, 
std::ostream& os) {
   } else {
     cluster_cta_x_is_linear_rank_ = false;
   }
+  auto max_registers = f->GetAttr<int64_t>(tirx::attr::kMaxRegisters);
+  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() << ")";
+    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
diff --git a/src/backend/cuda/runtime/cuda_module.cc 
b/src/backend/cuda/runtime/cuda_module.cc
index 5a598c87cc..0d210ac0da 100644
--- a/src/backend/cuda/runtime/cuda_module.cc
+++ b/src/backend/cuda/runtime/cuda_module.cc
@@ -231,9 +231,19 @@ class CUDAWrappedFunc {
               << "Failed to set the allowed dynamic shared memory size to " << 
wl.dyn_shmem_size;
         }
       }
+      if (wl.cluster_dim(0) * wl.cluster_dim(1) * wl.cluster_dim(2) > 8) {
+        CUresult result = cuFuncSetAttribute(
+            fcache_[device_id], 
CU_FUNC_ATTRIBUTE_NON_PORTABLE_CLUSTER_SIZE_ALLOWED, 1);
+        if (result != CUDA_SUCCESS) {
+          TVM_FFI_THROW(InternalError)
+              << "Failed to allow non-portable CUDA cluster size (" << 
wl.cluster_dim(0) << ", "
+              << wl.cluster_dim(1) << ", " << wl.cluster_dim(2) << ")";
+        }
+      }
     }
     CUstream strm = static_cast<CUstream>(TVMFFIEnvGetStream(kDLCUDA, 
device_id));
-    std::vector<CUlaunchAttribute> attrs;
+    std::array<CUlaunchAttribute, 4> attrs{};
+    unsigned int num_attrs = 0;
 
     // 1) Cluster
     if (wl.cluster_dim(0) != 1 || wl.cluster_dim(1) != 1 || wl.cluster_dim(2) 
!= 1) {
@@ -242,7 +252,7 @@ class CUDAWrappedFunc {
       attr.value.clusterDim.x = wl.cluster_dim(0);
       attr.value.clusterDim.y = wl.cluster_dim(1);
       attr.value.clusterDim.z = wl.cluster_dim(2);
-      attrs.push_back(attr);
+      attrs[num_attrs++] = attr;
     }
 
     // 1b) Preferred cluster (CUDA 12.8+, 
cudaLaunchAttributePreferredClusterDimension)
@@ -253,7 +263,7 @@ class CUDAWrappedFunc {
       attr.value.clusterDim.x = wl.preferred_cluster_dim(0);
       attr.value.clusterDim.y = wl.preferred_cluster_dim(1);
       attr.value.clusterDim.z = wl.preferred_cluster_dim(2);
-      attrs.push_back(attr);
+      attrs[num_attrs++] = attr;
     }
 
     // 2) Programmatic stream serialization
@@ -261,7 +271,7 @@ class CUDAWrappedFunc {
       CUlaunchAttribute attr{};
       attr.id = CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION;
       attr.value.programmaticStreamSerializationAllowed = 1;
-      attrs.push_back(attr);
+      attrs[num_attrs++] = attr;
     }
 
     // 3) Cooperative
@@ -269,7 +279,7 @@ class CUDAWrappedFunc {
       CUlaunchAttribute attr{};
       attr.id = CU_LAUNCH_ATTRIBUTE_COOPERATIVE;
       attr.value.cooperative = 1;
-      attrs.push_back(attr);
+      attrs[num_attrs++] = attr;
     }
 
     // 4) Launch
@@ -282,8 +292,8 @@ class CUDAWrappedFunc {
     config.blockDimZ = wl.block_dim(2);
     config.sharedMemBytes = wl.dyn_shmem_size;
     config.hStream = strm;
-    config.attrs = attrs.empty() ? nullptr : attrs.data();
-    config.numAttrs = static_cast<unsigned int>(attrs.size());
+    config.attrs = num_attrs == 0 ? nullptr : attrs.data();
+    config.numAttrs = num_attrs;
 
     CUresult result = cuLaunchKernelEx(&config, fcache_[device_id], void_args, 
nullptr);
 
diff --git a/src/tirx/transform/split_host_device.cc 
b/src/tirx/transform/split_host_device.cc
index fc6890666a..b46650f864 100644
--- a/src/tirx/transform/split_host_device.cc
+++ b/src/tirx/transform/split_host_device.cc
@@ -90,15 +90,20 @@ class LaunchBoundsAttrExtractor : public StmtMutator {
   Stmt Extract(Stmt stmt) {
     min_blocks_per_sm_.reset();
     max_blocks_per_cluster_.reset();
+    max_registers_.reset();
     Stmt result = operator()(std::move(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";
     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_; }
 
  private:
   Stmt VisitStmt_(const AttrStmtNode* op) final {
@@ -126,12 +131,24 @@ class LaunchBoundsAttrExtractor : public StmtMutator {
       }
       max_blocks_per_cluster_ = max_blocks_per_cluster->value;
       return VisitStmt(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_ = max_registers->value;
+      return VisitStmt(op->body);
     }
     return StmtMutator::VisitStmt_(op);
   }
 
   std::optional<int64_t> min_blocks_per_sm_;
   std::optional<int64_t> max_blocks_per_cluster_;
+  std::optional<int64_t> max_registers_;
 };
 
 class HostDeviceSplitter : public StmtMutator {
@@ -255,6 +272,10 @@ class HostDeviceSplitter : public StmtMutator {
         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());
+      }
     }
     auto num_inputs = cur_func_->GetAttr<int64_t>(tvm::attr::kNumInputs);
     if (num_inputs.has_value()) {
diff --git a/tests/python/tirx/codegen/test_codegen_cuda.py 
b/tests/python/tirx/codegen/test_codegen_cuda.py
index daaa9f36d6..e4e2fce5ba 100644
--- a/tests/python/tirx/codegen/test_codegen_cuda.py
+++ b/tests/python/tirx/codegen/test_codegen_cuda.py
@@ -203,6 +203,41 @@ def 
test_tirx_launch_bounds_max_blocks_per_cluster_emits_third_operand():
     assert "tirx.launch_bounds_max_blocks_per_cluster" not in src
 
 
+def test_tirx_max_registers_attr_emits_cuda_maxnreg():
+    @T.prim_func
+    def main(A: T.Buffer((4,), "int32")):
+        T.device_entry()
+        T.attr({"tirx.max_registers": 92})
+        bx = T.cta_id([4])
+        tx = T.thread_id([128])
+        if tx == 0:
+            A[bx] = A[bx] + 1
+
+    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
+
+
+def test_tirx_max_registers_rejects_launch_bounds():
+    @T.prim_func
+    def main(A: T.Buffer((4,), "int32")):
+        T.device_entry()
+        T.attr(
+            {
+                "tirx.max_registers": 92,
+                "tirx.launch_bounds_min_blocks_per_sm": 1,
+            }
+        )
+        bx = T.cta_id([4])
+        tx = T.thread_id([128])
+        if tx == 0:
+            A[bx] = A[bx] + 1
+
+    with pytest.raises(tvm.error.InternalError, match="cannot be combined with 
CUDA launch bounds"):
+        _get_source(main)
+
+
 def test_tirx_cuda_kernel_return_zero_codegen_is_void_early_return():
     @T.prim_func
     def main(A: T.Buffer((4,), "int32")):

Reply via email to