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 b365abb18b [REFACTOR][IR] Localize attribute ownership and fix 
documentation (#20801)
b365abb18b is described below

commit b365abb18b20ed27bf47879b49aa253459f3af85
Author: Tianqi Chen <[email protected]>
AuthorDate: Mon Oct 19 22:29:38 2026 -0400

    [REFACTOR][IR] Localize attribute ownership and fix documentation (#20801)
    
    Use `script.namespace` for S-TIR construction, printing and lowering,
    removing the redundant `s_tir` flag. Give Trainium its own
    `trn.num_input_args` contract and annotate kernel arguments in a backend
    pass after the ordinary `SplitHostDevice` pass and before
    `MakePackedAPI`, deriving each kernel's input count from the arguments
    it actually captures.
    
    Move external-codegen constant metadata into Relax and remove unused
    C-host device-context plumbing.
    
    Repair duplicate Python API registrations and tutorial section
    boundaries so canonical IR objects and tutorial headings render
    correctly.
---
 docs/arch/tvmscript.rst                            |   4 +-
 docs/how_to/tutorials/optimize_llm.py              |   1 +
 docs/reference/api/python/script/ir_builder.rst    |   5 +-
 docs/reference/api/python/script/script.rst        |   7 +-
 include/tvm/relax/expr.h                           |  10 ++
 python/tvm/s_tir/pipeline.py                       |   2 +-
 src/relax/backend/vm/vm_shape_lower.cc             |   1 -
 src/relax/transform/compute_prim_value.cc          |   1 -
 src/relax/transform/fuse_tir.cc                    |   1 -
 src/relax/transform/run_codegen.cc                 |   3 +-
 src/s_tir/script/ir_builder/frame.cc               |  15 ---
 src/te/operation/create_function.cc                |   4 +-
 .../relax/script/test_relax_script_printer.py      |   6 +-
 .../python/s_tir/base/test_compilation_pipeline.py |   2 +
 .../s_tir/script/test_s_tir_script_ir_builder.py   |   4 +-
 .../s_tir/script/test_s_tir_script_namespace.py    |  32 +----
 .../s_tir/script/test_s_tir_script_printer.py      |   4 +-
 .../script/test_script_constructor_contracts.py    |   2 +-
 tirx/include/tvm/backend/trn/attr.h                |  41 ++++++
 tirx/include/tvm/ir/function.h                     |  22 +---
 tirx/include/tvm/ir/module.h                       |  13 --
 tirx/python/tvm_tirx/backend/trn/pipeline.py       |   1 +
 .../tvm_tirx/backend/trn/transform/__init__.py     |   9 +-
 .../trn/transform/annotate_kernel_arguments.py     | 137 +++++++++++++++++++++
 tirx/src/backend/cuda/codegen/codegen_cuda_host.cc |   2 +-
 tirx/src/backend/trn/codegen/codegen_trn.cc        |   7 +-
 tirx/src/codegen/c/codegen_c_host.cc               |  14 +--
 tirx/src/codegen/c/codegen_c_host.h                |   4 +-
 tirx/src/tirx/transform/split_host_device.cc       |  38 ++----
 tirx/tests/python/tirx/codegen/test_nki.py         |   6 +-
 30 files changed, 253 insertions(+), 145 deletions(-)

diff --git a/docs/arch/tvmscript.rst b/docs/arch/tvmscript.rst
index e4f49c8909..43cf0ccc2e 100644
--- a/docs/arch/tvmscript.rst
+++ b/docs/arch/tvmscript.rst
@@ -140,7 +140,9 @@ lookup in the active module.
 Public script namespaces expose decorators and construction operations. The 
same
 underlying builders can also be used directly from Python. The transpiler 
therefore
 needs no separate mutable IR representation: it generates calls to this 
construction
-protocol.
+protocol. ``script.namespace`` records the construction namespace for printing
+and dialect lowering. S-TIR functions use ``"s_tir"``; their builders own block
+completion and validation.
 
 Ordinary IR constructors can be used directly in parsed source. Shared exports
 such as ``I.Call`` and ``T.Range`` use the same constructor contracts as
diff --git a/docs/how_to/tutorials/optimize_llm.py 
b/docs/how_to/tutorials/optimize_llm.py
index c8f4cc1868..263a4c26ce 100644
--- a/docs/how_to/tutorials/optimize_llm.py
+++ b/docs/how_to/tutorials/optimize_llm.py
@@ -45,6 +45,7 @@ TinyLlama model from Hugging Face and deploy it on various 
devices.
 # - **Build and Universal Deployment**: Build the optimized model to a 
deployable module to the
 #   universal runtime, and execute it on different devices, such as CPU, GPU, 
or other accelerators.
 #
+
 ######################################################################
 # Construct the model architecture
 # --------------------------------
diff --git a/docs/reference/api/python/script/ir_builder.rst 
b/docs/reference/api/python/script/ir_builder.rst
index 564e9c1db6..3b3d84ce4d 100644
--- a/docs/reference/api/python/script/ir_builder.rst
+++ b/docs/reference/api/python/script/ir_builder.rst
@@ -20,10 +20,13 @@ tvm_tirx.script.ir_builder
 
 tvm_tirx.script.ir_builder
 **************************
+
+Shared IR classes are documented in :mod:`tvm_tirx.ir`.
+
 .. automodule:: tvm_tirx.script.ir_builder
    :members:
    :imported-members:
-   :exclude-members: Call, DataTypeImm, FuncType, GenericConst, MissingType, 
Op, PrimType, Range, StringImm, StringType, TensorRegion, TensorRegionType, 
Tuple, Type, Var
+   :exclude-members: Bind, Call, DataTypeImm, FuncType, GenericConst, 
MissingType, NoneType, Op, PrimType, Range, StringImm, StringType, 
TensorRegion, TensorRegionType, Tuple, Type, Var
 
 tvm.relax.script.ir_builder.distributed
 ***************************************
diff --git a/docs/reference/api/python/script/script.rst 
b/docs/reference/api/python/script/script.rst
index 8fd2ae0545..43cc6676cf 100644
--- a/docs/reference/api/python/script/script.rst
+++ b/docs/reference/api/python/script/script.rst
@@ -29,10 +29,11 @@ tvm.relax.script
 ****************
 
 Operator attributes re-exported by this namespace are documented in 
:mod:`tvm.relax.op`.
+The shared ``NoneType`` class is documented in :mod:`tvm_tirx.ir`.
 
 .. automodule:: tvm.relax.script
    :members:
-   :exclude-members: ExternFunc, ShapeExpr, TupleGetItem, Range, function, 
macro,
+   :exclude-members: ExternFunc, NoneType, ShapeExpr, TupleGetItem, Range, 
function, macro,
       ArgmaxArgminAttrs, ArgsortAttrs, AstypeAttrs, CallInplacePackedAttrs, 
CallTIRInplaceAttrs,
       CallTIRWithGradAttrs, ConcatAttrs, EinsumAttrs, ExpandDimsAttrs, 
FlipAttrs,
       GatherElementsAttrs, GatherNDAttrs, HintOnDeviceAttrs, IndexPutAttrs, 
InitAttrs,
@@ -56,8 +57,8 @@ The canonical ``MemoryScope`` class is documented in 
:mod:`tvm_tirx.runtime`.
 
 .. automodule:: tvm_tirx.script.tirx
    :members:
-   :exclude-members: AlreadyEmitted, DataTypeImm, FuncType, GenericConst, 
IRBuilder,
-      IRModuleFrame, MemoryScope, MissingType, Op, PrimType, StringImm, 
StringType, TensorLoad, TensorRegion,
+   :exclude-members: AlreadyEmitted, Bind, DataTypeImm, FuncType, 
GenericConst, IRBuilder,
+      IRModuleFrame, MemoryScope, MissingType, NoneType, Op, PrimType, 
StringImm, StringType, TensorLoad, TensorRegion,
       TensorRegionType, Type, Range, meta_var, Var, Call, CommReducer, Reduce, 
FloatImm, IntImm, Cast, Add, Sub, Mul, Div, Mod, FloorDiv, FloorMod, LShift, 
RShift, BitwiseAnd, BitwiseOr, BitwiseXor, BitwiseNot, Min, Max, EQ, NE, LT, 
LE, GT, GE, And, Or, Not, Select, Ramp, Broadcast, Shuffle, CallEffectKind, 
IterVar, ComposeLayout, DtypeConstructor, ThreadScope, Iter, Layout, 
LetAnnotation, LocalVectorAnnotation, TileLayout, CallFFIKernelAttr, 
TensorMapEncodeTiledAttr, function, jit, inli [...]
 
 .. autofunction:: tvm_tirx.script.tirx.function
diff --git a/include/tvm/relax/expr.h b/include/tvm/relax/expr.h
index ec27709cb3..7374ab124d 100644
--- a/include/tvm/relax/expr.h
+++ b/include/tvm/relax/expr.h
@@ -495,6 +495,16 @@ TVM_DLL Expr GetShapeOf(const Expr& expr);
 // TODO(@sunggg): Investigate the exact usage of kComposite, 
kPartitionedFromPattern, and
 // kPrimitive.
 namespace attr {
+/*!
+ * \brief All the named Tensors accumulated during compilation by external 
codegen.
+ * Generally the associated runtime::Module will indicate it requires bindings 
for these names,
+ * and during module initialization these bindings will be recovered from a 
ConstLoaderModule.
+ *
+ * Type: ffi::Map<ffi::String, Tensor>
+ */
+inline constexpr ffi::reflection::AttrKey<ffi::Map<ffi::String, ffi::Tensor>> 
kConstNameToConstant{
+    "const_name_to_constant"};
+
 /*! \brief Mark the function as a primitive function. */
 inline constexpr ffi::reflection::AttrKey<bool> kPrimitive{"Primitive"};
 /*!
diff --git a/python/tvm/s_tir/pipeline.py b/python/tvm/s_tir/pipeline.py
index 56390ae988..dea38d3196 100644
--- a/python/tvm/s_tir/pipeline.py
+++ b/python/tvm/s_tir/pipeline.py
@@ -127,7 +127,7 @@ def _partition_dialects(mod):
     for gv, func in mod.functions.items():
         group = (
             scheduled
-            if isinstance(func, tirx.Function) and func.attrs.get("s_tir", 
False)
+            if isinstance(func, tirx.Function) and 
func.attrs.get("script.namespace") == "s_tir"
             else native
         )
         group[gv] = func
diff --git a/src/relax/backend/vm/vm_shape_lower.cc 
b/src/relax/backend/vm/vm_shape_lower.cc
index 0be9aa8f72..977c3739d1 100644
--- a/src/relax/backend/vm/vm_shape_lower.cc
+++ b/src/relax/backend/vm/vm_shape_lower.cc
@@ -750,7 +750,6 @@ class VMShapeLowerMutator
     // the shape_func to indicate that this is a host function
     // This could require us to attach target to the relax function here.
     tirx::Function shape_func(params, body, ret_type);
-    shape_func = WithAttr(std::move(shape_func), tvm::attr::kSTir.name, true);
     shape_func =
         WithAttr(std::move(shape_func), tvm::attr::kScriptNamespace.name, 
ffi::String("s_tir"));
     if 
(!shape_func->attrs.GetAttr<tvm::Target>(tvm::attr::kTarget.name).has_value()) {
diff --git a/src/relax/transform/compute_prim_value.cc 
b/src/relax/transform/compute_prim_value.cc
index 18b4d5733c..1bb1edb000 100644
--- a/src/relax/transform/compute_prim_value.cc
+++ b/src/relax/transform/compute_prim_value.cc
@@ -118,7 +118,6 @@ class PrimExprComputeInjector : public ExprMutator {
 
     tirx::Function func(param_vars, tvm::SeqStmt(body), ret_ty,
                         DictAttrs({{tvm::tirx::attr::kIsHostFunc.name, true},
-                                   {tvm::attr::kSTir.name, true},
                                    {tvm::attr::kScriptNamespace.name, 
ffi::String("s_tir")}}));
     func = tirx::RenewDef(func);
 
diff --git a/src/relax/transform/fuse_tir.cc b/src/relax/transform/fuse_tir.cc
index bf4daa52ec..8d75beece6 100644
--- a/src/relax/transform/fuse_tir.cc
+++ b/src/relax/transform/fuse_tir.cc
@@ -866,7 +866,6 @@ class FusedTIRConstructor : public ExprVisitor {
   tirx::Function ConstructFunc() {
     ffi::Map<ffi::String, Any> attr_map;
     attr_map.Set(tvm::tirx::attr::kNoAlias.name, true);
-    attr_map.Set(tvm::attr::kSTir.name, true);
     attr_map.Set(tvm::attr::kScriptNamespace.name, ffi::String("s_tir"));
     auto subst = 
ffi::make_object<tirx::FuseTIRTensorSubstitutor>(func_info_.tensor_subst_map,
                                                                   
func_info_.symbolic_var_remap);
diff --git a/src/relax/transform/run_codegen.cc 
b/src/relax/transform/run_codegen.cc
index b6b5b7b9e6..f425768157 100644
--- a/src/relax/transform/run_codegen.cc
+++ b/src/relax/transform/run_codegen.cc
@@ -100,7 +100,8 @@ class CodeGenRunner : ExprMutator {
         TVM_FFI_ICHECK(!constants.count(name)) << "More than one constant with 
the name " << name;
         constants.Set(name, constant->value.cast<ffi::Tensor>());
       }
-      out_mod = WithAttr(out_mod, tvm::attr::kConstNameToConstant.name, 
std::move(constants));
+      out_mod =
+          WithAttr(out_mod, tvm::relax::attr::kConstNameToConstant.name, 
std::move(constants));
     }
 
     // TODO(@tvm-team): Implicit pass dependency. Revisit when we have a 
better way to handle this.
diff --git a/src/s_tir/script/ir_builder/frame.cc 
b/src/s_tir/script/ir_builder/frame.cc
index c0da42aaf5..4ad1cf37a0 100644
--- a/src/s_tir/script/ir_builder/frame.cc
+++ b/src/s_tir/script/ir_builder/frame.cc
@@ -33,20 +33,6 @@ TVM_FFI_STATIC_INIT_BLOCK() {
   FunctionFrameObj::RegisterReflection();
   SBlockFrameObj::RegisterReflection();
   BlockInitFrameObj::RegisterReflection();
-  tirx::FunctionFrameObj::RegisterAttrValidator(
-      tvm::attr::kSTir.name, [](const tirx::FunctionFrameObj* frame, const 
ffi::Any& value) {
-        TVM_FFI_CHECK(frame->IsInstance<FunctionFrameObj>(), ValueError)
-            << "The s_tir attribute requires Ts.function";
-        bool enabled = false;
-        if (auto flag = value.as<bool>()) {
-          enabled = *flag;
-        } else if (auto flag = value.as<int64_t>()) {
-          enabled = *flag != 0;
-        } else if (auto flag = value.as<tvm::IntImm>()) {
-          enabled = !tvm::prim::IsZero(*flag);
-        }
-        TVM_FFI_CHECK(enabled, ValueError) << "Ts.function cannot disable the 
s_tir attribute";
-      });
 }
 
 tvm::tirx::Function FunctionFrameObj::FinalizeFunction(tvm::tirx::Function 
func) {
@@ -54,7 +40,6 @@ tvm::tirx::Function 
FunctionFrameObj::FinalizeFunction(tvm::tirx::Function func)
   TVM_FFI_CHECK(!is_declaration || root_alloc_tensors.empty(), ValueError)
       << "A function declaration cannot allocate tensors";
   if (!is_declaration) {
-    func = WithAttr(std::move(func), tvm::attr::kSTir.name, true);
     func =
         tvm::s_tir::ScriptComplete(std::move(func), root_alloc_tensors, 
root_allocated_addresses);
   }
diff --git a/src/te/operation/create_function.cc 
b/src/te/operation/create_function.cc
index db722e9b1f..732a331bfb 100644
--- a/src/te/operation/create_function.cc
+++ b/src/te/operation/create_function.cc
@@ -916,7 +916,7 @@ Function GenerateAndCompleteFunction(const 
ffi::Array<te::Tensor>& arg_list,
                                      /*ret_type=*/VoidType()),
                             {{tvm::attr::kGlobalSymbol.name, 
ffi::String("main")},
                              {tvm::tirx::attr::kNoAlias.name, true},
-                             {tvm::attr::kSTir.name, true}});
+                             {tvm::attr::kScriptNamespace.name, 
ffi::String("s_tir")}});
   const auto fcomplete = 
