From cdcf3c1ef7172e746f513523603dfe277ebee156 Mon Sep 17 00:00:00 2001 From: Nicola Lancellotti Date: Mon, 16 Jan 2023 17:37:53 +0000 Subject: [PATCH 1/3] [CMSIS-NN] Add a runtime error message Change-Id: I71363e9f472224058d68c1654752cd77c6016a1e --- python/tvm/testing/aot.py | 48 +++++-- .../backend/contrib/cmsisnn/compiler_attrs.cc | 2 + .../backend/contrib/cmsisnn/compiler_attrs.h | 4 + src/relay/backend/contrib/cmsisnn/target.cc | 1 + .../backend/contrib/cmsisnn/tir_to_runtime.cc | 59 ++++++--- .../contrib/test_cmsisnn/test_last_error.py | 119 ++++++++++++++++++ tests/python/contrib/test_cmsisnn/utils.py | 6 +- tests/python/relay/aot/corstone300.mk | 18 ++- 8 files changed, 228 insertions(+), 29 deletions(-) create mode 100644 tests/python/contrib/test_cmsisnn/test_last_error.py diff --git a/python/tvm/testing/aot.py b/python/tvm/testing/aot.py index d58a1af9d660..e294d3735ca3 100644 --- a/python/tvm/testing/aot.py +++ b/python/tvm/testing/aot.py @@ -200,6 +200,7 @@ def _emit_main_prologue( compiled_models, interface_api, use_stack_allocator=True, + debug_last_error=False, ): if use_stack_allocator: workspace_define = f"#define WORKSPACE_SIZE ({workspace_bytes}" @@ -243,11 +244,28 @@ def _emit_main_prologue( va_start(args, msg); vfprintf(stdout, msg, args); va_end(args); -}\n -TVM_DLL int TVMFuncRegisterGlobal(const char* name, TVMFunctionHandle f, int override) {} -int main(){\n +} """ ) + if debug_last_error: + main_file.write( + """\n +tvm_crt_error_t TVMPlatformTimerStart() { + return kTvmErrorFunctionCallNotImplemented; +} +tvm_crt_error_t TVMPlatformTimerStop(double* elapsed_time_seconds) { + return kTvmErrorFunctionCallNotImplemented; +} +const TVMModule* TVMSystemLibEntryPoint(void) { return NULL; } +""" + ) + else: + main_file.write( + """\n +TVM_DLL int TVMFuncRegisterGlobal(const char* name, TVMFunctionHandle f, int override) {} +""" + ) + main_file.write("\nint main(){\n") main_file.write(custom_prologue) @@ -332,10 +350,10 @@ def _emit_main_data_setup(main_file, input_map, output_map, mod_name): def _emit_main_c_interface_call( - main_file, devices, workspace_pool_names, mod_name, use_workspace_io + main_file, devices, workspace_pool_names, mod_name, use_workspace_io, debug_last_error ): sub_strings = list() - sub_strings.append(f'{_mangle_name(mod_name,"run")}(') + sub_strings.append(f'if ({_mangle_name(mod_name,"run")}(') if not use_workspace_io: sub_strings.append(f'&{_mangle_name(mod_name,"inputs")}, ') sub_strings.append(f'&{_mangle_name(mod_name,"outputs")}, ') @@ -346,10 +364,14 @@ def _emit_main_c_interface_call( # Removing the last two characters that is a comma and a space sub_strings[-1] = sub_strings[-1][:-2] # Adding brackets and newline instead - sub_strings[-1] = sub_strings[-1] + ");\n" - + sub_strings[-1] = sub_strings[-1] + ") == -1) {\n" main_file_string = "".join(sub_strings) main_file.write(main_file_string) + if debug_last_error: + main_file.write(f'\tprintf("ERROR: %s\\n", TVMGetLastError());\n') + main_file.write(f'\tprintf("{AOT_FAILURE_TOKEN}\\n");\n') + main_file.write(f"\treturn -1;\n") + main_file.write("}\n") def _emit_main_fake_packed_values(main_file): @@ -447,13 +469,15 @@ def _emit_main_epilogue(main_file, custom_epilogue): main_file.write("}\n") -def _emit_main_common_includes(main_file, custom_includes): +def _emit_main_common_includes(main_file, custom_includes, debug_last_error): main_file.write("#include \n") main_file.write("#include \n") main_file.write("#include \n") main_file.write("#include \n") main_file.write('#include "tvm/runtime/c_runtime_api.h"\n') main_file.write('#include "tvm/runtime/crt/stack_allocator.h"\n') + if debug_last_error: + main_file.write('#include "tvm/runtime/crt/module.h"\n') for include in custom_includes: main_file.write(f'#include "{include}"\n') @@ -474,12 +498,13 @@ def _create_main( workspace_bytes, use_stack_allocator=True, use_workspace_io=False, + debug_last_error=False, ): file_path = pathlib.Path(f"{output_path}/" + test_name).resolve() # create header file raw_path = file_path.with_suffix(".c").resolve() with open(raw_path, "w") as main_file: - _emit_main_common_includes(main_file, custom_includes) + _emit_main_common_includes(main_file, custom_includes, debug_last_error) if interface_api == "c": for compiled_model in compiled_models: @@ -497,6 +522,7 @@ def _create_main( compiled_models, interface_api, use_stack_allocator, + debug_last_error, ) if use_stack_allocator: _emit_main_init_memory_manager(main_file) @@ -529,6 +555,7 @@ def _create_main( list(workspace_pool_names.keys()), model.name, use_workspace_io, + debug_last_error, ) else: _emit_main_fake_packed_values(main_file) @@ -765,6 +792,8 @@ def run_and_check_body(base_path): ) use_usmp = runner.pass_config.get("tir.usmp.enable", False) + options = runner.pass_config.get("relay.ext.cmsisnn.options") + debug_last_error = options.get("debug_last_error", False) if options else False # We only need the stack allocator if USMP is not used use_stack_allocator = not use_usmp @@ -780,6 +809,7 @@ def run_and_check_body(base_path): workspace_bytes, use_stack_allocator, use_workspace_io, + debug_last_error, ) # Verify that compiles fine diff --git a/src/relay/backend/contrib/cmsisnn/compiler_attrs.cc b/src/relay/backend/contrib/cmsisnn/compiler_attrs.cc index 1df84d94d877..61b6c9ce897f 100644 --- a/src/relay/backend/contrib/cmsisnn/compiler_attrs.cc +++ b/src/relay/backend/contrib/cmsisnn/compiler_attrs.cc @@ -40,11 +40,13 @@ Target CreateTarget(const tvm::transform::PassContext& ctx) { String mcpu = cfg.value()->mcpu; Array mattr = {cfg.value()->mattr}; + Bool debug_last_error = cfg.value()->debug_last_error; Target cmsis_nn_target(TargetJSON{ {"kind", String("cmsis-nn")}, {"mcpu", mcpu}, {"mattr", mattr}, + {"debug_last_error", debug_last_error}, }); return cmsis_nn_target; diff --git a/src/relay/backend/contrib/cmsisnn/compiler_attrs.h b/src/relay/backend/contrib/cmsisnn/compiler_attrs.h index 794dcbfa19fc..7bb355e0b212 100644 --- a/src/relay/backend/contrib/cmsisnn/compiler_attrs.h +++ b/src/relay/backend/contrib/cmsisnn/compiler_attrs.h @@ -37,6 +37,7 @@ namespace cmsisnn { struct CMSISNNCompilerConfigNode : public tvm::AttrsNode { String mcpu; String mattr; + Bool debug_last_error = Bool(false); TVM_DECLARE_ATTRS(CMSISNNCompilerConfigNode, "ext.attrs.CMSISNNCompilerConfigNode") { TVM_ATTR_FIELD(mcpu) @@ -47,6 +48,9 @@ struct CMSISNNCompilerConfigNode : public tvm::AttrsNode>("mattr") .add_attr_option("mcpu") + .add_attr_option("debug_last_error") .set_attr(tvm::attr::kRelayToTIR, RelayToTIR()) .set_attr("TIRToRuntime", TIRToRuntime) .set_target_parser(tvm::target::parsers::cpu::ParseTarget); diff --git a/src/relay/backend/contrib/cmsisnn/tir_to_runtime.cc b/src/relay/backend/contrib/cmsisnn/tir_to_runtime.cc index a592eb74b4fc..ba2aea54bb91 100644 --- a/src/relay/backend/contrib/cmsisnn/tir_to_runtime.cc +++ b/src/relay/backend/contrib/cmsisnn/tir_to_runtime.cc @@ -16,6 +16,8 @@ * specific language governing permissions and limitations * under the License. */ +#include + #include #include #include @@ -26,6 +28,7 @@ #include "../../../../runtime/file_utils.h" #include "../../../../target/source/codegen_c.h" #include "../../../../target/source/codegen_c_host.h" +#include "compiler_attrs.h" namespace tvm { using namespace tir; @@ -35,7 +38,9 @@ namespace cmsisnn { class CodeGenCMSISNN : public codegen::CodeGenCHost { public: - void Init(bool output_ssa, bool emit_asserts, bool emit_fwd_func_decl, std::string target_str) { + void Init(bool output_ssa, bool emit_asserts, bool emit_fwd_func_decl, std::string target_str, + bool debug_last_error) { + this->debug_last_error = debug_last_error; std::unordered_set devices; devices.insert("cmsis-nn"); CodeGenCHost::Init(output_ssa, emit_asserts, emit_fwd_func_decl, target_str, devices); @@ -49,6 +54,9 @@ class CodeGenCMSISNN : public codegen::CodeGenCHost { void AddFunction(const PrimFunc& prim_func) { CodeGenC::AddFunction(prim_func); } private: + /*! * \brief Enable storing the last error */ + bool debug_last_error; + /*! * \brief CMSIS-NN context buffer info */ struct CMSISNNContextBuffer { std::string name; @@ -357,13 +365,7 @@ class CodeGenCMSISNN : public codegen::CodeGenCHost { stream << "&" << filter_dim << ", " << filter_data << ", "; stream << "&" << bias_dim << ", " << bias_data << ", "; stream << "&" << output_dim << ", " << output_data << ");\n"; - PrintIndent(); - stream << "if (status != ARM_CMSIS_NN_SUCCESS) {\n"; - PrintIndent(); - PrintIndent(); - stream << "return -1;\n"; - PrintIndent(); - stream << "}\n"; + EmitErrorCheck(); } /*! * \brief Emits CMSIS-NN APIs for every call_extern comprising fully connected */ @@ -426,13 +428,7 @@ class CodeGenCMSISNN : public codegen::CodeGenCHost { stream << "&" << filter_dim << ", " << filter_data << ", "; stream << "&" << bias_dim << ", " << bias_data << ", "; stream << "&" << output_dim << ", " << output_data << ");\n"; - PrintIndent(); - stream << "if (status != ARM_CMSIS_NN_SUCCESS) {\n"; - PrintIndent(); - PrintIndent(); - stream << "return -1;\n"; - PrintIndent(); - stream << "}\n"; + EmitErrorCheck(); } /*! * \brief Emits CMSIS-NN APIs for every call_extern comprising pooling ops */ @@ -480,24 +476,51 @@ class CodeGenCMSISNN : public codegen::CodeGenCHost { stream << "&" << input_dim << ", " << input_data << ", "; stream << "&" << filter_dim << ", "; stream << "&" << output_dim << ", " << output_data << ");\n"; + EmitErrorCheck(); + } + + void EmitErrorCheck() { + auto emit_error = [&](std::string error) { + if (this->debug_last_error) { + stream << "TVMAPISetLastError(\"" << error << "\"); "; + } + }; + PrintIndent(); - stream << "if (status != ARM_CMSIS_NN_SUCCESS) {\n"; + stream << "switch (!status) {\n"; PrintIndent(); + stream << "case ARM_CMSIS_NN_SUCCESS: break;\n"; PrintIndent(); + stream << "case ARM_CMSIS_NN_ARG_ERROR: "; + emit_error("ARM_CMSIS_NN_ARG_ERROR"); + stream << "return -1;\n"; + PrintIndent(); + stream << "case ARM_CMSIS_NN_NO_IMPL_ERROR: "; + emit_error("ARM_CMSIS_NN_NO_IMPL_ERROR"); stream << "return -1;\n"; PrintIndent(); stream << "}\n"; } }; +static CMSISNNCompilerConfig GetCompilerAttrs() { + auto ctx = tvm::tir::transform::PassContext::Current(); + Optional cfg = + ctx->GetConfig("relay.ext.cmsisnn.options"); + if (!cfg.defined()) { + return AttrsWithDefaultValues(); + } + return cfg.value(); +} + runtime::Module TIRToRuntime(IRModule mod, Target target) { bool output_ssa = false; bool emit_asserts = false; bool emit_fwd_func_decl = false; + bool debug_last_error = GetCompilerAttrs()->debug_last_error; CodeGenCMSISNN codegen; Array function_names; - codegen.Init(output_ssa, emit_asserts, emit_fwd_func_decl, target->str()); - + codegen.Init(output_ssa, emit_asserts, emit_fwd_func_decl, target->str(), debug_last_error); std::vector> funcs; for (auto kv : mod->functions) { funcs.push_back(kv); diff --git a/tests/python/contrib/test_cmsisnn/test_last_error.py b/tests/python/contrib/test_cmsisnn/test_last_error.py new file mode 100644 index 000000000000..7a8416fdd49e --- /dev/null +++ b/tests/python/contrib/test_cmsisnn/test_last_error.py @@ -0,0 +1,119 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""CMSIS-NN integration tests: test if the model builds in case debug_last_error is enabled""" + +import numpy as np +import pytest + +import tvm +from tvm import relay +from tvm.relay.op.contrib import cmsisnn +from tvm.testing.aot import get_dtype_range, generate_ref_data, AOTTestModel, compile_and_run + + +from .utils import ( + skip_if_no_reference_system, + make_module, + make_qnn_relu, + assert_partitioned_function, + create_test_runner, +) + + +def generate_variable(name, dtype="int8"): + return relay.var(name, shape=(1, 16, 16, 3), dtype=dtype) + + +def make_model( + op, + input_0, + input_1, + input_0_scale, + input_0_zero_point, + input_1_scale, + input_1_zero_point, + relu_type="NONE", + out_scale=1.0 / 256, + out_zero_point=-128, +): + """Create a Relay Function / network model""" + binary_op = op( + input_0, + input_1, + relay.const(input_0_scale, "float32"), + relay.const(input_0_zero_point, "int32"), + relay.const(input_1_scale, "float32"), + relay.const(input_1_zero_point, "int32"), + relay.const(out_scale, "float32"), + relay.const(out_zero_point, "int32"), + ) + return make_qnn_relu(binary_op, relu_type, out_scale, out_zero_point, "int8") + + +@skip_if_no_reference_system +@tvm.testing.requires_cmsisnn +@pytest.mark.parametrize("debug_last_error", [True, False]) +def test_last_error(debug_last_error): + """Tests debug_last_error""" + op = relay.qnn.op.add + relu_type = "NONE" + input_0_scale, input_0_zero_point = (0.256, 33) + input_1_scale, input_1_zero_point = (0.256, 33) + compiler_cpu = "cortex-m55" + cpu_flags = "" + + interface_api = "c" + use_unpacked_api = True + + dtype = "int8" + shape = [1, 16, 16, 3] + model = make_model( + op, + generate_variable("input_0"), + generate_variable("input_1"), + input_0_scale, + input_0_zero_point, + input_1_scale, + input_1_zero_point, + relu_type, + ) + orig_mod = make_module(model) + + cmsisnn_mod = cmsisnn.partition_for_cmsisnn(orig_mod) + + # validate pattern matching + assert_partitioned_function(orig_mod, cmsisnn_mod) + + # validate the output + in_min, in_max = get_dtype_range(dtype) + inputs = { + "input_0": np.random.randint(in_min, high=in_max, size=shape, dtype=dtype), + "input_1": np.random.randint(in_min, high=in_max, size=shape, dtype=dtype), + } + output_list = generate_ref_data(orig_mod["main"], inputs) + compile_and_run( + AOTTestModel( + module=cmsisnn_mod, + inputs=inputs, + outputs=output_list, + output_tolerance=1, + ), + create_test_runner(compiler_cpu, cpu_flags, debug_last_error=debug_last_error), + interface_api, + use_unpacked_api, + ) diff --git a/tests/python/contrib/test_cmsisnn/utils.py b/tests/python/contrib/test_cmsisnn/utils.py index 74d9686a784e..65f7402e6b83 100644 --- a/tests/python/contrib/test_cmsisnn/utils.py +++ b/tests/python/contrib/test_cmsisnn/utils.py @@ -252,7 +252,7 @@ def assert_pads_within_func(self): assert self.num_pads_ > 0, "Composite function should have pads within it." -def create_test_runner(compiler_cpu="cortex-m55", cpu_flags=""): +def create_test_runner(compiler_cpu="cortex-m55", cpu_flags="", debug_last_error=False): """ Creates AOT test runner for CMSIS-NN tests. @@ -267,6 +267,8 @@ def create_test_runner(compiler_cpu="cortex-m55", cpu_flags=""): Arm(R) Cortex(R)-M55: when null +mve is set by default. +nomve disables vector extensions. Arm(R) Cortex(R)-M7 does not support mve. + debug_last_error: bool + Whether to enable storing the last error """ # cmsis_cpu is used to find out start up code inside CMSIS package cmsis_cpu = "ARMCM7" if compiler_cpu == "cortex-m7" else "ARMCM55" @@ -280,6 +282,7 @@ def create_test_runner(compiler_cpu="cortex-m55", cpu_flags=""): pass_config={ "relay.ext.cmsisnn.options": { "mcpu": compiler_cpu + cpu_flags, + "debug_last_error": debug_last_error, }, "tir.usmp.enable": True, "tir.disable_storage_rewrite": True, @@ -289,5 +292,6 @@ def create_test_runner(compiler_cpu="cortex-m55", cpu_flags=""): "MCPU": compiler_cpu, "MCPU_FLAGS": cpu_flags, "MFLOAT_ABI": mfloat_abi, + "DEBUG_LAST_ERROR": 1 if debug_last_error else 0, }, ) diff --git a/tests/python/relay/aot/corstone300.mk b/tests/python/relay/aot/corstone300.mk index 61373ec3efba..a0fb656a30c2 100644 --- a/tests/python/relay/aot/corstone300.mk +++ b/tests/python/relay/aot/corstone300.mk @@ -98,6 +98,22 @@ $(build_dir)/crt_backend_api.o: $(TVM_ROOT)/src/runtime/crt/common/crt_backend_a $(QUIET)mkdir -p $(@D) $(QUIET)$(CC) -c $(PKG_CFLAGS) -o $@ $^ +ifeq ($(DEBUG_LAST_ERROR), 1) +$(build_dir)/crt_runtime_api.o: $(TVM_ROOT)/src/runtime/crt/common/crt_runtime_api.c + $(QUIET)mkdir -p $(@D) + $(QUIET)$(CC) -c $(PKG_CFLAGS) -o $@ $^ + +$(build_dir)/func_registry.o: $(TVM_ROOT)/src/runtime/crt/common/func_registry.c + $(QUIET)mkdir -p $(@D) + $(QUIET)$(CC) -c $(PKG_CFLAGS) -o $@ $^ + +$(build_dir)/ndarray.o: $(TVM_ROOT)/src/runtime/crt/common/ndarray.c + $(QUIET)mkdir -p $(@D) + $(QUIET)$(CC) -c $(PKG_CFLAGS) -o $@ $^ + +DEBUG_LAST_ERROR_SOURCES = $(build_dir)/crt_runtime_api.o $(build_dir)/func_registry.o $(build_dir)/ndarray.o +endif + $(build_dir)/tvm_ethosu_runtime.o: $(TVM_ROOT)/src/runtime/contrib/ethosu/bare_metal/tvm_ethosu_runtime.c $(QUIET)mkdir -p $(@D) $(QUIET)$(CC) -c $(PKG_CFLAGS) -o $@ $^ @@ -131,7 +147,7 @@ ${build_dir}/ethosu_core_platform/libethosu_uart_cmsdk_apb.a: $(QUIET)cd ${ETHOSU_PLATFORM_PATH}/drivers/uart && $(CMAKE) -B $(abspath $(build_dir)/ethosu_core_platform) $(CMAKE_FLAGS) $(QUIET)cd $(abspath $(build_dir)/ethosu_core_platform) && $(MAKE) -$(build_dir)/aot_test_runner: $(build_dir)/test.c $(build_dir)/crt_backend_api.o $(build_dir)/stack_allocator.o $(build_dir)/libcodegen.a ${build_dir}/libcmsis_startup.a ${build_dir}/libcmsis_nn.a ${build_dir}/libcorstone.a ${build_dir}/ethosu_core_platform/libethosu_uart_cmsdk_apb.a $(ETHOSU_DRIVER_LIBS) $(ETHOSU_RUNTIME) +$(build_dir)/aot_test_runner: $(build_dir)/test.c $(build_dir)/crt_backend_api.o $(build_dir)/stack_allocator.o $(build_dir)/libcodegen.a ${build_dir}/libcmsis_startup.a ${build_dir}/libcmsis_nn.a ${build_dir}/libcorstone.a ${build_dir}/ethosu_core_platform/libethosu_uart_cmsdk_apb.a $(ETHOSU_DRIVER_LIBS) $(ETHOSU_RUNTIME) $(DEBUG_LAST_ERROR_SOURCES) $(QUIET)mkdir -p $(@D) $(QUIET)$(CC) $(PKG_CFLAGS) $(ETHOSU_INCLUDE) -o $@ -Wl,--whole-archive $^ -Wl,--no-whole-archive $(PKG_LDFLAGS) From f04c9f0fddd841053a73cc0573ff3d02e041911f Mon Sep 17 00:00:00 2001 From: Nicola Lancellotti Date: Thu, 23 Feb 2023 11:57:53 +0000 Subject: [PATCH 2/3] Add test Change-Id: I4b0da1365e0771e6b243ec37e12fbb498aba4a44 --- python/tvm/testing/aot.py | 18 ++- .../contrib/test_cmsisnn/test_last_error.py | 138 ++++++++++++------ 2 files changed, 104 insertions(+), 52 deletions(-) diff --git a/python/tvm/testing/aot.py b/python/tvm/testing/aot.py index e294d3735ca3..8f7c4eb4dc70 100644 --- a/python/tvm/testing/aot.py +++ b/python/tvm/testing/aot.py @@ -23,7 +23,7 @@ import subprocess import tarfile import logging -from typing import Any, NamedTuple, Union, Tuple, Optional, List, Dict +from typing import Any, NamedTuple, Union, Tuple, Optional, List, Dict, Callable import numpy as np import tvm @@ -728,6 +728,7 @@ def run_and_check( test_dir: str = None, verbose: bool = False, use_workspace_io: bool = False, + checker: Optional[Callable[[str], bool]] = None, ): """ This method uses the original test data and compiled runtime.Modules @@ -812,6 +813,9 @@ def run_and_check_body(base_path): debug_last_error, ) + if checker and (not checker(base_path)): + return False + # Verify that compiles fine file_dir = os.path.dirname(os.path.abspath(__file__)) makefile_dir = os.path.join(file_dir, "../../../tests/python/relay/aot") @@ -859,11 +863,13 @@ def run_and_check_body(base_path): with open(run_log_path) as run_log: assert AOT_SUCCESS_TOKEN in run_log.read() + return True + if test_dir is None: tmpdir = utils.tempdir() - run_and_check_body(os.path.join(tmpdir.path, "test")) + return run_and_check_body(os.path.join(tmpdir.path, "test")) else: - run_and_check_body(test_dir) + return run_and_check_body(test_dir) def compile_and_run( @@ -882,7 +888,8 @@ def compile_and_run( test_dir: str = None, verbose: bool = False, schedule_name: str = None, -): + checker: Optional[Callable[[str], bool]] = None, +) -> bool: """This is a wrapper API to compile and run models as test for AoT Parameters @@ -913,7 +920,7 @@ def compile_and_run( schedule_name=schedule_name, ) - run_and_check( + return run_and_check( models=compiled_test_mods, runner=runner, interface_api=interface_api, @@ -923,6 +930,7 @@ def compile_and_run( data_linkage=data_linkage, test_dir=test_dir, verbose=verbose, + checker=checker, ) diff --git a/tests/python/contrib/test_cmsisnn/test_last_error.py b/tests/python/contrib/test_cmsisnn/test_last_error.py index 7a8416fdd49e..874a5ce1adb0 100644 --- a/tests/python/contrib/test_cmsisnn/test_last_error.py +++ b/tests/python/contrib/test_cmsisnn/test_last_error.py @@ -15,82 +15,102 @@ # specific language governing permissions and limitations # under the License. -"""CMSIS-NN integration tests: test if the model builds in case debug_last_error is enabled""" +"""CMSIS-NN integration tests: debug_last_error""" +import re import numpy as np import pytest - import tvm from tvm import relay from tvm.relay.op.contrib import cmsisnn -from tvm.testing.aot import get_dtype_range, generate_ref_data, AOTTestModel, compile_and_run - +from tvm.testing.aot import ( + get_dtype_range, + generate_ref_data, + AOTTestModel, + compile_and_run, +) from .utils import ( - skip_if_no_reference_system, make_module, + get_same_padding, make_qnn_relu, assert_partitioned_function, create_test_runner, ) -def generate_variable(name, dtype="int8"): - return relay.var(name, shape=(1, 16, 16, 3), dtype=dtype) - - def make_model( - op, - input_0, - input_1, - input_0_scale, - input_0_zero_point, - input_1_scale, - input_1_zero_point, - relu_type="NONE", - out_scale=1.0 / 256, - out_zero_point=-128, + pool_op, + shape, + pool_size, + strides, + padding, + dtype, + scale, + zero_point, + relu_type, + layout, + input_op, ): """Create a Relay Function / network model""" - binary_op = op( - input_0, - input_1, - relay.const(input_0_scale, "float32"), - relay.const(input_0_zero_point, "int32"), - relay.const(input_1_scale, "float32"), - relay.const(input_1_zero_point, "int32"), - relay.const(out_scale, "float32"), - relay.const(out_zero_point, "int32"), + if input_op: + op = input_op + else: + op = relay.var("input", shape=shape, dtype=dtype) + pad_ = (0, 0, 0, 0) + if padding == "SAME": + dilation = (1, 1) + pad_ = get_same_padding((shape[1], shape[2]), pool_size, dilation, strides) + op = relay.nn.pad( + op, + pad_width=[(0, 0), (pad_[0], pad_[2]), (pad_[1], pad_[3]), (0, 0)], + pad_value=zero_point, + pad_mode="constant", + ) + if pool_op.__name__ == relay.nn.avg_pool2d.__name__: + op = relay.cast(op, "int32") + op = pool_op( + op, pool_size=pool_size, strides=strides, padding=pad_, ceil_mode=True, layout=layout ) - return make_qnn_relu(binary_op, relu_type, out_scale, out_zero_point, "int8") + if pool_op.__name__ == relay.nn.avg_pool2d.__name__: + op = relay.cast(op, dtype) + op = make_qnn_relu(op, relu_type, scale, zero_point, dtype) + return op -@skip_if_no_reference_system @tvm.testing.requires_cmsisnn @pytest.mark.parametrize("debug_last_error", [True, False]) def test_last_error(debug_last_error): """Tests debug_last_error""" - op = relay.qnn.op.add + dtype = "int16" + in_shape = (1, 28, 28, 12) + pool_size = (3, 3) + strides = (2, 2) + padding = "SAME" relu_type = "NONE" - input_0_scale, input_0_zero_point = (0.256, 33) - input_1_scale, input_1_zero_point = (0.256, 33) + pool_type = relay.nn.avg_pool2d + zero_point = -34 + scale = 0.0256 compiler_cpu = "cortex-m55" - cpu_flags = "" + cpu_flags = "+nomve" + layout = "NHWC" + input_op = None interface_api = "c" use_unpacked_api = True - dtype = "int8" - shape = [1, 16, 16, 3] model = make_model( - op, - generate_variable("input_0"), - generate_variable("input_1"), - input_0_scale, - input_0_zero_point, - input_1_scale, - input_1_zero_point, - relu_type, + pool_op=pool_type, + shape=in_shape, + pool_size=pool_size, + strides=strides, + padding=padding, + dtype=dtype, + scale=scale, + zero_point=zero_point, + relu_type=relu_type, + layout=layout, + input_op=input_op, ) orig_mod = make_module(model) @@ -102,18 +122,42 @@ def test_last_error(debug_last_error): # validate the output in_min, in_max = get_dtype_range(dtype) inputs = { - "input_0": np.random.randint(in_min, high=in_max, size=shape, dtype=dtype), - "input_1": np.random.randint(in_min, high=in_max, size=shape, dtype=dtype), + "input": np.random.randint(in_min, high=in_max, size=in_shape, dtype=dtype), } output_list = generate_ref_data(orig_mod["main"], inputs) - compile_and_run( + + def checker(base_path: str) -> bool: + def read_file(path): + with open(path) as f: + return f.read() + + test = read_file(base_path + "/build/test.c") + test_check = "TVMGetLastError" in test + + default_lib2 = read_file(base_path + "/codegen/host/src/default_lib2.c") + regex = ( + r"(?s)arm_avgpool_s16(.*?)" + r'ARM_CMSIS_NN_ARG_ERROR: TVMAPISetLastError\("ARM_CMSIS_NN_ARG_ERROR(.*?)' + r'ARM_CMSIS_NN_NO_IMPL_ERROR: TVMAPISetLastError\("ARM_CMSIS_NN_NO_IMPL_ERROR' + ) + default_lib2_check = re.search(regex, default_lib2) is not None + + if debug_last_error: + return test_check and default_lib2_check + else: + return not (test_check or default_lib2_check) + + result = compile_and_run( AOTTestModel( module=cmsisnn_mod, inputs=inputs, outputs=output_list, + params=None, output_tolerance=1, ), create_test_runner(compiler_cpu, cpu_flags, debug_last_error=debug_last_error), interface_api, use_unpacked_api, + checker=checker, ) + assert result From 7643c6547b62402f8b521fd96aee33361d0b0dbc Mon Sep 17 00:00:00 2001 From: Nicola Lancellotti Date: Mon, 27 Feb 2023 10:46:04 +0000 Subject: [PATCH 3/3] Address comment Change-Id: If48e8bf6a1dbc227e791544c48f057cca2f272ec --- python/tvm/testing/aot.py | 5 +++-- tests/python/contrib/test_cmsisnn/test_last_error.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/python/tvm/testing/aot.py b/python/tvm/testing/aot.py index 8f7c4eb4dc70..086b2a1bfd90 100644 --- a/python/tvm/testing/aot.py +++ b/python/tvm/testing/aot.py @@ -728,6 +728,7 @@ def run_and_check( test_dir: str = None, verbose: bool = False, use_workspace_io: bool = False, + debug_last_error: bool = False, checker: Optional[Callable[[str], bool]] = None, ): """ @@ -793,8 +794,6 @@ def run_and_check_body(base_path): ) use_usmp = runner.pass_config.get("tir.usmp.enable", False) - options = runner.pass_config.get("relay.ext.cmsisnn.options") - debug_last_error = options.get("debug_last_error", False) if options else False # We only need the stack allocator if USMP is not used use_stack_allocator = not use_usmp @@ -888,6 +887,7 @@ def compile_and_run( test_dir: str = None, verbose: bool = False, schedule_name: str = None, + debug_last_error: bool = False, checker: Optional[Callable[[str], bool]] = None, ) -> bool: """This is a wrapper API to compile and run models as test for AoT @@ -930,6 +930,7 @@ def compile_and_run( data_linkage=data_linkage, test_dir=test_dir, verbose=verbose, + debug_last_error=debug_last_error, checker=checker, ) diff --git a/tests/python/contrib/test_cmsisnn/test_last_error.py b/tests/python/contrib/test_cmsisnn/test_last_error.py index 874a5ce1adb0..f21d5d1a0383 100644 --- a/tests/python/contrib/test_cmsisnn/test_last_error.py +++ b/tests/python/contrib/test_cmsisnn/test_last_error.py @@ -158,6 +158,7 @@ def read_file(path): create_test_runner(compiler_cpu, cpu_flags, debug_last_error=debug_last_error), interface_api, use_unpacked_api, + debug_last_error=debug_last_error, checker=checker, ) assert result