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 6583b2b44a [Fix][Relax] Preserve identity permute_dims in 
AdjustMatmulOrder (#20287)
6583b2b44a is described below

commit 6583b2b44a29b04871b7ff2575e8bfd84966e0a0
Author: Yifan Chen <[email protected]>
AuthorDate: Tue Sep 8 08:58:05 2026 -0700

    [Fix][Relax] Preserve identity permute_dims in AdjustMatmulOrder (#20287)
    
    ## Summary
    
    `AdjustMatmulOrder` matched any `relax.permute_dims` around an inner
    matmul as though it were a transpose. An explicit identity axis order,
    such as `[0, 1]`, preserves the inner result and must not trigger that
    transpose-specific reassociation.
    
    This change checks explicit axes before the transpose rewrite and leaves
    identity permutations unchanged. It preserves the existing behavior for
    genuine transposes.
    
    Fixes #20277.
    
    ## Validation
    
    - `python -m pytest
    tests/python/relax/test_transform_adjust_matmul_order.py -q` — 27 passed
    with a local CPU/LLVM build.
    - The new integer LLVM regression returns `[-14, 42]` after
    `AdjustMatmulOrder`; the previous implementation returns `[-20, 40]`.
    - `pre-commit run --files src/relax/transform/adjust_matmul_order.cc
    tests/python/relax/test_transform_adjust_matmul_order.py`
    
    Generated-by: OpenAI Codex (GPT-6)
---
 src/relax/transform/adjust_matmul_order.cc         | 36 ++++++++-
 .../relax/test_transform_adjust_matmul_order.py    | 89 ++++++++++++++++++++++
 2 files changed, 123 insertions(+), 2 deletions(-)

diff --git a/src/relax/transform/adjust_matmul_order.cc 
b/src/relax/transform/adjust_matmul_order.cc
index 1f0cf87281..615bb6fd18 100644
--- a/src/relax/transform/adjust_matmul_order.cc
+++ b/src/relax/transform/adjust_matmul_order.cc
@@ -24,6 +24,7 @@
 
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/relax/analysis.h>
+#include <tvm/relax/attrs/manipulate.h>
 #include <tvm/relax/dataflow_matcher.h>
 #include <tvm/relax/expr.h>
 #include <tvm/relax/expr_functor.h>
@@ -54,6 +55,33 @@ PrimExpr ProductDims(const ffi::Array<PrimExpr>& dims) {
   return product;
 }
 
+bool IsLastTwoDimsSwap(const Expr& expr) {
+  const auto* call = expr.as<CallNode>();
+  if (call == nullptr) return false;
+
+  const auto* attrs = call->attrs.as<PermuteDimsAttrs>();
+  const auto* input_type = GetTypeAs<TensorTypeNode>(call->args[0]);
+  if (attrs == nullptr || input_type == nullptr || input_type->ndim < 2) 
return false;
+
+  size_t ndim = input_type->ndim;
+  if (!attrs->axes.has_value()) return ndim == 2;
+
+  const auto& axes = attrs->axes.value();
+  if (axes.size() != ndim) return false;
+  for (size_t i = 0; i < axes.size(); ++i) {
+    int64_t axis = axes[i];
+    if (axis < 0) axis += ndim;
+    size_t expected = i;
+    if (i == ndim - 2) {
+      expected = ndim - 1;
+    } else if (i == ndim - 1) {
+      expected = ndim - 2;
+    }
+    if (axis != static_cast<int64_t>(expected)) return false;
+  }
+  return true;
+}
+
 ffi::Optional<ffi::Array<PrimExpr>> InferBatchedMatmulBroadcastPrefix(
     arith::AnalyzerObj* analyzer, const ffi::Array<PrimExpr>& x1, const 
ffi::Array<PrimExpr>& x2) {
   auto infer_result = InferBinaryBroadcastShape(analyzer, x1, x2);
@@ -89,8 +117,10 @@ std::tuple<DFPattern, ffi::TypedFunction<Expr(Expr, 
ffi::Map<DFPattern, Expr>)>>
   auto pat_matmul_on_lhs = pat_matmul(pat_matmul(pat_a, pat_b), pat_c);
   auto pat_matmul_on_rhs = pat_matmul(pat_a, pat_matmul(pat_b, pat_c));
 
-  auto pat_permuted_matmul_on_lhs = 
pat_matmul(pat_permute_dims(pat_matmul(pat_b, pat_a)), pat_c);
-  auto pat_permuted_matmul_on_rhs = pat_matmul(pat_a, 
pat_permute_dims(pat_matmul(pat_c, pat_b)));
+  auto pat_permuted_inner_matmul_on_lhs = pat_permute_dims(pat_matmul(pat_b, 
pat_a));
+  auto pat_permuted_inner_matmul_on_rhs = pat_permute_dims(pat_matmul(pat_c, 
pat_b));
+  auto pat_permuted_matmul_on_lhs = 
pat_matmul(pat_permuted_inner_matmul_on_lhs, pat_c);
+  auto pat_permuted_matmul_on_rhs = pat_matmul(pat_a, 
pat_permuted_inner_matmul_on_rhs);
 
   auto pat = pat_matmul_on_lhs | pat_matmul_on_rhs | 
pat_permuted_matmul_on_lhs |
              pat_permuted_matmul_on_rhs;
@@ -194,12 +224,14 @@ std::tuple<DFPattern, ffi::TypedFunction<Expr(Expr, 
ffi::Map<DFPattern, Expr>)>>
     };
 
     if (matches.count(pat_permuted_matmul_on_lhs)) {
+      if (!IsLastTwoDimsSwap(matches[pat_permuted_inner_matmul_on_lhs])) 
return expr;
       if (shape_a.size() < 2 || shape_b.size() < 2) return expr;
       expr_a = permute_last_two_dims(expr_a);
       expr_b = permute_last_two_dims(expr_b);
       transpose_shape_last_two_dims(shape_a);
       transpose_shape_last_two_dims(shape_b);
     } else if (matches.count(pat_permuted_matmul_on_rhs)) {
+      if (!IsLastTwoDimsSwap(matches[pat_permuted_inner_matmul_on_rhs])) 
return expr;
       if (shape_b.size() < 2 || shape_c.size() < 2) return expr;
       expr_b = permute_last_two_dims(expr_b);
       expr_c = permute_last_two_dims(expr_c);
diff --git a/tests/python/relax/test_transform_adjust_matmul_order.py 
b/tests/python/relax/test_transform_adjust_matmul_order.py
index b5f3155248..eb886fd4d0 100644
--- a/tests/python/relax/test_transform_adjust_matmul_order.py
+++ b/tests/python/relax/test_transform_adjust_matmul_order.py
@@ -564,6 +564,68 @@ class TestRHSPermuteDims(Base):
             return x
 
 
+class TestRHSPermuteDimsIdentity(Base):
+    """Do not treat an explicit identity permutation as a transpose.
+
+    `TestRHSPermuteDims` above covers the real transpose case.  Here, the
+    explicit axes preserve the inner matmul's order, so reassociation must not
+    insert transposes for its operands.
+    """
+
+    @I.ir_module
+    class Before:
+        @R.function
+        def main(
+            x: R.Tensor([2]),
+            A: R.Tensor([2, 1]),
+            B: R.Tensor([1, 2]),
+        ) -> R.Tensor([2]):
+            linear_weight: R.Tensor([2, 2]) = R.matmul(A, B)
+            matmul_weight: R.Tensor([2, 2]) = R.permute_dims(linear_weight, 
axes=[0, 1])
+            out: R.Tensor([2]) = R.matmul(x, matmul_weight)
+            return out
+
+    Expected = Before
+
+
+class TestRHSPermuteDimsNonMatrixAxes(Base):
+    """Do not rewrite permutations that move a batch axis."""
+
+    @I.ir_module
+    class Before:
+        @R.function
+        def main(
+            x: R.Tensor([4, 1, 4]),
+            A: R.Tensor([4, 4, 1]),
+            B: R.Tensor([4, 1, 4]),
+        ) -> R.Tensor([4, 1, 4]):
+            weight: R.Tensor([4, 4, 4]) = R.matmul(A, B)
+            permuted: R.Tensor([4, 4, 4]) = R.permute_dims(weight, axes=[1, 0, 
2])
+            out: R.Tensor([4, 1, 4]) = R.matmul(x, permuted)
+            return out
+
+    Expected = Before
+
+
+class TestLHSPermuteDimsNonMatrixAxes(Base):
+    """Apply the same batch-axis guard to the left-hand pattern."""
+
+    @I.ir_module
+    class Before:
+        @R.function
+        def main(
+            A: R.Tensor([4, 4, 1]),
+            B: R.Tensor([4, 1, 4]),
+            x: R.Tensor([4, 4, 1]),
+        ) -> R.Tensor([4, 4, 1]):
+            weight: R.Tensor([4, 4, 4]) = R.matmul(A, B)
+            permuted: R.Tensor([4, 4, 4]) = R.permute_dims(weight, axes=[1, 0, 
2])
+            out: R.Tensor([4, 4, 1]) = R.matmul(permuted, x)
+            return out
+
+    Expected = Before
+
+
 class TestRHSPermuteDimsDynamic(Base):
     """Prefer (x*A)*B instead of x*(A*B)
 
@@ -852,6 +914,33 @@ class TestAdjustMatmulOrderAttentionBlock:
         tvm.testing.assert_allclose(out_after, ref, rtol=1e-3, atol=1e-3)
         tvm.testing.assert_allclose(out_before, out_after, rtol=1e-5, 
atol=1e-5)
 
+    def test_identity_permute_dims_numerics(self):
+        bb = relax.BlockBuilder()
+        x = relax.Var("x", relax.TensorType((2,), "int32"))
+        A = relax.Var("A", relax.TensorType((2, 1), "int32"))
+        B = relax.Var("B", relax.TensorType((1, 2), "int32"))
+        with bb.function("main", [x, A, B]):
+            with bb.dataflow():
+                linear_weight = bb.emit(relax.op.matmul(A, B))
+                identity_weight = bb.emit(relax.op.permute_dims(linear_weight, 
axes=[0, 1]))
+                out = bb.emit_output(relax.op.matmul(x, identity_weight))
+            bb.emit_func_output(out)
+        mod = bb.finalize()
+        mod_opt = relax.transform.AdjustMatmulOrder()(mod)
+
+        inputs = [
+            np.array([-1, 3], dtype="int32"),
+            np.array([[2], [-4]], dtype="int32"),
+            np.array([[1, -3]], dtype="int32"),
+        ]
+        expected = np.array([-14, 42], dtype="int32")
+
+        out_before = self._run_relax_main(mod, inputs)
+        out_after = self._run_relax_main(mod_opt, inputs)
+
+        np.testing.assert_array_equal(out_before, expected)
+        np.testing.assert_array_equal(out_after, expected)
+
 
 if __name__ == "__main__":
     tvm.testing.main()

Reply via email to