This is an automated email from the ASF dual-hosted git repository.
kparzysz pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new dc522a6ff6 [Hexagon] Run single RPC server on Android in each testing
session (#11547)
dc522a6ff6 is described below
commit dc522a6ff65b68532cd1bba43827cd981114df2c
Author: Mehrdad Hessar <[email protected]>
AuthorDate: Fri Jun 10 14:33:24 2022 -0700
[Hexagon] Run single RPC server on Android in each testing session (#11547)
* Reuse hexagon launcher in test session
* separate random name generation
* revert get_aot_executor
* Fix launcher for simulator case
* add stop server for simulator
---
python/tvm/contrib/hexagon/build.py | 158 +++++++++++----------
python/tvm/contrib/hexagon/pytest_plugin.py | 66 +++++++--
python/tvm/contrib/hexagon/session.py | 90 +++++++-----
tests/python/contrib/test_hexagon/test_launcher.py | 2 -
4 files changed, 195 insertions(+), 121 deletions(-)
diff --git a/python/tvm/contrib/hexagon/build.py
b/python/tvm/contrib/hexagon/build.py
index c659d66bec..7e29f645ce 100644
--- a/python/tvm/contrib/hexagon/build.py
+++ b/python/tvm/contrib/hexagon/build.py
@@ -28,6 +28,7 @@ import stat
import random
import string
import subprocess
+import tempfile
from typing import Union
import tvm
@@ -36,6 +37,7 @@ from .session import Session
HEXAGON_RPC_LIB_DIR = os.environ.get("HEXAGON_RPC_LIB_DIR")
+ANDROID_BASH_FILE_NAME = "android_bash.sh"
def _get_hexagon_rpc_lib_dir() -> pathlib.Path:
@@ -116,7 +118,6 @@ class HexagonLauncherRPC(metaclass=abc.ABCMeta):
self._rpc_info.update(rpc_info)
self._workspace = self._create_workspace(workspace)
self._device_key = self.HEXAGON_REMOTE_DEVICE_KEY
- self._serial_number = None
@abc.abstractmethod
def start_server(self):
@@ -128,6 +129,11 @@ class HexagonLauncherRPC(metaclass=abc.ABCMeta):
"""Stop the RPC server"""
...
+ @abc.abstractmethod
+ def cleanup_directory(self):
+ """Cleanup working directory"""
+ ...
+
@abc.abstractmethod
def _copy_to_remote(
self, local_path: Union[str, pathlib.Path], remote_path: Union[str,
pathlib.Path]
@@ -144,13 +150,18 @@ class HexagonLauncherRPC(metaclass=abc.ABCMeta):
...
@abc.abstractmethod
- def _create_remote_directory(self, remote_path: Union[str, pathlib.Path]):
+ def _create_remote_directory(self, remote_path: Union[str, pathlib.Path])
-> pathlib.Path:
"""Create a directory in the remote location.
Parameters
----------
remote_path : str or pathlib.Path
Name of the directory to be created.
+
+ Returns
+ -------
+ pathlib.Path :
+ Absolute path of the remote workspace.
"""
...
@@ -171,10 +182,9 @@ class HexagonLauncherRPC(metaclass=abc.ABCMeta):
if not workspace:
base_dir = self._rpc_info["workspace_base"]
workspace = os.path.join(base_dir, _get_test_directory_name())
- self._create_remote_directory(workspace)
- return pathlib.Path(workspace)
+ return self._create_remote_directory(workspace)
- def upload(self, local_path: Union[str, pathlib.Path], remote_filename:
str):
+ def upload(self, local_path: Union[str, pathlib.Path], remote_filename:
str) -> pathlib.Path:
"""Upload a local file to the remote workspace.
Parameters
@@ -183,9 +193,16 @@ class HexagonLauncherRPC(metaclass=abc.ABCMeta):
Path to the local file to be copied.
remote_filename : str
Name of the file in the remote workspace.
+
+ Returns
+ -------
+ pathlib.Path :
+ Uploaded file remote path.
"""
assert self._workspace
- self._copy_to_remote(local_path, os.path.join(str(self._workspace),
remote_filename))
+ remote_file_path = self._workspace / remote_filename
+ self._copy_to_remote(local_path, str(remote_file_path))
+ return remote_file_path
def start_session(self, session_name: str = "hexagon-rpc") -> Session:
"""Connect to the RPC server.
@@ -221,10 +238,7 @@ class HexagonLauncherRPC(metaclass=abc.ABCMeta):
session and loaded.
If the object passed 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. If it is a file name,
- the file must already have been uploaded to the remote,
- and be placed in the remote workspace.
+ be a full path in the remote system.
session : Session
@@ -240,7 +254,10 @@ class HexagonLauncherRPC(metaclass=abc.ABCMeta):
return session.load_module(module)
def get_graph_executor(
- self, graph_json: str, module_name: Union[str, pathlib.Path], session:
Session
+ self,
+ graph_json: str,
+ module: Union[str, pathlib.Path, tvm.runtime.Module],
+ session: Session,
):
"""Create a local GraphModule which consumes a remote libmod.
@@ -248,8 +265,14 @@ class HexagonLauncherRPC(metaclass=abc.ABCMeta):
----------
graph_json : str
The string with the graph JSON.
- module_name : str or pathlib.Path
- Remote module filename. Same restrictions apply as in
load_module().
+ module : Union[str, pathlib.Path, tvm.runtime.Module]
+
+ The module to load. If `module` is a
+ `tvm.runtime.Module`, it will be uploaded to the remote
+ session and loaded.
+
+ If the object passed is a string or pathlib.Path, it must
+ be a full path in the remote system.
session : Session
Remote session. The session must be established (via __enter__)
prior to calling this function.
@@ -259,13 +282,12 @@ class HexagonLauncherRPC(metaclass=abc.ABCMeta):
GraphModule :
Runtime graph module that can be used to execute the graph.
"""
- graph_mod = self.load_module(module_name, session)
- return tvm.contrib.graph_executor.create(graph_json, graph_mod,
session.device)
+ return session.get_graph_executor(graph_json, module)
def get_graph_debug_executor(
self,
graph_json: str,
- module_name: Union[str, pathlib.Path],
+ module: Union[str, pathlib.Path, tvm.runtime.Module],
session: Session,
dump_root: Union[str, pathlib.Path] = None,
):
@@ -275,39 +297,24 @@ class HexagonLauncherRPC(metaclass=abc.ABCMeta):
----------
graph_json : str
The string with the graph JSON.
- module_name : str or pathlib.Path
- Remote module filename. Same restrictions apply as in
load_module().
- session : Session
- Remote session. The session must be established (via __enter__)
- prior to calling this function.
-
- Returns
- -------
- GraphModuleDebug :
- Runtime debug graph module that can be used to debug the graph.
- """
- graph_mod = self.load_module(module_name, session)
- return tvm.contrib.debugger.debug_executor.create(
- graph_json, graph_mod, session.device, dump_root=str(dump_root)
- )
+ module : Union[str, pathlib.Path, tvm.runtime.Module]
- def get_aot_executor(self, module_name: Union[str, pathlib.Path], session:
Session):
- """Create a local AoTModule which consumes a remote libmod.
+ The module to load. If `module` is a
+ `tvm.runtime.Module`, it will be uploaded to the remote
+ session and loaded.
- Parameters
- ----------
- module_name : str or pathlib.Path
- Remote module filename. Same restrictions apply as in
load_module().
+ If the object passed is a string or pathlib.Path, it must
+ be a full path in the remote system.
session : Session
Remote session. The session must be established (via __enter__)
prior to calling this function.
Returns
-------
- aot_module : AotModule
- Runtime AOT module that can be used to execute.
+ GraphModuleDebug :
+ Runtime debug graph module that can be used to debug the graph.
"""
- return session.get_aot_executor(module_name)
+ return session.get_graph_debug_executor(graph_json, module,
dump_root=dump_root)
class HexagonLauncherAndroid(HexagonLauncherRPC):
@@ -315,7 +322,6 @@ class HexagonLauncherAndroid(HexagonLauncherRPC):
ANDROID_HEXAGON_TEST_BASE_DIR =
pathlib.Path("/data/local/tmp/hexagon_test")
ANDROID_HEXAGON_RPC_FILES = [
- "android_bash.sh",
"libhexagon_rpc_skel.so",
"libtvm_runtime.so",
"tvm_rpc_android",
@@ -354,39 +360,42 @@ class HexagonLauncherAndroid(HexagonLauncherRPC):
self._adb_device_sub_cmd + ["push", str(local_path),
str(remote_path)]
)
- def _create_remote_directory(self, remote_path: Union[str, pathlib.Path]):
+ def _create_remote_directory(self, remote_path: Union[str, pathlib.Path])
-> pathlib.Path:
"""Abstract method implementation. See description in
HexagonLauncherRPC."""
subprocess.check_call(self._adb_device_sub_cmd + ["shell", "mkdir",
"-p", str(remote_path)])
+ return pathlib.Path(remote_path)
def _copy_binaries(self):
"""Upload Android server binaries."""
# Create bash script
- android_bash_script_path = _get_hexagon_rpc_lib_dir() /
"android_bash.sh"
- with open(_get_hexagon_rpc_lib_dir() / "android_bash.sh.template",
"r") as src_f:
- if os.path.exists(android_bash_script_path):
- os.remove(android_bash_script_path)
- with open(android_bash_script_path, "w") as dest_f:
- for line in src_f.readlines():
- if "<RPC_TRACKER_HOST>" in line:
- line = line.replace(
- "<RPC_TRACKER_HOST>",
str(self._rpc_info["rpc_tracker_host"])
- )
- if "<RPC_TRACKER_PORT>" in line:
- line = line.replace(
- "<RPC_TRACKER_PORT>",
str(self._rpc_info["rpc_tracker_port"])
- )
- if "<HEXAGON_REMOTE_DEVICE_KEY>" in line:
- line = line.replace("<HEXAGON_REMOTE_DEVICE_KEY>",
self._device_key)
- if "<RPC_SERVER_PORT>" in line:
- line = line.replace(
- "<RPC_SERVER_PORT>",
str(self._rpc_info["rpc_server_port"])
- )
- dest_f.write(line)
-
- # Make shell script executable
- android_bash_stat = os.stat(android_bash_script_path)
- os.chmod(android_bash_script_path, android_bash_stat.st_mode |
stat.S_IEXEC)
+ with open(_get_hexagon_rpc_lib_dir() /
f"{ANDROID_BASH_FILE_NAME}.template", "r") as src_f:
+ with tempfile.TemporaryDirectory() as temp_dir:
+ android_bash_script_path = pathlib.Path(temp_dir) /
ANDROID_BASH_FILE_NAME
+ with open(android_bash_script_path, "w") as dest_f:
+ for line in src_f.readlines():
+ if "<RPC_TRACKER_HOST>" in line:
+ line = line.replace(
+ "<RPC_TRACKER_HOST>",
str(self._rpc_info["rpc_tracker_host"])
+ )
+ if "<RPC_TRACKER_PORT>" in line:
+ line = line.replace(
+ "<RPC_TRACKER_PORT>",
str(self._rpc_info["rpc_tracker_port"])
+ )
+ if "<HEXAGON_REMOTE_DEVICE_KEY>" in line:
+ line = line.replace("<HEXAGON_REMOTE_DEVICE_KEY>",
self._device_key)
+ if "<RPC_SERVER_PORT>" in line:
+ line = line.replace(
+ "<RPC_SERVER_PORT>",
str(self._rpc_info["rpc_server_port"])
+ )
+ dest_f.write(line)
+
+ # Make shell script executable
+ android_bash_stat = os.stat(android_bash_script_path)
+ os.chmod(android_bash_script_path, android_bash_stat.st_mode |
stat.S_IEXEC)
+ self._copy_to_remote(
+ android_bash_script_path, self._workspace /
android_bash_script_path.name
+ )
# Push files
lib_dir = _get_hexagon_rpc_lib_dir()
@@ -436,7 +445,8 @@ class HexagonLauncherAndroid(HexagonLauncherRPC):
# Run server and connect to tracker
subprocess.Popen(
- self._adb_device_sub_cmd + ["shell", f"cd {self._workspace} &&
./android_bash.sh"],
+ self._adb_device_sub_cmd
+ + ["shell", f"cd {self._workspace} && ./{ANDROID_BASH_FILE_NAME}"],
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
@@ -472,8 +482,8 @@ class HexagonLauncherAndroid(HexagonLauncherRPC):
self._adb_device_sub_cmd + ["shell", f"kill `cat
{self._workspace}/rpc_pid.txt`"]
)
- def _cleanup_directory(self):
- # Remove workspace directory on remote target
+ def cleanup_directory(self):
+ """Abstract method implementation. See description in
HexagonLauncherRPC."""
subprocess.Popen(self._adb_device_sub_cmd + ["shell", f"rm -rf
{self._workspace}"])
def start_server(self):
@@ -485,7 +495,7 @@ class HexagonLauncherAndroid(HexagonLauncherRPC):
"""Abstract method implementation. See description in
HexagonLauncherRPC."""
self._cleanup_port_forwarding()
self._terminate_remote()
- self._cleanup_directory()
+ self.cleanup_directory()
class HexagonLauncherSimulator(HexagonLauncherRPC):
@@ -511,9 +521,10 @@ class HexagonLauncherSimulator(HexagonLauncherRPC):
"""Abstract method implementation. See description in
HexagonLauncherRPC."""
subprocess.check_call(["cp", str(local_path), str(remote_path)])
- def _create_remote_directory(self, remote_path: Union[str, pathlib.Path]):
+ def _create_remote_directory(self, remote_path: Union[str, pathlib.Path])
-> pathlib.Path:
"""Abstract method implementation. See description in
HexagonLauncherRPC."""
subprocess.check_call(["mkdir", "-p", str(remote_path)])
+ return pathlib.Path(os.path.abspath(remote_path))
def _copy_libcxx(self, dest_dir: Union[str, pathlib.Path]):
"""Copy libc++ libraries to the remote workspace."""
@@ -585,6 +596,9 @@ class HexagonLauncherSimulator(HexagonLauncherRPC):
self._server_process = mp.Process(target=lambda *a: _start(self, *a))
self._server_process.start()
+ def cleanup_directory(self):
+ """Abstract method implementation. See description in
HexagonLauncherRPC."""
+
def stop_server(self):
"""Abstract method implementation. See description in
HexagonLauncherRPC."""
self._server_process.terminate()
diff --git a/python/tvm/contrib/hexagon/pytest_plugin.py
b/python/tvm/contrib/hexagon/pytest_plugin.py
index 278bd833da..1841c654b9 100644
--- a/python/tvm/contrib/hexagon/pytest_plugin.py
+++ b/python/tvm/contrib/hexagon/pytest_plugin.py
@@ -56,7 +56,7 @@ def _compose(args, decs):
requires_hexagon_toolchain =
tvm.testing.requires_hexagon(support_required="compile-only")
[email protected]
[email protected](scope="session")
def android_serial_number() -> Optional[str]:
serial = os.getenv(ANDROID_SERIAL_NUMBER, default="")
# Setting ANDROID_SERIAL_NUMBER to an empty string should be
@@ -138,22 +138,29 @@ def tvm_tracker_port(_tracker_info) -> int:
return port
[email protected]
[email protected](scope="session")
+def rpc_server_port_for_session() -> int:
+ return get_free_port()
+
+
[email protected]()
def rpc_server_port() -> int:
return get_free_port()
[email protected]
[email protected](scope="session")
def adb_server_socket() -> str:
return os.getenv(ADB_SERVER_SOCKET, default="tcp:5037")
[email protected]
-def hexagon_launcher(
- request, android_serial_number, rpc_server_port, adb_server_socket
[email protected](scope="session")
+def hexagon_server_process(
+ request, android_serial_number, rpc_server_port_for_session,
adb_server_socket
) -> HexagonLauncherRPC:
- """Initials and returns hexagon launcher if ANDROID_SERIAL_NUMBER is
defined"""
- if android_serial_number is None:
+ """Initials and returns hexagon launcher if ANDROID_SERIAL_NUMBER is
defined.
+ This launcher is started only once per test session.
+ """
+ if android_serial_number is None or android_serial_number == "simulator":
yield None
else:
# Requesting these fixtures sets up a local tracker, if one
@@ -165,19 +172,54 @@ def hexagon_launcher(
rpc_info = {
"rpc_tracker_host": tvm_tracker_host,
"rpc_tracker_port": tvm_tracker_port,
- "rpc_server_port": rpc_server_port,
+ "rpc_server_port": rpc_server_port_for_session,
"adb_server_socket": adb_server_socket,
}
launcher = HexagonLauncher(serial_number=android_serial_number,
rpc_info=rpc_info)
- launcher.start_server()
+
try:
+ launcher.start_server()
yield launcher
finally:
launcher.stop_server()
[email protected]
-def hexagon_session(hexagon_launcher) -> Session:
[email protected]
+def hexagon_launcher(
+ hexagon_server_process,
+ rpc_server_port,
+ tvm_tracker_host,
+ tvm_tracker_port,
+ adb_server_socket,
+ android_serial_number,
+) -> HexagonLauncherRPC:
+ """Initials and returns hexagon launcher which reuses RPC info and Android
serial number."""
+ if android_serial_number is None:
+ yield None
+
+ if android_serial_number != "simulator":
+ rpc_info = hexagon_server_process._rpc_info
+ else:
+ rpc_info = {
+ "rpc_tracker_host": tvm_tracker_host,
+ "rpc_tracker_port": tvm_tracker_port,
+ "rpc_server_port": rpc_server_port,
+ "adb_server_socket": adb_server_socket,
+ }
+
+ launcher = HexagonLauncher(serial_number=android_serial_number,
rpc_info=rpc_info)
+ try:
+ if android_serial_number == "simulator":
+ launcher.start_server()
+ yield launcher
+ finally:
+ if android_serial_number == "simulator":
+ launcher.stop_server()
+ launcher.cleanup_directory()
+
+
[email protected]
+def hexagon_session(hexagon_launcher: HexagonLauncherRPC) -> Session:
if hexagon_launcher is None:
yield None
else:
diff --git a/python/tvm/contrib/hexagon/session.py
b/python/tvm/contrib/hexagon/session.py
index f30fe6e470..0c0bf296df 100644
--- a/python/tvm/contrib/hexagon/session.py
+++ b/python/tvm/contrib/hexagon/session.py
@@ -93,7 +93,8 @@ class Session:
raise exception
def __exit__(self, exc_type, exc_value, exc_traceback):
- pass
+ # close session to the tracker
+ del self._rpc
@property
def device(self):
@@ -109,7 +110,7 @@ class Session:
return self._device
- def upload(self, local_path: Union[str, pathlib.Path], remote_filename:
str):
+ def upload(self, local_path: Union[str, pathlib.Path], remote_filename:
str) -> pathlib.Path:
"""Upload a local file to the remote workspace.
Parameters
@@ -118,8 +119,13 @@ class Session:
Path to the local file to be copied.
remote_filename : str
Name of the file in the remote workspace.
+
+ Returns
+ -------
+ pathlib.Path :
+ Uploaded file remote path.
"""
- self._launcher.upload(local_path, remote_filename)
+ return self._launcher.upload(local_path, remote_filename)
def load_module(self, module: Union[str, pathlib.Path,
tvm.runtime.Module]):
"""Load TVM module.
@@ -136,10 +142,7 @@ class Session:
session and loaded.
If the object passed 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. If it is a file name,
- the file must already have been uploaded to the remote,
- and be placed in the remote workspace.
+ be a full path in the remote system.
Returns
-------
@@ -155,16 +158,19 @@ class Session:
binary_name = "test_binary.so"
binary_path = temp_dir / binary_name
module.save(str(binary_path))
- self.upload(binary_path, binary_name)
- module = binary_name
+ remote_file_path = self.upload(binary_path, binary_name)
+ else:
+ remote_file_path = module
- assert isinstance(module, (str, pathlib.Path)), "Invalid path type:" +
str(type(module))
- return self._rpc.get_function("tvm.hexagon.load_module")(str(module))
+ assert isinstance(remote_file_path, (str, pathlib.Path)), "Invalid
path type:" + str(
+ type(remote_file_path)
+ )
+ return
self._rpc.get_function("tvm.hexagon.load_module")(str(remote_file_path))
def get_graph_executor(
self,
graph_json: str,
- module_name: Union[str, pathlib.Path],
+ module_name: Union[str, pathlib.Path, tvm.runtime.Module],
):
"""Create a local GraphModule which consumes a remote libmod.
@@ -173,14 +179,10 @@ class Session:
Parameters
----------
-
- module_name : Union[str, pathlib.Path]
-
+ module_name : Union[str, pathlib.Path, tvm.runtime.Module]
The remote module filename, following the same restrictions
as `load_module`.
-
graph_json : str
-
The string with the graph JSON.
Returns
@@ -196,31 +198,54 @@ class Session:
def get_aot_executor(
self,
- module_name: Union[str, pathlib.Path],
+ module_file: Union[str, pathlib.Path],
):
"""Create a local GraphModule which consumes a remote libmod.
-
The session must be established (via __enter__) prior to
calling this function.
-
Parameters
----------
+ module_file : Union[str, pathlib.Path]
+ The remote module filename, following the same restrictions
+ as `load_module`. The filename should be an absolute path.
+ Returns
+ -------
+ GraphModule :
+ Runtime graph module that can be used to execute the graph.
+ """
+ aot_mod = self.load_module(module_file)
+ return tvm.runtime.executor.AotModule(aot_mod["default"](self.device))
- module_name : Union[str, pathlib.Path]
+ def get_graph_debug_executor(
+ self,
+ graph_json: str,
+ module_name: Union[str, pathlib.Path, tvm.runtime.Module],
+ dump_root: Union[str, pathlib.Path] = None,
+ ):
+ """Create a local GraphModuleDebug which consumes a remote libmod.
+ Parameters
+ ----------
+ graph_json : str
+ The string with the graph JSON.
+ module_name : Union[str, pathlib.Path, tvm.runtime.Module]
The remote module filename, following the same restrictions
as `load_module`.
+ session : Session
+ Remote session. The session must be established (via __enter__)
+ prior to calling this function.
Returns
-------
- GraphModule :
- Runtime graph module that can be used to execute the graph.
-
+ GraphModuleDebug :
+ Runtime debug graph module that can be used to debug the graph.
"""
- aot_mod = self.load_module(module_name)
- self._set_device_type(aot_mod)
- return tvm.runtime.executor.AotModule(aot_mod["default"](self.device))
+ graph_debug_mod = self.load_module(module_name)
+ self._set_device_type(graph_debug_mod)
+ return tvm.contrib.debugger.debug_executor.create(
+ graph_json, graph_debug_mod, self.device, dump_root=str(dump_root)
+ )
def get_executor_from_factory(self, module: ExecutorFactoryModule):
"""Create a local GraphModule which consumes a remote libmod.
@@ -286,11 +311,7 @@ class Session:
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)
+ return self.get_graph_executor(module.get_graph_json(),
module.get_lib())
def _aot_executor_from_factory(
self,
@@ -354,7 +375,6 @@ class Session:
f"Target kind should be from these options: [hexagon,
llvm]."
)
- self.upload(binary_path, binary_name)
+ remote_file_path = self.upload(binary_path, binary_name)
- aot_mod = self.load_module(binary_name)
- return tvm.runtime.executor.AotModule(aot_mod["default"](self.device))
+ return self.get_aot_executor(remote_file_path)
diff --git a/tests/python/contrib/test_hexagon/test_launcher.py
b/tests/python/contrib/test_hexagon/test_launcher.py
index ad798925ee..aae2e598f6 100644
--- a/tests/python/contrib/test_hexagon/test_launcher.py
+++ b/tests/python/contrib/test_hexagon/test_launcher.py
@@ -15,8 +15,6 @@
# specific language governing permissions and limitations
# under the License.
-import sys
-import pytest
import numpy as np
import tvm.testing