Skip to content

Commit e883dcb

Browse files
Mousiusgrant-arm
andauthored
Run AOT tests against reference system (apache#8744)
* Run AOT tests against reference system This introduces an alternative way of running AOT tests using the reference system added in apache#8514. This gives us additional assurance that the AOT output runs successfully on embedded platforms in our core test suite. I've also changed calculate_workspace_sizes to debug_workspace_sizes and default to False in most cases as it only needs to be True for a few cases to check theoutput with the debug flag - this was discovered trying to allocate 16MB in an embedded test 🙀 Co-authored-by: Grant Watson <grant.watson@arm.com> * Skip AOT reference system tests in i386 container * Add comment clarifying the reference system runner Co-authored-by: Grant Watson <grant.watson@arm.com>
1 parent 4524567 commit e883dcb

File tree

6 files changed

+548
-68
lines changed

6 files changed

+548
-68
lines changed

tests/python/relay/aot/aot_test_utils.py

Lines changed: 99 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import logging
2222
import os
2323
import pathlib
24+
import platform
2425
import shutil
2526
import subprocess
2627
import tarfile
@@ -39,6 +40,9 @@
3940

4041
_LOG = logging.getLogger(__name__)
4142

43+
AOT_SUCCESS_TOKEN = "AOT_TEST_SUCCESS"
44+
AOT_FAILURE_TOKEN = "AOT_TEST_FAILURE"
45+
4246

4347
class AOTTestModel(NamedTuple):
4448
"""Class to describe a model under test
@@ -64,6 +68,41 @@ class AOTTestModel(NamedTuple):
6468
params: Optional[Dict[str, np.array]] = None
6569

6670

71+
class AOTTestRunner(NamedTuple):
72+
"""Class to describe a test runner for AOT code
73+
74+
Parameters
75+
----------
76+
makefile: str
77+
Premade Makefile to use from the AOT test folder
78+
prologue: str
79+
Code to prepend to the main function
80+
includes: List[str]
81+
Additional includes required to run the AOT test runner
82+
parameters: Map[str, str]
83+
Additional parameters to pass to the make command
84+
"""
85+
86+
makefile: str = "default"
87+
prologue: str = ""
88+
includes: List[str] = []
89+
parameters: Dict[str, str] = {}
90+
91+
92+
AOT_DEFAULT_RUNNER = AOTTestRunner()
93+
94+
# AOT Test Runner using the Arm® Corstone™-300 Reference Systems
95+
# see: https://developer.arm.com/ip-products/subsystem/corstone/corstone-300
96+
AOT_CORSTONE300_RUNNER = AOTTestRunner(
97+
makefile="corstone300",
98+
prologue="""
99+
uart_init();
100+
""",
101+
includes=["uart.h"],
102+
parameters={"NPU_VARIANT": "256"},
103+
)
104+
105+
67106
def mangle_name(mod_name, name):
68107
mod_name = mangle_module_name(mod_name)
69108
return mod_name + "_" + name
@@ -112,20 +151,41 @@ def convert_to_list(x):
112151
def parametrize_aot_options(test):
113152
"""Parametrize over valid option combinations"""
114153

154+
skip_i386 = pytest.mark.skipif(
155+
platform.machine() == "i686", reason="Reference system unavailable in i386 container"
156+
)
115157
interface_api = ["packed", "c"]
116158
use_unpacked_api = [True, False]
117-
use_calculated_workspaces = [True, False]
159+
test_runner = [AOT_DEFAULT_RUNNER, AOT_CORSTONE300_RUNNER]
160+
161+
all_combinations = itertools.product(interface_api, use_unpacked_api, test_runner)
118162

119-
all_combinations = itertools.product(interface_api, use_unpacked_api, use_calculated_workspaces)
120163
# Filter out packed operators with c interface
121164
valid_combinations = filter(
122-
lambda parameters: not (parameters[0] == "c" and parameters[1] == False),
165+
lambda parameters: not (parameters[0] == "c" and not parameters[1]),
123166
all_combinations,
124167
)
125168

126-
return pytest.mark.parametrize(
127-
["interface_api", "use_unpacked_api", "use_calculated_workspaces"],
169+
# Only use reference system for C interface and unpacked API calls
170+
valid_combinations = filter(
171+
lambda parameters: not (
172+
parameters[2] == AOT_CORSTONE300_RUNNER
173+
and (parameters[0] == "packed" or not parameters[1])
174+
),
128175
valid_combinations,
176+
)
177+
178+
# Skip reference system tests if running in i386 container
179+
marked_combinations = map(
180+
lambda parameters: pytest.param(*parameters, marks=skip_i386)
181+
if parameters[2] == AOT_CORSTONE300_RUNNER
182+
else parameters,
183+
valid_combinations,
184+
)
185+
186+
return pytest.mark.parametrize(
187+
["interface_api", "use_unpacked_api", "test_runner"],
188+
marked_combinations,
129189
)(test)
130190

131191

@@ -160,7 +220,7 @@ def subprocess_log_output(cmd, cwd, logfile):
160220
return proc.wait()
161221

162222

163-
def emit_main_prologue(main_file, workspace_bytes):
223+
def emit_main_prologue(main_file, custom_prologue, workspace_bytes):
164224
# Add TVM_RUNTIME_ALLOC_ALIGNMENT_BYTES because of memory alignment.
165225
main_file.write(
166226
f"#define WORKSPACE_SIZE ({workspace_bytes} + TVM_RUNTIME_ALLOC_ALIGNMENT_BYTES)\n"
@@ -185,6 +245,7 @@ def emit_main_prologue(main_file, workspace_bytes):
185245
int main(){\n
186246
"""
187247
)
248+
main_file.write(custom_prologue)
188249