tvm::ffi::Function::GetGlobal("s_tir.script.Complete");
   TVM_FFI_ICHECK(fcomplete.has_value());
   func = (*fcomplete)(std::move(func), info->root_alloc).cast<Function>();
@@ -988,7 +988,7 @@ Function GenerateAndCompleteFunction(const 
ffi::Array<ffi::ObjectRef>& arg_tir_v
                                      /*ret_type=*/VoidType()),
                             {{tvm::attr::kGlobalSymbol.name, 
ffi::String("main")},
                              {tvm::tirx::attr::kNoAlias.name, true},
-                             {tvm::attr::kSTir.name, true}});
+                             {tvm::attr::kScriptNamespace.name, 
ffi::String("s_tir")}});
   const auto fcomplete = 
tvm::ffi::Function::GetGlobal("s_tir.script.Complete");
   TVM_FFI_ICHECK(fcomplete.has_value());
   func = (*fcomplete)(std::move(func), info->root_alloc).cast<Function>();
diff --git a/tests/python/relax/script/test_relax_script_printer.py 
b/tests/python/relax/script/test_relax_script_printer.py
index 2862600443..ded8bb4be2 100644
--- a/tests/python/relax/script/test_relax_script_printer.py
+++ b/tests/python/relax/script/test_relax_script_printer.py
@@ -195,7 +195,7 @@ class Module:
     T.module_global_infos({"mesh": [R.device_mesh((2, 2), T.Range(T.int32(0), 
T.int32(4))), R.device_mesh((1,), T.Range(T.int32(4), T.int32(5)))]})
     @Ts.function
     def tir_func(x: T.Tensor((T.int64(128), T.int64(128)), T.float32), y: 
T.Tensor((T.int64(128), T.int64(128)), T.float32)):
-        T.func_attr({"s_tir": True, "script.namespace": "s_tir", 
"tirx.noalias": True})
+        T.func_attr({"script.namespace": "s_tir", "tirx.noalias": True})
         with Ts.sblock("root"):
             Ts.reads()
             Ts.writes()
