From 65eb3fec16b72d48fdd26c501153ece90c901bcd Mon Sep 17 00:00:00 2001 From: Chris Sidebottom Date: Thu, 9 Jun 2022 21:41:26 +0000 Subject: [PATCH] [CI] Apply linting rules to AOT tests This enables pylint against the AOT test cases. One issue I found was with the `tvm.testing.parameter` which breaks the naming convention rules in pylint (constants are upper case and function parameters are lower case). It may be worth a syntax similar to: ``` tvm.testing.parameter("enable_usmp", [True, False]) ``` --- tests/lint/pylint.sh | 1 + tests/python/relay/aot/test_c_device_api.py | 34 ++- tests/python/relay/aot/test_cpp_aot.py | 34 +-- tests/python/relay/aot/test_crt_aot.py | 294 +++++++++++--------- tests/python/relay/aot/test_crt_aot_usmp.py | 71 ++--- 5 files changed, 244 insertions(+), 190 deletions(-) diff --git a/tests/lint/pylint.sh b/tests/lint/pylint.sh index b442c33c0ff6..3e55168f265e 100755 --- a/tests/lint/pylint.sh +++ b/tests/lint/pylint.sh @@ -21,4 +21,5 @@ python3 -m pylint python/tvm --rcfile="$(dirname "$0")"/pylintrc python3 -m pylint vta/python/vta --rcfile="$(dirname "$0")"/pylintrc python3 -m pylint tests/python/unittest/test_tvmscript_type.py --rcfile="$(dirname "$0")"/pylintrc python3 -m pylint tests/python/contrib/test_cmsisnn --rcfile="$(dirname "$0")"/pylintrc +python3 -m pylint tests/python/relay/aot/*.py --rcfile="$(dirname "$0")"/pylintrc diff --git a/tests/python/relay/aot/test_c_device_api.py b/tests/python/relay/aot/test_c_device_api.py index b972b0845c30..6c32735c23a6 100644 --- a/tests/python/relay/aot/test_c_device_api.py +++ b/tests/python/relay/aot/test_c_device_api.py @@ -14,32 +14,38 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +"""AOT with C Device API Tests""" -import sys +import re from collections import OrderedDict import numpy as np import pytest -import re -import tvm.testing +import tvm.testing from tvm import relay from tvm.ir.module import IRModule from tvm.testing.aot import AOTTestModel, generate_ref_data, compile_models from tvm.micro.testing.aot_test_utils import AOT_DEFAULT_RUNNER -@pytest.fixture -def device_api_main_func(): +@pytest.fixture(name="device_api_main_func") +def fixture_device_api_main_func(): + """Test function generator which generates C Device API calls""" + # Ideally we should have a sample Target registered here # but we're going to re-use this for now pytest.importorskip("ethosu.vela") + + # pylint: disable=import-outside-toplevel import tensorflow as tf import tflite.Model from tests.python.contrib.test_ethosu.infra import create_test_runner, generate_ref_data_tflite from tvm.relay.op.contrib.ethosu import partition_for_ethosu + # pylint: enable=import-outside-toplevel + tf.config.run_functions_eagerly(True) class Model(tf.Module): @@ -97,8 +103,9 @@ def compile_to_main_func(interface_api="c", use_unpacked_api=True): return compile_to_main_func -@pytest.fixture -def non_device_api_main_func(): +@pytest.fixture(name="non_device_api_main_func") +def fixture_non_device_api_main_func(): + """Test function generator which does not generate C Device API calls""" x = relay.var("x", shape=(10, 10)) y = relay.var("y", shape=(1, 10)) func = relay.Function([x, y], relay.multiply(x, y)) @@ -151,7 +158,7 @@ def test_device_api_hooks_unpacked_api(device_api_main_func): # We dont need to check exact input and output var names in this test. # Hence, using a regex to cover any legal I/O name. regex = re.compile( - 'tir\.tvm_check_return\(0, -1, tir\.call_extern\("tvmgen_default_ethos_u_main_0", \w+, \w+, device_context_ethos_u\)\)' + r'tir\.tvm_check_return\(0, -1, tir\.call_extern\("tvmgen_default_ethos_u_main_0", \w+, \w+, device_context_ethos_u\)\)' # pylint: disable=line-too-long ) assert regex.match(str(main_func.body[1][0][0][1])) # Close Device @@ -171,7 +178,9 @@ def test_device_api_hooks_unpacked_api(device_api_main_func): @pytest.mark.skip( - "Skipping this test as this is incorrectly using Arm(R) Ethos(TM)-U NPU with packed calling convention which is not supported by the NPU codegen's TIR to Runtime Hook. We need to use a different target to test this feature" + "Skipping this test as this is incorrectly using Arm(R) Ethos(TM)-U NPU " + + "with packed calling convention which is not supported by the NPU codegen's " + + "TIR to Runtime Hook. We need to use a different target to test this feature" ) def test_device_api_hooks_packed_api(device_api_main_func): """Check for Device API hooks with packed internal calls""" @@ -236,11 +245,12 @@ def test_without_device_api_packed_api(non_device_api_main_func): """Test a graph without the Device API with the packed internal calls""" main_func = non_device_api_main_func(interface_api="packed", use_unpacked_api=False) + assert str(main_func.body) == ( 'tir.tvm_call_cpacked("tvmgen_default_fused_multiply", ' - "tir.tvm_stack_make_array(x_buffer_var, tir.tvm_stack_make_shape(10, 10), tir.reinterpret((uint64)0), (uint32)2, float32(0), 0), " - "tir.tvm_stack_make_array(y_buffer_var, tir.tvm_stack_make_shape(1, 10), tir.reinterpret((uint64)0), (uint32)2, float32(0), 0), " - "tir.tvm_stack_make_array(output_buffer_var, tir.tvm_stack_make_shape(10, 10), tir.reinterpret((uint64)0), (uint32)2, float32(0), 0), " + "tir.tvm_stack_make_array(x_buffer_var, tir.tvm_stack_make_shape(10, 10), tir.reinterpret((uint64)0), (uint32)2, float32(0), 0), " # pylint: disable=line-too-long + "tir.tvm_stack_make_array(y_buffer_var, tir.tvm_stack_make_shape(1, 10), tir.reinterpret((uint64)0), (uint32)2, float32(0), 0), " # pylint: disable=line-too-long + "tir.tvm_stack_make_array(output_buffer_var, tir.tvm_stack_make_shape(10, 10), tir.reinterpret((uint64)0), (uint32)2, float32(0), 0), " # pylint: disable=line-too-long "tir.reinterpret((uint64)0))\n" ) diff --git a/tests/python/relay/aot/test_cpp_aot.py b/tests/python/relay/aot/test_cpp_aot.py index 04a1111e357c..742b681ae619 100644 --- a/tests/python/relay/aot/test_cpp_aot.py +++ b/tests/python/relay/aot/test_cpp_aot.py @@ -14,10 +14,9 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - +"""AOT with C++ Runtime Tests""" import re -import sys import textwrap import numpy as np @@ -28,13 +27,10 @@ from tvm import relay from tvm.relay import backend, testing from tvm.testing.aot import generate_ref_data -from tvm.micro.testing.aot_test_utils import AOT_DEFAULT_RUNNER def test_error_c_interface(): - interface_api = "c" - use_unpacked_api = False - test_runner = AOT_DEFAULT_RUNNER + """Checks that an error occurs when using the packed API in combination with C interface""" two = relay.add(relay.const(1), relay.const(1)) func = relay.Function([], two) @@ -53,12 +49,11 @@ def test_error_c_interface(): ) -enable_usmp = tvm.testing.parameter(True, False) -target_kind = tvm.testing.parameter("c", "llvm") - - +@pytest.mark.parametrize("enable_usmp", [True, False]) +@pytest.mark.parametrize("target_kind", ["c", "llvm"]) def test_conv2d(enable_usmp, target_kind): - RELAY_MODEL = textwrap.dedent( + """Tests compilation of convolutions""" + relay_model = textwrap.dedent( """\ #[version = "0.0.5"] def @main(%data : Tensor[(1, 3, 64, 64), uint8], %weight : Tensor[(3, 3, 5, 5), int8]) { @@ -86,7 +81,7 @@ def @main(%data : Tensor[(1, 3, 64, 64), uint8], %weight : Tensor[(3, 3, 5, 5), } """ ) - ir_mod = tvm.parser.fromtext(RELAY_MODEL) + ir_mod = tvm.parser.fromtext(relay_model) main_func = ir_mod["main"] shape_dict = {p.name_hint: p.checked_type.concrete_shape for p in main_func.params} @@ -119,7 +114,10 @@ def @main(%data : Tensor[(1, 3, 64, 64), uint8], %weight : Tensor[(3, 3, 5, 5), assert (runner.get_output(0).asnumpy() == list(ref_outputs.values())[0]).all() +@pytest.mark.parametrize("enable_usmp", [True, False]) +@pytest.mark.parametrize("target_kind", ["c", "llvm"]) def test_mobilenet(enable_usmp, target_kind): + """Full network test with Mobilenet""" ir_mod, params = testing.mobilenet.get_workload(batch_size=1) data_shape = [int(x) for x in ir_mod["main"].checked_type.arg_types[0].shape] data = np.random.uniform(size=data_shape).astype("float32") @@ -147,10 +145,11 @@ def test_mobilenet(enable_usmp, target_kind): def test_module_list(): - x = tvm.relay.var("x", tvm.relay.TensorType([1], dtype="float32")) - expr = tvm.relay.add(x, tvm.relay.Constant(tvm.nd.array(np.array([1], dtype="float32")))) + """Checks the correct list of module names is generated""" + input_x = tvm.relay.var("x", tvm.relay.TensorType([1], dtype="float32")) + expr = tvm.relay.add(input_x, tvm.relay.Constant(tvm.nd.array(np.array([1], dtype="float32")))) mod = tvm.relay.build( - tvm.IRModule.from_expr(tvm.relay.Function([x], expr)), + tvm.IRModule.from_expr(tvm.relay.Function([input_x], expr)), target="c", executor=tvm.relay.backend.Executor("aot", {"interface-api": "packed"}), mod_name="unusual_module_name_fred", @@ -177,6 +176,7 @@ def test_create_executor(): def test_pass_wrong_device_arg(): + """Ensure an error is generated if the incorrect number of devices are passed""" x = tvm.relay.var("x", tvm.relay.TensorType([1], dtype="float32")) expr = tvm.relay.add(x, tvm.relay.Constant(tvm.nd.array(np.array([1], dtype="float32")))) with tvm.transform.PassContext(opt_level=3, config={"tir.disable_vectorize": True}): @@ -191,12 +191,12 @@ def test_pass_wrong_device_arg(): mod.export_library(test_so_path, cc="gcc", options=["-std=c11"]) loaded_mod = tvm.runtime.load_module(test_so_path) - with pytest.raises(tvm.TVMError) as cm: + with pytest.raises(tvm.TVMError) as error: tvm.runtime.executor.AotModule(loaded_mod["default"](tvm.cpu(0), tvm.cpu(0))) assert ( "Check failed: devices_.size() == 1 (2 vs. 1) : Expect exactly 1 device passed." - in str(cm.exception) + in str(error.exception) ) # TODO write asserts for # and type of device. diff --git a/tests/python/relay/aot/test_crt_aot.py b/tests/python/relay/aot/test_crt_aot.py index f4ef8d784531..1a4f23ad467a 100644 --- a/tests/python/relay/aot/test_crt_aot.py +++ b/tests/python/relay/aot/test_crt_aot.py @@ -14,11 +14,10 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +"""AOT with C Runtime Tests""" from collections import OrderedDict -import platform import re -import sys import os import tarfile import pathlib @@ -48,6 +47,7 @@ def test_error_c_interface_with_packed_api(): + """Checks that an error occurs when using the packed API in combination with C interface""" interface_api = "c" use_unpacked_api = False test_runner = AOT_DEFAULT_RUNNER @@ -75,7 +75,8 @@ def test_error_c_interface_with_packed_api(): @parametrize_aot_options def test_conv_with_params(interface_api, use_unpacked_api, test_runner): - RELAY_MODEL = """ + """Tests compilation of convolution with parameters""" + relay_model = """ #[version = "0.0.5"] def @main(%data : Tensor[(1, 3, 64, 64), uint8], %weight : Tensor[(8, 3, 5, 5), int8]) { %1 = nn.conv2d( @@ -90,7 +91,7 @@ def @main(%data : Tensor[(1, 3, 64, 64), uint8], %weight : Tensor[(8, 3, 5, 5), %1 } """ - mod = tvm.parser.fromtext(RELAY_MODEL) + mod = tvm.parser.fromtext(relay_model) main_func = mod["main"] shape_dict = {p.name_hint: p.checked_type.concrete_shape for p in main_func.params} type_dict = {p.name_hint: p.checked_type.dtype for p in main_func.params} @@ -112,16 +113,17 @@ def @main(%data : Tensor[(1, 3, 64, 64), uint8], %weight : Tensor[(8, 3, 5, 5), @parametrize_aot_options def test_add_with_params(interface_api, use_unpacked_api, test_runner): - x = relay.var("x", shape=(1, 10)) - y = relay.var("y", shape=(1, 10)) - z = relay.add(x, y) - func = relay.Function([x, y], z) + """Tests compilation of add with parameters""" + input_x = relay.var("x", shape=(1, 10)) + input_y = relay.var("y", shape=(1, 10)) + input_z = relay.add(input_x, input_y) + func = relay.Function([input_x, input_y], input_z) - x_in = np.ones((1, 10)).astype("float32") - y_in = np.random.uniform(size=(1, 10)).astype("float32") + input_x_data = np.ones((1, 10)).astype("float32") + input_y_data = np.random.uniform(size=(1, 10)).astype("float32") - params = {"x": x_in} - inputs = {"y": y_in} + params = {"x": input_x_data} + inputs = {"y": input_y_data} output_list = generate_ref_data(func, inputs, params) compile_and_run( @@ -231,21 +233,23 @@ def test_packed_global_variables(): # Collect all functions starting with tvmgen_default tvmgen_funcs += re.findall(r"(?<=).*(?=\()", item) - # Check if any function name has a packed variable name in all items that start with tvmgen_default + # Check if any function name has a packed variable name in all + # items that start with tvmgen_default for func in tvmgen_funcs: assert f"{func}_packed" not in tvmgen_names @parametrize_aot_options def test_concatenate(interface_api, use_unpacked_api, test_runner): + """Tests compilation of concatenate""" dtype = "float32" - x = relay.var("x", shape=(10, 5), dtype=dtype) - y = relay.var("y", shape=(10, 5), dtype=dtype) - t = relay.var("z", shape=(), dtype=dtype) - z = relay.concatenate((x, y), axis=1) - z = relay.add(z, t) + input_x = relay.var("x", shape=(10, 5), dtype=dtype) + input_y = relay.var("y", shape=(10, 5), dtype=dtype) + input_z = relay.var("z", shape=(), dtype=dtype) + concat_inputs = relay.concatenate((input_x, input_y), axis=1) + func_output = relay.add(input_z, concat_inputs) # Check result. - func = relay.Function([x, y, t], z) + func = relay.Function([input_x, input_y, input_z], func_output) x_data = np.random.rand(10, 5).astype(dtype) y_data = np.random.rand(10, 5).astype(dtype) t_data = np.random.uniform(size=()).astype(dtype) @@ -262,13 +266,16 @@ def test_concatenate(interface_api, use_unpacked_api, test_runner): @parametrize_aot_options def test_nested_tuples(interface_api, use_unpacked_api, test_runner): - x = relay.var("x", shape=(10,)) - x1 = x + relay.const(1.0) - x2 = x1 + relay.const(1.0) - x3 = x2 + relay.const(1.0) - x4 = x3 + relay.const(1.0) - out = relay.Tuple([x1, relay.Tuple([relay.Tuple([x2, x3]), x4])]) - func = relay.Function([x], out) + """Tests compilation of functions with nested tuple outputs""" + input_x = relay.var("x", shape=(10,)) + output_1 = input_x + relay.const(1.0) + output_2 = output_1 + relay.const(1.0) + output_3 = output_2 + relay.const(1.0) + output_4 = output_3 + relay.const(1.0) + full_output = relay.Tuple( + [output_1, relay.Tuple([relay.Tuple([output_2, output_3]), output_4])] + ) + func = relay.Function([input_x], full_output) x_data = np.random.uniform(size=(10,)).astype(np.float32) inputs = {"x": x_data} @@ -326,7 +333,8 @@ def test_add_const(interface_api, use_unpacked_api, test_runner): @parametrize_aot_options -def test_mul_param(interface_api, use_unpacked_api, test_runner): +def test_multiply(interface_api, use_unpacked_api, test_runner): + """Tests compilation of multiply""" x = relay.var("x", shape=(10, 10)) y = relay.var("y", shape=(1, 10)) func = relay.Function([x, y], relay.multiply(x, y)) @@ -362,6 +370,7 @@ def test_subtract(interface_api, use_unpacked_api, test_runner): @parametrize_aot_options def test_tuple_output(interface_api, use_unpacked_api, test_runner): + """Tests getting items from tuples""" x = relay.var("x", shape=(6, 9)) y = relay.split(x, 3).astuple() a = relay.TupleGetItem(y, 0) @@ -383,6 +392,7 @@ def test_tuple_output(interface_api, use_unpacked_api, test_runner): ["debug_calculated_workspaces", "workspace_byte_alignment"], [(True, 1), (True, 16), (False, 1)] ) def test_mobilenet(debug_calculated_workspaces, workspace_byte_alignment): + """Full network test with Mobilenet""" use_unpacked_api = True interface_api = "c" test_runner = AOT_DEFAULT_RUNNER @@ -413,33 +423,36 @@ def test_mobilenet(debug_calculated_workspaces, workspace_byte_alignment): @pytest.mark.parametrize("merge_compiler_regions", [False, True]) def test_byoc_microtvm(merge_compiler_regions): - """This is a simple test to check BYOC capabilities of AOT - with and without merging compiler regions to test for https://github.com/apache/tvm/issues/9036""" + """ + This is a simple test to check BYOC capabilities of AOT + with and without merging compiler regions to test for https://github.com/apache/tvm/issues/9036 + """ use_unpacked_api = False interface_api = "packed" test_runner = AOT_DEFAULT_RUNNER - x = relay.var("x", shape=(10, 10)) - w0 = relay.var("w0", shape=(10, 10)) - w1 = relay.var("w1", shape=(10, 10)) + input_x = relay.var("x", shape=(10, 10)) + input_w0 = relay.var("w0", shape=(10, 10)) + input_w1 = relay.var("w1", shape=(10, 10)) # z0 = x + w0 - x_ = compiler_begin(x, "ccompiler") - w0_ = compiler_begin(w0, "ccompiler") - z0_ = relay.add(x_, w0_) - z0 = compiler_end(z0_, "ccompiler") + marked_input_x = compiler_begin(input_x, "ccompiler") + marked_input_w0 = compiler_begin(input_w0, "ccompiler") + add_x_and_w0 = relay.add(marked_input_x, marked_input_w0) + end_inner_add = compiler_end(add_x_and_w0, "ccompiler") # z1 = z0 + w1 - z0__ = compiler_begin(z0, "ccompiler") - w1_ = compiler_begin(w1, "ccompiler") - z1_ = relay.add(z0__, w1_) - z1 = compiler_end(z1_, "ccompiler") + marked_inner_add = compiler_begin(end_inner_add, "ccompiler") + marked_w1 = compiler_begin(input_w1, "ccompiler") + add_nested_and_w1 = relay.add(marked_inner_add, marked_w1) + end_outer_add = compiler_end(add_nested_and_w1, "ccompiler") # z2 = z0 + z1 - z2 = relay.add(z0, z1) + final_add = relay.add(end_inner_add, end_outer_add) - f = relay.Function([x, w0, w1], z2) + relay_func = relay.Function([input_x, input_w0, input_w1], final_add) mod = tvm.IRModule() - mod["main"] = f + mod["main"] = relay_func if merge_compiler_regions: mod = transform.MergeCompilerRegions()(mod) @@ -467,34 +480,37 @@ def test_byoc_microtvm_multiple_subgraphs(merge_compiler_regions): interface_api = "packed" test_runner = AOT_DEFAULT_RUNNER - x = relay.var("x", shape=(10, 10)) - w0 = relay.var("w0", shape=(10, 10)) - w1 = relay.var("w1", shape=(10, 10)) - w2 = relay.var("w2", shape=(10, 10)) - w3 = relay.var("w3", shape=(10, 10)) - w4 = relay.var("w4", shape=(10, 10)) - w5 = relay.var("w5", shape=(10, 10)) - w6 = relay.var("w6", shape=(10, 10)) - w7 = relay.var("w7", shape=(10, 10)) + input_x = relay.var("x", shape=(10, 10)) + input_w0 = relay.var("w0", shape=(10, 10)) + input_w1 = relay.var("w1", shape=(10, 10)) + input_w2 = relay.var("w2", shape=(10, 10)) + input_w3 = relay.var("w3", shape=(10, 10)) + input_w4 = relay.var("w4", shape=(10, 10)) + input_w5 = relay.var("w5", shape=(10, 10)) + input_w6 = relay.var("w6", shape=(10, 10)) + input_w7 = relay.var("w7", shape=(10, 10)) # C compiler - z0 = relay.add(x, w0) - p0 = relay.subtract(z0, w1) - q0 = relay.multiply(p0, w2) + ccompiler_add_1 = relay.add(input_x, input_w0) + ccompiler_sub_1 = relay.subtract(ccompiler_add_1, input_w1) + ccompiler_mul_1 = relay.multiply(ccompiler_sub_1, input_w2) - z1 = relay.add(x, w3) - p1 = relay.subtract(z1, w4) - q1 = relay.multiply(p1, w5) + ccompiler_add_2 = relay.add(input_x, input_w3) + ccompiler_sub_2 = relay.subtract(ccompiler_add_2, input_w4) + ccompiler_mul_2 = relay.multiply(ccompiler_sub_2, input_w5) # Other parts on TVM - z2 = relay.add(x, w6) - q2 = relay.subtract(z2, w7) + tvm_add = relay.add(input_x, input_w6) + tvm_sub = relay.subtract(tvm_add, input_w7) - r = relay.concatenate((q0, q1, q2), axis=0) - f = relay.Function([x, w0, w1, w2, w3, w4, w5, w6, w7], r) + concat_outputs = relay.concatenate((ccompiler_mul_1, ccompiler_mul_2, tvm_sub), axis=0) + relay_func = relay.Function( + [input_x, input_w0, input_w1, input_w2, input_w3, input_w4, input_w5, input_w6, input_w7], + concat_outputs, + ) mod = tvm.IRModule() ann = byoc.CcompilerAnnotator() - mod["main"] = ann.visit(f) + mod["main"] = ann.visit(relay_func) if merge_compiler_regions: mod = transform.MergeCompilerRegions()(mod) @@ -521,22 +537,23 @@ def test_byoc_microtvm_multiple_subgraphs(merge_compiler_regions): @parametrize_aot_options def test_add_name_mangling_with_params(interface_api, use_unpacked_api, test_runner): - x = relay.var("x", shape=(1, 10)) - y = relay.var("y", shape=(1, 10)) - z = relay.add(x, y) - func = relay.Function([x, y], z) + """Checks name mangling works with parameters""" + input_x = relay.var("x", shape=(1, 10)) + input_y = relay.var("y", shape=(1, 10)) + func_add = relay.add(input_x, input_y) + relay_func = relay.Function([input_x, input_y], func_add) x_in = np.ones((1, 10)).astype("float32") y_in = np.random.uniform(size=(1, 10)).astype("float32") params = {"x": x_in} inputs = {"y": y_in} - output_list = generate_ref_data(func, inputs, params) + output_list = generate_ref_data(relay_func, inputs, params) compile_and_run( AOTTestModel( name="my_mod", - module=func, + module=relay_func, inputs=inputs, outputs=output_list, params=params, @@ -549,6 +566,7 @@ def test_add_name_mangling_with_params(interface_api, use_unpacked_api, test_run @parametrize_aot_options def test_multiple_models(interface_api, use_unpacked_api, test_runner): + """Compiles multiple models to ensure both can be compiled into one output""" # Identity model without params x = relay.var("x", "float32") mod1 = relay.Function([x], x) @@ -558,22 +576,23 @@ def test_multiple_models(interface_api, use_unpacked_api, test_runner): params1 = None # Convolution model - RELAY_MODEL = """ -#[version = "0.0.5"] -def @main(%data : Tensor[(1, 3, 64, 64), uint8], %weight : Tensor[(8, 3, 5, 5), int8]) { - %1 = nn.conv2d( - %data, - %weight, - padding=[2, 2], - channels=8, - kernel_size=[5, 5], - data_layout="NCHW", - kernel_layout="OIHW", - out_dtype="int32"); - %1 -} -""" - mod2 = tvm.parser.fromtext(RELAY_MODEL) + relay_model = """ + #[version = "0.0.5"] + def @main(%data : Tensor[(1, 3, 64, 64), uint8], %weight : Tensor[(8, 3, 5, 5), int8]) { + %1 = nn.conv2d( + %data, + %weight, + padding=[2, 2], + channels=8, + kernel_size=[5, 5], + data_layout="NCHW", + kernel_layout="OIHW", + out_dtype="int32"); + %1 + } + """ + + mod2 = tvm.parser.fromtext(relay_model) main_func = mod2["main"] shape_dict = {p.name_hint: p.checked_type.concrete_shape for p in main_func.params} type_dict = {p.name_hint: p.checked_type.dtype for p in main_func.params} @@ -609,12 +628,14 @@ def @main(%data : Tensor[(1, 3, 64, 64), uint8], %weight : Tensor[(8, 3, 5, 5), def test_quant_mobilenet_tfl(): - """Since in AOT we pass directly the output buffer from the user, in quantized networks sharing the output buffers is not possible. - This is because the output data type is int8 and the intermediate buffer are int32 or int16. We use mobilenet quantized to stress this + """Since in AOT we pass directly the output buffer from the user, + in quantized networks sharing the output buffers is not possible. + This is because the output data type is int8 and the intermediate + buffer are int32 or int16. We use mobilenet quantized to stress this situation and verify that the output buffer sharing is disabled in AOT.""" pytest.importorskip("tflite") - import tvm.relay.testing.tf as tf_testing + import tvm.relay.testing.tf as tf_testing # pylint: disable=import-outside-toplevel use_unpacked_api = True interface_api = "c" @@ -640,22 +661,22 @@ def test_transpose(interface_api, use_unpacked_api, test_runner): """Test that non-inpleaceable operations (e.g., transpose) do not happen in-place.""" dtype = "float32" - x = relay.var("x", shape=(10, 5), dtype=dtype) - y = relay.var("y", shape=(10, 5), dtype=dtype) - t = relay.var("z", shape=(), dtype=dtype) - a = relay.add(x, y) - b = relay.transpose(a) - z = relay.add(b, t) + input_x = relay.var("x", shape=(10, 5), dtype=dtype) + input_y = relay.var("y", shape=(10, 5), dtype=dtype) + input_z = relay.var("z", shape=(), dtype=dtype) + first_add = relay.add(input_x, input_y) + transpose_add = relay.transpose(first_add) + final_add = relay.add(transpose_add, input_z) # Check result. - func = relay.Function([x, y, t], z) + relay_func = relay.Function([input_x, input_y, input_z], final_add) x_data = np.random.rand(10, 5).astype(dtype) y_data = np.random.rand(10, 5).astype(dtype) t_data = np.random.uniform(size=()).astype(dtype) inputs = {"x": x_data, "y": y_data, "z": t_data} - output_list = generate_ref_data(func, inputs) + output_list = generate_ref_data(relay_func, inputs) compile_and_run( - AOTTestModel(module=IRModule.from_expr(func), inputs=inputs, outputs=output_list), + AOTTestModel(module=IRModule.from_expr(relay_func), inputs=inputs, outputs=output_list), test_runner, interface_api, use_unpacked_api, @@ -693,15 +714,15 @@ def test_name_sanitiser_name_clash(): test_runner = AOT_DEFAULT_RUNNER dtype = "float32" - x = relay.var("input::-1", shape=(10, 5), dtype=dtype) + input_non_clashing = relay.var("input::-1", shape=(10, 5), dtype=dtype) # Next 2 input tensor names will clash once sanitized. - y = relay.var("input::-2", shape=(10, 5), dtype=dtype) - t = relay.var("input:--2", shape=(), dtype=dtype) - a = relay.add(x, y) - b = relay.transpose(a) - z = relay.add(b, t) + input_clashing_1 = relay.var("input::-2", shape=(10, 5), dtype=dtype) + input_clashing_2 = relay.var("input:--2", shape=(), dtype=dtype) + inner_add = relay.add(input_non_clashing, input_clashing_1) + transpose_add = relay.transpose(inner_add) + final_add = relay.add(transpose_add, input_clashing_2) # Check result. - func = relay.Function([x, y, t], z) + func = relay.Function([input_non_clashing, input_clashing_1, input_clashing_2], final_add) x_data = np.random.rand(10, 5).astype(dtype) y_data = np.random.rand(10, 5).astype(dtype) t_data = np.random.uniform(size=()).astype(dtype) @@ -721,17 +742,17 @@ def test_name_sanitiser_name_clash(): # This tests for deprecated AOT executor arguments # TODO(Mousius) Remove deprecated arguments later -def test_deprecated_target_arguments(capsys): +def test_deprecated_target_arguments(): """Tests we can still use relay.build with -executor, -runtime and -link-params""" interface_api = "c" use_unpacked_api = True test_runner = AOT_DEFAULT_RUNNER - x = relay.var("x", shape=(1, 10)) - y = relay.var("y", shape=(1, 10)) - z = relay.add(x, y) - func = relay.Function([x, y], z) + input_x = relay.var("x", shape=(1, 10)) + input_y = relay.var("y", shape=(1, 10)) + func_add = relay.add(input_x, input_y) + func = relay.Function([input_x, input_y], func_add) x_in = np.ones((1, 10)).astype("float32") y_in = np.random.uniform(size=(1, 10)).astype("float32") @@ -761,6 +782,7 @@ def test_aot_codegen_backend_alloc_workspace_calls(): # The %data and %weight shapes in the following primitive Relay should create # small tensors that would get lowered to stack allocations in the CPU PrimFuncs. # However, the AoT executor codegen should retain them as TVMBAW calls + # pylint: disable=line-too-long relay_mod = tvm.parser.fromtext( """ #[version = "0.0.5"] @@ -784,6 +806,8 @@ def @main(%data: Tensor[(1, 4, 4, 4), float32], %weight: Tensor[(4, 4, 3, 3), fl } """ ) + # pylint: enable=line-too-long + compiled_test_mods = compile_models( models=AOTTestModel(module=relay_mod, inputs=None, outputs=None), interface_api="c", @@ -822,10 +846,12 @@ def test_output_tensor_names(): """Test that the output names generated match those in the model""" pytest.importorskip("tflite") - import os + # pylint: disable=import-outside-toplevel import tensorflow as tf import tflite.Model + # pylint: enable=import-outside-toplevel + ifm_shape = (1, 299, 299, 3) padding = "VALID" strides = (1, 1) @@ -836,38 +862,40 @@ def create_tflite_graph_two_outs(): """Create a model with 2 output tensors""" class Model(tf.Module): + """Simple TFLite test model""" + @tf.function - def tf_function(self, x): - # Use tf.nn API to create the model + def tf_function(self, tf_input_x): + """Single TFLite function with two convolutions""" tf_strides = [1, strides[0], strides[1], 1] filter_shape = [kernel_shape[0], kernel_shape[1], 3, 3] filter1 = tf.constant( np.arange(np.prod(filter_shape)).reshape(filter_shape), dtype=tf.float32, ) - op = tf.nn.conv2d( - x, + first_conv2d = tf.nn.conv2d( + tf_input_x, filters=filter1, strides=tf_strides, padding=padding, dilations=dilation, ) - op = tf.nn.relu(op) - # Second convolution + first_conv2d = tf.nn.relu(first_conv2d) + filter2 = tf.constant( 1000 + np.arange(np.prod(filter_shape)).reshape(filter_shape), dtype=tf.float32, ) - op2 = tf.nn.conv2d( - x, + second_conv2d = tf.nn.conv2d( + tf_input_x, filters=filter2, strides=strides, padding=padding, data_format="NHWC", dilations=dilation, ) - op2 = tf.nn.relu(op2) - return op, op2 + second_conv2d = tf.nn.relu(second_conv2d) + return first_conv2d, second_conv2d model = Model() concrete_func = model.tf_function.get_concrete_function( @@ -934,6 +962,7 @@ def representative_dataset(): ], ) def test_workspace_calculation(workspace_byte_alignment, main_workspace_size): + """Checks calculated workspace against known values""" mod, params = tvm.relay.testing.synthetic.get_workload() target = "c" runtime = Runtime("crt") @@ -964,9 +993,12 @@ def test_workspace_calculation_cmsis_nn(): -hierarchical manner.""" pytest.importorskip("tflite") + # pylint: disable=import-outside-toplevel from tvm.relay.op.contrib import cmsisnn from tvm.contrib.download import download_testdata + # pylint: enable=import-outside-toplevel + target = "c" runtime = Runtime("crt") executor = Executor( @@ -978,7 +1010,11 @@ def test_workspace_calculation_cmsis_nn(): }, ) - base_url = "https://github.com/ARM-software/ML-zoo/raw/48a22ee22325d15d2371a6df24eb7d67e21dcc97/models/keyword_spotting/cnn_small/tflite_int8" + base_url = ( + "https://github.com/ARM-software/ML-zoo/raw/" + "48a22ee22325d15d2371a6df24eb7d67e21dcc97" + "/models/keyword_spotting/cnn_small/tflite_int8" + ) file_to_download = "cnn_s_quantized.tflite" file_saved = "cnn_s_quantized_15Dec2021.tflite" model_file = download_testdata("{}/{}".format(base_url, file_to_download), file_saved) @@ -997,10 +1033,10 @@ def test_workspace_calculation_cmsis_nn(): def test_aot_codegen_checks_returns(): """This test checks whether AoT lowering creates calls that check the return value correctly""" - x = relay.var("x", shape=(1, 10)) - y = relay.var("y", shape=(1, 10)) - z = relay.add(x, y) - func = relay.Function([x, y], z) + input_x = relay.var("x", shape=(1, 10)) + input_y = relay.var("y", shape=(1, 10)) + func_add = relay.add(input_x, input_y) + func = relay.Function([input_x, input_y], func_add) compiled_test_mods = compile_models( models=AOTTestModel(module=IRModule.from_expr(func), inputs=None, outputs=None), @@ -1021,17 +1057,17 @@ def test_aot_codegen_checks_returns(): ) # TODO(Mousius) - Create a better place for C codegen tests assert ( - "if (tvmgen_default_fused_add(x_buffer_var, y_buffer_var, output_buffer_var) != 0 ) return -1;" + "if (tvmgen_default_fused_add(x_buffer_var, y_buffer_var, output_buffer_var) != 0 ) return -1;" # pylint: disable=line-too-long in source ) def test_aot_uses_anf(): """Checks that A-Normal Form is being used in the AOT lowering pipeline.""" - x = relay.var("x", shape=(1, 10, 10, 10)) - y = relay.var("y", shape=(1, 10, 10, 10)) - z = relay.add(x, y) - func = relay.Function([x, y], z) + input_x = relay.var("x", shape=(1, 10, 10, 10)) + input_y = relay.var("y", shape=(1, 10, 10, 10)) + func_add = relay.add(input_x, input_y) + func = relay.Function([input_x, input_y], func_add) @pass_instrument class CheckANFRuns: diff --git a/tests/python/relay/aot/test_crt_aot_usmp.py b/tests/python/relay/aot/test_crt_aot_usmp.py index 3ede2298873b..4373ddb2b309 100644 --- a/tests/python/relay/aot/test_crt_aot_usmp.py +++ b/tests/python/relay/aot/test_crt_aot_usmp.py @@ -17,17 +17,14 @@ """ This file contains test that use USMP + AoT using C runtime APIs""" from collections import OrderedDict -import sys import re import numpy as np import pytest import tvm -from tvm import relay, TVMError -from tvm.ir.module import IRModule -from tvm.relay import testing, transform -from tvm.relay.testing import byoc +from tvm import relay +from tvm.relay import transform from tvm.relay.op.annotation import compiler_begin, compiler_end from tvm.relay.backend import Executor, Runtime from tvm import WorkspaceMemoryPools, PoolInfo @@ -47,7 +44,7 @@ def _check_for_no_tvm_backendallocworkspace_calls(mod: tvm.runtime.module): assert ( - is_tvm_backendallocworkspace_calls(mod) == False + is_tvm_backendallocworkspace_calls(mod) is False ), "This is failing because USMP was unable to plan for every tir.allocate node." @@ -60,6 +57,7 @@ def _check_for_no_tvm_backendallocworkspace_calls(mod: tvm.runtime.module): ], ) def test_memory_planning(workspace_byte_alignment, main_workspace_size): + """Checks calculated workspace against known values""" mod, params = tvm.relay.testing.synthetic.get_workload() target = "c" runtime = Runtime("crt") @@ -141,33 +139,36 @@ def test_conv2d(interface_api, use_unpacked_api, test_runner, groups, weight_sha @pytest.mark.parametrize("merge_compiler_regions", [False, True]) def test_byoc_microtvm(merge_compiler_regions): - """This is a simple test to check BYOC capabilities of AOT - with and without merging compiler regions to test for https://github.com/apache/tvm/issues/9036""" + """ + This is a simple test to check BYOC capabilities of AOT + with and without merging compiler regions to test for https://github.com/apache/tvm/issues/9036 + """ use_unpacked_api = False interface_api = "packed" test_runner = AOTTestRunner(pass_config={"tir.usmp.enable": True}) - x = relay.var("x", shape=(10, 10)) - w0 = relay.var("w0", shape=(10, 10)) - w1 = relay.var("w1", shape=(10, 10)) + input_x = relay.var("x", shape=(10, 10)) + input_w0 = relay.var("w0", shape=(10, 10)) + input_w1 = relay.var("w1", shape=(10, 10)) # z0 = x + w0 - x_ = compiler_begin(x, "ccompiler") - w0_ = compiler_begin(w0, "ccompiler") - z0_ = relay.add(x_, w0_) - z0 = compiler_end(z0_, "ccompiler") + marked_input_x = compiler_begin(input_x, "ccompiler") + marked_input_w0 = compiler_begin(input_w0, "ccompiler") + add_x_and_w0 = relay.add(marked_input_x, marked_input_w0) + end_inner_add = compiler_end(add_x_and_w0, "ccompiler") # z1 = z0 + w1 - z0__ = compiler_begin(z0, "ccompiler") - w1_ = compiler_begin(w1, "ccompiler") - z1_ = relay.add(z0__, w1_) - z1 = compiler_end(z1_, "ccompiler") + marked_inner_add = compiler_begin(end_inner_add, "ccompiler") + marked_w1 = compiler_begin(input_w1, "ccompiler") + add_nested_and_w1 = relay.add(marked_inner_add, marked_w1) + end_outer_add = compiler_end(add_nested_and_w1, "ccompiler") # z2 = z0 + z1 - z2 = relay.add(z0, z1) + final_add = relay.add(end_inner_add, end_outer_add) - f = relay.Function([x, w0, w1], z2) + relay_func = relay.Function([input_x, input_w0, input_w1], final_add) mod = tvm.IRModule() - mod["main"] = f + mod["main"] = relay_func if merge_compiler_regions: mod = transform.MergeCompilerRegions()(mod) @@ -199,11 +200,13 @@ def test_byoc_microtvm(merge_compiler_regions): MOBILENET_V1_URL = ( - "https://storage.googleapis.com/download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_1.0_224_quant.tgz", + "https://storage.googleapis.com/download.tensorflow.org/models/" + + "/mobilenet_v1_2018_08_02/mobilenet_v1_1.0_224_quant.tgz", "mobilenet_v1_1.0_224_quant.tflite", ) MOBILENET_V2_URL = ( - "https://storage.googleapis.com/download.tensorflow.org/models/tflite_11_05_08/mobilenet_v2_1.0_224_quant.tgz", + "https://storage.googleapis.com/download.tensorflow.org/models/" + + "tflite_11_05_08/mobilenet_v2_1.0_224_quant.tgz", "mobilenet_v2_1.0_224_quant.tflite", ) @@ -217,10 +220,13 @@ def test_byoc_microtvm(merge_compiler_regions): ], ) def test_tflite_model_u1_usecase(model_url, usmp_algo, workspace_size): - """This checks for ML models and the memory used by them when using USMP with different algorithms""" + """ + This checks for ML models and the memory used by them + when using USMP with different algorithms + """ pytest.importorskip("tflite") - import tvm.relay.testing.tf as tf_testing + import tvm.relay.testing.tf as tf_testing # pylint: disable=import-outside-toplevel use_unpacked_api = True interface_api = "c" @@ -287,7 +293,7 @@ def test_tflite_model_u3_usecase_single_external_pool(model_url, usmp_algo): """This checks for inference with USMP using external pool placed in the application""" pytest.importorskip("tflite") - import tvm.relay.testing.tf as tf_testing + import tvm.relay.testing.tf as tf_testing # pylint: disable=import-outside-toplevel use_unpacked_api = True interface_api = "c" @@ -341,7 +347,7 @@ def test_tflite_model_u3_usecase_two_external_pools(model_url, usmp_algo): """This checks for inference using two external pools placed in the application""" pytest.importorskip("tflite") - import tvm.relay.testing.tf as tf_testing + import tvm.relay.testing.tf as tf_testing # pylint: disable=import-outside-toplevel use_unpacked_api = True interface_api = "c" @@ -397,11 +403,11 @@ def test_tflite_model_u3_usecase_two_external_pools(model_url, usmp_algo): ((MOBILENET_V1_URL, MOBILENET_V2_URL), "greedy_by_size"), ], ) -def test_tflite_model_u2_usecase_two_models_with_a_single_external_pool(model_urls, usmp_algo): +def test_two_models_with_a_single_external_pool(model_urls, usmp_algo): """This checks for inference using a single large enough common pool""" pytest.importorskip("tflite") - import tvm.relay.testing.tf as tf_testing + import tvm.relay.testing.tf as tf_testing # pylint: disable=import-outside-toplevel use_unpacked_api = True interface_api = "c" @@ -469,7 +475,7 @@ def test_tflite_model_u4_usecase_single_external_pool(model_url, usmp_algo): """This checks for inference with USMP using external pool placed in the application""" pytest.importorskip("tflite") - import tvm.relay.testing.tf as tf_testing + import tvm.relay.testing.tf as tf_testing # pylint: disable=import-outside-toplevel use_unpacked_api = True interface_api = "c" @@ -538,7 +544,7 @@ def test_tflite_model_u4_usecase_two_external_pools(model_url, usmp_algo): """This checks for inference with USMP using external pool placed in the application""" pytest.importorskip("tflite") - import tvm.relay.testing.tf as tf_testing + import tvm.relay.testing.tf as tf_testing # pylint: disable=import-outside-toplevel use_unpacked_api = True interface_api = "c" @@ -604,7 +610,8 @@ def test_tflite_model_u4_usecase_two_external_pools(model_url, usmp_algo): ) -def test_u4_usecase_incompatible_interface_api_errors(): +def test_incompatible_interface_api_errors(): + """Ensures an error is thrown if not using the C interface API""" mod, params = tvm.relay.testing.synthetic.get_workload() target = "c" runtime = Runtime("crt")