Skip to content

Commit 68a080a

Browse files
authored
Normalize permute dims in PostponePermuteOpBelowSqueezeOrUnsqueezeLikeView
Summary: When the pass moves a permute below a view that inserts or removes a size-1 dim, it shifts each permute dim by comparing it with the inserted or removed axis. That comparison assumes non-negative dims. A negative dim that points below the axis keeps its value, so it lands on a different axis once the rank changes and the rewritten permute is invalid: `permute(-3, 2, 1).unsqueeze(2)` becomes `[1, 3, 2, 1]`, and `permute(-4, 2, 1, 3).squeeze(1)` keeps a `-4` at rank 3. Normalize the dims to their non-negative form before rewriting. Differential Revision: D128123955 Pull Request resolved: #24248
1 parent 043c65b commit 68a080a

3 files changed

Lines changed: 83 additions & 1 deletion

File tree

‎backends/transforms/postpone_permute_below_squeeze_view.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import torch
1313
import torch.fx
1414
from executorch.backends.transforms.channels_last_layout import PERMUTE_COPY_TARGETS
15+
from executorch.backends.transforms.dim_maps import _normalize_dims
1516
from executorch.backends.transforms.permute_pass_utils import (
1617
get_shape,
1718
RemoveOrReplacePassInterface,
@@ -78,7 +79,9 @@ def maybe_remove_or_replace(self, node: torch.fx.Node) -> bool:
7879

7980
permute_node_shape = [*cast(list, get_shape(node.graph.owning_module, node))]
8081

81-
permute_dims = cast(list, node.args[1])
82+
permute_dims = _normalize_dims(
83+
cast(list, node.args[1]), len(permute_node_shape)
84+
)
8285
view_node = users[0]
8386

8487
if view_node.meta.get("val") is None:

‎backends/transforms/targets.bzl‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,7 @@ def define_common_targets():
750750
],
751751
deps = [
752752
":channels_last_layout",
753+
":dim_maps",
753754
"//caffe2:torch",
754755
"//executorch/exir:pass_base",
755756
"//executorch/exir/dialects:lib",

‎backends/transforms/test/test_permute_optimization_passes.py‎

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,84 @@ def test_negative_not_squeeze_like(self) -> None:
591591
self.assertEqual(targets[0], exir_ops.edge.aten.view_copy.default)
592592
self.assertEqual(targets[1], exir_ops.edge.aten.permute_copy.default)
593593

594+
def test_negative_permute_dims_below_unsqueeze_axis(self) -> None:
595+
builder = GraphBuilder()
596+
x_data = torch.randn(2, 3, 4)
597+
x = builder.placeholder("x", x_data)
598+
p = builder.call_operator(
599+
op=exir_ops.edge.aten.permute_copy.default, args=(x, [-3, 2, 1])
600+
)
601+
v = builder.call_operator(
602+
op=exir_ops.edge.aten.view_copy.default, args=(p, [2, 4, 1, 3])
603+
)
604+
builder.output([v])
605+
original = builder.get_graph_module()
606+
gm_before = copy.deepcopy(original)
607+
608+
result = cast(
609+
PassResult,
610+
PostponePermuteOpBelowSqueezeOrUnsqueezeLikeView().call(original),
611+
)
612+
613+
self.assertTrue(result.modified)
614+
gm = result.graph_module
615+
self.assertEqual(
616+
get_compute_nodes(gm),
617+
[
618+
exir_ops.edge.aten.view_copy.default,
619+
exir_ops.edge.aten.permute_copy.default,
620+
],
621+
)
622+
(permute,) = gm.graph.find_nodes(
623+
op="call_function", target=exir_ops.edge.aten.permute_copy.default
624+
)
625+
self.assertEqual(permute.args[1], [0, 3, 2, 1])
626+
validate_numerics(
627+
gm_before,
628+
gm,
629+
[x_data],
630+
"PostponePermuteOpBelowSqueezeOrUnsqueezeLikeView",
631+
)
632+
633+
def test_negative_permute_dims_below_squeeze_axis(self) -> None:
634+
builder = GraphBuilder()
635+
x_data = torch.randn(2, 3, 1, 4)
636+
x = builder.placeholder("x", x_data)
637+
p = builder.call_operator(
638+
op=exir_ops.edge.aten.permute_copy.default, args=(x, [-4, 2, 1, 3])
639+
)
640+
v = builder.call_operator(
641+
op=exir_ops.edge.aten.view_copy.default, args=(p, [2, 3, 4])
642+
)
643+
builder.output([v])
644+
original = builder.get_graph_module()
645+
gm_before = copy.deepcopy(original)
646+
647+
result = cast(
648+
PassResult,
649+
PostponePermuteOpBelowSqueezeOrUnsqueezeLikeView().call(original),
650+
)
651+
652+
self.assertTrue(result.modified)
653+
gm = result.graph_module
654+
self.assertEqual(
655+
get_compute_nodes(gm),
656+
[
657+
exir_ops.edge.aten.view_copy.default,
658+
exir_ops.edge.aten.permute_copy.default,
659+
],
660+
)
661+
(permute,) = gm.graph.find_nodes(
662+
op="call_function", target=exir_ops.edge.aten.permute_copy.default
663+
)
664+
self.assertEqual(permute.args[1], [0, 1, 2])
665+
validate_numerics(
666+
gm_before,
667+
gm,
668+
[x_data],
669+
"PostponePermuteOpBelowSqueezeOrUnsqueezeLikeView",
670+
)
671+
594672

595673
class FuseTransposeOrPermuteOpPairsTest(unittest.TestCase):
596674
def test_per_tensor_qdq_is_bypassed(self) -> None:

0 commit comments

Comments
 (0)