Files
apache--tvm/python/tvm/runtime/script_printer.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

450 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.
"""Configuration of TVMScript printer"""
import os
from collections.abc import Sequence
from tvm_ffi import get_global_func, register_object
from tvm_ffi.access_path import AccessPath
from tvm.runtime import Object
from . import _ffi_node_api
@register_object("script.PrinterConfig")
class PrinterConfig(Object):
"""Configuration of TVMScript printer"""
binding_names: Sequence[str]
show_meta: bool
ir_prefix: str
tir_prefix: str
tir_import_module: str
relax_prefix: str
module_alias: str
int_dtype: str
float_dtype: str
verbose_expr: bool
indent_spaces: int
print_line_numbers: bool
num_context_lines: int
syntax_sugar: bool
show_object_address: bool
extra_config: dict
path_to_underline: list[AccessPath] | None
path_to_annotate: dict[AccessPath, str] | None
obj_to_underline: list[AccessPath] | None
obj_to_annotate: dict[AccessPath, str] | None
def __init__(
self,
*,
name: str | None = None,
show_meta: bool = False,
ir_prefix: str = "I",
tir_prefix: str = "T",
tir_import_module: str = "tir",
relax_prefix: str = "R",
module_alias: str = "cls",
buffer_dtype: str = "float32",
int_dtype: str = "int32",
float_dtype: str = "void",
verbose_expr: bool = False,
indent_spaces: int = 4,
print_line_numbers: bool = False,
num_context_lines: int | None = None,
syntax_sugar: bool = True,
show_object_address: bool = False,
show_all_struct_info: bool = True,
path_to_underline: list[AccessPath] | None = None,
path_to_annotate: dict[AccessPath, str] | None = None,
obj_to_underline: list[Object] | None = None,
obj_to_annotate: dict[Object, str] | None = None,
) -> None:
if num_context_lines is None:
num_context_lines = -1
cfg = {
"show_meta": show_meta,
"ir_prefix": ir_prefix,
"tir_prefix": tir_prefix,
"tir_import_module": tir_import_module,
"relax_prefix": relax_prefix,
"module_alias": module_alias,
"int_dtype": int_dtype,
"float_dtype": float_dtype,
"verbose_expr": verbose_expr,
"indent_spaces": indent_spaces,
"print_line_numbers": print_line_numbers,
"num_context_lines": num_context_lines,
"syntax_sugar": syntax_sugar,
"show_object_address": show_object_address,
"path_to_underline": path_to_underline,
"path_to_annotate": path_to_annotate,
"obj_to_underline": obj_to_underline,
"obj_to_annotate": obj_to_annotate,
# Dialect-specific config via dotted keys in extra_config
"tirx.prefix": tir_prefix,
"tirx.buffer_dtype": buffer_dtype,
"relax.prefix": relax_prefix,
"relax.show_all_struct_info": show_all_struct_info,
}
if name is not None:
cfg["name"] = name
self.__init_handle_by_constructor__(
_ffi_node_api.PrinterConfig,
cfg, # type: ignore # pylint: disable=no-member
)
def _script(obj: Object, config: PrinterConfig) -> str:
return _ffi_node_api.TVMScriptPrinterScript(obj, config) # type: ignore # pylint: disable=no-member
def _relax_script(obj: Object, config: PrinterConfig) -> str:
func = get_global_func("script.printer.ReprPrintRelax")
return func(obj, config)
class Scriptable:
"""A base class that enables the script() and show() method."""
def script(
self,
*,
name: str | None = None,
show_meta: bool = False,
ir_prefix: str = "I",
tir_prefix: str = "T",
tir_import_module: str = "tir",
relax_prefix: str = "R",
module_alias: str = "cls",
buffer_dtype: str = "float32",
int_dtype: str = "int32",
float_dtype: str = "void",
verbose_expr: bool = False,
indent_spaces: int = 4,
print_line_numbers: bool = False,
num_context_lines: int = -1,
syntax_sugar: bool = True,
show_object_address: bool = False,
show_all_struct_info: bool = True,
path_to_underline: list[AccessPath] | None = None,
path_to_annotate: dict[AccessPath, str] | None = None,
obj_to_underline: list[Object] | None = None,
obj_to_annotate: dict[Object, str] | None = None,
) -> str:
"""Print TVM IR into TVMScript text format
Parameters
----------
name : Optional[str] = None
The name of the object
show_meta : bool = False
Whether to print the meta data of the object
ir_prefix : str = "I"
The prefix of AST nodes from tvm.ir
tir_prefix : str = "T"
The prefix of AST nodes from tvm.tir
tir_import_module : str = "tir"
The module name in the printed import (e.g. \"tir\" or \"tirx\").
Use tir_import_module=\"tirx\" with tir_prefix=\"Tx\" for all-Tx output.
relax_prefix : str = "R"
The prefix of AST nodes from tvm.relax
module_alias : str = "cls"
The alias of the current module at cross-function call,
Directly use module name if it's empty.
buffer_dtype : str = "float32"
The default data type of buffer
int_dtype : str = "int32"
The default data type of integer
float_dtype : str = "void"
The default data type of float
verbose_expr : bool = False
Whether to print the detailed definition of each variable in the expression
indent_spaces : int = 4
The number of spaces for indentation
print_line_numbers : bool = False
Whether to print line numbers
num_context_lines : int = -1
The number of lines of context to print before and after the line to underline.
syntax_sugar: bool = True
Whether to output with syntax sugar, set false for complete printing.
show_object_address: bool = False
Whether to include the object's address as part of the TVMScript name
show_all_struct_info: bool = True
If True (default), annotate all variable bindings with the struct
info of that variable. If False, only add annotations where
required for unambiguous round-trip of Relax -> TVMScript -> Relax.
path_to_underline : Optional[List[AccessPath]] = None
Object path to be underlined
path_to_annotate : Optional[Dict[AccessPath, str]] = None
Object path to be annotated
obj_to_underline : Optional[List[Object]] = None
Object to be underlined
obj_to_annotate : Optional[Dict[Object, str]] = None
Object to be annotated
Returns
-------
script : str
The TVM Script of the given TVM IR
"""
# Auto-switch to tirx (`Tx`/`tirx`) flavor only when explicitly
# printing a PrimFunc / IRModule that has no s_tir-tagged content.
# Free objects (Buffer, BufferRegion, ...) keep the default `T`/`tir`
# flavor — they have no enclosing function to indicate tirx vs s_tir.
tir_prefix_val = tir_prefix
tir_import_module_val = tir_import_module
if tir_prefix == "T" and tir_import_module == "tir":
from tvm.ir import IRModule # pylint: disable=import-outside-toplevel
from tvm.tirx import PrimFunc # pylint: disable=import-outside-toplevel
switch_to_tirx = False
if isinstance(self, PrimFunc):
attrs = getattr(self, "attrs", None)
if attrs is None or not attrs.get("s_tir", False):
switch_to_tirx = True
elif isinstance(self, IRModule):
any_prim = False
any_s_tir = False
for _, base_func in self.functions.items():
if isinstance(base_func, PrimFunc):
any_prim = True
if getattr(base_func, "attrs", None) and base_func.attrs.get(
"s_tir", False
):
any_s_tir = True
break
if any_prim and not any_s_tir:
switch_to_tirx = True
if switch_to_tirx:
tir_prefix_val = "Tx"
tir_import_module_val = "tirx"
return _script(
self,
PrinterConfig(
name=name,
show_meta=show_meta,
ir_prefix=ir_prefix,
tir_prefix=tir_prefix_val,
tir_import_module=tir_import_module_val,
relax_prefix=relax_prefix,
module_alias=module_alias,
buffer_dtype=buffer_dtype,
int_dtype=int_dtype,
float_dtype=float_dtype,
verbose_expr=verbose_expr,
indent_spaces=indent_spaces,
print_line_numbers=print_line_numbers,
num_context_lines=num_context_lines,
syntax_sugar=syntax_sugar,
show_object_address=show_object_address,
show_all_struct_info=show_all_struct_info,
path_to_underline=path_to_underline,
path_to_annotate=path_to_annotate,
obj_to_underline=obj_to_underline,
obj_to_annotate=obj_to_annotate,
),
)
def _relax_script(
self,
*,
name: str | None = None,
show_meta: bool = False,
ir_prefix: str = "I",
tir_prefix: str = "T",
tir_import_module: str = "tir",
relax_prefix: str = "R",
module_alias: str = "cls",
buffer_dtype: str = "float32",
int_dtype: str = "int32",
float_dtype: str = "void",
verbose_expr: bool = False,
indent_spaces: int = 4,
print_line_numbers: bool = False,
num_context_lines: int = -1,
syntax_sugar: bool = True,
show_object_address: bool = False,
path_to_underline: list[AccessPath] | None = None,
path_to_annotate: dict[AccessPath, str] | None = None,
obj_to_underline: list[Object] | None = None,
obj_to_annotate: dict[Object, str] | None = None,
) -> str:
return _relax_script(
self,
PrinterConfig(
name=name,
show_meta=show_meta,
ir_prefix=ir_prefix,
tir_prefix=tir_prefix,
tir_import_module=tir_import_module,
relax_prefix=relax_prefix,
module_alias=module_alias,
buffer_dtype=buffer_dtype,
int_dtype=int_dtype,
float_dtype=float_dtype,
verbose_expr=verbose_expr,
indent_spaces=indent_spaces,
print_line_numbers=print_line_numbers,
num_context_lines=num_context_lines,
syntax_sugar=syntax_sugar,
show_object_address=show_object_address,
path_to_underline=path_to_underline,
path_to_annotate=path_to_annotate,
obj_to_underline=obj_to_underline,
obj_to_annotate=obj_to_annotate,
),
)
def show(
self,
style: str | None = None,
black_format: bool | None = None,
*,
name: str | None = None,
show_meta: bool = False,
ir_prefix: str = "I",
tir_prefix: str = "T",
tir_import_module: str = "tir",
relax_prefix: str = "R",
module_alias: str = "cls",
buffer_dtype: str = "float32",
int_dtype: str = "int32",
float_dtype: str = "void",
verbose_expr: bool = False,
indent_spaces: int = 4,
print_line_numbers: bool = False,
num_context_lines: int = -1,
syntax_sugar: bool = True,
show_object_address: bool = False,
show_all_struct_info: bool = True,
path_to_underline: list[AccessPath] | None = None,
path_to_annotate: dict[AccessPath, str] | None = None,
obj_to_underline: list[Object] | None = None,
obj_to_annotate: dict[Object, str] | None = None,
) -> None:
"""A sugar for print highlighted TVM script.
Parameters
----------
style : str, optional
Pygmentize printing style, auto-detected if None. See
`tvm.script.highlight.cprint` for more details.
black_format: Optional[bool]
If true, use the formatter Black to format the TVMScript.
If false, do not apply the auto-formatter.
If None (default), determine the behavior based on the
environment variable "TVM_BLACK_FORMAT". If this
environment variable is unset, set to the empty string, or
set to the integer zero, black auto-formatting will be
disabled. If the environment variable is set to a
non-zero integer, black auto-formatting will be enabled.
Note that the "TVM_BLACK_FORMAT" environment variable only
applies to the `.show()` method, and not the underlying
`.script()` method. The `.show()` method is intended for
human-readable output based on individual user
preferences, while the `.script()` method is intended to
provided a consistent output regardless of environment.
name : Optional[str] = None
The name of the object
show_meta : bool = False
Whether to print the meta data of the object
ir_prefix : str = "I"
The prefix of AST nodes from tvm.ir
tir_prefix : str = "T"
The prefix of AST nodes from tvm.tirx
relax_prefix : str = "R"
The prefix of AST nodes from tvm.relax
module_alias : str = "cls"
The alias of the current module at cross-function call,
Directly use module name if it's empty.
buffer_dtype : str = "float32"
The default data type of buffer
int_dtype : str = "int32"
The default data type of integer
float_dtype : str = "void"
The default data type of float
verbose_expr : bool = False
Whether to print the detailed definition of each variable in the expression
indent_spaces : int = 4
The number of spaces for indentation
print_line_numbers : bool = False
Whether to print line numbers
num_context_lines : int = -1
The number of lines of context to print before and after the line to underline.
syntax_sugar: bool = True
Whether to output with syntax sugar, set false for complete printing.
show_object_address: bool = False
Whether to include the object's address as part of the TVMScript name
show_all_struct_info: bool = True
If True (default), annotate all variable bindings with the struct
info of that variable. If False, only add annotations where
required for unambiguous round-trip of Relax -> TVMScript -> Relax.
path_to_underline : Optional[List[AccessPath]] = None
Object path to be underlined
path_to_annotate : Optional[Dict[AccessPath, str]] = None
Object path to be annotated
obj_to_underline : Optional[List[Object]] = None
Object to be underlined
obj_to_annotate : Optional[Dict[Object, str]] = None
Object to be annotated
"""
from tvm.script.highlight import cprint # pylint: disable=import-outside-toplevel
if black_format is None:
env = os.environ.get("TVM_BLACK_FORMAT")
black_format = env and int(env)
cprint(
self.script(
name=name,
show_meta=show_meta,
ir_prefix=ir_prefix,
tir_prefix=tir_prefix,
tir_import_module=tir_import_module,
relax_prefix=relax_prefix,
module_alias=module_alias,
buffer_dtype=buffer_dtype,
int_dtype=int_dtype,
float_dtype=float_dtype,
verbose_expr=verbose_expr,
indent_spaces=indent_spaces,
print_line_numbers=print_line_numbers,
num_context_lines=num_context_lines,
syntax_sugar=syntax_sugar,
show_object_address=show_object_address,
show_all_struct_info=show_all_struct_info,
path_to_underline=path_to_underline,
path_to_annotate=path_to_annotate,
obj_to_underline=obj_to_underline,
obj_to_annotate=obj_to_annotate,
),
style=style,
black_format=black_format,
)