Skip to content

Commit 2c92642

Browse files
authored
Arm backend: Add tests for automatic list of supported ops (#21053)
Add tests for VGF backend cc @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell @rascani Signed-off-by: Elena Zhelezina <elena.zhelezina@arm.com>
1 parent 69a9918 commit 2c92642

6 files changed

Lines changed: 269 additions & 16 deletions

File tree

backends/arm/test/ops/test_add.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,3 +507,44 @@ def test_add_tensor_u85_INT_16a8w(test_data: input_t1):
507507
get_symmetric_a16w8_quantization_config(is_per_channel=per_channel_quantization)
508508
)
509509
pipeline.run()
510+
511+
512+
aten_op_scalar = "torch.ops.aten.add.Scalar"
513+
exir_op_scalar = "executorch_exir_dialects_edge__ops_aten_add_Scalar"
514+
515+
516+
class AddScalar(torch.nn.Module):
517+
def forward(self, x: torch.Tensor):
518+
return torch.ops.aten.add.Scalar(x, 1.5)
519+
520+
521+
@common.parametrize(
522+
"test_data", Add.test_data | Add.test_data_fp16 | Add.test_data_bf16
523+
)
524+
@common.SkipIfNoModelConverter
525+
def test_add_scalar_vgf_no_quant(test_data: input_t1):
526+
pipeline = VgfPipeline[input_t1](
527+
AddScalar(),
528+
test_data(),
529+
aten_op_scalar,
530+
exir_op_scalar,
531+
run_on_vulkan_runtime=True,
532+
quantize=False,
533+
)
534+
pipeline.run()
535+
536+
537+
@common.parametrize("test_data", Add.test_data)
538+
@common.SkipIfNoModelConverter
539+
def test_add_scalar_vgf_quant(test_data: input_t1):
540+
# Quantized scalar add is lowered through the Tensor overload.
541+
# We keep this as add.Tensor rather than add.Scalar.
542+
pipeline = VgfPipeline[input_t1](
543+
AddScalar(),
544+
test_data(),
545+
aten_op,
546+
exir_op,
547+
run_on_vulkan_runtime=True,
548+
quantize=True,
549+
)
550+
pipeline.run()

backends/arm/test/ops/test_div.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from typing import Optional, Tuple, Union
1010

11+
import pytest
1112
import torch
1213
from executorch.backends.arm.test import common
1314

@@ -89,6 +90,12 @@ def forward(
8990
return torch.div(input=input_, other=other_, rounding_mode=rounding_mode)
9091

9192

93+
# We need to get this op directly to have coverage
94+
class DivScalar(torch.nn.Module):
95+
def forward(self, input_: torch.Tensor):
96+
return torch.ops.aten.div.Scalar(input_, 2.0)
97+
98+
9299
@common.parametrize("test_data", test_data_suite)
93100
def test_div_tensor_tosa_FP(test_data: Tuple):
94101
pipeline = TosaPipelineFP[input_t1](Div(), test_data(), aten_op, exir_op)
@@ -149,3 +156,54 @@ def test_div_tensor_vgf_quant(test_data: Tuple):
149156
quantize=True,
150157
)
151158
pipeline.run()
159+
160+
161+
aten_op_scalar = "torch.ops.aten.div.Scalar"
162+
exir_op_scalar = "executorch_exir_dialects_edge__ops_aten_div_Scalar"
163+
164+
test_data_suite_scalar = {
165+
"op_div_scalar_rank1_rand": lambda: (torch.rand(5) + 1.0,),
166+
"op_div_scalar_rank4_rand": lambda: (torch.rand(5, 10, 25, 20) + 1.0,),
167+
}
168+
169+
170+
@common.parametrize("test_data", test_data_suite_scalar)
171+
@common.SkipIfNoModelConverter
172+
def test_div_scalar_vgf_no_quant(test_data: input_t1):
173+
"""Test Tensor / Scalar division (VGF FP)."""
174+
pipeline = VgfPipeline[input_t1](
175+
DivScalar(),
176+
test_data(),
177+
aten_op_scalar,
178+
exir_op_scalar,
179+
quantize=False,
180+
)
181+
pipeline.run()
182+
183+
184+
@common.parametrize("test_data", test_data_suite_scalar)
185+
@pytest.mark.xfail(
186+
reason=(
187+
"Quantized div.Scalar is rewritten to div.Tensor after the initial "
188+
"quantizer support match, so it misses DecomposeDivPass and remains "
189+
"outside the VGF delegate. MLETORCH-2366"
190+
),
191+
strict=True,
192+
)
193+
@common.SkipIfNoModelConverter
194+
def test_div_scalar_vgf_quant(test_data: input_t1):
195+
"""Test Tensor / Scalar division (VGF INT).
196+
197+
The quantized scalar path is rewritten before the ATen and should not be
198+
checked as div.Scalar. We keep expected op lists empty, matching the
199+
existing quantized div.Tensor test.
200+
201+
"""
202+
pipeline = VgfPipeline[input_t1](
203+
DivScalar(),
204+
test_data(),
205+
[],
206+
[],
207+
quantize=True,
208+
)
209+
pipeline.run()

