859498dc01
## Summary This PR adds the initial TIRx support needed for low-level programming of Blackwell-class GPU architectures. As part of the ongoing TIRx refactor, it introduces TVMScript support for directly scripting advanced hardware features without relying on scheduling as the primary programming interface. The change keeps existing `s_tir` script support intact while making direct scripting a first-class path for TIRx programs. ## Main Changes - Add TIRx operator dispatch and layout infrastructure. - Add TVMScript support for new low-level TIRx operations. - Add analysis, transform, and lowering support for TIRx IR nodes. - Add CUDA/Blackwell-oriented codegen and intrinsic coverage. - Add Python and C++ integration points for TIRx scripting and runtime support. ## Validation - `pre-commit run --all-files` - `ninja -C build -j32` - `CUDA_VISIBLE_DEVICES=2 pytest tests/python/tirx/ -n 16` - `1723 passed, 47 skipped, 32 warnings` - `CUDA_VISIBLE_DEVICES=2 python -m pytest -v tests/python/all-platform-minimal-test` - `37 passed, 105 skipped` - `TVM_TEST_TARGETS=llvm python -m pytest -v tests/python/tirx-analysis tests/python/tirx-base tests/python/tirx-transform -n 16` - `664 passed, 25 skipped, 9 xfailed, 1 xpassed` ## Local CI Notes Some full CI-equivalent jobs were not locally reproducible because this machine is missing parts of the Apache TVM CI environment, including `llvm-config-15/17`, Vulkan, ROCm, Maven, Sphinx, Doxygen, Emscripten, and ARM/QEMU cross-toolchain components. Metal-specific tests were skipped locally because no Metal runtime is available.
164 lines
6.2 KiB
Python
164 lines
6.2 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=import-outside-toplevel
|
|
"""The entry point of TVM parser for ir module."""
|
|
|
|
import inspect
|
|
from collections.abc import Callable
|
|
|
|
from tvm import cpu, ir
|
|
from tvm.ir import GlobalVar, IRModule
|
|
|
|
from .._core import parse, utils
|
|
|
|
|
|
# this formulation allows us to support having @I.ir_module
|
|
# appear as a decorator by itself or to have optional arguments
|
|
# like @I.ir_module(check_well_formed=False)
|
|
def ir_module(
|
|
mod: type | None = None, check_well_formed: bool = True, s_tir: bool = False
|
|
) -> IRModule:
|
|
"""The parsing method for ir module, by using `@ir_module` as decorator.
|
|
|
|
Parameters
|
|
----------
|
|
mod : Type
|
|
The class to be parsed as ir module.
|
|
|
|
check_well_formed : bool
|
|
Whether to check well-formedness during parsing.
|
|
|
|
Returns
|
|
-------
|
|
ir_module : IRModule
|
|
The parsed ir module.
|
|
"""
|
|
|
|
# Capture stack outside wrapper (wrapper adds to the stack)
|
|
outer_stack = inspect.stack()
|
|
|
|
def decorator_wrapper(mod):
|
|
if not inspect.isclass(mod):
|
|
raise TypeError(f"Expect a class, but got: {mod}")
|
|
|
|
# Check BasePyModule inheritance
|
|
base_py_module_inherited = any(base.__name__ == "BasePyModule" for base in mod.__bases__)
|
|
|
|
extra_vars = utils.inspect_class_capture(mod)
|
|
# Resolve closure variables hidden by PEP 563 (annotation-only names)
|
|
utils.resolve_closure_vars(mod, extra_vars, outer_stack)
|
|
m = parse(mod, extra_vars, check_well_formed=check_well_formed, s_tir=s_tir)
|
|
|
|
if base_py_module_inherited:
|
|
# Lazy import: tvm.relax cannot be imported at module level in tvm.script.parser
|
|
# because tvm.script is loaded before tvm.relax during tvm initialization.
|
|
from tvm.relax.base_py_module import BasePyModule
|
|
from tvm.relax.expr import ExternFunc # pylint: disable=import-outside-toplevel
|
|
|
|
# Collect pyfunc methods
|
|
pyfunc_methods = [
|
|
name
|
|
for name, attr in mod.__dict__.items()
|
|
if hasattr(attr, "dispatch_token") and attr.dispatch_token == "pyfunc"
|
|
]
|
|
|
|
mod._pyfunc_methods = pyfunc_methods
|
|
|
|
# Create ExternFunc nodes
|
|
|
|
for method_name in pyfunc_methods:
|
|
try:
|
|
existing_gvars = [
|
|
global_var
|
|
for global_var in m.get_global_vars()
|
|
if global_var.name_hint == method_name
|
|
]
|
|
|
|
extern_func = ExternFunc(method_name)
|
|
extern_func = extern_func.with_attr("is_pyfunc", True)
|
|
extern_func = extern_func.with_attr("function_type", "python")
|
|
extern_func = extern_func.with_attr("python_function_name", method_name)
|
|
extern_func = extern_func.with_attr(
|
|
"python_source", f"# Source for {method_name}"
|
|
)
|
|
extern_func = extern_func.with_attr("python_packed_func", None)
|
|
|
|
if existing_gvars:
|
|
m[existing_gvars[0]] = extern_func
|
|
else:
|
|
m[GlobalVar(method_name)] = extern_func
|
|
|
|
except Exception: # pylint: disable=broad-exception-caught
|
|
continue
|
|
|
|
class ModuleFactory:
|
|
"""Factory class for creating BasePyModule instances with Python functions."""
|
|
|
|
def __init__(self, module, pyfunc_methods, original_class):
|
|
self.ir_module = module
|
|
self.pyfunc_methods = pyfunc_methods
|
|
self.original_class = original_class
|
|
|
|
def __call__(self, device=None, target=None):
|
|
if device is None:
|
|
device = cpu(0)
|
|
|
|
instance_ir_mod = ir.IRModule()
|
|
for global_var, func in self.ir_module.functions_items():
|
|
instance_ir_mod[global_var] = func
|
|
|
|
instance = BasePyModule(instance_ir_mod, device, target)
|
|
|
|
for method_name in self.pyfunc_methods:
|
|
if hasattr(self.original_class, method_name):
|
|
method = getattr(self.original_class, method_name)
|
|
instance.add_python_function(method_name, method)
|
|
|
|
return instance
|
|
|
|
def __getattr__(self, name):
|
|
if hasattr(self.ir_module, name):
|
|
return getattr(self.ir_module, name)
|
|
raise AttributeError(
|
|
f"'{self.__class__.__name__}' object has no attribute '{name}'"
|
|
)
|
|
|
|
factory = ModuleFactory(m, pyfunc_methods, mod)
|
|
setattr(factory, "__name__", mod.__name__)
|
|
return factory
|
|
|
|
setattr(m, "__name__", mod.__name__)
|
|
return m
|
|
|
|
if mod is not None:
|
|
# if there are no optional args given, this will directly invoke the wrapper
|
|
return decorator_wrapper(mod)
|
|
else:
|
|
# if there is a optional arg given, it returns the wrapper function
|
|
# as a new decorator and applies it
|
|
setattr(decorator_wrapper, "dispatch_token", "ir")
|
|
return decorator_wrapper
|
|
|
|
|
|
def pyfunc(func: Callable):
|
|
# Set the dispatch_token on the decorated function
|
|
setattr(func, "dispatch_token", "pyfunc")
|
|
return func
|
|
|
|
|
|
setattr(pyfunc, "dispatch_token", "pyfunc")
|