From 66d686369cae65521b9c0e8b075826f3fa1348eb Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Fri, 8 Apr 2022 15:43:48 +0200 Subject: [PATCH 1/4] TVMC: Add new text/relay frontend This feature enables passing a textural representation of a relay module to the tvmc command line. Example: `tvmc compile relay.txt --target c --runtime=crt --executor=aot --executor-aot-unpacked-api=1 --pass-config tir.disable_vectorize=1 -f mlf` Currently it is not possible to supply parameters as it is mainly intended to be used for testing certain relay functions or operators. In the future (with minor changes to the tvmc frontend api) params could be passed via an additional i.e. `params.bin` file This commit also adds minimal unit testing of the added feature. Resolve PR comments TVMC: add warning if relay frontend is used --- python/tvm/driver/tvmc/frontends.py | 37 ++++++++++++++++++++ tests/python/driver/tvmc/conftest.py | 39 ++++++++++++++++++++++ tests/python/driver/tvmc/test_frontends.py | 13 ++++++++ 3 files changed, 89 insertions(+) diff --git a/python/tvm/driver/tvmc/frontends.py b/python/tvm/driver/tvmc/frontends.py index cfe5a4ac7b2e..28d35b97184a 100644 --- a/python/tvm/driver/tvmc/frontends.py +++ b/python/tvm/driver/tvmc/frontends.py @@ -32,6 +32,7 @@ import numpy as np from tvm import relay +from tvm import parser from tvm.driver.tvmc import TVMCException, TVMCImportError from tvm.driver.tvmc.model import TVMCModel @@ -294,6 +295,41 @@ def load(self, path, shape_dict=None, **kwargs): return relay.frontend.from_paddle(prog, shape_dict=shape_dict, **kwargs) +class RelayFrontend(Frontend): + """Relay frontend for TVMC""" + + @staticmethod + def name(): + return "relay" + + @staticmethod + def suffixes(): + return ["relay"] + + def load(self, path, shape_dict=None, **kwargs): + with open(path, "r", encoding="utf-8") as relay_text: + text = relay_text.read() + if shape_dict is not None: + logger.warning("Supplied shape_dict argument ignored for text frontend") + ir_mod = parser.fromtext(text) + + def _gen_params(ir_mod): + """Populate the all the params in the mode with ones.""" + main_func = ir_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} + params = {} + for name, shape in shape_dict.items(): + data = np.ones(shape).astype(type_dict[name]) + params[name] = data + return params + + logger.warning("Parameters in the relay module will be initialized with ones.") + params = _gen_params(ir_mod) + + return ir_mod, params + + ALL_FRONTENDS = [ KerasFrontend, OnnxFrontend, @@ -301,6 +337,7 @@ def load(self, path, shape_dict=None, **kwargs): TFLiteFrontend, PyTorchFrontend, PaddleFrontend, + RelayFrontend, ] diff --git a/tests/python/driver/tvmc/conftest.py b/tests/python/driver/tvmc/conftest.py index fcf079620e25..48b465e507ae 100644 --- a/tests/python/driver/tvmc/conftest.py +++ b/tests/python/driver/tvmc/conftest.py @@ -17,6 +17,7 @@ import os import pytest import tarfile +import textwrap import numpy as np @@ -229,3 +230,41 @@ def tflite_cnn_s_quantized(tmpdir_factory): "{}/{}".format(base_url, file_to_download), file_to_download, module=["tvmc"] ) return model_file + + +@pytest.fixture(scope="session") +def relay_text_conv2d(tmpdir_factory): + file_path = os.path.join(tmpdir_factory.mktemp("model"), "relay.txt") + + RELAY_MODEL = textwrap.dedent( + """\ + #[version = "0.0.5"] + def @main(%data : Tensor[(1, 3, 64, 64), uint8], %weight : Tensor[(3, 3, 5, 5), int8]) { + %1 = nn.conv2d( + %data, + %weight, + padding=[2, 2], + channels=3, + kernel_size=[5, 5], + data_layout="NCHW", + kernel_layout="OIHW", + out_dtype="int32"); + %2 = cast(nn.max_pool2d(%1, pool_size=[3, 3]), dtype="int8"); + %3 = nn.conv2d( + %2, + %weight, + padding=[2, 2], + channels=3, + kernel_size=[5, 5], + data_layout="NCHW", + kernel_layout="OIHW", + out_dtype="int32"); + %4 = nn.max_pool2d(%3, pool_size=[3, 3]); + %4 + } + """ + ) + + with open(file_path, "w") as relay_text: + relay_text.write(RELAY_MODEL) + return file_path diff --git a/tests/python/driver/tvmc/test_frontends.py b/tests/python/driver/tvmc/test_frontends.py index b76066994cb2..1e6efb4a3b24 100644 --- a/tests/python/driver/tvmc/test_frontends.py +++ b/tests/python/driver/tvmc/test_frontends.py @@ -106,6 +106,12 @@ def test_guess_frontend_paddle(): assert type(sut) is tvmc.frontends.PaddleFrontend +def test_guess_frontend_relay(): + + sut = tvmc.frontends.guess_frontend("relay.relay") + assert type(sut) is tvmc.frontends.RelayFrontend + + def test_guess_frontend_invalid(): with pytest.raises(TVMCException): tvmc.frontends.guess_frontend("not/a/file.txt") @@ -193,6 +199,13 @@ def test_load_model__paddle(paddle_resnet50): assert type(tvmc_model.params) is dict +def test_load_model__relay(relay_text_conv2d): + tvmc_model = tvmc.load(relay_text_conv2d, model_format="relay") + assert type(tvmc_model) is TVMCModel + assert type(tvmc_model.mod) is IRModule + assert type(tvmc_model.params) is dict + + def test_load_model___wrong_language__to_keras(tflite_mobilenet_v1_1_quant): # some CI environments wont offer TensorFlow/Keras, so skip in case it is not present pytest.importorskip("tensorflow") From 6a12a9e6cbb4ed31ebaafabd10ce96f2ccd13308 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 20 Apr 2022 09:22:02 +0200 Subject: [PATCH 2/4] [TVMC] populate parameters with random values instead of ones --- python/tvm/driver/tvmc/frontends.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/tvm/driver/tvmc/frontends.py b/python/tvm/driver/tvmc/frontends.py index 28d35b97184a..42955904a412 100644 --- a/python/tvm/driver/tvmc/frontends.py +++ b/python/tvm/driver/tvmc/frontends.py @@ -320,7 +320,10 @@ def _gen_params(ir_mod): type_dict = {p.name_hint: p.checked_type.dtype for p in main_func.params} params = {} for name, shape in shape_dict.items(): - data = np.ones(shape).astype(type_dict[name]) + if "int" in type_dict[name]: + data = np.random.randint(128, size=shape, dtype=type_dict[name]) + else: + data = np.random.uniform(-1, 1, size=shape).astype(type_dict[name]) params[name] = data return params From 3fd99a2ed79ca9f00085c58b5d39055278161bd3 Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 20 Apr 2022 09:23:32 +0200 Subject: [PATCH 3/4] [TVMC] Relay frontend: do not populate input tensor buffers if --input-shapes is provided This prevents that the constants inputs are used for Constant folding, thus changing the complexity of the model. If there would be a way, to distinguish between model inputs and parameter this workaround would not be required. --- python/tvm/driver/tvmc/frontends.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/python/tvm/driver/tvmc/frontends.py b/python/tvm/driver/tvmc/frontends.py index 42955904a412..aef0f798067a 100644 --- a/python/tvm/driver/tvmc/frontends.py +++ b/python/tvm/driver/tvmc/frontends.py @@ -313,13 +313,21 @@ def load(self, path, shape_dict=None, **kwargs): logger.warning("Supplied shape_dict argument ignored for text frontend") ir_mod = parser.fromtext(text) - def _gen_params(ir_mod): + if shape_dict: + input_names = shape_dict.keys() + else: + input_names = [] + + def _gen_params(ir_mod, skip_names=None): """Populate the all the params in the mode with ones.""" main_func = ir_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} params = {} for name, shape in shape_dict.items(): + if skip_names and name in skip_names: + continue + if "int" in type_dict[name]: data = np.random.randint(128, size=shape, dtype=type_dict[name]) else: @@ -328,7 +336,7 @@ def _gen_params(ir_mod): return params logger.warning("Parameters in the relay module will be initialized with ones.") - params = _gen_params(ir_mod) + params = _gen_params(ir_mod, skip_names=input_names) return ir_mod, params From 2797383aeb4603b7b11ea6c2eaeb8c27939d0d7f Mon Sep 17 00:00:00 2001 From: Philipp van Kempen Date: Wed, 20 Apr 2022 09:52:05 +0200 Subject: [PATCH 4/4] [TVMC] Relay frontend: check provided file contents before calling tvm.parser.fromtext() --- python/tvm/driver/tvmc/frontends.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/python/tvm/driver/tvmc/frontends.py b/python/tvm/driver/tvmc/frontends.py index aef0f798067a..2da548356446 100644 --- a/python/tvm/driver/tvmc/frontends.py +++ b/python/tvm/driver/tvmc/frontends.py @@ -23,6 +23,7 @@ import logging import os import sys +import re import importlib from abc import ABC from abc import abstractmethod @@ -309,8 +310,30 @@ def suffixes(): def load(self, path, shape_dict=None, **kwargs): with open(path, "r", encoding="utf-8") as relay_text: text = relay_text.read() - if shape_dict is not None: - logger.warning("Supplied shape_dict argument ignored for text frontend") + if shape_dict is None: + logger.warning( + "Specify --input-shapes to ensure that model inputs " + "will not be considered as constants." + ) + + def _validate_text(text): + """Check the provided file contents. + The relay.txt artifact contained in the MLF is missing the version header and + the metadata which is required to use meta[relay.Constant].""" + + if re.compile(r".*\#\[version\.*").match(text) is None: + raise TVMCException( + "The relay model does not include the required version information." + ) + if re.compile(r".*meta\[.+\].*", re.DOTALL).match(text): + if "#[metadata]" not in text: + raise TVMCException( + "The relay model does not include the required #[metadata] section. " + "Use ir_mod.astext(show_meta_data=True) to export compatible code." + ) + + _validate_text(text) + ir_mod = parser.fromtext(text) if shape_dict: @@ -335,7 +358,6 @@ def _gen_params(ir_mod, skip_names=None): params[name] = data return params - logger.warning("Parameters in the relay module will be initialized with ones.") params = _gen_params(ir_mod, skip_names=input_names) return ir_mod, params