backends/arm/test/ops/test_index_tensor.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
OpNotSupportedPipeline,
1313
TosaPipelineFP,
1414
TosaPipelineINT,
15+
VgfPipeline,
1516
)
1617

1718

@@ -587,3 +588,37 @@ def test_index_tensor_u55_INT_not_delegated(test_data: input_params):
587588
quantize=True,
588589
u55_subset=True,
589590
).run()
591+
592+
593+
@common.parametrize(
594+
"test_data",
595+
IndexTensor.test_data_fp | IndexTensor.test_data_bf16 | IndexTensor.test_data_fp16,
596+
)
597+
@common.SkipIfNoModelConverter
598+
def test_index_tensor_vgf_no_quant(test_data: input_params):
599+
test_input = test_data
600+
pipeline = VgfPipeline[input_params](
601+
IndexTensor(),
602+
test_input,
603+
IndexTensorTestCommon.aten_op,
604+
IndexTensorTestCommon.exir_op,
605+
atol=IndexTensorTestCommon.atol,
606+
rtol=IndexTensorTestCommon.rtol,
607+
tosa_extensions=["bf16"],
608+
quantize=False,
609+
)
610+
pipeline.run()
611+
612+
613+
@common.parametrize("test_data", IndexTensor.test_data_int | IndexTensor.test_data_fp)
614+
@common.SkipIfNoModelConverter
615+
def test_index_tensor_vgf_quant(test_data: input_params):
616+
test_input = test_data
617+
pipeline = VgfPipeline[input_params](
618+
IndexTensor(),
619+
test_input,
620+
IndexTensorTestCommon.aten_op,
621+
IndexTensorTestCommon.exir_op,
622+
quantize=True,
623+
)
624+
pipeline.run()

backends/arm/test/ops/test_mean_dim.py

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
#
55
# This source code is licensed under the BSD-style license found in the
66
# LICENSE file in the root directory of this source tree.
7-
from typing import Callable
87

98
import pytest
109
import torch
@@ -351,43 +350,43 @@ def test_mean_dim_vgf_quant(test_data):
351350
pipeline = VgfPipeline[input_t](
352351
MeanDim(dim, keep_dim),
353352
(test_data_val,),
353+
[], # Quantized mean.dim may be decomposed to sum, avgpool, or both.
354354
[],
355355
symmetric_io_quantization=True,
356356
quantize=True,
357357
)
358358
pipeline.run()
359359

360360

361-
mean_input_t = tuple[torch.Tensor, bool]
362-
363-
364361
class MeanDefault(torch.nn.Module):
365-
def forward(self, tensor: torch.Tensor, keepdim: bool):
366-
return tensor.mean()
362+
torch_op = "torch.ops.aten.mean.default"
363+
exir_op = "executorch_exir_dialects_edge__ops_aten_mean_default"
367364

