Skip to content

Commit ee168ee

Browse files
authored
Arm backend: Add count_program_io_kinds to Arm tester (#18405)
* Enables tests to check the ExportedProgram inputs/outputs are as expected * Re-enable fold_quantize path through ArmQuantizer to stop mutable inputs in test_static_cache.py from being folded Change-Id: I3b017923d6684aee784474d4894a81f4adc4c254 cc @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell Signed-off-by: Tom Allsop <tom.allsop@arm.com>
1 parent d33c72f commit ee168ee

4 files changed

Lines changed: 105 additions & 8 deletions

File tree

backends/arm/quantizer/arm_quantizer.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -808,17 +808,19 @@ def quantize_with_submodules(
808808
for _, submodule, _ in get_cond_while_submodules_nested(
809809
prepared, apply_quantization=True
810810
):
811-
converted = convert_pt2e(submodule)
811+
converted = convert_pt2e(submodule, fold_quantize=fold_quantize)
812812
for submodule_node in submodule.graph.nodes:
813813
if is_submodule_node(submodule_node):
814814
for nested_name, nested_sub, _ in get_cond_while_submodules_nested(
815815
submodule, apply_quantization=True
816816
):
817817
converted.set_submodule(
818-
nested_name, convert_pt2e(nested_sub), strict=True
818+
nested_name,
819+
convert_pt2e(nested_sub, fold_quantize=fold_quantize),
820+
strict=True,
819821
)
820822

821-
return convert_pt2e(prepared)
823+
return convert_pt2e(prepared, fold_quantize=fold_quantize)
822824

823825

824826
class _TOSAQuantizerV1(Quantizer):

backends/arm/test/modules/test_static_cache.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
TosaPipelineINT,
2020
VgfPipeline,
2121
)
22+
from torch.export.graph_signature import InputKind, OutputKind
23+
2224
from transformers import LlamaConfig
2325
from transformers.cache_utils import StaticCache
2426

@@ -34,6 +36,17 @@
3436
}
3537

3638

39+
EXPECTED_INPUT_COUNTS = {
40+
InputKind.BUFFER: 2,
41+
InputKind.USER_INPUT: 3,
42+
}
43+
44+
EXPECTED_OUTPUT_COUNTS = {
45+
OutputKind.BUFFER_MUTATION: 2,
46+
OutputKind.USER_OUTPUT: 2,
47+
}
48+
49+
3750
@torch.no_grad()
3851
class StaticCacheModule(torch.nn.Module):
3952
def __init__(
@@ -121,15 +134,18 @@ def test_static_cache_tosa_FP(test_data):
121134
exir_op=[],
122135
transform_passes=[InsertInt32CastsAfterInt64PlaceholdersPass()],
123136
)
137+
pipeline.count_program_io_kinds(EXPECTED_INPUT_COUNTS, EXPECTED_OUTPUT_COUNTS)
124138
pipeline.run()
125139

126140

141+
@pytest.mark.xfail(reason="BUFFER_MUTATION count mismatch: MLETORCH-1971")
127142
@common.parametrize("test_data", test_configs)
128143
def test_static_cache_tosa_INT(test_data):
129144
module = StaticCacheModule(test_data).eval()
130145
pipeline = TosaPipelineINT[input_t](
131146
module, module.get_inputs(), aten_op=[], exir_op=[], fold_quantize=False
132147
)
148+
pipeline.count_program_io_kinds(EXPECTED_INPUT_COUNTS, EXPECTED_OUTPUT_COUNTS)
133149
pipeline.run()
134150

135151

@@ -146,14 +162,22 @@ def test_static_cache_u55_INT(test_data):
146162
pipeline.run()
147163

148164

149-
@common.XfailIfNoCorstone320
150165
@common.parametrize(
151166
"test_data",
152167
test_configs,
153168
xfails={
154-
"multihead_attention": "Incorrect numerical behavior: MLBEDSW-11589",
155-
"grouped_query_attention": "Incorrect numerical behavior: MLBEDSW-11589",
156-
"multi_query_attention": "Incorrect numerical behavior: MLBEDSW-11589",
169+
"multihead_attention": (
170+
"BUFFER_MUTATION count mismatch: MLETORCH-1971"
171+
"Incorrect numerical behavior: MLBEDSW-11589"
172+
),
173+
"grouped_query_attention": (
174+
"BUFFER_MUTATION count mismatch: MLETORCH-1971"
175+
"Incorrect numerical behavior: MLBEDSW-11589"
176+
),
177+
"multi_query_attention": (
178+
"BUFFER_MUTATION count mismatch: MLETORCH-1971"
179+
"Incorrect numerical behavior: MLBEDSW-11589"
180+
),
157181
},
158182
)
159183
def test_static_cache_u85_INT(test_data):
@@ -166,6 +190,7 @@ def test_static_cache_u85_INT(test_data):
166190
)
167191
# U85: keep _to_dim_order_copy portable for int64->int32 cast of cache_position (not delegatable).
168192
pipeline.tester.use_portable_ops = True
193+
pipeline.count_program_io_kinds(EXPECTED_INPUT_COUNTS, EXPECTED_OUTPUT_COUNTS)
169194
pipeline.run()
170195

