Skip to content

Commit 7682d9c

Browse files
authored
Cortex-M: Fix pass ordering for quantized attention (#23923)
Convert scalar arguments before folding quantization nodes, and replace quantization nodes after lowering ATen ops to Cortex-M kernels. Avoid retracing partially lowered attention graphs whose matrix inputs have incompatible int8/int32 types. Unblock default Cortex-M lowering of attention patterns used by DeiT Tiny and similar models. Add M55/M85 coverage for numerical accuracy and PTE serialization. AI-assisted: OpenAI Codex Signed-off-by: Zingo Andersen <Zingo.Andersen@arm.com>
1 parent 569e030 commit 7682d9c

5 files changed

Lines changed: 84 additions & 6 deletions

File tree

‎backends/cortex_m/passes/cortex_m_pass_manager.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,15 @@ class CortexMPassManager(ExportedProgramPassManager):
111111
legacy_pass_list: list[PassClass] = [
112112
# Run before folding so qparams attach to max_pool2d values, not tuple + getitem.
113113
RemoveGetItemPass,
114-
FoldAndAnnotateQParamsPass,
114+
# Retrace scalar ops while the graph still has floating-point Q/DQ inputs.
115115
ReplaceScalarWithTensorArgPass,
116-
ReplaceQuantNodesPass,
116+
FoldAndAnnotateQParamsPass,
117117
ActivationFusionPass,
118118
QuantizedClampActivationPass,
119119
DecomposeHardswishPass,
120120
AtenToCortexMPass,
121+
# Retrace only after quantized ATen ops have become Cortex-M kernels.
122+
ReplaceQuantNodesPass,
121123
FuseConvPaddingPass,
122124
InitializeScratchBuffersPass,
123125
LiftConstantTensorsPass,

‎backends/cortex_m/test/build_test_runner.sh‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,11 @@ ops_list=(
5151
aten::cat.out
5252
aten::full.out
5353
aten::ge.Tensor_out
54+
aten::permute_copy.out
55+
aten::slice_copy.Tensor_out
5456
aten::unsqueeze_copy.out
5557
aten::squeeze_copy.dim_out
58+
aten::squeeze_copy.dims_out
5659
aten::select_copy.int_out
5760
aten::amax.out
5861
cortex_m::quantize_per_tensor.out
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Copyright 2026 Arm Limited and/or its affiliates.
2+
#
3+
# This source code is licensed under the BSD-style license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
6+
import torch
7+
from executorch.backends.arm.test.common import parametrize
8+
from executorch.backends.cortex_m.test.tester import CortexMTester, McuTestCase
9+
10+
11+
class CortexMAttention(torch.nn.Module):
12+
ops_before_transforms = {
13+
"executorch_exir_dialects_edge__ops_aten_linear_default": 1,
14+
"executorch_exir_dialects_edge__ops_aten_bmm_default": 2,
15+
"executorch_exir_dialects_edge__ops_aten_mul_Tensor": 1,
16+
"executorch_exir_dialects_edge__ops_aten__softmax_default": 1,
17+
}
18+
19+
ops_after_transforms = {
20+
"executorch_exir_dialects_edge__ops_cortex_m_quantized_linear_default": 1,
21+
"executorch_exir_dialects_edge__ops_cortex_m_quantized_batch_matmul_default": 2,
22+
"executorch_exir_dialects_edge__ops_cortex_m_softmax_default": 1,
23+
}
24+
25+
def __init__(self):
26+
super().__init__()
27+
self.qkv = torch.nn.Linear(12, 36)
28+
29+
def forward(self, x):
30+
qkv = self.qkv(x).reshape(1, 5, 3, 3, 4).permute(2, 0, 3, 1, 4)
31+
q, k, v = qkv.unbind(0)
32+
scores = (q * 0.5) @ k.transpose(-2, -1)
33+
return (scores.softmax(dim=-1) @ v).transpose(1, 2).reshape(1, 5, 12)
34+
35+
36+
test_cases = {
37+
"qkv_attention": McuTestCase(CortexMAttention(), (torch.randn(1, 5, 12),)),
38+
}
39+
40+
41+
@parametrize("test_case", test_cases)
42+
def test_dialect_attention(test_case, cortex_m_target):
43+
tester = CortexMTester(
44+
test_case.model, test_case.example_inputs, target_config=cortex_m_target
45+
)
46+
tester.test_dialect(
47+
test_case.model.ops_before_transforms,
48+
test_case.model.ops_after_transforms,
49+
qtol=2,
50+
)
51+
52+
53+
@parametrize("test_case", test_cases)
54+
def test_implementation_attention(test_case, cortex_m_target):
55+
tester = CortexMTester(
56+
test_case.model, test_case.example_inputs, target_config=cortex_m_target
57+
)
58+
tester.test_implementation(qtol=2)

‎backends/cortex_m/test/ops/test_sdpa.py‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,6 @@ def _qkv(query_shape, key_shape, value_shape, dtype=torch.float32):
129129
"explicit_scale": (_SCALE_NOT_FOLDED, RuntimeError),
130130
"multihead_attention": (_LINEAR_RETRACED_AS_INT32, Exception),
131131
}
132-
xfail_cases_impl: dict[str, xfail_type] = {
133-
"multihead_attention": (_LINEAR_RETRACED_AS_INT32, Exception),
134-
}
135132

136133

137134
def _transform_for_annotation(model, example_inputs, dynamic_shapes=None):
@@ -161,7 +158,7 @@ def test_dialect_sdpa(test_case, cortex_m_target):
161158
)
162159

163160

164-
@parametrize("test_case", test_cases, xfails=xfail_cases_impl)
161+
@parametrize("test_case", test_cases)
165162
def test_implementation_sdpa(test_case, cortex_m_target):
166163
tester = CortexMTester(
167164
test_case.model, test_case.example_inputs, target_config=cortex_m_target

‎backends/cortex_m/test/targets.bzl‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Copyright (c) Meta Platforms, Inc. and affiliates.
22
# All rights reserved.
3+
# Copyright 2026 Arm Limited and/or its affiliates.
34
#
45
# This source code is licensed under the BSD-style license found in the
56
# LICENSE file in the root directory of this source tree.
@@ -104,6 +105,23 @@ def define_common_targets(is_fbcode = False):
104105
],
105106
)
106107

108+
python_pytest(
109+
name = "test_attention",
110+
srcs = ["ops/test_attention.py"],
111+
resources = ["conftest.py"],
112+
compile = "with-source",
113+
typing = False,
114+
# Implementation tests require the Cortex-M FVP runner, which Buck does not provide.
115+
env = {"PYTEST_ADDOPTS": "-k 'not test_implementation'"},
116+
deps = [
117+
"//caffe2:torch",
118+
"//executorch/backends/arm/test:common",
119+
"//executorch/backends/cortex_m:target_config",
120+
":tester",
121+
"fbsource//third-party/pypi/pytest:pytest",
122+
],
123+
)
124+
107125
python_pytest(
108126
name = "test_pass_manager",
109127
srcs = ["test_pass_manager.py"],

0 commit comments

Comments
 (0)