405 lines
13 KiB
Python
405 lines
13 KiB
Python
# 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()
|