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 218743c339 [REFACTOR][s_tir] Keep partition hints on their owning
loops (#20551)
218743c339 is described below
commit 218743c3390f7bbae273573d575a3de7039ddc29
Author: Tianqi Chen <[email protected]>
AuthorDate: Mon Oct 5 13:49:27 2026 -0400
[REFACTOR][s_tir] Keep partition hints on their owning loops (#20551)
Loop partition hints currently become enclosing attributes that point to
the variable defined by an inner loop. Keep this policy in a
`loop_partition_hint` For annotation and have LoopPartition consume the
owning loop directly, including expression-valued hints and recursive
partitions. Preserve unrelated annotations when splitting or unrolling
loops.
Run LoopPartition before opaque lowering in the existing partitioning
pipelines and handle thread-binding For nodes with the existing
hardware-thread partition rules. Custom pipelines with hinted
thread-binding loops must use the same order; opaque lowering reports an
ordering error instead of discarding the hint. In-tree producers use the
new annotation spelling.
---
include/tvm/s_tir/stmt.h | 4 +-
include/tvm/s_tir/transform.h | 1 +
include/tvm/tirx/transform.h | 1 +
python/tvm/backend/trn/pipeline.py | 2 +-
python/tvm/s_tir/backend/adreno/pipeline.py | 2 +-
python/tvm/s_tir/pipeline.py | 2 +-
python/tvm/s_tir/transform/transform.py | 6 +
python/tvm/tirx/transform/transform.py | 3 +
src/s_tir/transform/annotate_irregular_loop.cc | 2 +-
src/s_tir/transform/loop_partition.cc | 132 +++++++++++++++------
src/s_tir/transform/lower_opaque_block.cc | 4 +
src/tirx/transform/lower_tirx_opaque.cc | 4 +
.../test_s_tir_transform_compact_buffer_region.py | 4 +-
...est_s_tir_transform_inject_software_pipeline.py | 4 +-
.../test_s_tir_transform_loop_partition.py | 38 +++---
15 files changed, 143 insertions(+), 66 deletions(-)
diff --git a/include/tvm/s_tir/stmt.h b/include/tvm/s_tir/stmt.h
index 5e4978d24c..47fcbd0765 100644
--- a/include/tvm/s_tir/stmt.h
+++ b/include/tvm/s_tir/stmt.h
@@ -254,9 +254,9 @@ constexpr const char* fragment_shape = "fragment_shape";
constexpr const char* fragment_layout = "fragment_layout";
/*!
- * \brief Mark that the loop should be partitioned.
+ * \brief For annotation requesting partitioning when its PrimExpr value is
provably true.
*/
-constexpr const char* pragma_loop_partition_hint =
"pragma_loop_partition_hint";
+constexpr const char* loop_partition_hint = "loop_partition_hint";
/*! \brief Mark of reduce scope */
constexpr const char* reduce_scope = "reduce_scope";
diff --git a/include/tvm/s_tir/transform.h b/include/tvm/s_tir/transform.h
index 0f2c2c4b1f..90cdf5cba1 100644
--- a/include/tvm/s_tir/transform.h
+++ b/include/tvm/s_tir/transform.h
@@ -158,6 +158,7 @@ TVM_DLL Pass TransformMmaBufferLayout();
/*!
* \brief Remove the block to ensure that the TIR can not be scheduled again.
+ * Run LoopPartition first when thread-binding loops carry loop_partition_hint.
* \return The pass.
*/
TVM_DLL Pass LowerOpaqueBlock();
diff --git a/include/tvm/tirx/transform.h b/include/tvm/tirx/transform.h
index dbbfe60689..8e95ffb7fd 100644
--- a/include/tvm/tirx/transform.h
+++ b/include/tvm/tirx/transform.h
@@ -345,6 +345,7 @@ TVM_DLL Pass LowerTIRxCleanup();
* \brief Lower opaque constructs in TIRX programs: allocation calls,
For(thread_binding),
* unit loop elimination. This is the tirx-specific counterpart of
* s_tir::LowerOpaqueBlock, without any SBlock handling.
+ * Run LoopPartition first when thread-binding loops carry loop_partition_hint.
* \return The pass.
*/
TVM_DLL Pass LowerTIRxOpaque();
diff --git a/python/tvm/backend/trn/pipeline.py
b/python/tvm/backend/trn/pipeline.py
index d0663218e6..e0225400bf 100644
--- a/python/tvm/backend/trn/pipeline.py
+++ b/python/tvm/backend/trn/pipeline.py
@@ -36,8 +36,8 @@ def trn_pipeline():
trn_transform.LowerTrainiumLayout(),
tvm.s_tir.transform.DecorateDeviceScope(),
tirx.transform.StmtSimplify(),
- tirx.transform.LowerTIRxOpaque(),
tvm.s_tir.transform.LoopPartition(),
+ tirx.transform.LowerTIRxOpaque(),
tvm.s_tir.transform.HoistIfThenElse(),
tirx.transform.StmtSimplify(),
tirx.transform.RemoveNoOp(),
diff --git a/python/tvm/s_tir/backend/adreno/pipeline.py
b/python/tvm/s_tir/backend/adreno/pipeline.py
index 32f486d4de..e5a8dd0774 100644
--- a/python/tvm/s_tir/backend/adreno/pipeline.py
+++ b/python/tvm/s_tir/backend/adreno/pipeline.py
@@ -49,12 +49,12 @@ def default_tir_pipeline(*, prepare_only=False):
s_tir.transform.AnnotateIrregularLoop(),
s_tir.transform.InjectSoftwarePipeline(),
s_tir.transform.TransformMmaBufferLayout(),
+ s_tir.transform.LoopPartition(),
s_tir.transform.LowerOpaqueBlock(),
s_tir.backend.adreno.transform.InjectTextureAlloc(),
tirx.transform.FlattenBuffer(),
tirx.transform.BF16ComputeLegalize(),
tirx.transform.NarrowDataType(32),
- s_tir.transform.LoopPartition(),
tirx.transform.VectorizeLoop(not
bool(config.get("tirx.disable_vectorize", False))),
s_tir.transform.InjectVirtualThread(),
s_tir.transform.InjectDoubleBuffer(),
diff --git a/python/tvm/s_tir/pipeline.py b/python/tvm/s_tir/pipeline.py
index ee069b4fb8..d6f159f976 100644
--- a/python/tvm/s_tir/pipeline.py
+++ b/python/tvm/s_tir/pipeline.py
@@ -50,11 +50,11 @@ def default_s_tir_pipeline(*, prepare_only=False):
s_tir.transform.AnnotateIrregularLoop(),
s_tir.transform.InjectSoftwarePipeline(),
s_tir.transform.TransformMmaBufferLayout(),
+ s_tir.transform.LoopPartition(),
s_tir.transform.LowerOpaqueBlock(),
tirx.transform.FlattenBuffer(),
tirx.transform.BF16ComputeLegalize(),
tirx.transform.NarrowDataType(32),
- s_tir.transform.LoopPartition(),
tirx.transform.VectorizeLoop(not
bool(config.get("tirx.disable_vectorize", False))),
s_tir.transform.InjectVirtualThread(),
s_tir.transform.InjectDoubleBuffer(),
diff --git a/python/tvm/s_tir/transform/transform.py
b/python/tvm/s_tir/transform/transform.py
index e6095a656b..65eb20b4f9 100644
--- a/python/tvm/s_tir/transform/transform.py
+++ b/python/tvm/s_tir/transform/transform.py
@@ -136,6 +136,8 @@ def LowerMatchBuffer():
def LowerOpaqueBlock():
"""Remove the block to ensure that the TIR can not be scheduled again.
+ Run LoopPartition first when thread-binding loops carry
loop_partition_hint.
+
Returns
-------
fpass : tvm.transform.Pass
@@ -230,6 +232,10 @@ class LoopPartitionConfig(_ffi.Object):
def LoopPartition():
"""Partition loops in the stmt.
+ Consumes the owning For's ``loop_partition_hint`` annotation. A provably
+ true value enables partitioning even without likely tags. Run this pass
+ before opaque lowering when hinting thread-binding loops.
+
Returns
-------
fpass : tvm.transform.Pass
diff --git a/python/tvm/tirx/transform/transform.py
b/python/tvm/tirx/transform/transform.py
index 57d46d03dd..06184633d9 100644
--- a/python/tvm/tirx/transform/transform.py
+++ b/python/tvm/tirx/transform/transform.py
@@ -549,6 +549,9 @@ def LowerTIRxOpaque():
This is the tirx-specific counterpart of s_tir.LowerOpaqueBlock,
without any SBlock/SBlockRealize handling.
+ Run s_tir.transform.LoopPartition first when thread-binding loops carry
+ loop_partition_hint.
+
Returns
-------
fpass : tvm.transform.Pass
diff --git a/src/s_tir/transform/annotate_irregular_loop.cc
b/src/s_tir/transform/annotate_irregular_loop.cc
index 3519271665..b56dd8a3d7 100644
--- a/src/s_tir/transform/annotate_irregular_loop.cc
+++ b/src/s_tir/transform/annotate_irregular_loop.cc
@@ -59,7 +59,7 @@ class IrregularLoopAnnotator : public StmtExprMutator {
<< "Loop kind " << op->kind << " is invalid for irregular loop " <<
op->loop_var;
for (const char* key :
{tirx::attr::pragma_auto_unroll_max_step,
tirx::attr::pragma_unroll_explicit,
- s_tir::attr::pragma_loop_partition_hint,
s_tir::attr::software_pipeline_stage}) {
+ s_tir::attr::loop_partition_hint,
s_tir::attr::software_pipeline_stage}) {
TVM_FFI_ICHECK(!res->annotations.count(key))
<< "Annotation `" << key << "` is invalid for irregular loop " <<
op->loop_var;
}
diff --git a/src/s_tir/transform/loop_partition.cc
b/src/s_tir/transform/loop_partition.cc
index be3561eb20..537225f41d 100644
--- a/src/s_tir/transform/loop_partition.cc
+++ b/src/s_tir/transform/loop_partition.cc
@@ -65,8 +65,7 @@ struct LoopPartitionConfigNode : public ffi::Object {
"Don't unroll loops with extent 1", refl::DefaultValue(false))
.def_ro("unroll_loop_with_partition_hint_no_interval",
&LoopPartitionConfigNode::unroll_loop_with_partition_hint_no_interval,
- "Unroll loops with pragma_loop_partition_hint and no interval",
- refl::DefaultValue(false));
+ "Unroll loops with loop_partition_hint and no interval",
refl::DefaultValue(false));
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.transform.LoopPartitionConfig",
LoopPartitionConfigNode,
ffi::Object);
@@ -108,6 +107,46 @@ using Partition = std::unordered_map<PartitionKey, IntSet,
PartitionKeyHash, Par
using ExpressionSet = std::unordered_set<PrimExpr, ffi::ObjectPtrHash,
ffi::ObjectPtrEqual>;
+// Virtual threads are lowered separately and are not hardware partition
scopes.
+static bool IsVirtualThread(const ForNode* op) {
+ if (op->kind != ForKind::kThreadBinding) return false;
+ const auto& tag = op->thread_binding.value()->thread_tag;
+ return tag == "vthread" || tag == "vthread.x" || tag == "vthread.y" || tag
== "vthread.z";
+}
+
+static bool HasPartitionHint(const ForNode* op) {
+ auto it = op->annotations.find(attr::loop_partition_hint);
+ return it != op->annotations.end() &&
is_one((*it).second.as_or_throw<PrimExpr>());
+}
+
+// Resolve expression-valued hints before partitioning clones or renames loops.
+// Like the original hint selection, this proof does not assume loop bounds.
+class NormalizePartitionHints : public StmtExprMutator {
+ public:
+ using StmtExprMutator::Mutate;
+ using StmtExprMutator::Mutate_;
+
+ UnchangedOr<Stmt> Mutate_(const ForNode* op, InplaceMode inplace_mode) final
{
+ For loop = StmtExprMutator::Mutate_(op, inplace_mode)
+ .ValueOrUnchanged(ffi::GetRef<Stmt>(op))
+ .as_or_throw<For>();
+ auto it = loop->annotations.find(attr::loop_partition_hint);
+ if (it != loop->annotations.end()) {
+ auto value = (*it).second;
+ if (value == nullptr) {
+ loop.CopyOnWrite()->annotations.erase(attr::loop_partition_hint);
+ } else {
+ bool enabled = analyzer_->CanProve(value.cast<PrimExpr>());
+ loop.CopyOnWrite()->annotations.Set(attr::loop_partition_hint,
IntImm::Bool(enabled));
+ }
+ }
+ return loop;
+ }
+
+ private:
+ sym::Analyzer analyzer_;
+};
+
// Select potential candidate IRs that can be partitioned.
// Rule:
// - the range should not be const
@@ -120,12 +159,16 @@ class CandidateSelector final : public StmtExprVisitor {
: partition_const_loop_(partition_const_loop) {}
ffi::Optional<VisitInterrupt> Visit_(const ForNode* op) final {
- // always treat var with hint to be partitioned
+ if (IsVirtualThread(op)) return StmtExprVisitor::Visit_(op);
const VarNode* var = op->loop_var.get();
- if (partition_hint_vars.count(var)) {
+ if (HasPartitionHint(op)) {
candidates.insert(ffi::GetRef<Stmt>(op));
return StmtExprVisitor::Visit_(op);
}
+ if (op->kind == ForKind::kThreadBinding &&
+
runtime::ThreadScope::Create(op->thread_binding.value()->thread_tag).rank != 0)
{
+ return StmtExprVisitor::Visit_(op);
+ }
// partition const loop when sets partition_const_loop_
if (!is_const_int(op->min) || !is_const_int(op->extent) ||
partition_const_loop_) {
record_.insert({var, false});
@@ -145,11 +188,6 @@ class CandidateSelector final : public StmtExprVisitor {
const IterVarNode* iv = op->node.as<IterVarNode>();
TVM_FFI_ICHECK(iv);
Var var = iv->var;
- // always treat var with hint to be partitioned
- if (partition_hint_vars.count(var.get())) {
- candidates.insert(ffi::GetRef<Stmt>(op));
- return StmtExprVisitor::Visit_(op);
- }
runtime::ThreadScope scope =
runtime::ThreadScope::Create(iv->thread_tag);
auto value = op->value.as<PrimExpr>();
if ((scope.rank == 0) && (!value || !is_const_int(value.value()) ||
partition_const_loop_)) {
@@ -161,17 +199,6 @@ class CandidateSelector final : public StmtExprVisitor {
record_.erase(var.get());
return std::nullopt;
}
- } else if (op->attr_key == s_tir::attr::pragma_loop_partition_hint) {
- if (analyzer_->CanProve(op->value.as_or_throw<PrimExpr>())) {
- const VarNode* var = nullptr;
- if (op->node.as<VarNode>()) {
- var = op->node.as<VarNode>();
- } else if (op->node.as<IterVarNode>()) {
- var = op->node.as<IterVarNode>()->var.get();
- }
- TVM_FFI_ICHECK(var);
- partition_hint_vars.insert(var);
- }
}
return StmtExprVisitor::Visit_(op);
}
@@ -212,14 +239,12 @@ class CandidateSelector final : public StmtExprVisitor {
}
std::unordered_set<Stmt, ffi::ObjectPtrHash, ffi::ObjectPtrEqual> candidates;
- std::unordered_set<const VarNode*> partition_hint_vars;
private:
bool in_likely_{false};
bool no_split_{false};
bool partition_const_loop_{false};
std::unordered_map<const VarNode*, VarIsUsed> record_;
- sym::Analyzer analyzer_;
};
// Finder try best to find partitions for hinted vars
@@ -254,6 +279,7 @@ class PartitionFinder : public StmtExprVisitor {
}
ffi::Optional<VisitInterrupt> Visit_(const ForNode* op) final {
+ if (IsVirtualThread(op)) return StmtExprVisitor::Visit_(op);
auto f_vset_contains = [this](const VarNode* var) { return
out_vars_.count(var); };
auto walkfn = [&](const Var& var) -> ffi::Expected<ffi::WalkResult> {
return f_vset_contains(var.get()) ?
ffi::WalkResult::Interrupt(ffi::VisitInterrupt(var))
@@ -425,6 +451,25 @@ class ThreadPartitionInserter : public StmtExprMutator {
explicit ThreadPartitionInserter(const ExpressionSet& ps, PrimExpr cond)
: ps_(ps), cond_(cond), innermost_thread_scope_(false) {}
+ UnchangedOr<Stmt> Mutate_(const ForNode* op, InplaceMode inplace_mode) final
{
+ if (op->kind != ForKind::kThreadBinding || IsVirtualThread(op)) {
+ return StmtExprMutator::Mutate_(op, inplace_mode);
+ }
+ innermost_thread_scope_ = true;
+ For loop = StmtExprMutator::Mutate_(op, inplace_mode)
+ .ValueOrUnchanged(ffi::GetRef<Stmt>(op))
+ .as_or_throw<For>();
+ if (innermost_thread_scope_) {
+ Stmt body = loop->body;
+ Stmt simplified_body = ffi::make_object<ConditionEliminator>(ps_)
+ ->Mutate(body, InplaceMode::kDisallow)
+ .ValueOrUnchanged(body);
+ loop.CopyOnWrite()->body = IfThenElse(cond_, simplified_body, body);
+ }
+ innermost_thread_scope_ = false;
+ return loop;
+ }
+
UnchangedOr<Stmt> Mutate_(const AttrStmtNode* op, InplaceMode inplace_mode)
final {
if (op->attr_key == tirx::attr::thread_extent) {
innermost_thread_scope_ = true;
@@ -474,19 +519,24 @@ class LoopPartitioner : public StmtExprMutator {
}
UnchangedOr<Stmt> Mutate_(const ForNode* op, InplaceMode inplace_mode) final
{
+ if (IsVirtualThread(op)) return StmtExprMutator::Mutate_(op, inplace_mode);
+ bool thread_scope = op->kind == ForKind::kThreadBinding;
analyzer_->Bind(op->loop_var, Range::FromMinExtent(op->min, op->extent),
true);
auto fs = ffi::GetRef<Stmt>(op);
if (selector->candidates.count(fs)) {
- auto s = TryPartition(fs, op->loop_var, op->min, op->min + op->extent -
1, op->body, false);
+ auto s =
+ TryPartition(fs, op->loop_var, op->min, op->min + op->extent - 1,
op->body, thread_scope);
if (s.has_value()) return s.value();
}
- // normal path when loop partition fails
- // normal loop variable can be put into hint map.
- hint_map_.insert({op->loop_var.get(), IntSet::Interval(op->min, op->min +
op->extent - 1)});
+ // Relax threadIdx ranges to avoid introducing divergent partition
branches.
+ bool relax = thread_scope &&
+
runtime::ThreadScope::Create(op->thread_binding.value()->thread_tag).rank == 1;
+ auto& ranges = relax ? relax_map_ : hint_map_;
+ ranges.insert({op->loop_var.get(), IntSet::Interval(op->min, op->min +
op->extent - 1)});
Stmt res = StmtExprMutator::Mutate_(op, InplaceMode::kDisallow)
.ValueOrUnchanged(ffi::GetRef<Stmt>(op));
- hint_map_.erase(op->loop_var.get());
+ ranges.erase(op->loop_var.get());
return res;
}
@@ -647,7 +697,8 @@ ffi::Optional<Stmt> LoopPartitioner::TryPartition(const
Stmt& stmt, Var var, Pri
// include hint of var.
hint_map_.insert({var.get(), IntSet::Interval(min, max)});
- bool has_partition_hint_ = selector->partition_hint_vars.count(var.get());
+ const auto* loop = stmt.as<ForNode>();
+ bool has_partition_hint_ = loop && HasPartitionHint(loop);
auto finder = ffi::make_object<PartitionFinder>(var, hint_map_, relax_map_,
has_partition_hint_);
finder->Visit(body);
@@ -719,10 +770,13 @@ ffi::Optional<Stmt> LoopPartitioner::TryPartition(const
Stmt& stmt, Var var, Pri
}
if (!opt_cond_value.has_value()) {
- if (has_partition_hint_ && unroll_loop_with_partition_hint_no_interval_ &&
- analyzer_->CanProve(max - min > 0)) {
+ if (!partition_thread_scope && has_partition_hint_ &&
+ unroll_loop_with_partition_hint_no_interval_ &&
analyzer_->CanProve(max - min > 0)) {
auto new_body = VisitAndMutate(body);
- return For(var.as_or_throw<PrimVar>(), min, max - min + 1,
ForKind::kUnrolled, new_body);
+ auto unrolled = ffi::make_object<ForNode>(*loop);
+ unrolled->kind = ForKind::kUnrolled;
+ unrolled->body = std::move(new_body);
+ return For(unrolled);
}
return std::nullopt;
}
@@ -856,7 +910,7 @@ inline Stmt LoopPartitioner::MakeFor(const ffi::Object*
node, PrimExpr extent, S
TVM_FFI_ICHECK(for_node);
if (analyzer_->CanProve(extent == IntImm::Int32(1)) &&
!no_unroll_loop_with_extent_one_ &&
- for_node->annotations.empty()) {
+ for_node->annotations.size() ==
for_node->annotations.count(attr::loop_partition_hint)) {
// If the loop extent is 1, do not create the loop anymore
auto f_substitute = [loop_var = for_node->loop_var](
const Var& var) ->
ffi::Expected<ffi::UnchangedOr<ffi::Any>> {
@@ -891,16 +945,22 @@ class RemoveLikelyTagsAndHints : public StmtExprMutator {
}
}
- UnchangedOr<Stmt> Mutate_(const AttrStmtNode* op, InplaceMode inplace_mode)
final {
- if (op->attr_key == s_tir::attr::pragma_loop_partition_hint) {
- return Mutate(op->body, inplace_mode).ValueOrUnchanged(op->body);
+ UnchangedOr<Stmt> Mutate_(const ForNode* op, InplaceMode inplace_mode) final
{
+ For loop = StmtExprMutator::Mutate_(op, inplace_mode)
+ .ValueOrUnchanged(ffi::GetRef<Stmt>(op))
+ .as_or_throw<For>();
+ if (loop->annotations.count(attr::loop_partition_hint)) {
+ loop.CopyOnWrite()->annotations.erase(attr::loop_partition_hint);
}
- return StmtExprMutator::Mutate_(op, inplace_mode);
+ return loop;
}
};
Stmt LoopPartition(Stmt stmt, bool partition_const_loop, bool
no_unroll_loop_with_extent_one,
bool unroll_loop_with_partition_hint_no_interval) {
+ stmt = ffi::make_object<NormalizePartitionHints>()
+ ->Mutate(stmt, InplaceMode::kAllow)
+ .ValueOrUnchanged(std::move(stmt));
stmt = ffi::make_object<LoopPartitioner>(partition_const_loop,
no_unroll_loop_with_extent_one,
unroll_loop_with_partition_hint_no_interval)
->VisitAndMutate(std::move(stmt));
diff --git a/src/s_tir/transform/lower_opaque_block.cc
b/src/s_tir/transform/lower_opaque_block.cc
index bbb68f392e..4bf291ee38 100644
--- a/src/s_tir/transform/lower_opaque_block.cc
+++ b/src/s_tir/transform/lower_opaque_block.cc
@@ -115,6 +115,10 @@ class OpaqueBlockLower : public StmtExprMutator {
// Step 4. Create new For loop accordingly
if (op->kind == ForKind::kThreadBinding) {
// Case 1. Thread binding
+ TVM_FFI_ICHECK(!op->annotations.count("loop_partition_hint") ||
+ op->annotations.at("loop_partition_hint") == nullptr)
+ << "Run LoopPartition before opaque lowering of a thread-binding
loop with "
+ "loop_partition_hint";
TVM_FFI_ICHECK(op->thread_binding.has_value());
ffi::String thread_tag = op->thread_binding.value()->thread_tag;
body = MakeLaunchThread(min, extent, op->loop_var, thread_tag, body);
diff --git a/src/tirx/transform/lower_tirx_opaque.cc
b/src/tirx/transform/lower_tirx_opaque.cc
index 949d9d821d..d8792dda9d 100644
--- a/src/tirx/transform/lower_tirx_opaque.cc
+++ b/src/tirx/transform/lower_tirx_opaque.cc
@@ -72,6 +72,10 @@ class TIRxOpaqueLower : public StmtExprMutator {
// Step 4. Create new For loop accordingly
if (op->kind == ForKind::kThreadBinding) {
// Case 1. Thread binding → AttrStmt(thread_extent)
+ TVM_FFI_ICHECK(!op->annotations.count("loop_partition_hint") ||
+ op->annotations.at("loop_partition_hint") == nullptr)
+ << "Run LoopPartition before opaque lowering of a thread-binding
loop with "
+ "loop_partition_hint";
TVM_FFI_ICHECK(op->thread_binding.has_value());
ffi::String thread_tag = op->thread_binding.value()->thread_tag;
body = MakeLaunchThread(min, extent, op->loop_var, thread_tag, body);
diff --git
a/tests/python/s_tir/transform/test_s_tir_transform_compact_buffer_region.py
b/tests/python/s_tir/transform/test_s_tir_transform_compact_buffer_region.py
index a28c75387c..1760d81d04 100644
--- a/tests/python/s_tir/transform/test_s_tir_transform_compact_buffer_region.py
+++ b/tests/python/s_tir/transform/test_s_tir_transform_compact_buffer_region.py
@@ -1011,8 +1011,8 @@ class TestTileAwareCompaction(BaseCompactTest):
B: T.Tensor((128, 128), "float32"),
C: T.Tensor((128, 128), "float32"),
):
- for i_0 in range(5, annotations={"pragma_loop_partition_hint": 1}):
- for j_0 in range(5, annotations={"pragma_loop_partition_hint":
1}):
+ for i_0 in range(5, annotations={"loop_partition_hint": 1}):
+ for j_0 in range(5, annotations={"loop_partition_hint": 1}):
A_local = T.decl_tensor((26, 128), scope="local")
B_local = T.decl_tensor((128, 26), scope="local")
C_local = T.decl_tensor((26, 26), scope="local")
diff --git
a/tests/python/s_tir/transform/test_s_tir_transform_inject_software_pipeline.py
b/tests/python/s_tir/transform/test_s_tir_transform_inject_software_pipeline.py
index 438dcaa923..b13248c4e5 100644
---
a/tests/python/s_tir/transform/test_s_tir_transform_inject_software_pipeline.py
+++
b/tests/python/s_tir/transform/test_s_tir_transform_inject_software_pipeline.py
@@ -236,7 +236,7 @@ def simple_compute_with_other_annotation(
annotations={
"software_pipeline_stage": [0, 1],
"software_pipeline_order": [0, 1],
- "pragma_loop_partition_hint": True,
+ "loop_partition_hint": True,
},
):
with Ts.sblock("compute"):
@@ -272,7 +272,7 @@ def transformed_simple_compute_with_other_annotation(
for i in T.serial(
0,
15,
- annotations={"pragma_loop_partition_hint": True},
+ annotations={"loop_partition_hint": True},
):
with Ts.sblock():
Ts.reads([A[tx, i + 1]])
diff --git
a/tests/python/s_tir/transform/test_s_tir_transform_loop_partition.py
b/tests/python/s_tir/transform/test_s_tir_transform_loop_partition.py
index 25d48f99a8..2df8c3e69a 100644
--- a/tests/python/s_tir/transform/test_s_tir_transform_loop_partition.py
+++ b/tests/python/s_tir/transform/test_s_tir_transform_loop_partition.py
@@ -223,10 +223,10 @@ def partitioned_concat(
def partition_from_scheduled_tir(prim_func, pass_cfg, do_flatten=True):
with tvm.transform.PassContext(config=pass_cfg):
mod = IRModule.from_expr(prim_func.with_attr("global_symbol", "main"))
+ mod = tvm.s_tir.transform.LoopPartition()(mod)
mod = tvm.s_tir.transform.LowerOpaqueBlock()(mod)
if do_flatten:
mod = tvm.tirx.transform.FlattenBuffer()(mod)
- mod = tvm.s_tir.transform.LoopPartition()(mod)
mod = tvm.tirx.transform.StmtSimplify()(mod)
mod = tvm.tirx.transform.RemoveNoOp()(mod)
return mod
@@ -262,7 +262,7 @@ def concat_func_3(
placeholder_1_flat = T.decl_tensor([25088], "int8",
data=placeholder_1.data)
placeholder_2_flat = T.decl_tensor([25088], "int8",
data=placeholder_2.data)
T_concat_flat = T.decl_tensor([100352], "int8", data=T_concat.data)
- for i1 in T.serial(128, annotations={"pragma_loop_partition_hint": 1}):
+ for i1 in T.serial(128, annotations={"loop_partition_hint": 1}):
for i2, i3 in T.grid(28, 28):
if 96 <= i1:
T_concat_flat[i1 * 784 + i2 * 28 + i3] = placeholder_2_flat[
@@ -294,7 +294,7 @@ def test_loop_partition_unroll_hint():
B = T.decl_tensor(25088, "int8", data=B_arg.data)
for ax0 in T.serial(
112,
- annotations={"pragma_loop_partition_hint": True},
+ annotations={"loop_partition_hint": True},
):
for ax1, ax2, ax3 in T.grid(224, 7, 16):
if 3 <= ax0 * 2 + ax2 and ax0 * 2 + ax2 < 227 and ax3 < 3:
@@ -339,8 +339,8 @@ def test_loop_partition_recursive_unroll_hint():
@Ts.prim_func
def main():
placeholder_0_dm = T.decl_tensor([1, 32, 32, 16], dtype="int8")
- for i3_0 in T.serial(5, annotations={"pragma_loop_partition_hint": 1}):
- for i2_0 in T.serial(2, annotations={"pragma_loop_partition_hint":
1}):
+ for i3_0 in T.serial(5, annotations={"loop_partition_hint": 1}):
+ for i2_0 in T.serial(2, annotations={"loop_partition_hint": 1}):
pad_temp = T.decl_tensor([1, 16, 16, 16], dtype="int8")
for ax0, ax1, ax2 in T.grid(16, 16, 16):
if (
@@ -405,7 +405,7 @@ def test_loop_partition_keep_loop_annotations():
def before(A: T.Tensor(160, "int32"), B: T.Tensor(160, "int32")) -> None:
for i in T.serial(
160,
- annotations={"pragma_loop_partition_hint": True, "key": "value"},
+ annotations={"loop_partition_hint": True, "key": "value"},
):
if i < 10:
B[i] = A[i] + 1
@@ -445,7 +445,7 @@ def test_loop_partition_with_unit_loop_in_condition():
T_concat: T.Tensor((100352,), "int8"),
) -> None:
for k in range(1, annotations={"preserve_unit_loop": True}):
- for i1 in T.serial(128, annotations={"pragma_loop_partition_hint":
1}):
+ for i1 in T.serial(128, annotations={"loop_partition_hint": 1}):
for i2, i3 in T.grid(28, 28):
if 96 <= k * 128 + i1:
T_concat[k * i1 * 784 + i2 * 28 + i3] = placeholder_2[
@@ -498,7 +498,7 @@ def concat_func_single_point(
T_concat: T.Tensor((28, 128), "int8"),
) -> None:
for i0 in range(28):
- for i1 in T.serial(128, annotations={"pragma_loop_partition_hint": 1}):
+ for i1 in T.serial(128, annotations={"loop_partition_hint": 1}):
if i1 > 63:
T_concat[i0, i1] = placeholder[i0, i1 - 64]
elif i1 == 63:
@@ -534,7 +534,7 @@ def concat_func_start_point_equality(
T_concat: T.Tensor((28, 128), "int8"),
) -> None:
for i0 in range(28):
- for i1 in range(128, annotations={"pragma_loop_partition_hint": 1}):
+ for i1 in range(128, annotations={"loop_partition_hint": 1}):
if i1 == 0:
# Special case for i1 == 0
T_concat[i0, i1] = placeholder_1[i0, 0]
@@ -573,7 +573,7 @@ def concat_func_end_point_equality(
T_concat: T.Tensor((28, 128), "int8"),
) -> None:
for i0 in range(28):
- for i1 in range(128, annotations={"pragma_loop_partition_hint": 1}):
+ for i1 in range(128, annotations={"loop_partition_hint": 1}):
if i1 == 127:
# Explicit equality check for the end point i1 == 127
T_concat[i0, i1] = placeholder_1[i0, 0]
@@ -612,9 +612,7 @@ def concat_func_edge_equalities(
T_concat: T.Tensor((28, 66), "int8"),
) -> None:
for i0 in range(28):
- for i1 in range(
- 66, annotations={"pragma_loop_partition_hint": 1}
- ): # Loop from 0 to 65 inclusive
+ for i1 in range(66, annotations={"loop_partition_hint": 1}): # Loop
from 0 to 65 inclusive
if i1 == 0:
# Handle equality at the start of the range: i1 == 0
T_concat[i0, i1] = placeholder_2[i0, 0]
@@ -654,7 +652,7 @@ def concat_five_buffers_with_equalities(
T_concat: T.Tensor((28, 129), "int8"),
) -> None:
for i0 in range(28):
- for i1 in range(130, annotations={"pragma_loop_partition_hint": 1}):
+ for i1 in range(130, annotations={"loop_partition_hint": 1}):
if i1 == 0:
T_concat[i0, i1] = buffer_a[i0, 0]
elif i1 == 64:
@@ -694,13 +692,13 @@ def concat_five_buffers_with_equalities_expected(
@Ts.prim_func
def nested_partition_with_single_points(A: T.Tensor((25,), "int32")):
- for i in T.serial(5, annotations={"pragma_loop_partition_hint": 1}):
+ for i in T.serial(5, annotations={"loop_partition_hint": 1}):
if i == 1:
- for j in T.serial(5, annotations={"pragma_loop_partition_hint":
1}):
+ for j in T.serial(5, annotations={"loop_partition_hint": 1}):
if j > 2:
A[i * 5 + j] = i * 5 + j
else:
- for j in T.serial(5, annotations={"pragma_loop_partition_hint":
1}):
+ for j in T.serial(5, annotations={"loop_partition_hint": 1}):
if j > 2:
A[i * 5 + j] = i * 15 + j
@@ -745,9 +743,9 @@ def test_single_point_partition(origin, expected):
def test_equation_on_floordiv():
@Ts.prim_func
def before(A: T.Tensor((2, 2, 20), "int32")):
- for i in T.serial(5, annotations={"pragma_loop_partition_hint": 1}):
+ for i in T.serial(5, annotations={"loop_partition_hint": 1}):
if i == 1:
- for vv in T.vectorized(640,
annotations={"pragma_loop_partition_hint": 1}):
+ for vv in T.vectorized(640,
annotations={"loop_partition_hint": 1}):
if i * 2 + vv // 320 == 3:
A[i - 1, i * 2 + vv // 320 - 3, vv % 320 // 16] = 1
@@ -770,7 +768,7 @@ def test_ignore_loop_partition_hint():
def before(A: T.Tensor((10), "float32"), D: T.Tensor((10), "float32")):
B = T.decl_tensor([2], "float32")
C = T.decl_tensor([2], "float32")
- for i in T.serial(12, annotations={"pragma_loop_partition_hint": 1}):
+ for i in T.serial(12, annotations={"loop_partition_hint": 1}):
if T.ignore_loop_partition(i < 10):
B[i % 2] = A[i] + 1.0
if T.ignore_loop_partition(1 <= i and i < 11):