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 1dc56a6bd1 [REFACTOR][TIR] Attach fragment metadata to allocation 
calls (#20520)
1dc56a6bd1 is described below

commit 1dc56a6bd1c0c9a0c3f666ffefdc9238214a48f0
Author: Tianqi Chen <[email protected]>
AuthorDate: Fri Oct 2 14:30:45 2026 -0400

    [REFACTOR][TIR] Attach fragment metadata to allocation calls (#20520)
    
    Move TensorCore fragment shape and layout metadata from AttrStmt
    wrappers onto their owning allocation calls. InferFragment writes direct
    attributes, and CUDA and SPIR-V read them when emitting each allocation.
    
    Preserve annotated allocation ownership through storage rewriting,
    including custom pass ordering, while keeping unrelated allocation and
    external-storage protocols unchanged.
---
 include/tvm/s_tir/stmt.h                         |  4 +--
 src/backend/cuda/codegen/codegen_cuda.cc         | 37 +++++++++----------
 src/backend/cuda/codegen/codegen_cuda.h          |  8 ++---
 src/backend/vulkan/codegen/codegen_spirv.cc      |  9 +++--
 src/s_tir/transform/tensorcore_infer_fragment.cc | 45 ++++++++++++------------
 src/tirx/transform/storage_rewrite.cc            | 28 ++++++++++++---
 6 files changed, 70 insertions(+), 61 deletions(-)

diff --git a/include/tvm/s_tir/stmt.h b/include/tvm/s_tir/stmt.h
index 810435142d..d79674ff3e 100644
--- a/include/tvm/s_tir/stmt.h
+++ b/include/tvm/s_tir/stmt.h
@@ -226,12 +226,12 @@ constexpr const char* double_buffer_scope = 
"double_buffer_scope";
 constexpr const char* double_buffer_write = "double_buffer_write";
 
 /*!
- * \brief Mark that the shape of TensorCore fragment
+ * \brief String-valued allocation Call attribute containing the TensorCore 
fragment shape
  */
 constexpr const char* fragment_shape = "fragment_shape";
 
 /*!
- * \brief Mark that the layout of TensorCore fragment
+ * \brief String-valued allocation Call attribute containing the TensorCore 
fragment layout
  */
 constexpr const char* fragment_layout = "fragment_layout";
 
diff --git a/src/backend/cuda/codegen/codegen_cuda.cc 
b/src/backend/cuda/codegen/codegen_cuda.cc
index 31ce6f3692..8945d5bce3 100644
--- a/src/backend/cuda/codegen/codegen_cuda.cc
+++ b/src/backend/cuda/codegen/codegen_cuda.cc
@@ -1544,15 +1544,7 @@ void CodeGenCUDA::Dispatch_(const CallNode* op, 
std::ostream& os) {
 }
 
 void CodeGenCUDA::Dispatch_(const AttrStmtNode* op) {
-  if (op->attr_key == s_tir::attr::fragment_shape) {
-    const VarNode* buffer = op->node.as<VarNode>();
-    const StringImmNode* shape_str = op->value.as<StringImmNode>();
-    fragment_shapes[buffer] = shape_str->value;
-  } else if (op->attr_key == s_tir::attr::fragment_layout) {
-    const VarNode* buffer = op->node.as<VarNode>();
-    const StringImmNode* layout_str = op->value.as<StringImmNode>();
-    fragment_layouts[buffer] = layout_str->value;
-  } else if (op->attr_key == s_tir::attr::async_commit_queue_scope) {
+  if (op->attr_key == s_tir::attr::async_commit_queue_scope) {
     const IntImmNode* queue_id = op->value.as<IntImmNode>();
     TVM_FFI_ICHECK(queue_id && queue_id->value == 0)
         << "For CUDA, the index of an async queue must be 0.";
@@ -1607,6 +1599,16 @@ void CodeGenCUDA::DispatchAllocBuffer(const BindNode* 
op, const CallNode* buffer
   TVM_FFI_ICHECK(buffer.defined());
   std::string vid = AllocVarID(buffer.get(), buffer.name() + "_ptr");
 
+  std::string fragment_shape;
+  std::string fragment_layout;
+  if (std::string(scope).find("wmma.") == 0) {
+    auto shape = annotations->dict.Get(s_tir::attr::fragment_shape);
+    TVM_FFI_ICHECK(shape.has_value()) << "Cannot find shape of the wmma 
fragment " << buffer.name();
+    fragment_shape = shape.value().as_or_throw<ffi::String>();
+    if (auto layout = annotations->dict.Get(s_tir::attr::fragment_layout)) {
+      fragment_layout = layout.value().as_or_throw<ffi::String>();
+    }
+  }
   this->PrintIndent();
   if (std::string(scope).find("wmma.") == 0) {
     if (scope == "wmma.matrix_a" || scope == "wmma.matrix_b") {
@@ -1625,7 +1627,7 @@ void CodeGenCUDA::DispatchAllocBuffer(const BindNode* op, 
const CallNode* buffer
       TVM_FFI_ICHECK(supported_wmma_accumulator_dtype)
           << "Accumulator only support half, float and int type for now";
     }
-    PrintWmmaScope(scope, PrimType(dtype), buffer.get(), stream);
+    PrintWmmaScope(scope, PrimType(dtype), fragment_shape, fragment_layout, 
stream);
   } else {
     PrintStorageScope(scope, stream);
     int align = buffer->data_alignment;
@@ -1656,7 +1658,7 @@ void CodeGenCUDA::DispatchAllocBuffer(const BindNode* op, 
const CallNode* buffer
     TVM_FFI_ICHECK_GT(constant_size, 0) << "Can only handle constant size 
stack allocation for now";
 
     if (std::string(scope).find("wmma.") == 0) {
-      constant_size = GetWmmaFragmentSize(scope, buffer.get(), constant_size);
+      constant_size = GetWmmaFragmentSize(scope, fragment_shape, 
constant_size);
     }
     bool is_packed_integer_dtype = PrimType(dtype) == PrimType::Int(4) ||
                                    PrimType(dtype) == PrimType::UInt(4) ||
@@ -1952,12 +1954,10 @@ void CodeGenCUDA::Dispatch_(const FloatImmNode* op, 
std::ostream& os) {  // NOLI
 }
 
 void CodeGenCUDA::PrintWmmaScope(const std::string& scope, const PrimType& t,
-                                 const VarNode* variable, std::ostream& os) {
+                                 const std::string& shape_str, const 
std::string& layout_str,
+                                 std::ostream& os) {
   std::stringstream type;
   PrintType(t, type);
-  TVM_FFI_ICHECK(fragment_shapes.count(variable))
-      << "Cannot find shape of the wmma fragment " << variable->name;
-  std::string shape_str = fragment_shapes.at(variable);
   if ((t.MatchesCode(DLDataTypeCode::kDLInt, DLDataTypeCode::kDLUInt)) && 
t.bits() < 8 &&
       t.lanes() == 1) {
     type.str(std::string());
@@ -1979,13 +1979,11 @@ void CodeGenCUDA::PrintWmmaScope(const std::string& 
scope, const PrimType& t,
   }
   if (scope == "wmma.matrix_a") {
     codegen_tags_.insert("mma");
-    std::string layout_str = fragment_layouts[variable];
     TVM_FFI_ICHECK_NE(layout_str, "") << "Layout must be defined for matrix_a";
     os << "nvcuda::wmma::fragment<nvcuda::wmma::matrix_a, " << shape_str << ", 
" << type.str()
        << ", nvcuda::wmma::" << layout_str << ">";
   } else if (scope == "wmma.matrix_b") {
     codegen_tags_.insert("mma");
-    std::string layout_str = fragment_layouts[variable];
     TVM_FFI_ICHECK_NE(layout_str, "") << "Layout must be defined for matrix_b";
     os << "nvcuda::wmma::fragment<nvcuda::wmma::matrix_b, " << shape_str << ", 
" << type.str()
        << ", nvcuda::wmma::" << layout_str << ">";
@@ -2005,11 +2003,8 @@ int stoi(const std::string& str) {
   }
 }
 
-int32_t CodeGenCUDA::GetWmmaFragmentSize(const std::string& scope, const 
VarNode* variable,
+int32_t CodeGenCUDA::GetWmmaFragmentSize(const std::string& scope, const 
std::string& shape_str,
                                          int32_t size) {
-  TVM_FFI_ICHECK(fragment_shapes.count(variable))
-      << "Cannot find shape of the wmma fragment " << variable->name;
-  std::string shape_str = fragment_shapes.at(variable);
   std::pair<int32_t, int32_t> dim = GetWmmaFragmentDimSize(shape_str, scope);
   if (dim.first * dim.second != 0)
     return size / dim.first / dim.second;
diff --git a/src/backend/cuda/codegen/codegen_cuda.h 
b/src/backend/cuda/codegen/codegen_cuda.h
index 933bc2a6a7..d383a67426 100644
--- a/src/backend/cuda/codegen/codegen_cuda.h
+++ b/src/backend/cuda/codegen/codegen_cuda.h
@@ -124,12 +124,10 @@ class CodeGenCUDA final : public CodeGenC {
   // The name prefix of the cuda::barrier::arrival_token array in registers
   const std::string cuda_barrier_arrival_token_name_ = "cubar_tok";
 
-  std::unordered_map<const VarNode*, std::string> fragment_shapes;
-  std::unordered_map<const VarNode*, std::string> fragment_layouts;
   friend void PrintConst(const FloatImmNode* op, std::ostream& os, 
CodeGenCUDA* p);
-  void PrintWmmaScope(const std::string& scope, const PrimType& t, const 
VarNode* variable,
-                      std::ostream& os);
-  int32_t GetWmmaFragmentSize(const std::string& scope, const VarNode* 
variable, int32_t size);
+  void PrintWmmaScope(const std::string& scope, const PrimType& t, const 
std::string& shape,
+                      const std::string& layout, std::ostream& os);
+  int32_t GetWmmaFragmentSize(const std::string& scope, const std::string& 
shape, int32_t size);
 };
 
 }  // namespace codegen
diff --git a/src/backend/vulkan/codegen/codegen_spirv.cc 
b/src/backend/vulkan/codegen/codegen_spirv.cc
index c9821b239f..0e48715677 100644
--- a/src/backend/vulkan/codegen/codegen_spirv.cc
+++ b/src/backend/vulkan/codegen/codegen_spirv.cc
@@ -898,7 +898,10 @@ void CodeGenSPIRV::DispatchAllocBuffer(const BindNode* op, 
const CallNode* buffe
     case runtime::StorageRank::kWMMAMatrixA:
     case runtime::StorageRank::kWMMAMatrixB:
     case runtime::StorageRank::kWMMAAccumulator: {
-      TVM_FFI_ICHECK(fragment_info_.count(var_node));
+      auto shape = annotations->dict.Get(s_tir::attr::fragment_shape);
+      TVM_FFI_ICHECK(shape.has_value())
+          << "Cannot find shape of the wmma fragment " << buffer.name();
+      fragment_info_[var_node] = {shape.value().as_or_throw<ffi::String>()};
       fragment_info_[var_node].scope = scope;
       etype = GetFragmentSType(var_node, PrimType(dtype));
       storage_class = spv::StorageClassFunction;
@@ -989,10 +992,6 @@ void CodeGenSPIRV::Dispatch_(const AttrStmtNode* op) {
         var_map_[iv->var.get()] = GetThreadIndex(iv, 
op->value.as_or_throw<PrimExpr>());
       }
     }
-  } else if (op->attr_key == s_tir::attr::fragment_shape) {
-    const VarNode* buffer = op->node.as<VarNode>();
-    const StringImmNode* shape_str = op->value.as<StringImmNode>();
-    fragment_info_[buffer] = {shape_str->value};
   }
   this->Dispatch(op->body);
 }
diff --git a/src/s_tir/transform/tensorcore_infer_fragment.cc 
b/src/s_tir/transform/tensorcore_infer_fragment.cc
index 32f5bc3ab0..a2faffa6c8 100644
--- a/src/s_tir/transform/tensorcore_infer_fragment.cc
+++ b/src/s_tir/transform/tensorcore_infer_fragment.cc
@@ -209,33 +209,32 @@ class InferFragmenter : public s_tir::StmtExprMutator {
   UnchangedOr<Stmt> Mutate_(const BindNode* op, InplaceMode inplace_mode) 
final {
     if (const auto* call = op->value.as<CallNode>();
         call && call->op.same_as(tirx::builtin::alloc_buffer())) {
-      return Mutate_AllocBuffer(op, inplace_mode);
+      auto it = fragment_getter.fragments.find(op->var.get());
+      if (it == fragment_getter.fragments.end()) return ffi::Unchanged();
+      const FragmentInfo& info = it->second;
+      DictAttrs attrs = call->attrs.as_or_throw<DictAttrs>();
+      auto annotations = attrs->dict;
+      auto set_annotation = [&](const char* key, const std::string& value) {
+        if (auto existing = annotations.Get(key)) {
+          auto str = existing.value().as<ffi::String>();
+          TVM_FFI_CHECK(str && str.value() == value, ValueError)
+              << "Conflicting " << key << " on fragment " << op->var->name;
+        } else {
+          annotations.Set(key, ffi::String(value));
+        }
+      };
+      set_annotation(
+          s_tir::attr::fragment_shape,
+          std::to_string(info.m) + ", " + std::to_string(info.n) + ", " + 
std::to_string(info.k));
+      if (!info.layout.empty()) set_annotation(s_tir::attr::fragment_layout, 
info.layout);
+      if (annotations.same_as(attrs->dict)) return ffi::Unchanged();
+      auto value = ffi::GetRef<Call>(call);
+      value.CopyOnWrite()->attrs = DictAttrs(std::move(annotations));
+      return Bind(op->var, std::move(value), op->span);
     }
     return s_tir::StmtExprMutator::Mutate_(op, inplace_mode);
   }
 
-  UnchangedOr<Stmt> Mutate_AllocBuffer(const BindNode* op, InplaceMode 
inplace_mode) {
-    Stmt stmt =
-        s_tir::StmtExprMutator::Mutate_(op, 
inplace_mode).ValueOrUnchanged(ffi::GetRef<Stmt>(op));
-    const VarNode* buffer = op->var.get();
-    if (fragment_getter.fragments.count(buffer)) {
-      FragmentInfo info = fragment_getter.fragments.at(buffer);
-
-      std::string shape =
-          std::to_string(info.m) + ", " + std::to_string(info.n) + ", " + 
std::to_string(info.k);
-      Expr shape_expr = StringImm(shape);
-      Stmt shape_attr = AttrStmt(op->var, s_tir::attr::fragment_shape, 
shape_expr, stmt);
-      if (info.layout != "") {
-        Stmt layout_attr =
-            AttrStmt(op->var, s_tir::attr::fragment_layout, 
StringImm(info.layout), shape_attr);
-        return layout_attr;
-      } else {
-        return shape_attr;
-      }
-    }
-    return stmt;
-  }
-
  private:
   // Fragment infomation
   const FragmentGetter& fragment_getter;
diff --git a/src/tirx/transform/storage_rewrite.cc 
b/src/tirx/transform/storage_rewrite.cc
index be31a612b1..3442a16a65 100644
--- a/src/tirx/transform/storage_rewrite.cc
+++ b/src/tirx/transform/storage_rewrite.cc
@@ -780,6 +780,8 @@ class StoragePlanRewriter : public StmtExprMutator {
     PrimType elem_type = PrimType::Void();
     // Whether any constituent allocation was marked volatile.
     bool is_volatile{false};
+    // Fragment metadata belongs to this allocation, not interchangeable 
storage.
+    bool has_fragment_metadata{false};
     // This is non-zero if this alloc_buffer is folded into another one
     // the address(in bits) becomes alloc_var + bits_offset;
     // can be effectively converted to the element type.
@@ -794,6 +796,12 @@ class StoragePlanRewriter : public StmtExprMutator {
     uint64_t bits_offset{0};
   };
 
+  static bool HasFragmentMetadata(const CallNode* call) {
+    const auto& annotations = call->attrs.as_or_throw<DictAttrs>()->dict;
+    return annotations.count(s_tir::attr::fragment_shape) ||
+           annotations.count(s_tir::attr::fragment_layout);
+  }
+
   // Checks whether the storage_scope is especially tagged for a specific 
memory.
   // Special memory is all combined into a single allocation.
   bool IsSpecialTaggedMemory(const StorageScope& scope) {
@@ -836,10 +844,10 @@ class StoragePlanRewriter : public StmtExprMutator {
       // try to find merge, for tagged memory
       for (size_t i = 0; i < vec.size(); ++i) {
         StorageEntry* e = vec[i];
-        if (IsSpecialTaggedMemory(e->scope)) {
+        if (!e->has_fragment_metadata && IsSpecialTaggedMemory(e->scope)) {
           TVM_FFI_ICHECK_NE(e->const_nbits, 0U) << "Special tagged memory must 
be const size";
           for (size_t j = 0; j < i; ++j) {
-            if (e->scope == vec[j]->scope) {
+            if (!vec[j]->has_fragment_metadata && e->scope == vec[j]->scope) {
               vec[j]->merged_children.push_back(e);
               break;
             }
@@ -849,6 +857,13 @@ class StoragePlanRewriter : public StmtExprMutator {
       // Start allocation
       for (size_t i = 0; i < vec.size(); ++i) {
         StorageEntry* e = vec[i];
+        if (e->has_fragment_metadata) {
+          TVM_FFI_ICHECK_EQ(e->allocs.size(), 1U);
+          TVM_FFI_ICHECK(e->merged_children.empty());
+          e->alloc_var = e->allocs[0]->var;
+          e->alloc_nest.push_back(ffi::GetRef<Bind>(e->allocs[0]));
+          continue;
+        }
         // already merged
         if (e->bits_offset != 0) continue;
         if (e->merged_children.size() != 0) {
@@ -1106,7 +1121,7 @@ class StoragePlanRewriter : public StmtExprMutator {
           auto storage_scope = StorageScope::Create(scope);
           StorageEntry* dst_entry = nullptr;
           // inplace detection
-          if (detect_inplace) {
+          if (detect_inplace && !HasFragmentMetadata(call)) {
             DLDataType dtype = call->args[1].as_or_throw<DataTypeImm>()->value;
             PrimType element_type(dtype);
             // only one inplace var for s.stmt
@@ -1115,7 +1130,7 @@ class StoragePlanRewriter : public StmtExprMutator {
               if (!inplace_flag.count(src) && alloc_map_.count(src)) {
                 auto visitor = ffi::make_object<InplaceOpVerifier>();
                 StorageEntry* src_entry = alloc_map_.at(src);
-                if (src_entry->scope == storage_scope &&
+                if (!src_entry->has_fragment_metadata && src_entry->scope == 
storage_scope &&
                     src_entry->attach_scope_ == thread_scope_ && 
!element_type.IsScalableVector() &&
                     src_entry->elem_type == element_type.WithLanes(1) &&
                     visitor->Check(s.stmt, var, src)) {
@@ -1189,6 +1204,7 @@ class StoragePlanRewriter : public StmtExprMutator {
     entry->scope = scope;
     entry->elem_type = element_type.WithLanes(1);
     entry->const_nbits = const_nbits;
+    entry->has_fragment_metadata = HasFragmentMetadata(call);
     StorageEntry* e = entry.get();
     alloc_vec_.emplace_back(std::move(entry));
     return e;
@@ -1225,7 +1241,8 @@ class StoragePlanRewriter : public StmtExprMutator {
     bool is_small_array = (scope.tag.length() == 0) && (scope.rank >= 
StorageRank::kWarp ||
                                                         (is_known_size && 
const_nbits <= 32));
 
-    if (is_scalable_vector || !enable_reuse || is_small_array || 
!is_flat_memory_space) {
+    if (HasFragmentMetadata(call) || is_scalable_vector || !enable_reuse || 
is_small_array ||
+        !is_flat_memory_space) {
       return NewAlloc(op, attach_scope, scope, const_nbits);
     }
 
@@ -1281,6 +1298,7 @@ class StoragePlanRewriter : public StmtExprMutator {
     TVM_FFI_ICHECK(it != alloc_map_.end());
     StorageEntry* e = it->second;
     TVM_FFI_ICHECK_NE(e->allocs.size(), 0U);
+    if (e->has_fragment_metadata) return;
 
     // disable reuse of small arrays, they will be lowered to registers in LLVM
     // This rules only apply if we are using non special memory

Reply via email to