This is an automated email from the ASF dual-hosted git repository.
tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new 18f246fb2e [PERF][TVMSCRIPT] Use lightweight module decorator
detection (#20435)
18f246fb2e is described below
commit 18f246fb2e40686ec7864869eb6650d480ee481f
Author: Tianqi Chen <[email protected]>
AuthorDate: Thu Sep 24 17:13:57 2026 -0400
[PERF][TVMSCRIPT] Use lightweight module decorator detection (#20435)
Replace the per-member full-source AST scan with a source-text check of
the enclosing class declaration. `_is_inside_ir_module` uses the
existing caller frames to recognize `ir_module`, `py_module`, and
`rewriter` spellings, including decorator options, while retaining the
non-class fast path.
---
python/tvm/script/parser/entry.py | 43 +++++++++++++++------------------------
1 file changed, 16 insertions(+), 27 deletions(-)
diff --git a/python/tvm/script/parser/entry.py
b/python/tvm/script/parser/entry.py
index 44aae05d9d..39312c8d27 100644
--- a/python/tvm/script/parser/entry.py
+++ b/python/tvm/script/parser/entry.py
@@ -43,7 +43,7 @@ from .inspect_source import (
capture_definition_scope,
capture_lexical_bindings,
)
-from .prescan import PrescanCollector, resolve_namespace_key
+from .prescan import PrescanCollector
from .transpile import FunctionContext, IRBuilderTranspiler, ModuleContext
if TYPE_CHECKING:
@@ -179,35 +179,24 @@ def _recompose_builder(
return namespace.pop(name)
-def _is_inside_class(function: FunctionType, frame: FrameType) -> bool:
- """Defer only in the exact class frame of a registered module decorator."""
+def _is_inside_ir_module(function: FunctionType, frame: FrameType) -> bool:
+ """Recognize module decorators from the enclosing class declaration."""
local = frame.f_locals
if local.get("__module__") != function.__module__ or "__qualname__" not in
local:
return False
- text = "".join(linecache.getlines(frame.f_code.co_filename))
- if not text:
+ caller = frame.f_back
+ if caller is None:
return False
- classes = [
- node
- for node in ast.walk(ast.parse(text))
- if isinstance(node, ast.ClassDef)
- and node.name == frame.f_code.co_name
- and node.lineno <= frame.f_lineno <= node.end_lineno
- ]
- if not classes:
- return False
- node = min(classes, key=lambda item: item.end_lineno - item.lineno)
- environment = dict(frame.f_globals)
- if frame.f_back is not None:
- environment.update(frame.f_back.f_locals)
-
- return any(
- syntax_protocol.MODULE_DECORATOR.get(
- resolve_namespace_key(item.func if isinstance(item, ast.Call) else
item, environment),
- False,
- )
- for item in node.decorator_list
- )
+ # The class code starts at its first decorator, while its caller is still
+ # executing the class declaration. Read only those lines, including options
+ # spread across multiple lines, rather than parsing the file for each
member.
+ for lineno in range(frame.f_code.co_firstlineno, caller.f_lineno + 1):
+ line = linecache.getline(frame.f_code.co_filename, lineno).strip()
+ if line.startswith("@") and any(
+ name in line for name in ("ir_module", "py_module", "rewriter")
+ ):
+ return True
+ return False
def make_decorator(
@@ -293,7 +282,7 @@ def make_decorator(
try:
if frame.f_code is decorator.__code__:
frame = frame.f_back
- deferred = _is_inside_class(function, frame)
+ deferred = _is_inside_ir_module(function, frame)
# The module root supplies one scope for all deferred members.
definition_scope = {} if deferred else
capture_definition_scope(frame)
finally: