From c102174172a048598fcee126f3a7b1a8f6518d7b Mon Sep 17 00:00:00 2001 From: Eric Lunderberg Date: Tue, 5 Apr 2022 15:43:33 -0500 Subject: [PATCH 1/6] [Hexagon] Move aot/graph_executor interactions into launcher Follow-up from https://github.com/apache/tvm/pull/10581, applying similar changes to the AOT and graph executor interactions. This moves the file management and upload/download from the unit tests into the launcher. --- python/tvm/contrib/hexagon/session.py | 139 +++++++++++++++++- .../contrib/test_hexagon/test_launcher.py | 58 +------- 2 files changed, 146 insertions(+), 51 deletions(-) diff --git a/python/tvm/contrib/hexagon/session.py b/python/tvm/contrib/hexagon/session.py index 44c4d145555c..7b483a25f5c0 100644 --- a/python/tvm/contrib/hexagon/session.py +++ b/python/tvm/contrib/hexagon/session.py @@ -20,10 +20,12 @@ import os import pathlib import tempfile -from typing import Union +from typing import Union, Optional import tvm from tvm import rpc as _rpc +import tvm.contrib.hexagon as hexagon +from tvm.relay.backend.executor_factory import ExecutorFactoryModule class Session: @@ -136,3 +138,138 @@ def load_module(self, module: Union[str, pathlib.Path, tvm.runtime.Module]): assert isinstance(module, (str, pathlib.Path)), "Invalid path type:" + str(type(module)) return self._rpc.get_function("tvm.hexagon.load_module")(str(module)) + + def get_graph_executor( + self, + module: Union[str, pathlib.Path, ExecutorFactoryModule], + graph_json: Optional[str] = None, + ): + """Create a local GraphModule which consumes a remote libmod. + + The session must be established (via __enter__) prior to + calling this function. + + Parameters + ---------- + + module : Union[str, pathlib.Path, ExecutorFactoryModule] + + The module to load. If `module` is a + `ExecutorFactoryModule`, it will be uploaded to the remote + session and loaded. In this case, `graph_json` should be + None. + + If `module` is a string or `pathlib.Path`, it must be + either a bare file name (without any path components), or + a full path in the remote system. In this case, the file + must already have been uploaded to the remote and placed + in the remote workspace, and `graph_json` must be + provided. + + graph_json : Optional[str] + + The string with the graph JSON. This should be passed if + and only if `module` is a file path to a binary that has + been uploaded to the remote. + + Returns + ------- + GraphModule : + Runtime graph module that can be used to execute the graph. + + """ + + assert ( + self.device is not None + ), "Hexagon session must be started using __enter__ prior to use" + + if isinstance(module, ExecutorFactoryModule): + assert graph_json is None, "May not specify graph_json if full module is provided" + graph_json = module.get_graph_json() + graph_mod = self.load_module(module.get_lib()) + else: + assert ( + graph_json is not None + ), "Must specify graph_json if binary module has already been uploaded" + graph_mod = self.load_module(module) + + return tvm.contrib.graph_executor.create(graph_json, graph_mod, self.device) + + def get_aot_executor( + self, + module: Union[str, pathlib.Path, ExecutorFactoryModule], + ): + """Create a local GraphModule which consumes a remote libmod. + + The session must be established (via __enter__) prior to + calling this function. + + Parameters + ---------- + + module : Union[str, pathlib.Path, ExecutorFactoryModule] + + The module to load. If `module` is a + `ExecutorFactoryModule`, it will be uploaded to the remote + session and loaded. + + If `module` is a string or `pathlib.Path`, it must be + either a bare file name (without any path components), or + a full path in the remote system. In this case, the file + must already have been uploaded to the remote and placed + in the remote workspace, and `graph_json` must be + provided. + + Returns + ------- + GraphModule : + Runtime graph module that can be used to execute the graph. + + """ + + assert ( + self.device is not None + ), "Hexagon session must be started using __enter__ prior to use" + + def _workaround_create_aot_shared(): + # The C codegen uses TVM/RT functions directly. On Hexagon it should use + # functions pointers via __TVMxyz variables. This workaround makes the + # runtime symbols visible to the compiled shared library. + extra_link_flags = os.environ.get("HEXAGON_SHARED_LINK_FLAGS") + extra_options = str(extra_link_flags).split() if extra_link_flags else [] + return lambda so_name, files, hexagon_arch, options: hexagon.create_aot_shared( + so_name, files, hexagon_arch, options=extra_options + options + ) + + if isinstance(module, ExecutorFactoryModule): + hexagon_arch = set( + target.mcpu.replace("hexagon", "") + for target in module.target.values() + if "hexagon" in target.keys + ) + assert hexagon_arch, "No hexagon target architecture found" + assert ( + len(hexagon_arch) == 1 + ), f"Inconsistent hexagon architecture found, {hexagon_arch}" + hexagon_arch = hexagon_arch.pop() + + with tempfile.TemporaryDirectory() as temp_dir: + temp_dir = pathlib.Path(temp_dir) + binary_name = "test_binary.so" + binary_path = temp_dir / binary_name + + # Uncomment this once the workaround is not needed. + # module.export_library( + # str(binary_path), fcompile=hexagon.create_aot_shared, hexagon_arch=hexagon_arch + # ) + module.export_library( + str(binary_path), + fcompile=_workaround_create_aot_shared(), + hexagon_arch=hexagon_arch, + ) + + self.upload(binary_path, binary_name) + module = binary_name + + aot_mod = self.load_module(module) + return tvm.runtime.executor.AotModule(aot_mod["default"](self.device)) diff --git a/tests/python/contrib/test_hexagon/test_launcher.py b/tests/python/contrib/test_hexagon/test_launcher.py index 3e72c38f1909..4b6db6fda38b 100644 --- a/tests/python/contrib/test_hexagon/test_launcher.py +++ b/tests/python/contrib/test_hexagon/test_launcher.py @@ -144,7 +144,7 @@ def test_matmul(self, hexagon_session, M, N, K): @requires_hexagon_toolchain -def test_graph_executor(hexagon_launcher, hexagon_session): +def test_graph_executor(hexagon_session): dtype = "float32" data = relay.var("data", relay.TensorType((1, 64, 64, 3), dtype)) weight = relay.var("weight", relay.TensorType((5, 5, 3, 8), dtype)) @@ -170,10 +170,6 @@ def test_graph_executor(hexagon_launcher, hexagon_session): params = {"weight": weight_in} inputs = {"data": data_in} - temp = utils.tempdir() - dso_binary = "test_binary.so" - dso_binary_path = temp.relpath(dso_binary) - with tvm.transform.PassContext(opt_level=3): lowered = tvm.relay.build( relay_mod, @@ -181,16 +177,11 @@ def test_graph_executor(hexagon_launcher, hexagon_session): runtime=runtime, executor=executor, ) - lowered.get_lib().save(dso_binary_path) if hexagon_session is None: pytest.skip(msg="Skip hardware test since ANDROID_SERIAL_NUMBER is not set.") - hexagon_launcher.upload(dso_binary_path, dso_binary) - - graph_mod = hexagon_launcher.get_graph_executor( - lowered.get_graph_json(), dso_binary, hexagon_session - ) + graph_mod = hexagon_session.get_graph_executor(lowered) graph_mod.set_input(**params) graph_mod.run(**inputs) hexagon_output = graph_mod.get_output(0).numpy() @@ -212,7 +203,7 @@ def test_graph_executor(hexagon_launcher, hexagon_session): @requires_hexagon_toolchain -def test_graph_executor_multiple_conv2d(hexagon_launcher, hexagon_session): +def test_graph_executor_multiple_conv2d(hexagon_session): dtype = "float32" input_shape = (1, 8, 8, 3) w1_shape = (5, 5, 3, 1) @@ -246,10 +237,6 @@ def test_graph_executor_multiple_conv2d(hexagon_launcher, hexagon_session): runtime = Runtime("cpp") executor = Executor("graph") - temp = utils.tempdir() - dso_binary = "test_binary.so" - dso_binary_path = temp.relpath(dso_binary) - with tvm.transform.PassContext(opt_level=3): lowered = tvm.relay.build( relay_mod, @@ -257,13 +244,10 @@ def test_graph_executor_multiple_conv2d(hexagon_launcher, hexagon_session): runtime=runtime, executor=executor, ) - lowered.get_lib().save(dso_binary_path) if hexagon_session is None: pytest.skip(msg="Skip hardware test since ANDROID_SERIAL_NUMBER is not set.") - hexagon_launcher.upload(dso_binary_path, dso_binary) - weight1_data = np.random.rand(w1_shape[0], w1_shape[1], w1_shape[2], w1_shape[3]).astype( dtype=dtype ) @@ -277,9 +261,7 @@ def test_graph_executor_multiple_conv2d(hexagon_launcher, hexagon_session): params = {"weight1": weight1_data, "weight2": weight2_data} inputs = {"data": input_data} - graph_mod = hexagon_launcher.get_graph_executor( - lowered.get_graph_json(), dso_binary, hexagon_session - ) + graph_mod = hexagon_session.get_graph_executor(lowered) graph_mod.set_input(**params) graph_mod.run(**inputs) hexagon_output = graph_mod.get_output(0).numpy() @@ -312,7 +294,7 @@ def _workaround_create_aot_shared(): @requires_hexagon_toolchain -def test_aot_executor(hexagon_launcher, hexagon_session): +def test_aot_executor(hexagon_session): dtype = "float32" input_shape = (1, 128, 128, 3) w_shape = (5, 5, 3, 8) @@ -332,9 +314,6 @@ def test_aot_executor(hexagon_launcher, hexagon_session): relay_mod = relay.transform.InferType()(relay_mod) target_hexagon = tvm.target.hexagon("v68") - temp = utils.tempdir() - dso_binary = "test_binary.so" - dso_binary_path = temp / dso_binary weight_data = np.random.rand(w_shape[0], w_shape[1], w_shape[2], w_shape[3]).astype(dtype=dtype) input_data = np.random.rand( @@ -352,20 +331,11 @@ def test_aot_executor(hexagon_launcher, hexagon_session): runtime=Runtime("cpp"), executor=Executor("aot", {"unpacked-api": False, "interface-api": "c"}), ) - # Uncomment this once the workaround is not needed. - # lowered.export_library( - # dso_binary_path, fcompile=hexagon.create_aot_shared, hexagon_arch="v68" - # ) - lowered.export_library( - dso_binary_path, fcompile=_workaround_create_aot_shared(), hexagon_arch="v68" - ) if hexagon_session is None: pytest.skip(msg="Skip hardware test, ANDROID_SERIAL_NUMBER is not set.") - hexagon_launcher.upload(dso_binary_path, dso_binary) - - aot_mod = hexagon_launcher.get_aot_executor(dso_binary, hexagon_session) + aot_mod = hexagon_session.get_aot_executor(lowered) aot_mod.set_input(**inputs) aot_mod.run() hexagon_output = aot_mod.get_output(0).numpy() @@ -388,7 +358,7 @@ def test_aot_executor(hexagon_launcher, hexagon_session): @requires_hexagon_toolchain -def test_aot_executor_multiple_conv2d(hexagon_launcher, hexagon_session): +def test_aot_executor_multiple_conv2d(hexagon_session): dtype = "float32" input_shape = (1, 8, 8, 3) w1_shape = (5, 5, 3, 1) @@ -419,9 +389,6 @@ def test_aot_executor_multiple_conv2d(hexagon_launcher, hexagon_session): relay_mod = relay.transform.InferType()(relay_mod) target_hexagon = tvm.target.hexagon("v68") - temp = utils.tempdir() - dso_binary = "test_binary.so" - dso_binary_path = temp / dso_binary weight1_data = np.random.rand(w1_shape[0], w1_shape[1], w1_shape[2], w1_shape[3]).astype( dtype=dtype @@ -444,20 +411,11 @@ def test_aot_executor_multiple_conv2d(hexagon_launcher, hexagon_session): runtime=Runtime("cpp"), executor=Executor("aot", {"unpacked-api": False, "interface-api": "c"}), ) - # Uncomment this once the workaround is not needed. - # lowered.export_library( - # dso_binary_path, fcompile=hexagon.create_aot_shared, hexagon_arch="v68" - # ) - lowered.export_library( - dso_binary_path, fcompile=_workaround_create_aot_shared(), hexagon_arch="v68" - ) if hexagon_session is None: pytest.skip(msg="Skip hardware test, ANDROID_SERIAL_NUMBER is not set.") - hexagon_launcher.upload(dso_binary_path, dso_binary) - - aot_mod = hexagon_launcher.get_aot_executor(dso_binary, hexagon_session) + aot_mod = hexagon_session.get_aot_executor(lowered) aot_mod.set_input(**inputs) aot_mod.run() hexagon_output = aot_mod.get_output(0).numpy() From 1acefcb185af93a045fbe6e4d638ae8c77061523 Mon Sep 17 00:00:00 2001 From: Eric Lunderberg Date: Tue, 5 Apr 2022 15:56:37 -0500 Subject: [PATCH 2/6] Added Session.test_executor to avoid duplication in graph/aot test. --- python/tvm/contrib/hexagon/session.py | 38 +++++++++++++++---- .../contrib/test_hexagon/test_launcher.py | 8 ++-- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/python/tvm/contrib/hexagon/session.py b/python/tvm/contrib/hexagon/session.py index 7b483a25f5c0..055e49a70d0c 100644 --- a/python/tvm/contrib/hexagon/session.py +++ b/python/tvm/contrib/hexagon/session.py @@ -25,7 +25,11 @@ import tvm from tvm import rpc as _rpc import tvm.contrib.hexagon as hexagon -from tvm.relay.backend.executor_factory import ExecutorFactoryModule +from tvm.relay.backend.executor_factory import ( + ExecutorFactoryModule, + AOTExecutorFactoryModule, + GraphExecutorFactoryModule, +) class Session: @@ -141,7 +145,7 @@ def load_module(self, module: Union[str, pathlib.Path, tvm.runtime.Module]): def get_graph_executor( self, - module: Union[str, pathlib.Path, ExecutorFactoryModule], + module: Union[str, pathlib.Path, GraphExecutorFactoryModule], graph_json: Optional[str] = None, ): """Create a local GraphModule which consumes a remote libmod. @@ -152,10 +156,10 @@ def get_graph_executor( Parameters ---------- - module : Union[str, pathlib.Path, ExecutorFactoryModule] + module : Union[str, pathlib.Path, GraphExecutorFactoryModule] The module to load. If `module` is a - `ExecutorFactoryModule`, it will be uploaded to the remote + `GraphExecutorFactoryModule`, it will be uploaded to the remote session and loaded. In this case, `graph_json` should be None. @@ -183,7 +187,7 @@ def get_graph_executor( self.device is not None ), "Hexagon session must be started using __enter__ prior to use" - if isinstance(module, ExecutorFactoryModule): + if isinstance(module, GraphExecutorFactoryModule): assert graph_json is None, "May not specify graph_json if full module is provided" graph_json = module.get_graph_json() graph_mod = self.load_module(module.get_lib()) @@ -197,7 +201,7 @@ def get_graph_executor( def get_aot_executor( self, - module: Union[str, pathlib.Path, ExecutorFactoryModule], + module: Union[str, pathlib.Path, AOTExecutorFactoryModule], ): """Create a local GraphModule which consumes a remote libmod. @@ -207,10 +211,10 @@ def get_aot_executor( Parameters ---------- - module : Union[str, pathlib.Path, ExecutorFactoryModule] + module : Union[str, pathlib.Path, AOTExecutorFactoryModule] The module to load. If `module` is a - `ExecutorFactoryModule`, it will be uploaded to the remote + `AOTExecutorFactoryModule`, it will be uploaded to the remote session and loaded. If `module` is a string or `pathlib.Path`, it must be @@ -273,3 +277,21 @@ def _workaround_create_aot_shared(): aot_mod = self.load_module(module) return tvm.runtime.executor.AotModule(aot_mod["default"](self.device)) + + def get_executor(self, module: ExecutorFactoryModule): + """Create a local GraphModule which consumes a remote libmod. + + Parameters + ---------- + + module : ExecutorFactoryModule + + The module to upload to the remote + session and load. + """ + if isinstance(module, AOTExecutorFactoryModule): + return self.get_aot_executor(module) + elif isinstance(module, GraphExecutorFactoryModule): + return self.get_graph_executor(module) + else: + raise TypeError(f"Unsupported executor type: {type(module)}") diff --git a/tests/python/contrib/test_hexagon/test_launcher.py b/tests/python/contrib/test_hexagon/test_launcher.py index 4b6db6fda38b..8666e3e1fc89 100644 --- a/tests/python/contrib/test_hexagon/test_launcher.py +++ b/tests/python/contrib/test_hexagon/test_launcher.py @@ -181,7 +181,7 @@ def test_graph_executor(hexagon_session): if hexagon_session is None: pytest.skip(msg="Skip hardware test since ANDROID_SERIAL_NUMBER is not set.") - graph_mod = hexagon_session.get_graph_executor(lowered) + graph_mod = hexagon_session.get_executor(lowered) graph_mod.set_input(**params) graph_mod.run(**inputs) hexagon_output = graph_mod.get_output(0).numpy() @@ -261,7 +261,7 @@ def test_graph_executor_multiple_conv2d(hexagon_session): params = {"weight1": weight1_data, "weight2": weight2_data} inputs = {"data": input_data} - graph_mod = hexagon_session.get_graph_executor(lowered) + graph_mod = hexagon_session.get_executor(lowered) graph_mod.set_input(**params) graph_mod.run(**inputs) hexagon_output = graph_mod.get_output(0).numpy() @@ -335,7 +335,7 @@ def test_aot_executor(hexagon_session): if hexagon_session is None: pytest.skip(msg="Skip hardware test, ANDROID_SERIAL_NUMBER is not set.") - aot_mod = hexagon_session.get_aot_executor(lowered) + aot_mod = hexagon_session.get_executor(lowered) aot_mod.set_input(**inputs) aot_mod.run() hexagon_output = aot_mod.get_output(0).numpy() @@ -415,7 +415,7 @@ def test_aot_executor_multiple_conv2d(hexagon_session): if hexagon_session is None: pytest.skip(msg="Skip hardware test, ANDROID_SERIAL_NUMBER is not set.") - aot_mod = hexagon_session.get_aot_executor(lowered) + aot_mod = hexagon_session.get_executor(lowered) aot_mod.set_input(**inputs) aot_mod.run() hexagon_output = aot_mod.get_output(0).numpy() From 188538a251eac145b7494ebdc10a4ab131c13ec8 Mon Sep 17 00:00:00 2001 From: Eric Lunderberg Date: Wed, 6 Apr 2022 09:47:56 -0500 Subject: [PATCH 3/6] Resolve lint errors --- python/tvm/contrib/hexagon/session.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/python/tvm/contrib/hexagon/session.py b/python/tvm/contrib/hexagon/session.py index 055e49a70d0c..1963159955b2 100644 --- a/python/tvm/contrib/hexagon/session.py +++ b/python/tvm/contrib/hexagon/session.py @@ -264,7 +264,9 @@ def _workaround_create_aot_shared(): # Uncomment this once the workaround is not needed. # module.export_library( - # str(binary_path), fcompile=hexagon.create_aot_shared, hexagon_arch=hexagon_arch + # str(binary_path), + # fcompile=hexagon.create_aot_shared, + # hexagon_arch=hexagon_arch, # ) module.export_library( str(binary_path), @@ -291,7 +293,7 @@ def get_executor(self, module: ExecutorFactoryModule): """ if isinstance(module, AOTExecutorFactoryModule): return self.get_aot_executor(module) - elif isinstance(module, GraphExecutorFactoryModule): + if isinstance(module, GraphExecutorFactoryModule): return self.get_graph_executor(module) - else: - raise TypeError(f"Unsupported executor type: {type(module)}") + + raise TypeError(f"Unsupported executor type: {type(module)}") From a7c03f82055700934c321dfc804e8c1ff618b7f8 Mon Sep 17 00:00:00 2001 From: Eric Lunderberg Date: Mon, 11 Apr 2022 09:10:36 -0500 Subject: [PATCH 4/6] Moved link flags workaround out of session, into create_aot_shared --- python/tvm/contrib/hexagon/session.py | 18 +----------------- python/tvm/contrib/hexagon/tools.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/python/tvm/contrib/hexagon/session.py b/python/tvm/contrib/hexagon/session.py index 1963159955b2..dca9b78fa067 100644 --- a/python/tvm/contrib/hexagon/session.py +++ b/python/tvm/contrib/hexagon/session.py @@ -235,16 +235,6 @@ def get_aot_executor( self.device is not None ), "Hexagon session must be started using __enter__ prior to use" - def _workaround_create_aot_shared(): - # The C codegen uses TVM/RT functions directly. On Hexagon it should use - # functions pointers via __TVMxyz variables. This workaround makes the - # runtime symbols visible to the compiled shared library. - extra_link_flags = os.environ.get("HEXAGON_SHARED_LINK_FLAGS") - extra_options = str(extra_link_flags).split() if extra_link_flags else [] - return lambda so_name, files, hexagon_arch, options: hexagon.create_aot_shared( - so_name, files, hexagon_arch, options=extra_options + options - ) - if isinstance(module, ExecutorFactoryModule): hexagon_arch = set( target.mcpu.replace("hexagon", "") @@ -262,15 +252,9 @@ def _workaround_create_aot_shared(): binary_name = "test_binary.so" binary_path = temp_dir / binary_name - # Uncomment this once the workaround is not needed. - # module.export_library( - # str(binary_path), - # fcompile=hexagon.create_aot_shared, - # hexagon_arch=hexagon_arch, - # ) module.export_library( str(binary_path), - fcompile=_workaround_create_aot_shared(), + fcompile=hexagon.create_aot_shared, hexagon_arch=hexagon_arch, ) diff --git a/python/tvm/contrib/hexagon/tools.py b/python/tvm/contrib/hexagon/tools.py index 5e241a990fe2..edf2821d3136 100644 --- a/python/tvm/contrib/hexagon/tools.py +++ b/python/tvm/contrib/hexagon/tools.py @@ -160,6 +160,19 @@ def create_aot_shared(so_name: Union[str, pathlib.Path], files, hexagon_arch: st + "HEXAGON_SDK_PATH in your environment." ) + # The AOT C codegen uses TVM runtime functions + # (e.g. TVMBackendAllocWorkspace) directly. On Hexagon these calls + # should be made using functions pointers provided as __TVM* + # variables in the provided context. This workaround allows the + # the TVM runtime symbols to be visible to the compiled shared + # library. + # + # This workaround can be removed when AOT codegen can be done with + # LLVM codegen. + workaround_link_flags = os.environ.get("HEXAGON_SHARED_LINK_FLAGS") + if workaround_link_flags: + options.extend(workaround_link_flags.split()) + tvm_dir = pathlib.Path(os.path.dirname(os.path.realpath(__file__))) / ".." / ".." / ".." / ".." compute_arch = f"compute{hexagon_arch}" compile_options = [ From cbcfa556023f9282866d92e74f8cfc632acfb549 Mon Sep 17 00:00:00 2001 From: Eric Lunderberg Date: Mon, 11 Apr 2022 09:31:55 -0500 Subject: [PATCH 5/6] Separated Session.get_*_executor and Session.get_executor_from_factory --- python/tvm/contrib/hexagon/build.py | 3 +- python/tvm/contrib/hexagon/session.py | 189 ++++++++++-------- .../contrib/test_hexagon/test_launcher.py | 8 +- 3 files changed, 110 insertions(+), 90 deletions(-) diff --git a/python/tvm/contrib/hexagon/build.py b/python/tvm/contrib/hexagon/build.py index a40903b822ba..16d3a30fd643 100644 --- a/python/tvm/contrib/hexagon/build.py +++ b/python/tvm/contrib/hexagon/build.py @@ -266,8 +266,7 @@ def get_aot_executor(self, module_name: Union[str, pathlib.Path], session: Sessi aot_module : AotModule Runtime AOT module that can be used to execute. """ - aot_mod = self.load_module(module_name, session) - return tvm.runtime.executor.AotModule(aot_mod["default"](session.device)) + return session.get_aot_executor(module_name) class HexagonLauncherAndroid(HexagonLauncherRPC): diff --git a/python/tvm/contrib/hexagon/session.py b/python/tvm/contrib/hexagon/session.py index dca9b78fa067..24f4666e7c22 100644 --- a/python/tvm/contrib/hexagon/session.py +++ b/python/tvm/contrib/hexagon/session.py @@ -20,7 +20,7 @@ import os import pathlib import tempfile -from typing import Union, Optional +from typing import Union import tvm from tvm import rpc as _rpc @@ -107,6 +107,9 @@ def upload(self, local_path: Union[str, pathlib.Path], remote_filename: str): def load_module(self, module: Union[str, pathlib.Path, tvm.runtime.Module]): """Load TVM module. + The session must be established (via __enter__) prior to + calling this function. + Parameters ---------- module : Union[str, pathlib.Path, tvm.runtime.Module] @@ -121,16 +124,16 @@ def load_module(self, module: Union[str, pathlib.Path, tvm.runtime.Module]): the file must already have been uploaded to the remote, and be placed in the remote workspace. - session : Session - - Remote session. The session must be established (via __enter__) - prior to calling this function. - Returns ------- TVMModule : TVM module object. """ + + assert ( + self.device is not None + ), "Hexagon session must be started using __enter__ prior to use" + if isinstance(module, tvm.runtime.Module): with tempfile.TemporaryDirectory() as temp_dir: temp_dir = pathlib.Path(temp_dir) @@ -145,8 +148,8 @@ def load_module(self, module: Union[str, pathlib.Path, tvm.runtime.Module]): def get_graph_executor( self, - module: Union[str, pathlib.Path, GraphExecutorFactoryModule], - graph_json: Optional[str] = None, + graph_json: str, + module_name: Union[str, pathlib.Path], ): """Create a local GraphModule which consumes a remote libmod. @@ -156,25 +159,14 @@ def get_graph_executor( Parameters ---------- - module : Union[str, pathlib.Path, GraphExecutorFactoryModule] + module_name : Union[str, pathlib.Path] - The module to load. If `module` is a - `GraphExecutorFactoryModule`, it will be uploaded to the remote - session and loaded. In this case, `graph_json` should be - None. - - If `module` is a string or `pathlib.Path`, it must be - either a bare file name (without any path components), or - a full path in the remote system. In this case, the file - must already have been uploaded to the remote and placed - in the remote workspace, and `graph_json` must be - provided. + The remote module filename, following the same restrictions + as `load_module`. - graph_json : Optional[str] + graph_json : str - The string with the graph JSON. This should be passed if - and only if `module` is a file path to a binary that has - been uploaded to the remote. + The string with the graph JSON. Returns ------- @@ -183,25 +175,12 @@ def get_graph_executor( """ - assert ( - self.device is not None - ), "Hexagon session must be started using __enter__ prior to use" - - if isinstance(module, GraphExecutorFactoryModule): - assert graph_json is None, "May not specify graph_json if full module is provided" - graph_json = module.get_graph_json() - graph_mod = self.load_module(module.get_lib()) - else: - assert ( - graph_json is not None - ), "Must specify graph_json if binary module has already been uploaded" - graph_mod = self.load_module(module) - + graph_mod = self.load_module(module_name) return tvm.contrib.graph_executor.create(graph_json, graph_mod, self.device) def get_aot_executor( self, - module: Union[str, pathlib.Path, AOTExecutorFactoryModule], + module_name: Union[str, pathlib.Path], ): """Create a local GraphModule which consumes a remote libmod. @@ -211,18 +190,10 @@ def get_aot_executor( Parameters ---------- - module : Union[str, pathlib.Path, AOTExecutorFactoryModule] + module_name : Union[str, pathlib.Path] - The module to load. If `module` is a - `AOTExecutorFactoryModule`, it will be uploaded to the remote - session and loaded. - - If `module` is a string or `pathlib.Path`, it must be - either a bare file name (without any path components), or - a full path in the remote system. In this case, the file - must already have been uploaded to the remote and placed - in the remote workspace, and `graph_json` must be - provided. + The remote module filename, following the same restrictions + as `load_module`. Returns ------- @@ -231,40 +202,10 @@ def get_aot_executor( """ - assert ( - self.device is not None - ), "Hexagon session must be started using __enter__ prior to use" - - if isinstance(module, ExecutorFactoryModule): - hexagon_arch = set( - target.mcpu.replace("hexagon", "") - for target in module.target.values() - if "hexagon" in target.keys - ) - assert hexagon_arch, "No hexagon target architecture found" - assert ( - len(hexagon_arch) == 1 - ), f"Inconsistent hexagon architecture found, {hexagon_arch}" - hexagon_arch = hexagon_arch.pop() - - with tempfile.TemporaryDirectory() as temp_dir: - temp_dir = pathlib.Path(temp_dir) - binary_name = "test_binary.so" - binary_path = temp_dir / binary_name - - module.export_library( - str(binary_path), - fcompile=hexagon.create_aot_shared, - hexagon_arch=hexagon_arch, - ) - - self.upload(binary_path, binary_name) - module = binary_name - - aot_mod = self.load_module(module) + aot_mod = self.load_module(module_name) return tvm.runtime.executor.AotModule(aot_mod["default"](self.device)) - def get_executor(self, module: ExecutorFactoryModule): + def get_executor_from_factory(self, module: ExecutorFactoryModule): """Create a local GraphModule which consumes a remote libmod. Parameters @@ -276,8 +217,88 @@ def get_executor(self, module: ExecutorFactoryModule): session and load. """ if isinstance(module, AOTExecutorFactoryModule): - return self.get_aot_executor(module) + return self._get_aot_executor_from_factory(module) if isinstance(module, GraphExecutorFactoryModule): - return self.get_graph_executor(module) + return self._get_graph_executor_from_factory(module) raise TypeError(f"Unsupported executor type: {type(module)}") + + def _get_graph_executor_from_factory( + self, + module: Union[str, pathlib.Path, GraphExecutorFactoryModule], + ): + """Create a local GraphModule which consumes a remote libmod. + + The session must be established (via __enter__) prior to + calling this function. + + Parameters + ---------- + + module : GraphExecutorFactoryModule + + The graph executor module to upload to the remote and load. + This will typically be the output of `tvm.relay.build`, + when passing `executor=Executor("graph")`. + + Returns + ------- + GraphModule : + Runtime graph module that can be used to execute the graph. + + """ + + graph_json = module.get_graph_json() + graph_mod = self.load_module(module.get_lib()) + + return tvm.contrib.graph_executor.create(graph_json, graph_mod, self.device) + + def _get_aot_executor_from_factory( + self, + module: Union[str, pathlib.Path, AOTExecutorFactoryModule], + ): + """Create a local GraphModule which consumes a remote libmod. + + The session must be established (via __enter__) prior to + calling this function. + + Parameters + ---------- + + module : AOTExecutorFactoryModule + + The graph executor module to upload to the remote and load. + This will typically be the output of `tvm.relay.build`, + when passing `executor=Executor("aot")`. + + Returns + ------- + GraphModule : + Runtime graph module that can be used to execute the graph. + + """ + + hexagon_arch = set( + target.mcpu.replace("hexagon", "") + for target in module.target.values() + if "hexagon" in target.keys + ) + assert hexagon_arch, "No hexagon target architecture found" + assert len(hexagon_arch) == 1, f"Inconsistent hexagon architecture found, {hexagon_arch}" + hexagon_arch = hexagon_arch.pop() + + with tempfile.TemporaryDirectory() as temp_dir: + temp_dir = pathlib.Path(temp_dir) + binary_name = "test_binary.so" + binary_path = temp_dir / binary_name + + module.export_library( + str(binary_path), + fcompile=hexagon.create_aot_shared, + hexagon_arch=hexagon_arch, + ) + + self.upload(binary_path, binary_name) + + aot_mod = self.load_module(binary_name) + return tvm.runtime.executor.AotModule(aot_mod["default"](self.device)) diff --git a/tests/python/contrib/test_hexagon/test_launcher.py b/tests/python/contrib/test_hexagon/test_launcher.py index 8666e3e1fc89..ec892c1c7a9d 100644 --- a/tests/python/contrib/test_hexagon/test_launcher.py +++ b/tests/python/contrib/test_hexagon/test_launcher.py @@ -181,7 +181,7 @@ def test_graph_executor(hexagon_session): if hexagon_session is None: pytest.skip(msg="Skip hardware test since ANDROID_SERIAL_NUMBER is not set.") - graph_mod = hexagon_session.get_executor(lowered) + graph_mod = hexagon_session.get_executor_from_factory(lowered) graph_mod.set_input(**params) graph_mod.run(**inputs) hexagon_output = graph_mod.get_output(0).numpy() @@ -261,7 +261,7 @@ def test_graph_executor_multiple_conv2d(hexagon_session): params = {"weight1": weight1_data, "weight2": weight2_data} inputs = {"data": input_data} - graph_mod = hexagon_session.get_executor(lowered) + graph_mod = hexagon_session.get_executor_from_factory(lowered) graph_mod.set_input(**params) graph_mod.run(**inputs) hexagon_output = graph_mod.get_output(0).numpy() @@ -335,7 +335,7 @@ def test_aot_executor(hexagon_session): if hexagon_session is None: pytest.skip(msg="Skip hardware test, ANDROID_SERIAL_NUMBER is not set.") - aot_mod = hexagon_session.get_executor(lowered) + aot_mod = hexagon_session.get_executor_from_factory(lowered) aot_mod.set_input(**inputs) aot_mod.run() hexagon_output = aot_mod.get_output(0).numpy() @@ -415,7 +415,7 @@ def test_aot_executor_multiple_conv2d(hexagon_session): if hexagon_session is None: pytest.skip(msg="Skip hardware test, ANDROID_SERIAL_NUMBER is not set.") - aot_mod = hexagon_session.get_executor(lowered) + aot_mod = hexagon_session.get_executor_from_factory(lowered) aot_mod.set_input(**inputs) aot_mod.run() hexagon_output = aot_mod.get_output(0).numpy() From 8bdc59f0080a996f92e840809602ecbfa0b5dc40 Mon Sep 17 00:00:00 2001 From: Eric Lunderberg Date: Mon, 11 Apr 2022 14:33:42 -0500 Subject: [PATCH 6/6] Updated to resolve lint error --- python/tvm/contrib/hexagon/session.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/tvm/contrib/hexagon/session.py b/python/tvm/contrib/hexagon/session.py index 24f4666e7c22..783e1cd3a014 100644 --- a/python/tvm/contrib/hexagon/session.py +++ b/python/tvm/contrib/hexagon/session.py @@ -217,13 +217,13 @@ def get_executor_from_factory(self, module: ExecutorFactoryModule): session and load. """ if isinstance(module, AOTExecutorFactoryModule): - return self._get_aot_executor_from_factory(module) + return self._aot_executor_from_factory(module) if isinstance(module, GraphExecutorFactoryModule): - return self._get_graph_executor_from_factory(module) + return self._graph_executor_from_factory(module) raise TypeError(f"Unsupported executor type: {type(module)}") - def _get_graph_executor_from_factory( + def _graph_executor_from_factory( self, module: Union[str, pathlib.Path, GraphExecutorFactoryModule], ): @@ -253,7 +253,7 @@ def _get_graph_executor_from_factory( return tvm.contrib.graph_executor.create(graph_json, graph_mod, self.device) - def _get_aot_executor_from_factory( + def _aot_executor_from_factory( self, module: Union[str, pathlib.Path, AOTExecutorFactoryModule], ):