@@ -970,7 +970,7 @@ from __future__ import annotations
 class Module:
     @Ts.function
     def tir_func(x: T.Tensor((T.int64(128),), T.float32), y: 
T.Tensor((T.int64(128),), T.float32)):
-        T.func_attr({"s_tir": True, "script.namespace": "s_tir"})
+        T.func_attr({"script.namespace": "s_tir"})
 
     @R.function
     def foo(x: R.Tensor((128,), dtype="float32")) -> R.Tensor((128,), 
dtype="float32"):
@@ -994,7 +994,7 @@ from __future__ import annotations
 class Module:
     @Ts.function
     def tir_func(x: T.Tensor((T.int64(128),), T.float32), y: 
T.Tensor((T.int64(128),), T.float32)):
-        T.func_attr({"s_tir": True, "script.namespace": "s_tir"})
+        T.func_attr({"script.namespace": "s_tir"})
 
     @R.function
     def foo(x: R.Tensor((128,), dtype="float32")) -> R.Tensor((128,), 
dtype="float32"):
diff --git a/tests/python/s_tir/base/test_compilation_pipeline.py 
b/tests/python/s_tir/base/test_compilation_pipeline.py
index d076fe110b..a23c7ee503 100644
--- a/tests/python/s_tir/base/test_compilation_pipeline.py
+++ b/tests/python/s_tir/base/test_compilation_pipeline.py
@@ -41,6 +41,8 @@ def test_default_pipeline_selects_dialect(mixed):
         for i in range(16):
             B[i] = A[i] + T.float32(2)
 