171196

@@ -181,10 +206,12 @@ def test_static_cache_vgf_no_quant(test_data):
181206
transform_passes=[InsertInt32CastsAfterInt64PlaceholdersPass()],
182207
quantize=False,
183208
)
209+
pipeline.count_program_io_kinds(EXPECTED_INPUT_COUNTS, EXPECTED_OUTPUT_COUNTS)
184210
pipeline.run()
185211

186212

187213
@common.SkipIfNoModelConverter
214+
@pytest.mark.xfail(reason="BUFFER_MUTATION count mismatch: MLETORCH-1971")
188215
@common.parametrize("test_data", test_configs)
189216
def test_static_cache_vgf_quant(test_data):
190217
module = StaticCacheModule(test_data).eval()
@@ -197,4 +224,5 @@ def test_static_cache_vgf_quant(test_data):
197224
fold_quantize=False,
198225
tosa_spec="TOSA-1.0+INT",
199226
)
227+
pipeline.count_program_io_kinds(EXPECTED_INPUT_COUNTS, EXPECTED_OUTPUT_COUNTS)
200228
pipeline.run()

backends/arm/test/tester/arm_tester.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,13 @@
9090
)
9191
from tabulate import tabulate # type: ignore[import-untyped]
9292

93-
from torch.export.graph_signature import ExportGraphSignature, InputSpec, OutputSpec
93+
from torch.export.graph_signature import (
94+
ExportGraphSignature,
95+
InputKind,
96+
InputSpec,
97+
OutputKind,
98+
OutputSpec,
99+
)
94100
from torch.fx import Graph
95101

96102
from torchao.quantization.pt2e.quantizer import QuantizationSpec, SharedQuantizationSpec
@@ -1272,3 +1278,39 @@ def count_tosa_ops(graph_module: torch.fx.GraphModule, expected_ops: Dict[str, i
12721278
raise AssertionError(
12731279
f"Expected {expected_count} occurrences of TOSA op {op} but found {actual_count}."
12741280
)
1281+
1282+
1283+
def count_program_io_kinds(
1284+
exported_program: ExportedProgram,
1285+
expected_input_kinds: dict[InputKind, int] | None,
1286+
expected_output_kinds: dict[OutputKind, int] | None,
1287+
):
1288+
"""Checks that the number of InputKinds and OutputKinds in the final
1289+
ExportedProgram are equal to those in expected_inputs and
1290+
expected_outputs.
1291+
"""
1292+
1293+
def check_spec_count(
1294+
spec: Sequence[InputSpec] | Sequence[OutputSpec],
1295+
expected_counts: dict[Any, int],
1296+
):
1297+
kind = type(spec[0].kind)
1298+
counts: dict[Any, int] = Counter([s.kind for s in spec])
1299+
1300+
for e in kind:
1301+
if counts[e] != expected_counts.get(e, 0):
1302+
raise AssertionError(
1303+
f"Expected to find {expected_counts[e]} for input/output kind {e}, but found {counts[e]}."
1304+
)
1305+
1306+
if expected_input_kinds:
1307+
check_spec_count(
1308+
exported_program.graph_signature.input_specs,
1309+
expected_input_kinds,
1310+
)
1311+
1312+
if expected_output_kinds:
1313+
check_spec_count(
1314+
exported_program.graph_signature.output_specs,
1315+
expected_output_kinds,
1316+
)

backends/arm/test/tester/test_pipeline.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
from executorch.backends.test.harness.stages import StageType
5050
from executorch.exir.pass_base import ExportPass
5151
from torch._export.pass_base import PassType
52+
from torch.export.graph_signature import InputKind, OutputKind
5253
from torchao.quantization.pt2e.quantizer import QuantizationSpec
5354

5455
logger = logging.getLogger(__name__)
@@ -322,6 +323,30 @@ def _count_tosa_ops():
322323
)
323324
return self
324325

326+
def count_program_io_kinds(
327+
self,
328+
expected_inputs: dict[InputKind, int] | None,
329+
expected_outputs: dict[OutputKind, int] | None,
330+
):
331+
"""Assert the count of inputs/output specs of the ExportedProgram is
332+
correct.
333+
"""
334+
if not self.has_stage("to_edge_transform_and_lower"):
335+
raise RuntimeError(
336+
"count_program_io requires to_edge_transform_and_lower in the pipeline."
337+
)
338+
339+
def _count_program_io_kinds():
340+
stage = self.tester.get_artifact(StageType.TO_EDGE_TRANSFORM_AND_LOWER)
341+
arm_tester_module.count_program_io_kinds(
342+
stage.exported_program(), expected_inputs, expected_outputs
343+
)
344+
345+
self.add_stage_after(
346+
"to_edge_transform_and_lower", _count_program_io_kinds, suffix="count_io"
347+
)
348+
return self
349+
325350
def change_args(self, stage_id: str, *args, **kwargs):
326351
"""Updates the args to the given stage id."""
327352
pos = self.find_pos(stage_id)

0 commit comments

Comments
 (0)