# 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