+    assert "s_tir" not in scheduled.attrs
+    assert scheduled.attrs["script.namespace"] == "s_tir"
     functions = {"scheduled": scheduled}
     if mixed:
         functions["native"] = native
diff --git a/tests/python/s_tir/script/test_s_tir_script_ir_builder.py 
b/tests/python/s_tir/script/test_s_tir_script_ir_builder.py
index c069eab951..82e179e1d3 100644
--- a/tests/python/s_tir/script/test_s_tir_script_ir_builder.py
+++ b/tests/python/s_tir/script/test_s_tir_script_ir_builder.py
@@ -42,7 +42,7 @@ def test_ir_builder_tir_function_base():
         params=[],
         body=tvm_tirx.ir.Evaluate(0),
         ret_type=None,
-        attrs=tvm_tirx.ir.make_node("ir.DictAttrs", **{"s_tir": True, 
"script.namespace": "s_tir"}),
+        attrs=tvm_tirx.ir.make_node("ir.DictAttrs", **{"script.namespace": 
"s_tir"}),
     )
 
     # Check if the generated ir is expected
@@ -80,7 +80,7 @@ def test_ir_builder_tir_function_complete():
         body=tvm_tirx.ir.Evaluate(0),
         ret_type=tvm_tirx.ir.PrimType("int64"),
         attrs=tvm_tirx.ir.make_node(
-            "ir.DictAttrs", **{"key": "value", "s_tir": True, 
"script.namespace": "s_tir"}
+            "ir.DictAttrs", **{"key": "value", "script.namespace": "s_tir"}
         ),
     )
 
diff --git a/tests/python/s_tir/script/test_s_tir_script_namespace.py 
b/tests/python/s_tir/script/test_s_tir_script_namespace.py
index 482383c835..764357be01 100644
--- a/tests/python/s_tir/script/test_s_tir_script_namespace.py
+++ b/tests/python/s_tir/script/test_s_tir_script_namespace.py
@@ -44,11 +44,11 @@ def test_shared_operations_and_aliases():
                 v = Ts.axis.spatial(A.shape[0], i)
                 A[v] = S.float32(1)
 
-    assert shared.attrs["s_tir"]
+    assert shared.attrs["script.namespace"] == "s_tir"
     assert shared.params[0].ty.layout is None
     assert Ts.Tensor is T.Tensor
     assert Ts.serial is T.serial
