Files
wehub-resource-sync 26446540fa
Lint / lint (push) Waiting to run
CI / MacOS (push) Waiting to run
CI / Windows (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:25 +08:00

342 lines
10 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.
# pylint: disable=invalid-name,unused-argument
"""Common pass instrumentation across IR variants."""
import functools
import inspect
import re
import shutil
from pathlib import Path
import tvm_ffi
import tvm.runtime
from . import _ffi_instrument_api
@tvm_ffi.register_object("instrument.PassInstrument")
class PassInstrument(tvm.runtime.Object):
"""A pass instrument implementation.
To use, a user class can either subclass from PassInstrument
directly, or can apply the :py:func:`pass_instrument` wrapper. In
either case, the `enter_pass_ctx`, `exit_pass_ctx`, `should_run`,
`run_before_pass`, and `run_after_pass` methods can be defined to
adjust the instrument's behavior. See the no-op implementations
in this class definition for more information on each.
"""
def __init__(self):
# initialize handle in case pi_cls creation failed.
cls = type(self)
# If the child class declared the method, then use it.
# Otherwise, pass None to avoid a C++ -> Python round trip for
# a no-op.
def get_child_method(name):
if getattr(cls, name) is getattr(PassInstrument, name):
return None
return getattr(self, name)
# Create runtime pass instrument object.
# register instance's enter_pass_ctx,exit_pass_ctx, should_run, run_before_pass and
# run_after_pass methods to it if present.
self.__init_handle_by_constructor__(
_ffi_instrument_api.PassInstrument,
cls.__name__,
get_child_method("enter_pass_ctx"),
get_child_method("exit_pass_ctx"),
get_child_method("should_run"),
get_child_method("run_before_pass"),
get_child_method("run_after_pass"),
)
def enter_pass_ctx(self):
"""Called when entering the instrumented context.
Returns
-------
None
"""
def exit_pass_ctx(self):
"""Called when exiting the instrumented context.
Returns
-------
None
"""
def should_run(self, mod, info):
"""Determine whether to run the pass or not.
Called once for each pass that is run while the instrumented
context is active.
Parameters
----------
mod : tvm.ir.module.IRModule
The module on which an optimization pass is being run.
info : tvm.transform.PassInfo
The pass information.
Returns
-------
should_run : bool
True to run the pass, or False to skip the pass.
"""
def run_before_pass(self, mod, info):
"""Instrument before the pass runs.
Called once for each pass that is run while the instrumented
context is active.
Parameters
----------
mod : tvm.ir.module.IRModule
The module on which an optimization pass is being run.
info : tvm.transform.PassInfo
The pass information.
Returns
-------
None
"""
def run_after_pass(self, mod, info):
"""Instrument after the pass runs.
Called once for each pass that is run while the instrumented
context is active.
Parameters
----------
mod : tvm.ir.module.IRModule
The module on which an optimization pass is being run.
info : tvm.transform.PassInfo
The pass information.
Returns
-------
None
"""
def _wrap_class_pass_instrument(pi_cls):
"""Wrap a python class as pass instrument"""
# No additional wrapping needed if the user class already
# inherits.
if issubclass(pi_cls, PassInstrument):
return pi_cls
class PyPassInstrument(pi_cls, PassInstrument):
"""Internal wrapper class to create a class instance."""
def __init__(self, *args, **kwargs):
# initialize handle in case pi_cls creation failed.
pi_cls.__init__(self, *args, **kwargs)
PassInstrument.__init__(self)
functools.update_wrapper(PyPassInstrument.__init__, pi_cls.__init__)
PyPassInstrument.__name__ = pi_cls.__name__
PyPassInstrument.__doc__ = pi_cls.__doc__
PyPassInstrument.__module__ = pi_cls.__module__
return PyPassInstrument
def pass_instrument(pi_cls=None):
"""Decorate a pass instrument.
Parameters
----------
pi_class : class
Instrument class. See example below.
Examples
--------
.. code-block:: python
@tvm.instrument.pass_instrument
class SkipPass:
def __init__(self, skip_pass_name):
self.skip_pass_name = skip_pass_name
# Uncomment to customize
# def enter_pass_ctx(self):
# pass
# Uncomment to customize
# def exit_pass_ctx(self):
# pass
# If pass name contains keyword, skip it by return False. (return True: not skip)
def should_run(self, mod, pass_info)
if self.skip_pass_name in pass_info.name:
return False
return True
# Uncomment to customize
# def run_before_pass(self, mod, pass_info):
# pass
# Uncomment to customize
# def run_after_pass(self, mod, pass_info):
# pass
skip_annotate = SkipPass("AnnotateSpans")
with tvm.transform.PassContext(instruments=[skip_annotate]):
tvm.compile(mod, "llvm")
"""
def create_pass_instrument(pi_cls):
if not inspect.isclass(pi_cls):
raise TypeError("pi_cls must be a class")
return _wrap_class_pass_instrument(pi_cls)
if pi_cls:
return create_pass_instrument(pi_cls)
return create_pass_instrument
@tvm_ffi.register_object("instrument.PassInstrument")
class PassTimingInstrument(tvm.runtime.Object):
"""A wrapper to create a passes time instrument that implemented in C++"""
def __init__(self):
self.__init_handle_by_constructor__(_ffi_instrument_api.MakePassTimingInstrument)
@staticmethod
def render():
"""Retrieve rendered time profile result
Returns
-------
string : string
The rendered string result of time profiles
Examples
--------
.. code-block:: python
timing_inst = PassTimingInstrument()
with tvm.transform.PassContext(instruments=[timing_inst]):
relax_mod = relax.transform.FuseOps()(relax_mod)
# before exiting the context, get profile results.
profiles = timing_inst.render()
"""
return _ffi_instrument_api.RenderTimePassProfiles()
@pass_instrument
class PassPrintingInstrument:
"""A pass instrument to print if before or
print ir after each element of a named pass."""
def __init__(self, print_before_pass_names, print_after_pass_names):
self.print_before_pass_names = print_before_pass_names
self.print_after_pass_names = print_after_pass_names
def run_before_pass(self, mod, pass_info):
if pass_info.name in self.print_before_pass_names:
print(f"Print IR before: {pass_info.name}\n{mod}\n\n")
def run_after_pass(self, mod, pass_info):
if pass_info.name in self.print_after_pass_names:
print(f"Print IR after: {pass_info.name}\n{mod}\n\n")
@pass_instrument
class PrintAfterAll:
"""Print the name of the pass, the IR, only after passes execute."""
def run_after_pass(self, mod, info):
print(f"After Running Pass: {info}")
print(mod)
@pass_instrument
class PrintBeforeAll:
"""Print the name of the pass, the IR, only before passes execute."""
def run_before_pass(self, mod, info):
print(f"Before Running Pass: {info}")
print(mod)
@pass_instrument
class DumpIR:
"""Dump the IR after the pass runs."""
def __init__(self, dump_dir: Path | str, refresh: bool = False):
if isinstance(dump_dir, Path):
self.dump_dir = dump_dir
else:
self.dump_dir = Path(dump_dir)
self.counter = 0
if refresh and self.dump_dir.is_dir():
self._safe_remove_dump_dir()
def _safe_remove_dump_dir(self):
"""Remove dump directory only if it contains only dumped IR files."""
# Pattern for dumped files: {counter:03d}_{pass_name}.py
dump_pattern = re.compile(r"^\d{3}_.*\.py$")
# Check all files in the directory
for item in self.dump_dir.iterdir():
# If there's a subdirectory or a file that doesn't match the pattern, abort
if item.is_dir() or not dump_pattern.match(item.name):
print(
f"WARNING: Skipping removal of {self.dump_dir} as it contains "
f"non-dumped files or directories. Please clean it manually."
)
return
# Safe to remove - only contains dumped files
try:
shutil.rmtree(self.dump_dir)
except OSError as e:
print(f"WARNING: Failed to remove directory {self.dump_dir}: {e}")
def run_after_pass(self, mod, info):
self.dump_dir.mkdir(parents=True, exist_ok=True)
try:
sanitized_pass_name = re.sub(r'[<>:"/\\|?*]', "_", info.name)
with open(self.dump_dir / f"{self.counter:03d}_{sanitized_pass_name}.py", "w") as f:
f.write(mod.script())
except Exception: # pylint: disable=broad-exception-caught
print(f"WARNING: Failed to dump IR for pass {info.name}")
finally:
self.counter += 1