Skip to content

Commit 649d7b1

Browse files
Erik-Lundellfacebook-github-bot
authored andcommitted
Integrate Corestone300 in unit tests (#4015)
Summary: setup_testing.sh needs to be run to build an executor_runner for tests. To enable this, pytest needs to be run with the following flags: -p executorch.backends.arm.test.common --arm_quantize_io --arm_run_corstone300 This is not yet enabled in CI. Pull Request resolved: #4015 Reviewed By: mergennachin Differential Revision: D59260236 Pulled By: digantdesai fbshipit-source-id: 2a138d0ef1936c376689258a4f5024e0b60028e4
1 parent c2147cb commit 649d7b1

File tree

15 files changed

+1119
-563
lines changed

15 files changed

+1119
-563
lines changed

backends/arm/arm_backend.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,9 @@ def tosa_compile_spec(self):
9494
self.output_format = "tosa"
9595
return self
9696

97-
def dump_intermediate_tosa(self, output_path: str):
97+
def dump_intermediate_artifacts_to(self, output_path: str):
9898
"""
99-
Output intermediate .tosa file
99+
Sets a path for dumping intermediate results during such as tosa and pte.
100100
"""
101101
self.path_for_intermediates = output_path
102102
return self
@@ -131,7 +131,7 @@ def build(self):
131131

132132
if self.path_for_intermediates is not None:
133133
self.compile_spec.append(
134-
CompileSpec("debug_tosa_path", self.path_for_intermediates.encode())
134+
CompileSpec("debug_artifact_path", self.path_for_intermediates.encode())
135135
)
136136

137137
if self.permute_nhwc:
@@ -161,7 +161,7 @@ def is_tosa(compile_spec: List[CompileSpec]) -> bool:
161161

162162
def get_intermediate_path(compile_spec: List[CompileSpec]) -> str:
163163
for spec in compile_spec:
164-
if spec.key == "debug_tosa_path":
164+
if spec.key == "debug_artifact_path":
165165
return spec.value.decode()
166166
return None
167167

@@ -198,7 +198,7 @@ def generate_tosa_compile_spec(
198198
ArmCompileSpecBuilder()
199199
.tosa_compile_spec()
200200
.set_permute_memory_format(permute_memory_to_nhwc)
201-
.dump_intermediate_tosa(output_path)
201+
.dump_intermediate_artifacts_to(output_path)
202202
.build()
203203
)
204204

@@ -213,15 +213,13 @@ def preprocess( # noqa: C901
213213
logger.info("ArmBackend::preprocess")
214214

215215
# if a debug/test build capture output files from TOSA stage
216-
path = None
217-
debug_output = False
216+
artifact_path = None
218217
output_format = ""
219218
compile_flags = []
220219
permute_memory_to_nhwc = False
221220
for spec in compile_spec:
222-
if spec.key == "debug_tosa_path":
223-
path = spec.value.decode()
224-
debug_output = True
221+
if spec.key == "debug_artifact_path":
222+
artifact_path = spec.value.decode()
225223
if spec.key == "output_format":
226224
output_format = spec.value.decode()
227225
if spec.key == "compile_flags":
@@ -242,7 +240,7 @@ def preprocess( # noqa: C901
242240

243241
# Converted output for this subgraph, serializer needs path early as it emits
244242
# const data directly. Path created and data written only in debug builds.
245-
tosa_graph = ts.TosaSerializer(path)
243+
tosa_graph = ts.TosaSerializer(artifact_path)
246244

247245
node_visitors = get_node_visitors(edge_program)
248246

@@ -317,13 +315,13 @@ def preprocess( # noqa: C901
317315
else:
318316
# This will only happen if an unpartitioned graph is passed without
319317
# any checking of compatibility.
320-
dbg_fail(node, tosa_graph, path)
318+
dbg_fail(node, tosa_graph, artifact_path)
321319

322320
# TODO: It would be awesome if this dump could somehow be done on top level and not here.
323321
# Problem is that the desc.json has to be created on the tosa_graph object, which we can't
324322
# access from top level.
325-
if debug_output is True:
326-
dbg_tosa_dump(tosa_graph, path)
323+
if artifact_path is not None:
324+
dbg_tosa_dump(tosa_graph, artifact_path)
327325

328326
# Serialize and return the program. While we have always produced TOSA
329327
# output as an intermediate, some flows compile to device binaries in

backends/arm/runtime/ArmBackendEthosU.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,9 @@ class ArmBackend final : public PyTorchBackendInterface {
124124
if (!supported) {
125125
ET_LOG(
126126
Error,
127-
"Input %d expected Integer (4 byte) or Char (1 byte) integer inputs",
128-
i);
127+
"Input %d expected Integer (4 byte) or Char (1 byte) integer inputs, got ScalarType id %d",
128+
i,
129+
tensor_in.scalar_type());
129130
return Error::InvalidProgram;
130131
}
131132

@@ -199,11 +200,16 @@ class ArmBackend final : public PyTorchBackendInterface {
199200
const char* output_addr =
200201
handles.scratch_data + handles.outputs->io[i].offset;
201202
// Process input EValue into scratch
202-
int* output_address = (int*)output_addr;
203203
// Outputs are in the index immediately after inputs
204204
auto tensor_out = args[handles.inputs->count + i]->toTensor();
205205
for (int j = 0; j < tensor_out.numel(); j++) {
206-
tensor_out.mutable_data_ptr<int>()[j] = output_address[j];
206+
if (tensor_out.scalar_type() == ScalarType::Char) {
207+
char* output_address = (char*)output_addr;
208+
tensor_out.mutable_data_ptr<char>()[j] = output_address[j];
209+
} else {
210+
int* output_address = (int*)output_addr;
211+
tensor_out.mutable_data_ptr<int>()[j] = output_address[j];
212+
}
207213
}
208214
}
209215

backends/arm/test/common.py

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,85 @@
55
# LICENSE file in the root directory of this source tree.
66

77
import os
8+
import shutil
9+
import subprocess
810
import tempfile
911

12+
import pytest
13+
14+
import torch
15+
1016
from executorch.backends.arm.arm_backend import ArmCompileSpecBuilder
1117

18+
_enabled_options: list[str] = []
19+
20+
# ==== Pytest hooks ====
21+
22+
23+
def pytest_addoption(parser):
24+
parser.addoption("--arm_quantize_io", action="store_true")
25+
parser.addoption("--arm_run_corstone300", action="store_true")
26+
27+
28+
def pytest_configure(config):
29+
if config.option.arm_quantize_io:
30+
load_libquantized_ops_aot_lib()
31+
_enabled_options.append("quantize_io")
32+
if config.option.arm_run_corstone300:
33+
corstone300_exists = shutil.which("FVP_Corstone_SSE-300_Ethos-U55")
34+
if not corstone300_exists:
35+
raise RuntimeError(
36+
"Tests are run with --arm_run_corstone300 but corstone300 FVP is not installed."
37+
)
38+
_enabled_options.append("corstone300")
39+
40+
41+
def pytest_collection_modifyitems(config, items):
42+
if not config.option.arm_quantize_io:
43+
skip_if_aot_lib_not_loaded = pytest.mark.skip(
44+
"u55 tests can only run with quantize_io=True."
45+
)
46+
47+
for item in items:
48+
if "u55" in item.name:
49+
item.add_marker(skip_if_aot_lib_not_loaded)
50+
51+
52+
# ==== End of Pytest hooks =====
53+
54+
55+
def load_libquantized_ops_aot_lib():
56+
find_lib_cmd = [
57+
"find",
58+
"cmake-out-aot-lib",
59+
"-name",
60+
"libquantized_ops_aot_lib.so",
61+
]
62+
res = subprocess.run(find_lib_cmd, capture_output=True)
63+
if res.returncode == 0:
64+
library_path = res.stdout.decode().strip()
65+
torch.ops.load_library(library_path)
66+
67+
68+
def is_option_enabled(option: str, fail_if_not_enabled: bool = False) -> bool:
69+
"""
70+
Returns whether an option is successfully enabled, i.e. if the flag was
71+
given to pytest and the necessary requirements are available.
72+
Implemented options are:
73+
- corstone300.
74+
- quantize_io.
75+
76+
The optional parameter 'fail_if_not_enabled' makes the function raise
77+
a RuntimeError instead of returning False.
78+
"""
79+
if option.lower() in _enabled_options:
80+
return True
81+
else:
82+
if fail_if_not_enabled:
83+
raise RuntimeError(f"Required option '{option}' for test is not enabled")
84+
else:
85+
return False
86+
1287

1388
def get_tosa_compile_spec(permute_memory_to_nhwc=False, custom_path=None):
1489
"""
@@ -21,16 +96,17 @@ def get_tosa_compile_spec(permute_memory_to_nhwc=False, custom_path=None):
2196
ArmCompileSpecBuilder()
2297
.tosa_compile_spec()
2398
.set_permute_memory_format(permute_memory_to_nhwc)
24-
.dump_intermediate_tosa(intermediate_path)
99+
.dump_intermediate_artifacts_to(intermediate_path)
25100
.build()
26101
)
27102
return compile_spec
28103

29104

30-
def get_u55_compile_spec(permute_memory_to_nhwc=False):
105+
def get_u55_compile_spec(permute_memory_to_nhwc=False, custom_path=None):
31106
"""
32107
Default compile spec for Ethos-U55 tests.
33108
"""
109+
artifact_path = custom_path or tempfile.mkdtemp(prefix="arm_u55_")
34110
compile_spec = (
35111
ArmCompileSpecBuilder()
36112
.ethosu_compile_spec(
@@ -39,7 +115,9 @@ def get_u55_compile_spec(permute_memory_to_nhwc=False):
39115
memory_mode="Shared_Sram",
40116
extra_flags=None,
41117
)
118+
.set_quantize_io(is_option_enabled("quantize_io"))
42119
.set_permute_memory_format(permute_memory_to_nhwc)
120+
.dump_intermediate_artifacts_to(artifact_path)
43121
.build()
44122
)
45123
return compile_spec

backends/arm/test/ops/test_add.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
import torch
1414
from executorch.backends.arm.test import common
15-
1615
from executorch.backends.arm.test.tester.arm_tester import ArmTester
1716
from executorch.exir import EdgeCompileConfig
1817
from parameterized import parameterized
@@ -24,9 +23,11 @@
2423
class TestSimpleAdd(unittest.TestCase):
2524
class Add(torch.nn.Module):
2625
test_parameters = [
27-
(torch.ones(5),),
26+
(torch.FloatTensor([1, 2, 3, 5, 7]),),
2827
(3 * torch.ones(8),),
2928
(10 * torch.randn(8),),
29+
(torch.ones(1, 1, 4, 4),),
30+
(torch.ones(1, 3, 4, 2),),
3031
]
3132

3233
def __init__(self):
@@ -38,6 +39,10 @@ def forward(self, x):
3839

3940
class Add2(torch.nn.Module):
4041
test_parameters = [
42+
(
43+
torch.FloatTensor([1, 2, 3, 5, 7]),
44+
(torch.FloatTensor([2, 1, 2, 1, 10])),
45+
),
4146
(torch.ones(1, 1, 4, 4), torch.ones(1, 1, 4, 4)),
4247
(torch.randn(1, 1, 4, 4), torch.ones(1, 1, 4, 1)),
4348
(torch.randn(1, 1, 4, 4), torch.randn(1, 1, 4, 1)),
@@ -95,9 +100,11 @@ def _test_add_tosa_BI_pipeline(
95100
)
96101

97102
def _test_add_u55_BI_pipeline(
98-
self, module: torch.nn.Module, test_data: Tuple[torch.Tensor]
103+
self,
104+
module: torch.nn.Module,
105+
test_data: Tuple[torch.Tensor],
99106
):
100-
(
107+
tester = (
101108
ArmTester(
102109
module,
103110
example_inputs=test_data,
@@ -107,12 +114,16 @@ def _test_add_u55_BI_pipeline(
107114
.export()
108115
.check_count({"torch.ops.aten.add.Tensor": 1})
109116
.check(["torch.ops.quantized_decomposed"])
110-
.to_edge(config=self._edge_compile_config)
117+
.to_edge()
111118
.partition()
112119
.check_count({"torch.ops.higher_order.executorch_call_delegate": 1})
113120
.to_executorch()
121+
.serialize()
114122
)
115123

124+
if common.is_option_enabled("corstone300"):
125+
tester.run_method_and_compare_outputs(qtol=1, inputs=test_data)
126+
116127
@parameterized.expand(Add.test_parameters)
117128
def test_add_tosa_MI(self, test_data: torch.Tensor):
118129
test_data = (test_data,)

backends/arm/test/ops/test_conv.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,9 @@ def forward(self, x):
239239
testsuite_u55.remove(("2x2_3x1x40x40_nobias", conv2d_2x2_3x1x40x40_nobias))
240240
testsuite_u55.remove(("5x5_3x2x128x128_st1", conv2d_5x5_3x2x128x128_st1))
241241

242+
# Fails when enabling CompileSpec.set_quantize_io(True). MLETORCH-191.
243+
testsuite_u55.remove(("2x2_1x1x14x14_st2", conv2d_2x2_1x1x14x14_st2))
244+
242245

243246
class TestConv2D(unittest.TestCase):
244247
def _test_conv2d_tosa_MI_pipeline(

backends/arm/test/ops/test_depthwise_conv.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@
125125
)
126126
testsuite_u55.remove(("two_dw_conv2d", two_dw_conv2d))
127127

128+
# Fails when enabling CompileSpec.set_quantize_io(True). MLETORCH-191.
129+
testsuite_u55.remove(("3x3_1x3x256x256_gp3_st1", dw_conv2d_3x3_1x3x256x256_gp3_st1))
130+
128131

129132
class TestDepthwiseConv2D(unittest.TestCase):
130133
def _test_dw_conv2d_tosa_MI_pipeline(
@@ -190,6 +193,7 @@ def test_dw_conv2d_tosa_MI(self, test_name, model):
190193
def test_dw_conv2d_tosa_BI(self, test_name, model):
191194
self._test_dw_conv2d_tosa_BI_pipeline(model, model.get_inputs())
192195

193-
@parameterized.expand(testsuite_u55)
196+
@parameterized.expand(testsuite_u55, skip_on_empty=True)
197+
@unittest.expectedFailure
194198
def test_dw_conv2d_u55_BI(self, test_name, model):
195199
self._test_dw_conv2d_u55_BI_pipeline(model, model.get_inputs())

0 commit comments

Comments
 (0)