-    assert Ts.bind is T.bind
+    assert Ts.Bind is T.Bind
     assert Ts.tile is T.tile
     tvm_tirx.ir.assert_structural_equal(
         shared,
@@ -74,8 +74,8 @@ def test_mixed_module_roundtrip():
             for i in T.serial(4):
                 A[i] = 2.0
 
-    assert Mixed["scheduled"].attrs["s_tir"]
-    assert not Mixed["direct"].attrs.get("s_tir", False)
+    assert Mixed["scheduled"].attrs["script.namespace"] == "s_tir"
+    assert Mixed["direct"].attrs.get("script.namespace") != "s_tir"
     script = Mixed.script()
     assert "@Ts.function" in script
     assert "@T.function" in script
@@ -172,7 +172,7 @@ def test_s_tir_options_and_helpers():
     def scheduled(A: Ts.Tensor((4,), "float32")):
         fill(A)
 
-    assert scheduled.attrs["s_tir"]
+    assert scheduled.attrs["script.namespace"] == "s_tir"
     assert "global_symbol" not in scheduled.attrs
     tvm_tirx.ir.assert_structural_equal(
         scheduled,
@@ -223,27 +223,5 @@ def test_import_order(first):
     )
 
 
[email protected]("value", [True, False])
-def test_tirx_cannot_change_dialect_with_attribute(value):
-    with pytest.raises(ValueError, match="Ts.function"):
-        tvm_tirx.script.from_source(
-            "@T.function(check_well_formed=False)\n"
-            f"def main():\n    T.func_attr({{'s_tir': {value}}})\n    
T.evaluate(0)\n",
-            extra_vars={"I": tvm_tirx.script.ir, "T": tvm_tirx.script.tirx, 
"Ts": tvm.s_tir.script},
-        )
-
-
[email protected]("value", [True, False])
-def test_tirx_cannot_change_dialect_from_exit_callback(value):
-    from tvm_tirx.script.ir_builder import IRBuilder
-    from tvm_tirx.tirx.script import ir_builder as builder
-
-    with pytest.raises(ValueError, match="Ts.function"):
-        with IRBuilder():
-            with builder.function() as frame:
-                frame.add_callback(lambda: builder.func_attr({"s_tir": value}))
-                builder.evaluate(0)
-
-
 if __name__ == "__main__":
     tvm_tirx.testing.main()
diff --git a/tests/python/s_tir/script/test_s_tir_script_printer.py 
b/tests/python/s_tir/script/test_s_tir_script_printer.py
index ab1a0d8cf2..9f2886a1c6 100644
--- a/tests/python/s_tir/script/test_s_tir_script_printer.py
+++ b/tests/python/s_tir/script/test_s_tir_script_printer.py
@@ -142,7 +142,7 @@ def test_remap():
 
 @Ts.function
 def main():
-    T.func_attr({"s_tir": True, "script.namespace": "s_tir"})
+    T.func_attr({"script.namespace": "s_tir"})
     with Ts.sblock("root"):
         Ts.reads()
         Ts.writes()
@@ -190,7 +190,7 @@ def test_root_block():
 
 @Ts.function
 def main():
-    T.func_attr({"s_tir": True, "script.namespace": "s_tir"})
+    T.func_attr({"script.namespace": "s_tir"})
     with Ts.sblock("root"):
         Ts.reads()
         Ts.writes()
diff --git a/tests/python/s_tir/script/test_script_constructor_contracts.py 
b/tests/python/s_tir/script/test_script_constructor_contracts.py
index 94be4cd150..daf986a753 100644
--- a/tests/python/s_tir/script/test_script_constructor_contracts.py
+++ b/tests/python/s_tir/script/test_script_constructor_contracts.py
@@ -42,7 +42,7 @@ def test_cast_script_positional_and_keyword_type():
         S.evaluate(S.cast(x, ty="float32"))
 
     assert S.cast is T.cast
-    assert positional.attrs["s_tir"]
+    assert "s_tir" not in positional.attrs
     assert positional.attrs["script.namespace"] == "s_tir"
     ir.assert_structural_equal(positional, keyword)
     cast_expr = positional.body[0].value
diff --git a/tirx/include/tvm/backend/trn/attr.h 
b/tirx/include/tvm/backend/trn/attr.h
new file mode 100644
index 0000000000..791e2669b4
--- /dev/null
+++ b/tirx/include/tvm/backend/trn/attr.h
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+/*!
+ * \file tvm/backend/trn/attr.h
+ * \brief Trainium backend attribute keys.
+ */
+#ifndef TVM_BACKEND_TRN_ATTR_H_
+#define TVM_BACKEND_TRN_ATTR_H_
+
+#include <tvm/ffi/reflection/accessor.h>
+
+#include <cstdint>
+
+namespace tvm {
+namespace backend {
+namespace trn {
+namespace attr {
+/*! \brief Number of input arguments preceding the mutable output arguments. */
+inline constexpr ffi::reflection::AttrKey<int64_t> 
kNumInputArgs{"trn.num_input_args"};
+}  // namespace attr
+}  // namespace trn
+}  // namespace backend
+}  // namespace tvm
+
+#endif  // TVM_BACKEND_TRN_ATTR_H_
diff --git a/tirx/include/tvm/ir/function.h b/tirx/include/tvm/ir/function.h
index 0511324526..95992db20b 100644
--- a/tirx/include/tvm/ir/function.h
+++ b/tirx/include/tvm/ir/function.h
@@ -252,31 +252,13 @@ inline constexpr ffi::reflection::AttrKey<Target> 
kTarget{"target"};
 inline constexpr ffi::reflection::AttrKey<ffi::String> 
kGlobalSymbol{"global_symbol"};
 
 /*!
- * \brief The function uses s_tir (apache-derived TIR) semantics:
- *        parser fills layout=None, ScriptComplete wraps body in a root SBlock,
- *        independent of the script construction namespace.
- *        Default (attr absent or False) is tirx semantics.
- *
- * Type: IntImm (bool dtype)
- */
-inline constexpr ffi::reflection::AttrKey<bool> kSTir{"s_tir"};
-
-/*!
- * \brief Script construction namespace used to select the function decorator.
- * Absent metadata selects the function kind's default namespace. This does not
- * change the function's semantic dialect.
+ * \brief Script namespace used to select construction, printing, and dialect 
lowering.
+ * Absent metadata selects the function kind's default namespace.
  *
  * Type: String
  */
 inline constexpr ffi::reflection::AttrKey<ffi::String> 
kScriptNamespace{"script.namespace"};
 
-/*!
- * \brief Number of inputs of the Function
- *
- * Type: Int
- */
-inline constexpr ffi::reflection::AttrKey<int64_t> kNumInputs{"num_inputs"};
-
 }  // namespace attr
 
 inline LinkageType BaseFuncNode::GetLinkageType() const {
diff --git a/tirx/include/tvm/ir/module.h b/tirx/include/tvm/ir/module.h
index c0a157fcd7..9a22f91a46 100644
--- a/tirx/include/tvm/ir/module.h
+++ b/tirx/include/tvm/ir/module.h
@@ -353,19 +353,6 @@ inline constexpr 
ffi::reflection::AttrKey<ffi::Array<ffi::Module>> kExternalMods
  */
 inline constexpr ffi::reflection::AttrKey<ffi::String> 
kSystemLibPrefix{"system_lib_prefix"};
 
-/*!
- * \brief All the named Tensors accumulated during compilation by external 
codegen.
- * Generally the associated runtime::Module will indicate it requires bindings 
for these names,
- * and during module initialization these bindings will be recovered from a 
ConstLoaderModule.
- *
- * Type: ffi::Map<ffi::String, Tensor>
- */
-inline constexpr ffi::reflection::AttrKey<ffi::Map<ffi::String, ffi::Tensor>> 
kConstNameToConstant{
-    "const_name_to_constant"};
-
-inline constexpr ffi::reflection::AttrKey<ffi::Map<GlobalVar, ffi::String>> 
kDeviceContexts{
-    "device_contexts"};
-
 }  // namespace attr
 }  // namespace tvm
 #endif  // TVM_IR_MODULE_H_
