chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
# 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.
|
||||
"""Package `tvm.s_tir.meta_schedule`. The meta schedule infrastructure."""
|
||||
|
||||
from . import (
|
||||
arg_info,
|
||||
builder,
|
||||
cost_model,
|
||||
database,
|
||||
feature_extractor,
|
||||
measure_callback,
|
||||
mutator,
|
||||
postproc,
|
||||
relax_integration,
|
||||
runner,
|
||||
schedule,
|
||||
schedule_rule,
|
||||
search_strategy,
|
||||
space_generator,
|
||||
tir_integration,
|
||||
trace_apply,
|
||||
post_optimization,
|
||||
)
|
||||
from .builder import Builder
|
||||
from .cost_model import CostModel
|
||||
from .database import Database
|
||||
from .extracted_task import ExtractedTask
|
||||
from .feature_extractor import FeatureExtractor
|
||||
from .measure_callback import MeasureCallback
|
||||
from .mutator import Mutator
|
||||
from .postproc import Postproc
|
||||
from .profiler import Profiler
|
||||
from .runner import Runner
|
||||
from .schedule_rule import ScheduleRule
|
||||
from .search_strategy import MeasureCandidate, SearchStrategy
|
||||
from .space_generator import SpaceGenerator
|
||||
from .task_scheduler import TaskScheduler
|
||||
from .tir_integration import tune_tir
|
||||
from .tune import tune_tasks
|
||||
from .tune_context import TuneContext
|
||||
from .post_optimization import post_opt
|
||||
@@ -0,0 +1,21 @@
|
||||
# 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.
|
||||
"""FFI APIs for tvm.s_tir.meta_schedule"""
|
||||
|
||||
import tvm_ffi
|
||||
|
||||
tvm_ffi.init_ffi_api("s_tir.meta_schedule", __name__) # pylint: disable=protected-access
|
||||
@@ -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.
|
||||
"""The argument information"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from tvm_ffi import Shape, register_object
|
||||
|
||||
from tvm.ir import IRModule
|
||||
from tvm.runtime import DataType, Object
|
||||
from tvm.tirx import PrimFunc
|
||||
|
||||
from . import _ffi_api
|
||||
from .utils import _json_de_tvm
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.ArgInfo")
|
||||
class ArgInfo(Object):
|
||||
"""Argument information"""
|
||||
|
||||
def as_json(self) -> Any:
|
||||
"""Converts the ArgInfo to its corresponding JSON representation."""
|
||||
return _json_de_tvm(_ffi_api.ArgInfoAsJSON(self)) # type: ignore # pylint: disable=no-member
|
||||
|
||||
@staticmethod
|
||||
def from_json(json_obj: Any) -> "ArgInfo":
|
||||
"""Parse the argument information from a JSON object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
json_obj : Any
|
||||
The json object to parse.
|
||||
|
||||
Returns
|
||||
-------
|
||||
parsed : ArgInfo
|
||||
The argument information parsed.
|
||||
"""
|
||||
return _ffi_api.ArgInfoFromJSON(json_obj) # type: ignore # pylint: disable=no-member
|
||||
|
||||
@staticmethod
|
||||
def from_prim_func(func: PrimFunc) -> list["ArgInfo"]:
|
||||
"""Extract a list of the argument information from PrimFunc.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
func : PrimFunc
|
||||
The PrimFunc to get argument information from.
|
||||
|
||||
Returns
|
||||
-------
|
||||
extracted : List[ArgInfo]
|
||||
An array of the argument information derived.
|
||||
"""
|
||||
return _ffi_api.ArgInfoFromPrimFunc(func) # type: ignore # pylint: disable=no-member
|
||||
|
||||
@staticmethod
|
||||
def from_entry_func(mod: IRModule, remove_preproc: bool = True) -> list["ArgInfo"]:
|
||||
"""Extract a list of the argument information from the entry func of an IRModule.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : IRModule
|
||||
The IRModule to get argument information from.
|
||||
remove_preproc : bool
|
||||
Whether to remove the preprocessing blocks.
|
||||
|
||||
Returns
|
||||
-------
|
||||
extracted : List[ArgInfo]
|
||||
An array of the argument information derived.
|
||||
"""
|
||||
return _ffi_api.ArgInfoFromEntryFunc(mod, remove_preproc) # type: ignore # pylint: disable=no-member
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.TensorInfo")
|
||||
class TensorInfo(ArgInfo):
|
||||
"""Tensor argument information
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dtype : DataType
|
||||
The data type of the tensor.
|
||||
shape : Shape
|
||||
The shape of the tensor.
|
||||
"""
|
||||
|
||||
dtype: DataType
|
||||
shape: Shape
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dtype: DataType,
|
||||
shape: Shape | list[int],
|
||||
) -> None:
|
||||
"""Constructor
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dtype : DataType
|
||||
The data type of the tensor.
|
||||
shape : Shape
|
||||
The shape of the tensor.
|
||||
"""
|
||||
shape_tuple = shape if isinstance(shape, Shape) else Shape(shape)
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.TensorInfo, # type: ignore # pylint: disable=no-member
|
||||
dtype,
|
||||
shape_tuple,
|
||||
)
|
||||
@@ -0,0 +1,25 @@
|
||||
# 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.builder package.
|
||||
Meta Schedule builders that translate IRModule to runtime.Module,
|
||||
and then export
|
||||
"""
|
||||
|
||||
from .builder import Builder, BuilderInput, BuilderResult, PyBuilder, create
|
||||
from .local_builder import LocalBuilder
|
||||
@@ -0,0 +1,203 @@
|
||||
# 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
|
||||
"""Meta Schedule builders that translate IRModule to runtime.Module, and then export"""
|
||||
|
||||
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.ir import IRModule
|
||||
from tvm.runtime import Object, Tensor
|
||||
from tvm.target import Target
|
||||
|
||||
from .. import _ffi_api
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.BuilderInput")
|
||||
class BuilderInput(Object):
|
||||
"""The builder's input.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : IRModule
|
||||
The IRModule to be built.
|
||||
target : Target
|
||||
The target to be built for.
|
||||
params: Optional[Dict[str, Tensor]]
|
||||
The parameters for Relax build module
|
||||
"""
|
||||
|
||||
mod: IRModule
|
||||
target: Target
|
||||
params: dict[str, Tensor] | None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mod: IRModule,
|
||||
target: Target,
|
||||
params: dict[str, Tensor] | None = None,
|
||||
) -> None:
|
||||
"""Constructor.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : IRModule
|
||||
The IRModule to be built.
|
||||
target : Target
|
||||
The target to be built for.
|
||||
params: Optional[Dict[str, Tensor]]
|
||||
The parameters for Relax build module
|
||||
"""
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.BuilderInput, # type: ignore # pylint: disable=no-member
|
||||
mod,
|
||||
target,
|
||||
params,
|
||||
)
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.BuilderResult")
|
||||
class BuilderResult(Object):
|
||||
"""The builder's result.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
artifact_path : Optional[str]
|
||||
The path to the artifact.
|
||||
error_msg : Optional[str]
|
||||
The error message.
|
||||
"""
|
||||
|
||||
artifact_path: str | None
|
||||
error_msg: str | None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
artifact_path: str | None,
|
||||
error_msg: str | None,
|
||||
) -> None:
|
||||
"""Constructor.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
artifact_path : Optional[str]
|
||||
The path to the artifact.
|
||||
error_msg : Optional[str]
|
||||
The error message.
|
||||
"""
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.BuilderResult, # type: ignore # pylint: disable=no-member
|
||||
artifact_path,
|
||||
error_msg,
|
||||
)
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.Builder")
|
||||
class Builder(Object):
|
||||
"""The abstract builder interface."""
|
||||
|
||||
BuilderType = Union["Builder", Literal["local"]]
|
||||
|
||||
def build(self, build_inputs: list[BuilderInput]) -> list[BuilderResult]:
|
||||
"""Build the given inputs.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
build_inputs : List[BuilderInput]
|
||||
The inputs to be built.
|
||||
Returns
|
||||
-------
|
||||
build_results : List[BuilderResult]
|
||||
The results of building the given inputs.
|
||||
"""
|
||||
return _ffi_api.BuilderBuild(self, build_inputs) # type: ignore # pylint: disable=no-member
|
||||
|
||||
@staticmethod
|
||||
def create( # pylint: disable=keyword-arg-before-vararg
|
||||
kind: Literal["local"] = "local",
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> "Builder":
|
||||
"""Create a Builder.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
kind : Literal["local"]
|
||||
The kind of the builder. For now, only "local" is supported.
|
||||
|
||||
Returns
|
||||
-------
|
||||
builder : Builder
|
||||
The builder created.
|
||||
"""
|
||||
from . import LocalBuilder # pylint: disable=import-outside-toplevel
|
||||
|
||||
if kind == "local":
|
||||
return LocalBuilder(*args, **kwargs) # type: ignore
|
||||
raise ValueError(f"Unknown Builder: {kind}")
|
||||
|
||||
|
||||
create = Builder.create # pylint: disable=invalid-name
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.PyBuilder")
|
||||
class _PyBuilder(Builder):
|
||||
"""
|
||||
A TVM object builder to support customization on the python side.
|
||||
This is NOT the user facing class for function overloading inheritance.
|
||||
|
||||
See also: PyBuilder
|
||||
"""
|
||||
|
||||
def __init__(self, f_build: Callable | None = None):
|
||||
"""Constructor."""
|
||||
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.BuilderPyBuilder, # type: ignore # pylint: disable=no-member
|
||||
f_build,
|
||||
)
|
||||
|
||||
|
||||
class PyBuilder:
|
||||
"""
|
||||
An abstract builder with customized build 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": _PyBuilder, "methods": ["build"]}
|
||||
|
||||
def build(self, build_inputs: list[BuilderInput]) -> list[BuilderResult]:
|
||||
"""Build the given inputs.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
build_inputs : List[BuilderInput]
|
||||
The inputs to be built.
|
||||
Returns
|
||||
-------
|
||||
build_results : List[BuilderResult]
|
||||
The results of building the given inputs.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,300 @@
|
||||
# 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: F401
|
||||
"""Local builder that compile on the local host"""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from collections.abc import Callable
|
||||
from typing import Optional, Union
|
||||
|
||||
from tvm_ffi import register_global_func
|
||||
|
||||
from tvm.ir import IRModule
|
||||
from tvm.ir.utils import derived_object
|
||||
from tvm.runtime import Module, Tensor, load_param_dict, save_param_dict
|
||||
from tvm.support.popen_pool import MapResult, PopenPoolExecutor, StatusKind
|
||||
from tvm.target import Target
|
||||
|
||||
from ..logging import get_logger
|
||||
from ..utils import cpu_count, get_global_func_with_default_on_worker
|
||||
from .builder import BuilderInput, BuilderResult, PyBuilder
|
||||
|
||||
logger = get_logger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
T_BUILD = Callable[ # pylint: disable=invalid-name
|
||||
[IRModule, Target, dict[str, Tensor] | None], Module
|
||||
]
|
||||
T_EXPORT = Callable[[Module], str] # pylint: disable=invalid-name
|
||||
|
||||
|
||||
def _serialize_params(params: dict[str, Tensor] | None) -> bytearray | None:
|
||||
if params is None:
|
||||
return None
|
||||
return save_param_dict(params)
|
||||
|
||||
|
||||
def _deserialize_params(params: bytearray | None) -> dict[str, Tensor] | None:
|
||||
if params is None:
|
||||
return None
|
||||
return load_param_dict(params)
|
||||
|
||||
|
||||
@derived_object
|
||||
class LocalBuilder(PyBuilder):
|
||||
"""A builder that builds the given input on local host.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pool : PopenPoolExecutor
|
||||
The process pool to run the build.
|
||||
max_workers: int
|
||||
The max number of Popen workers.
|
||||
timeout_sec : float
|
||||
The timeout in seconds for the build.
|
||||
initializer: Optional[Callable[[], None]]
|
||||
The initializer function for each popen worker.
|
||||
f_build : Union[None, str, T_BUILD]
|
||||
Name of the build function to be used.
|
||||
Defaults to `meta_schedule.builder.default_build`.
|
||||
f_export : Union[None, str, T_EXPORT]
|
||||
Name of the export function to be used.
|
||||
Defaults to `meta_schedule.builder.default_export`.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
T_BUILD : typing._GenericAlias
|
||||
The signature of the function `f_build`, which is
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def default_build(
|
||||
mod: IRModule,
|
||||
target: Target,
|
||||
params: Optional[Dict[str, Tensor]]
|
||||
) -> Module:
|
||||
...
|
||||
|
||||
T_EXPORT : typing._GenericAlias
|
||||
The signature of the function `f_export`, which is
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def default_export(mod: Module) -> str:
|
||||
...
|
||||
|
||||
Note
|
||||
----
|
||||
The build function and export function should be registered in the worker process.
|
||||
The worker process is only aware of functions registered in TVM package,
|
||||
if there are extra functions to be registered,
|
||||
please send the registration logic via initializer.
|
||||
"""
|
||||
|
||||
max_workers: int
|
||||
timeout_sec: float
|
||||
initializer: Callable[[], None] | None
|
||||
f_build: None | str | T_BUILD
|
||||
f_export: None | str | T_EXPORT
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
max_workers: int | None = None,
|
||||
timeout_sec: float = 30.0,
|
||||
f_build: None | str | T_BUILD = None,
|
||||
f_export: None | str | T_EXPORT = None,
|
||||
initializer: Callable[[], None] | None = None,
|
||||
) -> None:
|
||||
"""Constructor.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
max_workers : Optional[int]
|
||||
The maximum number of worker processes to be used.
|
||||
Defaults to number of CPUs.
|
||||
timeout_sec : float
|
||||
The timeout in seconds for the build.
|
||||
f_build : T_BUILD
|
||||
Name of the build function to be used.
|
||||
Defaults to `meta_schedule.builder.default_build`.
|
||||
f_export : T_EXPORT
|
||||
Name of the export function to be used.
|
||||
Defaults to `meta_schedule.builder.default_export`.
|
||||
initializer : Optional[Callable[[], None]]
|
||||
The initializer to be used for the worker processes.
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
if max_workers is None:
|
||||
max_workers = cpu_count(logical=True)
|
||||
logger.info("LocalBuilder: max_workers = %d", max_workers)
|
||||
|
||||
self.max_workers = max_workers
|
||||
self.timeout_sec = timeout_sec
|
||||
self.initializer = initializer
|
||||
self.f_build = f_build
|
||||
self.f_export = f_export
|
||||
self._sanity_check()
|
||||
|
||||
def build(self, build_inputs: list[BuilderInput]) -> list[BuilderResult]:
|
||||
results: list[BuilderResult] = []
|
||||
map_result: MapResult
|
||||
|
||||
# Here we restart the PopenPool everytime because of a known memory leak issue with the
|
||||
# PopenPool workers after a couple times of usage. We don't apply the same to runners to
|
||||
# avoid potential problem caused by async behaviour.
|
||||
pool = PopenPoolExecutor(
|
||||
max_workers=self.max_workers,
|
||||
timeout=self.timeout_sec,
|
||||
initializer=self.initializer,
|
||||
)
|
||||
|
||||
# Dispatch the build inputs to the worker processes.
|
||||
for map_result in pool.map_with_error_catching(
|
||||
lambda x: _worker_func(*x),
|
||||
[
|
||||
(
|
||||
self.f_build,
|
||||
self.f_export,
|
||||
build_input.mod,
|
||||
build_input.target,
|
||||
_serialize_params(build_input.params),
|
||||
)
|
||||
for build_input in build_inputs
|
||||
],
|
||||
):
|
||||
if map_result.status == StatusKind.COMPLETE:
|
||||
results.append(BuilderResult(map_result.value, None))
|
||||
elif map_result.status == StatusKind.TIMEOUT:
|
||||
results.append(
|
||||
BuilderResult(
|
||||
None,
|
||||
f"LocalBuilder: Timeout, killed after {self.timeout_sec} seconds",
|
||||
)
|
||||
)
|
||||
elif map_result.status == StatusKind.EXCEPTION:
|
||||
results.append(
|
||||
BuilderResult(
|
||||
None,
|
||||
"LocalBuilder: An exception occurred\n" + str(map_result.value),
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise ValueError("Unreachable: unexpected result: {map_result}")
|
||||
pool.shutdown()
|
||||
return results
|
||||
|
||||
def _sanity_check(self) -> None:
|
||||
def _check(f_build, f_export) -> None:
|
||||
get_global_func_with_default_on_worker(name=f_build, default=None)
|
||||
get_global_func_with_default_on_worker(name=f_export, default=None)
|
||||
|
||||
# Same reason for the single use PopenPool as mentioned above
|
||||
pool = PopenPoolExecutor(
|
||||
max_workers=self.max_workers,
|
||||
timeout=self.timeout_sec,
|
||||
initializer=self.initializer,
|
||||
)
|
||||
value = pool.submit(_check, self.f_build, self.f_export)
|
||||
value.result()
|
||||
pool.shutdown()
|
||||
|
||||
|
||||
def _worker_func(
|
||||
_f_build: None | str | T_BUILD,
|
||||
_f_export: None | str | T_EXPORT,
|
||||
mod: IRModule,
|
||||
target: Target,
|
||||
params: bytearray | None,
|
||||
) -> str:
|
||||
# Step 0. Get the registered functions
|
||||
f_build: T_BUILD = get_global_func_with_default_on_worker(
|
||||
_f_build,
|
||||
default_build,
|
||||
)
|
||||
f_export: T_EXPORT = get_global_func_with_default_on_worker(
|
||||
_f_export,
|
||||
default_export,
|
||||
)
|
||||
# Step 1. Build the IRModule
|
||||
rt_mod: Module = f_build(mod, target, _deserialize_params(params))
|
||||
# Step 2. Export the Module
|
||||
artifact_path: str = f_export(rt_mod)
|
||||
return artifact_path
|
||||
|
||||
|
||||
@register_global_func("s_tir.meta_schedule.builder.default_build")
|
||||
def default_build(mod: IRModule, target: Target, _params: dict[str, Tensor] | None) -> Module:
|
||||
"""Default build function.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : IRModule
|
||||
The IRModule to be built.
|
||||
target : Target
|
||||
The target to be built.
|
||||
_params : Optional[Dict[str, Tensor]]
|
||||
The parameters to be used for the build. Must be None.
|
||||
|
||||
Returns
|
||||
-------
|
||||
rt_mod : Module
|
||||
The built Module.
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import tvm.s_tir.tensor_intrin # pylint: disable=unused-import
|
||||
from tvm.driver import build as tvm_build
|
||||
from tvm.s_tir.transform import RemoveWeightLayoutRewriteBlock
|
||||
|
||||
# pylint: enable=import-outside-toplevel
|
||||
mod = RemoveWeightLayoutRewriteBlock(skip_tensor_rewrite=True)(mod)
|
||||
return tvm_build(mod, target=target)
|
||||
|
||||
|
||||
@register_global_func("s_tir.meta_schedule.builder.default_export")
|
||||
def default_export(mod: Module) -> str:
|
||||
"""Default export function.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : Module
|
||||
The Module to be exported.
|
||||
|
||||
Returns
|
||||
-------
|
||||
artifact_path : str
|
||||
The path to the exported Module.
|
||||
"""
|
||||
from tvm.support.tar import tar # pylint: disable=import-outside-toplevel
|
||||
|
||||
artifact_path = os.path.join(tempfile.mkdtemp(), "tvm_tmp_mod." + tar.output_format)
|
||||
mod.export_library(artifact_path, fcompile=tar)
|
||||
return artifact_path
|
||||
|
||||
|
||||
@register_global_func("s_tir.meta_schedule.builder.get_local_builder")
|
||||
def get_local_builder() -> LocalBuilder:
|
||||
"""Get the local builder.
|
||||
|
||||
Returns
|
||||
-------
|
||||
builder : LocalBuilder
|
||||
The local builder.
|
||||
"""
|
||||
return LocalBuilder()
|
||||
@@ -0,0 +1,24 @@
|
||||
# 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.cost_model package.
|
||||
"""
|
||||
|
||||
from .cost_model import CostModel, PyCostModel
|
||||
from .random_model import RandomModel
|
||||
from .xgb_model import XGBModel
|
||||
@@ -0,0 +1,260 @@
|
||||
# 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
|
||||
"""Meta Schedule CostModel."""
|
||||
|
||||
import ctypes
|
||||
from collections.abc import Callable
|
||||
from typing import Union
|
||||
|
||||
# isort: off
|
||||
from typing import Literal
|
||||
|
||||
# isort: on
|
||||
|
||||
import numpy as np # type: ignore
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from tvm.runtime import Object
|
||||
|
||||
from .. import _ffi_api
|
||||
from ..runner import RunnerResult
|
||||
from ..search_strategy import MeasureCandidate
|
||||
from ..tune_context import TuneContext
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.CostModel")
|
||||
class CostModel(Object):
|
||||
"""Cost model."""
|
||||
|
||||
CostModelType = Union["CostModel", Literal["xgb", "mlp", "random"]]
|
||||
|
||||
def load(self, path: str) -> None:
|
||||
"""Load the cost model from given file location.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : str
|
||||
The file path.
|
||||
"""
|
||||
_ffi_api.CostModelLoad(self, path) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def save(self, path: str) -> None:
|
||||
"""Save the cost model to given file location.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : str
|
||||
The file path.
|
||||
"""
|
||||
_ffi_api.CostModelSave(self, path) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def update(
|
||||
self,
|
||||
context: TuneContext,
|
||||
candidates: list[MeasureCandidate],
|
||||
results: list[RunnerResult],
|
||||
) -> None:
|
||||
"""Update the cost model given running results.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : TuneContext,
|
||||
The tuning context.
|
||||
candidates : List[MeasureCandidate]
|
||||
The measure candidates.
|
||||
results : List[RunnerResult]
|
||||
The running results of the measure candidates.
|
||||
"""
|
||||
_ffi_api.CostModelUpdate(self, context, candidates, results) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def predict(self, context: TuneContext, candidates: list[MeasureCandidate]) -> np.ndarray:
|
||||
"""Predict normalized score with the cost model.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : TuneContext,
|
||||
The tuning context.
|
||||
candidates : List[MeasureCandidate]
|
||||
The measure candidates.
|
||||
|
||||
Return
|
||||
------
|
||||
result : np.ndarray
|
||||
The predicted normalized score.
|
||||
"""
|
||||
n = len(candidates)
|
||||
results = np.zeros(shape=(n,), dtype="float64")
|
||||
_ffi_api.CostModelPredict( # type: ignore # pylint: disable=no-member
|
||||
self,
|
||||
context,
|
||||
candidates,
|
||||
results.ctypes.data_as(ctypes.c_void_p),
|
||||
)
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def create(
|
||||
kind: Literal["xgb", "mlp", "random", "none"],
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> "CostModel":
|
||||
"""Create a CostModel.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
kind : Literal["xgb", "mlp", "random", "none"]
|
||||
The kind of the cost model. Can be "xgb", "mlp", "random" or "none".
|
||||
|
||||
Returns
|
||||
-------
|
||||
cost_model : CostModel
|
||||
The created cost model.
|
||||
"""
|
||||
from . import RandomModel, XGBModel # pylint: disable=import-outside-toplevel
|
||||
|
||||
if kind == "xgb":
|
||||
return XGBModel(*args, **kwargs) # type: ignore
|
||||
|
||||
# params only relevant to XGBModel
|
||||
_xgb_params = ["num_tuning_cores", "tree_method"]
|
||||
|
||||
for param in _xgb_params:
|
||||
if param in kwargs:
|
||||
kwargs.pop(param)
|
||||
|
||||
if kind == "random":
|
||||
return RandomModel(*args, **kwargs) # type: ignore
|
||||
if kind == "mlp":
|
||||
from .mlp_model import ( # type: ignore # pylint: disable=import-outside-toplevel
|
||||
MLPModel,
|
||||
)
|
||||
|
||||
return MLPModel(*args, **kwargs) # type: ignore
|
||||
if kind == "none":
|
||||
return None # no cost model required
|
||||
raise ValueError(f"Unknown CostModel: {kind}")
|
||||
|
||||
|
||||
create = CostModel.create # pylint: disable=invalid-name
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.PyCostModel")
|
||||
class _PyCostModel(CostModel):
|
||||
"""
|
||||
A TVM object cost model to support customization on the python side.
|
||||
This is NOT the user facing class for function overloading inheritance.
|
||||
|
||||
See also: PyCostModel
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
f_load: Callable | None = None,
|
||||
f_save: Callable | None = None,
|
||||
f_update: Callable | None = None,
|
||||
predict_func: Callable | None = None,
|
||||
):
|
||||
"""Constructor."""
|
||||
|
||||
def f_predict(context: TuneContext, candidates: list[MeasureCandidate], return_ptr) -> None:
|
||||
n = len(candidates)
|
||||
return_ptr = ctypes.cast(return_ptr, ctypes.POINTER(ctypes.c_double))
|
||||
array_wrapper = np.ctypeslib.as_array(return_ptr, shape=(n,))
|
||||
res = predict_func(context, candidates)
|
||||
array_wrapper[:] = res
|
||||
assert array_wrapper.dtype == "float64", (
|
||||
"ValueError: Invalid data type returned from CostModel Predict!"
|
||||
)
|
||||
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.CostModelPyCostModel, # type: ignore # pylint: disable=no-member
|
||||
f_load,
|
||||
f_save,
|
||||
f_update,
|
||||
f_predict,
|
||||
)
|
||||
|
||||
|
||||
class PyCostModel:
|
||||
"""
|
||||
An abstract cost model with customized methods 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": _PyCostModel,
|
||||
"methods": ["load", "save", "update", "predict"],
|
||||
}
|
||||
|
||||
def load(self, path: str) -> None:
|
||||
"""Load the cost model from given file location.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : str
|
||||
The file path.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def save(self, path: str) -> None:
|
||||
"""Save the cost model to given file location.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : str
|
||||
The file path.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def update(
|
||||
self,
|
||||
context: TuneContext,
|
||||
candidates: list[MeasureCandidate],
|
||||
results: list[RunnerResult],
|
||||
) -> None:
|
||||
"""Update the cost model given running results.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : TuneContext,
|
||||
The tuning context.
|
||||
candidates : List[MeasureCandidate]
|
||||
The measure candidates.
|
||||
results : List[RunnerResult]
|
||||
The running results of the measure candidates.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def predict(self, context: TuneContext, candidates: list[MeasureCandidate]) -> np.ndarray:
|
||||
"""Predict given the measure candidates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : TuneContext,
|
||||
The tuning context.
|
||||
candidates : List[MeasureCandidate]
|
||||
The measure candidates.
|
||||
|
||||
Return
|
||||
------
|
||||
result : np.ndarray
|
||||
The predicted normalized score.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,40 @@
|
||||
# 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.
|
||||
"""Cost model metrics for meta schedule"""
|
||||
|
||||
import numpy as np # type: ignore
|
||||
|
||||
|
||||
def max_curve(trial_scores: np.ndarray) -> np.ndarray:
|
||||
"""f(n) = max([s[i] fo i < n])
|
||||
|
||||
Parameters
|
||||
----------
|
||||
trial_scores : List[float]
|
||||
the score of i-th trial
|
||||
|
||||
Returns
|
||||
-------
|
||||
curve : np.ndarray
|
||||
A vector, the max-curve function values
|
||||
"""
|
||||
ret = np.empty(len(trial_scores))
|
||||
keep = -1e9
|
||||
for i, score in enumerate(trial_scores):
|
||||
keep = max(keep, score)
|
||||
ret[i] = keep
|
||||
return ret
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,133 @@
|
||||
# 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.
|
||||
"""
|
||||
Random cost model
|
||||
"""
|
||||
|
||||
from tvm.ir.utils import derived_object
|
||||
|
||||
from ..cost_model import PyCostModel
|
||||
from ..runner import RunnerResult
|
||||
from ..search_strategy import MeasureCandidate
|
||||
from ..tune_context import TuneContext
|
||||
|
||||
|
||||
@derived_object
|
||||
class RandomModel(PyCostModel):
|
||||
"""Random cost model
|
||||
|
||||
Parameters
|
||||
----------
|
||||
random_state : Union[Tuple[str, np.ndarray, int, int, float], dict]
|
||||
The random state of the random number generator.
|
||||
path : Optional[str]
|
||||
The path of the random cost model.
|
||||
max_range : Optional[int]
|
||||
The maximum range of random results, [0, max_range].
|
||||
|
||||
Reference
|
||||
---------
|
||||
https://numpy.org/doc/stable/reference/random/generated/numpy.random.get_state.html
|
||||
"""
|
||||
|
||||
import numpy as np # type: ignore # pylint: disable=import-outside-toplevel
|
||||
|
||||
random_state: tuple[str, np.ndarray, int, int, float] | dict
|
||||
path: str | None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
seed: int | None = None,
|
||||
path: str | None = None,
|
||||
max_range: int | None = 100,
|
||||
):
|
||||
import numpy as np # type: ignore # pylint: disable=import-outside-toplevel
|
||||
|
||||
super().__init__()
|
||||
if path is not None:
|
||||
self.load(path)
|
||||
else:
|
||||
np.random.seed(seed)
|
||||
self.random_state = np.random.get_state()
|
||||
self.max_range = max_range
|
||||
|
||||
def load(self, path: str) -> None:
|
||||
"""Load the cost model from given file location.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : str
|
||||
The file path.
|
||||
"""
|
||||
import numpy as np # type: ignore # pylint: disable=import-outside-toplevel
|
||||
|
||||
self.random_state = tuple(np.load(path, allow_pickle=True)) # type: ignore
|
||||
|
||||
def save(self, path: str) -> None:
|
||||
"""Save the cost model to given file location.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : str
|
||||
The file path.
|
||||
"""
|
||||
import numpy as np # type: ignore # pylint: disable=import-outside-toplevel
|
||||
|
||||
np.save(path, np.array(self.random_state, dtype=object), allow_pickle=True)
|
||||
|
||||
def update(
|
||||
self,
|
||||
context: TuneContext,
|
||||
candidates: list[MeasureCandidate],
|
||||
results: list[RunnerResult],
|
||||
) -> None:
|
||||
"""Update the cost model given running results.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : TuneContext,
|
||||
The tuning context.
|
||||
candidates : List[MeasureCandidate]
|
||||
The measure candidates.
|
||||
results : List[RunnerResult]
|
||||
The running results of the measure candidates.
|
||||
"""
|
||||
|
||||
def predict(self, context: TuneContext, candidates: list[MeasureCandidate]) -> np.ndarray: # type: ignore # pylint: disable=used-before-assignment
|
||||
"""Update the cost model given running results.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : TuneContext,
|
||||
The tuning context.
|
||||
candidates : List[MeasureCandidate]
|
||||
The measure candidates.
|
||||
|
||||
Return
|
||||
------
|
||||
result : np.ndarray
|
||||
The predicted running results.
|
||||
"""
|
||||
import numpy as np # type: ignore # pylint: disable=import-outside-toplevel
|
||||
|
||||
np.random.set_state(self.random_state)
|
||||
# TODO(@zxybazh): Use numpy's RandState object:
|
||||
# https://numpy.org/doc/1.16/reference/generated/numpy.random.RandomState.html#numpy.random.RandomState
|
||||
result = np.random.rand(len(candidates)) * self.max_range # type: ignore
|
||||
self.random_state = np.random.get_state()
|
||||
return result
|
||||
@@ -0,0 +1,867 @@
|
||||
# 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.
|
||||
"""XGBoost-based cost model"""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from collections import OrderedDict
|
||||
from collections.abc import Callable
|
||||
from itertools import chain as itertools_chain
|
||||
from typing import TYPE_CHECKING, Any, Literal, NamedTuple, Optional
|
||||
|
||||
import numpy as np # type: ignore
|
||||
|
||||
from tvm.ir.utils import derived_object
|
||||
from tvm.support.tar import tar, untar
|
||||
|
||||
from ....runtime import Tensor
|
||||
from ..cost_model import PyCostModel
|
||||
from ..feature_extractor import FeatureExtractor
|
||||
from ..logging import get_logger
|
||||
from ..runner import RunnerResult
|
||||
from ..search_strategy import MeasureCandidate
|
||||
from ..utils import cpu_count, shash2hex
|
||||
from .metric import max_curve
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import xgboost as xgb # type: ignore
|
||||
from xgboost.callback import TrainingCallback # type: ignore
|
||||
|
||||
from ..tune_context import TuneContext
|
||||
|
||||
|
||||
logger = get_logger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
def make_metric_sorter(focused_metric):
|
||||
"""Make sure the focused metric is the first one."""
|
||||
|
||||
def metric_name_for_sort(name):
|
||||
if focused_metric == name:
|
||||
return "!" + name
|
||||
return name
|
||||
|
||||
def sort_key(key):
|
||||
key, _ = key
|
||||
return metric_name_for_sort(key)
|
||||
|
||||
return sort_key
|
||||
|
||||
|
||||
class PackSum:
|
||||
"""The pack-sum format
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dmatrix : xgb.DMatrix
|
||||
A float64 array of shape [n, m],
|
||||
where `n` is the packed number of blocks,
|
||||
and `m` is the length of feature vector on each block
|
||||
ids : np.ndarray
|
||||
An int64 array of shape [n] containing nonnegative integers,
|
||||
indicating which the index of a sample that a block belongs to
|
||||
"""
|
||||
|
||||
dmatrix: "xgb.DMatrix" # type: ignore # pylint: disable=invalid-name
|
||||
ids: np.ndarray
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
xs: list[np.ndarray], # pylint: disable=invalid-name
|
||||
ys: np.ndarray | None, # pylint: disable=invalid-name
|
||||
):
|
||||
"""Create PackSum format given a batch of samples
|
||||
|
||||
Parameters
|
||||
----------
|
||||
xs : List[np.ndarray]
|
||||
A batch of input samples
|
||||
ys : Optional[List[float]]
|
||||
A batch of labels. None means no labels available.
|
||||
"""
|
||||
import xgboost as xgb # type: ignore # pylint: disable=import-outside-toplevel
|
||||
|
||||
repeats = [x.shape[0] for x in xs]
|
||||
xs = np.concatenate(xs, axis=0)
|
||||
self.ids = np.concatenate([[i] * repeat for i, repeat in enumerate(repeats)], axis=0)
|
||||
if ys is None:
|
||||
self.dmatrix = xgb.DMatrix(data=xs, label=None)
|
||||
else:
|
||||
ys = np.concatenate([[y] * repeat for y, repeat in zip(ys, repeats)], axis=0)
|
||||
self.dmatrix = xgb.DMatrix(data=xs, label=ys)
|
||||
self.dmatrix.set_weight(ys)
|
||||
|
||||
def predict_with_score(self, pred: np.ndarray) -> np.ndarray:
|
||||
"""Predict the labels given the block level prediction scores.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pred : np.ndarray
|
||||
The block level predictions
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : np.ndarray
|
||||
The predictions for each candidate.
|
||||
"""
|
||||
return np.bincount(self.ids, weights=pred)
|
||||
|
||||
def obj_square_error(self, ys_pred: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""Implement square error loss on pack-sum format as
|
||||
a custom objective function for xgboost.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ys_pred: np.ndarray
|
||||
The predictions
|
||||
|
||||
Returns
|
||||
-------
|
||||
gradient: np.ndarray
|
||||
The gradient according to the xgboost format
|
||||
hessian: np.ndarray
|
||||
The hessian according to the xgboost format
|
||||
"""
|
||||
# Making prediction
|
||||
ys_pred = self.predict_with_score(ys_pred)
|
||||
# Propagate prediction to each block
|
||||
ys_pred = ys_pred[self.ids] # pylint: disable=invalid-sequence-index
|
||||
# The gradient and hessian
|
||||
ys = self.dmatrix.get_label() # type: ignore # pylint: disable=invalid-name
|
||||
gradient = ys_pred - ys
|
||||
hessian = np.ones_like(gradient)
|
||||
return gradient * ys, hessian * ys
|
||||
|
||||
def rmse(self, ys_pred: np.ndarray) -> tuple[str, float]:
|
||||
"""Evaluate RMSE (rooted mean square error) in the pack-sum format
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ys_pred: np.ndarray
|
||||
The raw predictions
|
||||
|
||||
Returns
|
||||
-------
|
||||
name: str
|
||||
The name of the metric
|
||||
score: float
|
||||
The score of the metric
|
||||
"""
|
||||
# Making prediction
|
||||
ys_pred = self.predict_with_score(ys_pred)
|
||||
# Propagate prediction to each block
|
||||
ys_pred = ys_pred[self.ids] # pylint: disable=invalid-sequence-index
|
||||
# The RMSE
|
||||
ys = self.dmatrix.get_label() # type: ignore # pylint: disable=invalid-name
|
||||
square_error = np.square(ys_pred - ys)
|
||||
rmse = np.sqrt(square_error.mean())
|
||||
return "p-rmse", rmse
|
||||
|
||||
def average_peak_score(
|
||||
self,
|
||||
ys_pred: np.ndarray,
|
||||
n: int,
|
||||
) -> tuple[str, float]:
|
||||
"""Evaluate average-peak-score@N in the pack-sum format
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ys_pred: np.ndarray
|
||||
The raw prediction
|
||||
n : int
|
||||
The N in average-peak-score@N
|
||||
|
||||
Returns
|
||||
-------
|
||||
name: str
|
||||
The name of the metric
|
||||
score: float
|
||||
The score of the metric
|
||||
"""
|
||||
ys = self.dmatrix.get_label() # type: ignore # pylint: disable=invalid-name
|
||||
ys = self.predict_with_score(ys) # type: ignore # pylint: disable=invalid-name
|
||||
ys = ys / np.unique(self.ids, return_counts=True)[1] # type: ignore # pylint: disable=invalid-name
|
||||
ys_pred = self.predict_with_score(ys_pred)
|
||||
trials = np.argsort(ys_pred)[::-1][:n]
|
||||
trial_scores = ys[trials]
|
||||
curve = max_curve(trial_scores) / np.max(ys)
|
||||
score = np.mean(curve)
|
||||
return f"a-peak@{n}", score
|
||||
|
||||
|
||||
class XGBConfig(NamedTuple):
|
||||
"""XGBoost model configuration
|
||||
|
||||
Reference: https://xgboost.readthedocs.io/en/stable/parameter.html
|
||||
|
||||
Parameters
|
||||
----------
|
||||
max_depth : int
|
||||
The maximum depth.
|
||||
gamma : float
|
||||
The gamma.
|
||||
min_child_weight : float
|
||||
The minimum child weight.
|
||||
eta : float
|
||||
The eta, learning rate.
|
||||
seed : int
|
||||
The random seed.
|
||||
nthread : Optional[int],
|
||||
The number of threads to use.
|
||||
Default is None, which means to use physical number of cores.
|
||||
tree_method : Literal["auto", "exact", "approx", "hist", "gpu_hist"]
|
||||
The tree construction algorithm used in XGBoost.
|
||||
"""
|
||||
|
||||
max_depth: int = 10
|
||||
gamma: float = 0.001
|
||||
min_child_weight: float = 0
|
||||
eta: float = 0.2
|
||||
seed: int = 43
|
||||
nthread: int | None = None
|
||||
tree_method: Literal["auto", "exact", "approx", "hist", "gpu_hist"] = "auto"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert to dict"""
|
||||
|
||||
return {
|
||||
"max_depth": self.max_depth,
|
||||
"gamma": self.gamma,
|
||||
"min_child_weight": self.min_child_weight,
|
||||
"eta": self.eta,
|
||||
"seed": self.seed,
|
||||
"nthread": self.nthread,
|
||||
"tree_method": self.tree_method,
|
||||
}
|
||||
|
||||
|
||||
class FeatureGroup:
|
||||
"""Feature group
|
||||
|
||||
Parameters
|
||||
----------
|
||||
group_hash : str
|
||||
The hash of the group
|
||||
features : List[np.ndarray]
|
||||
The features
|
||||
costs : List[float]
|
||||
The costs
|
||||
min_cost : float
|
||||
The minimum cost
|
||||
"""
|
||||
|
||||
group_hash: str
|
||||
features: list[np.ndarray]
|
||||
costs: np.ndarray
|
||||
min_cost: float
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
group_hash: str,
|
||||
features: list[np.ndarray],
|
||||
costs: np.ndarray,
|
||||
) -> None:
|
||||
self.group_hash = group_hash
|
||||
self.features = features
|
||||
self.costs = costs
|
||||
self.min_cost = np.min(costs)
|
||||
|
||||
def append(
|
||||
self,
|
||||
features: list[np.ndarray],
|
||||
costs: np.ndarray,
|
||||
) -> None:
|
||||
self.features.extend(features)
|
||||
self.costs = np.append(self.costs, costs)
|
||||
self.min_cost = np.min(self.costs)
|
||||
|
||||
|
||||
@derived_object
|
||||
class XGBModel(PyCostModel):
|
||||
"""XGBoost model
|
||||
|
||||
Parameters
|
||||
----------
|
||||
extractor : FeatureExtractor
|
||||
The feature extractor for the model.
|
||||
config : XGBConfig
|
||||
The XGBoost model config.
|
||||
num_warmup_samples : int
|
||||
The number of samples that are used for warmup, i.e., the first few samples are predicted
|
||||
with random results.
|
||||
early_stopping_rounds : int
|
||||
The number of rounds for early stopping.
|
||||
verbose_eval : int
|
||||
The verbose level when doing evaluation.
|
||||
average_peak_n : int
|
||||
The number to calculate average peak score.
|
||||
adaptive_training : bool
|
||||
Whether use adaptive training to reduce tuning time.
|
||||
"""
|
||||
|
||||
# feature extractor
|
||||
extractor: FeatureExtractor
|
||||
# xgboost model config
|
||||
config: XGBConfig
|
||||
# behavior of randomness
|
||||
num_warmup_samples: int
|
||||
# evaluation
|
||||
early_stopping_rounds: int
|
||||
verbose_eval: int
|
||||
average_peak_n: int
|
||||
# states
|
||||
data: dict[str, FeatureGroup]
|
||||
data_size: int
|
||||
booster: Optional["xgb.Booster"]
|
||||
# adaptive training
|
||||
adaptive_training: bool
|
||||
last_train_size: int
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
# feature extractor
|
||||
extractor: FeatureExtractor.FeatureExtractorType = "per-store-feature",
|
||||
# xgboost model config
|
||||
config: XGBConfig = XGBConfig(),
|
||||
# random result before enough samples
|
||||
num_warmup_samples: int = 100,
|
||||
# evaluation
|
||||
early_stopping_rounds: int = 50,
|
||||
verbose_eval: int = 25,
|
||||
average_peak_n: int = 32,
|
||||
adaptive_training: bool = True,
|
||||
num_tuning_cores: int | None = None,
|
||||
tree_method: Literal["auto", "exact", "approx", "hist", "gpu_hist"] | None = None,
|
||||
):
|
||||
super().__init__()
|
||||
if not isinstance(extractor, FeatureExtractor):
|
||||
extractor = FeatureExtractor.create(extractor)
|
||||
# feature extractor
|
||||
self.extractor = extractor
|
||||
# model-related
|
||||
if config.nthread is None:
|
||||
# use physical core number
|
||||
if num_tuning_cores is None:
|
||||
config = config._replace(nthread=cpu_count(logical=False))
|
||||
else:
|
||||
config = config._replace(nthread=num_tuning_cores)
|
||||
|
||||
if tree_method is not None:
|
||||
config._replace(tree_method=tree_method)
|
||||
|
||||
self.config = config
|
||||
# behavior of randomness
|
||||
self.num_warmup_samples = num_warmup_samples
|
||||
# evaluation
|
||||
self.early_stopping_rounds = early_stopping_rounds
|
||||
self.verbose_eval = verbose_eval
|
||||
self.average_peak_n = average_peak_n
|
||||
# states
|
||||
self.data = OrderedDict()
|
||||
self.data_size = 0
|
||||
self.booster = None
|
||||
# adaptive training
|
||||
self.adaptive_training = adaptive_training
|
||||
self.last_train_size = 0
|
||||
|
||||
def load(self, path: str) -> None:
|
||||
"""Load the cost model from given file location.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : str
|
||||
The file path.
|
||||
|
||||
Note
|
||||
----
|
||||
Since XGBoost model trains from scratch, each time this method loads the model together with
|
||||
previously cached feature vectors and results, so that the subsequent training process could
|
||||
use all the existing data being stored on disk.
|
||||
"""
|
||||
import xgboost as xgb # pylint: disable=import-outside-toplevel
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
model_path = os.path.join(tmp_dir, "model.bin")
|
||||
data_path = os.path.join(tmp_dir, "data.npy")
|
||||
# Step 1. Untar
|
||||
untar(path, tmp_dir)
|
||||
# Step 2. Load data
|
||||
data = OrderedDict()
|
||||
data_size = 0
|
||||
for group_hash, features, costs in np.load(data_path, allow_pickle=True):
|
||||
data[group_hash] = FeatureGroup(
|
||||
group_hash=group_hash,
|
||||
features=list(features),
|
||||
costs=costs,
|
||||
)
|
||||
data_size += len(costs)
|
||||
# Step 3. Load the model
|
||||
if os.path.exists(model_path):
|
||||
booster = xgb.Booster()
|
||||
booster.load_model(model_path)
|
||||
else:
|
||||
self.booster = None
|
||||
self.data = data
|
||||
self.data_size = data_size
|
||||
self.booster = booster
|
||||
|
||||
def save(self, path: str) -> None:
|
||||
"""Save the cost model to given file location.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : str
|
||||
The file path.
|
||||
|
||||
Note
|
||||
----
|
||||
Since XGBoost model trains from scratch, each time this method saves the model together with
|
||||
previously cached feature vectors and results, so that the subsequent training process could
|
||||
use all the existing data being stored on disk.
|
||||
"""
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
model_path = os.path.join(tmp_dir, "model.bin")
|
||||
data_path = os.path.join(tmp_dir, "data.npy")
|
||||
# Step 1. Save the model
|
||||
booster = self.booster
|
||||
if booster is not None:
|
||||
booster.save_model(model_path)
|
||||
else:
|
||||
model_path = None
|
||||
# Step 2. Save data
|
||||
data = [
|
||||
(
|
||||
g.group_hash,
|
||||
g.features,
|
||||
g.costs,
|
||||
)
|
||||
for g in self.data.values()
|
||||
]
|
||||
np.save(
|
||||
file=data_path,
|
||||
arr=np.array(data, dtype=object),
|
||||
)
|
||||
# Step 3. Tar it
|
||||
tar(path, [x for x in [model_path, data_path] if x is not None])
|
||||
logger.info("Saved XGBModel to %s", path)
|
||||
|
||||
def update(
|
||||
self,
|
||||
context: "TuneContext",
|
||||
candidates: list[MeasureCandidate],
|
||||
results: list[RunnerResult],
|
||||
) -> None:
|
||||
"""Update the cost model given running results.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : TuneContext
|
||||
The tuning context.
|
||||
candidates : List[MeasureCandidate]
|
||||
The measure candidates.
|
||||
results : List[RunnerResult]
|
||||
The running results of the measure candidates.
|
||||
"""
|
||||
assert len(candidates) == len(results)
|
||||
if len(candidates) == 0:
|
||||
return
|
||||
|
||||
# Step 1. Get the feature group
|
||||
new_group_hash = shash2hex(context.mod)
|
||||
group = self.data.get(new_group_hash, None)
|
||||
|
||||
# Step 2. Extract features
|
||||
def _feature(x: Tensor) -> np.ndarray:
|
||||
return x.numpy().astype("float32")
|
||||
|
||||
def _mean_cost(x: RunnerResult) -> float:
|
||||
if not x.run_secs:
|
||||
return 1e10
|
||||
return float(np.median([float(s) for s in x.run_secs]))
|
||||
|
||||
new_features = [_feature(x) for x in self.extractor.extract_from(context, candidates)]
|
||||
new_mean_costs = [_mean_cost(x) for x in results]
|
||||
|
||||
# Filter instances with no features
|
||||
new_mean_costs = [c for i, c in enumerate(new_mean_costs) if len(new_features[i]) != 0]
|
||||
new_mean_costs_np = np.array(new_mean_costs).astype("float32")
|
||||
new_features = [f for f in new_features if len(f) != 0]
|
||||
if not new_features:
|
||||
return
|
||||
|
||||
# Steps 3. Run validation
|
||||
if group is not None and self.booster is not None:
|
||||
logger.debug(
|
||||
"XGB validation: %s",
|
||||
"\t".join(
|
||||
f"{key}: {score:.6f}"
|
||||
for key, score in self._validate(
|
||||
xs=new_features,
|
||||
ys=group.min_cost / new_mean_costs_np,
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
# Step 4. Add the features into the data points
|
||||
if group is None:
|
||||
group = FeatureGroup(
|
||||
group_hash=new_group_hash,
|
||||
features=new_features,
|
||||
costs=new_mean_costs_np,
|
||||
)
|
||||
else:
|
||||
group.append(new_features, new_mean_costs_np)
|
||||
self.data[new_group_hash] = group
|
||||
self.data_size += len(new_features)
|
||||
|
||||
if (
|
||||
self.adaptive_training
|
||||
and self.data_size - self.last_train_size < self.last_train_size / 5
|
||||
):
|
||||
# Set a training threshold related to `last_train_size` to reduce the training
|
||||
# overhead when there're too many results
|
||||
return
|
||||
self.last_train_size = self.data_size
|
||||
|
||||
# Step 5. Re-train the model
|
||||
with np.errstate(divide="ignore", invalid="ignore"):
|
||||
feature_list = list(
|
||||
itertools_chain.from_iterable([g.features for g in self.data.values()])
|
||||
)
|
||||
cost_ratio_list = [
|
||||
np.divide(g.min_cost, g.costs, out=np.zeros_like(g.costs), where=g.costs != 0)
|
||||
for g in self.data.values()
|
||||
]
|
||||
cost_ratios = np.concatenate(cost_ratio_list, axis=0)
|
||||
|
||||
self._train(xs=feature_list, ys=cost_ratios)
|
||||
|
||||
def predict(
|
||||
self,
|
||||
context: "TuneContext",
|
||||
candidates: list[MeasureCandidate],
|
||||
) -> np.ndarray:
|
||||
"""Predict the normalized score using the cost model.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : TuneContext
|
||||
The tuning context.
|
||||
candidates : List[MeasureCandidate]
|
||||
The measure candidates.
|
||||
|
||||
Return
|
||||
------
|
||||
result : np.ndarray
|
||||
The predicted normalized score.
|
||||
"""
|
||||
if self.data_size >= self.num_warmup_samples and self.booster is not None:
|
||||
ret = self._predict(
|
||||
xs=[
|
||||
x.numpy().astype("float32")
|
||||
for x in self.extractor.extract_from(
|
||||
context,
|
||||
candidates,
|
||||
)
|
||||
]
|
||||
)
|
||||
else:
|
||||
ret = np.random.uniform(
|
||||
low=0,
|
||||
high=1,
|
||||
size=(len(candidates),),
|
||||
)
|
||||
return ret.astype("float64")
|
||||
|
||||
def _train( # type: ignore # pylint: disable=invalid-name
|
||||
self,
|
||||
xs: list[np.ndarray],
|
||||
ys: np.ndarray,
|
||||
) -> None:
|
||||
import xgboost as xgb # type: ignore # pylint: disable=import-outside-toplevel
|
||||
|
||||
self.d_train = PackSum(xs=xs, ys=ys)
|
||||
|
||||
def obj(ys_pred: np.ndarray, d_train: "xgb.DMatrix"): # type: ignore # pylint: disable = unused-argument
|
||||
return self.d_train.obj_square_error(ys_pred)
|
||||
|
||||
def rmse(ys_pred: np.ndarray, d_train: "xgb.DMatrix"): # type: ignore # pylint: disable = unused-argument
|
||||
return self.d_train.rmse(ys_pred)
|
||||
|
||||
def avg_peak_score(ys_pred: np.ndarray, d_train: "xgb.DMatrix"): # type: ignore # pylint: disable = unused-argument
|
||||
return self.d_train.average_peak_score(ys_pred, self.average_peak_n)
|
||||
|
||||
self.booster = xgb.train(
|
||||
self.config.to_dict(),
|
||||
self.d_train.dmatrix,
|
||||
num_boost_round=10000,
|
||||
obj=obj,
|
||||
callbacks=[
|
||||
_get_custom_call_back(
|
||||
early_stopping_rounds=self.early_stopping_rounds,
|
||||
verbose_eval=self.verbose_eval,
|
||||
fevals=[rmse, avg_peak_score],
|
||||
evals=[(self.d_train.dmatrix, "tr")],
|
||||
cvfolds=None,
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
del self.d_train
|
||||
|
||||
def _predict( # type: ignore # pylint: disable=invalid-name
|
||||
self,
|
||||
xs: list[np.ndarray],
|
||||
) -> np.ndarray:
|
||||
d_test = PackSum(xs=xs, ys=None)
|
||||
pred = self.booster.predict(d_test.dmatrix)
|
||||
ret = d_test.predict_with_score(pred)
|
||||
return ret
|
||||
|
||||
def _validate( # type: ignore # pylint: disable=invalid-name
|
||||
self,
|
||||
xs: list[np.ndarray],
|
||||
ys: np.ndarray,
|
||||
) -> list[tuple[str, float]]:
|
||||
"""Evaluate the score of inputs.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
xs : List[np.ndarray]
|
||||
A batch of input samples
|
||||
ys : List[float]
|
||||
A batch of labels
|
||||
|
||||
Returns
|
||||
-------
|
||||
scores: np.ndarray
|
||||
The predicted result for all inputs.
|
||||
"""
|
||||
assert self.booster is not None
|
||||
|
||||
d_valid = PackSum(xs=xs, ys=ys)
|
||||
|
||||
def average_peak_score(ys_pred: np.ndarray):
|
||||
return d_valid.average_peak_score(ys_pred, n=self.average_peak_n)
|
||||
|
||||
ys_pred = self.booster.predict(d_valid.dmatrix)
|
||||
eval_result: list[tuple[str, float]] = [
|
||||
feval(ys_pred)
|
||||
for feval in (
|
||||
average_peak_score,
|
||||
d_valid.rmse,
|
||||
)
|
||||
]
|
||||
eval_result.sort(key=make_metric_sorter("p-rmse"))
|
||||
return eval_result
|
||||
|
||||
|
||||
def _get_custom_call_back(
|
||||
early_stopping_rounds: int,
|
||||
verbose_eval: int,
|
||||
fevals: list[Callable],
|
||||
evals: list[tuple["xgb.DMatrix", str]],
|
||||
focused_metric: str = "tr-p-rmse",
|
||||
cvfolds: list["xgb.training.CVPack"] | None = None,
|
||||
) -> "TrainingCallback":
|
||||
"""Get a customized callback function for XGBoost. Work around xgboost import."""
|
||||
|
||||
def optional_xgboost_callback(cls):
|
||||
"""Decorator for importing TrainingCallback from xgboost"""
|
||||
# pylint:disable = import-outside-toplevel
|
||||
try:
|
||||
from xgboost.callback import TrainingCallback # type: ignore
|
||||
# pylint:enable = import-outside-toplevel
|
||||
except ImportError:
|
||||
|
||||
class TrainingCallback: # type: ignore
|
||||
pass
|
||||
|
||||
class OptXGBoostCustomCallback(cls, TrainingCallback): # type: ignore
|
||||
pass
|
||||
|
||||
return OptXGBoostCustomCallback
|
||||
|
||||
@optional_xgboost_callback
|
||||
class XGBoostCustomCallback:
|
||||
"""Custom callback class for xgboost to support multiple custom evaluation functions"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
early_stopping_rounds: int,
|
||||
verbose_eval: int,
|
||||
fevals: list[Callable],
|
||||
evals: list[tuple["xgb.DMatrix", str]],
|
||||
focused_metric: str = "tr-p-rmse",
|
||||
cvfolds: list["xgb.training.CVPack"] | None = None,
|
||||
):
|
||||
self.early_stopping_rounds = early_stopping_rounds
|
||||
self.verbose_eval = verbose_eval
|
||||
self.fevals = fevals
|
||||
self.evals = evals
|
||||
self.state: dict[str, Any] = {}
|
||||
self.focused_metric = focused_metric
|
||||
self.sort_key = make_metric_sorter(focused_metric=focused_metric)
|
||||
self.cvfolds = cvfolds
|
||||
if cvfolds is not None:
|
||||
self.aggregated_cv = None
|
||||
|
||||
def __call__(self, env: "xgb.core.CallbackEnv"):
|
||||
# Compatibility with xgboost < 1.3
|
||||
return self.after_iteration(env.model, env.iteration, env.evaluation_result_list)
|
||||
|
||||
def init(self, model: "xgb.Booster"):
|
||||
"""Internal function for initialization"""
|
||||
booster: xgb.Booster = model
|
||||
self.state["best_iteration"] = 0
|
||||
self.state["best_score"] = float("inf")
|
||||
if booster is None:
|
||||
assert self.cvfolds is not None
|
||||
return
|
||||
if booster.attr("best_score") is not None:
|
||||
self.state["best_score"] = float(booster.attr("best_score"))
|
||||
self.state["best_iteration"] = int(booster.attr("best_iteration"))
|
||||
self.state["best_msg"] = booster.attr("best_msg")
|
||||
else:
|
||||
booster.set_attr(best_iteration=str(self.state["best_iteration"]))
|
||||
booster.set_attr(best_score=str(self.state["best_score"]))
|
||||
|
||||
def after_iteration(self, model: "xgb.Booster", epoch: int, evals_log: dict): # pylint: disable = unused-argument
|
||||
"""Internal function for after_iteration"""
|
||||
# pylint:disable = import-outside-toplevel
|
||||
try:
|
||||
from xgboost.callback import _fmt_metric # type: ignore
|
||||
except ImportError:
|
||||
# Compatibility with xgboost >= 1.6
|
||||
|
||||
def _fmt_metric(value, show_stdv=True):
|
||||
if len(value) == 2:
|
||||
return f"{value[0]}:{value[1]:.5f}"
|
||||
if len(value) == 3:
|
||||
if show_stdv:
|
||||
return f"{value[0]}:{value[1]:.5f}+{value[2]:.5f}"
|
||||
return f"{value[0]}:{value[1]:.5f}"
|
||||
raise ValueError("wrong metric value", value)
|
||||
|
||||
import xgboost as xgb
|
||||
|
||||
# make it compatible with xgboost<1.7
|
||||
try:
|
||||
from xgboost import rabit as collective # type: ignore
|
||||
except ImportError:
|
||||
from xgboost import collective # type: ignore
|
||||
|
||||
try:
|
||||
from xgboost.training import aggcv # type: ignore
|
||||
except ImportError:
|
||||
from xgboost.callback import _aggcv as aggcv # type: ignore
|
||||
|
||||
# pylint:enable = import-outside-toplevel
|
||||
if not self.state:
|
||||
self.init(model)
|
||||
booster: xgb.Booster = model
|
||||
iteration: int = epoch
|
||||
cvfolds: list[xgb.training.CVPack] = self.cvfolds
|
||||
##### Evaluation #####
|
||||
# `eval_result` is a list of (key, score)
|
||||
eval_result: list[tuple[str, float]] = []
|
||||
if cvfolds is None:
|
||||
eval_result = list(
|
||||
itertools_chain.from_iterable(
|
||||
[
|
||||
(key, float(value))
|
||||
for key, value in map(
|
||||
lambda x: x.split(":"),
|
||||
booster.eval_set(
|
||||
evals=self.evals,
|
||||
iteration=iteration,
|
||||
feval=feval,
|
||||
).split()[1:],
|
||||
)
|
||||
]
|
||||
for feval in self.fevals
|
||||
)
|
||||
)
|
||||
else:
|
||||
eval_result = list(
|
||||
itertools_chain.from_iterable(
|
||||
[
|
||||
(key, score)
|
||||
for key, score, _std in aggcv(
|
||||
fold.eval(
|
||||
iteration=iteration,
|
||||
feval=feval,
|
||||
)
|
||||
for fold in cvfolds
|
||||
)
|
||||
]
|
||||
for feval in self.fevals
|
||||
)
|
||||
)
|
||||
eval_result = list(eval_result)
|
||||
eval_result.sort(key=self.sort_key)
|
||||
|
||||
##### Print eval result #####
|
||||
if self.verbose_eval and iteration % self.verbose_eval == 0:
|
||||
info = []
|
||||
for key, score in eval_result:
|
||||
if "null" not in key:
|
||||
info.append(f"{key}: {score:.6f}")
|
||||
logger.debug("XGB iter %3d: %s", iteration, "\t".join(info))
|
||||
|
||||
##### Choose score and do early stopping #####
|
||||
score = None
|
||||
for key, _score in eval_result:
|
||||
if key == self.focused_metric:
|
||||
score = _score
|
||||
break
|
||||
assert score is not None
|
||||
|
||||
best_score = self.state["best_score"]
|
||||
best_iteration = self.state["best_iteration"]
|
||||
if score < best_score:
|
||||
tab = "\t" # to work with f-string
|
||||
msg = f"[{epoch}] {tab.join([_fmt_metric(x) for x in eval_result])}"
|
||||
self.state["best_msg"] = msg
|
||||
self.state["best_score"] = score
|
||||
self.state["best_iteration"] = epoch
|
||||
# save the property to attributes, so they will occur in checkpoint.
|
||||
if model is not None:
|
||||
model.set_attr(
|
||||
best_score=str(self.state["best_score"]),
|
||||
best_iteration=str(self.state["best_iteration"]),
|
||||
best_msg=self.state["best_msg"],
|
||||
)
|
||||
elif epoch - best_iteration >= self.early_stopping_rounds:
|
||||
best_msg = self.state["best_msg"]
|
||||
|
||||
if self.verbose_eval and collective.get_rank() == 0:
|
||||
logger.debug("XGB stopped. Best iteration: %s ", best_msg)
|
||||
# instead of raising EarlyStopException, returning True to end the training
|
||||
return True
|
||||
# False to indicate training should not stop.
|
||||
return False
|
||||
|
||||
return XGBoostCustomCallback(
|
||||
early_stopping_rounds=early_stopping_rounds,
|
||||
verbose_eval=verbose_eval,
|
||||
fevals=fevals,
|
||||
evals=evals,
|
||||
focused_metric=focused_metric,
|
||||
cvfolds=cvfolds,
|
||||
)
|
||||
@@ -0,0 +1,28 @@
|
||||
# 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.database package.
|
||||
The database that stores serialized tuning records and workloads
|
||||
"""
|
||||
|
||||
from .database import Database, PyDatabase, TuningRecord, Workload, create
|
||||
from .json_database import JSONDatabase
|
||||
from .memory_database import MemoryDatabase
|
||||
from .ordered_union_database import OrderedUnionDatabase
|
||||
from .schedule_fn_database import ScheduleFnDatabase
|
||||
from .union_database import UnionDatabase
|
||||
@@ -0,0 +1,644 @@
|
||||
# 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
|
||||
"""TuningRecord database"""
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
# isort: off
|
||||
from typing import Literal
|
||||
|
||||
# isort: on
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from tvm.ir.module import IRModule
|
||||
from tvm.runtime import Object
|
||||
from tvm.s_tir.schedule import Schedule, Trace
|
||||
from tvm.target import Target
|
||||
|
||||
from .. import _ffi_api
|
||||
from ..arg_info import ArgInfo
|
||||
from ..utils import _json_de_tvm
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.Workload")
|
||||
class Workload(Object):
|
||||
"""A workload, i.e. an IRModule and its structural hash.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : IRModule
|
||||
The workload's IRModule
|
||||
"""
|
||||
|
||||
mod: IRModule
|
||||
|
||||
def __init__(self, mod: IRModule) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.Workload, # type: ignore # pylint: disable=no-member
|
||||
mod,
|
||||
)
|
||||
|
||||
def as_json(self) -> Any:
|
||||
"""Export the workload to JSON as a python object.
|
||||
|
||||
Returns
|
||||
-------
|
||||
json : Any
|
||||
The JSON serialized as a python object (e.g. a Dict or List).
|
||||
Use json.dumps() to get the associated json string.
|
||||
"""
|
||||
return _json_de_tvm(_ffi_api.WorkloadAsJSON(self)) # type: ignore # pylint: disable=no-member
|
||||
|
||||
@staticmethod
|
||||
def from_json(json_obj: Any) -> "Workload":
|
||||
"""Create a workload from a json object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
json_obj : Any
|
||||
The json object to parse.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuning_record : TuningRecord
|
||||
The parsed tuning record.
|
||||
"""
|
||||
return _ffi_api.WorkloadFromJSON(json_obj) # type: ignore # pylint: disable=no-member
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.TuningRecord")
|
||||
class TuningRecord(Object):
|
||||
"""The class of tuning records.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
trace : tvm.ir.Trace
|
||||
The trace of the tuning record.
|
||||
workload : Workload
|
||||
The workload of the tuning record.
|
||||
run_secs : Optional[List[float]]
|
||||
The run time of the tuning record.
|
||||
target : Optional[Target]
|
||||
The target of the tuning record.
|
||||
args_info : Optional[List[ArgInfo]]
|
||||
The argument information of the tuning record.
|
||||
"""
|
||||
|
||||
trace: Trace
|
||||
workload: Workload
|
||||
run_secs: list[float] | None
|
||||
target: Target | None
|
||||
args_info: list[ArgInfo] | None
|
||||
|
||||
def __init__( # type: ignore # pylint: disable=too-many-arguments
|
||||
self,
|
||||
trace: Trace,
|
||||
workload: Workload,
|
||||
run_secs: list[float] | None = None,
|
||||
target: Target | None = None,
|
||||
args_info: list[ArgInfo] | None = None,
|
||||
) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.TuningRecord, # type: ignore # pylint: disable=no-member
|
||||
trace,
|
||||
workload,
|
||||
run_secs,
|
||||
target,
|
||||
args_info,
|
||||
)
|
||||
|
||||
def as_measure_candidate(self) -> Any:
|
||||
"""Generate a measure candidate given an initial IR module and a trace
|
||||
stored in the tuning record.
|
||||
|
||||
Returns
|
||||
-------
|
||||
candidate : MeasureCandidate
|
||||
A generated candidate.
|
||||
"""
|
||||
return _ffi_api.TuningRecordAsMeasureCandidate(self) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def as_json(self) -> Any:
|
||||
"""Export the tuning record to a JSON string.
|
||||
|
||||
Returns
|
||||
-------
|
||||
json_str : str
|
||||
The JSON string exported.
|
||||
"""
|
||||
return _json_de_tvm(_ffi_api.TuningRecordAsJSON(self)) # type: ignore # pylint: disable=no-member
|
||||
|
||||
@staticmethod
|
||||
def from_json(json_obj: Any, workload: Workload) -> "TuningRecord":
|
||||
"""Create a tuning record from a json object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
json_obj : Any
|
||||
The json object to parse.
|
||||
workload : Workload
|
||||
The workload.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuning_record : TuningRecord
|
||||
The parsed tuning record.
|
||||
"""
|
||||
return _ffi_api.TuningRecordFromJSON(json_obj, workload) # type: ignore # pylint: disable=no-member
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.Database")
|
||||
class Database(Object):
|
||||
"""The abstract database interface."""
|
||||
|
||||
DatabaseType = Union["Database", Literal["json", "memory"]]
|
||||
|
||||
def has_workload(self, mod: IRModule) -> bool:
|
||||
"""Check if the database has the given workload.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : IRModule
|
||||
The IRModule to be searched for.
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : bool
|
||||
Whether the database has the given workload.
|
||||
"""
|
||||
return _ffi_api.DatabaseHasWorkload(self, mod) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def commit_workload(self, mod: IRModule) -> Workload:
|
||||
"""Commit a workload to the database if missing.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : IRModule
|
||||
The IRModule to be searched for or added.
|
||||
|
||||
Returns
|
||||
-------
|
||||
workload : Workload
|
||||
The workload corresponding to the given IRModule.
|
||||
"""
|
||||
return _ffi_api.DatabaseCommitWorkload(self, mod) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def commit_tuning_record(self, record: TuningRecord) -> None:
|
||||
"""Commit a tuning record to the database.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
record : TuningRecord
|
||||
The tuning record to add.
|
||||
"""
|
||||
_ffi_api.DatabaseCommitTuningRecord(self, record) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def get_top_k(self, workload: Workload, top_k: int) -> list[TuningRecord]:
|
||||
"""Get the top K valid tuning records of given workload from the database.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
workload : Workload
|
||||
The workload to be searched for.
|
||||
top_k : int
|
||||
The number of top records to get.
|
||||
|
||||
Returns
|
||||
-------
|
||||
top_k_records : List[TuningRecord]
|
||||
The top K records.
|
||||
"""
|
||||
return _ffi_api.DatabaseGetTopK(self, workload, top_k) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def get_all_tuning_records(self) -> list[TuningRecord]:
|
||||
"""Get all the tuning records from the database.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuning_records : List[TuningRecord]
|
||||
All tuning records from the database.
|
||||
"""
|
||||
return _ffi_api.DatabaseGetAllTuningRecords(self) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""Get the number of records in the database.
|
||||
|
||||
Returns
|
||||
-------
|
||||
num_records : int
|
||||
The number of records in the database
|
||||
"""
|
||||
return _ffi_api.DatabaseSize(self) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def query_tuning_record(
|
||||
self,
|
||||
mod: IRModule,
|
||||
target: Target,
|
||||
workload_name: str,
|
||||
) -> TuningRecord | None:
|
||||
"""Query the best record of the given workload from the database.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : IRModule
|
||||
The IRModule to be searched for.
|
||||
target : Target
|
||||
The target to be searched for.
|
||||
workload_name : str
|
||||
The name of the workload to be searched for.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuning_record : Optional[TuningRecord]
|
||||
The best record of the given workload; None if not found.
|
||||
"""
|
||||
return _ffi_api.DatabaseQueryTuningRecord(self, mod, target, workload_name) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def query_schedule(
|
||||
self,
|
||||
mod: IRModule,
|
||||
target: Target,
|
||||
workload_name: str,
|
||||
) -> Schedule | None:
|
||||
"""Query the best schedule of the given workload from the database.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : IRModule
|
||||
The IRModule to be searched for.
|
||||
target : Target
|
||||
The target to be searched for.
|
||||
workload_name : str
|
||||
The name of the workload to be searched for.
|
||||
|
||||
Returns
|
||||
-------
|
||||
schedule : Optional[tvm.s_tir.Schedule]
|
||||
The best schedule of the given workload; None if not found.
|
||||
"""
|
||||
return _ffi_api.DatabaseQuerySchedule(self, mod, target, workload_name) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def query_ir_module(
|
||||
self,
|
||||
mod: IRModule,
|
||||
target: Target,
|
||||
workload_name: str,
|
||||
) -> IRModule | None:
|
||||
"""Query the best IRModule of the given workload from the database.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : IRModule
|
||||
The IRModule to be searched for.
|
||||
target : Target
|
||||
The target to be searched for.
|
||||
workload_name : str
|
||||
The name of the workload to be searched for.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ir_module : Optional[IRModule]
|
||||
The best IRModule of the given workload; None if not found.
|
||||
"""
|
||||
return _ffi_api.DatabaseQueryIRModule(self, mod, target, workload_name) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def dump_pruned(self, destination: "Database") -> None:
|
||||
"""Dump the pruned database to files of JSONDatabase format.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
destination : Database
|
||||
The destination database to be dumped to.
|
||||
"""
|
||||
return _ffi_api.DatabaseDumpPruned( # type: ignore # pylint: disable=no-member
|
||||
self, destination
|
||||
)
|
||||
|
||||
def query(
|
||||
self,
|
||||
mod: IRModule,
|
||||
target: Target,
|
||||
*,
|
||||
workload_name: str = "main",
|
||||
kind: Literal["schedule"] | Literal["record"] | Literal["ir_module"] = "schedule",
|
||||
) -> Schedule | IRModule | TuningRecord:
|
||||
"""Query the database to retrieve the best optimization outcome of the given workload.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : IRModule
|
||||
The IRModule to be searched for.
|
||||
target : Target
|
||||
The target to be searched for.
|
||||
kind : str = "schedule" | "record" | "ir_module"
|
||||
The kind of the optimization outcome to be returned.
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : Union[tvm.s_tir.Schedule, IRModule, TuningRecord]
|
||||
The best optimization outcome of the given workload.
|
||||
"""
|
||||
if kind == "schedule":
|
||||
return self.query_schedule(mod, target, workload_name)
|
||||
if kind == "record":
|
||||
return self.query_tuning_record(mod, target, workload_name)
|
||||
if kind == "ir_module":
|
||||
return self.query_ir_module(mod, target, workload_name)
|
||||
raise ValueError(f'Unknown kind: {kind}. Candidates are: "schedule", "record", "ir_module"')
|
||||
|
||||
def __enter__(self) -> "Database":
|
||||
"""Entering the scope of the context manager"""
|
||||
_ffi_api.DatabaseEnterWithScope(self) # type: ignore # pylint: disable=no-member
|
||||
return self
|
||||
|
||||
def __exit__(self, ptype, value, trace) -> None:
|
||||
"""Exiting the scope of the context manager"""
|
||||
_ffi_api.DatabaseExitWithScope(self) # type: ignore # pylint: disable=no-member
|
||||
|
||||
@staticmethod
|
||||
def current() -> Optional["Database"]:
|
||||
"""Get the current database under scope."""
|
||||
return _ffi_api.DatabaseCurrent() # type: ignore # pylint: disable=no-member
|
||||
|
||||
@staticmethod
|
||||
def create( # pylint: disable=keyword-arg-before-vararg
|
||||
kind: (
|
||||
Literal["json", "memory", "union", "ordered_union"] | Callable[[Schedule], bool]
|
||||
) = "json",
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> "Database":
|
||||
"""Create a Database.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
kind : str = "json" | "memory" | "union" | "ordered_union" | Callable[[tvm.s_tir.Schedule],
|
||||
bool]
|
||||
The kind of the database to be created. The following kinds are supported:
|
||||
"json", "memory", "union", "ordered_union", and a custom schedule function.
|
||||
|
||||
Returns
|
||||
-------
|
||||
database : Database
|
||||
The created database.
|
||||
"""
|
||||
from . import ( # pylint: disable=import-outside-toplevel
|
||||
JSONDatabase,
|
||||
MemoryDatabase,
|
||||
OrderedUnionDatabase,
|
||||
ScheduleFnDatabase,
|
||||
UnionDatabase,
|
||||
)
|
||||
|
||||
if callable(kind):
|
||||
return ScheduleFnDatabase(kind, *args, **kwargs) # type: ignore
|
||||
if kind == "json":
|
||||
return JSONDatabase(*args, **kwargs)
|
||||
if kind == "memory":
|
||||
return MemoryDatabase(*args, **kwargs) # type: ignore
|
||||
if kind == "union":
|
||||
return UnionDatabase(*args, **kwargs) # type: ignore
|
||||
if kind == "ordered_union":
|
||||
return OrderedUnionDatabase(*args, **kwargs) # type: ignore
|
||||
raise ValueError(f"Unknown Database: {kind}")
|
||||
|
||||
|
||||
create = Database.create # pylint: disable=invalid-name
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.PyDatabase")
|
||||
class _PyDatabase(Database):
|
||||
"""
|
||||
A TVM object database to support customization on the python side.
|
||||
This is NOT the user facing class for function overloading inheritance.
|
||||
|
||||
See also: PyDatabase
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
f_has_workload: Callable | None = None,
|
||||
f_commit_workload: Callable | None = None,
|
||||
f_commit_tuning_record: Callable | None = None,
|
||||
f_get_top_k: Callable | None = None,
|
||||
f_get_all_tuning_records: Callable | None = None,
|
||||
f_query_tuning_record: Callable | None = None,
|
||||
f_query_schedule: Callable | None = None,
|
||||
f_query_ir_module: Callable | None = None,
|
||||
f_size: Callable | None = None,
|
||||
module_equality: str = "structural",
|
||||
):
|
||||
"""Constructor."""
|
||||
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.DatabasePyDatabase, # type: ignore # pylint: disable=no-member
|
||||
f_has_workload,
|
||||
f_commit_workload,
|
||||
f_commit_tuning_record,
|
||||
f_get_top_k,
|
||||
f_get_all_tuning_records,
|
||||
f_query_tuning_record,
|
||||
f_query_schedule,
|
||||
f_query_ir_module,
|
||||
f_size,
|
||||
module_equality,
|
||||
)
|
||||
|
||||
|
||||
class PyDatabase:
|
||||
"""
|
||||
An abstract database with customized methods 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": _PyDatabase,
|
||||
"methods": [
|
||||
"has_workload",
|
||||
"commit_workload",
|
||||
"commit_tuning_record",
|
||||
"get_top_k",
|
||||
"get_all_tuning_records",
|
||||
"query_tuning_record",
|
||||
"query_schedule",
|
||||
"query_ir_module",
|
||||
"__len__",
|
||||
],
|
||||
}
|
||||
|
||||
def has_workload(self, mod: IRModule) -> bool:
|
||||
"""Check if the database has the given workload.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : IRModule
|
||||
The IRModule to be searched for.
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : bool
|
||||
Whether the database has the given workload.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def commit_workload(self, mod: IRModule) -> Workload:
|
||||
"""Commit a workload to the database if missing.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : IRModule
|
||||
The IRModule to be searched for or added.
|
||||
|
||||
Returns
|
||||
-------
|
||||
workload : Workload
|
||||
The workload corresponding to the given IRModule.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def commit_tuning_record(self, record: TuningRecord) -> None:
|
||||
"""Commit a tuning record to the database.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
record : TuningRecord
|
||||
The tuning record to add.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_top_k(self, workload: Workload, top_k: int) -> list[TuningRecord]:
|
||||
"""Get the top K tuning records of given workload from the database.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
workload : Workload
|
||||
The workload to be searched for.
|
||||
top_k : int
|
||||
The number of top records to get.
|
||||
|
||||
Returns
|
||||
-------
|
||||
top_k_records : List[TuningRecord]
|
||||
The top K records.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_all_tuning_records(self) -> list[TuningRecord]:
|
||||
"""Get all the tuning records from the database.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuning_records : List[TuningRecord]
|
||||
All tuning records from the database.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def query_tuning_record(
|
||||
self, mod: IRModule, target: Target, workload_name: str | None = None
|
||||
) -> TuningRecord | None:
|
||||
"""Query a tuning record from the database.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : IRModule
|
||||
The IRModule to be searched for.
|
||||
target : Target
|
||||
The target to be searched for.
|
||||
workload_name : Optional[str]
|
||||
The workload name to be searched for.
|
||||
|
||||
Returns
|
||||
-------
|
||||
record : Optional[TuningRecord]
|
||||
The tuning record corresponding to the given workload.
|
||||
"""
|
||||
# Using self._outer to replace the self pointer
|
||||
return _ffi_api.DatabaseQueryTuningRecord( # type: ignore # pylint: disable=no-member
|
||||
self._outer(),
|
||||
mod,
|
||||
target,
|
||||
workload_name, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
|
||||
def query_schedule(
|
||||
self, mod: IRModule, target: Target, workload_name: str | None = None
|
||||
) -> Schedule | None:
|
||||
"""Query a schedule from the database.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : IRModule
|
||||
The IRModule to be searched for.
|
||||
target : Target
|
||||
The target to be searched for.
|
||||
workload_name : Optional[str]
|
||||
The workload name to be searched for.
|
||||
|
||||
Returns
|
||||
-------
|
||||
schedule : Optional[Schedule]
|
||||
The schedule corresponding to the given workload.
|
||||
"""
|
||||
# Using self._outer to replace the self pointer
|
||||
return _ffi_api.DatabaseQuerySchedule( # type: ignore # pylint: disable=no-member
|
||||
self._outer(),
|
||||
mod,
|
||||
target,
|
||||
workload_name, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
|
||||
def query_ir_module(
|
||||
self, mod: IRModule, target: Target, workload_name: str | None = None
|
||||
) -> IRModule | None:
|
||||
"""Query an IRModule from the database.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : IRModule
|
||||
The IRModule to be searched for.
|
||||
target : Target
|
||||
The target to be searched for.
|
||||
workload_name : Optional[str]
|
||||
The workload name to be searched for.
|
||||
|
||||
Returns
|
||||
-------
|
||||
mod : Optional[IRModule]
|
||||
The IRModule corresponding to the given workload.
|
||||
"""
|
||||
# Using self._outer to replace the self pointer
|
||||
return _ffi_api.DatabaseQueryIRModule( # type: ignore # pylint: disable=no-member
|
||||
self._outer(),
|
||||
mod,
|
||||
target,
|
||||
workload_name, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""Get the number of records in the database.
|
||||
|
||||
Returns
|
||||
-------
|
||||
num_records : int
|
||||
The number of records in the database
|
||||
"""
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,93 @@
|
||||
# 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 default database that uses a JSON File to store tuning records"""
|
||||
|
||||
import os.path as osp
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .database import Database
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.JSONDatabase")
|
||||
class JSONDatabase(Database):
|
||||
"""Database class backed by JSON.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path_workload : str
|
||||
The path to the workload table.
|
||||
path_tuning_record : str
|
||||
The path to the tuning record table.
|
||||
module_equality : Optional[str]
|
||||
A string to specify the module equality testing and hashing method.
|
||||
It must be one of the followings:
|
||||
|
||||
- "structural": Use StructuralEqual/Hash
|
||||
- "ignore-tensor": Same as "structural", but ignore tensor raw data during
|
||||
equality testing and hashing.
|
||||
- "anchor-block": Apply equality testing and hashing on the anchor block extracted from a
|
||||
given module. The "ignore-tensor" varint is used for the extracted
|
||||
blocks or in case no anchor block is found.
|
||||
For the definition of the anchor block, see tirx/analysis/analysis.py.
|
||||
"""
|
||||
|
||||
path_workload: str
|
||||
path_tuning_record: str
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
path_workload: str | None = None,
|
||||
path_tuning_record: str | None = None,
|
||||
*,
|
||||
work_dir: str | None = None,
|
||||
allow_missing: bool = True,
|
||||
module_equality: str = "structural",
|
||||
) -> None:
|
||||
"""Constructor.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path_workload : Optional[str] = None
|
||||
The path to the workload table. If not specified,
|
||||
will be generated from `work_dir` as `$work_dir/database_workload.json`.
|
||||
path_tuning_record : Optional[str] = None
|
||||
The path to the tuning record table. If not specified,
|
||||
will be generated from `work_dir` as `$work_dir/database_tuning_record.json`.
|
||||
work_dir : Optional[str] = None
|
||||
The work directory, if specified, will be used to generate `path_tuning_record`
|
||||
and `path_workload`.
|
||||
allow_missing : bool
|
||||
Whether to create new file when the given path is not found.
|
||||
"""
|
||||
if work_dir is not None:
|
||||
if path_workload is None:
|
||||
path_workload = osp.join(work_dir, "database_workload.json")
|
||||
if path_tuning_record is None:
|
||||
path_tuning_record = osp.join(work_dir, "database_tuning_record.json")
|
||||
if path_workload is None:
|
||||
raise ValueError("`path_workload` is not specified.")
|
||||
if path_tuning_record is None:
|
||||
raise ValueError("`path_tuning_record` is not specified.")
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.DatabaseJSONDatabase, # type: ignore # pylint: disable=no-member
|
||||
path_workload,
|
||||
path_tuning_record,
|
||||
allow_missing,
|
||||
module_equality,
|
||||
)
|
||||
@@ -0,0 +1,51 @@
|
||||
# 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.
|
||||
"""A database that stores TuningRecords in memory"""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .database import Database
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.MemoryDatabase")
|
||||
class MemoryDatabase(Database):
|
||||
"""An in-memory database
|
||||
|
||||
Parameters
|
||||
----------
|
||||
module_equality : Optional[str]
|
||||
A string to specify the module equality testing and hashing method.
|
||||
It must be one of the followings:
|
||||
|
||||
- "structural": Use StructuralEqual/Hash
|
||||
- "ignore-tensor": Same as "structural", but ignore tensor raw data during
|
||||
equality testing and hashing.
|
||||
- "anchor-block": Apply equality testing and hashing on the anchor block extracted from a
|
||||
given module. The "ignore-tensor" varint is used for the extracted
|
||||
blocks or in case no anchor block is found.
|
||||
For the definition of the anchor block, see tirx/analysis/analysis.py.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
module_equality: str = "structural",
|
||||
) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.DatabaseMemoryDatabase, # type: ignore # pylint: disable=no-member,
|
||||
module_equality,
|
||||
)
|
||||
@@ -0,0 +1,113 @@
|
||||
# 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.
|
||||
"""A database consists of multiple databases."""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .database import Database
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.OrderedUnionDatabase")
|
||||
class OrderedUnionDatabase(Database):
|
||||
"""A database composed of multiple databases, allowing users to guide IR rewriting using
|
||||
combined knowledge of those databases. To each query, it returns the record from the first
|
||||
database that responds to the query.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Examples below demonstrate the usecases of and difference between UnionDatabase and
|
||||
OrderDatabase.
|
||||
|
||||
Assumption:
|
||||
* db1, db2 do not have tuning records for the target workload.
|
||||
* Each of db3, db4, db5 has tuning records r3, r4, r5 for target workload respectively.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
#### Case 1. `UnionDatabase`:
|
||||
merged_db = ms.database.UnionDatabase(
|
||||
db1, # no record
|
||||
db2, # no record
|
||||
db3, # has r3
|
||||
db4 # has r4
|
||||
)
|
||||
# returns the better one between r3 and r4
|
||||
merged_db.query_tuning_record(..., target_workload)
|
||||
|
||||
### Case 2. `OrderedUnionDatabase`
|
||||
merged_db = ms.database.OrderedUnionDatabase(
|
||||
db1, # no record
|
||||
db2, # no record
|
||||
db3, # has r3
|
||||
db4 # has r4
|
||||
)
|
||||
# returns r3
|
||||
merged_db.query_tuning_record(..., target_workload)
|
||||
|
||||
### Case 3. Mix-use scenario
|
||||
merged_db = ms.database.UnionDatabase(
|
||||
db1, # no record
|
||||
db2, # no record
|
||||
db3, # has r3
|
||||
ms.database.OrderedUnionDatabase( # returns r4
|
||||
db4, # has r4
|
||||
db5, # has r5
|
||||
)
|
||||
)
|
||||
# returns the better one between r3 and r4
|
||||
merged_db.query_tuning_record(..., target_workload)
|
||||
|
||||
### Case 4. Another mix-use scenario
|
||||
merged_db = ms.database.UnionDatabase(
|
||||
db1, # no record
|
||||
db2, # no record
|
||||
db3, # has r3
|
||||
ms.database.UnionDatabase( # returns best one between r4 and r5
|
||||
db4, # has r4
|
||||
db5, # has r5
|
||||
)
|
||||
)
|
||||
# returns the best one among r3, r4 and r5
|
||||
merged_db.query_tuning_record(..., target_workload)
|
||||
|
||||
### Case 5. Yet another mix-use scenario
|
||||
merged_db = ms.database.OrderedUnionDatabase(
|
||||
db1, # no record
|
||||
db2, # no record
|
||||
ms.database.UnionDatabase( # returns best one between r3 and r4
|
||||
db3, # has r3
|
||||
db4, # has r4
|
||||
)
|
||||
db5, # has r5
|
||||
)
|
||||
# returns the better one between r3 and r4
|
||||
merged_db.query_tuning_record(..., target_workload)
|
||||
"""
|
||||
|
||||
def __init__(self, *databases: Database) -> None:
|
||||
"""Construct a merged database from multiple databases.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
*databases : Database
|
||||
The list of databases to combine.
|
||||
"""
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.DatabaseOrderedUnionDatabase, # type: ignore # pylint: disable=no-member
|
||||
databases,
|
||||
)
|
||||
@@ -0,0 +1,60 @@
|
||||
# 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.
|
||||
"""A database for injecting handcrafted schedule functions."""
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from tvm.s_tir import Schedule
|
||||
|
||||
from .. import _ffi_api
|
||||
from .database import Database
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.ScheduleFnDatabase")
|
||||
class ScheduleFnDatabase(Database):
|
||||
"""A database for injecting handcrafted schedule functions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
schedule_fn : Callable[[Schedule], bool],
|
||||
The function to do scheduling, which takes a TIR schedule, and returns
|
||||
a boolean indicating if the schedule is committed to the database.
|
||||
module_equality : Optional[str]
|
||||
A string to specify the module equality testing and hashing method.
|
||||
It must be one of the followings:
|
||||
|
||||
- "structural": Use StructuralEqual/Hash
|
||||
- "ignore-tensor": Same as "structural", but ignore tensor raw data during
|
||||
equality testing and hashing.
|
||||
- "anchor-block": Apply equality testing and hashing on the anchor block extracted from a
|
||||
given module. The "ignore-tensor" varint is used for the extracted
|
||||
blocks or in case no anchor block is found.
|
||||
For the definition of the anchor block, see tirx/analysis/analysis.py.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
schedule_fn: Callable[[Schedule], bool],
|
||||
module_equality: str = "structural",
|
||||
) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.DatabaseScheduleFnDatabase, # type: ignore # pylint: disable=no-member
|
||||
schedule_fn,
|
||||
module_equality,
|
||||
)
|
||||
@@ -0,0 +1,113 @@
|
||||
# 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.
|
||||
"""A database consists of multiple databases."""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .database import Database
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.UnionDatabase")
|
||||
class UnionDatabase(Database):
|
||||
"""A database composed of multiple databases, allowing users to guide IR rewriting using
|
||||
combined knowledge of those databases. To each query, it returns the best record among all the
|
||||
databases given.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Examples below demonstrate the usecases of and difference between UnionDatabase and
|
||||
OrderDatabase.
|
||||
|
||||
Assumption:
|
||||
* db1, db2 do not have tuning records for the target workload.
|
||||
* Each of db3, db4, db5 has tuning records r3, r4, r5 for target workload respectively.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
#### Case 1. `UnionDatabase`:
|
||||
merged_db = ms.database.UnionDatabase(
|
||||
db1, # no record
|
||||
db2, # no record
|
||||
db3, # has r3
|
||||
db4 # has r4
|
||||
)
|
||||
# returns the better one between r3 and r4
|
||||
merged_db.query_tuning_record(..., target_workload)
|
||||
|
||||
### Case 2. `OrderedUnionDatabase`
|
||||
merged_db = ms.database.OrderedUnionDatabase(
|
||||
db1, # no record
|
||||
db2, # no record
|
||||
db3, # has r3
|
||||
db4 # has r4
|
||||
)
|
||||
# returns r3
|
||||
merged_db.query_tuning_record(..., target_workload)
|
||||
|
||||
### Case 3. Mix-use scenario
|
||||
merged_db = ms.database.UnionDatabase(
|
||||
db1, # no record
|
||||
db2, # no record
|
||||
db3, # has r3
|
||||
ms.database.OrderedUnionDatabase( # returns r4
|
||||
db4, # has r4
|
||||
db5, # has r5
|
||||
)
|
||||
)
|
||||
# returns the better one between r3 and r4
|
||||
merged_db.query_tuning_record(..., target_workload)
|
||||
|
||||
### Case 4. Another mix-use scenario
|
||||
merged_db = ms.database.UnionDatabase(
|
||||
db1, # no record
|
||||
db2, # no record
|
||||
db3, # has r3
|
||||
ms.database.UnionDatabase( # returns best one between r4 and r5
|
||||
db4, # has r4
|
||||
db5, # has r5
|
||||
)
|
||||
)
|
||||
# returns the best one among r3, r4 and r5
|
||||
merged_db.query_tuning_record(..., target_workload)
|
||||
|
||||
### Case 5. Yet another mix-use scenario
|
||||
merged_db = ms.database.OrderedUnionDatabase(
|
||||
db1, # no record
|
||||
db2, # no record
|
||||
ms.database.UnionDatabase( # returns best one between r3 and r4
|
||||
db3, # has r3
|
||||
db4, # has r4
|
||||
)
|
||||
db5, # has r5
|
||||
)
|
||||
# returns the better one between r3 and r4
|
||||
merged_db.query_tuning_record(..., target_workload)
|
||||
"""
|
||||
|
||||
def __init__(self, *databases: Database) -> None:
|
||||
"""Construct a merged database from multiple databases.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
*databases : Database
|
||||
The list of databases to combine.
|
||||
"""
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.DatabaseUnionDatabase, # type: ignore # pylint: disable=no-member
|
||||
databases,
|
||||
)
|
||||
@@ -0,0 +1,66 @@
|
||||
# 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.
|
||||
"""Extracted tasks from high-level IR."""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from tvm.ir import IRModule
|
||||
from tvm.runtime import Object
|
||||
from tvm.target import Target
|
||||
|
||||
from . import _ffi_api
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.ExtractedTask")
|
||||
class ExtractedTask(Object):
|
||||
"""A tuning task extracted from the high-level IR
|
||||
|
||||
Parameters
|
||||
----------
|
||||
task_name : str
|
||||
The name of the task extracted
|
||||
mod : IRModule
|
||||
The high-level IR
|
||||
target: Target
|
||||
Target information
|
||||
dispatched : List[IRModule]
|
||||
A list of low-level IRs that the high-level IR could potentially dispatch to
|
||||
weight : int
|
||||
The weight of the task
|
||||
"""
|
||||
|
||||
task_name: str
|
||||
mod: IRModule
|
||||
dispatched: list[IRModule]
|
||||
weight: int
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
task_name: str,
|
||||
mod: IRModule,
|
||||
target: Target,
|
||||
dispatched: list[IRModule],
|
||||
weight: int,
|
||||
) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.ExtractedTask, # type: ignore # pylint: disable=no-member
|
||||
task_name,
|
||||
mod,
|
||||
target,
|
||||
dispatched,
|
||||
weight,
|
||||
)
|
||||
@@ -0,0 +1,26 @@
|
||||
# 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.feature_extractor package.
|
||||
Meta Schedule feature extractors that extracts features from
|
||||
measure candidates for use in cost model.
|
||||
"""
|
||||
|
||||
from .feature_extractor import FeatureExtractor, PyFeatureExtractor
|
||||
from .per_store_feature import PerStoreFeature
|
||||
from .random_feature_extractor import RandomFeatureExtractor
|
||||
@@ -0,0 +1,128 @@
|
||||
# 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
|
||||
"""Meta Schedule FeatureExtractor."""
|
||||
|
||||
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 tvm.runtime._tensor import Tensor
|
||||
|
||||
from .. import _ffi_api
|
||||
from ..search_strategy import MeasureCandidate
|
||||
from ..tune_context import TuneContext
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.FeatureExtractor")
|
||||
class FeatureExtractor(Object):
|
||||
"""Extractor for features from measure candidates for use in cost model."""
|
||||
|
||||
FeatureExtractorType = Union[Literal["per-store-feature"], "FeatureExtractor"]
|
||||
|
||||
def extract_from(
|
||||
self, context: TuneContext, candidates: list[MeasureCandidate]
|
||||
) -> list[Tensor]:
|
||||
"""Extract features from the given measure candidate.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : TuneContext
|
||||
The tuning context for feature extraction.
|
||||
candidates : List[MeasureCandidate]
|
||||
The measure candidates to extract features from.
|
||||
|
||||
Returns
|
||||
-------
|
||||
features : List[Tensor]
|
||||
The feature tvm ndarray extracted.
|
||||
"""
|
||||
result = _ffi_api.FeatureExtractorExtractFrom( # type: ignore # pylint: disable=no-member
|
||||
self, context, candidates
|
||||
)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def create(
|
||||
kind: Literal["per-store-feature"],
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> "FeatureExtractor":
|
||||
"""Create a CostModel."""
|
||||
from . import PerStoreFeature # pylint: disable=import-outside-toplevel
|
||||
|
||||
if kind == "per-store-feature":
|
||||
return PerStoreFeature(*args, **kwargs) # type: ignore
|
||||
raise ValueError(f"Unknown CostModel: {kind}")
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.PyFeatureExtractor")
|
||||
class _PyFeatureExtractor(FeatureExtractor):
|
||||
"""
|
||||
A TVM object feature extractor to support customization on the python side.
|
||||
This is NOT the user facing class for function overloading inheritance.
|
||||
|
||||
See also: PyFeatureExtractor
|
||||
"""
|
||||
|
||||
def __init__(self, f_extract_from: Callable):
|
||||
"""Constructor."""
|
||||
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.FeatureExtractorPyFeatureExtractor, # type: ignore # pylint: disable=no-member
|
||||
f_extract_from,
|
||||
)
|
||||
|
||||
|
||||
class PyFeatureExtractor:
|
||||
"""
|
||||
An abstract feature extractor with customized methods 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": _PyFeatureExtractor,
|
||||
"methods": ["extract_from"],
|
||||
}
|
||||
|
||||
def extract_from(
|
||||
self, context: TuneContext, candidates: list[MeasureCandidate]
|
||||
) -> list[Tensor]:
|
||||
"""Extract features from the given measure candidate.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : TuneContext
|
||||
The tuning context for feature extraction.
|
||||
candidates : List[MeasureCandidate]
|
||||
The measure candidates to extract features from.
|
||||
|
||||
Returns
|
||||
-------
|
||||
features : List[Tensor]
|
||||
The feature tvm ndarray extracted.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,68 @@
|
||||
# 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.
|
||||
# pylint: disable=invalid-name
|
||||
"""We extract one feature vector per BufferStoreNode statement in a TIR Stmt,
|
||||
so we call this feature as "per-store" feature.
|
||||
"""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .feature_extractor import FeatureExtractor
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.PerStoreFeature")
|
||||
class PerStoreFeature(FeatureExtractor):
|
||||
"""PerStoreFeature extracts one feature vector per BufferStoreNode
|
||||
|
||||
Parameters
|
||||
----------
|
||||
buffers_per_store : int
|
||||
The number of buffers in each BufferStore; Pad or truncate if necessary.
|
||||
arith_intensity_curve_num_samples : int
|
||||
The number of samples used in the arithmetic intensity curve.
|
||||
cache_line_bytes : int
|
||||
The number of bytes in a cache line.
|
||||
extract_workload : bool
|
||||
Whether to extract features in the workload in tuning context or not.
|
||||
"""
|
||||
|
||||
buffers_per_store: int
|
||||
"""The number of buffers in each BufferStore; Pad or truncate if necessary."""
|
||||
arith_intensity_curve_num_samples: int # pylint: disable=invalid-name
|
||||
"""The number of samples used in the arithmetic intensity curve."""
|
||||
cache_line_bytes: int
|
||||
"""The number of bytes in a cache line."""
|
||||
extract_workload: bool
|
||||
"""Whether to extract features in the workload in tuning context or not."""
|
||||
feature_vector_length: int
|
||||
"""Length of the feature vector."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
buffers_per_store: int = 5,
|
||||
arith_intensity_curve_num_samples: int = 10,
|
||||
cache_line_bytes: int = 64,
|
||||
extract_workload: bool = False,
|
||||
):
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.FeatureExtractorPerStoreFeature, # type: ignore # pylint: disable=no-member
|
||||
buffers_per_store,
|
||||
arith_intensity_curve_num_samples,
|
||||
cache_line_bytes,
|
||||
extract_workload,
|
||||
)
|
||||
@@ -0,0 +1,64 @@
|
||||
# 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.
|
||||
"""Random Feature Extractor."""
|
||||
|
||||
import numpy as np # type: ignore
|
||||
|
||||
import tvm.runtime
|
||||
from tvm.ir.utils import derived_object
|
||||
|
||||
from ..feature_extractor import PyFeatureExtractor
|
||||
from ..search_strategy import MeasureCandidate
|
||||
from ..tune_context import TuneContext
|
||||
|
||||
|
||||
@derived_object
|
||||
class RandomFeatureExtractor(PyFeatureExtractor):
|
||||
"""Random Feature Extractor
|
||||
|
||||
Parameters
|
||||
----------
|
||||
feature_size : int
|
||||
The size of each block's feature vector.
|
||||
max_block_num : int
|
||||
The maximum number of blocks in each schedule.
|
||||
random_state : Union[Tuple[str, np.ndarray, int, int, float], dict]
|
||||
The current random state of the f
|
||||
"""
|
||||
|
||||
feature_size: int
|
||||
max_block_num: int
|
||||
random_state: tuple[str, np.ndarray, int, int, float] | dict
|
||||
|
||||
def __init__(self, *, feature_size: int = 30, max_block_num: int = 5, seed=0):
|
||||
super().__init__()
|
||||
assert max_block_num >= 1, "Max block number must be greater or equal to one!"
|
||||
self.max_block_num = max_block_num
|
||||
self.feature_size = feature_size
|
||||
np.random.seed(seed)
|
||||
self.random_state = np.random.get_state()
|
||||
|
||||
def extract_from(
|
||||
self, context: TuneContext, candidates: list[MeasureCandidate]
|
||||
) -> list[tvm.runtime.Tensor]:
|
||||
np.random.set_state(self.random_state)
|
||||
result = [
|
||||
np.random.rand(np.random.randint(1, self.max_block_num + 1), self.feature_size)
|
||||
for candidate in candidates
|
||||
]
|
||||
self.random_state = np.random.get_state()
|
||||
return [tvm.runtime.tensor(x) for x in result]
|
||||
@@ -0,0 +1,266 @@
|
||||
# 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.
|
||||
"""Logging interface in MetaSchedule"""
|
||||
|
||||
import logging
|
||||
import logging.config
|
||||
import os
|
||||
import os.path as osp
|
||||
from collections.abc import Callable
|
||||
from logging import Logger
|
||||
from typing import Any
|
||||
|
||||
|
||||
def get_logger(name: str) -> Logger:
|
||||
"""Create or get a logger by its name. This is essentially a wrapper of python's native logger.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
The name of the logger.
|
||||
|
||||
Returns
|
||||
-------
|
||||
logger : Logger
|
||||
The logger instance.
|
||||
"""
|
||||
return logging.getLogger(name)
|
||||
|
||||
|
||||
def get_logging_func(logger: Logger) -> Callable[[int, str, int, str], None] | None:
|
||||
"""Get the logging function.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
logger : Logger
|
||||
The logger instance.
|
||||
Returns
|
||||
-------
|
||||
result : Optional[Callable]
|
||||
The function to do the specified level of logging.
|
||||
"""
|
||||
if logger is None:
|
||||
return None
|
||||
|
||||
level2log = {
|
||||
logging.DEBUG: logger.debug,
|
||||
logging.INFO: logger.info,
|
||||
logging.WARNING: logger.warning,
|
||||
logging.ERROR: logger.error,
|
||||
# logging.FATAL not included
|
||||
}
|
||||
|
||||
def logging_func(level: int, filename: str, lineo: int, msg: str):
|
||||
if level < 0: # clear the output in notebook / console
|
||||
from IPython.display import ( # type: ignore # pylint: disable=import-outside-toplevel
|
||||
clear_output,
|
||||
)
|
||||
|
||||
clear_output(wait=True)
|
||||
else:
|
||||
level2log[level](f"[{os.path.basename(filename)}:{lineo}] " + msg)
|
||||
|
||||
return logging_func
|
||||
|
||||
|
||||
def create_loggers(
|
||||
log_dir: str,
|
||||
params: list[dict[str, Any]],
|
||||
logger_config: dict[str, Any] | None = None,
|
||||
disable_existing_loggers: bool = False,
|
||||
):
|
||||
"""Create loggers from configuration"""
|
||||
if logger_config is None:
|
||||
config = {}
|
||||
else:
|
||||
config = logger_config
|
||||
|
||||
config.setdefault("loggers", {})
|
||||
config.setdefault("handlers", {})
|
||||
config.setdefault("formatters", {})
|
||||
|
||||
global_logger_name = "tvm.s_tir.meta_schedule"
|
||||
global_logger = logging.getLogger(global_logger_name)
|
||||
if global_logger.level is logging.NOTSET:
|
||||
global_logger.setLevel(logging.DEBUG)
|
||||
console_logging_level = logging._levelToName[ # pylint: disable=protected-access
|
||||
global_logger.level
|
||||
]
|
||||
|
||||
config["loggers"].setdefault(
|
||||
global_logger_name,
|
||||
{
|
||||
"level": logging.DEBUG,
|
||||
"handlers": [handler.get_name() for handler in global_logger.handlers]
|
||||
+ [global_logger_name + ".console", global_logger_name + ".file"],
|
||||
"propagate": False,
|
||||
},
|
||||
)
|
||||
config["loggers"].setdefault(
|
||||
"{logger_name}",
|
||||
{
|
||||
"level": "DEBUG",
|
||||
"handlers": [
|
||||
"{logger_name}.file",
|
||||
],
|
||||
"propagate": False,
|
||||
},
|
||||
)
|
||||
config["handlers"].setdefault(
|
||||
global_logger_name + ".console",
|
||||
{
|
||||
"class": "logging.StreamHandler",
|
||||
"stream": "ext://sys.stdout",
|
||||
"formatter": "tvm.s_tir.meta_schedule.standard_formatter",
|
||||
"level": console_logging_level,
|
||||
},
|
||||
)
|
||||
config["handlers"].setdefault(
|
||||
global_logger_name + ".file",
|
||||
{
|
||||
"class": "logging.FileHandler",
|
||||
"filename": "{log_dir}/" + __name__ + ".task_scheduler.log",
|
||||
"mode": "a",
|
||||
"level": "DEBUG",
|
||||
"formatter": "tvm.s_tir.meta_schedule.standard_formatter",
|
||||
},
|
||||
)
|
||||
config["handlers"].setdefault(
|
||||
"{logger_name}.file",
|
||||
{
|
||||
"class": "logging.FileHandler",
|
||||
"filename": "{log_dir}/{logger_name}.log",
|
||||
"mode": "a",
|
||||
"level": "DEBUG",
|
||||
"formatter": "tvm.s_tir.meta_schedule.standard_formatter",
|
||||
},
|
||||
)
|
||||
config["formatters"].setdefault(
|
||||
"tvm.s_tir.meta_schedule.standard_formatter",
|
||||
{
|
||||
"format": "%(asctime)s [%(levelname)s] %(message)s",
|
||||
"datefmt": "%Y-%m-%d %H:%M:%S",
|
||||
},
|
||||
)
|
||||
|
||||
# set up dictConfig loggers
|
||||
p_config = {"version": 1, "disable_existing_loggers": disable_existing_loggers}
|
||||
for k, v in config.items():
|
||||
if k in ["formatters", "handlers", "loggers"]:
|
||||
p_config[k] = _batch_parameterize_config(v, params) # type: ignore
|
||||
else:
|
||||
p_config[k] = v
|
||||
logging.config.dictConfig(p_config)
|
||||
|
||||
# check global logger
|
||||
if global_logger.level not in [logging.DEBUG, logging.INFO]:
|
||||
global_logger.warning(
|
||||
"Logging level set to %s, please set to logging.INFO"
|
||||
" or logging.DEBUG to view full log.",
|
||||
logging._levelToName[global_logger.level], # pylint: disable=protected-access
|
||||
)
|
||||
global_logger.info("Logging directory: %s", log_dir)
|
||||
|
||||
|
||||
def _batch_parameterize_config(
|
||||
config: dict[str, Any],
|
||||
params: list[dict[str, str]],
|
||||
) -> dict[str, Any]:
|
||||
"""Parameterize the given configuration with multiple parameters sets.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
config : Dict[str, Any]
|
||||
The given config dict.
|
||||
Params : List[Dict[str, str]]
|
||||
List of the given multiple parameters sets.
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : Dict[str, Any]
|
||||
The parameterized configuration.
|
||||
"""
|
||||
results = {}
|
||||
for name, cfg in config.items():
|
||||
for p in params:
|
||||
p_name = name.format(**p)
|
||||
if p_name not in results:
|
||||
p_cfg = _parameterize_config(cfg, p)
|
||||
results[p_name] = p_cfg
|
||||
return results
|
||||
|
||||
|
||||
def _parameterize_config(
|
||||
config: dict[str, Any],
|
||||
params: dict[str, str],
|
||||
) -> dict[str, Any]:
|
||||
"""Parameterize the given configuration.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
config : Dict[str, Any]
|
||||
The given config dict.
|
||||
Params : Dict[str, str]
|
||||
The given parameters.
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : Dict[str, Any]
|
||||
The parameterized configuration.
|
||||
"""
|
||||
result = {}
|
||||
for k, v in config.items():
|
||||
if isinstance(k, str):
|
||||
k = k.format(**params)
|
||||
if isinstance(v, str):
|
||||
v = v.format(**params)
|
||||
elif isinstance(v, dict):
|
||||
v = _parameterize_config(v, params)
|
||||
elif isinstance(v, list):
|
||||
v = [t.format(**params) for t in v]
|
||||
result[k] = v
|
||||
return result
|
||||
|
||||
|
||||
def get_loggers_from_work_dir(
|
||||
work_dir: str,
|
||||
task_names: list[str],
|
||||
) -> list[Logger]:
|
||||
"""Create loggers from work directory
|
||||
|
||||
Parameters
|
||||
----------
|
||||
work_dir : str
|
||||
The work directory.
|
||||
task_names : List[str]
|
||||
The list of task names.
|
||||
|
||||
Returns
|
||||
-------
|
||||
loggers : List[Logger]
|
||||
The list of loggers.
|
||||
"""
|
||||
log_dir = osp.join(work_dir, "logs")
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
pattern = __name__ + ".task_{i:0" + f"{len(str(len(task_names) - 1))}" + "d}_{name}"
|
||||
# Very long names may need be clipped to prevent os errors, we use the first 100 characters.
|
||||
loggers = [pattern.format(i=i, name=name[:100]) for i, name in enumerate(task_names)]
|
||||
create_loggers(
|
||||
log_dir=log_dir,
|
||||
params=[{"log_dir": log_dir, "logger_name": logger} for logger in loggers],
|
||||
)
|
||||
return [get_logger(logger) for logger in loggers]
|
||||
@@ -0,0 +1,23 @@
|
||||
# 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.measure_callback package."""
|
||||
|
||||
from .add_to_database import AddToDatabase
|
||||
from .measure_callback import MeasureCallback, PyMeasureCallback
|
||||
from .remove_build_artifact import RemoveBuildArtifact
|
||||
from .update_cost_model import UpdateCostModel
|
||||
@@ -0,0 +1,31 @@
|
||||
# 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.
|
||||
"""A callback that adds the measurement results into the database"""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .measure_callback import MeasureCallback
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.AddToDatabase")
|
||||
class AddToDatabase(MeasureCallback):
|
||||
def __init__(self) -> None:
|
||||
"""A callback that adds the measurement results into the database"""
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.MeasureCallbackAddToDatabase, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
@@ -0,0 +1,141 @@
|
||||
# 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
|
||||
"""Meta Schedule MeasureCallback."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING, 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 ..builder import BuilderResult
|
||||
from ..runner import RunnerResult
|
||||
from ..search_strategy import MeasureCandidate
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..task_scheduler import TaskScheduler
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.MeasureCallback")
|
||||
class MeasureCallback(Object):
|
||||
"""Rules to apply after measure results is available."""
|
||||
|
||||
CallbackListType = Union[list["MeasureCallback"], "MeasureCallback", Literal["default"]]
|
||||
|
||||
def apply(
|
||||
self,
|
||||
task_scheduler: "TaskScheduler",
|
||||
task_id: int,
|
||||
measure_candidates: list[MeasureCandidate],
|
||||
builder_results: list[BuilderResult],
|
||||
runner_results: list[RunnerResult],
|
||||
) -> None:
|
||||
"""Apply a measure callback to the given schedule.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
task_scheduler: TaskScheduler
|
||||
The task scheduler.
|
||||
task_id: int
|
||||
The task id.
|
||||
measure_candidates: List[MeasureCandidate]
|
||||
The measure candidates.
|
||||
builder_results: List[BuilderResult]
|
||||
The builder results by building the measure candidates.
|
||||
runner_results: List[RunnerResult]
|
||||
The runner results by running the built measure candidates.
|
||||
"""
|
||||
return _ffi_api.MeasureCallbackApply( # type: ignore # pylint: disable=no-member
|
||||
self,
|
||||
task_scheduler,
|
||||
task_id,
|
||||
measure_candidates,
|
||||
builder_results,
|
||||
runner_results,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create(kind: Literal["default"]) -> list["MeasureCallback"]:
|
||||
"""Create a list of measure callbacks."""
|
||||
if kind == "default":
|
||||
return _ffi_api.MeasureCallbackDefault() # type: ignore # pylint: disable=no-member
|
||||
raise ValueError(f"Unknown kind of MeasureCallback list: {kind}")
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.PyMeasureCallback")
|
||||
class _PyMeasureCallback(MeasureCallback):
|
||||
"""
|
||||
A TVM object measure callback to support customization on the python side.
|
||||
This is NOT the user facing class for function overloading inheritance.
|
||||
|
||||
See also: PyMeasureCallback
|
||||
"""
|
||||
|
||||
def __init__(self, f_apply: Callable):
|
||||
"""Constructor."""
|
||||
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.MeasureCallbackPyMeasureCallback, # type: ignore # pylint: disable=no-member
|
||||
f_apply,
|
||||
)
|
||||
|
||||
|
||||
class PyMeasureCallback:
|
||||
"""
|
||||
An abstract measure callback with customized methods 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": _PyMeasureCallback,
|
||||
"methods": ["apply"],
|
||||
}
|
||||
|
||||
def apply(
|
||||
self,
|
||||
task_scheduler: "TaskScheduler",
|
||||
task_id: int,
|
||||
measure_candidates: list[MeasureCandidate],
|
||||
builder_results: list[BuilderResult],
|
||||
runner_results: list[RunnerResult],
|
||||
) -> None:
|
||||
"""Apply a measure callback to the given schedule.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
task_scheduler: TaskScheduler
|
||||
The task scheduler.
|
||||
task_id: int
|
||||
The task id.
|
||||
measure_candidates: List[MeasureCandidate]
|
||||
The measure candidates.
|
||||
builder_results: List[BuilderResult]
|
||||
The builder results by building the measure candidates.
|
||||
runner_results: List[RunnerResult]
|
||||
The runner results by running the built measure candidates.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,31 @@
|
||||
# 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.
|
||||
"""A callback that removes the build artifacts from the disk"""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .measure_callback import MeasureCallback
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.RemoveBuildArtifact")
|
||||
class RemoveBuildArtifact(MeasureCallback):
|
||||
def __init__(self) -> None:
|
||||
"""A callback that removes the build artifacts from the disk"""
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.MeasureCallbackRemoveBuildArtifact, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
@@ -0,0 +1,31 @@
|
||||
# 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.
|
||||
"""A measure callback that updates the cost model"""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .measure_callback import MeasureCallback
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.UpdateCostModel")
|
||||
class UpdateCostModel(MeasureCallback):
|
||||
def __init__(self) -> None:
|
||||
"""A measure callback that updates the cost model"""
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.MeasureCallbackUpdateCostModel, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
@@ -0,0 +1,29 @@
|
||||
# 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.mutator package.
|
||||
Meta Schedule mutator that mutates the trace to explore the
|
||||
design space.
|
||||
"""
|
||||
|
||||
from .mutator import Mutator, PyMutator
|
||||
from .mutate_compute_location import MutateComputeLocation
|
||||
from .mutate_tile_size import MutateTileSize
|
||||
from .mutate_thread_binding import MutateThreadBinding
|
||||
from .mutate_parallel import MutateParallel
|
||||
from .mutate_unroll import MutateUnroll
|
||||
@@ -0,0 +1,32 @@
|
||||
# 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.
|
||||
"""A mutator that mutates the compute-at location decision of SampleComputeLocation"""
|
||||
|
||||
from tvm_ffi.registry import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .mutator import Mutator
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.MutateComputeLocation")
|
||||
class MutateComputeLocation(Mutator):
|
||||
"""A mutator that mutates the compute-at location decision of SampleComputeLocation"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.MutatorMutateComputeLocation, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
# 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.
|
||||
"""Mutator that mutates the parallel extent"""
|
||||
|
||||
from tvm_ffi.registry import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .mutator import Mutator
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.MutateParallel")
|
||||
class MutateParallel(Mutator):
|
||||
"""Mutator that mutates the parallel extent"""
|
||||
|
||||
def __init__(self, max_jobs_per_core: int) -> None:
|
||||
"""Mutator that mutates the parallel extent"""
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.MutatorMutateParallel, # type: ignore # pylint: disable=no-member
|
||||
max_jobs_per_core,
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
# 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.
|
||||
"""Mutator that mutates the thread binding extent"""
|
||||
|
||||
from tvm_ffi.registry import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .mutator import Mutator
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.MutateThreadBinding")
|
||||
class MutateThreadBinding(Mutator):
|
||||
"""Mutator that mutates the binding extent"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Mutator that mutates the binding extent"""
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.MutateThreadBinding, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
# 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.
|
||||
"""Mutator that mutates the decision of instruction Sample-Perfect-Tile"""
|
||||
|
||||
from tvm_ffi.registry import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .mutator import Mutator
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.MutateTileSize")
|
||||
class MutateTileSize(Mutator):
|
||||
"""Mutator that mutates the decision of instruction Sample-Perfect-Tile"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.MutatorMutateTileSize, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
# 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.
|
||||
"""Mutator that mutates auto unroll step"""
|
||||
|
||||
from tvm_ffi.registry import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .mutator import Mutator
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.MutateUnroll")
|
||||
class MutateUnroll(Mutator):
|
||||
"""Mutator that mutates auto unroll step"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.MutatorMutateUnroll, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
@@ -0,0 +1,188 @@
|
||||
# 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
|
||||
"""Meta Schedule Mutator."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
# isort: off
|
||||
from typing import Literal
|
||||
|
||||
# isort: on
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from tvm.runtime import Object
|
||||
from tvm.s_tir.schedule import Trace
|
||||
|
||||
from .. import _ffi_api
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..tune_context import TuneContext
|
||||
|
||||
|
||||
class Mutator(Object):
|
||||
"""Mutator is designed to mutate the trace to explore the design space."""
|
||||
|
||||
def _initialize_with_tune_context(self, context: "TuneContext") -> None:
|
||||
"""Initialize the mutator with a tune context.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : TuneContext
|
||||
The tuning context for initializing the mutator.
|
||||
"""
|
||||
_ffi_api.MutatorInitializeWithTuneContext( # type: ignore # pylint: disable=no-member
|
||||
self, context
|
||||
)
|
||||
|
||||
def apply(self, trace: Trace) -> Trace | None:
|
||||
"""Apply the mutator function to the given trace.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
trace : Trace
|
||||
The given trace for mutation.
|
||||
|
||||
Returns
|
||||
-------
|
||||
trace : Optional[Trace]
|
||||
None if mutator failed, otherwise return the mutated trace.
|
||||
"""
|
||||
return _ffi_api.MutatorApply(self, trace, -1) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def clone(self) -> "Mutator":
|
||||
"""Clone the mutator.
|
||||
|
||||
Returns
|
||||
-------
|
||||
mutator : Mutator
|
||||
The cloned mutator.
|
||||
"""
|
||||
return _ffi_api.MutatorClone(self) # type: ignore # pylint: disable=no-member
|
||||
|
||||
@staticmethod
|
||||
def create(
|
||||
kind: Literal[
|
||||
"llvm",
|
||||
"cuda",
|
||||
"cuda-tensorcore",
|
||||
"hexagon",
|
||||
],
|
||||
) -> dict["Mutator", float]:
|
||||
"""Create a list of default mutators.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
kind : Literal["llvm", "cuda", "cuda-tensorcore", "hexagon"]
|
||||
The kind of mutators.
|
||||
|
||||
Returns
|
||||
-------
|
||||
mutators : List[Mutator]
|
||||
The list of mutators.
|
||||
"""
|
||||
funcs = {
|
||||
# pylint: disable=no-member
|
||||
"llvm": _ffi_api.MutatorDefaultLLVM, # type: ignore
|
||||
"cuda": _ffi_api.MutatorDefaultCUDA, # type: ignore
|
||||
"cuda-tensorcore": _ffi_api.MutatorDefaultCUDATensorCore, # type: ignore
|
||||
"hexagon": _ffi_api.MutatorDefaultHexagon, # type: ignore
|
||||
# pylint: enable=no-member
|
||||
}
|
||||
for k, v in funcs.items():
|
||||
if k == kind:
|
||||
return v()
|
||||
raise ValueError(f"Unsupported kind {kind} for mutator creation.")
|
||||
|
||||
|
||||
create = Mutator.create # pylint: disable=invalid-name
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.PyMutator")
|
||||
class _PyMutator(Mutator):
|
||||
"""
|
||||
A TVM object mutator to support customization on the python side.
|
||||
This is NOT the user facing class for function overloading inheritance.
|
||||
|
||||
See also: PyMutator
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
f_initialize_with_tune_context: Callable | None = None,
|
||||
f_apply: Callable | None = None,
|
||||
f_clone: Callable | None = None,
|
||||
):
|
||||
"""Constructor."""
|
||||
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.MutatorPyMutator, # type: ignore # pylint: disable=no-member
|
||||
f_initialize_with_tune_context,
|
||||
f_apply,
|
||||
f_clone,
|
||||
)
|
||||
|
||||
|
||||
class PyMutator:
|
||||
"""
|
||||
An abstract mutator with customized methods 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": _PyMutator,
|
||||
"methods": ["_initialize_with_tune_context", "apply", "clone"],
|
||||
}
|
||||
|
||||
def _initialize_with_tune_context(self, context: "TuneContext") -> None:
|
||||
"""Initialize the mutator with a tune context.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : TuneContext
|
||||
The tuning context for initializing the mutator.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def apply(self, trace: Trace, _) -> Trace | None:
|
||||
"""Apply the mutator function to the given trace.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
trace : Trace
|
||||
The given trace for mutation.
|
||||
|
||||
Returns
|
||||
-------
|
||||
trace : Optional[Trace]
|
||||
None if mutator failed, otherwise return the mutated trace.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def clone(self) -> Mutator:
|
||||
"""Clone the mutator.
|
||||
|
||||
Returns
|
||||
-------
|
||||
mutator : Mutator
|
||||
The cloned mutator.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,26 @@
|
||||
# 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.database package.
|
||||
The database that stores serialized tuning records and workloads
|
||||
"""
|
||||
|
||||
from .post_opt import PostOpt
|
||||
from .droplet import Droplet
|
||||
from .space import Space
|
||||
from .utils import write_file, get_time
|
||||
@@ -0,0 +1,135 @@
|
||||
# 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.
|
||||
|
||||
"""Droplet algorithm"""
|
||||
|
||||
import os
|
||||
|
||||
import numpy as np # type: ignore
|
||||
|
||||
from .space import Space
|
||||
from .utils import get_time, write_file
|
||||
|
||||
|
||||
class Droplet:
|
||||
"""Tuner with droplet algorithm in Meta Schedule.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
json_file: str
|
||||
json format file
|
||||
target:
|
||||
hardware target
|
||||
log: str
|
||||
path to save json file
|
||||
trials: int
|
||||
number of samples, the default is 100
|
||||
pvalue: float
|
||||
statistical value to confidence level, the default is 0.05
|
||||
"""
|
||||
|
||||
def __init__(self, json_file, workload_file, target, log, pvalue=0.05) -> None:
|
||||
self.space = Space(json_file, workload_file, target)
|
||||
self.final_log = write_file([json_file], log)
|
||||
self.pvalue = pvalue
|
||||
self.next = [(0, [0] * len(self.space.dims))]
|
||||
best_avg, _ = get_time(log)
|
||||
self.best_choice = [0, [0] * len(self.space.dims), best_avg]
|
||||
self.count, self.execution, self.found_best_pos = 1, 1, True
|
||||
self.total_execution = 1
|
||||
if len(self.space.dims) > 0:
|
||||
self.total_execution = max(self.space.dims)
|
||||
self.dims, self.step = self.space.dims, 1
|
||||
self.visited, self.batch = set([0]), max(os.cpu_count(), len(self.dims))
|
||||
|
||||
def next_batch(self, batch_size):
|
||||
i, json_file_list = 0, []
|
||||
while i < len(self.next):
|
||||
if batch_size > 0 and self.count >= self.trials:
|
||||
break
|
||||
json_file_list.append(self.space.template(values=self.next[i][1], create=False))
|
||||
i, self.count = i + 1, self.count + 1
|
||||
return self.space.run(json_file_list, self.final_log)
|
||||
|
||||
def has_next(self):
|
||||
return len(self.next) > 0 and self.found_best_pos
|
||||
|
||||
def tune(self, n_trial=100):
|
||||
self.trials = n_trial
|
||||
self.speculation()
|
||||
while self.has_next():
|
||||
res = self.next_batch(self.batch)
|
||||
self.update(res)
|
||||
|
||||
def num_to_bin(self, value, factor=1):
|
||||
bin_format = str(0) * (len(self.dims) - len(bin(value)[2:])) + bin(value)[2:]
|
||||
return [int(i) * factor for i in bin_format]
|
||||
|
||||
def search_space(self, factor=1):
|
||||
"create a search space"
|
||||
search_space: list = []
|
||||
for i in range(0, len(self.space.dims)):
|
||||
if len(search_space) > self.batch - len(self.next):
|
||||
break
|
||||
space = self.num_to_bin(2**i, factor)
|
||||
idx = self.space.knob2point(space)
|
||||
if idx not in self.visited:
|
||||
search_space.append(space)
|
||||
return search_space
|
||||
|
||||
def next_pos(self, new_positions):
|
||||
"returns the neighbors of the best solution"
|
||||
next_set = []
|
||||
for p in new_positions:
|
||||
new_p = [
|
||||
(x + y) % self.dims[i] if (x + y > 0) else 0
|
||||
for i, (x, y) in enumerate(zip(p, self.best_choice[1]))
|
||||
]
|
||||
idx_p = self.space.knob2point(new_p)
|
||||
if idx_p not in self.visited:
|
||||
self.visited.add(idx_p)
|
||||
next_set.append((idx_p, new_p))
|
||||
return next_set
|
||||
|
||||
def speculation(self):
|
||||
# Gradient descending direction prediction and search space filling
|
||||
while len(self.next) < self.batch and self.execution < self.total_execution:
|
||||
self.next += self.next_pos(self.search_space(self.execution))
|
||||
self.execution += self.step
|
||||
|
||||
def update(self, results):
|
||||
"""Update the values"""
|
||||
self.found_best_pos, count_valids = False, 0
|
||||
for i, res in enumerate(results):
|
||||
if np.mean(self.best_choice[2]) > np.mean(res):
|
||||
self.best_choice = [self.next[i][0], self.next[i][1], res]
|
||||
self.found_best_pos = True
|
||||
if np.mean(res) != 10000:
|
||||
count_valids += 1
|
||||
|
||||
self.next = []
|
||||
|
||||
# stop, because all neighborhoods are invalid.
|
||||
if count_valids == 0:
|
||||
self.speculation()
|
||||
self.found_best_pos = True
|
||||
return
|
||||
|
||||
if self.found_best_pos:
|
||||
self.next += self.next_pos(self.search_space())
|
||||
self.execution = 1
|
||||
self.speculation()
|
||||
@@ -0,0 +1,76 @@
|
||||
# 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.
|
||||
"""Post optimization method"""
|
||||
|
||||
import numpy as np # type: ignore
|
||||
|
||||
from tvm.target import Target
|
||||
|
||||
from .droplet import Droplet
|
||||
from .utils import clean_file, get_time, read_cfg_file, write_file
|
||||
|
||||
|
||||
class PostOpt:
|
||||
"""PostOpt class
|
||||
|
||||
Parameters
|
||||
----------
|
||||
work_dir : str
|
||||
The working directory.
|
||||
target: Target data
|
||||
Target device information
|
||||
trials: integer value
|
||||
Max number of trials to execute the optimization
|
||||
"""
|
||||
|
||||
def __init__(self, work_dir: str, target: Target, trials: int = 100) -> None:
|
||||
self.work_dir = work_dir
|
||||
self.target = target
|
||||
self.trials = trials
|
||||
|
||||
def run(self) -> None:
|
||||
"""Execute the post optimization"""
|
||||
|
||||
tuning_file = self.work_dir + "/database_tuning_record.json"
|
||||
workload_file = self.work_dir + "/database_workload.json"
|
||||
|
||||
cfg = read_cfg_file(tuning_file, workload_file)
|
||||
|
||||
print("id | time MS (s) | time DPMS (s) | speedup")
|
||||
for idx, layer in enumerate(cfg):
|
||||
time, data, workload = cfg[layer]
|
||||
ms_time = np.mean(time)
|
||||
|
||||
temp_log = f"{self.work_dir}/opt_{idx}.log"
|
||||
|
||||
# Run the exploitation by Droplet
|
||||
droplet = Droplet(data, workload, self.target, temp_log)
|
||||
droplet.tune(self.trials)
|
||||
|
||||
dpms_time, dpm_sol = get_time(temp_log)
|
||||
dpms_time = np.mean(dpms_time)
|
||||
|
||||
speedup = ms_time / dpms_time
|
||||
|
||||
# save the best solution
|
||||
write_file([dpm_sol], tuning_file, mode="a")
|
||||
|
||||
# show the perfomance
|
||||
print(f"{idx:2d} | {ms_time:.10f} | {dpms_time:.10f} | {speedup:.2f}")
|
||||
|
||||
# clean the temporary files
|
||||
clean_file(temp_log)
|
||||
@@ -0,0 +1,260 @@
|
||||
# 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 class of Space used to optimize the Meta parameters"""
|
||||
|
||||
import json
|
||||
import random
|
||||
from copy import deepcopy
|
||||
from typing import Any
|
||||
|
||||
import numpy as np # type: ignore
|
||||
|
||||
from tvm.s_tir import Schedule
|
||||
from tvm.s_tir import meta_schedule as ms
|
||||
from tvm.s_tir.meta_schedule.database import TuningRecord, Workload
|
||||
from tvm.s_tir.meta_schedule.utils import remove_build_dir
|
||||
from tvm.target import Target
|
||||
|
||||
from .utils import write_file
|
||||
|
||||
|
||||
class Space:
|
||||
"""Space class
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data: json data
|
||||
A json file template
|
||||
workload: json data
|
||||
A json file workload
|
||||
target: Target data
|
||||
Target device information
|
||||
"""
|
||||
|
||||
def __init__(self, data: Any, workload: Any, target: Target):
|
||||
self.cfg = deepcopy(data)
|
||||
self._id = data[0]
|
||||
self.workload = Workload.from_json(workload)
|
||||
self.target = target
|
||||
self.dev = self.get_device_type(target)
|
||||
self.total_dims = 0
|
||||
self.dims: list[int] = []
|
||||
self.start: list[int] = []
|
||||
self.config_space: dict[str, list[int]] = dict()
|
||||
self.create_space()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Print the config space"""
|
||||
out = ""
|
||||
for key in self.config_space:
|
||||
out += f"{key}: dims={self.config_space[key]}\n"
|
||||
out += f"Total dimensions: {self.total_dims}\n"
|
||||
return out
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Print the config space"""
|
||||
out = ""
|
||||
for key in self.config_space:
|
||||
out += f"{key}: dims={self.config_space[key]}\n"
|
||||
out += f"Total dimensions: {self.total_dims}\n"
|
||||
return out
|
||||
|
||||
def get_value(self, key, pos):
|
||||
"""Return the space"""
|
||||
return self.config_space[key][pos]
|
||||
|
||||
def add_space(self, space_list: list, element_list: list, limit=10000) -> list[int]:
|
||||
"""Return a list without repeat and with limited value"""
|
||||
new_list = element_list
|
||||
for elem in space_list:
|
||||
if elem not in new_list and elem <= limit:
|
||||
new_list.append(elem)
|
||||
return new_list
|
||||
|
||||
def knob2point(self, knob):
|
||||
"""Convert a array to point"""
|
||||
point = 0
|
||||
for j, k in enumerate(knob):
|
||||
point += int(np.prod(self.dims[:j])) * k
|
||||
return point
|
||||
|
||||
def point2knob(self, point):
|
||||
"""Convert point form (single integer) to knob (vector)"""
|
||||
knob = []
|
||||
for dim in self.dims:
|
||||
knob.append(point % dim)
|
||||
point //= dim
|
||||
return knob
|
||||
|
||||
def power_of_two(self, min_value: int, max_value: int) -> list:
|
||||
"""Return power of two array in interval"""
|
||||
return [1 << i for i in range(min_value, max_value + 1)]
|
||||
|
||||
def get_index(self, array: list, value: int):
|
||||
"""returns an index if it finds the value"""
|
||||
for i in range(len(array)):
|
||||
if array[i][0] == value:
|
||||
return i
|
||||
return -1
|
||||
|
||||
def template(self, values=None, create=True):
|
||||
"""Generate the template from the values"""
|
||||
idx = -1
|
||||
config = deepcopy(self.cfg[1])
|
||||
for counter, cfg in enumerate(config[0][0]):
|
||||
opt = cfg[0]
|
||||
if opt == "Annotate":
|
||||
ann_key = cfg[2]
|
||||
if ann_key == ["meta_schedule.parallel"]:
|
||||
interval = self.power_of_two(5, 9)
|
||||
elif ann_key == ["meta_schedule.vectorize"]:
|
||||
interval = self.power_of_two(4, 8)
|
||||
elif ann_key == ["pragma_auto_unroll_max_step"]:
|
||||
interval = self.power_of_two(7, 11)
|
||||
elif ann_key == ["meta_schedule.thread_extent_low_inclusive"]:
|
||||
interval = self.power_of_two(5, 6)
|
||||
elif ann_key == ["meta_schedule.thread_extent_high_inclusive"]:
|
||||
interval = self.power_of_two(8, 12)
|
||||
else:
|
||||
continue
|
||||
idx += 1
|
||||
key = f"ann_{idx}"
|
||||
ann_value = cfg[1][1]
|
||||
if create:
|
||||
self.config_space[key] = self.add_space(interval, [ann_value])
|
||||
else:
|
||||
cfg[1][1] = self.get_value(key, values[idx])
|
||||
elif opt == "SamplePerfectTile":
|
||||
tile = config[0][1]
|
||||
tile_idx = self.get_index(tile, counter)
|
||||
tile_val = tile[tile_idx][1]
|
||||
interval = self.power_of_two(1, 6)
|
||||
for i in range(len(tile_val)):
|
||||
idx += 1
|
||||
key = f"sp_{counter}_{idx}"
|
||||
split = tile_val[i]
|
||||
if create:
|
||||
self.config_space[key] = self.add_space(interval, [split])
|
||||
else:
|
||||
config[0][1][tile_idx][1][i] = self.get_value(key, values[idx])
|
||||
elif opt == "TransformLayout":
|
||||
del config[0][0][counter]
|
||||
if create:
|
||||
return None
|
||||
return config
|
||||
|
||||
def create_space(self):
|
||||
"""Create the space using Meta's space"""
|
||||
self.template(create=True)
|
||||
# print(self.config_space)
|
||||
self.dims = []
|
||||
for key in self.config_space:
|
||||
self.dims.append(len(self.config_space[key]))
|
||||
self.total_dims = 1
|
||||
if len(self.dims) > 0:
|
||||
for dim in self.dims:
|
||||
self.total_dims *= dim
|
||||
|
||||
def get_device_type(self, target: Target) -> str:
|
||||
"""Get the device type string from a target.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
target : Target
|
||||
The target to get the device type from.
|
||||
|
||||
Returns
|
||||
-------
|
||||
device_type : str
|
||||
The device type string.
|
||||
"""
|
||||
if target.kind.name == "llvm":
|
||||
return "cpu"
|
||||
elif target.kind.name == "cuda":
|
||||
return "cuda"
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported target kind for device type: {target.kind.name}")
|
||||
|
||||
def save_log(
|
||||
self,
|
||||
path: str,
|
||||
record: ms.database.TuningRecord,
|
||||
results: ms.runner.RunnerResult,
|
||||
) -> None:
|
||||
"""Save the log file"""
|
||||
new_json = [self._id, record.as_json()]
|
||||
new_json[1][1] = results
|
||||
write_file([new_json], path, "a")
|
||||
|
||||
def run(
|
||||
self,
|
||||
json_file_list,
|
||||
final_log,
|
||||
timeout=10,
|
||||
number=2,
|
||||
repeat=3,
|
||||
min_repeat_ms=0,
|
||||
cpu_cache=False,
|
||||
):
|
||||
"""Execute a log file and save"""
|
||||
|
||||
builder = ms.builder.LocalBuilder(timeout_sec=timeout)
|
||||
runner = ms.runner.LocalRunner(
|
||||
evaluator_config=ms.runner.EvaluatorConfig(
|
||||
number=number,
|
||||
repeat=repeat,
|
||||
min_repeat_ms=min_repeat_ms,
|
||||
enable_cpu_cache_flush=cpu_cache,
|
||||
),
|
||||
)
|
||||
|
||||
results = np.full(len(json_file_list), [10000], dtype=list)
|
||||
records, mods = [], []
|
||||
for i, cfg in enumerate(json_file_list):
|
||||
try:
|
||||
record = TuningRecord.from_json(json.loads(json.dumps(cfg)), self.workload)
|
||||
sch = Schedule(self.workload.mod)
|
||||
# In some layers this is a heavy impact in time cost, so
|
||||
# I applied this only 25% of the samples.
|
||||
remove_postproc = random.random() > 0.75
|
||||
record.trace.apply_to_schedule(sch, remove_postproc=remove_postproc)
|
||||
mods.append(sch.mod)
|
||||
records.append(record)
|
||||
except Exception: # pylint: disable=broad-except, invalid-name
|
||||
continue
|
||||
|
||||
builder_res = builder.build([ms.builder.BuilderInput(mod, self.target) for mod in mods])
|
||||
|
||||
for i, record in enumerate(records):
|
||||
try:
|
||||
inp = ms.runner.RunnerInput(
|
||||
builder_res[i].artifact_path,
|
||||
device_type=self.dev,
|
||||
args_info=ms.arg_info.TensorInfo.from_prim_func(mods[i]["main"]),
|
||||
)
|
||||
runner_res = runner.run([inp])[0].result()
|
||||
results[i] = [v.value for v in runner_res.run_secs] # type: ignore
|
||||
except Exception: # pylint: disable=broad-except, invalid-name
|
||||
results[i] = [1e10]
|
||||
continue
|
||||
|
||||
# save the solution in json file
|
||||
self.save_log(final_log, record, results[i])
|
||||
|
||||
# clean up
|
||||
remove_build_dir(builder_res[i].artifact_path)
|
||||
return results
|
||||
@@ -0,0 +1,112 @@
|
||||
# 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.
|
||||
"""Utils file for exploitation schedule"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
import numpy as np # type: ignore
|
||||
|
||||
|
||||
def write_file(json_list: list, log: str = "/tmp/file.json", mode: str = "w") -> str:
|
||||
"""Write the log file
|
||||
|
||||
Parameters
|
||||
----------
|
||||
json_list: list
|
||||
The list input json
|
||||
log: Optional[str]
|
||||
Path destiny to save the log file
|
||||
mode: Optional[str]
|
||||
Mode save, "a" means append and "w" means write
|
||||
|
||||
Returns
|
||||
-------
|
||||
ret: str
|
||||
log path file
|
||||
"""
|
||||
with open(log, mode, encoding="utf-8") as outfile:
|
||||
for j in json_list:
|
||||
outfile.write(json.dumps(j) + "\n")
|
||||
return log
|
||||
|
||||
|
||||
def clean_file(filename: str) -> None:
|
||||
"""Clean temporary files
|
||||
|
||||
Parameters
|
||||
----------
|
||||
filename: str
|
||||
The filepath with remove from the system
|
||||
"""
|
||||
if os.path.isfile(filename):
|
||||
os.remove(filename)
|
||||
|
||||
|
||||
def get_time(log: str) -> list:
|
||||
"""Get the time from the log file
|
||||
|
||||
Parameters
|
||||
----------
|
||||
log: str
|
||||
log file
|
||||
|
||||
Returns
|
||||
-------
|
||||
ret: list
|
||||
A list with the best time and the json data
|
||||
"""
|
||||
best_time = [1e10, None]
|
||||
with open(log, encoding="utf-8") as log_file:
|
||||
for line in log_file.readlines():
|
||||
data = json.loads(line)
|
||||
params = data[1]
|
||||
time = params[1]
|
||||
if np.mean(best_time[0]) > np.mean(time):
|
||||
best_time = [time, data]
|
||||
return best_time
|
||||
|
||||
|
||||
def read_cfg_file(path_tuning_file: str, path_workload_file: str) -> dict[int, list]:
|
||||
"""Colect the info from meta logfile
|
||||
|
||||
Parameters
|
||||
----------
|
||||
log: str
|
||||
The input log path with the meta parameter
|
||||
|
||||
Returns
|
||||
-------
|
||||
ret: dict[layer, Union[time, dict]]
|
||||
Returns the best time, total time, and data
|
||||
"""
|
||||
workload_list = []
|
||||
with open(path_workload_file, encoding="utf-8") as log_file:
|
||||
for line in log_file.readlines():
|
||||
workload_list.append(json.loads(line))
|
||||
|
||||
cfg: dict[int, list] = dict()
|
||||
with open(path_tuning_file, encoding="utf-8") as log_file:
|
||||
for line in log_file.readlines():
|
||||
data = json.loads(line)
|
||||
layer = data[0]
|
||||
params = data[1]
|
||||
time = params[1]
|
||||
|
||||
if layer not in cfg.keys() or np.mean(cfg[layer][0]) > np.mean(time):
|
||||
cfg[layer] = [time, data, workload_list[layer]]
|
||||
return cfg
|
||||
@@ -0,0 +1,30 @@
|
||||
# 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.postproc package."""
|
||||
|
||||
from .disallow_dynamic_loop import DisallowDynamicLoop
|
||||
from .disallow_async_strided_mem_copy import DisallowAsyncStridedMemCopy
|
||||
from .postproc import Postproc, PyPostproc
|
||||
from .rewrite_cooperative_fetch import RewriteCooperativeFetch
|
||||
from .rewrite_layout import RewriteLayout
|
||||
from .rewrite_parallel_vectorize_unroll import RewriteParallelVectorizeUnroll
|
||||
from .rewrite_reduction_block import RewriteReductionBlock
|
||||
from .rewrite_tensorize import RewriteTensorize
|
||||
from .rewrite_unbound_block import RewriteUnboundBlock
|
||||
from .verify_gpu_code import VerifyGPUCode
|
||||
from .verify_vtcm_limit import VerifyVTCMLimit
|
||||
@@ -0,0 +1,32 @@
|
||||
# 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.
|
||||
"""A postprocessor that checks if the IRModule has any strided memory copies"""
|
||||
|
||||
from tvm_ffi.registry import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .postproc import Postproc
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.DisallowAsyncStridedMemCopy")
|
||||
class DisallowAsyncStridedMemCopy(Postproc):
|
||||
"""A postprocessor that disallows schedules that use async strided mem copies."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.PostprocDisallowAsyncStridedMemCopy, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
# 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.
|
||||
"""A postprocessor that checks if the IRModule has any loop with non-constant extent"""
|
||||
|
||||
from tvm_ffi.registry import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .postproc import Postproc
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.DisallowDynamicLoop")
|
||||
class DisallowDynamicLoop(Postproc):
|
||||
"""A postprocessor that checks if the IRModule has any loop with non-constant extent"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.PostprocDisallowDynamicLoop, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
@@ -0,0 +1,182 @@
|
||||
# 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
|
||||
"""Meta Schedule Postproc."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
# isort: off
|
||||
from typing import Literal
|
||||
|
||||
# isort: on
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from tvm.runtime import Object
|
||||
from tvm.s_tir.schedule import Schedule
|
||||
|
||||
from .. import _ffi_api
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..tune_context import TuneContext
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.Postproc")
|
||||
class Postproc(Object):
|
||||
"""Rules to apply a postprocessor to a schedule."""
|
||||
|
||||
def _initialize_with_tune_context(self, context: "TuneContext") -> None:
|
||||
"""Initialize the postprocessor with a tune context.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : TuneContext
|
||||
The tuning context for initializing the postprocessor.
|
||||
"""
|
||||
_ffi_api.PostprocInitializeWithTuneContext( # type: ignore # pylint: disable=no-member
|
||||
self, context
|
||||
)
|
||||
|
||||
def apply(self, sch: Schedule) -> bool:
|
||||
"""Apply a postprocessor to the given schedule.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sch : tvm.s_tir.Schedule
|
||||
The schedule to be post processed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : bool
|
||||
Whether the postprocessor was successfully applied.
|
||||
"""
|
||||
return _ffi_api.PostprocApply(self, sch) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def clone(self) -> "Postproc":
|
||||
"""Clone the postprocessor.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cloned_postproc : Postproc
|
||||
The cloned postprocessor.
|
||||
"""
|
||||
return _ffi_api.PostprocClone(self) # type: ignore # pylint: disable=no-member
|
||||
|
||||
@staticmethod
|
||||
def create(kind: Literal["llvm", "cuda", "cuda-tensorcore", "hexagon"]) -> list["Postproc"]:
|
||||
"""Create a list of default postprocessors.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
kind : Literal["llvm", "cuda", "cuda-tensorcore", "hexagon"]
|
||||
The kind of the postprocessors.
|
||||
|
||||
Returns
|
||||
-------
|
||||
postprocs : List[Mutator]
|
||||
The list of postprocessors.
|
||||
"""
|
||||
funcs = {
|
||||
# pylint: disable=no-member
|
||||
"llvm": _ffi_api.PostprocDefaultLLVM, # type: ignore
|
||||
"cuda": _ffi_api.PostprocDefaultCUDA, # type: ignore
|
||||
"cuda-tensorcore": _ffi_api.PostprocDefaultCUDATensorCore, # type: ignore
|
||||
"hexagon": _ffi_api.PostprocDefaultHexagon, # type: ignore
|
||||
# pylint: enable=no-member
|
||||
}
|
||||
for k, v in funcs.items():
|
||||
if k == kind:
|
||||
return v()
|
||||
raise ValueError(f"Unsupported kind {kind} for postproc creation.")
|
||||
|
||||
|
||||
create = Postproc.create # pylint: disable=invalid-name
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.PyPostproc")
|
||||
class _PyPostproc(Postproc):
|
||||
"""
|
||||
A TVM object post processor to support customization on the python side.
|
||||
This is NOT the user facing class for function overloading inheritance.
|
||||
|
||||
See also: PyPostproc
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
f_initialize_with_tune_context: Callable | None = None,
|
||||
f_apply: Callable | None = None,
|
||||
f_clone: Callable | None = None,
|
||||
):
|
||||
"""Constructor."""
|
||||
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.PostprocPyPostproc, # type: ignore # pylint: disable=no-member
|
||||
f_initialize_with_tune_context,
|
||||
f_apply,
|
||||
f_clone,
|
||||
)
|
||||
|
||||
|
||||
class PyPostproc:
|
||||
"""
|
||||
An abstract post processor with customized methods 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": _PyPostproc,
|
||||
"methods": ["_initialize_with_tune_context", "apply", "clone"],
|
||||
}
|
||||
|
||||
def _initialize_with_tune_context(self, context: "TuneContext") -> None:
|
||||
"""Initialize the postprocessor with a tune context.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : TuneContext
|
||||
The tuning context for initializing the postprocessor.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def apply(self, sch: Schedule) -> bool:
|
||||
"""Apply a postprocessor to the given schedule.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sch : Schedule
|
||||
The schedule to be post processed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : bool
|
||||
Whether the postprocessor was successfully applied.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def clone(self) -> Postproc:
|
||||
"""Clone the postprocessor.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cloned_postproc : Postproc
|
||||
The cloned postprocessor.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,35 @@
|
||||
# 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.
|
||||
"""A postprocessor that rewrites the cooperative fetch annotation to actual
|
||||
vectorized cooperative fetching in loop bindings."""
|
||||
|
||||
from tvm_ffi.registry import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .postproc import Postproc
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.RewriteCooperativeFetch")
|
||||
class RewriteCooperativeFetch(Postproc):
|
||||
"""A postprocessor that rewrites the cooperative fetch annotation to actual vectorized
|
||||
cooperative fetching in loop bindings.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.PostprocRewriteCooperativeFetch, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
# 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.
|
||||
"""A postprocessor that rewrites the layout of input tensor"""
|
||||
|
||||
from tvm_ffi.registry import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .postproc import Postproc
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.RewriteLayout")
|
||||
class RewriteLayout(Postproc):
|
||||
"""A postprocessor that rewrites the layout of input tensor"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.PostprocRewriteLayout, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
# 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.
|
||||
"""A postprocessor that applies parallelization, vectorization and auto unrolling
|
||||
according to the annotation of each block"""
|
||||
|
||||
from tvm_ffi.registry import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .postproc import Postproc
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.RewriteParallelVectorizeUnroll")
|
||||
class RewriteParallelVectorizeUnroll(Postproc):
|
||||
"""A postprocessor that applies parallelization, vectorization and auto unrolling
|
||||
according to the annotation of each block"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.PostprocRewriteParallelVectorizeUnroll, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
# 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.
|
||||
"""A postprocessor that rewrites reduction block by moving the init block out."""
|
||||
|
||||
from tvm_ffi.registry import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .postproc import Postproc
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.RewriteReductionBlock")
|
||||
class RewriteReductionBlock(Postproc):
|
||||
"""A postprocessor that rewrites reduction block by moving the init block out."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.PostprocRewriteReductionBlock, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
@@ -0,0 +1,39 @@
|
||||
# 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.
|
||||
"""A postprocessor that tensorize related components."""
|
||||
|
||||
from tvm_ffi.registry import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .postproc import Postproc
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.RewriteTensorize")
|
||||
class RewriteTensorize(Postproc):
|
||||
"""A postprocessor that applies tensorization to annotated blocks.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
vectorize_init_loop : bool
|
||||
Whether or not vectorize the initialization loop produced by DecomposeReduction
|
||||
"""
|
||||
|
||||
def __init__(self, vectorize_init_loop=False) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.PostprocRewriteTensorize, # type: ignore # pylint: disable=no-member
|
||||
vectorize_init_loop,
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
# 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.
|
||||
"""A postprocessor that adds thread binding to unbound blocks"""
|
||||
|
||||
from tvm_ffi.registry import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .postproc import Postproc
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.RewriteUnboundBlock")
|
||||
class RewriteUnboundBlock(Postproc):
|
||||
"""A postprocessor that adds thread binding to unbound blocks"""
|
||||
|
||||
def __init__(self, max_threadblocks: int = 256) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.PostprocRewriteUnboundBlock, # type: ignore # pylint: disable=no-member
|
||||
max_threadblocks,
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
# 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.
|
||||
"""A postprocessor that verifies if the GPU code is correct"""
|
||||
|
||||
from tvm_ffi.registry import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .postproc import Postproc
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.VerifyGPUCode")
|
||||
class VerifyGPUCode(Postproc):
|
||||
"""A postprocessor that verifies if the GPU code is correct"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.PostprocVerifyGPUCode, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
# 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.
|
||||
"""A postprocessor that verifies the VTCM usage of a given schedule."""
|
||||
|
||||
from tvm_ffi.registry import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .postproc import Postproc
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.VerifyVTCMLimit")
|
||||
class VerifyVTCMLimit(Postproc):
|
||||
"""Verifies that the VTCM usage of a given schedule is within the provided limit."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.PostprocVerifyVTCMLimit, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
@@ -0,0 +1,74 @@
|
||||
# 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.
|
||||
# pylint: disable=used-before-assignment
|
||||
"""A context manager that profiles tuning time cost for different parts."""
|
||||
|
||||
from contextlib import contextmanager
|
||||
from typing import Optional
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from tvm.runtime import Object
|
||||
|
||||
from . import _ffi_api
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.Profiler")
|
||||
class Profiler(Object):
|
||||
"""Tuning time profiler."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.Profiler, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
|
||||
def get(self) -> dict[str, float]:
|
||||
"""Get the profiling results in seconds"""
|
||||
return _ffi_api.ProfilerGet(self) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def table(self) -> str:
|
||||
"""Get the profiling results in a table format"""
|
||||
return _ffi_api.ProfilerTable(self) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def __enter__(self) -> "Profiler":
|
||||
"""Entering the scope of the context manager"""
|
||||
_ffi_api.ProfilerEnterWithScope(self) # type: ignore # pylint: disable=no-member
|
||||
return self
|
||||
|
||||
def __exit__(self, ptype, value, trace) -> None:
|
||||
"""Exiting the scope of the context manager"""
|
||||
_ffi_api.ProfilerExitWithScope(self) # type: ignore # pylint: disable=no-member
|
||||
|
||||
@staticmethod
|
||||
def current() -> Optional["Profiler"]:
|
||||
"""Get the current profiler."""
|
||||
return _ffi_api.ProfilerCurrent() # type: ignore # pylint: disable=no-member
|
||||
|
||||
@staticmethod
|
||||
def timeit(name: str):
|
||||
"""Timeit a block of code"""
|
||||
|
||||
@contextmanager
|
||||
def _timeit():
|
||||
try:
|
||||
f = _ffi_api.ProfilerTimedScope(name) # type: ignore # pylint: disable=no-member
|
||||
yield
|
||||
finally:
|
||||
if f:
|
||||
f()
|
||||
|
||||
return _timeit()
|
||||
@@ -0,0 +1,494 @@
|
||||
# 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.
|
||||
"""Meta schedule integration with high-level IR"""
|
||||
|
||||
import warnings
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
# isort: off
|
||||
from typing import Literal
|
||||
|
||||
# isort: on
|
||||
|
||||
from tvm_ffi import get_global_func, register_global_func
|
||||
|
||||
from tvm.ir import IRModule
|
||||
from tvm.ir.transform import PassContext
|
||||
from tvm.runtime import Tensor
|
||||
from tvm.target import Target
|
||||
from tvm.tirx.expr import IntImm
|
||||
|
||||
from .builder import Builder
|
||||
from .cost_model import CostModel
|
||||
from .database import Database
|
||||
from .extracted_task import ExtractedTask
|
||||
from .logging import get_loggers_from_work_dir
|
||||
from .measure_callback import MeasureCallback
|
||||
from .runner import Runner
|
||||
from .search_strategy import SearchStrategy
|
||||
from .space_generator import SpaceGenerator
|
||||
from .task_scheduler import TaskScheduler
|
||||
from .tune import tune_tasks
|
||||
from .tune_context import TuneContext
|
||||
from .utils import fork_seed
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from tvm import relax
|
||||
|
||||
_extract_task_func = get_global_func( # pylint: disable=invalid-name
|
||||
"relax.backend.MetaScheduleExtractTask",
|
||||
allow_missing=True,
|
||||
)
|
||||
|
||||
|
||||
def extract_tasks(
|
||||
mod: Union[IRModule, "relax.Function"],
|
||||
target: Target,
|
||||
params: dict[str, Tensor] | None = None,
|
||||
module_equality: str = "structural",
|
||||
) -> list[ExtractedTask]:
|
||||
"""Extract tuning tasks from a relax program.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : Union[IRModule, relax.Function]
|
||||
The module or function to tune
|
||||
target : tvm.target.Target
|
||||
The compilation target
|
||||
params : Optional[Dict[str, tvm.runtime.Tensor]]
|
||||
The associated parameters of the program
|
||||
module_equality : Optional[str]
|
||||
A string to specify the module equality testing and hashing method.
|
||||
It must be one of the followings:
|
||||
|
||||
- "structural": Use StructuralEqual/Hash
|
||||
- "ignore-tensor": Same as "structural", but ignore tensor raw data during
|
||||
equality testing and hashing.
|
||||
- "anchor-block": Apply equality testing and hashing on the anchor block extracted from a
|
||||
given module. The "ignore-tensor" varint is used for the extracted
|
||||
blocks or in case no anchor block is found.
|
||||
For the definition of the anchor block, see tirx/analysis/analysis.py.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tasks: List[ExtractedTask]
|
||||
The tasks extracted from this module
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from tvm.relax.expr import Function as RelaxFunc
|
||||
from tvm.relax.transform import BindParams
|
||||
|
||||
# pylint: enable=import-outside-toplevel
|
||||
if isinstance(mod, RelaxFunc):
|
||||
mod = IRModule({"main": mod})
|
||||
if not isinstance(target, Target):
|
||||
target = Target(target)
|
||||
if params:
|
||||
mod = BindParams("main", params)(mod)
|
||||
return list(_extract_task_func(mod, target, module_equality))
|
||||
|
||||
|
||||
def extracted_tasks_to_tune_contexts(
|
||||
extracted_tasks: list[ExtractedTask],
|
||||
work_dir: str,
|
||||
space: SpaceGenerator.SpaceGeneratorType = "post-order-apply",
|
||||
strategy: SearchStrategy.SearchStrategyType = "evolutionary",
|
||||
num_threads: Literal["physical", "logical"] | int = "physical",
|
||||
seed: int | None = None,
|
||||
) -> tuple[list[TuneContext], list[float]]:
|
||||
"""Convert ExtractedTask to TuneContext.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tasks : List[ExtractedTask]
|
||||
The tasks to be converted
|
||||
work_dir : str
|
||||
The working directory to store logs and databases
|
||||
space : SpaceGenerator.SpaceGeneratorType
|
||||
The space generator to use.
|
||||
strategy : SearchStrategy.SearchStrategyType
|
||||
The search strategy to use.
|
||||
num_threads : Union[Literal["physical", "logical"], int]
|
||||
The number of threads to use in multi-threaded search algorithm.
|
||||
seed : Optional[int]
|
||||
The random seed to use.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tasks : List[TuneContext]
|
||||
The converted tasks
|
||||
task_weights : List[float]
|
||||
The weights of the tasks
|
||||
"""
|
||||
tasks: list[TuneContext] = []
|
||||
task_weights: list[float] = []
|
||||
for task, logger, rand_state in zip(
|
||||
extracted_tasks,
|
||||
get_loggers_from_work_dir(work_dir, [t.task_name for t in extracted_tasks]),
|
||||
fork_seed(seed, n=len(extracted_tasks)),
|
||||
):
|
||||
if task.mod.attrs.get("tirx.is_scheduled", False):
|
||||
warnings.warn("The task {task.task_name} is already scheduled, skipping it.")
|
||||
continue
|
||||
tasks.append(
|
||||
TuneContext(
|
||||
mod=task.dispatched[0],
|
||||
target=task.target,
|
||||
space_generator=space,
|
||||
search_strategy=strategy,
|
||||
task_name=task.task_name,
|
||||
logger=logger,
|
||||
rand_state=rand_state,
|
||||
num_threads=num_threads,
|
||||
).clone()
|
||||
)
|
||||
task_weights.append(task.weight)
|
||||
return tasks, task_weights
|
||||
|
||||
|
||||
def tune_relax(
|
||||
mod: Union[IRModule, "relax.Function"],
|
||||
params: dict[str, Tensor],
|
||||
target: str | Target,
|
||||
work_dir: str,
|
||||
max_trials_global: int,
|
||||
max_trials_per_task: int | None = None,
|
||||
op_names: list[str] | None = None,
|
||||
*,
|
||||
num_trials_per_iter: int = 64,
|
||||
builder: Builder.BuilderType = "local",
|
||||
runner: Runner.RunnerType = "local",
|
||||
database: Database.DatabaseType = "json",
|
||||
cost_model: CostModel.CostModelType = "xgb",
|
||||
measure_callbacks: MeasureCallback.CallbackListType = "default",
|
||||
task_scheduler: TaskScheduler.TaskSchedulerType = "gradient",
|
||||
space: SpaceGenerator.SpaceGeneratorType = "post-order-apply",
|
||||
strategy: SearchStrategy.SearchStrategyType = "evolutionary",
|
||||
seed: int | None = None,
|
||||
module_equality: str = "structural",
|
||||
) -> Database:
|
||||
"""Tune a Relax program.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : Union[IRModule, relax.Function]
|
||||
The module or function to tune
|
||||
params : Optional[Dict[str, tvm.runtime.Tensor]]
|
||||
The associated parameters of the program
|
||||
target : Union[Target, str]
|
||||
The compilation target
|
||||
work_dir : str
|
||||
The working directory to store the tuning records
|
||||
max_trials_global : int
|
||||
The maximum number of trials to run
|
||||
max_trials_per_task : Optional[int]
|
||||
The maximum number of trials to run for each task
|
||||
op_names: Optional[List[str]]
|
||||
A list of operator names to specify which op to tune. When it is None, all operators
|
||||
are tuned.
|
||||
num_trials_per_iter : int
|
||||
The number of trials to run per iteration
|
||||
builder : BuilderType
|
||||
The builder to use
|
||||
runner : RunnerType
|
||||
The runner to use
|
||||
database : DatabaseType
|
||||
The database to use
|
||||
cost_model : CostModelType
|
||||
The cost model to use
|
||||
measure_callbacks : CallbackListType
|
||||
The measure callbacks to use
|
||||
task_scheduler : TaskSchedulerType
|
||||
The task scheduler to use
|
||||
space : SpaceGeneratorType
|
||||
The space generator to use
|
||||
strategy : SearchStrategyType
|
||||
The search strategy to use
|
||||
seed : Optional[int]
|
||||
The random seed
|
||||
module_equality : Optional[str]
|
||||
A string to specify the module equality testing and hashing method.
|
||||
It must be one of the followings:
|
||||
|
||||
- "structural": Use StructuralEqual/Hash
|
||||
- "ignore-tensor": Same as "structural", but ignore tensor raw data during
|
||||
equality testing and hashing.
|
||||
- "anchor-block": Apply equality testing and hashing on the anchor block extracted from a
|
||||
given module. The "ignore-tensor" variant is used for the extracted
|
||||
blocks or in case no anchor block is found.
|
||||
For the definition of the anchor block, see tirx/analysis/analysis.py.
|
||||
|
||||
Returns
|
||||
-------
|
||||
database : Database
|
||||
The database that contains the tuning records
|
||||
"""
|
||||
all_tasks = extract_tasks(mod, target, params, module_equality=module_equality)
|
||||
|
||||
if not op_names:
|
||||
selected_tasks = all_tasks
|
||||
else:
|
||||
selected_tasks = []
|
||||
|
||||
for task in all_tasks:
|
||||
for op_name in op_names:
|
||||
if op_name in task.task_name:
|
||||
selected_tasks.append(task)
|
||||
|
||||
tasks, task_weights = extracted_tasks_to_tune_contexts(
|
||||
extracted_tasks=selected_tasks,
|
||||
work_dir=work_dir,
|
||||
space=space,
|
||||
strategy=strategy,
|
||||
seed=seed,
|
||||
)
|
||||
return tune_tasks(
|
||||
tasks=tasks,
|
||||
task_weights=task_weights,
|
||||
work_dir=work_dir,
|
||||
max_trials_global=max_trials_global,
|
||||
max_trials_per_task=max_trials_per_task,
|
||||
num_trials_per_iter=num_trials_per_iter,
|
||||
builder=builder,
|
||||
runner=runner,
|
||||
database=database,
|
||||
cost_model=cost_model,
|
||||
measure_callbacks=measure_callbacks,
|
||||
task_scheduler=task_scheduler,
|
||||
module_equality=module_equality,
|
||||
)
|
||||
|
||||
|
||||
@register_global_func("tvm.s_tir.meta_schedule.tune_relax")
|
||||
def _tune_relax(
|
||||
mod: Union[IRModule, "relax.Function"],
|
||||
params: dict[str, Tensor],
|
||||
target: str | Target,
|
||||
work_dir: str,
|
||||
max_trials_global: int,
|
||||
max_trials_per_task: int | None = None,
|
||||
op_names: list[str] | None = None,
|
||||
*,
|
||||
num_trials_per_iter: int = 64,
|
||||
builder: Builder.BuilderType = "local",
|
||||
runner: Runner.RunnerType = "local",
|
||||
database: Database.DatabaseType = "json",
|
||||
cost_model: CostModel.CostModelType = "xgb",
|
||||
measure_callbacks: MeasureCallback.CallbackListType = "default",
|
||||
task_scheduler: TaskScheduler.TaskSchedulerType = "gradient",
|
||||
space: SpaceGenerator.SpaceGeneratorType = "post-order-apply",
|
||||
strategy: SearchStrategy.SearchStrategyType = "evolutionary",
|
||||
seed: int | None = None,
|
||||
module_equality: str = "structural",
|
||||
) -> Database:
|
||||
"""Interface with tuning api to tune a Relax program.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : Union[IRModule, relax.Function]
|
||||
The module or function to tune
|
||||
params : Optional[Dict[str, tvm.runtime.Tensor]]
|
||||
The associated parameters of the program
|
||||
target : Union[Target, str]
|
||||
The compilation target
|
||||
work_dir : str
|
||||
The working directory to store the tuning records
|
||||
max_trials_global : int
|
||||
The maximum number of trials to run
|
||||
max_trials_per_task : Optional[int]
|
||||
The maximum number of trials to run for each task
|
||||
op_names: Optional[List[str]]
|
||||
A list of operator names to specify which op to tune. When it is None, all operators
|
||||
are tuned.
|
||||
num_trials_per_iter : int
|
||||
The number of trials to run per iteration
|
||||
builder : BuilderType
|
||||
The builder to use
|
||||
runner : RunnerType
|
||||
The runner to use
|
||||
database : DatabaseType
|
||||
The database to use
|
||||
cost_model : CostModelType
|
||||
The cost model to use
|
||||
measure_callbacks : CallbackListType
|
||||
The measure callbacks to use
|
||||
task_scheduler : TaskSchedulerType
|
||||
The task scheduler to use
|
||||
space : SpaceGeneratorType
|
||||
The space generator to use
|
||||
strategy : SearchStrategyType
|
||||
The search strategy to use
|
||||
seed : Optional[int]
|
||||
The random seed
|
||||
module_equality : Optional[str]
|
||||
A string to specify the module equality testing and hashing method.
|
||||
It must be one of the followings:
|
||||
|
||||
- "structural": Use StructuralEqual/Hash
|
||||
- "ignore-tensor": Same as "structural", but ignore tensor raw data during
|
||||
equality testing and hashing.
|
||||
- "anchor-block": Apply equality testing and hashing on the anchor block extracted from a
|
||||
given module. The "ignore-tensor" varint is used for the extracted
|
||||
blocks or in case no anchor block is found.
|
||||
For the definition of the anchor block, see tirx/analysis/analysis.py.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ret_mod : IRModule
|
||||
IRModule
|
||||
"""
|
||||
if isinstance(max_trials_global, IntImm):
|
||||
max_trials_global = int(max_trials_global)
|
||||
if isinstance(max_trials_per_task, IntImm):
|
||||
max_trials_per_task = int(max_trials_per_task)
|
||||
|
||||
tune_relax(
|
||||
mod,
|
||||
params,
|
||||
target,
|
||||
work_dir,
|
||||
max_trials_global,
|
||||
max_trials_per_task=max_trials_per_task,
|
||||
num_trials_per_iter=num_trials_per_iter,
|
||||
op_names=op_names,
|
||||
builder=builder,
|
||||
runner=runner,
|
||||
database=database,
|
||||
cost_model=cost_model,
|
||||
measure_callbacks=measure_callbacks,
|
||||
task_scheduler=task_scheduler,
|
||||
space=space,
|
||||
strategy=strategy,
|
||||
seed=seed,
|
||||
module_equality=module_equality,
|
||||
)
|
||||
# Return original IRModule
|
||||
# This pass only makes optimization decision
|
||||
return mod
|
||||
|
||||
|
||||
def compile_relax(
|
||||
database: Database,
|
||||
mod: IRModule,
|
||||
target: Target | str,
|
||||
params: dict[str, Tensor] | None,
|
||||
enable_warning: bool = False,
|
||||
) -> "relax.VMExecutable":
|
||||
"""Compile a relax program with a MetaSchedule database.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
database : Database
|
||||
The database to use
|
||||
mod : IRModule
|
||||
The Relax program to be compiled
|
||||
target : tvm.target.Target
|
||||
The compilation target
|
||||
params : Optional[Dict[str, tvm.runtime.Tensor]]
|
||||
The associated parameters of the program
|
||||
enable_warning : bool
|
||||
A boolean value indicating if to print warnings for TIR functions not
|
||||
showing up in the database. By default we don't print warning.
|
||||
|
||||
Returns
|
||||
-------
|
||||
lib : relax.VMExecutable
|
||||
The built runtime module or vm VMExecutable for the given relax workload.
|
||||
"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import tvm
|
||||
from tvm import relax
|
||||
from tvm.relax import build as relax_build
|
||||
from tvm.relax import pipeline as relax_pipeline_mod
|
||||
from tvm.relax.transform import BindParams, MetaScheduleApplyDatabase
|
||||
from tvm.s_tir import dlight as dl
|
||||
|
||||
# pylint: enable=import-outside-toplevel
|
||||
if not isinstance(target, Target):
|
||||
target = Target(target)
|
||||
if params:
|
||||
mod = BindParams("main", params)(mod)
|
||||
|
||||
# Build a pipeline with the correct ordering:
|
||||
# 1. library_dispatch + LegalizeOps + FuseOps + FuseTIR
|
||||
# (same preparation as extract_tasks, so database keys match)
|
||||
# 2. MetaScheduleApplyDatabase — replaces tuned fused-TIR functions
|
||||
# 3. DLight fallback — schedules remaining untuned functions
|
||||
# 4. dataflow_lower + finalize passes
|
||||
#
|
||||
# Applying MetaScheduleApplyDatabase BEFORE FuseOps (the original bug)
|
||||
# caused DLight.Matmul to fail on cache-write stages embedded in fused TIR.
|
||||
#
|
||||
# All pass lists are obtained from relax.pipeline.*_passes(target) so that
|
||||
# target-specific helpers (dispatch, finalize) are shared with the default
|
||||
# pipeline rather than duplicated here.
|
||||
try:
|
||||
dispatch_passes = relax_pipeline_mod.library_dispatch_passes(target)
|
||||
except (ValueError, AttributeError):
|
||||
dispatch_passes = []
|
||||
|
||||
try:
|
||||
lower_passes = relax_pipeline_mod.dataflow_lower_passes(target)
|
||||
finalize_passes = relax_pipeline_mod.finalize_passes(target)
|
||||
except (ValueError, AttributeError):
|
||||
# Fallback for targets not yet registered in the pipeline dispatcher
|
||||
lower_passes = [
|
||||
relax.transform.RewriteDataflowReshape(),
|
||||
relax.transform.ToNonDataflow(),
|
||||
relax.transform.RemovePurityChecking(),
|
||||
relax.transform.CallTIRRewrite(),
|
||||
]
|
||||
finalize_passes = [
|
||||
relax.transform.StaticPlanBlockMemory(),
|
||||
relax.transform.LowerAllocTensor(),
|
||||
relax.transform.KillAfterLastUse(),
|
||||
relax.transform.LowerRuntimeBuiltin(),
|
||||
relax.transform.ComputePrimValue(),
|
||||
relax.transform.VMShapeLower(),
|
||||
relax.transform.AttachGlobalSymbol(),
|
||||
]
|
||||
|
||||
is_gpu_target = relax_pipeline_mod.BackendDispatcher.is_gpu_target(target)
|
||||
|
||||
@tvm.transform.module_pass(opt_level=3)
|
||||
def _ms_pipeline(mod: tvm.ir.IRModule, _ctx: tvm.transform.PassContext) -> tvm.ir.IRModule:
|
||||
fuse_seq = [
|
||||
*dispatch_passes,
|
||||
relax.transform.LegalizeOps(enable_warning=enable_warning),
|
||||
relax.transform.AnnotateTIROpPattern(),
|
||||
relax.transform.FoldConstant(),
|
||||
relax.transform.FuseOps(),
|
||||
relax.transform.FuseTIR(),
|
||||
]
|
||||
mod = tvm.transform.Sequential(fuse_seq)(mod)
|
||||
mod = MetaScheduleApplyDatabase(enable_warning=enable_warning)(mod)
|
||||
# DLight handles functions not covered by the database.
|
||||
# GPU rules apply only for GPU targets.
|
||||
if is_gpu_target:
|
||||
mod = dl.ApplyDefaultSchedule(
|
||||
dl.gpu.Matmul(),
|
||||
dl.gpu.GEMV(),
|
||||
dl.gpu.Reduction(),
|
||||
dl.gpu.GeneralReduction(),
|
||||
dl.gpu.Fallback(),
|
||||
)(mod)
|
||||
mod = tvm.transform.Sequential(lower_passes + finalize_passes)(mod)
|
||||
return mod
|
||||
|
||||
with target, database, PassContext(opt_level=3):
|
||||
relax_ex = relax_build(mod, target=target, relax_pipeline=_ms_pipeline)
|
||||
return relax_ex
|
||||
@@ -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
|
||||
@@ -0,0 +1,20 @@
|
||||
# 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.
|
||||
"""Per-block schedule rules in MetaSchedule"""
|
||||
|
||||
from . import cpu, cuda, generic, x86
|
||||
@@ -0,0 +1,17 @@
|
||||
# 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.
|
||||
"""Per-block schedule rules in MetaSchedule for target key 'cpu'"""
|
||||
@@ -0,0 +1,19 @@
|
||||
# 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.
|
||||
"""Per-block schedule rules in MetaSchedule for target key 'cuda'"""
|
||||
|
||||
from . import layout_transform
|
||||
@@ -0,0 +1,581 @@
|
||||
# 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: RUF005
|
||||
"""layout_transform scheduling rule for CUDA."""
|
||||
|
||||
import math
|
||||
from collections import deque
|
||||
|
||||
import tvm
|
||||
from tvm.s_tir import meta_schedule
|
||||
from tvm.s_tir.schedule import ExprRV, LoopRV, SBlockRV, Schedule
|
||||
|
||||
## Tiling layout transforms:
|
||||
# Assume we have an input shape of [A, B, C, D] and want to layout transform
|
||||
# ABCD --> DBAC so the output shape would be [D, B, A, C].
|
||||
#
|
||||
# Consider reading from the input buffer in a cache-friendly fashion on CPU. We would
|
||||
# expect a loop structure like:
|
||||
# lAr, lBr, lCr, lDr = T.grid(A, B, C, D)
|
||||
#
|
||||
# Meanwhile consider writing to the output buffer in a cache-friendly fashion on CPU:
|
||||
# lDw, lBw, lAw, lCw = T.grid(D, B, A, C)
|
||||
#
|
||||
# Clearly in many scenarios it is impossible to guarantee contiguous writes and reads
|
||||
# within a single loop due to non-adjacent dimensions. Instead we work on transposing some
|
||||
# small sub-tensor of our input writing and then reading from shared memory. We must now
|
||||
# construct our submatrix so that reading and writing can both be done with some contiguous
|
||||
# access in global memory.
|
||||
#
|
||||
# Consider the case of a 2D transpose. For example [1024, 2048] -> [2048, 1024].
|
||||
# We note that if we deal with a submatrix of shape [32, 32] which corresponds
|
||||
# to the dimension of our input tensor, then rows of the submatrix are contiguous
|
||||
# in the input tensor. Meanwhile, columns of our submatrix are contiguous in our
|
||||
# output vector. Therefore, with this tile shape we have opportunity to read
|
||||
# contiguously in our input tensor and write to shared memory, and write contiguously
|
||||
# to our output tensor.
|
||||
#
|
||||
# The multiple dimensional case has a similar analogue. We want to allocate shared
|
||||
# memory per block of [`tile_size`, `tile_size`]. We want the inner most dimension
|
||||
# of our shared memory to correspond to contiguous reads from the input tensor and
|
||||
# the outer dimension to correspond to contiguous writes into the output tensor.
|
||||
#
|
||||
# In terms of the loop structure reading from the input tensor, the inner most loops
|
||||
# of our tile must correspond to the inner most dimensions of the input shape,
|
||||
# while the outer dimensions correspond to the inner most dimensions of the output shape.
|
||||
# To obtain an inner tile with this loop structure we factor out a contiguous `tile_size`
|
||||
# chunk of our loop in the shape of interest.
|
||||
#
|
||||
# An example is probably best to show this idea:
|
||||
# Let's say we want a layout transform of ABCD --> DCAB. With shape
|
||||
# [1024_a, 2_b, 32_c, 8_d] --> [8_d, 32_c, 1024_a, 2_b]
|
||||
#
|
||||
# And tile size 32.
|
||||
#
|
||||
# Then we initially have a coalesced-read loop pattern of:
|
||||
# T.grid(1024_a, 2_b, 32_c, 8_d)
|
||||
#
|
||||
# To obtain an inner tile of 32, we factor 4 from 32_c and 8 from 8_d:
|
||||
# T.grid(1024_a, 2_b, 8_c1, 1_d1, 4_c2t, 8_d2t)
|
||||
# T.grid(1024_a, 2_b, 8_cr, 1_dr, 32_dim1)
|
||||
#
|
||||
# To obtain an outer tile of 32, we factor from B then A to follow contiguous write
|
||||
# pattern:
|
||||
#
|
||||
# T.grid(64_a1, 1_b1, 8_cr, 1_dr, 16_a2t, 2_b2t, 32_dim1)
|
||||
# T.grid(64_ar, 1_br, 8_cr, 1_dr, 32_dim0, 32_dim1)
|
||||
#
|
||||
# Which allows us to read a tile with our wanted properties.
|
||||
# For writing we use the existing analysis infrastructure to generate the structure for writing.
|
||||
|
||||
|
||||
def tile_layout_transform(
|
||||
sch: Schedule,
|
||||
block_read: SBlockRV,
|
||||
block_write: SBlockRV,
|
||||
src_layout: str,
|
||||
dst_layout: str,
|
||||
input_shape: list[int],
|
||||
tile_size: ExprRV,
|
||||
) -> tuple[SBlockRV, SBlockRV]:
|
||||
"""
|
||||
High level tiling for layout transform block. Mutates sch in place.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sch:
|
||||
The initial schedule. We expect `block_read` and `block_write` to correspond to
|
||||
the blocks which reads and writes from global memory respectively. We also expect
|
||||
block_read's initial loops to follow
|
||||
|
||||
block_read:
|
||||
The block which reads from global memory and writes to shared memory buffer.
|
||||
|
||||
block_write:
|
||||
The block which writes to global memory and reads from shared memory buffer.
|
||||
|
||||
src_layout :
|
||||
The src_layout, each character should appear once and also appear in dst_layout.
|
||||
There should be not numeric characters and refer to potentially implicit reshapes.
|
||||
E.g. the transform NCHW --> NCHW4c really implies NCcHW --> NCHWc. In this case
|
||||
src_layout should be NCcHW.
|
||||
|
||||
dst_layout:
|
||||
The dst_layout. There should not be numeric characters, e.g. NCHW4c becomes NCHWc.
|
||||
|
||||
input_shape:
|
||||
The input shape after applying potentially implicit reshapes. Should match the loop
|
||||
extants corresponding to src_layout.
|
||||
|
||||
tile_size:
|
||||
The tile size of read and writes. There will be tile_size threads per block, each of which
|
||||
reads up to tile_size elements.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ret:
|
||||
A tuple of the block that writes to global memory, and the block that reads from
|
||||
global memory.
|
||||
"""
|
||||
|
||||
def pad_dimension_to_at_least_number(loop: LoopRV, requested_size: int):
|
||||
"""E.g. if loop has extant of 8 but we want 10, returns size 10 loop with padding."""
|
||||
left, right = sch.split(loop, [None, requested_size])
|
||||
return sch.fuse(left, right)
|
||||
|
||||
def pad_dimension_to_factor_of_tile_size(
|
||||
loop: LoopRV, initial_size: int, tile_size: int = tile_size
|
||||
) -> tuple[LoopRV, int]:
|
||||
"""
|
||||
Pads loop of given size until it is divisible into tile_size.
|
||||
If the given size of the loop is greater than tile size. Do not pad.
|
||||
|
||||
examples:
|
||||
- loop_size = 5 , tile_size = 32. loop_size --> 8
|
||||
- loop_size = 5 , tile_size = 36. loop_size --> 6
|
||||
- loop_size = 8 , tile_size = 32. loop_size --> 8 : since 8 already divides 32.
|
||||
- loop_size = 33, tile_size = 32. loop_size --> 33 : since 33 > 32.
|
||||
|
||||
Returns padded loopRV and the new size.
|
||||
"""
|
||||
if tile_size % initial_size == 0:
|
||||
return loop, int(initial_size)
|
||||
|
||||
if initial_size > tile_size or initial_size == tile_size:
|
||||
return loop, int(initial_size)
|
||||
|
||||
# if initial_size > tile_size return without change, factor = 1
|
||||
size = initial_size
|
||||
while (tile_size % size) % tile_size > 0:
|
||||
size += 1
|
||||
|
||||
return pad_dimension_to_at_least_number(loop, size), int(size)
|
||||
|
||||
def spin_out_factor(
|
||||
loops: list[LoopRV], loop_extants: list[int], index: int, factor_needed: int
|
||||
) -> tuple[list[LoopRV], list[int], int]:
|
||||
"""
|
||||
Factor out the requested loop's dimensions to reach the requested factor and
|
||||
places the requested factor as the innermost loop.
|
||||
|
||||
Updates the schedule in-place.
|
||||
|
||||
E.g. say we want to factors which eventually multiply to 32 (factor_needed).
|
||||
|
||||
Say we have the index we chose is a loop with an extant of 8.
|
||||
E.g. loops / loop_extants = [3, 32, 6, 8], factor_needed = 32, index=3 (dim=8)
|
||||
- 8 divides into 32 so we just split up the loop into two loops with extants 1 and 8.
|
||||
- we then keep the 1-loop in place and move the new 8-loop to back of the list of loops
|
||||
- ending loops / loop_extants = [3, 32, 6, 1, 8], remaining_factor_needed = 32 / 8 = 4
|
||||
|
||||
E.g. loops / loop_extants = [3, 32, 6, 8], factor_needed=32, index=0 (dim=3)
|
||||
- 3 does not divide 32, so we pad until the extant divides 32, e.g. 4
|
||||
- we then split up the loop into extants 1 and 4, moving the 4 to the back
|
||||
- ending loops / loop_extants = [1, 32, 6, 8, 4], remaining_factor_needed = 32 / 4 = 8
|
||||
|
||||
E.g. loops / loop_extants = [3, 32, 6, 8], factor_needed=5, index=3 (dim=8)
|
||||
- 8 is larger than 5 so we immediately do the splitting routine.
|
||||
- the 8 extant loop becomes loops with extants 2 and 5
|
||||
- ending loops / loop_extants = [1, 32, 6, 2, 5], remaining_factor_needed = 5 / 5 = 1
|
||||
|
||||
After updating loop ordering in place, returns the new list of loops, extants, and the
|
||||
remaining factor needed.
|
||||
"""
|
||||
cur_loop = loops[index]
|
||||
cur_extant = loop_extants[index]
|
||||
|
||||
# Pad loops to divide evenly for factors needed, and split
|
||||
new_loop, new_size = pad_dimension_to_factor_of_tile_size(
|
||||
cur_loop, cur_extant, tile_size=factor_needed
|
||||
)
|
||||
|
||||
split_factor = min(new_size, factor_needed)
|
||||
new_loop_split, factored_loop = sch.split(new_loop, [None, split_factor])
|
||||
factor_needed = factor_needed // split_factor
|
||||
|
||||
# update caching
|
||||
loops[index] = new_loop_split
|
||||
loops.append(factored_loop)
|
||||
|
||||
loop_extants[index] = math.ceil(int(new_size) / int(split_factor))
|
||||
loop_extants.append(split_factor)
|
||||
|
||||
sch.reorder(*loops)
|
||||
return loops, loop_extants, factor_needed
|
||||
|
||||
def factor_dim_in_order(
|
||||
indices: list[int],
|
||||
loops: list[LoopRV],
|
||||
cur_loop_extants: list[int],
|
||||
work_needed_inner_loop: int = tile_size,
|
||||
) -> tuple[list[LoopRV], list[int]]:
|
||||
"""Factors out the loops in the order of indices until we reach needed work.
|
||||
|
||||
Adds new loop factors to the back in reverse order of access. Returns new list
|
||||
of loops and their extants.
|
||||
"""
|
||||
for i in indices:
|
||||
loops, cur_loop_extants, work_needed_inner_loop = spin_out_factor(
|
||||
loops, cur_loop_extants, i, work_needed_inner_loop
|
||||
)
|
||||
if work_needed_inner_loop == 1:
|
||||
break
|
||||
return loops, cur_loop_extants
|
||||
|
||||
def get_high_level_loop_structure(
|
||||
block_read: SBlockRV, input_shape: list[int], src_layout: str, dst_layout: str
|
||||
):
|
||||
"""Runs the factorization described above."""
|
||||
# index 0 ... rank - 1 will always correspond to original loops
|
||||
# perhaps after they have been factored.
|
||||
rank = len(input_shape)
|
||||
loops = sch.get_loops(block_read)
|
||||
cur_loop_extants = list(input_shape)
|
||||
|
||||
# Factor dim0 tile size and fuse things together
|
||||
loops, cur_loop_extants = factor_dim_in_order(
|
||||
list(range(rank - 1, -1, -1)),
|
||||
loops,
|
||||
cur_loop_extants,
|
||||
work_needed_inner_loop=tile_size,
|
||||
)
|
||||
# The factors which multiply to tile_size are now in back of our
|
||||
# list of loops. However because we added them by traversing the inner
|
||||
# dimensions, they are actually reversed order to guarantee the best access
|
||||
# so reorder before fusing.
|
||||
loops = loops[:rank] + loops[rank:][::-1]
|
||||
cur_loop_extants = cur_loop_extants[:rank] + cur_loop_extants[rank::-1]
|
||||
sch.reorder(*loops)
|
||||
dim0_loop_tiled = sch.fuse(*loops[rank:])
|
||||
loops = loops[:rank]
|
||||
loops.append(dim0_loop_tiled)
|
||||
cur_loop_extants = cur_loop_extants[:rank]
|
||||
cur_loop_extants.append(tile_size)
|
||||
|
||||
# Same thing with dim1
|
||||
# [:rank + 1], since we placed dim0_loop_tiled in the end which we want to keep
|
||||
loops, cur_loop_extants = factor_dim_in_order(
|
||||
list(
|
||||
src_layout.index(dst_layout[loop_index_dst])
|
||||
for loop_index_dst in range(rank - 1, -1, -1)
|
||||
),
|
||||
loops,
|
||||
cur_loop_extants,
|
||||
work_needed_inner_loop=tile_size,
|
||||
)
|
||||
loops = loops[: rank + 1] + loops[rank + 1 :][::-1]
|
||||
cur_loop_extants = cur_loop_extants[: rank + 1] + cur_loop_extants[rank + 1 :: -1]
|
||||
sch.reorder(*loops)
|
||||
dim1_loop_tiled = sch.fuse(*loops[rank + 1 :])
|
||||
loops = loops[: rank + 1]
|
||||
loops.append(dim1_loop_tiled)
|
||||
cur_loop_extants = cur_loop_extants[: rank + 1]
|
||||
cur_loop_extants.append(tile_size)
|
||||
|
||||
# After this we have loops: [loop1, loop2, loop3 ... dim0_tiled, dim1_tiled]
|
||||
get_high_level_loop_structure(block_read, input_shape, src_layout, dst_layout)
|
||||
|
||||
# If there are insufficient elements, than dim1_tiled or dim0_tiled might be too small
|
||||
# In all likelihood you should use a smaller tile, but I don't want things to crash.
|
||||
loops = sch.get_loops(block_read)
|
||||
loops[-1] = pad_dimension_to_at_least_number(loops[-1], tile_size)
|
||||
loops[-2] = pad_dimension_to_at_least_number(loops[-2], tile_size)
|
||||
|
||||
# We want the dim0 and dim1 parent loops to be the inner most. Right now dim1 is inner-msot
|
||||
# and we just need to move dim0 in (last dimension of dst).
|
||||
# Recall right now structure is at least [l1 l2 ... ln, dim0_tiled, dim1_tiled]
|
||||
# where n >= 2.
|
||||
dim0_loop_index = src_layout.index(dst_layout[-1])
|
||||
dim0_loop = loops.pop(dim0_loop_index)
|
||||
loops = loops[:-3] + [dim0_loop, loops[-3]] + loops[-2:]
|
||||
sch.reorder(*loops)
|
||||
|
||||
# After this loops are: [outer_loop (block binding), dim0_tiled, dim1_tiled]
|
||||
outer_loop = sch.fuse(*loops[:-2])
|
||||
|
||||
# Now that we have the high level loop structure, we can use reverse_compute_at magic
|
||||
# To get the proper loop structure for writing! This is also as coalesced as possible
|
||||
# already.
|
||||
sch.reverse_compute_at(block_write, outer_loop)
|
||||
|
||||
# Fuse all inner loops for the write into 2 loops, grab inner loops for both read
|
||||
# and write block which have locality (we will bind these to threadIdx)
|
||||
fused_write_loop = sch.fuse(*sch.get_loops(block_write)[1:])
|
||||
_, inner_write_loop = sch.split(fused_write_loop, [None, tile_size])
|
||||
inner_read_loop = sch.get_loops(block_read)[-2]
|
||||
|
||||
sch.bind(loop=outer_loop, thread_axis="blockIdx.x")
|
||||
sch.bind(loop=inner_write_loop, thread_axis="threadIdx.x")
|
||||
sch.bind(loop=inner_read_loop, thread_axis="threadIdx.x")
|
||||
|
||||
return block_write, block_read
|
||||
|
||||
|
||||
def create_cached_read(
|
||||
sch: Schedule,
|
||||
block_write: SBlockRV,
|
||||
orig_input_shape: list[int],
|
||||
orig_src_layout: str,
|
||||
orig_dst_layout: str,
|
||||
) -> tuple[SBlockRV, list[int], str, str]:
|
||||
"""
|
||||
Creates the cached read block with expected structure.
|
||||
|
||||
Loop extants should follow the input shape closely. E.g. if the input is [2, 6, 8], we
|
||||
expect our loop structure to be T.grid(2, 6, 8). Possibly reshape to handle implicit reshapes,
|
||||
in which case we will match the implicit reshape shape.
|
||||
|
||||
Layout transform allows semantics like NCHW --> NCHW4c. Which involves splitting the original C
|
||||
axis into contiguous 4-element chunks. This axis is then moved to the end (NCHWc). This is
|
||||
guaranteed by the operator to be done without additional padding. To handle this we just split
|
||||
the associating axis (prev. type checking ensures C is divisible by 4)in src_layout found in
|
||||
block_read. E.g. NCHW -> NCHW4c now becomes NC4cHW -> NCHW4c.
|
||||
|
||||
Note: NCHW4c --> NCHW is not allowed, so the only numeric digits will be in dst.
|
||||
|
||||
The returned layout strings will be santized and made compatible. E.g. NCHW --> NCHW4c becomes
|
||||
NCcHW --> NCHWc.
|
||||
|
||||
TODO(AndrewZhaoLuo): Investigate using proper memory alignment to avoid bank conflict.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sch:
|
||||
The initial schedule. We expect `block_read`. We also expect
|
||||
block_read's initial loops to follow the original input shape.
|
||||
|
||||
block_read:
|
||||
The block which reads from global memory and writes to shared memory buffer.
|
||||
|
||||
orig_input_shape:
|
||||
The input shape of the input buffer to the primfunc.
|
||||
|
||||
orig_src_layout:
|
||||
The original src_layout string.
|
||||
|
||||
orig_dst_layout:
|
||||
The original dst_layout string.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ret:
|
||||
A tuple of the cached read block, new input shape of shared memory buffer,
|
||||
the new src_layout, and new dst_layout string.
|
||||
"""
|
||||
# Figure out split dimensions, entries are (loop index in src_layout, split amount)
|
||||
split_dimensions: list[tuple[int, int]] = []
|
||||
|
||||
# This is without numeric digits, e.g. NCHW4c -> NCHWc
|
||||
new_dst_layout = []
|
||||
|
||||
# Use state machine to parse NCHW4c string
|
||||
split_size = 0
|
||||
for char in orig_dst_layout:
|
||||
if char.isnumeric():
|
||||
split_size = split_size * 10 + int(char)
|
||||
else:
|
||||
if char.islower():
|
||||
# hit axis like 'c', need to find parent axis 'C' in src_layout
|
||||
src_layout_index = orig_src_layout.index(char.upper())
|
||||
split_dimensions.append((src_layout_index, split_size))
|
||||
split_size = 0
|
||||
new_dst_layout.append(char)
|
||||
|
||||
# If no splits were detected we are done
|
||||
if len(split_dimensions) == 0:
|
||||
block_read = sch.cache_read(block_write, 0, "shared")
|
||||
return block_read, orig_input_shape, orig_src_layout, orig_dst_layout
|
||||
|
||||
# Calculate final input shapes, each of these are a single element for unsplit dims
|
||||
# and tuples for split dims associated with the two new axis
|
||||
input_shape: list[int | tuple] = list(orig_input_shape)
|
||||
new_src_layout: list[str | tuple] = list(orig_src_layout)
|
||||
for src_layout_split_index, split_factor in split_dimensions:
|
||||
dimension_name = orig_src_layout[src_layout_split_index]
|
||||
new_src_layout[src_layout_split_index] = (dimension_name, dimension_name.lower())
|
||||
input_shape[src_layout_split_index] = (
|
||||
orig_input_shape[src_layout_split_index] // split_factor,
|
||||
split_factor,
|
||||
)
|
||||
|
||||
# Unpack any tuples introduced via appending
|
||||
def unpack_list(target_list) -> list:
|
||||
output: list = []
|
||||
for ele in target_list:
|
||||
if isinstance(ele, tuple):
|
||||
output.extend(ele)
|
||||
else:
|
||||
output.append(ele)
|
||||
return output
|
||||
|
||||
new_src_layout_str = "".join(unpack_list(new_src_layout))
|
||||
new_dst_layout_str = "".join(unpack_list(new_dst_layout))
|
||||
|
||||
# Write block loop extants match
|
||||
dst_to_src_map = [new_dst_layout_str.index(dim) for dim in new_src_layout_str]
|
||||
block_read = sch.reindex_cache_read(
|
||||
block_write,
|
||||
read_buffer_index=0,
|
||||
index_map=tvm.tirx.IndexMap.from_func(
|
||||
lambda *loops: [loops[dst_to_src_map[i]] for i, _ in enumerate(loops)],
|
||||
ndim=len(new_src_layout_str),
|
||||
),
|
||||
storage_scope="shared",
|
||||
)
|
||||
|
||||
loops_read = sch.get_loops(block_read)
|
||||
sch.reorder(
|
||||
*[loops_read[new_dst_layout_str.index(dst_dim_name)] for dst_dim_name in new_src_layout_str]
|
||||
)
|
||||
return block_read, unpack_list(input_shape), new_src_layout_str, new_dst_layout_str
|
||||
|
||||
|
||||
def auto_inline_into(sch: Schedule, start_block: SBlockRV) -> SBlockRV:
|
||||
"""
|
||||
Inlines given start_block's consumers and future dependencies into start_block.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sch:
|
||||
The initial schedule.
|
||||
|
||||
start_block:
|
||||
The block to inline into, should be a block which reads and writes to global memory, doing
|
||||
layout transform.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ret:
|
||||
The new block inlined into it's consumers.
|
||||
"""
|
||||
# Rules defined by DefaultCUDA schedule_rule set.
|
||||
autoinline_rule = meta_schedule.schedule_rule.AutoInline(
|
||||
into_producer=True,
|
||||
into_consumer=False,
|
||||
inline_const_tensor=True,
|
||||
disallow_if_then_else=False,
|
||||
require_injective=False,
|
||||
require_ordered=False,
|
||||
)
|
||||
|
||||
fringe = deque(sch.get_consumers(start_block))
|
||||
visited = set()
|
||||
while len(fringe) > 0:
|
||||
cur_block = fringe.popleft()
|
||||
if cur_block in visited:
|
||||
continue
|
||||
|
||||
visited.add(cur_block)
|
||||
consumer_blocks = sch.get_consumers(cur_block)
|
||||
fringe.extend(consumer_blocks)
|
||||
|
||||
sch = autoinline_rule.apply(sch, cur_block)[0]
|
||||
|
||||
|
||||
def get_max_tile_size() -> int:
|
||||
"""Returns the max tile size.
|
||||
|
||||
This is assuming only threads in a warp can have coalesced accesses. 32 is the default if
|
||||
no target information can be gotten.
|
||||
"""
|
||||
max_tile_size = 32
|
||||
cur_target = tvm.target.Target.current()
|
||||
if cur_target is not None and "thread_warp_size" in cur_target.attrs:
|
||||
max_tile_size = int(cur_target.attrs["thread_warp_size"])
|
||||
return max_tile_size
|
||||
|
||||
|
||||
@tvm.register_global_func("s_tir.meta_schedule.cuda.layout_transform")
|
||||
def cuda_layout_transform_schedule_rule(
|
||||
sch: Schedule, block: SBlockRV, testing_tile_sizes: list[int] | None = None
|
||||
) -> list[Schedule]:
|
||||
"""
|
||||
Applies tiling scheme to layout transform task (potentially fused with other injective funcs).
|
||||
|
||||
Returned schedules will be the default schedule, as well as tiled versions with tile_size in
|
||||
the range of 2,3...threads_per_warp.
|
||||
|
||||
This is assuming only threads in a warp can have coalesced accesses. 32 is the default if
|
||||
no target information can be gotten.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sch:
|
||||
The initial schedule.
|
||||
|
||||
block:
|
||||
The block corresponding to the layout transform.
|
||||
Should be a block which reads and writes to global memory, doing layout transform.
|
||||
|
||||
testing_tile_sizes:
|
||||
A list of tile sizes to try, overriding normal settings. For testing. None means
|
||||
ignore. Else overrides normal settings of tile sizes to try.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ret:
|
||||
A list of new schedules to try.
|
||||
"""
|
||||
# Info needed for tiling
|
||||
src_layout = sch.get_sref(block).stmt.annotations["src_layout"]
|
||||
dst_layout = sch.get_sref(block).stmt.annotations["dst_layout"]
|
||||
input_shape = [int(c) for c in sch.get_sref(block).stmt.annotations["input_shape"]]
|
||||
|
||||
schedules = []
|
||||
|
||||
# Always include the default schedules which will be handled via AutoBind schedule rule
|
||||
# Except during testing
|
||||
if not testing_tile_sizes:
|
||||
schedules.append(sch)
|
||||
|
||||
sch = sch.copy()
|
||||
|
||||
# Inline consumers of the layout transform into the layout transform block.
|
||||
# Normally default for injective schedules but must manually be called in new schedule rule
|
||||
# for consumers of the layout transform. TODO(AndrewZhaoLuo): Figure out why this is the case.
|
||||
auto_inline_into(sch, block)
|
||||
|
||||
# Setup up basic structure of schedule of creating read into shared mem, before applying tiling
|
||||
# Outer loop structure of read block matches that of src_layout
|
||||
# E.g. if input_shape is [4, 6, 8]. Loops for read block will be
|
||||
# for i, j, k in T.grid(4, 6, 8):
|
||||
# ...
|
||||
# Read block will read from global memory coalesced at the start
|
||||
# Assume write to output global memory is coalesced in block_write
|
||||
#
|
||||
# This also handles the case where there is an implicit reshape going on.
|
||||
# e.g. NCHW -> NCHW4c which is equivalent to reshaping NCHW
|
||||
# to NCcHW and then applying the new layout where the extant of c is 4.
|
||||
# Grab final input shape and src and dst layouts with possible implicit reshape.
|
||||
block_read, input_shape, src_layout, dst_layout = create_cached_read(
|
||||
sch, block, input_shape, src_layout, dst_layout
|
||||
)
|
||||
|
||||
# Try tile size 2,3...threads_per_warp as tile size of 1 has no coaslescing.
|
||||
if testing_tile_sizes is None:
|
||||
tile_sizes = list(range(2, get_max_tile_size() + 1))
|
||||
else:
|
||||
tile_sizes = testing_tile_sizes
|
||||
|
||||
for tile_size in tile_sizes:
|
||||
new_sch = sch.copy()
|
||||
tile_layout_transform(
|
||||
new_sch, block_read, block, src_layout, dst_layout, input_shape, tile_size
|
||||
)
|
||||
schedules.append(new_sch)
|
||||
|
||||
return schedules
|
||||
@@ -0,0 +1,17 @@
|
||||
# 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.
|
||||
"""Per-block schedule rules in MetaSchedule for generic cases"""
|
||||
@@ -0,0 +1,17 @@
|
||||
# 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.
|
||||
"""Per-block schedule rules in MetaSchedule for target key 'x86'"""
|
||||
@@ -0,0 +1,38 @@
|
||||
# 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.schedule_rule package.
|
||||
Meta Schedule schedule rules are used for modification of
|
||||
blocks in a schedule. See also PostOrderApply.
|
||||
"""
|
||||
|
||||
from .add_rfactor import AddRFactor
|
||||
from .apply_custom_rule import ApplyCustomRule
|
||||
from .auto_bind import AutoBind
|
||||
from .auto_inline import AutoInline, InlineConstantScalars
|
||||
from .cross_thread_reduction import CrossThreadReduction
|
||||
from .multi_level_tiling import (
|
||||
MultiLevelTiling,
|
||||
MultiLevelTilingTensorCore,
|
||||
MultiLevelTilingWideVector,
|
||||
MultiLevelTilingWithIntrin,
|
||||
ReuseType,
|
||||
)
|
||||
from .parallel_vectorize_unroll import ParallelizeVectorizeUnroll
|
||||
from .random_compute_location import RandomComputeLocation
|
||||
from .schedule_rule import PyScheduleRule, ScheduleRule
|
||||
@@ -0,0 +1,48 @@
|
||||
# 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.
|
||||
"""Add-rfactor Rule that add-rfactor to some blocks if needed"""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .schedule_rule import ScheduleRule
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.AddRFactor")
|
||||
class AddRFactor(ScheduleRule):
|
||||
"""Rules for add-rfactor to some blocks if needed.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
max_jobs_per_core: int
|
||||
The maximum number of jobs to be launched per CPU core. It sets the uplimit of CPU
|
||||
parallelism, i.e. `num_cores * max_jobs_per_core`.
|
||||
Use -1 to disable parallelism.
|
||||
max_innermost_factor: Optional[int] = None
|
||||
The maximum size of the innermost factor. None means no limit.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
max_jobs_per_core: int = 16,
|
||||
max_innermost_factor: int | None = None,
|
||||
) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.ScheduleRuleAddRFactor, # type: ignore # pylint: disable=no-member
|
||||
max_jobs_per_core,
|
||||
max_innermost_factor,
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
# 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.
|
||||
"""Create a rule that applies customized rules registered using block attribute `schedule_rule`.
|
||||
The rule will be dispatched according to target keys."""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .schedule_rule import ScheduleRule
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.ApplyCustomRule")
|
||||
class ApplyCustomRule(ScheduleRule):
|
||||
"""A rule that applies customized rules registered using block attribute `schedule_rule`.
|
||||
The rule will be dispatched according to target keys."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.ScheduleRuleApplyCustomRule, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
@@ -0,0 +1,52 @@
|
||||
# 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.
|
||||
"""Auto-bind Rule that binds blocks to threads if needed"""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .schedule_rule import ScheduleRule
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.AutoBind")
|
||||
class AutoBind(ScheduleRule):
|
||||
"""Auto bind loops around the block to BlockIdx and ThreadIdx
|
||||
|
||||
Parameters
|
||||
----------
|
||||
max_threadblocks: int
|
||||
The maximum number of threadblock on GPU.
|
||||
thread_extents: Optional[List[int]]
|
||||
Candidates of thread axis extent.
|
||||
max_threads_per_block: int
|
||||
The maximum number of threads per block, if it is known when this schedule rule is created.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
max_threadblocks: int = 256,
|
||||
thread_extents: list[int] | None = None,
|
||||
max_threads_per_block: int = -1,
|
||||
) -> None:
|
||||
if thread_extents is None:
|
||||
thread_extents = [32, 64, 128, 256, 512, 1024]
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.ScheduleRuleAutoBind, # type: ignore # pylint: disable=no-member
|
||||
max_threadblocks,
|
||||
thread_extents,
|
||||
max_threads_per_block,
|
||||
)
|
||||
@@ -0,0 +1,83 @@
|
||||
# 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.
|
||||
"""Auto-Inline. Rule that inlines spatial blocks if it satisfies some conditions"""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .schedule_rule import ScheduleRule
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.AutoInline")
|
||||
class AutoInline(ScheduleRule):
|
||||
"""Rule that inlines spatial blocks if it satisfies some conditions
|
||||
|
||||
Parameters
|
||||
----------
|
||||
into_producer : bool
|
||||
If allows to inline a block into its producer
|
||||
into_consumer : bool
|
||||
If allows to inline a block into its consumer
|
||||
inline_const_tensor : bool
|
||||
Always inline constant tensors
|
||||
disallow_if_then_else : bool
|
||||
Always disallow if-then-else-like constructs
|
||||
require_injective : bool
|
||||
Always require the read-to-write mapping to be ordered
|
||||
require_ordered : bool
|
||||
Always require the read-to-write mapping to be injective
|
||||
disallow_op : Optional[List[str]]
|
||||
The operators that are disallowed in auto inline
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
into_producer: bool,
|
||||
into_consumer: bool,
|
||||
inline_const_tensor: bool,
|
||||
disallow_if_then_else: bool,
|
||||
require_injective: bool,
|
||||
require_ordered: bool,
|
||||
disallow_op: list[str] | None = None,
|
||||
) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.ScheduleRuleAutoInline, # type: ignore # pylint: disable=no-member
|
||||
into_producer,
|
||||
into_consumer,
|
||||
inline_const_tensor,
|
||||
disallow_if_then_else,
|
||||
require_injective,
|
||||
require_ordered,
|
||||
disallow_op,
|
||||
)
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.InlineConstantScalars")
|
||||
class InlineConstantScalars(ScheduleRule):
|
||||
"""Inline blocks that produce a constant scalar.
|
||||
|
||||
Such blocks get in the way of ReverseComputeInline during AutoInline, since they are also
|
||||
counted as a producer block unless they are inlined first. So it is recommended to run
|
||||
InlineConstantScalars before AutoInline.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.ScheduleRuleInlineConstantScalars, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
@@ -0,0 +1,40 @@
|
||||
# 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.
|
||||
"""Rules which apply cross-thread reduction to some reduction blocks correspondingly when needed"""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .schedule_rule import ScheduleRule
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.CrossThreadReduction")
|
||||
class CrossThreadReduction(ScheduleRule):
|
||||
"""A schedule rule which applies cross-thread reduction to some reduction blocks
|
||||
correspondingly when needed
|
||||
|
||||
Parameters
|
||||
----------
|
||||
thread_extents: List[int]
|
||||
Candidates of thread axis extent (values are required to be positive).
|
||||
"""
|
||||
|
||||
def __init__(self, thread_extents: list[int]) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.ScheduleRuleCrossThreadReduction, # type: ignore # pylint: disable=no-member
|
||||
thread_extents,
|
||||
)
|
||||
@@ -0,0 +1,237 @@
|
||||
# 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.
|
||||
"""Multi-level tiling with reuse."""
|
||||
|
||||
from collections.abc import Callable, Mapping
|
||||
from typing import Any, NamedTuple
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from tvm.s_tir.schedule import SBlockRV, Schedule
|
||||
|
||||
from .. import _ffi_api
|
||||
from .schedule_rule import ScheduleRule
|
||||
|
||||
|
||||
class ReuseType(NamedTuple):
|
||||
"""Reuse type."""
|
||||
|
||||
req: str
|
||||
levels: list[int]
|
||||
scope: str
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
"""Return the dict representation of the reuse type."""
|
||||
return {
|
||||
"req": self.req,
|
||||
"levels": self.levels,
|
||||
"scope": self.scope,
|
||||
}
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.MultiLevelTiling")
|
||||
class MultiLevelTiling(ScheduleRule):
|
||||
"""Multi-level tiling with reuse.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
structure : str
|
||||
The tiling structure. Recommended:
|
||||
- 'SSRSRS' on CPU
|
||||
- 'SSSRRSRS' on GPU
|
||||
tile_bind : Optional[List[str]]
|
||||
For each level of tiles, which thread axis it is bound to. Recommended:
|
||||
- None on CPU
|
||||
- [blockIdx.x, vthread.x, threadIdx.x] on GPU
|
||||
max_innermost_factor : Optional[int]
|
||||
The maximum size of the innermost factor. None means no limit
|
||||
vector_load_lens : Optional[List[int]]
|
||||
The length of vector lane in vectorized cooperative fetching.
|
||||
None means disable vectorization
|
||||
reuse_read : Optional[ReuseType]
|
||||
Data reuse configuration for reading. None means no reuse.
|
||||
reuse_write : Optional[ReuseType]
|
||||
Data reuse configuration for writing. None means no reuse.
|
||||
filter_fn: Optional[Callable[[Schedule, SBlockRV], bool]]
|
||||
A function that can be passed to overwrite the default condition for applying
|
||||
MultiLevelTiling to a block. This is useful if there is a need to apply MultiLevelTiling
|
||||
to an operation / block which is ignored by default. This function should return True
|
||||
for a block that should be tiled (based on the block name, for example).
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
structure: str,
|
||||
tile_binds: list[str] | None = None,
|
||||
max_innermost_factor: int | None = None,
|
||||
vector_load_lens: list[int] | None = None,
|
||||
reuse_read: ReuseType | None = None,
|
||||
reuse_write: ReuseType | None = None,
|
||||
filter_fn: Callable[[Schedule, SBlockRV], bool] | None = None,
|
||||
) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.ScheduleRuleMultiLevelTiling, # type: ignore # pylint: disable=no-member
|
||||
structure,
|
||||
tile_binds,
|
||||
max_innermost_factor,
|
||||
vector_load_lens,
|
||||
reuse_read.as_dict() if reuse_read is not None else None,
|
||||
reuse_write.as_dict() if reuse_write is not None else None,
|
||||
filter_fn,
|
||||
)
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.MultiLevelTilingWithIntrin")
|
||||
class MultiLevelTilingWithIntrin(ScheduleRule):
|
||||
"""Extension of MultiLevelTiling for auto-tensorizing with a single intrinsic.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
intrin_name : str
|
||||
The name of a tensor intrinsic, must be registerd via TensorIntrin.register(...) beforehand
|
||||
structure : str
|
||||
The tiling structure. Recommended:
|
||||
- 'SSRSRS' on CPU
|
||||
- 'SSSRRSRS' on GPU
|
||||
tile_bind : Optional[List[str]]
|
||||
For each level of tiles, which thread axis it is bound to. Recommended:
|
||||
- None on CPU
|
||||
- [blockIdx.x, vthread.x, threadIdx.x] on GPU
|
||||
max_innermost_factor : Optional[int]
|
||||
The maximum size of the innermost factor. None means no limit
|
||||
vector_load_lens : Optional[List[int]]
|
||||
The length of vector lane in vectorized cooperative fetching.
|
||||
None means disable vectorization
|
||||
reuse_read : Optional[ReuseType]
|
||||
Data reuse configuration for reading. None means no reuse.
|
||||
reuse_write : Optional[ReuseType]
|
||||
Data reuse configuration for writing. None means no reuse.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
intrin_name: str,
|
||||
structure: str,
|
||||
tile_binds: list[str] | None = None,
|
||||
max_innermost_factor: int | None = None,
|
||||
vector_load_lens: list[int] | None = None,
|
||||
reuse_read: ReuseType | None = None,
|
||||
reuse_write: ReuseType | None = None,
|
||||
) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.ScheduleRuleMultiLevelTilingWithIntrin, # type: ignore # pylint: disable=no-member
|
||||
intrin_name,
|
||||
structure,
|
||||
tile_binds,
|
||||
max_innermost_factor,
|
||||
vector_load_lens,
|
||||
reuse_read.as_dict() if reuse_read is not None else None,
|
||||
reuse_write.as_dict() if reuse_write is not None else None,
|
||||
)
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.MultiLevelTilingTensorCore")
|
||||
class MultiLevelTilingTensorCore(ScheduleRule):
|
||||
"""Extension of MultiLevelTiling for auto-tensorizing with multiple groups of candidate tensor
|
||||
core intrinsics.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
intrin_groups : List[Mapping[str, str]]
|
||||
A list of groups of tensor core intrinsics. The map should contains key "init", "load_a",
|
||||
"load_b", "compute", "store", which represent the tensor intrin for initialization,
|
||||
loading operand A, loading operand B, tensor core computation, storing the result.
|
||||
The value of the map should be names of tensor intrinsics, must be registerd via
|
||||
TensorIntrin.register(...) beforehand
|
||||
structure : str
|
||||
The tiling structure. Recommended:
|
||||
- 'SSSRRSRS' on GPU
|
||||
tile_bind : Optional[List[str]]
|
||||
For each level of tiles, which thread axis it is bound to. Recommended:
|
||||
- [blockIdx.y, vthread.x, threadIdx.y] on GPU
|
||||
max_innermost_factor : Optional[int]
|
||||
The maximum size of the innermost factor. None means no limit
|
||||
vector_load_lens : Optional[List[int]]
|
||||
The length of vector lane in vectorized cooperative fetching.
|
||||
None means disable vectorization
|
||||
reuse_read : Optional[ReuseType]
|
||||
Data reuse configuration for reading. None means no reuse.
|
||||
reuse_write : Optional[ReuseType]
|
||||
Data reuse configuration for writing. None means no reuse.
|
||||
use_software_pipeline : bool
|
||||
Whether to use the software pipeline.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
intrin_groups: list[Mapping[str, str]],
|
||||
structure: str,
|
||||
tile_binds: list[str] | None = None,
|
||||
max_innermost_factor: int | None = None,
|
||||
vector_load_lens: list[int] | None = None,
|
||||
reuse_read: ReuseType | None = None,
|
||||
reuse_write: ReuseType | None = None,
|
||||
use_software_pipeline: bool = False,
|
||||
) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.ScheduleRuleMultiLevelTilingTensorCore, # type: ignore # pylint: disable=no-member
|
||||
intrin_groups,
|
||||
structure,
|
||||
tile_binds,
|
||||
max_innermost_factor,
|
||||
vector_load_lens,
|
||||
reuse_read.as_dict() if reuse_read is not None else None,
|
||||
reuse_write.as_dict() if reuse_write is not None else None,
|
||||
use_software_pipeline,
|
||||
)
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.MultiLevelTilingWideVector")
|
||||
class MultiLevelTilingWideVector(ScheduleRule):
|
||||
"""Extension of MultiLevelTiling for backends with wide vectors. The loop over the innermost
|
||||
spatial axis of the output buffer is always vectorized with the maximum vector length.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
structure : str
|
||||
The tiling structure. 'SSRSRS' is recommended.
|
||||
vector_length_in_bits: int
|
||||
The length of a vector register in bits.
|
||||
max_innermost_factor : Optional[int]
|
||||
The maximum size of the innermost factor. None means no limit
|
||||
reuse_read : Optional[ReuseType]
|
||||
Data reuse configuration for reading. None means no reuse.
|
||||
reuse_write : Optional[ReuseType]
|
||||
Data reuse configuration for writing. None means no reuse.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
structure: str,
|
||||
vector_length_in_bits: int,
|
||||
max_innermost_factor: int | None = None,
|
||||
reuse_read: ReuseType | None = None,
|
||||
reuse_write: ReuseType | None = None,
|
||||
) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.ScheduleRuleMultiLevelTilingWideVector, # type: ignore # pylint: disable=no-member
|
||||
structure,
|
||||
vector_length_in_bits,
|
||||
max_innermost_factor,
|
||||
reuse_read.as_dict() if reuse_read is not None else None,
|
||||
reuse_write.as_dict() if reuse_write is not None else None,
|
||||
)
|
||||
@@ -0,0 +1,63 @@
|
||||
# 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.
|
||||
"""Rule that mark parallelize, vectorize and unroll to the root block. The mark will be applied to
|
||||
each block in a follow-up post processor"""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .schedule_rule import ScheduleRule
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.ParallelizeVectorizeUnroll")
|
||||
class ParallelizeVectorizeUnroll(ScheduleRule):
|
||||
"""Rule that mark parallelize, vectorize and unroll to the root block. The mark will be applied
|
||||
to each block in a follow-up post processor
|
||||
|
||||
Parameters
|
||||
----------
|
||||
max_jobs_per_core: int
|
||||
The maximum number of jobs to be launched per CPU core. It sets the upper limit of CPU
|
||||
parallelism, i.e. `num_cores * max_jobs_per_core`.
|
||||
Use -1 to disable parallelism.
|
||||
max_vectorize_extent: int
|
||||
The maximum extent to be vectorized. It sets the upper limit of the hardware target
|
||||
vectorization.
|
||||
Use -1 to disable vectorization.
|
||||
unroll_max_steps: Optional[List[int]]
|
||||
The options of the maximum number of unroll steps to be done.
|
||||
Use None to disable unroll
|
||||
unroll_explicit: bool
|
||||
Whether to explicitly unroll the loop, or just add an "unroll" pragma
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
max_jobs_per_core: int = 16,
|
||||
max_vectorize_extent: int = 16,
|
||||
unroll_max_steps: list[int] | None = None,
|
||||
unroll_explicit: bool = True,
|
||||
) -> None:
|
||||
if unroll_max_steps is None:
|
||||
unroll_max_steps = []
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.ScheduleRuleParallelizeVectorizeUnroll, # type: ignore # pylint: disable=no-member
|
||||
max_jobs_per_core,
|
||||
max_vectorize_extent,
|
||||
unroll_max_steps,
|
||||
unroll_explicit,
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
# 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.
|
||||
"""Rule that randomly select a compute-at location for a free block"""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .schedule_rule import ScheduleRule
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.RandomComputeLocation")
|
||||
class RandomComputeLocation(ScheduleRule):
|
||||
"""A rule that randomly select a compute-at location for a free block"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.ScheduleRuleRandomComputeLocation, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
@@ -0,0 +1,191 @@
|
||||
# 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
|
||||
"""
|
||||
Meta Schedule schedule rules are used for modification of
|
||||
blocks in a schedule. See also PostOrderApply.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
# isort: off
|
||||
from typing import Literal
|
||||
|
||||
# isort: on
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from tvm.runtime import Object
|
||||
from tvm.s_tir.schedule import SBlockRV, Schedule
|
||||
|
||||
from .. import _ffi_api
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..tune_context import TuneContext
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.ScheduleRule")
|
||||
class ScheduleRule(Object):
|
||||
"""Rules to modify a block in a schedule."""
|
||||
|
||||
def _initialize_with_tune_context(self, context: "TuneContext") -> None:
|
||||
"""Initialize the schedule rule with a tune context.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : TuneContext
|
||||
The tuning context for initializing the schedule rule.
|
||||
"""
|
||||
_ffi_api.ScheduleRuleInitializeWithTuneContext( # type: ignore # pylint: disable=no-member
|
||||
self, context
|
||||
)
|
||||
|
||||
def apply(self, sch: Schedule, block: SBlockRV) -> list[Schedule]:
|
||||
"""Apply a schedule rule to the specific block in the given schedule.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sch : tvm.s_tir.Schedule
|
||||
The schedule to be modified.
|
||||
block : SBlockRV
|
||||
The specific block to apply the schedule rule.
|
||||
|
||||
Returns
|
||||
-------
|
||||
design_spaces : List[tvm.s_tir.Schedule]
|
||||
The list of schedules generated by applying the schedule rule.
|
||||
"""
|
||||
return _ffi_api.ScheduleRuleApply( # type: ignore # pylint: disable=no-member
|
||||
self, sch, block
|
||||
)
|
||||
|
||||
def clone(self) -> "ScheduleRule":
|
||||
"""Deep clone the schedule rule.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cloned_rule : ScheduleRule
|
||||
The cloned schedule rule.
|
||||
"""
|
||||
return _ffi_api.ScheduleRuleClone(self) # type: ignore # pylint: disable=no-member
|
||||
|
||||
@staticmethod
|
||||
def create(kind: Literal["llvm", "cuda", "cuda-tensorcore", "hexagon"]) -> list["ScheduleRule"]:
|
||||
"""Create a list of schedule rules for the given kind.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
kind : Literal["llvm", "cuda", "cuda-tensorcore", "hexagon"]
|
||||
The kind of the schedule rules.
|
||||
|
||||
Returns
|
||||
-------
|
||||
rules : List[ScheduleRule]
|
||||
The list of schedule rules.
|
||||
"""
|
||||
funcs = {
|
||||
# pylint: disable=no-member
|
||||
"llvm": _ffi_api.ScheduleRuleDefaultLLVM, # type: ignore
|
||||
"cuda": _ffi_api.ScheduleRuleDefaultCUDA, # type: ignore
|
||||
"cuda-tensorcore": _ffi_api.ScheduleRuleDefaultCUDATensorCore, # type: ignore
|
||||
"hexagon": _ffi_api.ScheduleRuleDefaultHexagon, # type: ignore
|
||||
# pylint: enable=no-member
|
||||
}
|
||||
for k, v in funcs.items():
|
||||
if k == kind:
|
||||
return v()
|
||||
raise ValueError(f"Unsupported kind {kind} for schedule rule creation.")
|
||||
|
||||
|
||||
create = ScheduleRule.create # pylint: disable=invalid-name
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.PyScheduleRule")
|
||||
class _PyScheduleRule(ScheduleRule):
|
||||
"""
|
||||
A TVM object schedule rule to support customization on the python side.
|
||||
This is NOT the user facing class for function overloading inheritance.
|
||||
|
||||
See also: PyScheduleRule
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
f_initialize_with_tune_context: Callable | None = None,
|
||||
f_apply: Callable | None = None,
|
||||
f_clone: Callable | None = None,
|
||||
):
|
||||
"""Constructor."""
|
||||
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.ScheduleRulePyScheduleRule, # type: ignore # pylint: disable=no-member
|
||||
f_initialize_with_tune_context,
|
||||
f_apply,
|
||||
f_clone,
|
||||
)
|
||||
|
||||
|
||||
class PyScheduleRule:
|
||||
"""
|
||||
An abstract schedule rule with customized methods 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": _PyScheduleRule,
|
||||
"methods": ["_initialize_with_tune_context", "apply", "clone"],
|
||||
}
|
||||
|
||||
def _initialize_with_tune_context(self, context: "TuneContext") -> None:
|
||||
"""Initialize the schedule rule with a tune context.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : TuneContext
|
||||
The tuning context for initializing the schedule rule.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def apply(self, sch: Schedule, block: SBlockRV) -> list[Schedule]:
|
||||
"""Apply a schedule rule to the specific block in the given schedule.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sch : Schedule
|
||||
The schedule to be modified.
|
||||
block : SBlockRV
|
||||
The specific block to apply the schedule rule.
|
||||
|
||||
Returns
|
||||
-------
|
||||
design_spaces : List[Schedule]
|
||||
The list of schedules generated by applying the schedule rule.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def clone(self) -> ScheduleRule:
|
||||
"""Deep clone the schedule rule.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cloned_rule : ScheduleRule
|
||||
The cloned schedule rule.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,26 @@
|
||||
# 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.search_strategy package.
|
||||
Meta Schedule search strategy utilizes the design spaces given
|
||||
to generate measure candidates.
|
||||
"""
|
||||
|
||||
from .evolutionary_search import EvolutionarySearch
|
||||
from .replay_func import ReplayFunc
|
||||
from .replay_trace import ReplayTrace
|
||||
from .search_strategy import MeasureCandidate, PySearchStrategy, SearchStrategy, create
|
||||
@@ -0,0 +1,82 @@
|
||||
# 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.
|
||||
"""Evolutionary Search Strategy"""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .search_strategy import SearchStrategy
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.EvolutionarySearch")
|
||||
class EvolutionarySearch(SearchStrategy):
|
||||
"""
|
||||
Replay Trace Search Strategy is a search strategy that always replays the trace by removing its
|
||||
decisions so that the decisions would be randomly re-generated.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
population_size : int
|
||||
The initial population of traces from measured samples and randomly generated samples.
|
||||
init_measured_ratio : int
|
||||
The ratio of measured samples in the initial population.
|
||||
init_min_unmeasured : int
|
||||
The minimal size of unmeasured population in the initial sampling.
|
||||
max_fail_count : int
|
||||
The maximum number of failure during initial sampling.
|
||||
genetic_num_iters : int
|
||||
The number of iterations for genetic algorithm.
|
||||
genetic_mutate_prob : float
|
||||
The probability of mutation.
|
||||
genetic_max_fail_count : int
|
||||
The maximum number to retry mutation.
|
||||
eps_greedy : float
|
||||
The ratio of greedy selected samples in the final picks.
|
||||
"""
|
||||
|
||||
population_size: int
|
||||
init_measured_ratio: int
|
||||
init_min_unmeasured: int
|
||||
genetic_num_iters: int
|
||||
genetic_mutate_prob: float
|
||||
genetic_max_fail_count: int
|
||||
eps_greedy: float
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
population_size: int = 512,
|
||||
init_measured_ratio: float = 0.2,
|
||||
init_min_unmeasured: int = 50,
|
||||
max_fail_count: int = 5,
|
||||
genetic_num_iters: int = 4,
|
||||
genetic_mutate_prob: float = 0.85,
|
||||
genetic_max_fail_count: int = 10,
|
||||
eps_greedy: float = 0.05,
|
||||
) -> None:
|
||||
"""Constructor"""
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.SearchStrategyEvolutionarySearch, # type: ignore # pylint: disable=no-member
|
||||
population_size,
|
||||
init_measured_ratio,
|
||||
init_min_unmeasured,
|
||||
max_fail_count,
|
||||
genetic_num_iters,
|
||||
genetic_mutate_prob,
|
||||
genetic_max_fail_count,
|
||||
eps_greedy,
|
||||
)
|
||||
@@ -0,0 +1,43 @@
|
||||
# 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.
|
||||
"""Replay Trace Search Strategy"""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .search_strategy import SearchStrategy
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.ReplayFunc")
|
||||
class ReplayFunc(SearchStrategy):
|
||||
"""
|
||||
Replay Func Search Strategy is a search strategy that generates measure candidates by
|
||||
calling a design space generator and transform the design space.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
num_trials_per_iter : int
|
||||
Number of trials per iteration.
|
||||
max_trials_per_task : int
|
||||
Total number of trials for one task
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Constructor"""
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.SearchStrategyReplayFunc, # type: ignore # pylint: disable=no-member
|
||||
)
|
||||
@@ -0,0 +1,44 @@
|
||||
# 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.
|
||||
"""Replay Trace Search Strategy"""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .search_strategy import SearchStrategy
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.ReplayTrace")
|
||||
class ReplayTrace(SearchStrategy):
|
||||
"""
|
||||
Replay Trace Search Strategy is a search strategy that always replays the trace by removing its
|
||||
decisions so that the decisions would be randomly re-generated.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
max_fail_count : int
|
||||
Max number of failures during trace replaying.
|
||||
"""
|
||||
|
||||
max_fail_count: int
|
||||
|
||||
def __init__(self, max_fail_count: int = 100):
|
||||
"""Constructor"""
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.SearchStrategyReplayTrace, # type: ignore # pylint: disable=no-member
|
||||
max_fail_count,
|
||||
)
|
||||
@@ -0,0 +1,342 @@
|
||||
# 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
|
||||
"""
|
||||
Meta Schedule search strategy that generates the measure
|
||||
candidates for measurement.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING, Optional, Union
|
||||
|
||||
# isort: off
|
||||
from typing import Literal
|
||||
|
||||
# isort: on
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from tvm.runtime import Object
|
||||
from tvm.s_tir.schedule import Schedule
|
||||
|
||||
from .. import _ffi_api
|
||||
from ..arg_info import ArgInfo
|
||||
from ..runner import RunnerResult
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..cost_model import CostModel
|
||||
from ..database import Database
|
||||
from ..tune_context import TuneContext
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.MeasureCandidate")
|
||||
class MeasureCandidate(Object):
|
||||
"""Measure candidate class.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sch : tvm.s_tir.Schedule
|
||||
The schedule to be measured.
|
||||
args_info : List[ArgInfo]
|
||||
The argument information.
|
||||
"""
|
||||
|
||||
sch: Schedule
|
||||
args_info: list[ArgInfo]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
sch: Schedule,
|
||||
args_info: list[ArgInfo],
|
||||
) -> None:
|
||||
"""Constructor.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sch : tvm.s_tir.Schedule
|
||||
The schedule to be measured.
|
||||
args_info : List[ArgInfo]
|
||||
The argument information.
|
||||
"""
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.MeasureCandidate, # type: ignore # pylint: disable=no-member
|
||||
sch,
|
||||
args_info,
|
||||
)
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.SearchStrategy")
|
||||
class SearchStrategy(Object):
|
||||
"""Search strategy is the class that generates the measure candidates."""
|
||||
|
||||
SearchStrategyType = Union[
|
||||
"SearchStrategy",
|
||||
Literal[
|
||||
"replay-func",
|
||||
"replay-trace",
|
||||
"evolutionary",
|
||||
],
|
||||
]
|
||||
|
||||
def __new__(cls, *args, **kwargs): # pylint: disable=unused-argument
|
||||
"""Prevent direct instantiation of abstract SearchStrategy class.
|
||||
|
||||
SearchStrategy is an abstract class and cannot be directly instantiated.
|
||||
Use SearchStrategy.create() or a concrete subclass instead.
|
||||
"""
|
||||
if cls is SearchStrategy:
|
||||
raise TypeError(
|
||||
"Cannot instantiate abstract class SearchStrategy. "
|
||||
"Use SearchStrategy.create() with a valid strategy type "
|
||||
"(e.g., 'evolutionary', 'replay-trace', 'replay-func') "
|
||||
"or use a concrete subclass instead."
|
||||
)
|
||||
return super().__new__(cls) # pylint: disable=no-value-for-parameter
|
||||
|
||||
def _initialize_with_tune_context(self, context: "TuneContext") -> None:
|
||||
"""Initialize the search strategy with tuning context.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : TuneContext
|
||||
The tuning context for initialization.
|
||||
"""
|
||||
_ffi_api.SearchStrategyInitializeWithTuneContext( # type: ignore # pylint: disable=no-member
|
||||
self, context
|
||||
)
|
||||
|
||||
def pre_tuning(
|
||||
self,
|
||||
max_trials: int,
|
||||
num_trials_per_iter: int,
|
||||
design_spaces: list[Schedule],
|
||||
database: Optional["Database"] = None,
|
||||
cost_model: Optional["CostModel"] = None,
|
||||
) -> None:
|
||||
"""Pre-tuning for the search strategy.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
max_trials : int
|
||||
The maximum number of trials.
|
||||
num_trials_per_iter : int
|
||||
The number of trials per iteration.
|
||||
design_spaces : List[tvm.s_tir.Schedule]
|
||||
The design spaces used during tuning process.
|
||||
database : Optional[Database] = None
|
||||
The database used during tuning process.
|
||||
cost_model : Optional[CostModel] = None
|
||||
The cost model used during tuning process.
|
||||
"""
|
||||
_ffi_api.SearchStrategyPreTuning( # type: ignore # pylint: disable=no-member
|
||||
self,
|
||||
max_trials,
|
||||
num_trials_per_iter,
|
||||
design_spaces,
|
||||
database,
|
||||
cost_model,
|
||||
)
|
||||
|
||||
def post_tuning(self) -> None:
|
||||
"""Post-tuning for the search strategy."""
|
||||
_ffi_api.SearchStrategyPostTuning(self) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def generate_measure_candidates(self) -> list[MeasureCandidate] | None:
|
||||
"""Generate measure candidates from design spaces for measurement.
|
||||
|
||||
Returns
|
||||
-------
|
||||
measure_candidates : Optional[List[IRModule]]
|
||||
The measure candidates generated, None if finished.
|
||||
"""
|
||||
return _ffi_api.SearchStrategyGenerateMeasureCandidates(self) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def notify_runner_results(
|
||||
self,
|
||||
measure_candidates: list[MeasureCandidate],
|
||||
results: list[RunnerResult],
|
||||
) -> None:
|
||||
"""Update the search strategy with profiling results.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
measure_candidates : List[MeasureCandidate]
|
||||
The measure candidates for update.
|
||||
results : List[RunnerResult]
|
||||
The profiling results from the runner.
|
||||
"""
|
||||
_ffi_api.SearchStrategyNotifyRunnerResults( # type: ignore # pylint: disable=no-member
|
||||
self,
|
||||
measure_candidates,
|
||||
results,
|
||||
)
|
||||
|
||||
def clone(self) -> "SearchStrategy":
|
||||
"""Clone the search strategy.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cloned : SearchStrategy
|
||||
The cloned search strategy.
|
||||
"""
|
||||
return _ffi_api.SearchStrategyClone(self) # type: ignore # pylint: disable=no-member
|
||||
|
||||
@staticmethod
|
||||
def create( # pylint: disable=keyword-arg-before-vararg
|
||||
kind: Literal[
|
||||
"evolutionary",
|
||||
"replay-trace",
|
||||
"replay-func",
|
||||
] = "evolutionary",
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> "SearchStrategy":
|
||||
"""Create a search strategy."""
|
||||
from . import ( # pylint: disable=import-outside-toplevel
|
||||
EvolutionarySearch,
|
||||
ReplayFunc,
|
||||
ReplayTrace,
|
||||
)
|
||||
|
||||
if kind == "evolutionary":
|
||||
return EvolutionarySearch(*args, **kwargs)
|
||||
if kind == "replay-trace":
|
||||
return ReplayTrace(*args, **kwargs)
|
||||
if kind == "replay-func":
|
||||
return ReplayFunc(*args, **kwargs) # type: ignore
|
||||
raise ValueError(f"Unknown SearchStrategy: {kind}")
|
||||
|
||||
|
||||
create = SearchStrategy.create # pylint: disable=invalid-name
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.PySearchStrategy")
|
||||
class _PySearchStrategy(SearchStrategy):
|
||||
"""
|
||||
A TVM object search strategy to support customization on the python side.
|
||||
This is NOT the user facing class for function overloading inheritance.
|
||||
|
||||
See also: PySearchStrategy
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
f_initialize_with_tune_context: Callable | None = None,
|
||||
f_pre_tuning: Callable | None = None,
|
||||
f_post_tuning: Callable | None = None,
|
||||
f_generate_measure_candidates: Callable | None = None,
|
||||
f_notify_runner_results: Callable | None = None,
|
||||
f_clone: Callable | None = None,
|
||||
):
|
||||
"""Constructor."""
|
||||
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.SearchStrategyPySearchStrategy, # type: ignore # pylint: disable=no-member
|
||||
f_initialize_with_tune_context,
|
||||
f_pre_tuning,
|
||||
f_post_tuning,
|
||||
f_generate_measure_candidates,
|
||||
f_notify_runner_results,
|
||||
f_clone,
|
||||
)
|
||||
|
||||
|
||||
class PySearchStrategy:
|
||||
"""
|
||||
An abstract search strategy with customized methods 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": _PySearchStrategy,
|
||||
"methods": [
|
||||
"_initialize_with_tune_context",
|
||||
"pre_tuning",
|
||||
"post_tuning",
|
||||
"generate_measure_candidates",
|
||||
"notify_runner_results",
|
||||
"clone",
|
||||
],
|
||||
}
|
||||
|
||||
def _initialize_with_tune_context(self, context: "TuneContext") -> None:
|
||||
"""Initialize the search strategy with tuning context.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : TuneContext
|
||||
The tuning context for initialization.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def pre_tuning(
|
||||
self,
|
||||
max_trials: int,
|
||||
num_trials_per_iter: int,
|
||||
design_spaces: list[Schedule],
|
||||
database: Optional["Database"] = None,
|
||||
cost_model: Optional["CostModel"] = None,
|
||||
) -> None:
|
||||
"""Pre-tuning for the search strategy.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
design_spaces : List[Schedule]
|
||||
The design spaces for pre-tuning.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def post_tuning(self) -> None:
|
||||
"""Post-tuning for the search strategy."""
|
||||
raise NotImplementedError
|
||||
|
||||
def generate_measure_candidates(self) -> list[MeasureCandidate] | None:
|
||||
"""Generate measure candidates from design spaces for measurement.
|
||||
|
||||
Returns
|
||||
-------
|
||||
measure_candidates : Optional[List[IRModule]]
|
||||
The measure candidates generated, None if finished.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def notify_runner_results(
|
||||
self,
|
||||
measure_candidates: list[MeasureCandidate],
|
||||
results: list[RunnerResult],
|
||||
) -> None:
|
||||
"""Update the search strategy with profiling results.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
measure_candidates : List[MeasureCandidate]
|
||||
The measure candidates for update.
|
||||
results : List[RunnerResult]
|
||||
The profiling results from the runner.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def clone(self) -> SearchStrategy:
|
||||
"""Clone the search strategy.
|
||||
|
||||
Returns
|
||||
-------
|
||||
strategy : SearchStrategy
|
||||
The cloned search strategy.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,27 @@
|
||||
# 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.space_generator package.
|
||||
Meta Schedule design space generators that generates design
|
||||
space for generation of measure candidates.
|
||||
"""
|
||||
|
||||
from .post_order_apply import PostOrderApply
|
||||
from .schedule_fn import ScheduleFn
|
||||
from .space_generator import PySpaceGenerator, ScheduleFnType, SpaceGenerator, create
|
||||
from .space_generator_union import SpaceGeneratorUnion
|
||||
@@ -0,0 +1,61 @@
|
||||
# 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.
|
||||
"""Post Order Apply Space Generator."""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .space_generator import (
|
||||
MutatorProbType,
|
||||
PostprocType,
|
||||
ScheduleRuleType,
|
||||
SpaceGenerator,
|
||||
_normalize_rules,
|
||||
)
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.PostOrderApply")
|
||||
class PostOrderApply(SpaceGenerator):
|
||||
"""
|
||||
PostOrderApply is the design space generator that generates design spaces by applying schedule
|
||||
rules to blocks in post-DFS order.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
f_block_filter : Optional[function]
|
||||
An optional callback function that is used to filter which blocks have schedules generated
|
||||
for them. The function should take in a block and return True if a schedule should
|
||||
be generated or False if that block should be skipped. If no function is provided
|
||||
all blocks will have schedules generated.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
f_block_filter=None,
|
||||
sch_rules: ScheduleRuleType = "from-target",
|
||||
postprocs: PostprocType = "from-target",
|
||||
mutator_probs: MutatorProbType = "from-target",
|
||||
):
|
||||
"""Constructor"""
|
||||
sch_rules, postprocs, mutator_probs = _normalize_rules(sch_rules, postprocs, mutator_probs)
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.SpaceGeneratorPostOrderApply, # type: ignore # pylint: disable=no-member
|
||||
f_block_filter,
|
||||
sch_rules,
|
||||
postprocs,
|
||||
mutator_probs,
|
||||
)
|
||||
@@ -0,0 +1,64 @@
|
||||
# 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.
|
||||
"""Union of meta Schedule design space generators."""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .space_generator import (
|
||||
MutatorProbType,
|
||||
PostprocType,
|
||||
ScheduleRuleType,
|
||||
SpaceGenerator,
|
||||
_normalize_rules,
|
||||
)
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.ScheduleFn")
|
||||
class ScheduleFn(SpaceGenerator):
|
||||
"""Create a design space generator with customized schedule function.
|
||||
The schedule function can have the following signatures:
|
||||
- 1) [Schedule] -> None
|
||||
- 2) [Schedule] -> Schedule
|
||||
- 3) [Schedule] -> List[Schedule]
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
sch_fn: SpaceGenerator.ScheduleFnType,
|
||||
sch_rules: ScheduleRuleType = "from-target",
|
||||
postprocs: PostprocType = "from-target",
|
||||
mutator_probs: MutatorProbType = "from-target",
|
||||
):
|
||||
"""Constructor.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sch_fn : SpaceGenerator.ScheduleFnType
|
||||
The schedule function, which can have the following signatures:
|
||||
- 1) [Schedule] -> None
|
||||
- 2) [Schedule] -> Schedule
|
||||
- 3) [Schedule] -> List[Schedule]
|
||||
"""
|
||||
sch_rules, postprocs, mutator_probs = _normalize_rules(sch_rules, postprocs, mutator_probs)
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.SpaceGeneratorScheduleFn, # type: ignore # pylint: disable=no-member
|
||||
sch_fn,
|
||||
sch_rules,
|
||||
postprocs,
|
||||
mutator_probs,
|
||||
)
|
||||
@@ -0,0 +1,264 @@
|
||||
# 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
|
||||
"""
|
||||
Meta Schedule design space generators that generates design
|
||||
space for generation of measure candidates.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
# isort: off
|
||||
from typing import Literal
|
||||
|
||||
# isort: on
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from tvm.ir import IRModule
|
||||
from tvm.runtime import Object
|
||||
from tvm.s_tir.schedule import Schedule
|
||||
|
||||
from .. import _ffi_api
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..mutator import Mutator
|
||||
from ..postproc import Postproc
|
||||
from ..schedule_rule import ScheduleRule
|
||||
from ..tune_context import TuneContext
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.SpaceGenerator")
|
||||
class SpaceGenerator(Object):
|
||||
"""The abstract design space generator interface."""
|
||||
|
||||
ScheduleFnType = (
|
||||
Callable[[Schedule], None] # No output
|
||||
| Callable[[Schedule], Schedule] # Single output
|
||||
| Callable[[Schedule], list[Schedule]] # Multiple outputs
|
||||
)
|
||||
|
||||
SpaceGeneratorType = Union[
|
||||
"SpaceGenerator",
|
||||
ScheduleFnType,
|
||||
Literal["post-order-apply", "union"],
|
||||
]
|
||||
|
||||
sch_rules: list["ScheduleRule"] | None
|
||||
postprocs: list["Postproc"] | None
|
||||
mutator_probs: dict["Mutator", float] | None
|
||||
|
||||
def _initialize_with_tune_context(self, context: "TuneContext") -> None:
|
||||
"""Initialize the design space generator with tuning context.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : TuneContext
|
||||
The tuning context for initializing the design space generator.
|
||||
"""
|
||||
_ffi_api.SpaceGeneratorInitializeWithTuneContext( # type: ignore # pylint: disable=no-member
|
||||
self, context
|
||||
)
|
||||
|
||||
def generate_design_space(self, mod: IRModule) -> list[Schedule]:
|
||||
"""Generate design spaces given a module.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : IRModule
|
||||
The module used for design space generation.
|
||||
|
||||
Returns
|
||||
-------
|
||||
design_spaces : List[tvm.s_tir.Schedule]
|
||||
The generated design spaces, i.e., schedules.
|
||||
"""
|
||||
return _ffi_api.SpaceGeneratorGenerateDesignSpace(self, mod) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def clone(self) -> "SpaceGenerator":
|
||||
"""Clone the design space generator.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cloned_sg : SpaceGenerator
|
||||
The cloned design space generator.
|
||||
"""
|
||||
return _ffi_api.SpaceGeneratorClone(self) # type: ignore # pylint: disable=no-member
|
||||
|
||||
@staticmethod
|
||||
def create( # pylint: disable=keyword-arg-before-vararg
|
||||
kind: Literal["post-order-apply", "union"] | ScheduleFnType = "post-order-apply",
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> "SpaceGenerator":
|
||||
"""Create a design space generator."""
|
||||
from . import ( # pylint: disable=import-outside-toplevel
|
||||
PostOrderApply,
|
||||
ScheduleFn,
|
||||
SpaceGeneratorUnion,
|
||||
)
|
||||
|
||||
if callable(kind):
|
||||
|
||||
def create_schedule_fn(
|
||||
func,
|
||||
sch_rules=[],
|
||||
postprocs=[],
|
||||
mutator_probs={},
|
||||
): # pylint: disable=dangerous-default-value
|
||||
return ScheduleFn(func, sch_rules, postprocs, mutator_probs)
|
||||
|
||||
return create_schedule_fn(kind, *args, **kwargs) # type: ignore
|
||||
if kind == "post-order-apply":
|
||||
return PostOrderApply(*args, **kwargs)
|
||||
if kind == "union":
|
||||
return SpaceGeneratorUnion(*args, **kwargs)
|
||||
if isinstance(kind, str):
|
||||
return PostOrderApply(sch_rules=kind, postprocs=kind, mutator_probs=kind)
|
||||
raise ValueError(f"Unknown SpaceGenerator: {kind}")
|
||||
|
||||
|
||||
ScheduleFnType = SpaceGenerator.ScheduleFnType
|
||||
ScheduleRuleType = (
|
||||
list["ScheduleRule"] | Literal["llvm", "cuda", "cuda-tensorcore", "hexagon", "from-target"]
|
||||
)
|
||||
PostprocType = (
|
||||
list["Postproc"] | Literal["llvm", "cuda", "cuda-tensorcore", "hexagon", "from-target"]
|
||||
)
|
||||
MutatorProbType = (
|
||||
dict["Mutator", float] | Literal["llvm", "cuda", "cuda-tensorcore", "hexagon", "from-target"]
|
||||
)
|
||||
create = SpaceGenerator.create # pylint: disable=invalid-name
|
||||
|
||||
|
||||
def _normalize_rules(
|
||||
sch_rules: ScheduleRuleType,
|
||||
postprocs: PostprocType,
|
||||
mutator_probs: MutatorProbType,
|
||||
) -> tuple[
|
||||
list["ScheduleRule"] | None,
|
||||
list["Postproc"] | None,
|
||||
dict["Mutator", float] | None,
|
||||
]:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from ..mutator import Mutator
|
||||
from ..postproc import Postproc
|
||||
from ..schedule_rule import ScheduleRule
|
||||
|
||||
# pylint: enable=import-outside-toplevel
|
||||
assert sch_rules is not None
|
||||
assert postprocs is not None
|
||||
assert mutator_probs is not None
|
||||
|
||||
if isinstance(sch_rules, str):
|
||||
if sch_rules == "from-target":
|
||||
sch_rules = None
|
||||
else:
|
||||
sch_rules = ScheduleRule.create(sch_rules)
|
||||
if isinstance(postprocs, str):
|
||||
if postprocs == "from-target":
|
||||
postprocs = None
|
||||
else:
|
||||
postprocs = Postproc.create(postprocs)
|
||||
if isinstance(mutator_probs, str):
|
||||
if mutator_probs == "from-target":
|
||||
mutator_probs = None
|
||||
else:
|
||||
mutator_probs = Mutator.create(mutator_probs)
|
||||
return sch_rules, postprocs, mutator_probs # type: ignore
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.PySpaceGenerator")
|
||||
class _PySpaceGenerator(SpaceGenerator):
|
||||
"""
|
||||
A TVM object space generator to support customization on the python side.
|
||||
This is NOT the user facing class for function overloading inheritance.
|
||||
|
||||
See also: PySpaceGenerator
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
sch_rules: ScheduleRuleType = "from-target",
|
||||
postprocs: PostprocType = "from-target",
|
||||
mutator_probs: MutatorProbType = "from-target",
|
||||
f_initialize_with_tune_context: Callable | None = None,
|
||||
f_generate_design_space: Callable | None = None,
|
||||
f_clone: Callable | None = None,
|
||||
):
|
||||
"""Constructor."""
|
||||
sch_rules, postprocs, mutator_probs = _normalize_rules(sch_rules, postprocs, mutator_probs)
|
||||
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.SpaceGeneratorPySpaceGenerator, # type: ignore # pylint: disable=no-member
|
||||
sch_rules,
|
||||
postprocs,
|
||||
mutator_probs,
|
||||
f_initialize_with_tune_context,
|
||||
f_generate_design_space,
|
||||
f_clone,
|
||||
)
|
||||
|
||||
|
||||
class PySpaceGenerator:
|
||||
"""
|
||||
An abstract space generator with customized methods 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": _PySpaceGenerator,
|
||||
"fields": ["sch_rules", "postprocs", "mutator_probs"],
|
||||
"methods": ["_initialize_with_tune_context", "generate_design_space", "clone"],
|
||||
}
|
||||
|
||||
def _initialize_with_tune_context(self, context: "TuneContext") -> None:
|
||||
"""Initialize the design space generator with tuning context.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
context : TuneContext
|
||||
The tuning context for initializing the design space generator.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def generate_design_space(self, mod: IRModule) -> list[Schedule]:
|
||||
"""Generate design spaces given a module.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mod : IRModule
|
||||
The module used for design space generation.
|
||||
|
||||
Returns
|
||||
-------
|
||||
design_spaces : List[tvm.s_tir.Schedule]
|
||||
The generated design spaces, i.e., schedules.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def clone(self) -> SpaceGenerator:
|
||||
"""Clone the design space generator.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cloned_sg : SpaceGenerator
|
||||
The cloned design space generator.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,56 @@
|
||||
# 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.
|
||||
"""Union of meta Schedule design space generators."""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from .space_generator import (
|
||||
MutatorProbType,
|
||||
PostprocType,
|
||||
ScheduleRuleType,
|
||||
SpaceGenerator,
|
||||
_normalize_rules,
|
||||
)
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.SpaceGeneratorUnion")
|
||||
class SpaceGeneratorUnion(SpaceGenerator):
|
||||
"""Union of design space generators."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
space_generators: list[SpaceGenerator],
|
||||
sch_rules: ScheduleRuleType = "from-target",
|
||||
postprocs: PostprocType = "from-target",
|
||||
mutator_probs: MutatorProbType = "from-target",
|
||||
):
|
||||
"""Constructor.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
space_generators : List[SpaceGenerator]
|
||||
The list of design space generators to be unioned.
|
||||
"""
|
||||
sch_rules, postprocs, mutator_probs = _normalize_rules(sch_rules, postprocs, mutator_probs)
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.SpaceGeneratorSpaceGeneratorUnion, # type: ignore # pylint: disable=no-member
|
||||
space_generators,
|
||||
sch_rules,
|
||||
postprocs,
|
||||
mutator_probs,
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
# 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.task_scheduler package.
|
||||
Meta Schedule task scheduler that manage the task scheduling
|
||||
for measure candidates generation and measurement, then save
|
||||
records to the database.
|
||||
"""
|
||||
|
||||
from .gradient_based import GradientBased
|
||||
from .round_robin import RoundRobin
|
||||
from .task_scheduler import PyTaskScheduler, TaskScheduler, create
|
||||
@@ -0,0 +1,56 @@
|
||||
# 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.
|
||||
"""Gradient Based Task Scheduler"""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from ..logging import get_logger, get_logging_func
|
||||
from .task_scheduler import TaskScheduler
|
||||
|
||||
logger = get_logger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.GradientBased")
|
||||
class GradientBased(TaskScheduler):
|
||||
"""Gradient Based Task Scheduler"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
alpha: float = 0.2,
|
||||
window_size: int = 3,
|
||||
seed: int = -1,
|
||||
) -> None:
|
||||
"""Constructor.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
alpha : float = 0.2
|
||||
The parameter alpha in gradient computation.
|
||||
window_size : int = 3
|
||||
The parameter to control backward window size in gradient computation.
|
||||
seed : int = -1
|
||||
The random seed.
|
||||
"""
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.TaskSchedulerGradientBased, # type: ignore # pylint: disable=no-member
|
||||
get_logging_func(logger),
|
||||
alpha,
|
||||
window_size,
|
||||
seed,
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
# 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.
|
||||
"""Round Robin Task Scheduler"""
|
||||
|
||||
from tvm_ffi import register_object
|
||||
|
||||
from .. import _ffi_api
|
||||
from ..logging import get_logger, get_logging_func
|
||||
from .task_scheduler import TaskScheduler
|
||||
|
||||
logger = get_logger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.RoundRobin")
|
||||
class RoundRobin(TaskScheduler):
|
||||
"""Round Robin Task Scheduler"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Constructor."""
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.TaskSchedulerRoundRobin, # type: ignore # pylint: disable=no-member
|
||||
get_logging_func(logger),
|
||||
)
|
||||
@@ -0,0 +1,284 @@
|
||||
# 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
|
||||
"""Auto-tuning Task Scheduler"""
|
||||
|
||||
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 ..builder import Builder, BuilderResult
|
||||
from ..cost_model import CostModel
|
||||
from ..database import Database
|
||||
from ..logging import get_logger, get_logging_func
|
||||
from ..measure_callback import MeasureCallback
|
||||
from ..runner import Runner, RunnerResult
|
||||
from ..search_strategy import MeasureCandidate
|
||||
from ..tune_context import TuneContext
|
||||
|
||||
logger = get_logger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.TaskRecord")
|
||||
class TaskRecord(Object):
|
||||
"""The running record of a task."""
|
||||
|
||||
ctx: TuneContext
|
||||
task_weight: float
|
||||
flop: float
|
||||
is_terminated: bool
|
||||
build_error_count: int
|
||||
run_error_count: int
|
||||
measure_candidates: list[MeasureCandidate]
|
||||
builder_results: list[BuilderResult]
|
||||
runner_results: list[RunnerResult]
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.TaskScheduler")
|
||||
class TaskScheduler(Object):
|
||||
"""The abstract task scheduler interface."""
|
||||
|
||||
tasks_: list[TaskRecord]
|
||||
measure_callbacks_: list[MeasureCallback]
|
||||
database_: Database | None
|
||||
cost_model_: CostModel | None
|
||||
remaining_tasks_: int
|
||||
|
||||
TaskSchedulerType = Union["TaskScheduler", Literal["gradient", "round-robin"]]
|
||||
|
||||
def next_task_id(self) -> int:
|
||||
"""Fetch the next task id.
|
||||
|
||||
Returns
|
||||
-------
|
||||
next_task_id : int
|
||||
The next task id.
|
||||
"""
|
||||
return _ffi_api.TaskSchedulerNextTaskId(self) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def join_running_task(self, task_id: int) -> list[RunnerResult]:
|
||||
"""Wait until the task is finished.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
task_id : int
|
||||
The task id to be joined.
|
||||
|
||||
Returns
|
||||
-------
|
||||
results : List[RunnerResult]
|
||||
The list of results.
|
||||
"""
|
||||
return _ffi_api.TaskSchedulerJoinRunningTask(self, task_id) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def tune(
|
||||
self,
|
||||
tasks: list[TuneContext],
|
||||
task_weights: list[float],
|
||||
max_trials_global: int,
|
||||
max_trials_per_task: int,
|
||||
num_trials_per_iter: int,
|
||||
builder: Builder,
|
||||
runner: Runner,
|
||||
measure_callbacks: list[MeasureCallback],
|
||||
database: Database | None,
|
||||
cost_model: CostModel | None,
|
||||
) -> None:
|
||||
"""Auto-tuning.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tasks : List[TuneContext]
|
||||
The list of tuning contexts as tasks.
|
||||
task_weights : List[float]
|
||||
The list of task weights.
|
||||
max_trials_global : int
|
||||
The maximum number of trials globally.
|
||||
max_trials_per_task : int
|
||||
The maximum number of trials per task.
|
||||
num_trials_per_iter : int
|
||||
The number of trials per iteration.
|
||||
builder : Builder
|
||||
The builder.
|
||||
runner : Runner
|
||||
The runner.
|
||||
measure_callbacks : List[MeasureCallback]
|
||||
The list of measure callbacks.
|
||||
database : Optional[Database]
|
||||
The database.
|
||||
cost_model : Optional[CostModel]
|
||||
The cost model.
|
||||
"""
|
||||
task_weights = [float(w) for w in task_weights]
|
||||
_ffi_api.TaskSchedulerTune( # type: ignore # pylint: disable=no-member
|
||||
self,
|
||||
tasks,
|
||||
task_weights,
|
||||
max_trials_global,
|
||||
max_trials_per_task,
|
||||
num_trials_per_iter,
|
||||
builder,
|
||||
runner,
|
||||
measure_callbacks,
|
||||
database,
|
||||
cost_model,
|
||||
)
|
||||
|
||||
def terminate_task(self, task_id: int) -> None:
|
||||
"""Terminate the task
|
||||
|
||||
Parameters
|
||||
----------
|
||||
task_id : int
|
||||
The task id to be terminated.
|
||||
"""
|
||||
_ffi_api.TaskSchedulerTerminateTask(self, task_id) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def touch_task(self, task_id: int) -> None:
|
||||
"""Touch the task and update its status
|
||||
|
||||
Parameters
|
||||
----------
|
||||
task_id : int
|
||||
The task id to be checked.
|
||||
"""
|
||||
_ffi_api.TaskSchedulerTouchTask(self, task_id) # type: ignore # pylint: disable=no-member
|
||||
|
||||
def print_tuning_statistics(self) -> None:
|
||||
"""Print out a human-readable format of the tuning statistics."""
|
||||
return _ffi_api.TaskSchedulerPrintTuningStatistics(self) # type: ignore # pylint: disable=no-member
|
||||
|
||||
@staticmethod
|
||||
def create( # pylint: disable=keyword-arg-before-vararg
|
||||
kind: Literal["round-robin", "gradient"] = "gradient",
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> "TaskScheduler":
|
||||
"""Create a task scheduler."""
|
||||
from . import ( # pylint: disable=import-outside-toplevel
|
||||
GradientBased,
|
||||
RoundRobin,
|
||||
)
|
||||
|
||||
if kind == "round-robin":
|
||||
return RoundRobin(*args, **kwargs) # type: ignore
|
||||
if kind == "gradient":
|
||||
return GradientBased(*args, **kwargs)
|
||||
raise ValueError(f"Unknown TaskScheduler name: {kind}")
|
||||
|
||||
|
||||
create = TaskScheduler.create # pylint: disable=invalid-name
|
||||
|
||||
|
||||
@register_object("s_tir.meta_schedule.PyTaskScheduler")
|
||||
class _PyTaskScheduler(TaskScheduler):
|
||||
"""
|
||||
A TVM object task scheduler to support customization on the python side.
|
||||
This is NOT the user facing class for function overloading inheritance.
|
||||
|
||||
See also: PyTaskScheduler
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
f_next_task_id: Callable,
|
||||
f_join_running_task: Callable,
|
||||
f_tune: Callable,
|
||||
):
|
||||
"""Constructor."""
|
||||
|
||||
self.__init_handle_by_constructor__(
|
||||
_ffi_api.TaskSchedulerPyTaskScheduler, # type: ignore # pylint: disable=no-member
|
||||
get_logging_func(logger),
|
||||
f_next_task_id,
|
||||
f_join_running_task,
|
||||
f_tune,
|
||||
)
|
||||
|
||||
|
||||
class PyTaskScheduler:
|
||||
"""
|
||||
An abstract task scheduler with customized methods 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": _PyTaskScheduler,
|
||||
"fields": [],
|
||||
"methods": ["next_task_id", "join_running_task", "tune"],
|
||||
}
|
||||
|
||||
def __init__(self): ...
|
||||
|
||||
def tune(
|
||||
self,
|
||||
tasks: list[TuneContext],
|
||||
task_weights: list[float],
|
||||
max_trials_global: int,
|
||||
max_trials_per_task: int,
|
||||
builder: Builder,
|
||||
runner: Runner,
|
||||
measure_callbacks: list[MeasureCallback],
|
||||
database: Database | None,
|
||||
cost_model: CostModel | None,
|
||||
) -> None:
|
||||
"""Auto-tuning."""
|
||||
# Using self._outer to replace the self pointer
|
||||
_ffi_api.TaskSchedulerTune( # type: ignore # pylint: disable=no-member
|
||||
self._outer(), # type: ignore # pylint: disable=no-member
|
||||
tasks,
|
||||
task_weights,
|
||||
max_trials_global,
|
||||
max_trials_per_task,
|
||||
builder,
|
||||
runner,
|
||||
measure_callbacks,
|
||||
database,
|
||||
cost_model,
|
||||
)
|
||||
|
||||
def next_task_id(self) -> int:
|
||||
"""Fetch the next task id.
|
||||
|
||||
Returns
|
||||
-------
|
||||
next_task_id : int
|
||||
The next task id.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def join_running_task(self, task_id: int) -> list[RunnerResult]:
|
||||
"""Wait until the task is finished.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
task_id : int
|
||||
The task id to be joined.
|
||||
"""
|
||||
# Using self._outer to replace the self pointer
|
||||
return _ffi_api.TaskSchedulerJoinRunningTask(self._outer(), task_id) # type: ignore # pylint: disable=no-member
|
||||
@@ -0,0 +1,19 @@
|
||||
# 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.
|
||||
"""Testing utilities in meta schedule"""
|
||||
|
||||
# NOTE: Do not import any module here by default
|
||||
@@ -0,0 +1,54 @@
|
||||
# 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.
|
||||
"""Customized builder and runner methods"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
import numpy as np # type: ignore
|
||||
|
||||
from tvm.runtime import Executable, Module
|
||||
from tvm.s_tir.meta_schedule.runner import RPCConfig
|
||||
|
||||
|
||||
def run_module_via_rpc(
|
||||
rpc_config: RPCConfig,
|
||||
lib: Module | Executable,
|
||||
dev_type: str,
|
||||
args: dict[int, np.ndarray] | dict[str, np.ndarray],
|
||||
continuation: Callable,
|
||||
):
|
||||
"""Execute a tvm.runtime.Module on RPC remote"""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from tvm.runtime import ndarray
|
||||
from tvm.support.tar import tar
|
||||
|
||||
# pylint: enable=import-outside-toplevel
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
filename = os.path.join(tmp_dir, "tvm_tmp_mod." + tar.output_format)
|
||||
lib.export_library(filename, fcompile=tar)
|
||||
session = rpc_config.connect_server()
|
||||
session.upload(filename)
|
||||
_, filename = os.path.split(filename)
|
||||
rt_mod = session.load_module(filename)
|
||||
dev = session.device(dev_type, 0)
|
||||
nd_args = {k: ndarray.array(v, dev) for k, v in args.items()}
|
||||
return continuation(rt_mod, dev, nd_args)
|
||||
@@ -0,0 +1,199 @@
|
||||
# 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.
|
||||
# pylint: disable=missing-docstring
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
|
||||
from tqdm import tqdm # type: ignore
|
||||
|
||||
from tvm.s_tir import meta_schedule as ms
|
||||
from tvm.target import Target
|
||||
|
||||
|
||||
def _parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--candidate_cache_dir", type=str, help="Please provide the full path to the candidates."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--result_cache_dir", type=str, help="Please provide the full path to the result database."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--target",
|
||||
type=str,
|
||||
default="nvidia/nvidia-v100",
|
||||
help="Please specify the target hardware for tuning context.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rpc_host", type=str, help="Please provide the private IPv4 address for the tracker."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rpc_port", type=int, default=4445, help="Please provide the port for the tracker."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rpc_key",
|
||||
type=str,
|
||||
default="p3.2xlarge",
|
||||
help="Please provide the key for the rpc servers.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--builder_timeout_sec",
|
||||
type=int,
|
||||
default=10,
|
||||
help="The time for the builder session to time out.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--min_repeat_ms", type=int, default=100, help="The time for preheating the gpu."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--runner_timeout_sec",
|
||||
type=int,
|
||||
default=100,
|
||||
help="The time for the runner session to time out.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cpu_flush", type=bool, default=False, help="Whether to enable cpu cache flush or not."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--batch_size",
|
||||
type=int,
|
||||
default=128,
|
||||
help="The batch size of candidates sent to builder and runner each time.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
# pylint: disable=too-many-locals
|
||||
def measure_candidates(database, builder, runner):
|
||||
"""Send the candidates to builder and runner for distributed measurement,
|
||||
and save the results in a new json database.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
database : JSONDatabase
|
||||
The database for candidates to be measured.
|
||||
builder : Builder
|
||||
The builder for building the candidates.
|
||||
runner : Runner
|
||||
The runner for measuring the candidates.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
candidates, runner_results, build_fail_indices, run_fail_indices = [], [], [], []
|
||||
context = ms.TuneContext(target=Target(args.target))
|
||||
tuning_records = database.get_all_tuning_records()
|
||||
for record in tuning_records:
|
||||
candidates.append(record.as_measure_candidate())
|
||||
with ms.Profiler() as profiler:
|
||||
for idx in range(0, len(candidates), args.batch_size):
|
||||
batch_candidates = candidates[idx : idx + args.batch_size]
|
||||
context._set_measure_candidates(batch_candidates) # pylint: disable=protected-access
|
||||
with ms.Profiler.timeit("build"):
|
||||
context._send_to_builder(builder) # pylint: disable=protected-access
|
||||
with ms.Profiler.timeit("run"):
|
||||
context._send_to_runner(runner) # pylint: disable=protected-access
|
||||
batch_runner_results = context._join() # pylint: disable=protected-access
|
||||
runner_results.extend(batch_runner_results)
|
||||
for i, result in enumerate(context.builder_results):
|
||||
if result.error_msg is None:
|
||||
ms.utils.remove_build_dir(result.artifact_path)
|
||||
else:
|
||||
build_fail_indices.append(i + idx)
|
||||
context._clear_measure_state() # pylint: disable=protected-access
|
||||
|
||||
model_name, workload_name = database.path_workload.split("/")[-2:]
|
||||
record_name = database.path_tuning_record.split("/")[-1]
|
||||
new_database = ms.database.JSONDatabase(
|
||||
path_workload=os.path.join(args.result_cache_dir, model_name, workload_name),
|
||||
path_tuning_record=os.path.join(args.result_cache_dir, model_name, record_name),
|
||||
)
|
||||
workload = tuning_records[0].workload
|
||||
new_database.commit_workload(workload.mod)
|
||||
for i, (record, result) in enumerate(zip(tuning_records, runner_results)):
|
||||
if result.error_msg is None:
|
||||
new_database.commit_tuning_record(
|
||||
ms.database.TuningRecord(
|
||||
trace=record.trace,
|
||||
workload=workload,
|
||||
run_secs=[v.value for v in result.run_secs],
|
||||
target=Target(args.target),
|
||||
)
|
||||
)
|
||||
else:
|
||||
run_fail_indices.append(i)
|
||||
fail_indices_name = workload_name.replace("_workload.json", "_failed_indices.txt")
|
||||
with open(
|
||||
os.path.join(args.result_cache_dir, model_name, fail_indices_name), "w", encoding="utf8"
|
||||
) as file:
|
||||
file.write(" ".join([str(n) for n in run_fail_indices]))
|
||||
print(
|
||||
f"Builder time: {profiler.get()['build']}, Runner time: {profiler.get()['run']}\n\
|
||||
Failed number of builds: {len(build_fail_indices)},\
|
||||
Failed number of runs: {len(run_fail_indices)}"
|
||||
)
|
||||
|
||||
|
||||
args = _parse_args() # pylint: disable=invalid-name
|
||||
|
||||
|
||||
def main():
|
||||
builder = ms.builder.LocalBuilder(timeout_sec=args.builder_timeout_sec)
|
||||
runner = ms.runner.RPCRunner(
|
||||
rpc_config=ms.runner.RPCConfig(
|
||||
tracker_host=args.rpc_host,
|
||||
tracker_port=args.rpc_port,
|
||||
tracker_key=args.rpc_key,
|
||||
session_timeout_sec=args.runner_timeout_sec,
|
||||
),
|
||||
evaluator_config=ms.runner.EvaluatorConfig(
|
||||
number=3,
|
||||
repeat=1,
|
||||
min_repeat_ms=args.min_repeat_ms,
|
||||
enable_cpu_cache_flush=args.cpu_flush,
|
||||
),
|
||||
max_workers=os.cpu_count(),
|
||||
)
|
||||
if not os.path.isdir(args.candidate_cache_dir):
|
||||
raise Exception("Please provide a correct candidate cache dir.")
|
||||
try:
|
||||
os.makedirs(args.result_cache_dir, exist_ok=True)
|
||||
except OSError:
|
||||
print(f"Directory {args.result_cache_dir} cannot be created successfully.")
|
||||
model_dirs = glob.glob(os.path.join(args.candidate_cache_dir, "*"))
|
||||
for model_dir in model_dirs:
|
||||
model_name = model_dir.split("/")[-1]
|
||||
os.makedirs(os.path.join(args.result_cache_dir, model_name), exist_ok=True)
|
||||
all_tasks = glob.glob(os.path.join(model_dir, "*.json"))
|
||||
workload_paths = []
|
||||
for path in all_tasks:
|
||||
if path.endswith("_workload.json"):
|
||||
workload_paths.append(path)
|
||||
for workload_path in tqdm(workload_paths):
|
||||
candidate_path = workload_path.replace("_workload.json", "_candidates.json")
|
||||
database = ms.database.JSONDatabase(
|
||||
path_workload=workload_path,
|
||||
path_tuning_record=candidate_path,
|
||||
)
|
||||
measure_candidates(database, builder, runner)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,63 @@
|
||||
# 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.
|
||||
"""Dummy objects for testing."""
|
||||
|
||||
import random
|
||||
|
||||
from tvm.ir.utils import derived_object
|
||||
from tvm.s_tir.schedule import Trace
|
||||
|
||||
from ..builder import BuilderInput, BuilderResult, PyBuilder
|
||||
from ..mutator import PyMutator
|
||||
from ..runner import PyRunner, PyRunnerFuture, RunnerFuture, RunnerInput, RunnerResult
|
||||
from ..tune_context import TuneContext # pylint: disable=unused-import
|
||||
|
||||
|
||||
@derived_object
|
||||
class DummyRunnerFuture(PyRunnerFuture):
|
||||
def done(self) -> bool:
|
||||
return True
|
||||
|
||||
def result(self) -> RunnerResult:
|
||||
run_secs = [random.uniform(5, 30) for _ in range(random.randint(1, 10))]
|
||||
return RunnerResult(run_secs, None)
|
||||
|
||||
|
||||
@derived_object
|
||||
class DummyBuilder(PyBuilder):
|
||||
def build(self, build_inputs: list[BuilderInput]) -> list[BuilderResult]:
|
||||
return [BuilderResult("test_path", None) for _ in build_inputs]
|
||||
|
||||
|
||||
@derived_object
|
||||
class DummyRunner(PyRunner):
|
||||
def run(self, runner_inputs: list[RunnerInput]) -> list[RunnerFuture]:
|
||||
return [DummyRunnerFuture() for _ in runner_inputs] # type: ignore
|
||||
|
||||
|
||||
@derived_object
|
||||
class DummyMutator(PyMutator):
|
||||
"""Dummy Mutator for testing"""
|
||||
|
||||
def _initialize_with_tune_context(self, context: "TuneContext") -> None:
|
||||
pass
|
||||
|
||||
def apply(self, trace: Trace, _) -> Trace | None:
|
||||
return Trace(trace.insts, {})
|
||||
|
||||
def clone(self):
|
||||
return DummyMutator()
|
||||
@@ -0,0 +1,72 @@
|
||||
# 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 tracker and server running locally"""
|
||||
|
||||
from tvm.rpc.server import Server
|
||||
from tvm.rpc.tracker import Tracker
|
||||
|
||||
|
||||
class LocalRPC:
|
||||
"""A pair of RPC tracker/server running locally
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tracker_host : str
|
||||
The host URL of the tracker
|
||||
tracker_port : int
|
||||
The port of the tracker
|
||||
tracker_key: str
|
||||
The key used in the tracker to refer to a worker
|
||||
"""
|
||||
|
||||
tracker_host: str
|
||||
tracker_port: int
|
||||
tracker_key: str
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tracker_key: str = "key",
|
||||
silent: bool = False,
|
||||
no_fork: bool = False,
|
||||
) -> None:
|
||||
self.tracker = Tracker(
|
||||
silent=silent,
|
||||
port=9190,
|
||||
port_end=12345,
|
||||
)
|
||||
self.server = Server(
|
||||
host="0.0.0.0",
|
||||
is_proxy=False,
|
||||
tracker_addr=(self.tracker.host, self.tracker.port),
|
||||
key=tracker_key,
|
||||
silent=silent,
|
||||
no_fork=no_fork,
|
||||
port=9190,
|
||||
port_end=12345,
|
||||
)
|
||||
self.tracker_host = self.tracker.host
|
||||
self.tracker_port = self.tracker.port
|
||||
self.tracker_key = tracker_key
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, _type, _value, _traceback):
|
||||
if hasattr(self, "server"):
|
||||
del self.server
|
||||
if hasattr(self, "tracker"):
|
||||
del self.tracker
|
||||
@@ -0,0 +1,150 @@
|
||||
# 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.
|
||||
# pylint: disable=missing-module-docstring,missing-function-docstring,missing-class-docstring
|
||||
|
||||
# isort: off
|
||||
from typing import Literal
|
||||
|
||||
# isort: on
|
||||
|
||||
import tvm_ffi
|
||||
|
||||
from tvm.ir import IRModule
|
||||
from tvm.s_tir import Schedule
|
||||
from tvm.s_tir import meta_schedule as ms
|
||||
from tvm.s_tir.schedule import Trace
|
||||
from tvm.s_tir.schedule.testing import verify_trace_roundtrip
|
||||
from tvm.target import Target
|
||||
|
||||
|
||||
def get_rules(
|
||||
kind: Literal["llvm", "cuda", "cuda-tensorcore", "hexagon"],
|
||||
types: type | tuple[type, ...],
|
||||
) -> list[ms.ScheduleRule]:
|
||||
"""Get default schedule rules"""
|
||||
rules = ms.ScheduleRule.create(kind)
|
||||
return [rule for rule in rules if isinstance(rule, types)]
|
||||
|
||||
|
||||
def structural_equal_no_gs(mod1: IRModule, mod2: IRModule) -> bool:
|
||||
"""
|
||||
Checks structural equality but ignores global symbols
|
||||
"""
|
||||
|
||||
# for every function in the modules, remove global symbols from the attrs and then compare
|
||||
def remove_global_symbols(mod: IRModule) -> IRModule:
|
||||
stripped_mod = IRModule()
|
||||
for global_var in mod.get_global_vars():
|
||||
func = mod[global_var]
|
||||
stripped_mod[global_var] = func.without_attr("global_symbol")
|
||||
return stripped_mod
|
||||
|
||||
return tvm_ffi.structural_equal(remove_global_symbols(mod1), remove_global_symbols(mod2))
|
||||
|
||||
|
||||
def generate_design_space(
|
||||
kind: Literal["llvm", "cuda", "cuda-tensorcore", "hexagon"],
|
||||
mod: IRModule,
|
||||
target: Target,
|
||||
types: type | tuple[type, ...],
|
||||
sch_rules: list[ms.ScheduleRule] | None = None,
|
||||
) -> list[Schedule]:
|
||||
if sch_rules is None:
|
||||
sch_rules = get_rules(kind, types)
|
||||
else:
|
||||
assert types is None
|
||||
return ms.TuneContext(
|
||||
mod=mod,
|
||||
target=target,
|
||||
space_generator=ms.space_generator.PostOrderApply(
|
||||
sch_rules=sch_rules,
|
||||
postprocs=[],
|
||||
mutator_probs={},
|
||||
),
|
||||
task_name="test",
|
||||
).generate_design_space()
|
||||
|
||||
|
||||
def _find_match_sketch_id(
|
||||
mod: IRModule,
|
||||
sketches: list[Schedule],
|
||||
expected_mod: IRModule,
|
||||
expected_decision: list[tuple[str, list[int]]],
|
||||
*,
|
||||
debug_mask="all",
|
||||
) -> int | None:
|
||||
for sketch_id, sketch in enumerate(sketches):
|
||||
i = 0
|
||||
new_decisions = {}
|
||||
for inst in sketch.trace.insts:
|
||||
if not inst.kind.name.startswith("Sample"):
|
||||
continue
|
||||
assert i < len(expected_decision)
|
||||
if inst.kind.name == expected_decision[i][0]:
|
||||
new_decisions[inst] = expected_decision[i][1]
|
||||
i += 1
|
||||
if len(new_decisions) != len(expected_decision):
|
||||
continue
|
||||
sch = Schedule(mod, debug_mask=debug_mask)
|
||||
Trace(
|
||||
insts=sketch.trace.insts,
|
||||
decisions=new_decisions,
|
||||
).apply_to_schedule(sch, remove_postproc=True)
|
||||
if structural_equal_no_gs(sch.mod, expected_mod):
|
||||
verify_trace_roundtrip(sch=sch, mod=mod, debug_mask=debug_mask, text_format="json")
|
||||
return sketch_id
|
||||
return None
|
||||
|
||||
|
||||
def check_sketches(
|
||||
mod: IRModule,
|
||||
sketches: list[Schedule],
|
||||
expected_mods: list[IRModule],
|
||||
expected_decisions: list[list[tuple[str, list[int]]]],
|
||||
*,
|
||||
debug_mask="all",
|
||||
):
|
||||
assert len(expected_mods) == len(expected_decisions)
|
||||
assert len(sketches) == len(expected_mods)
|
||||
expected_mods = [
|
||||
IRModule({"main": m}) if not isinstance(m, IRModule) else m for m in expected_mods
|
||||
]
|
||||
sketches = list(sketches)
|
||||
for expected_id, (expected_mod, expected_decision) in enumerate(
|
||||
zip(expected_mods, expected_decisions)
|
||||
):
|
||||
sketch_id = _find_match_sketch_id(
|
||||
mod,
|
||||
sketches,
|
||||
expected_mod,
|
||||
expected_decision,
|
||||
debug_mask=debug_mask,
|
||||
)
|
||||
if sketch_id is None:
|
||||
raise AssertionError(
|
||||
f"Expected sketch #{expected_id} doesn't exist in the generated sketches."
|
||||
)
|
||||
sketches.pop(sketch_id)
|
||||
|
||||
|
||||
def print_sketches(sketches: list[Schedule]):
|
||||
for i, sch in enumerate(sketches):
|
||||
print(f"###### {i}")
|
||||
sch.mod.show(black_format=False)
|
||||
for inst in sch.trace.insts:
|
||||
if inst in sch.trace.decisions:
|
||||
print(f'("{inst.kind.name}", {sch.trace.decisions[inst]}),')
|
||||
@@ -0,0 +1,837 @@
|
||||
# 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: E741
|
||||
"""Workloads in TE"""
|
||||
|
||||
# pylint: disable=missing-docstring
|
||||
|
||||
from tvm import te, tirx, topi
|
||||
from tvm.target import Target
|
||||
|
||||
|
||||
def batch_matmul_nkkm( # pylint: disable=invalid-name,missing-docstring
|
||||
B: int,
|
||||
N: int,
|
||||
M: int,
|
||||
K: int,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
x = te.placeholder((B, N, K), name="X", dtype=in_dtype)
|
||||
y = te.placeholder((B, K, M), name="Y", dtype=in_dtype)
|
||||
k = te.reduce_axis((0, K), name="k")
|
||||
z = te.compute( # pylint: disable=invalid-name
|
||||
(B, N, M),
|
||||
lambda b, i, j: te.sum(
|
||||
x[b][i][k].astype(out_dtype) * y[b][k][j].astype(out_dtype),
|
||||
axis=[k],
|
||||
),
|
||||
name="Z",
|
||||
)
|
||||
return (x, y, z)
|
||||
|
||||
|
||||
def conv1d_nlc( # pylint: disable=invalid-name,missing-docstring
|
||||
N: int,
|
||||
L: int,
|
||||
CI: int,
|
||||
CO: int,
|
||||
kernel_size: int,
|
||||
stride: int = 1,
|
||||
padding: int = 0,
|
||||
dilation: int = 1,
|
||||
groups: int = 1,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
inputs = te.placeholder((N, L, CI), name="inputs", dtype=in_dtype)
|
||||
weight = te.placeholder((kernel_size, CI // groups, CO), name="weight", dtype=in_dtype)
|
||||
|
||||
batch_size, in_len, _ = inputs.shape
|
||||
k_len, channel_per_group, out_channel = weight.shape
|
||||
out_channel_per_group = out_channel // groups
|
||||
out_len = (in_len + 2 * padding - dilation * (k_len - 1) - 1) // stride + 1
|
||||
rc = te.reduce_axis((0, channel_per_group), name="rc")
|
||||
rl = te.reduce_axis((0, k_len), name="rl")
|
||||
|
||||
padded = topi.nn.pad(inputs, [0, padding, 0])
|
||||
output = te.compute(
|
||||
(batch_size, out_len, out_channel),
|
||||
lambda n, l, co: te.sum(
|
||||
(
|
||||
padded[
|
||||
n,
|
||||
l * stride + rl * dilation,
|
||||
co // out_channel_per_group * channel_per_group + rc,
|
||||
].astype(out_dtype)
|
||||
* weight[rl, rc, co].astype(out_dtype)
|
||||
),
|
||||
axis=[rl, rc],
|
||||
),
|
||||
name="conv1d_nlc",
|
||||
)
|
||||
return (inputs, weight, output)
|
||||
|
||||
|
||||
def conv2d_nhwc( # pylint: disable=invalid-name,missing-docstring
|
||||
N: int,
|
||||
H: int,
|
||||
W: int,
|
||||
CI: int,
|
||||
CO: int,
|
||||
kernel_size: int,
|
||||
stride: int = 1,
|
||||
padding: int = 0,
|
||||
dilation: int = 1,
|
||||
groups: int = 1,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
inputs = te.placeholder((N, H, W, CI), name="inputs", dtype=in_dtype)
|
||||
weight = te.placeholder(
|
||||
(kernel_size, kernel_size, CI // groups, CO), name="weight", dtype=in_dtype
|
||||
)
|
||||
batch_size, in_h, in_w, _ = inputs.shape
|
||||
k_h, k_w, channel_per_group, out_channel = weight.shape
|
||||
out_channel_per_group = out_channel // groups
|
||||
|
||||
out_h = (in_h + 2 * padding - dilation * (k_h - 1) - 1) // stride + 1
|
||||
out_w = (in_w + 2 * padding - dilation * (k_w - 1) - 1) // stride + 1
|
||||
rh = te.reduce_axis((0, k_h), name="rh")
|
||||
rw = te.reduce_axis((0, k_w), name="rw")
|
||||
rc = te.reduce_axis((0, channel_per_group), name="rc")
|
||||
|
||||
padded = topi.nn.pad(inputs, [0, padding, padding, 0])
|
||||
output = te.compute(
|
||||
(batch_size, out_h, out_w, out_channel),
|
||||
lambda n, h, w, co: te.sum(
|
||||
(
|
||||
padded[
|
||||
n,
|
||||
h * stride + rh * dilation,
|
||||
w * stride + rw * dilation,
|
||||
co // out_channel_per_group * channel_per_group + rc,
|
||||
].astype(out_dtype)
|
||||
* weight[rh, rw, rc, co].astype(out_dtype)
|
||||
),
|
||||
axis=[rh, rw, rc],
|
||||
),
|
||||
name="conv2d_nhwc",
|
||||
)
|
||||
return (inputs, weight, output)
|
||||
|
||||
|
||||
def conv3d_ndhwc( # pylint: disable=invalid-name,missing-docstring
|
||||
N: int,
|
||||
D: int,
|
||||
H: int,
|
||||
W: int,
|
||||
CI: int,
|
||||
CO: int,
|
||||
kernel_size: int,
|
||||
stride: int = 1,
|
||||
padding: int = 0,
|
||||
dilation: int = 1,
|
||||
groups: int = 1,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
inputs = te.placeholder((N, D, H, W, CI), name="inputs", dtype=in_dtype)
|
||||
weight = te.placeholder(
|
||||
(kernel_size, kernel_size, kernel_size, CI // groups, CO), name="weight", dtype=in_dtype
|
||||
)
|
||||
batch_size, in_d, in_h, in_w, _ = inputs.shape
|
||||
k_d, k_h, k_w, channel_per_group, out_channel = weight.shape
|
||||
out_channel_per_group = out_channel // groups
|
||||
|
||||
out_d = (in_d + 2 * padding - dilation * (k_d - 1) - 1) // stride + 1
|
||||
out_h = (in_h + 2 * padding - dilation * (k_h - 1) - 1) // stride + 1
|
||||
out_w = (in_w + 2 * padding - dilation * (k_w - 1) - 1) // stride + 1
|
||||
rd = te.reduce_axis((0, k_d), name="rd")
|
||||
rh = te.reduce_axis((0, k_h), name="rh")
|
||||
rw = te.reduce_axis((0, k_w), name="rw")
|
||||
rc = te.reduce_axis((0, channel_per_group), name="rc")
|
||||
|
||||
padded = topi.nn.pad(inputs, [0, padding, padding, padding, 0])
|
||||
output = te.compute(
|
||||
(batch_size, out_d, out_h, out_w, out_channel),
|
||||
lambda n, d, h, w, co: te.sum(
|
||||
(
|
||||
padded[
|
||||
n,
|
||||
d * stride + rd * dilation,
|
||||
h * stride + rh * dilation,
|
||||
w * stride + rw * dilation,
|
||||
co // out_channel_per_group * channel_per_group + rc,
|
||||
].astype(out_dtype)
|
||||
* weight[rd, rh, rw, rc, co].astype(out_dtype)
|
||||
),
|
||||
axis=[rd, rh, rw, rc],
|
||||
),
|
||||
name="conv3d_ndhwc",
|
||||
)
|
||||
return (inputs, weight, output)
|
||||
|
||||
|
||||
def depthwise_conv2d_nhwc( # pylint: disable=invalid-name,missing-docstring
|
||||
N: int,
|
||||
H: int,
|
||||
W: int,
|
||||
C: int,
|
||||
kernel_size: int,
|
||||
stride: int = 1,
|
||||
padding: int = 0,
|
||||
dilation: int = 1,
|
||||
factor: int = 1,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
inputs = te.placeholder((N, H, W, C), dtype=in_dtype)
|
||||
weight = te.placeholder((factor, kernel_size, kernel_size, C), dtype=in_dtype)
|
||||
batch_size, in_h, in_w, in_channel = inputs.shape
|
||||
factor, k_h, k_w, in_channel = weight.shape
|
||||
out_channel = in_channel * factor
|
||||
assert int(factor) == 1, "Not optimized for factor != 1"
|
||||
out_h = (in_h + 2 * padding - dilation * (k_h - 1) - 1) // stride + 1
|
||||
out_w = (in_w + 2 * padding - dilation * (k_w - 1) - 1) // stride + 1
|
||||
rh = te.reduce_axis((0, k_h), name="rh")
|
||||
rw = te.reduce_axis((0, k_w), name="rw")
|
||||
padded = topi.nn.pad(inputs, [0, padding, padding, 0])
|
||||
output = te.compute(
|
||||
(batch_size, out_h, out_w, out_channel),
|
||||
lambda n, h, w, c: te.sum(
|
||||
(
|
||||
padded[
|
||||
n,
|
||||
h * stride + rh * dilation,
|
||||
w * stride + rw * dilation,
|
||||
c // factor,
|
||||
].astype(out_dtype)
|
||||
* weight[c % factor, rh, rw, c // factor].astype(out_dtype)
|
||||
),
|
||||
axis=[rh, rw],
|
||||
),
|
||||
name="depth_conv2d_nhwc",
|
||||
)
|
||||
return (inputs, weight, output)
|
||||
|
||||
|
||||
def conv2d_transpose_nhwc( # pylint: disable=invalid-name,missing-docstring
|
||||
N: int,
|
||||
H: int,
|
||||
W: int,
|
||||
CI: int,
|
||||
CO: int,
|
||||
kernel_size: int,
|
||||
stride: int = 1,
|
||||
padding: int = 0,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
inputs = te.placeholder((N, H, W, CI), name="inputs", dtype=in_dtype)
|
||||
weight = te.placeholder((kernel_size, kernel_size, CI, CO), name="weight", dtype=in_dtype)
|
||||
|
||||
batch, in_h, in_w, in_c = inputs.shape
|
||||
filter_h, filter_w, in_c, out_c = weight.shape
|
||||
stride_h, stride_w = (stride, stride)
|
||||
|
||||
# compute padding
|
||||
fpad_top, fpad_left, fpad_bottom, fpad_right = topi.nn.get_pad_tuple(
|
||||
padding, (filter_h, filter_w)
|
||||
)
|
||||
bpad_top = filter_h - 1 - fpad_top
|
||||
bpad_bottom = filter_h - 1 - fpad_bottom
|
||||
bpad_left = filter_w - 1 - fpad_left
|
||||
bpad_right = filter_w - 1 - fpad_right
|
||||
|
||||
# padding stage
|
||||
padded = topi.nn.pad(
|
||||
inputs,
|
||||
[
|
||||
0,
|
||||
(bpad_top + stride_h - 1) // stride_h,
|
||||
(bpad_left + stride_w - 1) // stride_w,
|
||||
0,
|
||||
],
|
||||
[
|
||||
0,
|
||||
(bpad_bottom + stride_h - 1) // stride_h,
|
||||
(bpad_right + stride_w - 1) // stride_w,
|
||||
0,
|
||||
],
|
||||
)
|
||||
|
||||
# remove extra padding introduced by dilatation
|
||||
idx_div = te.indexdiv
|
||||
idx_mod = te.indexmod
|
||||
border_h = idx_mod(stride_h - idx_mod(bpad_top, stride_h), stride_h)
|
||||
border_w = idx_mod(stride_w - idx_mod(bpad_left, stride_w), stride_w)
|
||||
|
||||
# dilation stage
|
||||
strides = [1, stride_h, stride_w, 1]
|
||||
n = len(padded.shape)
|
||||
|
||||
# We should embed this dilation directly into te.compute rather than creating a new te.compute.
|
||||
# Only in this way can we use unroll to eliminate the multiplication of zeros.
|
||||
def _dilate(*indices):
|
||||
not_zero = []
|
||||
index_tuple = []
|
||||
for i in range(n):
|
||||
if not strides[i] == 1:
|
||||
index_tuple.append(idx_div(indices[i], strides[i]))
|
||||
not_zero.append(idx_mod(indices[i], strides[i]).equal(0))
|
||||
else:
|
||||
index_tuple.append(indices[i])
|
||||
if not_zero:
|
||||
not_zero = te.all(*not_zero)
|
||||
return te.if_then_else(not_zero, padded(*index_tuple), tirx.const(0.0, padded.dtype))
|
||||
return padded(*index_tuple)
|
||||
|
||||
# convolution stage
|
||||
out_h = (in_h - 1) * stride_h - fpad_top - fpad_bottom + filter_h
|
||||
out_w = (in_w - 1) * stride_w - fpad_left - fpad_right + filter_w
|
||||
rc = te.reduce_axis((0, in_c), name="rc")
|
||||
rh = te.reduce_axis((0, filter_h), name="rh")
|
||||
rw = te.reduce_axis((0, filter_w), name="rw")
|
||||
|
||||
output = te.compute(
|
||||
(batch, out_h, out_w, out_c),
|
||||
lambda n, h, w, co: te.sum(
|
||||
_dilate(n, h + rh + border_h, w + rw + border_w, rc).astype(out_dtype)
|
||||
* weight[filter_h - 1 - rh, filter_w - 1 - rw, rc, co].astype(out_dtype),
|
||||
axis=[rh, rw, rc],
|
||||
),
|
||||
name="conv2d_transpose_nhwc",
|
||||
)
|
||||
return (inputs, weight, output)
|
||||
|
||||
|
||||
def conv2d_capsule_nhwijc( # pylint: disable=invalid-name,missing-docstring
|
||||
N: int,
|
||||
H: int,
|
||||
W: int,
|
||||
CI: int,
|
||||
CO: int,
|
||||
kernel_size: int,
|
||||
stride: int = 1,
|
||||
padding: int = 0,
|
||||
capsule_size: int = 4,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
inputs = te.placeholder(
|
||||
(N, H, W, capsule_size, capsule_size, CI), name="inputs", dtype=in_dtype
|
||||
)
|
||||
weight = te.placeholder(
|
||||
(kernel_size, kernel_size, capsule_size, capsule_size, CI, CO),
|
||||
name="weight",
|
||||
dtype=in_dtype,
|
||||
)
|
||||
batch_size, in_h, in_w, _, _, in_channel = inputs.shape
|
||||
k_h, k_w, _, _, _, out_channel = weight.shape
|
||||
|
||||
out_h = (in_h + 2 * padding - kernel_size) // stride + 1
|
||||
out_w = (in_w + 2 * padding - kernel_size) // stride + 1
|
||||
|
||||
rh = te.reduce_axis((0, k_h), name="rh")
|
||||
rw = te.reduce_axis((0, k_w), name="rw")
|
||||
cap_k = te.reduce_axis((0, capsule_size), name="cap_k")
|
||||
rc = te.reduce_axis((0, in_channel), name="rc")
|
||||
|
||||
padded = topi.nn.pad(inputs, [0, padding, padding, 0, 0, 0])
|
||||
output = te.compute(
|
||||
(batch_size, out_h, out_w, capsule_size, capsule_size, out_channel),
|
||||
lambda n, h, w, cap_i, cap_j, co: te.sum(
|
||||
(
|
||||
padded[n, h * stride + rh, w * stride + rw, cap_i, cap_k, rc].astype(out_dtype)
|
||||
* weight[rh, rw, cap_k, cap_j, rc, co].astype(out_dtype)
|
||||
),
|
||||
axis=[rh, rw, cap_k, rc],
|
||||
),
|
||||
name="conv2d_capsule_nhwijc",
|
||||
)
|
||||
return (inputs, weight, output)
|
||||
|
||||
|
||||
def norm_bmn( # pylint: disable=invalid-name,missing-docstring
|
||||
B: int,
|
||||
M: int,
|
||||
N: int,
|
||||
) -> tuple[te.Tensor, te.Tensor]:
|
||||
a = te.placeholder((B, M, N), name="A")
|
||||
i = te.reduce_axis((0, M), name="i")
|
||||
j = te.reduce_axis((0, N), name="j")
|
||||
c = te.compute(
|
||||
(B,),
|
||||
lambda b: te.sum(a[b][i][j] * a[b][i][j], axis=[i, j]),
|
||||
name="C",
|
||||
)
|
||||
d = te.compute((B,), lambda b: te.sqrt(c[b]), name="D")
|
||||
return (a, d)
|
||||
|
||||
|
||||
def conv2d_nhwc_without_layout_rewrite( # pylint: disable=invalid-name
|
||||
Input: te.Tensor,
|
||||
Filter: te.Tensor,
|
||||
stride: int,
|
||||
padding: int,
|
||||
dilation: int,
|
||||
out_dtype="float32",
|
||||
):
|
||||
"""A copy of `topi.nn.conv2d_nhwc` but without the 'layout_free` attribute.
|
||||
We use this in single op and subgraph evaluation
|
||||
because we don't want to introduce graph level optimization.
|
||||
"""
|
||||
assert isinstance(stride, int) or len(stride) == 2
|
||||
assert isinstance(dilation, int) or len(dilation) == 2
|
||||
|
||||
if isinstance(stride, int):
|
||||
stride_h = stride_w = stride
|
||||
else:
|
||||
stride_h, stride_w = stride
|
||||
|
||||
if isinstance(dilation, int):
|
||||
dilation_h = dilation_w = dilation
|
||||
else:
|
||||
dilation_h, dilation_w = dilation
|
||||
|
||||
batch, in_height, in_width, in_channel = Input.shape # type: ignore
|
||||
kernel_h, kernel_w, _channel, num_filter = Filter.shape # type: ignore
|
||||
|
||||
# compute the output shape
|
||||
dilated_kernel_h = (kernel_h - 1) * dilation_h + 1
|
||||
dilated_kernel_w = (kernel_w - 1) * dilation_w + 1
|
||||
pad_top, pad_left, pad_down, pad_right = topi.nn.get_pad_tuple(
|
||||
padding, (dilated_kernel_h, dilated_kernel_w)
|
||||
)
|
||||
out_channel = num_filter
|
||||
out_height = topi.utils.simplify(
|
||||
(in_height - dilated_kernel_h + pad_top + pad_down) // stride_h + 1
|
||||
)
|
||||
out_width = topi.utils.simplify(
|
||||
(in_width - dilated_kernel_w + pad_left + pad_right) // stride_w + 1
|
||||
)
|
||||
pad_before = [0, pad_top, pad_left, 0]
|
||||
pad_after = [0, pad_down, pad_right, 0]
|
||||
PaddedInput = topi.nn.pad(Input, pad_before, pad_after, name="PaddedInput")
|
||||
rc = te.reduce_axis((0, in_channel), name="rc")
|
||||
ry = te.reduce_axis((0, kernel_h), name="ry")
|
||||
rx = te.reduce_axis((0, kernel_w), name="rx")
|
||||
Output = te.compute(
|
||||
(batch, out_height, out_width, out_channel),
|
||||
lambda nn, yy, xx, ff: te.sum(
|
||||
PaddedInput[
|
||||
nn, yy * stride_h + ry * dilation_h, xx * stride_w + rx * dilation_w, rc
|
||||
].astype(out_dtype)
|
||||
* Filter[ry, rx, rc, ff].astype(out_dtype), # type: ignore
|
||||
axis=[ry, rx, rc],
|
||||
),
|
||||
name="Conv2dOutput",
|
||||
tag="conv2d_nhwc",
|
||||
)
|
||||
return Output
|
||||
|
||||
|
||||
def conv2d_nhwc_bn_relu( # pylint: disable=invalid-name,missing-docstring
|
||||
N: int,
|
||||
H: int,
|
||||
W: int,
|
||||
CI: int,
|
||||
CO: int,
|
||||
kernel_size: int,
|
||||
strides: int,
|
||||
padding: int,
|
||||
dilation: int = 1,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor, te.Tensor, te.Tensor, te.Tensor]:
|
||||
data = te.placeholder((N, H, W, CI), name="data", dtype=in_dtype)
|
||||
kernel = te.placeholder((kernel_size, kernel_size, CI, CO), name="kernel", dtype=in_dtype)
|
||||
bias = te.placeholder((CO,), name="bias")
|
||||
bn_scale = te.placeholder((CO,), name="bn_scale")
|
||||
bn_offset = te.placeholder((CO,), name="bn_offset")
|
||||
OH = (H + 2 * padding - (kernel_size - 1) * dilation - 1) // strides + 1
|
||||
OW = (W + 2 * padding - (kernel_size - 1) * dilation - 1) // strides + 1
|
||||
conv = conv2d_nhwc_without_layout_rewrite(data, kernel, strides, padding, dilation, out_dtype)
|
||||
conv = te.compute(
|
||||
(N, OH, OW, CO), lambda i, j, k, l: conv[i, j, k, l] + bias[l], name="bias_add"
|
||||
)
|
||||
conv = te.compute(
|
||||
(N, OH, OW, CO), lambda i, j, k, l: conv[i, j, k, l] * bn_scale[l], name="bn_mul"
|
||||
)
|
||||
conv = te.compute(
|
||||
(N, OH, OW, CO), lambda i, j, k, l: conv[i, j, k, l] + bn_offset[l], name="bn_add"
|
||||
)
|
||||
out = topi.nn.relu(conv)
|
||||
return (data, kernel, bias, bn_offset, bn_scale, out)
|
||||
|
||||
|
||||
def transpose_batch_matmul( # pylint: disable=invalid-name,missing-docstring
|
||||
batch: int,
|
||||
seq_len: int,
|
||||
n_head: int,
|
||||
n_dim: int,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
query = te.placeholder((batch, seq_len, n_head, n_dim), name="query", dtype=in_dtype)
|
||||
value = te.placeholder((batch, seq_len, n_head, n_dim), name="value", dtype=in_dtype)
|
||||
query_T = te.compute(
|
||||
(batch, n_head, seq_len, n_dim),
|
||||
lambda b, h, l, d: query[b, l, h, d],
|
||||
name="query_T",
|
||||
)
|
||||
value_T = te.compute(
|
||||
(batch, n_head, n_dim, seq_len),
|
||||
lambda b, h, d, l: value[b, l, h, d],
|
||||
name="value_T",
|
||||
)
|
||||
k = te.reduce_axis((0, n_dim), name="k")
|
||||
out = te.compute(
|
||||
(batch, n_head, seq_len, seq_len),
|
||||
lambda b, h, i, j: te.sum(
|
||||
query_T[b, h, i, k].astype(out_dtype) * value_T[b, h, k, j].astype(out_dtype), axis=[k]
|
||||
),
|
||||
name="C",
|
||||
)
|
||||
return (query, value, out)
|
||||
|
||||
|
||||
def conv2d_winograd_nhwc( # pylint: disable=invalid-name,missing-docstring
|
||||
N: int,
|
||||
H: int,
|
||||
W: int,
|
||||
CI: int,
|
||||
CO: int,
|
||||
kernel_size: int,
|
||||
stride: int = 1,
|
||||
padding: int = 0,
|
||||
dilation: int = 1,
|
||||
tile_size: int = 4,
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
from tvm.topi.nn.conv2d import ( # pylint: disable=import-outside-toplevel
|
||||
_conv2d_winograd_nhwc_impl,
|
||||
)
|
||||
|
||||
target = Target.current(allow_none=True)
|
||||
if target is not None and target.kind.name == "cuda":
|
||||
write_cache_level = 3
|
||||
else:
|
||||
write_cache_level = 2
|
||||
data = te.placeholder((N, H, W, CI), "float32", name="data")
|
||||
weight = te.placeholder((kernel_size, kernel_size, CO, CI), "float32", name="weight")
|
||||
out = _conv2d_winograd_nhwc_impl(
|
||||
data,
|
||||
weight,
|
||||
stride,
|
||||
padding,
|
||||
dilation,
|
||||
"float32",
|
||||
pre_computed=True,
|
||||
auto_scheduler_rewritten_layout="",
|
||||
meta_schedule_original_shape=None,
|
||||
tile_size=tile_size,
|
||||
write_cache_level=write_cache_level,
|
||||
)
|
||||
return (data, weight, out)
|
||||
|
||||
|
||||
def matmul(
|
||||
n: int, m: int, k: int, in_dtype: str = "float32", out_dtype: str = "float32"
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
a = te.placeholder((n, k), name="A", dtype=in_dtype)
|
||||
b = te.placeholder((k, m), name="B", dtype=in_dtype)
|
||||
k = te.reduce_axis((0, k), name="k")
|
||||
c = te.compute(
|
||||
(n, m),
|
||||
lambda i, j: te.sum(a[i, k].astype(out_dtype) * b[k, j].astype(out_dtype), axis=[k]),
|
||||
name="C",
|
||||
)
|
||||
return (a, b, c)
|
||||
|
||||
|
||||
def matmul_relu(
|
||||
n: int, m: int, k: int, in_dtype: str = "float32", out_dtype: str = "float32"
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
a = te.placeholder((n, k), name="A", dtype=in_dtype)
|
||||
b = te.placeholder((k, m), name="B", dtype=in_dtype)
|
||||
k = te.reduce_axis((0, k), name="k")
|
||||
c = te.compute(
|
||||
(n, m),
|
||||
lambda i, j: te.sum(a[i, k].astype(out_dtype) * b[k, j].astype(out_dtype), axis=[k]),
|
||||
name="C",
|
||||
)
|
||||
d = topi.nn.relu(c) # pylint: disable=invalid-name
|
||||
return (a, b, d)
|
||||
|
||||
|
||||
def conv2d_nchw( # pylint: disable=invalid-name
|
||||
n: int,
|
||||
h: int,
|
||||
w: int,
|
||||
ci: int,
|
||||
co: int,
|
||||
kh: int,
|
||||
kw: int,
|
||||
stride: int,
|
||||
padding: int,
|
||||
dilation: int = 1,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor]:
|
||||
x = te.placeholder((n, ci, h, w), name="X", dtype=in_dtype)
|
||||
w = te.placeholder((co, ci, kh, kw), name="W", dtype=in_dtype)
|
||||
y = topi.nn.conv2d_nchw(
|
||||
Input=x, Filter=w, stride=stride, padding=padding, dilation=dilation, out_dtype=out_dtype
|
||||
)
|
||||
return (x, w, y)
|
||||
|
||||
|
||||
def conv2d_nchw_bias_bn_relu( # pylint: disable=invalid-name
|
||||
n: int,
|
||||
h: int,
|
||||
w: int,
|
||||
ci: int,
|
||||
co: int,
|
||||
kh: int,
|
||||
kw: int,
|
||||
stride: int,
|
||||
padding: int,
|
||||
dilation: int = 1,
|
||||
in_dtype: str = "float32",
|
||||
out_dtype: str = "float32",
|
||||
) -> tuple[te.Tensor, te.Tensor, te.Tensor, te.Tensor, te.Tensor, te.Tensor]:
|
||||
oh = (h + 2 * padding - (kh - 1) * dilation - 1) // stride + 1 # pylint: disable=invalid-name
|
||||
ow = (w + 2 * padding - (kw - 1) * dilation - 1) // stride + 1 # pylint: disable=invalid-name
|
||||
x = te.placeholder((n, ci, h, w), name="X", dtype=in_dtype)
|
||||
w = te.placeholder((co, ci, kh, kw), name="W", dtype=in_dtype)
|
||||
b = te.placeholder((co, 1, 1), name="B", dtype=out_dtype)
|
||||
bn_scale = te.placeholder((co, 1, 1), name="bn_scale", dtype=out_dtype)
|
||||
bn_offset = te.placeholder((co, 1, 1), name="bn_offset", dtype=out_dtype)
|
||||
y = topi.nn.conv2d_nchw(
|
||||
Input=x, Filter=w, stride=stride, padding=padding, dilation=dilation, out_dtype=out_dtype
|
||||
)
|
||||
y = te.compute((n, co, oh, ow), lambda i, j, k, l: y[i, j, k, l] + b[j, 0, 0], name="bias_add")
|
||||
y = te.compute(
|
||||
(n, co, oh, ow), lambda i, j, k, l: y[i, j, k, l] * bn_scale[j, 0, 0], name="bn_mul"
|
||||
)
|
||||
y = te.compute(
|
||||
(n, co, oh, ow), lambda i, j, k, l: y[i, j, k, l] + bn_offset[j, 0, 0], name="bn_add"
|
||||
)
|
||||
y = topi.nn.relu(y)
|
||||
return (x, w, b, bn_scale, bn_offset, y)
|
||||
|
||||
|
||||
def max_pool2d_nchw( # pylint: disable=invalid-name
|
||||
n: int,
|
||||
h: int,
|
||||
w: int,
|
||||
ci: int,
|
||||
padding: int,
|
||||
) -> tuple[te.Tensor, te.Tensor]: # pylint: disable=invalid-name
|
||||
x = te.placeholder((n, ci, h, w), name="X")
|
||||
y = topi.nn.pool2d(x, [2, 2], [1, 1], [1, 1], [padding, padding, padding, padding], "max")
|
||||
return (x, y)
|
||||
|
||||
|
||||
def softmax_mn(m, n) -> tuple[te.Tensor, te.Tensor]: # pylint: disable=invalid-name
|
||||
a = te.placeholder((m, n), name="A")
|
||||
b = topi.nn.softmax(a, axis=1)
|
||||
|
||||
return (a, b)
|
||||
|
||||
|
||||
def create_te_workload(name: str, idx: int) -> tirx.PrimFunc:
|
||||
workload_func, params = CONFIGS[name]
|
||||
return te.create_prim_func(workload_func(*params[idx])) # type: ignore
|
||||
|
||||
|
||||
CONFIGS = {
|
||||
"C1D": (
|
||||
conv1d_nlc,
|
||||
[
|
||||
# derived from conv2d_shapes
|
||||
(1, 256, 64, 128, 3, 2, 1),
|
||||
# (1, 256, 64, 128, 1, 2, 0),
|
||||
# (1, 256, 64, 64, 1, 1, 0),
|
||||
# (1, 128, 128, 256, 3, 2, 1),
|
||||
(1, 128, 128, 256, 1, 2, 0),
|
||||
# (1, 128, 128, 128, 3, 1, 1),
|
||||
# (1, 64, 256, 512, 3, 2, 1),
|
||||
# (1, 64, 256, 512, 1, 2, 0),
|
||||
(1, 64, 256, 256, 5, 1, 2),
|
||||
(1, 32, 512, 512, 3, 1, 1),
|
||||
],
|
||||
),
|
||||
"C2D": (
|
||||
conv2d_nhwc,
|
||||
[
|
||||
# all conv2d layers in resnet-18
|
||||
(1, 224, 224, 3, 64, 7, 2, 3),
|
||||
# (1, 56, 56, 64, 128, 3, 2, 1),
|
||||
# (1, 56, 56, 64, 128, 1, 2, 0),
|
||||
# (1, 56, 56, 64, 64, 3, 1, 1),
|
||||
(1, 56, 56, 64, 64, 1, 1, 0),
|
||||
# (1, 28, 28, 128, 256, 3, 2, 1),
|
||||
# (1, 28, 28, 128, 256, 1, 2, 0),
|
||||
# (1, 28, 28, 128, 128, 3, 1, 1),
|
||||
# (1, 14, 14, 256, 512, 3, 2, 1),
|
||||
# (1, 14, 14, 256, 512, 1, 2, 0),
|
||||
(1, 14, 14, 256, 256, 3, 1, 1),
|
||||
(1, 7, 7, 512, 512, 3, 1, 1),
|
||||
],
|
||||
),
|
||||
"C3D": (
|
||||
conv3d_ndhwc,
|
||||
[
|
||||
# Derived from conv2d_shapes. Use depth=16 for all configurations
|
||||
(1, 16, 224, 224, 3, 64, 7, 2, 3),
|
||||
# (1, 16, 56, 56, 64, 128, 3, 2, 1),
|
||||
# (1, 16, 56, 56, 64, 128, 1, 2, 0),
|
||||
# (1, 16, 56, 56, 64, 64, 3, 1, 1),
|
||||
(1, 16, 56, 56, 64, 64, 1, 1, 0),
|
||||
# (1, 16, 28, 28, 128, 256, 3, 2, 1),
|
||||
# (1, 16, 28, 28, 128, 256, 1, 2, 0),
|
||||
# (1, 16, 28, 28, 128, 128, 3, 1, 1),
|
||||
# (1, 16, 14, 14, 256, 512, 3, 2, 1),
|
||||
# (1, 16, 14, 14, 256, 512, 1, 2, 0),
|
||||
(1, 16, 14, 14, 256, 256, 3, 1, 1),
|
||||
(1, 16, 7, 7, 512, 512, 3, 1, 1),
|
||||
],
|
||||
),
|
||||
"GMM": (
|
||||
batch_matmul_nkkm,
|
||||
[
|
||||
(1, 128, 128, 128),
|
||||
(1, 512, 32, 512),
|
||||
(1, 512, 512, 512),
|
||||
(1, 1024, 1024, 1024),
|
||||
],
|
||||
),
|
||||
"GRP": (
|
||||
conv2d_nhwc,
|
||||
[
|
||||
# Derived from conv2d_shapes. Use group=4 for all configurations
|
||||
(1, 56, 56, 64, 128, 3, 2, 1, 1, 4),
|
||||
# (1, 56, 56, 64, 128, 1, 2, 0 , 1, 4),
|
||||
# (1, 56, 56, 64, 64, 3, 1, 1 , 1, 4),
|
||||
(1, 56, 56, 64, 64, 1, 1, 0, 1, 4),
|
||||
# (1, 28, 28, 128, 256, 3, 2, 1, 1, 4),
|
||||
# (1, 28, 28, 128, 256, 1, 2, 0, 1, 4),
|
||||
# (1, 28, 28, 128, 128, 3, 1, 1, 1, 4),
|
||||
# (1, 14, 14, 256, 512, 3, 2, 1, 1, 4),
|
||||
# (1, 14, 14, 256, 512, 1, 2, 0, 1, 4),
|
||||
(1, 14, 14, 256, 256, 3, 1, 1, 1, 4),
|
||||
(1, 7, 7, 512, 512, 3, 1, 1, 1, 4),
|
||||
],
|
||||
),
|
||||
"DIL": (
|
||||
conv2d_nhwc,
|
||||
[
|
||||
# Derived from conv2d_shapes. Use dilation=2 for all configurations
|
||||
(1, 224, 224, 3, 64, 7, 2, 3, 2),
|
||||
# (1, 56, 56, 64, 128, 3, 2, 1 , 2),
|
||||
# (1, 56, 56, 64, 128, 1, 2, 0 , 2),
|
||||
# (1, 56, 56, 64, 64, 3, 1, 1 , 2),
|
||||
(1, 56, 56, 64, 64, 1, 1, 0, 2),
|
||||
# (1, 28, 28, 128, 256, 3, 2, 1, 2),
|
||||
# (1, 28, 28, 128, 256, 1, 2, 0, 2),
|
||||
# (1, 28, 28, 128, 128, 3, 1, 1, 2),
|
||||
# (1, 14, 14, 256, 512, 3, 2, 1, 2),
|
||||
# (1, 14, 14, 256, 512, 1, 2, 0, 2),
|
||||
(1, 14, 14, 256, 256, 3, 1, 1, 2),
|
||||
(1, 7, 7, 512, 512, 3, 1, 1, 2),
|
||||
],
|
||||
),
|
||||
"DEP": (
|
||||
depthwise_conv2d_nhwc,
|
||||
[
|
||||
# all depthwise conv2d layers in mobilenet
|
||||
(1, 112, 112, 32, 3, 1, 1),
|
||||
(1, 112, 112, 64, 3, 2, 1),
|
||||
# (1, 56, 56, 128, 3, 1, 1),
|
||||
# (1, 56, 56, 128, 3, 2, 1),
|
||||
# (1, 28, 28, 256, 3, 1, 1),
|
||||
# (1, 28, 28, 256, 3, 2, 1),
|
||||
# (1, 14, 14, 512, 3, 1, 1),
|
||||
(1, 14, 14, 512, 3, 2, 1),
|
||||
(1, 7, 7, 1024, 3, 1, 1),
|
||||
],
|
||||
),
|
||||
"T2D": (
|
||||
conv2d_transpose_nhwc,
|
||||
[
|
||||
# all conv2d transpose layers in DCGAN
|
||||
(1, 4, 4, 512, 256, 4, 2, 1),
|
||||
(1, 8, 8, 256, 128, 4, 2, 1),
|
||||
(1, 16, 16, 128, 64, 4, 2, 1),
|
||||
(1, 32, 32, 64, 3, 4, 2, 1),
|
||||
],
|
||||
),
|
||||
"CAP": (
|
||||
conv2d_capsule_nhwijc,
|
||||
[
|
||||
# all conv2d capsule layers in matrix capsules withemrouting (ICLR 2018)
|
||||
(1, 16, 16, 32, 32, 3, 2, 1),
|
||||
(1, 8, 8, 32, 32, 3, 1, 1),
|
||||
(1, 16, 16, 8, 16, 3, 2, 1),
|
||||
(1, 8, 8, 16, 16, 3, 1, 1),
|
||||
],
|
||||
),
|
||||
"NRM": (
|
||||
norm_bmn,
|
||||
[
|
||||
(1, 256, 256),
|
||||
(1, 512, 512),
|
||||
(1, 1024, 1024),
|
||||
(1, 4096, 1024),
|
||||
],
|
||||
),
|
||||
"SFM": (
|
||||
softmax_mn,
|
||||
[
|
||||
(256, 256),
|
||||
(512, 512),
|
||||
(1024, 1024),
|
||||
(2048, 2048),
|
||||
],
|
||||
),
|
||||
"CBR": (
|
||||
conv2d_nhwc_bn_relu,
|
||||
[
|
||||
(1, 224, 224, 3, 64, 7, 2, 3),
|
||||
(1, 56, 56, 64, 128, 3, 2, 1),
|
||||
(1, 28, 28, 128, 256, 1, 2, 0),
|
||||
(1, 7, 7, 512, 512, 3, 1, 1),
|
||||
],
|
||||
),
|
||||
"TBG": (
|
||||
transpose_batch_matmul,
|
||||
[
|
||||
(1, 128, 12, 64),
|
||||
(1, 128, 16, 64),
|
||||
(1, 64, 12, 128),
|
||||
(1, 128, 12, 128),
|
||||
],
|
||||
),
|
||||
"C2D_WIN_NHWC": (
|
||||
conv2d_winograd_nhwc,
|
||||
[
|
||||
(1, 14, 14, 128, 128, 6),
|
||||
],
|
||||
),
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
# 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.
|
||||
# pylint: disable=missing-docstring
|
||||
# ruff: noqa: F821
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
import tvm
|
||||
from tvm.s_tir import meta_schedule as ms
|
||||
from tvm.s_tir.meta_schedule.testing.te_workload import create_te_workload
|
||||
from tvm.support import describe
|
||||
from tvm.testing.utils import strtobool
|
||||
|
||||
|
||||
def _parse_args():
|
||||
args = argparse.ArgumentParser()
|
||||
args.add_argument(
|
||||
"--workload",
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
args.add_argument(
|
||||
"--target",
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
args.add_argument(
|
||||
"--num-trials",
|
||||
type=int,
|
||||
required=True,
|
||||
)
|
||||
args.add_argument(
|
||||
"--rpc-host",
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
args.add_argument(
|
||||
"--rpc-port",
|
||||
type=int,
|
||||
required=True,
|
||||
)
|
||||
args.add_argument(
|
||||
"--rpc-key",
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
args.add_argument(
|
||||
"--work-dir",
|
||||
type=str,
|
||||
required=True,
|
||||
)
|
||||
args.add_argument(
|
||||
"--number",
|
||||
type=int,
|
||||
default=3,
|
||||
)
|
||||
args.add_argument(
|
||||
"--repeat",
|
||||
type=int,
|
||||
default=1,
|
||||
)
|
||||
args.add_argument(
|
||||
"--min-repeat-ms",
|
||||
type=int,
|
||||
default=100,
|
||||
)
|
||||
args.add_argument(
|
||||
"--adaptive-training",
|
||||
type=lambda x: bool(strtobool(x)),
|
||||
required=False,
|
||||
help="example: True / False",
|
||||
default=True,
|
||||
)
|
||||
args.add_argument(
|
||||
"--cpu-flush",
|
||||
type=lambda x: bool(strtobool(x)),
|
||||
help="example: True / False",
|
||||
required=True,
|
||||
)
|
||||
parsed = args.parse_args()
|
||||
parsed.target = tvm.target.Target(parsed.target)
|
||||
parsed.rpc_config = ms.runner.RPCConfig(
|
||||
tracker_host=parsed.rpc_host,
|
||||
tracker_port=parsed.rpc_port,
|
||||
tracker_key=parsed.rpc_key,
|
||||
session_timeout_sec=60,
|
||||
)
|
||||
return parsed
|
||||
|
||||
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s.%(msecs)03d %(levelname)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
logging.getLogger("tvm.s_tir.meta_schedule").setLevel(logging.DEBUG)
|
||||
ARGS = _parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
describe()
|
||||
print(f"Workload: {ARGS.workload}")
|
||||
with ms.Profiler() as profiler:
|
||||
sch: s_tir.Schedule | None = ms.tir_integration.tune_tir(
|
||||
mod=create_te_workload(ARGS.workload, 0),
|
||||
target=ARGS.target,
|
||||
work_dir=ARGS.work_dir,
|
||||
max_trials_global=ARGS.num_trials,
|
||||
num_trials_per_iter=64,
|
||||
runner=ms.runner.RPCRunner( # type: ignore
|
||||
rpc_config=ARGS.rpc_config,
|
||||
evaluator_config=ms.runner.EvaluatorConfig(
|
||||
number=ARGS.number,
|
||||
repeat=ARGS.repeat,
|
||||
min_repeat_ms=ARGS.min_repeat_ms,
|
||||
enable_cpu_cache_flush=ARGS.cpu_flush,
|
||||
),
|
||||
alloc_repeat=1,
|
||||
),
|
||||
cost_model=ms.cost_model.XGBModel( # type: ignore
|
||||
extractor=ms.feature_extractor.PerStoreFeature(),
|
||||
adaptive_training=ARGS.adaptive_training,
|
||||
),
|
||||
strategy=ms.search_strategy.EvolutionarySearch(),
|
||||
)
|
||||
|
||||
print("Tuning Time:")
|
||||
print(profiler.table())
|
||||
|
||||
if sch is None:
|
||||
print("No valid schedule found!")
|
||||
else:
|
||||
print(sch.mod.script())
|
||||
print(sch.trace)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user