189250

190251
def emit_main_data(main_file, input_map, output_list, mod_name):
@@ -297,11 +358,11 @@ def emit_main_compare(main_file, output_list, mod_name):
297358
main_file.write(f"for (int i = 0; i<{actual_data_name}{i}_len; i++){{\n")
298359
if is_float_dtype:
299360
main_file.write(
300-
f'if (fabs({actual_data_name}{i}[i]-{expected_data_name}{i}[i]) > 0.001f){{\n\tprintf("ko\\n");\n\treturn -1;}}\n'
361+
f'if (fabs({actual_data_name}{i}[i]-{expected_data_name}{i}[i]) > 0.001f){{\n\tprintf("{AOT_FAILURE_TOKEN}\\n");\n\treturn -1;}}\n'
301362
)
302363
else:
303364
main_file.write(
304-
f'if ({actual_data_name}{i}[i]!={expected_data_name}{i}[i]){{\n\tprintf("ko\\n");\n\treturn -1;}}\n'
365+
f'if ({actual_data_name}{i}[i]!={expected_data_name}{i}[i]){{\n\tprintf("{AOT_FAILURE_TOKEN}\\n");\n\treturn -1;}}\n'
305366
)
306367
main_file.write("}\n")
307368

@@ -312,36 +373,40 @@ def emit_main_init_memory_manager(main_file):
312373

313374

314375
def emit_main_epilogue(main_file):
315-
main_file.write('printf("ok\\n");')
376+
main_file.write(f'printf("{AOT_SUCCESS_TOKEN}\\n");')
316377
main_file.write("return 0;")
317378
main_file.write("}\n")
318379

319380

320-
def emit_main_common_includes(main_file):
381+
def emit_main_common_includes(main_file, custom_includes):
321382
main_file.write("#include <stdio.h>\n")
322383
main_file.write("#include <math.h>\n")
323384
main_file.write('#include "tvm/runtime/c_runtime_api.h"\n')
324385
main_file.write('#include "tvm/runtime/crt/stack_allocator.h"\n')
386+
for include in custom_includes:
387+
main_file.write(f'#include "{include}"\n')
325388

326389

327390
def emit_main_micro_include(main_file, mod_name):
328391
main_file.write(f"#include <{mangle_module_name(mod_name)}.h>\n")
329392

330393

331-
def create_main(test_name, models, output_path, interface_api, workspace_bytes):
394+
def create_main(
395+
test_name, models, output_path, custom_includes, custom_prologue, interface_api, workspace_bytes
396+
):
332397
file_path = pathlib.Path(f"{output_path}/" + test_name).resolve()
333398
# create header file
334399
raw_path = file_path.with_suffix(".c").resolve()
335400
with open(raw_path, "w") as main_file:
336-
emit_main_common_includes(main_file)
401+
emit_main_common_includes(main_file, custom_includes)
337402