diff --git a/tirx/python/tvm_tirx/backend/trn/pipeline.py 
b/tirx/python/tvm_tirx/backend/trn/pipeline.py
index afccc76832..146b451ff8 100644
--- a/tirx/python/tvm_tirx/backend/trn/pipeline.py
+++ b/tirx/python/tvm_tirx/backend/trn/pipeline.py
@@ -56,6 +56,7 @@ def trn_pipeline(*, target=None):
             tirx.transform.RemoveNoOp(),
             tirx.transform.AnnotateEntryFunc(),
             tirx.transform.SplitHostDevice(),
+            trn_transform.AnnotateKernelArguments(),
             tirx.transform.MakePackedAPI(),
         ]
         return tvm_tirx.ir.transform.Sequential(passes)(mod)
diff --git a/tirx/python/tvm_tirx/backend/trn/transform/__init__.py 
b/tirx/python/tvm_tirx/backend/trn/transform/__init__.py
index 3f311cc17f..d0df43a06c 100644
--- a/tirx/python/tvm_tirx/backend/trn/transform/__init__.py
+++ b/tirx/python/tvm_tirx/backend/trn/transform/__init__.py
@@ -23,6 +23,7 @@ import tvm_tirx
 from tvm_tirx import tirx
 
 _LAZY_TRANSFORMS = {
+    "AnnotateKernelArguments": ".annotate_kernel_arguments",
     "TrnNaiveAllocator": ".naive_allocator",
     "TrnPrivateTensorAlloc": ".private_tensor_alloc",
 }
@@ -90,4 +91,10 @@ def __getattr__(name):
     return getattr(import_module(target, __name__), name)
 
 
