Skip to content

Commit 0afc0d3

Browse files
committed
Arm backend: match operand ranks after view/permute propagation
The view/permute propagation passes can rank-broadcast one operand of a binary elementwise op — a per-channel affine's activation becomes rank 4 while its scale/bias constant stays rank 1 — which TOSA rejects, so Vela/Regor fails to compile the graph. Running MatchArgRanksPass after propagation re-matches operand ranks and keeps the graph TOSA-legal. Adds a regression test that lowers a per-channel-affine module for Ethos-U55. Authored with assistance from Claude.
1 parent 3f2a56e commit 0afc0d3

2 files changed

Lines changed: 54 additions & 0 deletions

File tree

backends/arm/_passes/arm_pass_manager.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,9 @@ def _tosa_pipeline(
633633
FuseViewCopyTransformPass(),
634634
PropagateViewCopyPermuteDownPass(self.compile_spec, exported_program),
635635
PropagateViewCopyPermuteUpPass(self.compile_spec, exported_program),
636+
# Propagation can leave a binary op with mismatched operand ranks,
637+
# which TOSA rejects; re-match ranks before lowering.
638+
MatchArgRanksPass(exported_program),
636639
RewriteHighRankSingletonPermutePass(),
637640
DecomposePermuteForU55Pass(),
638641
RewriteSlicePass(),
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
"""Regression test: view/permute propagation can leave a binary op with
7+
mismatched operand ranks (rank-4 activation vs rank-1 per-channel constant),
8+
which TOSA rejects. MatchArgRanksPass must run after propagation to keep the
9+
graph TOSA-legal, so this guards that a per-channel-affine module lowers for U55.
10+
"""
11+
12+
from typing import Tuple
13+
14+
import torch
15+
16+
from executorch.backends.arm.test.tester.test_pipeline import EthosU55PipelineINT
17+
18+
input_t = Tuple[torch.Tensor]
19+
20+
21+
class PerChannelAffineRank4(torch.nn.Module):
22+
"""Per-channel scale-mul + bias-add with a transpose to engage
23+
propagation.
24+
"""
25+
26+
def __init__(self) -> None:
27+
super().__init__()
28+
self.scale = torch.nn.Parameter(torch.rand(240) * 0.1 + 0.9)
29+
self.bias = torch.nn.Parameter(torch.randn(240) * 0.1)
30+
31+
def forward(self, x: torch.Tensor) -> torch.Tensor:
32+
y = x.reshape(1, 1, 1, x.shape[-1])
33+
y = y * self.scale + self.bias
34+
y = y.permute(0, 3, 1, 2)
35+
y = y.reshape(1, x.shape[-1])
36+
y = y * self.scale + self.bias
37+
return y
38+
39+
data = (torch.randn(1, 240),)
40+
41+
42+
def test_per_channel_affine_rank4_u55_INT() -> None:
43+
pipeline = EthosU55PipelineINT[input_t](
44+
PerChannelAffineRank4(),
45+
PerChannelAffineRank4.data,
46+
aten_ops=[],
47+
exir_ops=[],
48+
run_on_fvp=False,
49+
a16w8_quantization=True,
50+
)
51+
pipeline.run()

0 commit comments

Comments
 (0)