7bd738a00b
* [TVMScript][Bugfix] Normalize relax::If with function's TIR var Prior to this commit, the branches of `relax::If` were normalized using `EraseToWellDefinedInScope`, using a fresh variable scope. While this had the intended behavior of preventing variables defined in a single branch from being usable outside of the conditional, it also caused the conditional's branches to treat function-scope symbolic variables as if they were undefined. This commit updates the `tvm::relax::Normalizer` so that `relax::If` is normalized within an inherited scope. This preserves the previous behavior for symbolic variables defined within a branch, but allows shapes within a branch to use symbolic variables defined outside of the branch. * [Relax] Canonicalize known symbolic shapes in Relax expressions Prior to this commit, known constants in Relax functions would be inlined by the `CanonicalizeBindings` pass, but only if they appeared as Relax expressions (e.g. `R.const` or `R.prim_value`). Known constants that appeared as TIR variables (e.g. symbolic shapes) would be kept as dynamic parameters, even if they were known at compile time. This commit updates the `CanonicalizeBindings` pass to identify known values of symbolic shapes, and to use these known values in shape expressions. * [Relax][Refactor] Reorganize pattern-matching A follow-up to https://github.com/apache/tvm/pull/16730. Now that the implementations for `rewrite_call` and `rewrite_bindings` are in separate classes, they can be further split out into separate files. * [Relax][Refactor] Implement Rewriter class for pattern-rewrite Prior to this commit, the pattern to be matched and the rewrite to be performed were provided as separate arguments. This commit introduces a new class `ExprRewriter`, which contains both parts. This abstraction will make it easier to combine multiple different rewrite rules, applying them in a single pass. * lint fixes * Remove unnecessary change which broke a unit test * lint fix for import order * Add docstrings * lint fix * Lint fix * lint fixes * lint fix * Update based on review comments * Add test case for matching against arbitrary dtype * Fix breakage in unit tests One unit test that had been relying on invalid shape propagation. Another unit test that required constructed an ill-formed output to test against. * Updated base class name from ExprRewriter to PatternMatchingRewriter * lint fix
129 lines
3.9 KiB
Python
129 lines
3.9 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.
|
|
"""TVM Script Parser utils"""
|
|
import inspect
|
|
from types import FrameType
|
|
from typing import Any, Callable, Dict, List
|
|
|
|
from .diagnostics import findsource
|
|
|
|
|
|
def get_func_nonlocals(func):
|
|
"""A modified version of `inspect.getclosurevars`"""
|
|
|
|
if inspect.ismethod(func):
|
|
func = func.__func__
|
|
|
|
if not inspect.isfunction(func):
|
|
raise TypeError("{!r} is not a Python function".format(func))
|
|
|
|
code = func.__code__
|
|
# Nonlocal references are named in co_freevars and resolved
|
|
# by looking them up in __closure__ by positional index
|
|
nonlocal_vars = {}
|
|
if func.__closure__ is not None:
|
|
for var, cell in zip(code.co_freevars, func.__closure__):
|
|
try:
|
|
nonlocal_vars[var] = cell.cell_contents
|
|
except ValueError as err:
|
|
# cell_contents may raise ValueError if the cell is empty.
|
|
if "empty" not in str(err):
|
|
raise
|
|
return nonlocal_vars
|
|
|
|
|
|
def inspect_function_capture(func: Callable) -> Dict[str, Any]:
|
|
"""Capture function non-locals and global variables.
|
|
|
|
Parameters
|
|
----------
|
|
func : Callable
|
|
The function to inspect.
|
|
|
|
Returns
|
|
-------
|
|
res : Dict[str, Any]
|
|
The function variables map with non-local or global variables.
|
|
"""
|
|
captured = {
|
|
**func.__globals__, # type: ignore
|
|
**get_func_nonlocals(func),
|
|
}
|
|
return captured
|
|
|
|
|
|
def inspect_class_capture(cls: type) -> Dict[str, Any]:
|
|
"""Capture class non-locals and global variables.
|
|
|
|
Parameters
|
|
----------
|
|
cls : type
|
|
The class to inspect.
|
|
|
|
Returns
|
|
-------
|
|
res : Dict[str, Any]
|
|
The class variables map with non-local or global variables.
|
|
"""
|
|
result: Dict[str, Any] = {}
|
|
for _, v in cls.__dict__.items():
|
|
if inspect.isfunction(v):
|
|
func_vars = inspect_function_capture(v)
|
|
result.update(**func_vars)
|
|
return result
|
|
|
|
|
|
def is_defined_in_class(frames: List[FrameType], obj: Any) -> bool:
|
|
"""Check whether a object is defined in a class scope.
|
|
|
|
Parameters
|
|
----------
|
|
frames : List[FrameType]
|
|
The frame stack of the object, obtained by `inspect.stack()`.
|
|
|
|
Returns
|
|
-------
|
|
res : bool
|
|
The result if the object is defined in a class scope.
|
|
"""
|
|
|
|
def _is_tvmscript_class_annotator(line: str) -> bool:
|
|
"""Checks if the line contains a TVMScript annotator for a class
|
|
|
|
These match either `@I.ir_module` or `@R.rewriter`, or their
|
|
imported names `@ir_module` or `@rewriter`.
|
|
"""
|
|
|
|
return line.startswith("@") and ("ir_module" in line or "rewriter" in line)
|
|
|
|
if len(frames) > 2:
|
|
frame_info = frames[2]
|
|
code_context = frame_info.code_context
|
|
if code_context is None:
|
|
return False
|
|
line = code_context[0].strip()
|
|
if _is_tvmscript_class_annotator(line):
|
|
return True
|
|
if line.startswith("class"):
|
|
lineno = frame_info.lineno
|
|
if lineno >= 2:
|
|
source, _ = findsource(obj)
|
|
line = source[lineno - 2].strip()
|
|
if _is_tvmscript_class_annotator(line):
|
|
return True
|
|
return False
|