495 lines
18 KiB
Python
495 lines
18 KiB
Python
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you under the Apache License, Version 2.0 (the
|
|
# "License"); you may not use this file except in compliance
|
|
# with the License. You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing,
|
|
# software distributed under the License is distributed on an
|
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
# KIND, either express or implied. See the License for the
|
|
# specific language governing permissions and limitations
|
|
# under the License.
|
|
"""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
|