338403
if interface_api == "c":
339404
for model in models:
340405
emit_main_micro_include(main_file, model.name)
341-
342-
emit_main_prologue(main_file, workspace_bytes)
343406
for model in models:
344407
emit_main_data(main_file, model.inputs, model.outputs, model.name)
408+
409+
emit_main_prologue(main_file, custom_prologue, workspace_bytes)
345410
emit_main_init_memory_manager(main_file)
346411

347412
if interface_api == "c":
@@ -396,9 +461,10 @@ def extract_main_workspace_size_bytes(extract_dir):
396461

397462
def compile_and_run(
398463
models: Union[List[AOTTestModel], AOTTestModel],
464+
runner: AOTTestRunner,
399465
interface_api,
400466
use_unpacked_api,
401-
use_calculated_workspaces,
467+
debug_calculated_workspaces=False,
402468
workspace_byte_alignment=8,
403469
enable_op_fusion=True,
404470
):
@@ -414,7 +480,7 @@ def compile_and_run(
414480
models = [models]
415481

416482
# The calculated workspaces will not account for stack allocator tags used for debugging
417-
if not use_calculated_workspaces:
483+
if debug_calculated_workspaces:
418484
cflags += "-DTVM_CRT_STACK_ALLOCATOR_ENABLE_LIFO_CHECK "
419485

420486
config = {"tir.disable_vectorize": True}
@@ -452,10 +518,7 @@ def compile_and_run(
452518
t = tarfile.open(tar_file)
453519
t.extractall(base_path)
454520

455-
if use_calculated_workspaces:
456-
workspace_bytes += extract_main_workspace_size_bytes(base_path)
457-
else:
458-
workspace_bytes += 16384 * 1024
521+
workspace_bytes += extract_main_workspace_size_bytes(base_path)
459522

460523
for key in model.inputs:
461524
create_header_file(
@@ -480,31 +543,41 @@ def compile_and_run(
480543
"test.c",
481544
models,
482545
build_path,
546+
runner.includes,
547+
runner.prologue,
483548
interface_api,
484549
workspace_bytes,
485550
)
486551

487552
# Verify that compiles fine
488553
file_dir = os.path.dirname(os.path.abspath(__file__))
489554
codegen_path = os.path.join(base_path, "codegen")
490-
makefile = os.path.join(file_dir, "aot_test.mk")
491-
make_cmd = (
492-
f"make CFLAGS='{cflags}' -f {makefile} build_dir="
493-
+ build_path
555+
makefile = os.path.join(file_dir, f"{runner.makefile}.mk")
556+
custom_params = " ".join([f" {param}='{value}'" for param, value in runner.parameters.items()])
557+
make_command = (
558+
f"make -f {makefile} build_dir={build_path}"
559+
+ f" CFLAGS='{cflags}'"
494560
+ f" TVM_ROOT={file_dir}/../../../.."
561+
+ f" AOT_TEST_ROOT={file_dir}"
495562
+ f" CODEGEN_ROOT={codegen_path}"
496563
+ f" STANDALONE_CRT_DIR={tvm.micro.get_standalone_crt_dir()}"
564+
+ custom_params
497565
)
498566

499567
compile_log_path = os.path.join(build_path, "test_compile.log")
500-
ret = subprocess_log_output(make_cmd, ".", compile_log_path)
568+
compile_command = f"{make_command} aot_test_runner"
569+
ret = subprocess_log_output(compile_command, ".", compile_log_path)
501570
assert ret == 0
502571

503572
# Verify that runs fine
504573
run_log_path = os.path.join(build_path, "test_run.log")
505-
ret = subprocess_log_output("./aot_test_runner", build_path, run_log_path)
574+
run_command = f"{make_command} run"
575+
ret = subprocess_log_output(run_command, build_path, run_log_path)
506576
assert ret == 0
507577

578+
with open(run_log_path) as run_log:
579+
assert AOT_SUCCESS_TOKEN in run_log.read()
580+
508581

509582
def generate_ref_data(mod, input_data, params=None, target="llvm"):
510583
"""Generate reference data through executing the relay module"""

0 commit comments

Comments
 (0)