-__all__ = ["LowerTIRx", "LowerTrainiumLayout", "TrnNaiveAllocator", 
"TrnPrivateTensorAlloc"]
+__all__ = [
+    "AnnotateKernelArguments",
+    "LowerTIRx",
+    "LowerTrainiumLayout",
+    "TrnNaiveAllocator",
+    "TrnPrivateTensorAlloc",
+]
diff --git 
a/tirx/python/tvm_tirx/backend/trn/transform/annotate_kernel_arguments.py 
b/tirx/python/tvm_tirx/backend/trn/transform/annotate_kernel_arguments.py
new file mode 100644
index 0000000000..374aeb87e1
--- /dev/null
+++ b/tirx/python/tvm_tirx/backend/trn/transform/annotate_kernel_arguments.py
@@ -0,0 +1,137 @@
+# 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.
+"""Apply the Trainium input/output contract after host/device splitting."""
+
+import tvm_ffi
+
+import tvm_tirx
+from tvm_tirx import tirx
+from tvm_tirx.ir import Bind, Call, GlobalVar, Op, Var
+from tvm_tirx.ir.transform import module_pass
+
+
+@module_pass(opt_level=0, name="AnnotateKernelArguments")
+class AnnotateKernelArguments:
+    """Order kernel inputs before outputs and annotate their per-kernel input 
count.
+
+    Run after ``SplitHostDevice`` and before ``MakePackedAPI``. The source
+    ``trn.num_input_args`` contract remains on each host function until its
+    kernel calls have been matched to the surviving host parameters and 
captures.
+    Functions without extracted kernels retain their own contract.
+    """
+
+    def transform_module(self, mod, _ctx):
+        attr = "trn.num_input_args"
+        kernels = {
+            gv.name_hint: gv
+            for gv, func in mod.functions.items()
+            if isinstance(func, tirx.Function)
+            and func.attrs.get("target") is not None
+            and func.attrs["target"].kind.name == "trn"
+            and func.attrs.get("tirx.is_global_func", False)
+            and attr not in func.attrs
+        }
+        updates = {}
+        for gv, host in mod.functions.items():
+            if not isinstance(host, tirx.Function) or attr not in host.attrs:
+                continue
+            num_inputs = host.attrs[attr]
+            if not isinstance(num_inputs, int) or not 0 <= num_inputs <= 
len(host.params):
+                raise ValueError("trn.num_input_args must be between zero and 
the parameter count")
+            param_order = {param: i for i, param in enumerate(host.params)}
+            has_kernel = False
+            bindings = {}
+            tvm_ffi.structural_walk(
+                host.body, (Bind, lambda bind: bindings.update({bind.var: 
bind.value}))
+            )
+
+            def reorder(call):
+                nonlocal has_kernel
+                offset = 0
+                if isinstance(call.op, GlobalVar):
+                    name = call.op.name_hint
+                elif isinstance(call.op, Op) and call.op.name == 
"tirx.call_ffi_kernel":
+                    name = call.args[0].value
+                    offset = 1
+                else:
+                    return call
+                if name not in kernels:
+                    return call
+                has_kernel = True
+                kernel_gv = kernels[name]
+                kernel = mod[kernel_gv]
+                args = list(call.args[offset : offset + len(kernel.params)])
+
+                def source_position(arg):
+                    # A device region may capture a tensor alias declared on 
the host.
+                    # Follow its pointer projection back to the source 
parameter.
+                    while True:
+                        if isinstance(arg, Var):
+                            if arg in param_order:
+                                return param_order[arg]
+                            if arg not in bindings:
+                                return None
+                            arg = bindings[arg]
+                        elif (
+                            isinstance(arg, Call)
+                            and arg.args
+                            and isinstance(arg.op, Op)
+                            and arg.op.name in ("tirx.tensor_data_ptr", 
"tirx.decl_tensor")
+                        ):
+                            arg = arg.args[0]
+                        else:
+                            return None
+
+                positions = [source_position(arg) for arg in args]
+
+                # Captures are additional inputs. Only surviving host outputs 
are mutable
+                # outputs; unused host inputs must not inflate the kernel's 
input count.
+                def sort_key(i):
+                    position = positions[i]
+                    if position is None:
+                        return (1, i)
+                    return (2 if position >= num_inputs else 0, position)
+
+                order = sorted(range(len(args)), key=sort_key)
+                input_count = sum(pos is None or pos < num_inputs for pos in 
positions)
+                updates[kernel_gv] = tirx.Function(
+                    [kernel.params[i] for i in order],
+                    kernel.body,
+                    kernel.ret_type,
+                    kernel.attrs,
+                    kernel.loc,
+                ).with_attr(attr, input_count)
+                return Call(
+                    call.op,
+                    [
+                        *call.args[:offset],
+                        *(args[i] for i in order),
+                        *call.args[offset + len(args) :],
+                    ],
+                    attrs=call.attrs,
+                    ty_args=call.ty_args,
+                    loc=call.loc,
+                    ty=call.ty,
+                )
+
+            updated = host.with_body(
+                tvm_ffi.structural_map(host.body, (Call, reorder), 
order="post")
+            )
+            if has_kernel:
+                updates[gv] = updated.without_attr(attr)
+        mod.update(tvm_tirx.IRModule(updates))
+        return mod
diff --git a/tirx/src/backend/cuda/codegen/codegen_cuda_host.cc 
b/tirx/src/backend/cuda/codegen/codegen_cuda_host.cc
index 867ec37123..f3a5ce0b1b 100644
--- a/tirx/src/backend/cuda/codegen/codegen_cuda_host.cc
+++ b/tirx/src/backend/cuda/codegen/codegen_cuda_host.cc
@@ -50,7 +50,7 @@ class CodeGenCUDAHost : public CodeGenCHost {
   using CodeGenCHost::Dispatch_;
 
   void Init(Target target) {
-    CodeGenCHost::Init(false, true, true, target->str(), {});
+    CodeGenCHost::Init(false, true, true, target->str());
     // Retain C-host wrapper initialization, with an FFI/CUDA-only prelude.
     decl_stream.str("");
     decl_stream.clear();
diff --git a/tirx/src/backend/trn/codegen/codegen_trn.cc 
b/tirx/src/backend/trn/codegen/codegen_trn.cc
index 260d9f3c82..48a49c2d34 100644
--- a/tirx/src/backend/trn/codegen/codegen_trn.cc
+++ b/tirx/src/backend/trn/codegen/codegen_trn.cc
@@ -22,6 +22,7 @@
  */
 #include "codegen_trn.h"
 
+#include <tvm/backend/trn/attr.h>
 #include <tvm/ffi/extra/structural_visit.h>
 #include <tvm/ir/function.h>
 #include <tvm/ir/prim/expr.h>
@@ -114,8 +115,10 @@ void CodeGenTrainium::AddFunction(const GlobalVar& gvar, 
const Function& func) {
   this->stream << "def " << static_cast<std::string>(global_symbol.value()) << 
"(";
 
   // Buffer arguments
-  auto num_inputs = func->GetAttr(tvm::attr::kNumInputs);
-  TVM_FFI_ICHECK(num_inputs.has_value());
+  auto num_inputs = func->GetAttr(tvm::backend::trn::attr::kNumInputArgs);
+  TVM_FFI_CHECK(num_inputs.has_value(), ValueError) << "Trainium requires 
trn.num_input_args";
+  TVM_FFI_CHECK_GE(num_inputs.value(), 0, ValueError);
+  TVM_FFI_CHECK_LE(num_inputs.value(), 
static_cast<int64_t>(func->params.size()), ValueError);
   std::vector<std::string> output_vids;
   size_t num_buffer = 0;
   for (size_t i = 0; i < func->params.size(); ++i, ++num_buffer) {
diff --git a/tirx/src/codegen/c/codegen_c_host.cc 
b/tirx/src/codegen/c/codegen_c_host.cc
index cc37cb66c8..152000c639 100644
--- a/tirx/src/codegen/c/codegen_c_host.cc
+++ b/tirx/src/codegen/c/codegen_c_host.cc
@@ -32,7 +32,6 @@
 
 #include <algorithm>
 #include <string>
-#include <unordered_set>
 #include <utility>
 #include <vector>
 
@@ -44,7 +43,7 @@ CodeGenCHost::CodeGenCHost() {
 }
 
 void CodeGenCHost::Init(bool output_ssa, bool emit_asserts, bool 
emit_fwd_func_decl,
-                        std::string target_str, const 
std::unordered_set<std::string>& devices) {
+                        std::string target_str) {
   emit_asserts_ = emit_asserts;
   emit_fwd_func_decl_ = emit_fwd_func_decl;
   declared_globals_.clear();
@@ -378,17 +377,8 @@ ffi::Module BuildCHost(IRModule mod, Target target) {
   bool emit_asserts = true;
   bool emit_fwd_func_decl = true;
 
-  std::unordered_set<std::string> devices;
-  if (mod->GetAttr(tvm::attr::kDeviceContexts) != nullptr) {
-    ffi::Map<GlobalVar, ffi::String> device_contexts =
-        mod->GetAttr(tvm::attr::kDeviceContexts).value();
-    for (auto const& context : device_contexts) {
-      devices.insert(context.second.data());
-    }
-  }
-
   CodeGenCHost cg;
-  cg.Init(output_ssa, emit_asserts, emit_fwd_func_decl, target->str(), 
devices);
+  cg.Init(output_ssa, emit_asserts, emit_fwd_func_decl, target->str());
   
cg.SetConstantsByteAlignment(target->GetAttr<int64_t>("constants-byte-alignment").value_or(16));
 
   std::vector<std::pair<GlobalVar, Function>> funcs;
diff --git a/tirx/src/codegen/c/codegen_c_host.h 
b/tirx/src/codegen/c/codegen_c_host.h
index e8fd3bfde3..5421ff3def 100644
--- a/tirx/src/codegen/c/codegen_c_host.h
+++ b/tirx/src/codegen/c/codegen_c_host.h
@@ -28,7 +28,6 @@
 
 #include <string>
 #include <unordered_map>
-#include <unordered_set>
 #include <utility>
 #include <vector>
 
@@ -41,8 +40,7 @@ namespace codegen {
 class CodeGenCHost : public CodeGenC {
  public:
   CodeGenCHost();
-  void Init(bool output_ssa, bool emit_asserts, bool emit_fwd_func_decl, 
std::string target_str,
-            const std::unordered_set<std::string>& devices);
+  void Init(bool output_ssa, bool emit_asserts, bool emit_fwd_func_decl, 
std::string target_str);
 
   void InitGlobalContext();
 
diff --git a/tirx/src/tirx/transform/split_host_device.cc 
b/tirx/src/tirx/transform/split_host_device.cc
index bb6875a12d..b75042b17a 100644
--- a/tirx/src/tirx/transform/split_host_device.cc
+++ b/tirx/src/tirx/transform/split_host_device.cc
@@ -157,27 +157,17 @@ class HostDeviceSplitter : public StmtExprMutator {
 
       // Sort first by variable type, then by variable name
       std::vector<Var> params{undefined.begin(), undefined.end()};
-      if (device_target->kind->name != "trn") {
-        std::sort(params.begin(), params.end(), [](const Var& a, const Var& b) 
{
-          auto sort_key = [](const Var& var) {
-            bool is_handle =
-                var->ty.as<PtrTypeNode>() != nullptr || 
var->ty.as<TensorTypeNode>() != nullptr;
-            return std::tuple{
-                !is_handle,
-                var->name,
-            };
+      std::sort(params.begin(), params.end(), [](const Var& a, const Var& b) {
+        auto sort_key = [](const Var& var) {
+          bool is_handle =
+              var->ty.as<PtrTypeNode>() != nullptr || 
var->ty.as<TensorTypeNode>() != nullptr;
+          return std::tuple{
+              !is_handle,
+              var->name,
           };
-          return sort_key(a) < sort_key(b);
-        });
-      } else {
-        std::unordered_map<Var, int, ffi::ObjectPtrHash, ffi::ObjectPtrEqual> 
param_order;
-        for (size_t i = 0; i < cur_func_->params.size(); ++i) {
-          
param_order[cur_func_->params[i].as_or_throw<tvm::tirx::TensorVar>()] = i;
-        }
-        // sort by original order
-        std::sort(params.begin(), params.end(),
-                  [&](const Var& a, const Var& b) { return param_order[a] < 
param_order[b]; });
-      }
+        };
+        return sort_key(a) < sort_key(b);
+      });
       return {params, tensors};
     }();
 
@@ -246,19 +236,11 @@ class HostDeviceSplitter : public StmtExprMutator {
       device_func = WithAttr(std::move(device_func), 
tvm::attr::kScriptNamespace.name,
                              script_namespace.value());
     }
-    bool is_stir = cur_func_->attrs->dict.count(tvm::attr::kSTir.name);
-    if (is_stir) {
-      device_func = WithAttr(std::move(device_func), tvm::attr::kSTir.name, 
true);
-    }
     if (auto launch_params = cur_func_->GetAttr<ffi::Array<ffi::String>>(
             tvm::tirx::attr::kKernelLaunchParams.name)) {
       device_func = WithAttr(std::move(device_func), 
tvm::tirx::attr::kKernelLaunchParams.name,
                              launch_params.value());
     }
-    auto num_inputs = cur_func_->GetAttr(tvm::attr::kNumInputs);
-    if (num_inputs.has_value()) {
-      device_func = WithAttr(std::move(device_func), 
tvm::attr::kNumInputs.name, num_inputs);
-    }
     GlobalVar kernel_symbol_global = var_supply_();
     if 
(region->attrs->dict.count(tvm::backend::cuda::attr::kLaunchFields.name)) {
       Stmt launch = MakeCudaKernelLaunch(kernel_symbol_global, &device_func, 
call_args, region);
diff --git a/tirx/tests/python/tirx/codegen/test_nki.py 
b/tirx/tests/python/tirx/codegen/test_nki.py
index e6493d7da1..bebc8121dd 100644
--- a/tirx/tests/python/tirx/codegen/test_nki.py
+++ b/tirx/tests/python/tirx/codegen/test_nki.py
@@ -40,7 +40,7 @@ def test_nki_add_1():
     # fmt: off
     @T.function
     def func(A: T.Tensor((128, 512), "float32"), B: T.Tensor((128, 512), 
"float32")):
-        T.func_attr({"num_inputs": 1})
+        T.func_attr({"trn.num_input_args": 1})
         T.device_entry()
         A_sbuf = T.alloc_tensor(ty_args=[T.Tensor((128, 512), "float32", 
scope="trn.sbuf")])
         B_sbuf = T.alloc_tensor(ty_args=[T.Tensor((128, 512), "float32", 
scope="trn.sbuf")])
@@ -94,7 +94,7 @@ def test_nki_add_2():
     # fmt: off
     @T.function
     def func(A: T.Tensor((128, 2048), "float32"), B: T.Tensor((128, 2048), 
"float32")):
-        T.func_attr({"num_inputs": 1})
+        T.func_attr({"trn.num_input_args": 1})
         T.device_entry()
         A_sbuf = T.alloc_tensor(ty_args=[T.Tensor((128, 512), "float32", 
scope="trn.sbuf")])
         B_sbuf = T.alloc_tensor(ty_args=[T.Tensor((128, 512), "float32", 
scope="trn.sbuf")])
@@ -174,7 +174,7 @@ def test_nki_matmul_1():
         rhs: T.Tensor((K, N), "float16"),
         result: T.Tensor((M, N), "float16"),
     ):
-        T.func_attr({"num_inputs": 2})
+        T.func_attr({"trn.num_input_args": 2})
         result_tiles = T.alloc_tensor(
             ty_args=[
                 T.Tensor(

Reply via email to