Skip to content

Commit 72945a9

Browse files
apullinfacebook-github-bot
authored andcommitted
Quantize moveaxis/movedim so they delegate to Ethos-U (pytorch#20314)
Summary: The ARM PT2 quantizer's pass-through shared-qspec set in quantization_annotator.py (_one_to_one_shared_input_qspec) covers permute/permute_copy/transpose/view/squeeze etc., but omits aten.moveaxis/aten.movedim. A model that uses torch.moveaxis therefore leaves those ops unquantized: the quantizer brackets each one with dequantize -> moveaxis(float) -> quantize. On lowering, moveaxis decomposes to a float permute_copy. The Ethos-U55 operator-support check (operator_support/ethos_u55_support.py) only delegates permute_copy for int8/int16/int32, so it rejects the float one. Each rejected permute is stranded on the host, splitting the model into many delegated partitions (one NPU island per permute), which bloats the .pte with per-partition delegate overhead and host round-trips. Add aten.moveaxis.int / aten.movedim.int to _one_to_one_shared_input_qspec (guarded with getattr for torch-build variance, mirroring the existing transpose.Dimname handling) so they share the input quantization spec exactly like transpose/permute. They then stay int8, decompose to int8 permute_copy, and delegate to the NPU -- eliminating the host float islands. Impact: a quantized example ensemble (ConvNeXt-style blocks that use torch.moveaxis) that previously lowered into 9 Ethos-U55 partitions now lowers into a single delegate, with zero host permutes and ~24% smaller .pte, with no model changes. Generalizes to any moveaxis/movedim-using model on the Ethos-U backend. Reviewed By: JakeStevens Differential Revision: D108478011
1 parent 6f6225c commit 72945a9

3 files changed

Lines changed: 34 additions & 1 deletion

File tree

backends/arm/quantizer/quantization_annotator.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,10 @@ def _get_fixed_qparams_qspec(
620620
# dequant -> neg -> requant chain.
621621
torch.ops.aten.neg.default,
622622
torch.ops.aten.detach_copy.default,
623+
torch.ops.aten.moveaxis.int,
624+
torch.ops.aten.moveaxis.intlist,
625+
torch.ops.aten.movedim.int,
626+
torch.ops.aten.movedim.intlist,
623627
}
624628

625629
# Dimname has been removed from upstream PyTorch, but there may be a window
@@ -631,6 +635,7 @@ def _get_fixed_qparams_qspec(
631635
if _transpose_dimname is not None:
632636
_one_to_one_shared_input_qspec.add(_transpose_dimname)
633637

638+
634639
_one_to_one_shared_input_or_input_act_qspec: set[OpOverload] = {
635640
torch.ops.aten.alias.default,
636641
torch.ops.aten.clone.default,

backends/arm/test/ops/test_permute.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ def forward(self, x):
7878
return torch.permute(x, self.dims)
7979

8080

81+
class SimpleMoveAxis(torch.nn.Module):
82+
83+
def forward(self, x):
84+
return torch.moveaxis(x, 1, -1)
85+
86+
8187
@common.parametrize(
8288
"test_data", test_data_suite | test_data_suite_fp16 | test_data_suite_bf16
8389
)
@@ -118,6 +124,17 @@ def test_permute_u55_INT(test_data):
118124
pipeline.run()
119125

120126

127+
def test_moveaxis_u55_INT():
128+
pipeline = EthosU55PipelineINT[input_t1](
129+
SimpleMoveAxis(),
130+
(torch.rand(1, 4, 5, 6),),
131+
"torch.ops.aten.moveaxis.int",
132+
exir_ops="executorch_exir_dialects_edge__ops_aten_permute_copy_default",
133+
run_on_fvp=False,
134+
)
135+
pipeline.run()
136+
137+
121138
@common.parametrize("test_data", test_data_suite_u55_reject)
122139
def test_permute_u55_INT_not_delegated(test_data: torch.Tensor):
123140
test_data, dims = test_data()

backends/arm/test/quantizer/test_generic_annotater.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from typing import Any, Callable, Tuple
88

99
import torch
10-
from executorch.backends.arm.quantizer import is_annotated
10+
from executorch.backends.arm.quantizer import is_annotated, quantization_annotator
1111
from executorch.backends.arm.test.tester.test_pipeline import TosaPipelineINT
1212
from executorch.backends.test.harness.stages import StageType
1313

@@ -89,6 +89,17 @@ def test_transpose_tosa_INT():
8989
)
9090

9191

92+
def test_moveaxis_movedim_shared_qspec_annotations():
93+
expected_ops = {
94+
torch.ops.aten.moveaxis.int,
95+
torch.ops.aten.moveaxis.intlist,
96+
torch.ops.aten.movedim.int,
97+
torch.ops.aten.movedim.intlist,
98+
}
99+
100+
assert expected_ops <= quantization_annotator._one_to_one_shared_input_qspec
101+
102+
92103
def test_tile_tosa_INT():
93104
check_annotation(
94105
SingleOpModel(torch.tile, (torch.randn(4, 4),), dims=(2,)),

0 commit comments

Comments
 (0)