Files
apache--tvm/python/tvm/script/parser/core/entry.py
T
Bohan Hou 859498dc01 [TIRx] Bringup TIRx Infrastructure (#19581)
## 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.
2026-05-18 16:44:43 -07:00

210 lines
6.7 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.
"""The entry point of TVM parser."""
import inspect
from typing import Any
import tvm
from ....ir.module import IRModule
from ...ir_builder import IRBuilder
from . import doc
from .diagnostics import Source
from .error import ParserError
from .parser import Parser
WELL_FORMED_ERROR_MESSAGE = (
"Program is not well-formed. If this is deliberate, consider "
"setting check_well_formed in the top-level decorator to False "
"(e.g., @I.ir_module(check_well_formed=False) or "
"@R.function(check_well_formed=False))."
)
def _default_globals() -> dict[str, Any]:
# lazy import here to avoid circular deps
from tvm.script import tirx as _tirx_dsl # pylint: disable=import-outside-toplevel
from tvm.script.parser import (
ir, # pylint: disable=import-outside-toplevel
relax, # pylint: disable=import-outside-toplevel
)
from tvm.script.parser import tirx as _tirx_parser # pylint: disable=import-outside-toplevel
from tvm.tirx import layout as _tirx_layout # pylint: disable=import-outside-toplevel
# Expose the layout `Axis` class so printed layout sugar like
# `4 @ Axis.laneid` round-trips without per-script imports. Injecting just
# `Axis` (one short symbol) avoids name collisions with common user shape
# vars like `m`, `P`, `F` that registered axes happen to share names with.
return {
"tvm": tvm,
"I": ir,
"ir": ir,
"T": _tirx_parser,
"tir": _tirx_parser,
"R": relax,
"relax": relax,
"Tx": _tirx_dsl,
"tirx": _tirx_dsl,
"Axis": _tirx_layout.Axis,
}
def scan_macro(program: Any | str, extra_vars: dict[str, Any] | None = None) -> Any:
"""Generate the AST, and the source code for __repr__."""
# The AST will be converted into TIR at the time of expansion.
source = Source(program)
closure_vars = extra_vars or _default_globals()
return source, closure_vars
def parse(
program: doc.AST | Any | str,
extra_vars: dict[str, Any] | None = None,
check_well_formed: bool = True,
s_tir: bool = False,
) -> Any:
"""Register a method for a operand type, AST operator node and operand index.
Parameters
----------
program : Union[doc.AST, Any, str]
The TVMScript code to parse.
extra_vars : Dict[str, Any]
The extra variable table for parsing.
check_well_formed : bool
Whether to check well-formedness after parsing.
Returns
-------
func : Any
The parsed TVMScript program.
"""
if extra_vars is None:
extra_vars = _default_globals()
ann = {}
all_pyfuncs = {}
if inspect.isfunction(program):
ann = {program.__name__: program.__annotations__}
elif inspect.isclass(program):
for name, func in program.__dict__.items():
if inspect.isfunction(func):
ann[name] = func.__annotations__
all_pyfuncs[name] = func
source = Source(program)
parser = Parser(source, ann)
with IRBuilder() as builder:
try:
parser.parse(extra_vars=extra_vars)
except ParserError as err:
parser.report_error(err.node, err.args[0])
ret = builder.get()
# Attach pyfuncs to the IRModule
if inspect.isclass(program) and isinstance(ret, IRModule):
_attach_pyfuncs_to_irmodule(ret, all_pyfuncs)
# check well-formedness in both Relax and TIR
if check_well_formed:
check_ret = ret
if not isinstance(check_ret, IRModule):
check_ret = IRModule.from_expr(ret)
source_ast = source.as_ast()
if isinstance(ret, IRModule | tvm.relax.Function) and not tvm.relax.analysis.well_formed(
ret
):
parser.report_error(source_ast, err=WELL_FORMED_ERROR_MESSAGE)
try:
if s_tir:
tvm.tirx.analysis.verify_well_formed(check_ret)
else:
tvm.tirx.analysis.verify_tirx_well_formed(check_ret)
except Exception as err: # pylint: disable=broad-exception-caught
parser.report_error(
source_ast,
err=f"{WELL_FORMED_ERROR_MESSAGE}\n\nTraceback: {err!s}",
)
return ret
def _create_python_packed_func(pyfunc):
"""Create a PackedFunc wrapper for a Python function.
This function creates a PackedFunc that can be called from TVM runtime
and will execute the original Python function.
Parameters
----------
pyfunc : Callable
The Python function to wrap.
Returns
-------
PackedFunc
A PackedFunc that wraps the Python function.
"""
def packed_func_wrapper(*args, **kwargs):
"""Wrapper function that calls the original Python function."""
try:
result = pyfunc(*args, **kwargs)
return result
except Exception as error:
print(f"Error calling Python function {pyfunc.__name__}: {error}")
raise
return packed_func_wrapper
def _attach_pyfuncs_to_irmodule(irmodule, all_pyfuncs):
"""Attach Python functions to IRModule with reduced nesting."""
if not all_pyfuncs:
return
if not hasattr(irmodule, "pyfuncs"):
irmodule.pyfuncs = {}
for global_var, func in irmodule.functions_items():
if not isinstance(func, tvm.relax.ExternFunc):
continue
if not func.attrs.get("is_pyfunc", False):
continue
pyfunc_name = global_var.name_hint
if pyfunc_name not in all_pyfuncs:
continue
pyfunc = all_pyfuncs[pyfunc_name]
irmodule.pyfuncs[pyfunc_name] = pyfunc
try:
source_code = inspect.getsource(pyfunc)
func = func.with_attr("python_source", source_code)
except (OSError, TypeError):
func = func.with_attr("python_source", f"# Source unavailable for {pyfunc_name}")
packed_func = _create_python_packed_func(pyfunc)
func = func.with_attr("python_packed_func", packed_func)
irmodule[global_var] = func