368-
test_data_suite: dict[str, Callable[[], mean_input_t]] = {
369-
"rank_2": lambda: (
370-
torch.rand(1, 2),
371-
False,
372-
),
373-
"rank_2_keepdim": lambda: (torch.rand(5, 5), True),
374-
"rank_4": lambda: (torch.rand(5, 1, 10, 1), False),
365+
test_data_suite = {
366+
"rank_1": lambda: (torch.rand(7),),
367+
"rank_2": lambda: (torch.rand(2, 5),),
368+
"rank_3": lambda: (torch.rand(2, 5, 7),),
369+
"rank_4": lambda: (torch.rand(1, 5, 7, 3),),
375370
}
376371

372+
def forward(self, x: torch.Tensor):
373+
return torch.mean(x)
374+
377375

378376
@common.parametrize("test_data", MeanDefault.test_data_suite)
379377
def test_mean_tosa_FP(test_data):
380-
pipeline = TosaPipelineFP[mean_input_t](
378+
pipeline = TosaPipelineFP[input_t](
381379
MeanDefault(),
382380
test_data(),
383-
[], # Might be sum, avgpool, or both
381+
MeanDefault.torch_op,
382+
MeanDefault.exir_op,
384383
)
385384
pipeline.run()
386385

387386

388387
@common.parametrize("test_data", MeanDefault.test_data_suite)
389388
def test_mean_tosa_INT(test_data):
390-
pipeline = TosaPipelineINT[mean_input_t](
389+
pipeline = TosaPipelineINT[input_t](
391390
MeanDefault(),
392391
test_data(),
393392
[], # Might be sum, avgpool, or both
@@ -396,6 +395,33 @@ def test_mean_tosa_INT(test_data):
396395
pipeline.run()
397396

398397

398+
@common.parametrize("test_data", MeanDefault.test_data_suite)
399+
@common.SkipIfNoModelConverter
400+
def test_mean_vgf_no_quant(test_data):
401+
pipeline = VgfPipeline[input_t](
402+
MeanDefault(),
403+
test_data(),
404+
MeanDefault.torch_op,
405+
MeanDefault.exir_op,
406+
quantize=False,
407+
)
408+
pipeline.run()
409+
410+
411+
@common.parametrize("test_data", MeanDefault.test_data_suite)
412+
@common.SkipIfNoModelConverter
413+
def test_mean_vgf_quant(test_data):
414+
pipeline = VgfPipeline[input_t](
415+
MeanDefault(),
416+
test_data(),
417+
[], # Quantized mean.default may be decomposed to sum, avgpool, or both.
418+
[],
419+
symmetric_io_quantization=True,
420+
quantize=True,
421+
)
422+
pipeline.run()
423+
424+
399425
a16w8_mean_test_parameters = {
400426
"rank_2_keepdim": lambda: (torch.rand(7, 3), (0, 1), True),
401427
"rank_3_keepdim": lambda: (torch.rand(5, 7, 3), (0, 1, 2), True),

backends/arm/test/ops/test_mul.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@
2020
)
2121

2222
input_t1 = Tuple[torch.Tensor, torch.Tensor] # Input x
23+
input_t_scalar = Tuple[torch.Tensor]
24+
2325
aten_op = "torch.ops.aten.mul.Tensor"
26+
exir_op = "executorch_exir_dialects_edge__ops_aten_mul_Tensor"
27+
aten_op_scalar = "torch.ops.aten.mul.Scalar"
28+
exir_op_scalar = "executorch_exir_dialects_edge__ops_aten_mul_Scalar"
29+
2430

2531
test_data_suite = {
2632
# (test_name, input, other,) See torch.mul() for info
@@ -117,6 +123,11 @@
117123
),
118124
}
119125

126+
test_data_suite_scalar = {
127+
"op_mul_scalar_rank1_rand": lambda: (torch.rand(5) * 3.7,),
128+
"op_mul_scalar_rank4_randn": lambda: (torch.randn(1, 10, 25, 20),),
129+
}
130+
120131

121132
class Mul(torch.nn.Module):
122133

@@ -128,6 +139,11 @@ def forward(
128139
return input_ * other_
129140

130141

142+
class MulScalar(torch.nn.Module):
143+
def forward(self, input_: torch.Tensor):
144+
return torch.ops.aten.mul.Scalar(input_, 2.0)
145+
146+
131147
@common.parametrize(
132148
"test_data", test_data_suite | test_data_suite_bf16 | test_data_suite_fp16
133149
)
@@ -365,3 +381,36 @@ def test_mul_tensor_16a8w_u85_INT(test_data: input_t1):
365381
a16w8_quantization=True,
366382
)
367383
pipeline.run()
384+
385+
386+
@common.parametrize("test_data", test_data_suite_scalar)
387+
@common.SkipIfNoModelConverter
388+
def test_mul_scalar_vgf_no_quant(test_data: input_t_scalar):
389+
"""Test Tensor * Scalar multiplication (VGF FP)."""
390+
pipeline = VgfPipeline[input_t_scalar](
391+
MulScalar(),
392+
test_data(),
393+
aten_op_scalar,
394+
exir_op_scalar,
395+
quantize=False,
396+
)
397+
pipeline.run()
398+
399+
400+
@common.parametrize("test_data", test_data_suite_scalar)
401+
@common.SkipIfNoModelConverter
402+
def test_mul_scalar_vgf_quant(test_data: input_t_scalar):
403+
"""Test Tensor * Scalar multiplication (VGF INT).
404+
405+
The quantized scalar path is lowered through the Tensor overload, so use
406+
mul.Tensor expectations rather than mul.Scalar.
407+
408+
"""
409+
pipeline = VgfPipeline[input_t_scalar](
410+
MulScalar(),
411+
test_data(),
412+
aten_op,
413+
exir_op,
414+
quantize=True,
415+
)
416+
pipeline.run()

0 commit comments

Comments
 (0)