chore: import upstream snapshot with attribution
Lint / lint (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Windows (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:36:25 +08:00
commit 26446540fa
3151 changed files with 974126 additions and 0 deletions
@@ -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