211 lines
6.8 KiB
Python
211 lines
6.8 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.script.tirx import tile as _tirx_tile # 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_tile,
|
|
"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.check_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
|