chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
# isort: skip_file
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""
|
||||
The tvm.s_tir.meta_schedule.runner package.
|
||||
Meta Schedule runners that runs an artifact either locally or through the RPC interface
|
||||
"""
|
||||
|
||||
from .config import EvaluatorConfig, RPCConfig
|
||||
from .local_runner import LocalRunner, LocalRunnerFuture
|
||||
from .rpc_runner import RPCRunner
|
||||
from .runner import (
|
||||
PyRunner,
|
||||
PyRunnerFuture,
|
||||
Runner,
|
||||
RunnerFuture,
|
||||
RunnerInput,
|
||||
RunnerResult,
|
||||
create,
|
||||
)
|
||||
@@ -0,0 +1,201 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""Configurations for measurements in the runner"""
|
||||
|
||||
import os
|
||||
from threading import Thread
|
||||
from typing import NamedTuple, Optional
|
||||
|
||||
from tvm import rpc
|
||||
|
||||
|
||||
class EvaluatorConfig(NamedTuple):
|
||||
"""Config Details of Evaluator
|
||||
|
||||
Parameters
|
||||
----------
|
||||
number: int
|
||||
The number of times to run this function for taking average.
|
||||
We call these runs as one `repeat` of measurement.
|
||||
repeat: int
|
||||
The number of times to repeat the measurement.
|
||||
In total, the function will be invoked (1 + number x repeat) times,
|
||||
where the first one is warm up and will be discarded.
|
||||
The returned result contains `repeat` costs,
|
||||
each of which is an average of `number` costs.
|
||||
min_repeat_ms: int
|
||||
Minimum repeat time in ms. if the execution latency is too short,
|
||||
increase the number of runs to the given time (in ms) to reduce the measurement error.
|
||||
enable_cpu_cache_flush: bool
|
||||
Whether to flush the cache on CPU.
|
||||
|
||||
Note
|
||||
----
|
||||
The total number of actual executions is 1+number*repeat because we would warm up 1 time before
|
||||
actual run. The number of runs would be increased if run time is below min_repeat_ms.
|
||||
"""
|
||||
|
||||
number: int = 3
|
||||
repeat: int = 1
|
||||
min_repeat_ms: int = 100
|
||||
enable_cpu_cache_flush: bool = False
|
||||
|
||||
@staticmethod
|
||||
def _normalized(config: Optional["EvaluatorConfig"]) -> "EvaluatorConfig":
|
||||
if config is None:
|
||||
return EvaluatorConfig()
|
||||
config = EvaluatorConfig(
|
||||
number=config.number,
|
||||
repeat=config.repeat,
|
||||
min_repeat_ms=config.min_repeat_ms,
|
||||
enable_cpu_cache_flush=config.enable_cpu_cache_flush,
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
class RPCConfig(NamedTuple):
|
||||
"""RPC configuration
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tracker_host: str
|
||||
Host of the RPC Tracker
|
||||
tracker_port: int
|
||||
Port of the RPC Tracker
|
||||
tracker_key: str
|
||||
Key of the Tracker
|
||||
session_timeout_sec: float
|
||||
Timeout of the RPC session
|
||||
session_priority: int
|
||||
Priority of the RPC session
|
||||
"""
|
||||
|
||||
tracker_host: str | None = None
|
||||
tracker_port: None | int | str = None
|
||||
tracker_key: str | None = None
|
||||
session_priority: int = 1
|
||||
session_timeout_sec: int = 10
|
||||
|
||||
def _sanity_check(self) -> None:
|
||||
err_str = (
|
||||
"RPCConfig.{0} is not provided. Please provide it explicitly,"
|
||||
"or set environment variable {1}"
|
||||
)
|
||||
if self.tracker_host is None:
|
||||
raise ValueError(err_str.format("tracker_host", "TVM_TRACKER_HOST"))
|
||||
if self.tracker_port is None:
|
||||
raise ValueError(err_str.format("tracker_port", "TVM_TRACKER_PORT"))
|
||||
if self.tracker_key is None:
|
||||
raise ValueError(err_str.format("tracker_key", "TVM_TRACKER_KEY"))
|
||||
|
||||
@staticmethod
|
||||
def _normalized(config: Optional["RPCConfig"]) -> "RPCConfig":
|
||||
if config is None:
|
||||
config = RPCConfig()
|
||||
tracker_host = config.tracker_host or os.environ.get("TVM_TRACKER_HOST", None)
|
||||
tracker_port = config.tracker_port or os.environ.get("TVM_TRACKER_PORT", None)
|
||||
tracker_key = config.tracker_key or os.environ.get("TVM_TRACKER_KEY", None)
|
||||
if isinstance(tracker_port, str):
|
||||
tracker_port = int(tracker_port)
|
||||
config = RPCConfig(
|
||||
tracker_host=tracker_host,
|
||||
tracker_port=tracker_port,
|
||||
tracker_key=tracker_key,
|
||||
session_priority=config.session_priority,
|
||||
session_timeout_sec=config.session_timeout_sec,
|
||||
)
|
||||
config._sanity_check() # pylint: disable=protected-access
|
||||
return config
|
||||
|
||||
def connect_tracker(self) -> rpc.TrackerSession:
|
||||
"""Connect to the tracker
|
||||
|
||||
Returns
|
||||
-------
|
||||
tracker : TrackerSession
|
||||
The connected tracker session
|
||||
"""
|
||||
tracker: rpc.TrackerSession | None = None
|
||||
|
||||
def _connect():
|
||||
nonlocal tracker
|
||||
tracker = rpc.connect_tracker(self.tracker_host, self.tracker_port)
|
||||
|
||||
t = Thread(target=_connect)
|
||||
t.start()
|
||||
t.join(self.session_timeout_sec)
|
||||
if t.is_alive() or tracker is None:
|
||||
raise ValueError(
|
||||
"Unable to connect to the tracker using the following configuration:\n"
|
||||
f" tracker host: {self.tracker_host}\n"
|
||||
f" tracker port: {self.tracker_port}\n"
|
||||
f" timeout (sec): {self.session_timeout_sec}\n"
|
||||
"Please check the tracker status via the following command:\n"
|
||||
" python3 -m tvm.exec.query_rpc_tracker "
|
||||
f"--host {self.tracker_host} --port {self.tracker_port}"
|
||||
)
|
||||
return tracker
|
||||
|
||||
def connect_server(self) -> rpc.RPCSession:
|
||||
"""Connect to the server
|
||||
|
||||
Returns
|
||||
-------
|
||||
session : RPCSession
|
||||
The connected rpc session
|
||||
"""
|
||||
tracker = self.connect_tracker()
|
||||
session: rpc.RPCSession = tracker.request(
|
||||
key=self.tracker_key,
|
||||
priority=self.session_priority,
|
||||
session_timeout=self.session_timeout_sec,
|
||||
)
|
||||
return session
|
||||
|
||||
def count_num_servers(self, allow_missing=True) -> int:
|
||||
"""Count the number of servers available in the tracker
|
||||
|
||||
Parameters
|
||||
----------
|
||||
allow_missing : bool
|
||||
Whether to allow no server to be found.
|
||||
|
||||
Returns
|
||||
-------
|
||||
num_servers : int
|
||||
The number of servers
|
||||
"""
|
||||
tracker = self.connect_tracker()
|
||||
tracker_summary = tracker.summary()
|
||||
result: int = 0
|
||||
for item in tracker_summary["server_info"]:
|
||||
_, item_key = item["key"].split(":")
|
||||
if item_key == self.tracker_key:
|
||||
result += 1
|
||||
if result == 0 and not allow_missing:
|
||||
raise ValueError(
|
||||
"Unable to find servers with the specific key using the following configuration:\n"
|
||||
f" tracker host: {self.tracker_host}\n"
|
||||
f" tracker port: {self.tracker_port}\n"
|
||||
f" tracker key: {self.tracker_key}\n"
|
||||
f" timeout (sec): {self.session_timeout_sec}\n"
|
||||
"Please check the tracker status via the following command:\n"
|
||||
" python3 -m tvm.exec.query_rpc_tracker "
|
||||
f"--host {self.tracker_host} --port {self.tracker_port}\n"
|
||||
f'and look for key: "{self.tracker_key}"'
|
||||
)
|
||||
return result
|
||||
@@ -0,0 +1,404 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""Local Runner"""
|
||||
|
||||
import logging
|
||||
import subprocess
|
||||
from collections.abc import Callable
|
||||
from contextlib import contextmanager
|
||||
|
||||
import tvm
|
||||
from tvm.ir.utils import derived_object
|
||||
from tvm.support.popen_pool import PopenPoolExecutor
|
||||
|
||||
from ....runtime import Device, Module
|
||||
from ..logging import get_logger
|
||||
from ..profiler import Profiler
|
||||
from ..utils import get_global_func_with_default_on_worker
|
||||
from .config import EvaluatorConfig
|
||||
from .runner import PyRunner, PyRunnerFuture, RunnerFuture, RunnerInput, RunnerResult
|
||||
from .utils import (
|
||||
T_ARG_INFO_JSON_OBJ_LIST,
|
||||
T_ARGUMENT_LIST,
|
||||
alloc_argument_common,
|
||||
run_evaluator_common,
|
||||
)
|
||||
|
||||
logger = get_logger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
T_ALLOC_ARGUMENT = Callable[ # pylint: disable=invalid-name
|
||||
[
|
||||
Device, # The device on the remote
|
||||
T_ARG_INFO_JSON_OBJ_LIST, # The metadata information of the arguments to be allocated
|
||||
int, # The number of repeated allocations to be done
|
||||
],
|
||||
list[T_ARGUMENT_LIST], # A list of argument lists
|
||||
]
|
||||
T_RUN_EVALUATOR = Callable[ # pylint: disable=invalid-name
|
||||
[
|
||||
Module, # The Module opened on the remote
|
||||
Device, # The device on the remote
|
||||
EvaluatorConfig, # The evaluator configuration
|
||||
list[T_ARGUMENT_LIST], # A list of argument lists
|
||||
],
|
||||
list[float], # A list of running time
|
||||
]
|
||||
T_CLEANUP = Callable[ # pylint: disable=invalid-name
|
||||
[],
|
||||
None,
|
||||
]
|
||||
|
||||
|
||||
@derived_object
|
||||
class LocalRunnerFuture(PyRunnerFuture):
|
||||
"""Local based runner future
|
||||
|
||||
Parameters
|
||||
----------
|
||||
res: Optional[List[float]]
|
||||
The optional result as a list of float.
|
||||
error_message: Optional[str]
|
||||
The optional error message.
|
||||
|
||||
Note
|
||||
----
|
||||
Only one of the parameters should be None upon the creation
|
||||
of LocalRunnerFuture object
|
||||
"""
|
||||
|
||||
res: list[float] | None
|
||||
error_message: str | None
|
||||
|
||||
def __init__(self, res: list[float] | None = None, error_message: str | None = None) -> None:
|
||||
"""Constructor
|
||||
|
||||
Parameters
|
||||
----------
|
||||
res: Optional[List[float]]
|
||||
The result of this LocalRunnerFuture
|
||||
error_message: Optional[str]
|
||||
The stringfied error message of any exception during execution
|
||||
|
||||
"""
|
||||
super().__init__()
|
||||
self.res = res
|
||||
self.error_message = error_message
|
||||
|
||||
# sanity check upon the creation of LocalRunnerFuture object
|
||||
if (res is None and error_message is None) or (
|
||||
res is not None and error_message is not None
|
||||
):
|
||||
raise AttributeError(
|
||||
"Only one of the two parameters should be None upon the creation"
|
||||
"of LocalRunnerFuture object."
|
||||
)
|
||||
|
||||
def done(self) -> bool:
|
||||
return True
|
||||
|
||||
def result(self) -> RunnerResult:
|
||||
return RunnerResult(self.res, self.error_message)
|
||||
|
||||
|
||||
def _worker_func(
|
||||
_f_alloc_argument: str | None,
|
||||
_f_run_evaluator: str | None,
|
||||
_f_cleanup: str | None,
|
||||
evaluator_config: EvaluatorConfig,
|
||||
alloc_repeat: int,
|
||||
artifact_path: str,
|
||||
device_type: str,
|
||||
args_info: T_ARG_INFO_JSON_OBJ_LIST,
|
||||
) -> list[float]:
|
||||
f_alloc_argument: T_ALLOC_ARGUMENT = get_global_func_with_default_on_worker(
|
||||
_f_alloc_argument, default_alloc_argument
|
||||
)
|
||||
f_run_evaluator: T_RUN_EVALUATOR = get_global_func_with_default_on_worker(
|
||||
_f_run_evaluator, default_run_evaluator
|
||||
)
|
||||
f_cleanup: T_CLEANUP = get_global_func_with_default_on_worker(_f_cleanup, default_cleanup)
|
||||
|
||||
@contextmanager
|
||||
def resource_handler():
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
# Final step. Always clean up
|
||||
with Profiler.timeit("LocalRunner/cleanup"):
|
||||
f_cleanup()
|
||||
|
||||
with resource_handler():
|
||||
# Step 1: create the local runtime module
|
||||
with Profiler.timeit("LocalRunner/load_module"):
|
||||
rt_mod = tvm.runtime.load_module(artifact_path)
|
||||
# Step 2: Allocate input arguments
|
||||
with Profiler.timeit("LocalRunner/alloc_argument"):
|
||||
device = tvm.runtime.device(device_type, 0)
|
||||
repeated_args: list[T_ARGUMENT_LIST] = f_alloc_argument(
|
||||
device,
|
||||
args_info,
|
||||
alloc_repeat,
|
||||
)
|
||||
# Step 3: Run time_evaluator
|
||||
with Profiler.timeit("LocalRunner/run_evaluator"):
|
||||
costs: list[float] = f_run_evaluator(
|
||||
rt_mod,
|
||||
device,
|
||||
evaluator_config,
|
||||
repeated_args,
|
||||
)
|
||||
return costs
|
||||
|
||||
|
||||
@derived_object
|
||||
class LocalRunner(PyRunner):
|
||||
"""Local runner
|
||||
|
||||
Parameters
|
||||
----------
|
||||
evaluator_config: EvaluatorConfig
|
||||
The evaluator configuration.
|
||||
cooldown_sec: float
|
||||
The cooldown in seconds.
|
||||
alloc_repeat: int
|
||||
The number of times to repeat the allocation.
|
||||
f_alloc_argument: Optional[str, Callable]
|
||||
The function name to allocate the arguments or the function itself.
|
||||
f_run_evaluator: Optional[str, Callable]
|
||||
The function name to run the evaluator or the function itself.
|
||||
f_cleanup: Optional[str, Callable]
|
||||
The function name to cleanup the session or the function itself.
|
||||
pool: PopenPoolExecutor
|
||||
The popen pool executor.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
T_ALLOC_ARGUMENT : typing._GenericAlias
|
||||
The signature of the function `f_alloc_argument`, which is:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def default_alloc_argument(
|
||||
device: Device,
|
||||
args_info: T_ARG_INFO_JSON_OBJ_LIST,
|
||||
alloc_repeat: int,
|
||||
) -> List[T_ARGUMENT_LIST]:
|
||||
...
|
||||
|
||||
T_RUN_EVALUATOR : typing._GenericAlias
|
||||
The signature of the function `f_run_evaluator`, which is:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def default_run_evaluator(
|
||||
rt_mod: Module,
|
||||
device: Device,
|
||||
evaluator_config: EvaluatorConfig,
|
||||
repeated_args: List[T_ARGUMENT_LIST],
|
||||
) -> List[float]:
|
||||
...
|
||||
|
||||
T_CLEANUP : typing._GenericAlias
|
||||
The signature of the function `f_cleanup`, which is:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def default_cleanup() -> None:
|
||||
...
|
||||
"""
|
||||
|
||||
timeout_sec: float
|
||||
evaluator_config: EvaluatorConfig
|
||||
cooldown_sec: float
|
||||
alloc_repeat: int
|
||||
|
||||
f_alloc_argument: T_ALLOC_ARGUMENT | str | None
|
||||
f_run_evaluator: T_RUN_EVALUATOR | str | None
|
||||
f_cleanup: T_CLEANUP | str | None
|
||||
|
||||
pool: PopenPoolExecutor
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
timeout_sec: float = 30,
|
||||
evaluator_config: EvaluatorConfig | None = None,
|
||||
cooldown_sec: float = 0.0,
|
||||
alloc_repeat: int = 1,
|
||||
f_alloc_argument: T_ALLOC_ARGUMENT | str | None = None,
|
||||
f_run_evaluator: T_RUN_EVALUATOR | str | None = None,
|
||||
f_cleanup: T_CLEANUP | str | None = None,
|
||||
initializer: Callable[[], None] | None = None,
|
||||
) -> None:
|
||||
"""Constructor
|
||||
|
||||
Parameters
|
||||
----------
|
||||
timeout_sec: float
|
||||
The timeout setting.
|
||||
evaluator_config: EvaluatorConfig
|
||||
The evaluator configuration.
|
||||
cooldown_sec: float
|
||||
The cooldown in seconds.
|
||||
alloc_repeat: int
|
||||
The number of times to random fill the allocation.
|
||||
f_alloc_argument: Union[T_ALLOC_ARGUMENT, str, None]
|
||||
The function name to allocate the arguments or the function itself.
|
||||
f_run_evaluator: Union[T_RUN_EVALUATOR, str, None]
|
||||
The function name to run the evaluator or the function itself.
|
||||
f_cleanup: Union[T_CLEANUP, str, None]
|
||||
The function name to cleanup the session or the function itself.
|
||||
initializer: Optional[Callable[[], None]]
|
||||
The initializer function.
|
||||
"""
|
||||
super().__init__()
|
||||
self.timeout_sec = timeout_sec
|
||||
self.evaluator_config = EvaluatorConfig._normalized(evaluator_config)
|
||||
self.cooldown_sec = cooldown_sec
|
||||
self.alloc_repeat = alloc_repeat
|
||||
self.f_alloc_argument = f_alloc_argument
|
||||
self.f_run_evaluator = f_run_evaluator
|
||||
self.f_cleanup = f_cleanup
|
||||
|
||||
err_path = subprocess.DEVNULL
|
||||
if logger.root.level <= logging.DEBUG:
|
||||
err_path = subprocess.STDOUT
|
||||
|
||||
logger.info("LocalRunner: max_workers = 1")
|
||||
self.pool = PopenPoolExecutor(
|
||||
max_workers=1, # one local worker
|
||||
timeout=timeout_sec,
|
||||
initializer=initializer,
|
||||
stderr=err_path, # suppress the stderr output
|
||||
)
|
||||
self._sanity_check()
|
||||
|
||||
def run(self, runner_inputs: list[RunnerInput]) -> list[RunnerFuture]:
|
||||
results: list[RunnerFuture] = []
|
||||
for runner_input in runner_inputs:
|
||||
future = self.pool.submit(
|
||||
_worker_func,
|
||||
self.f_alloc_argument,
|
||||
self.f_run_evaluator,
|
||||
self.f_cleanup,
|
||||
self.evaluator_config,
|
||||
self.alloc_repeat,
|
||||
str(runner_input.artifact_path),
|
||||
str(runner_input.device_type),
|
||||
tuple(arg_info.as_json() for arg_info in runner_input.args_info),
|
||||
)
|
||||
try:
|
||||
result: list[float] = future.result()
|
||||
error_message: str = None
|
||||
except TimeoutError:
|
||||
result = None
|
||||
error_message = f"LocalRunner: Timeout, killed after {self.timeout_sec} seconds\n"
|
||||
except Exception as exception: # pylint: disable=broad-except
|
||||
result = None
|
||||
error_message = "LocalRunner: An exception occurred\n" + str(exception)
|
||||
local_future = LocalRunnerFuture(res=result, error_message=error_message)
|
||||
results.append(local_future) # type: ignore
|
||||
return results
|
||||
|
||||
def _sanity_check(self) -> None:
|
||||
def _check(
|
||||
f_alloc_argument,
|
||||
f_run_evaluator,
|
||||
f_cleanup,
|
||||
) -> None:
|
||||
get_global_func_with_default_on_worker(name=f_alloc_argument, default=None)
|
||||
get_global_func_with_default_on_worker(name=f_run_evaluator, default=None)
|
||||
get_global_func_with_default_on_worker(name=f_cleanup, default=None)
|
||||
|
||||
value = self.pool.submit(
|
||||
_check,
|
||||
self.f_alloc_argument,
|
||||
self.f_run_evaluator,
|
||||
self.f_cleanup,
|
||||
)
|
||||
value.result()
|
||||
|
||||
|
||||
def default_alloc_argument(
|
||||
device: Device,
|
||||
args_info: T_ARG_INFO_JSON_OBJ_LIST,
|
||||
alloc_repeat: int,
|
||||
) -> list[T_ARGUMENT_LIST]:
|
||||
"""Default function to allocate the arguments
|
||||
|
||||
Parameters
|
||||
----------
|
||||
device: Device
|
||||
The device to allocate the arguments
|
||||
args_info: T_ARG_INFO_JSON_OBJ_LIST
|
||||
The arguments info
|
||||
alloc_repeat: int
|
||||
The number of times to repeat the allocation
|
||||
|
||||
Returns
|
||||
-------
|
||||
repeated_args: List[T_ARGUMENT_LIST]
|
||||
The allocation args
|
||||
"""
|
||||
f_random_fill = get_global_func_with_default_on_worker(
|
||||
name="tvm.contrib.random.random_fill_for_measure", default=None
|
||||
)
|
||||
return alloc_argument_common(f_random_fill, device, args_info, alloc_repeat)
|
||||
|
||||
|
||||
def default_run_evaluator(
|
||||
rt_mod: Module,
|
||||
device: Device,
|
||||
evaluator_config: EvaluatorConfig,
|
||||
repeated_args: list[T_ARGUMENT_LIST],
|
||||
) -> list[float]:
|
||||
"""Default function to run the evaluator
|
||||
|
||||
Parameters
|
||||
----------
|
||||
rt_mod: Module
|
||||
The runtime module
|
||||
device: Device
|
||||
The device to run the evaluator
|
||||
evaluator_config: EvaluatorConfig
|
||||
The evaluator config
|
||||
repeated_args: List[T_ARGUMENT_LIST]
|
||||
The repeated arguments
|
||||
|
||||
Returns
|
||||
-------
|
||||
costs: List[float]
|
||||
The evaluator results
|
||||
"""
|
||||
return run_evaluator_common(rt_mod, device, evaluator_config, repeated_args)
|
||||
|
||||
|
||||
def default_cleanup() -> None:
|
||||
"""Default function to clean up the session"""
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
|
||||
@tvm.register_global_func("s_tir.meta_schedule.runner.get_local_runner")
|
||||
def get_local_builder() -> LocalRunner:
|
||||
"""Get the local Runner.
|
||||
|
||||
Returns
|
||||
-------
|
||||
runner: LocalRunner
|
||||
The local runner
|
||||
"""
|
||||
return LocalRunner()
|
||||
@@ -0,0 +1,535 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""RPC Runner"""
|
||||
|
||||
import concurrent.futures
|
||||
import os.path as osp
|
||||
from collections.abc import Callable
|
||||
from contextlib import contextmanager
|
||||
|
||||
from tvm.ir.utils import derived_object
|
||||
from tvm.rpc import RPCSession
|
||||
from tvm.runtime import Device, Module
|
||||
from tvm.support.popen_pool import PopenPoolExecutor
|
||||
|
||||
from ..logging import get_logger
|
||||
from ..profiler import Profiler
|
||||
from ..utils import (
|
||||
get_global_func_on_rpc_session,
|
||||
get_global_func_with_default_on_worker,
|
||||
)
|
||||
from .config import EvaluatorConfig, RPCConfig
|
||||
from .runner import PyRunner, PyRunnerFuture, RunnerFuture, RunnerInput, RunnerResult
|
||||
from .utils import (
|
||||
T_ARG_INFO_JSON_OBJ_LIST,
|
||||
T_ARGUMENT_LIST,
|
||||
alloc_argument_common,
|
||||
run_evaluator_common,
|
||||
)
|
||||
|
||||
logger = get_logger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
T_CREATE_SESSION = Callable[ # pylint: disable=invalid-name
|
||||
[RPCConfig], # The RPC configuration
|
||||
RPCSession, # The RPC Session
|
||||
]
|
||||
T_UPLOAD_MODULE = Callable[ # pylint: disable=invalid-name
|
||||
[
|
||||
RPCSession, # The RPC Session
|
||||
str, # local path to the artifact
|
||||
str, # remote path to the artifact
|
||||
],
|
||||
Module, # the Module opened on the remote
|
||||
]
|
||||
T_ALLOC_ARGUMENT = Callable[ # pylint: disable=invalid-name
|
||||
[
|
||||
RPCSession, # The RPC Session
|
||||
Device, # The device on the remote
|
||||
T_ARG_INFO_JSON_OBJ_LIST, # The metadata information of the arguments to be allocated
|
||||
int, # The number of repeated allocations to be done
|
||||
],
|
||||
list[T_ARGUMENT_LIST], # A list of argument lists
|
||||
]
|
||||
T_RUN_EVALUATOR = Callable[ # pylint: disable=invalid-name
|
||||
[
|
||||
RPCSession, # The RPC Session
|
||||
Module, # The Module opened on the remote
|
||||
Device, # The device on the remote
|
||||
EvaluatorConfig, # The evaluator configuration
|
||||
list[T_ARGUMENT_LIST], # A list of argument lists
|
||||
],
|
||||
list[float], # A list of running time
|
||||
]
|
||||
T_CLEANUP = Callable[ # pylint: disable=invalid-name
|
||||
[
|
||||
RPCSession | None, # The RPC Session to be cleaned up
|
||||
str | None, # remote path to the artifact
|
||||
],
|
||||
None,
|
||||
]
|
||||
|
||||
|
||||
@derived_object
|
||||
class RPCRunnerFuture(PyRunnerFuture):
|
||||
"""RPC based runner future
|
||||
|
||||
Parameters
|
||||
----------
|
||||
future: concurrent.futures.Future
|
||||
The concurrent function to check when the function is done and to return the result.
|
||||
timeout_sec: float
|
||||
The timeout in seconds.
|
||||
"""
|
||||
|
||||
future: concurrent.futures.Future
|
||||
timeout_sec: float
|
||||
|
||||
def __init__(self, future: concurrent.futures.Future, timeout_sec: float) -> None:
|
||||
"""Constructor
|
||||
|
||||
Parameters
|
||||
----------
|
||||
future: concurrent.futures.Future
|
||||
The concurrent function to check when the function is done and to return the result.
|
||||
timeout_sec: float
|
||||
The timeout in seconds.
|
||||
"""
|
||||
super().__init__()
|
||||
self.future = future
|
||||
self.timeout_sec = timeout_sec
|
||||
|
||||
def done(self) -> bool:
|
||||
return self.future.done()
|
||||
|
||||
def result(self) -> RunnerResult:
|
||||
try:
|
||||
run_secs: list[float] = self.future.result()
|
||||
except TimeoutError:
|
||||
return RunnerResult(
|
||||
None,
|
||||
error_msg=f"RPCRunner: Timeout, killed after {self.timeout_sec} seconds",
|
||||
)
|
||||
except Exception as exception: # pylint: disable=broad-except
|
||||
return RunnerResult(
|
||||
None,
|
||||
error_msg="RPCRunner: An exception occurred\n" + str(exception),
|
||||
)
|
||||
return RunnerResult(run_secs, None)
|
||||
|
||||
|
||||
@derived_object
|
||||
class RPCRunner(PyRunner):
|
||||
"""RPC based runner
|
||||
|
||||
Parameters
|
||||
----------
|
||||
rpc_config: RPCConfig
|
||||
The rpc configuration.
|
||||
evaluator_config: EvaluatorConfig
|
||||
The evaluator configuration.
|
||||
cooldown_sec: float
|
||||
The cooldown in seconds. TODO(@junrushao1994,@zxybazh): This is not used yet.
|
||||
alloc_repeat: int
|
||||
The number of times to repeat the allocation.
|
||||
f_create_session: Optional[str, Callable]
|
||||
The function name to create the session or the function itself.
|
||||
f_upload_module: Optional[str, Callable]
|
||||
The function name to upload the module or the function itself.
|
||||
f_alloc_argument: Optional[str, Callable]
|
||||
The function name to allocate the arguments or the function itself.
|
||||
f_run_evaluator: Optional[str, Callable]
|
||||
The function name to run the evaluator or the function itself.
|
||||
f_cleanup: Optional[str, Callable]
|
||||
The function name to cleanup the session or the function itself.
|
||||
pool: PopenPoolExecutor
|
||||
The popen pool executor.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
T_CREATE_SESSION : typing._GenericAlias
|
||||
The signature of the function `f_create_session`, which is:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def default_create_session(rpc_config: RPCConfig) -> RPCSession:
|
||||
...
|
||||
|
||||
T_UPLOAD_MODULE : typing._GenericAlias
|
||||
The signature of the function `f_upload_module`, which is:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def default_upload_module(
|
||||
session: RPCSession,
|
||||
local_path: str,
|
||||
remote_path: str,
|
||||
) -> Module:
|
||||
...
|
||||
|
||||
T_ALLOC_ARGUMENT : typing._GenericAlias
|
||||
The signature of the function `f_alloc_argument`, which is:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def default_alloc_argument(
|
||||
session: RPCSession,
|
||||
device: Device,
|
||||
args_info: T_ARG_INFO_JSON_OBJ_LIST,
|
||||
alloc_repeat: int,
|
||||
) -> List[T_ARGUMENT_LIST]:
|
||||
...
|
||||
|
||||
T_RUN_EVALUATOR : typing._GenericAlias
|
||||
The signature of the function `f_run_evaluator`, which is:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def default_run_evaluator(
|
||||
session: RPCSession,
|
||||
rt_mod: Module,
|
||||
device: Device,
|
||||
evaluator_config: EvaluatorConfig,
|
||||
repeated_args: List[T_ARGUMENT_LIST],
|
||||
) -> List[float]:
|
||||
...
|
||||
|
||||
T_CLEANUP : typing._GenericAlias
|
||||
The signature of the function `f_cleanup`, which is:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def default_cleanup(
|
||||
session: Optional[RPCSession],
|
||||
remote_path: Optional[str],
|
||||
) -> None:
|
||||
...
|
||||
"""
|
||||
|
||||
rpc_config: RPCConfig
|
||||
evaluator_config: EvaluatorConfig
|
||||
cooldown_sec: float
|
||||
alloc_repeat: int
|
||||
|
||||
f_create_session: T_CREATE_SESSION | str | None
|
||||
f_upload_module: T_UPLOAD_MODULE | str | None
|
||||
f_alloc_argument: T_ALLOC_ARGUMENT | str | None
|
||||
f_run_evaluator: T_RUN_EVALUATOR | str | None
|
||||
f_cleanup: T_CLEANUP | str | None
|
||||
|
||||
pool: PopenPoolExecutor
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
rpc_config: RPCConfig | None = None,
|
||||
evaluator_config: EvaluatorConfig | None = None,
|
||||
cooldown_sec: float = 0.0,
|
||||
alloc_repeat: int = 1,
|
||||
f_create_session: T_CREATE_SESSION | str | None = None,
|
||||
f_upload_module: T_UPLOAD_MODULE | str | None = None,
|
||||
f_alloc_argument: T_ALLOC_ARGUMENT | str | None = None,
|
||||
f_run_evaluator: T_RUN_EVALUATOR | str | None = None,
|
||||
f_cleanup: T_CLEANUP | str | None = None,
|
||||
max_workers: int | None = None,
|
||||
initializer: Callable[[], None] | None = None,
|
||||
) -> None:
|
||||
"""Constructor
|
||||
|
||||
Parameters
|
||||
----------
|
||||
rpc_config: RPCConfig
|
||||
The rpc configuration.
|
||||
evaluator_config: EvaluatorConfig
|
||||
The evaluator configuration.
|
||||
cooldown_sec: float
|
||||
The cooldown in seconds.
|
||||
alloc_repeat: int
|
||||
The number of times to random fill the allocation.
|
||||
f_create_session: Union[T_CREATE_SESSION, str, None]
|
||||
The function name to create the session or the function itself.
|
||||
f_upload_module: Union[T_UPLOAD_MODULE, str, None]
|
||||
The function name to upload the module or the function itself.
|
||||
f_alloc_argument: Union[T_ALLOC_ARGUMENT, str, None]
|
||||
The function name to allocate the arguments or the function itself.
|
||||
f_run_evaluator: Union[T_RUN_EVALUATOR, str, None]
|
||||
The function name to run the evaluator or the function itself.
|
||||
f_cleanup: Union[T_CLEANUP, str, None]
|
||||
The function name to cleanup the session or the function itself.
|
||||
max_workers: Optional[int] = None
|
||||
The maximum number of connections. Defaults to 1.
|
||||
initializer: Optional[Callable[[], None]]
|
||||
The initializer function.
|
||||
"""
|
||||
super().__init__()
|
||||
self.rpc_config = RPCConfig._normalized(rpc_config)
|
||||
self.evaluator_config = EvaluatorConfig._normalized(evaluator_config)
|
||||
self.cooldown_sec = cooldown_sec
|
||||
self.alloc_repeat = alloc_repeat
|
||||
self.f_create_session = f_create_session
|
||||
self.f_upload_module = f_upload_module
|
||||
self.f_alloc_argument = f_alloc_argument
|
||||
self.f_run_evaluator = f_run_evaluator
|
||||
self.f_cleanup = f_cleanup
|
||||
if max_workers is None:
|
||||
max_workers = 1
|
||||
logger.info("RPCRunner: max_workers = %d", max_workers)
|
||||
self.pool = PopenPoolExecutor(
|
||||
max_workers=max_workers,
|
||||
initializer=initializer,
|
||||
)
|
||||
self._sanity_check()
|
||||
|
||||
def run(self, runner_inputs: list[RunnerInput]) -> list[RunnerFuture]:
|
||||
results: list[RunnerFuture] = []
|
||||
for runner_input in runner_inputs:
|
||||
future = RPCRunnerFuture(
|
||||
future=self.pool.submit(
|
||||
_worker_func,
|
||||
self.f_create_session,
|
||||
self.f_upload_module,
|
||||
self.f_alloc_argument,
|
||||
self.f_run_evaluator,
|
||||
self.f_cleanup,
|
||||
self.rpc_config,
|
||||
self.evaluator_config,
|
||||
self.alloc_repeat,
|
||||
str(runner_input.artifact_path),
|
||||
str(runner_input.device_type),
|
||||
tuple(arg_info.as_json() for arg_info in runner_input.args_info),
|
||||
),
|
||||
timeout_sec=self.rpc_config.session_timeout_sec,
|
||||
)
|
||||
results.append(future) # type: ignore
|
||||
return results
|
||||
|
||||
def _sanity_check(self) -> None:
|
||||
def _check(
|
||||
f_create_session,
|
||||
f_upload_module,
|
||||
f_alloc_argument,
|
||||
f_run_evaluator,
|
||||
f_cleanup,
|
||||
) -> None:
|
||||
get_global_func_with_default_on_worker(name=f_create_session, default=None)
|
||||
get_global_func_with_default_on_worker(name=f_upload_module, default=None)
|
||||
get_global_func_with_default_on_worker(name=f_alloc_argument, default=None)
|
||||
get_global_func_with_default_on_worker(name=f_run_evaluator, default=None)
|
||||
get_global_func_with_default_on_worker(name=f_cleanup, default=None)
|
||||
|
||||
value = self.pool.submit(
|
||||
_check,
|
||||
self.f_create_session,
|
||||
self.f_upload_module,
|
||||
self.f_alloc_argument,
|
||||
self.f_run_evaluator,
|
||||
self.f_cleanup,
|
||||
)
|
||||
value.result()
|
||||
|
||||
|
||||
def _worker_func(
|
||||
_f_create_session: T_CREATE_SESSION | str | None,
|
||||
_f_upload_module: T_UPLOAD_MODULE | str | None,
|
||||
_f_alloc_argument: T_ALLOC_ARGUMENT | str | None,
|
||||
_f_run_evaluator: T_RUN_EVALUATOR | str | None,
|
||||
_f_cleanup: T_CLEANUP | str | None,
|
||||
rpc_config: RPCConfig,
|
||||
evaluator_config: EvaluatorConfig,
|
||||
alloc_repeat: int,
|
||||
artifact_path: str,
|
||||
device_type: str,
|
||||
args_info: T_ARG_INFO_JSON_OBJ_LIST,
|
||||
) -> list[float]:
|
||||
# Step 0. Get the registered functions
|
||||
f_create_session: T_CREATE_SESSION = get_global_func_with_default_on_worker(
|
||||
_f_create_session, default_create_session
|
||||
)
|
||||
f_upload_module: T_UPLOAD_MODULE = get_global_func_with_default_on_worker(
|
||||
_f_upload_module, default_upload_module
|
||||
)
|
||||
f_alloc_argument: T_ALLOC_ARGUMENT = get_global_func_with_default_on_worker(
|
||||
_f_alloc_argument, default_alloc_argument
|
||||
)
|
||||
f_run_evaluator: T_RUN_EVALUATOR = get_global_func_with_default_on_worker(
|
||||
_f_run_evaluator, default_run_evaluator
|
||||
)
|
||||
f_cleanup: T_CLEANUP = get_global_func_with_default_on_worker(_f_cleanup, default_cleanup)
|
||||
# Managed resources
|
||||
session: RPCSession | None = None
|
||||
remote_path: str | None = None
|
||||
|
||||
@contextmanager
|
||||
def resource_handler():
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
# Final step. Always clean up
|
||||
with Profiler.timeit("RPCRunner/cleanup"):
|
||||
f_cleanup(session, remote_path)
|
||||
|
||||
with resource_handler():
|
||||
# Step 1. Create session
|
||||
with Profiler.timeit("RPCRunner/create_session"):
|
||||
session = f_create_session(rpc_config)
|
||||
device = session.device(device_type, 0)
|
||||
# Step 2. Upload the module
|
||||
with Profiler.timeit("RPCRunner/upload_module"):
|
||||
_, remote_path = osp.split(artifact_path)
|
||||
local_path: str = artifact_path
|
||||
rt_mod: Module = f_upload_module(session, local_path, remote_path)
|
||||
# Step 3: Allocate input arguments
|
||||
with Profiler.timeit("RPCRunner/alloc_argument"):
|
||||
repeated_args: list[T_ARGUMENT_LIST] = f_alloc_argument(
|
||||
session,
|
||||
device,
|
||||
args_info,
|
||||
alloc_repeat,
|
||||
)
|
||||
# Step 4: Run time_evaluator
|
||||
with Profiler.timeit("LocalRunner/run_evaluator"):
|
||||
costs: list[float] = f_run_evaluator(
|
||||
session,
|
||||
rt_mod,
|
||||
device,
|
||||
evaluator_config,
|
||||
repeated_args,
|
||||
)
|
||||
return costs
|
||||
|
||||
|
||||
def default_create_session(rpc_config: RPCConfig) -> RPCSession:
|
||||
"""Default function to create the session
|
||||
|
||||
Parameters
|
||||
----------
|
||||
rpc_config : RPCConfig
|
||||
The configuration of the RPC session
|
||||
|
||||
Returns
|
||||
-------
|
||||
session : RPCSession
|
||||
The created rpc session
|
||||
"""
|
||||
return rpc_config.connect_server()
|
||||
|
||||
|
||||
def default_upload_module(
|
||||
session: RPCSession,
|
||||
local_path: str,
|
||||
remote_path: str,
|
||||
) -> Module:
|
||||
"""Default function to upload the module
|
||||
|
||||
Parameters
|
||||
----------
|
||||
session: RPCSession
|
||||
The session to upload the module
|
||||
local_path: str
|
||||
The local path of the module
|
||||
remote_path: str
|
||||
The remote path to place the module
|
||||
|
||||
Returns
|
||||
-------
|
||||
rt_mod : Module
|
||||
The runtime module
|
||||
"""
|
||||
session.upload(local_path, remote_path)
|
||||
rt_mod: Module = session.load_module(remote_path)
|
||||
return rt_mod
|
||||
|
||||
|
||||
def default_alloc_argument(
|
||||
session: RPCSession,
|
||||
device: Device,
|
||||
args_info: T_ARG_INFO_JSON_OBJ_LIST,
|
||||
alloc_repeat: int,
|
||||
) -> list[T_ARGUMENT_LIST]:
|
||||
"""Default function to allocate the arguments
|
||||
|
||||
Parameters
|
||||
----------
|
||||
session: RPCSession
|
||||
The session to allocate the arguments
|
||||
device: Device
|
||||
The device to allocate the arguments
|
||||
args_info: T_ARG_INFO_JSON_OBJ_LIST
|
||||
The arguments info
|
||||
alloc_repeat: int
|
||||
The number of times to repeat the allocation
|
||||
|
||||
Returns
|
||||
-------
|
||||
repeated_args: List[Args]
|
||||
The allocation args
|
||||
"""
|
||||
f_random_fill = get_global_func_on_rpc_session(
|
||||
session,
|
||||
"tvm.contrib.random.random_fill_for_measure",
|
||||
"Please make sure 'USE_RANDOM' is turned ON in the config.cmake on the RPC server.",
|
||||
)
|
||||
|
||||
return alloc_argument_common(f_random_fill, device, args_info, alloc_repeat)
|
||||
|
||||
|
||||
def default_run_evaluator(
|
||||
session: RPCSession, # pylint: disable=unused-argument
|
||||
rt_mod: Module,
|
||||
device: Device,
|
||||
evaluator_config: EvaluatorConfig,
|
||||
repeated_args: list[T_ARGUMENT_LIST],
|
||||
) -> list[float]:
|
||||
"""Default function to run the evaluator
|
||||
|
||||
Parameters
|
||||
----------
|
||||
session: RPCSession
|
||||
The session to run the evaluator
|
||||
rt_mod: Module
|
||||
The runtime module
|
||||
device: Device
|
||||
The device to run the evaluator
|
||||
evaluator_config: EvaluatorConfig
|
||||
The evaluator config
|
||||
repeated_args: List[T_ARGUMENT_LIST]
|
||||
The repeated arguments
|
||||
|
||||
Returns
|
||||
-------
|
||||
costs: List[float]
|
||||
The evaluator results
|
||||
"""
|
||||
return run_evaluator_common(rt_mod, device, evaluator_config, repeated_args)
|
||||
|
||||
|
||||
def default_cleanup(
|
||||
session: RPCSession | None,
|
||||
remote_path: str | None,
|
||||
) -> None:
|
||||
"""Default function to clean up the session
|
||||
|
||||
Parameters
|
||||
----------
|
||||
session: RPCSession
|
||||
The session to clean up
|
||||
remote_path: str
|
||||
The remote path to clean up
|
||||
"""
|
||||
if session is not None and remote_path is not None:
|
||||
session.remove(remote_path)
|
||||
session.remove(remote_path + ".so")
|
||||
session.remove("")
|
||||
@@ -0,0 +1,257 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
# ruff: noqa: RUF012
|
||||
"""Runners"""
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import Union
|
||||
|
||||
# isort: off
|
||||
from typing import Literal
|
||||
|
||||
# isort: on
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from tvm.runtime import Object
|
||||
|
||||
from .. import _ffi_api
|
||||
from ..arg_info import ArgInfo
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.RunnerInput")
|
||||
class RunnerInput(Object):
|
||||
"""The runner's input
|
||||
|
||||
Parameters
|
||||
----------
|
||||
artifact_path : str
|
||||
The path to the built artifact.
|
||||
device_type : str
|
||||
The device type.
|
||||
args_info : List[ArgInfo]
|
||||
The argument information.
|
||||
"""
|
||||
|
||||
artifact_path: str
|
||||
device_type: str
|
||||
args_info: list[ArgInfo]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
artifact_path: str,
|
||||
device_type: str,
|
||||
args_info: list[ArgInfo],
|
||||
) -> None:
|
||||
"""Constructor
|
||||
|
||||
Parameters
|
||||
----------
|
||||
artifact_path : str
|
||||
The path to the built artifact.
|
||||
device_type : str
|
||||
The device type.
|
||||
args_info : List[ArgInfo]
|
||||
The argument information.
|
||||
"""
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.RunnerInput, # type: ignore # pylint: disable=no-member
|
||||
artifact_path,
|
||||
device_type,
|
||||
args_info,
|
||||
)
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.RunnerResult")
|
||||
class RunnerResult(Object):
|
||||
"""The runner's result
|
||||
|
||||
Parameters
|
||||
----------
|
||||
run_secs : Optional[List[float]]
|
||||
The run time in seconds.
|
||||
error_msg : Optional[str]
|
||||
The error message, if any.
|
||||
"""
|
||||
|
||||
run_secs: list[float] | None
|
||||
error_msg: str | None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
run_secs: list[float] | None,
|
||||
error_msg: str | None,
|
||||
) -> None:
|
||||
"""Constructor
|
||||
|
||||
Parameters
|
||||
----------
|
||||
run_secs : Optional[List[float]]
|
||||
The run time in seconds.
|
||||
error_msg : Optional[str]
|
||||
The error message, if any.
|
||||
"""
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.RunnerResult, # type: ignore # pylint: disable=no-member
|
||||
run_secs,
|
||||
error_msg,
|
||||
)
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.RunnerFuture")
|
||||
class RunnerFuture(Object):
|
||||
"""
|
||||
A class to fetch asynchronous runner's output.
|
||||
This is NOT the user facing class for function overloading inheritance.
|
||||
Can be used for general return type of runner.
|
||||
|
||||
See also: PyRunnerFuture
|
||||
"""
|
||||
|
||||
def __init__(self, f_done: Callable, f_result: Callable | None = None) -> None:
|
||||
"""Constructor"""
|
||||
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.RunnerFuture, # type: ignore # pylint: disable=no-member
|
||||
f_done,
|
||||
f_result,
|
||||
)
|
||||
|
||||
def done(self) -> bool:
|
||||
"""Check whether the runner has finished."""
|
||||
return _ffi_api.RunnerFutureDone(self) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def result(self) -> RunnerResult:
|
||||
"""Fetch the runner's output if it is ready."""
|
||||
return _ffi_api.RunnerFutureResult(self) # type: ignore # pylint: disable=no-member
|
||||
|
||||
|
||||
class PyRunnerFuture:
|
||||
"""
|
||||
A class to fetch asynchronous runner's output with customizable function on the python side.
|
||||
This is the user facing class for function overloading inheritance.
|
||||
Can NOT be used for general return type of runner.
|
||||
|
||||
Note: @derived_object is required for proper usage of any inherited class.
|
||||
Example::
|
||||
|
||||
@derived_object
|
||||
def LocalRunnerFuture(PyRunnerFuture):
|
||||
...
|
||||
"""
|
||||
|
||||
_tvm_metadata = {
|
||||
"cls": RunnerFuture,
|
||||
"methods": ["done", "result"],
|
||||
}
|
||||
|
||||
def done(self) -> bool:
|
||||
"""Check whether the runner has finished."""
|
||||
raise NotImplementedError
|
||||
|
||||
def result(self) -> RunnerResult:
|
||||
"""Fetch the runner's output if it is ready."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.Runner")
|
||||
class Runner(Object):
|
||||
"""The abstract runner interface"""
|
||||
|
||||
RunnerType = Union["Runner", Literal["local", "rpc"]]
|
||||
|
||||
def run(self, runner_inputs: list[RunnerInput]) -> list[RunnerFuture]:
|
||||
"""Run the built artifact and get runner futures.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
runner_inputs : List[RunnerInput]
|
||||
The inputs to the runner.
|
||||
|
||||
Returns
|
||||
-------
|
||||
runner_futures: List[RunnerFuture]
|
||||
The runner futures.
|
||||
"""
|
||||
return _ffi_api.RunnerRun(self, runner_inputs) # type: ignore # pylint: disable=no-member
|
||||
|
||||
@staticmethod
|
||||
def create( # pylint: disable=keyword-arg-before-vararg
|
||||
kind: Literal["local", "rpc"] = "local",
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> "Runner":
|
||||
"""Create a Runner."""
|
||||
from . import LocalRunner, RPCRunner # pylint: disable=import-outside-toplevel
|
||||
|
||||
if kind == "local":
|
||||
if "max_workers" in kwargs:
|
||||
kwargs.pop("max_workers")
|
||||
return LocalRunner(*args, **kwargs) # type: ignore
|
||||
elif kind == "rpc":
|
||||
return RPCRunner(*args, **kwargs) # type: ignore
|
||||
raise ValueError(f"Unknown Runner: {kind}")
|
||||
|
||||
|
||||
create = Runner.create # pylint: disable=invalid-name
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.PyRunner")
|
||||
class _PyRunner(Runner):
|
||||
"""
|
||||
A TVM object runner to support customization on the python side.
|
||||
This is NOT the user facing class for function overloading inheritance.
|
||||
|
||||
See also: PyRunner
|
||||
"""
|
||||
|
||||
def __init__(self, f_run: Callable | None = None) -> None:
|
||||
"""Constructor"""
|
||||
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.RunnerPyRunner, # type: ignore # pylint: disable=no-member
|
||||
f_run,
|
||||
)
|
||||
|
||||
|
||||
class PyRunner:
|
||||
"""
|
||||
An abstract runner with customized run method on the python-side.
|
||||
This is the user facing class for function overloading inheritance.
|
||||
|
||||
Note: @derived_object is required for proper usage of any inherited class.
|
||||
"""
|
||||
|
||||
_tvm_metadata = {
|
||||
"cls": _PyRunner,
|
||||
"methods": ["run"],
|
||||
}
|
||||
|
||||
def run(self, runner_inputs: list[RunnerInput]) -> list[RunnerFuture]:
|
||||
"""Run the built artifact and get runner futures.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
runner_inputs : List[RunnerInput]
|
||||
The inputs to the runner.
|
||||
|
||||
Returns
|
||||
-------
|
||||
runner_futures: List[RunnerFuture]
|
||||
The runner futures.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,124 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""Runner utility functions"""
|
||||
|
||||
import itertools
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
import tvm.runtime
|
||||
|
||||
from ....runtime import Device, Module
|
||||
from .config import EvaluatorConfig
|
||||
|
||||
T_ARG_INFO_JSON_OBJ = list[Any] # pylint: disable=invalid-name
|
||||
T_ARG_INFO_JSON_OBJ_LIST = list[T_ARG_INFO_JSON_OBJ] # pylint: disable=invalid-name
|
||||
T_ARGUMENT = Any # pylint: disable=invalid-name
|
||||
T_ARGUMENT_LIST = list[T_ARGUMENT] # pylint: disable=invalid-name
|
||||
|
||||
|
||||
def alloc_argument_common(
|
||||
f_random_fill: Callable,
|
||||
device: Device,
|
||||
args_info: T_ARG_INFO_JSON_OBJ_LIST,
|
||||
alloc_repeat: int,
|
||||
) -> list[T_ARGUMENT_LIST]:
|
||||
"""Common function to allocate the arguments
|
||||
|
||||
Parameters
|
||||
----------
|
||||
f_random_fill: Callable
|
||||
The callable function for random fill
|
||||
device: Device
|
||||
The device to allocate the arguments
|
||||
args_info: T_ARG_INFO_JSON_OBJ_LIST
|
||||
The arguments info
|
||||
alloc_repeat: int
|
||||
The number of times to repeat the allocation
|
||||
|
||||
Returns
|
||||
-------
|
||||
repeated_args: List[T_ARGUMENT_LIST]
|
||||
The allocation args
|
||||
"""
|
||||
|
||||
def alloc_tensor(_, dtype, shape) -> tvm.runtime.Tensor:
|
||||
arg = tvm.runtime.empty(shape=shape, dtype=dtype, device=device)
|
||||
f_random_fill(arg)
|
||||
return arg
|
||||
|
||||
def alloc_fail(*arg_info) -> None:
|
||||
raise NotImplementedError(arg_info)
|
||||
|
||||
dispatcher: dict[Any, Callable] = {
|
||||
"TENSOR": alloc_tensor,
|
||||
None: alloc_fail,
|
||||
}
|
||||
|
||||
repeated_args: list[T_ARGUMENT_LIST] = []
|
||||
for _ in range(alloc_repeat):
|
||||
args: T_ARGUMENT_LIST = []
|
||||
arg_info: T_ARG_INFO_JSON_OBJ
|
||||
for arg_info in args_info:
|
||||
arg_type = arg_info[0]
|
||||
arg: Any = dispatcher.get(arg_type, None)(*arg_info)
|
||||
args.append(arg)
|
||||
repeated_args.append(args)
|
||||
return repeated_args
|
||||
|
||||
|
||||
def run_evaluator_common(
|
||||
rt_mod: Module,
|
||||
device: Device,
|
||||
evaluator_config: EvaluatorConfig,
|
||||
repeated_args: list[T_ARGUMENT_LIST],
|
||||
) -> list[float]:
|
||||
"""Common function to run the evaluator
|
||||
|
||||
Parameters
|
||||
----------
|
||||
rt_mod: Module
|
||||
The runtime module
|
||||
device: Device
|
||||
The device to run the evaluator
|
||||
evaluator_config: EvaluatorConfig
|
||||
The evaluator config
|
||||
repeated_args: List[T_ARGUMENT_LIST]
|
||||
The repeated arguments
|
||||
|
||||
Returns
|
||||
-------
|
||||
costs: List[float]
|
||||
The evaluator results
|
||||
"""
|
||||
evaluator = rt_mod.time_evaluator(
|
||||
func_name=rt_mod.entry_name,
|
||||
dev=device,
|
||||
number=evaluator_config.number,
|
||||
repeat=evaluator_config.repeat,
|
||||
min_repeat_ms=evaluator_config.min_repeat_ms,
|
||||
f_preproc="cache_flush_cpu_non_first_arg"
|
||||
if evaluator_config.enable_cpu_cache_flush
|
||||
else "",
|
||||
)
|
||||
repeated_costs: list[list[float]] = []
|
||||
for args in repeated_args:
|
||||
device.sync()
|
||||
profile_result = evaluator(*args)
|
||||
repeated_costs.append(profile_result.results)
|
||||
costs = [float(cost) for cost in itertools.chain.from_iterable(repeated_costs)]
|
||||
return costs
|
||||
Reference in New Issue
Block a user