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

tlopex 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 e03e9784ef [Fix][S-TIR] Preserve general reduction predicates (#20242)
e03e9784ef is described below

commit e03e9784efd9ab11e754502779131fd65541b830
Author: Akaash Parthasarathy <[email protected]>
AuthorDate: Tue Sep 1 19:05:06 2026 -0700

    [Fix][S-TIR] Preserve general reduction predicates (#20242)
    
    Rebuild decomposed reduction predicates recursively across conjunctions.
    Preserve clauses independent of discarded loops and drop clauses that
    reference discarded loops. This supports comparisons beyond `<` and
    detects loop variables on either operand.
    
    Tail predicates generated by `split` and `rfactor`, such as `outer *
    factor + inner < extent`, are omitted from initialization when they
    reference a discarded reduction loop, while remaining on the update
    block.
---
 src/s_tir/schedule/primitive/reduction.cc          | 34 +++++-----
 .../s_tir/schedule/test_tir_schedule_reduction.py  | 78 ++++++++++++++++++++++
 2 files changed, 93 insertions(+), 19 deletions(-)

diff --git a/src/s_tir/schedule/primitive/reduction.cc 
b/src/s_tir/schedule/primitive/reduction.cc
index 99ff247f5d..beb05afbd5 100644
--- a/src/s_tir/schedule/primitive/reduction.cc
+++ b/src/s_tir/schedule/primitive/reduction.cc
@@ -159,23 +159,17 @@ class LoopHeightError : public ScheduleError {
   SBlock block_;
 };
 
-PrimExpr RemakePredicate(PrimExpr pred, const std::unordered_set<const 
VarNode*>& discarded_loops) {
+PrimExpr RewriteInitPredicate(PrimExpr pred,
+                              const std::unordered_set<const VarNode*>& 
discarded_loops) {
   if (is_one(pred)) return IntImm::Bool(true);
-  PrimExpr new_pred = IntImm::Bool(true);
-  auto f = [&](const VarNode* var) { return discarded_loops.count(var); };
-  arith::PVar<PrimExpr> lhs, rhs, rest;
-  for (;;) {
-    if ((rest && (lhs < rhs)).Match(pred)) {
-      if (!UsesVar(lhs.Eval(), f)) new_pred = new_pred && (lhs.Eval() < 
rhs.Eval());
-      pred = rest.Eval();
-    } else if ((lhs < rhs).Match(pred)) {
-      if (!UsesVar(lhs.Eval(), f)) new_pred = new_pred && (lhs.Eval() < 
rhs.Eval());
-      break;
-    } else {
-      TVM_FFI_ICHECK(false) << "Unexpected predicate for reduction block";
-    }
+  if (const auto* and_node = pred.as<AndNode>()) {
+    return RewriteInitPredicate(and_node->a, discarded_loops) &&
+           RewriteInitPredicate(and_node->b, discarded_loops);
   }
-  return new_pred;
+  auto uses_discarded_loop = [&discarded_loops](const VarNode* var) {
+    return discarded_loops.count(var);
+  };
+  return UsesVar(pred, uses_discarded_loop) ? IntImm::Bool(true) : pred;
 }
 
 StmtSRef DecomposeReduction(ScheduleState self, const StmtSRef& block_sref,
@@ -259,15 +253,17 @@ StmtSRef DecomposeReduction(ScheduleState self, const 
StmtSRef& block_sref,
       discarded = false;
       break;
     }
-    if (discarded) discarded_loops.insert(loop_var);
+    if (discarded) {
+      discarded_loops.insert(loop_var);
+    }
     // Only scan loops not higher than the given loop
     if (loops[i].same_as(loop_sref)) {
       break;
     }
   }
-  // Step 4. After scanning loops, make a new predicate in the init block 
realize
-  //         We discard predicate that is related to discarded loops
-  init_realize->predicate = RemakePredicate(realize->predicate, 
discarded_loops);
+  // Step 4. Derive the predicate for the init block realize.  Omit 
conjunction clauses that
+  //         depend on discarded loops.
+  init_realize->predicate = RewriteInitPredicate(realize->predicate, 
discarded_loops);
   // Step 5. Create new loops above init block
   std::unordered_map<Var, Var> loop_var_map;
   Stmt body = SBlockRealize(init_realize);
diff --git a/tests/python/s_tir/schedule/test_tir_schedule_reduction.py 
b/tests/python/s_tir/schedule/test_tir_schedule_reduction.py
index 1643b13df0..598f68b1f9 100644
--- a/tests/python/s_tir/schedule/test_tir_schedule_reduction.py
+++ b/tests/python/s_tir/schedule/test_tir_schedule_reduction.py
@@ -391,5 +391,83 @@ def test_decompose_reduction_with_thread_binding():
     tvm.ir.assert_structural_equal(After, Expected)
 
 
+def test_decompose_reduction_preserves_general_spatial_predicates():
+    @I.ir_module(s_tir=True)
+    class Before:
+        @T.prim_func(s_tir=True)
+        def main(A: T.Buffer((8, 8), "float32"), B: T.Buffer((8,), "float32")):
+            for i, k in T.grid(10, 10):
+                with T.sblock("B"):
+                    T.where(1 <= i and i < 9 and 1 <= k and k < 9)
+                    vi = T.axis.spatial(8, i - 1)
+                    vk = T.axis.reduce(8, k - 1)
+                    with T.init():
+                        B[vi] = T.float32(0)
+                    B[vi] += A[vi, vk]
+
+    @I.ir_module(s_tir=True)
+    class Expected:
+        @T.prim_func(s_tir=True)
+        def main(A: T.Buffer((8, 8), "float32"), B: T.Buffer((8,), "float32")):
+            for i_init in range(10):
+                with T.sblock("B_init"):
+                    T.where(1 <= i_init and i_init < 9)
+                    vi = T.axis.spatial(8, i_init - 1)
+                    B[vi] = T.float32(0)
+            for i, k in T.grid(10, 10):
+                with T.sblock("B_update"):
+                    T.where(1 <= i and i < 9 and 1 <= k and k < 9)
+                    vi = T.axis.spatial(8, i - 1)
+                    vk = T.axis.reduce(8, k - 1)
+                    B[vi] += A[vi, vk]
+
+    sch = tvm.s_tir.Schedule(Before)
+    i, _ = sch.get_loops("B")
+    sch.decompose_reduction("B", i)
+    tvm.ir.assert_structural_equal(sch.mod, Expected)
+
+
+def test_decompose_reduction_drops_mixed_rfactor_bound():
+    @I.ir_module(s_tir=True)
+    class Before:
+        @T.prim_func(s_tir=True)
+        def main(A: T.Buffer((20,), "float32"), B: T.Buffer((), "float32")):
+            for k in range(20):
+                with T.sblock("B"):
+                    vk = T.axis.reduce(20, k)
+                    with T.init():
+                        B[()] = T.float32(0)
+                    B[()] += A[vk]
+
+    @I.ir_module(s_tir=True)
+    class Expected:
+        @T.prim_func(s_tir=True)
+        def main(A: T.Buffer((20,), "float32"), B: T.Buffer((), "float32")):
+            B_rf = T.sblock_alloc_buffer((16,), elem_offset=T.int64(0))
+            for k_1_init in range(16):
+                with T.sblock("B_rf_init"):
+                    vk_1 = T.axis.spatial(16, k_1_init)
+                    B_rf[vk_1] = T.float32(0)
+            for k_0, k_1 in T.grid(2, 16):
+                with T.sblock("B_rf_update"):
+                    vk_1, vk_0 = T.axis.remap("SR", [k_1, k_0])
+                    T.where(k_0 * 16 + k_1 < 20)
+                    B_rf[vk_1] += A[vk_0 * 16 + vk_1]
+            for k_1 in range(16):
+                with T.sblock("B"):
+                    vk_1 = T.axis.reduce(16, k_1)
+                    with T.init():
+                        B[()] = T.float32(0)
+                    B[()] += B_rf[vk_1]
+
+    sch = tvm.s_tir.Schedule(Before)
+    (k,) = sch.get_loops("B")
+    _, k_1 = sch.split(k, factors=[None, 16])
+    rf = sch.rfactor(k_1, 0)
+    k_0, _ = sch.get_loops(rf)
+    sch.decompose_reduction(rf, k_0)
+    tvm.ir.assert_structural_equal(sch.mod, Expected)
+
+
 if __name__ == "__main__":
     tvm.testing.main()

Reply via email to