mirror of
https://github.com/pytorch/pytorch.git
synced 2025-10-20 21:14:14 +08:00
Mostly fixes for PEP-484 violation (i.e. when default arg is set to None, but type is not annotated as optional) Plus few real fixes: - Add missing `_get_upgraders_entry_map` to `torch/_C/__init__.pyi` - Add missing return statement to `torch._export. deserialize_graph` - Fix error message in `torch.ao.ns.fx.weight_utils.get_lstm_mod_weights` - TODO (in followup PR): - Fix erroneous `isinstance` check in `torch/ao/quantization/_pt2e/qat_utils.py` Pull Request resolved: https://github.com/pytorch/pytorch/pull/91983 Approved by: https://github.com/kit1980, https://github.com/ZainRizvi, https://github.com/huydhn, https://github.com/thiagocrepaldi, https://github.com/aaronenyeshi
153 lines
6.6 KiB
Python
153 lines
6.6 KiB
Python
from types import TracebackType
|
|
import tempfile
|
|
import traceback
|
|
import contextlib
|
|
import inspect
|
|
import os.path
|
|
|
|
# This file contains utilities for ensuring dynamically compile()'d
|
|
# code fragments display their line numbers in backtraces.
|
|
#
|
|
# The constraints:
|
|
#
|
|
# - We don't have control over the user exception printer (in particular,
|
|
# we cannot assume the linecache trick will work, c.f.
|
|
# https://stackoverflow.com/q/50515651/23845 )
|
|
#
|
|
# - We don't want to create temporary files every time we compile()
|
|
# some code; file creation should happen lazily only at exception
|
|
# time. Arguably, you *should* be willing to write out your
|
|
# generated Python code to file system, but in some situations
|
|
# (esp. library code) it would violate user expectation to write
|
|
# to the file system, so we try to avoid it. In particular, we'd
|
|
# like to keep the files around, so users can open up the files
|
|
# mentioned in the trace; if the file is invisible, we want to
|
|
# avoid clogging up the filesystem.
|
|
#
|
|
# If this is not a constraint for you, there is a substantially simpler
|
|
# way to implement the functionality in this PR: instead of using
|
|
# eval/exec directly, just always write a Python file to filesystem
|
|
# and compile that.
|
|
#
|
|
# - You have control over a context where the compiled code will get
|
|
# executed, so that we can interpose while the stack is unwinding
|
|
# (otherwise, we have no way to interpose on the exception printing
|
|
# process.)
|
|
#
|
|
# There are two things you have to do to make use of the utilities here:
|
|
#
|
|
# - When you compile your source code, you must save its string source
|
|
# in its f_globals under the magic name "__compile_source__"
|
|
#
|
|
# - Before running the compiled code, enter the
|
|
# report_compile_source_on_error() context manager.
|
|
|
|
@contextlib.contextmanager
|
|
def report_compile_source_on_error():
|
|
try:
|
|
yield
|
|
except Exception as exc:
|
|
tb = exc.__traceback__
|
|
|
|
# Walk the traceback, looking for frames that have
|
|
# source attached
|
|
stack = []
|
|
while tb is not None:
|
|
filename = tb.tb_frame.f_code.co_filename
|
|
source = tb.tb_frame.f_globals.get("__compile_source__")
|
|
|
|
if filename == "<string>" and source is not None:
|
|
# What black magic are we doing here? Intuitively, what
|
|
# we would like to do is overwrite the co_filename on any
|
|
# frames that were generated from exec/eval so that they
|
|
# point to a temporary file that has the actual line
|
|
# information, so Python's default error printer can print
|
|
# useful line information on it.
|
|
#
|
|
# Writing out the temporary file is easy. But overwriting
|
|
# co_filename is not! You can't modify the code object
|
|
# associated with a frame. You can, however, reconstruct
|
|
# a traceback with entirely new frames from scratch, so that's
|
|
# what we do. But there's another problem, which is how to
|
|
# make the frame?
|
|
#
|
|
# The black magic is we make a frankenstein frame and code
|
|
# object which resembles the original frame/code enough so
|
|
# that it will print properly under traceback and the default
|
|
# error printer, but IT IS NOT THE ORIGINAL FRAME (you
|
|
# couldn't, e.g., execute its code with different variables
|
|
# and expect it to work.)
|
|
|
|
# Don't delete the temporary file so the user can inspect it
|
|
# TODO: This creates a temporary file for every frame, but we
|
|
# technically only need one per distinct __compile_source__
|
|
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix=".py") as f:
|
|
f.write(source)
|
|
# Create a frame. Python doesn't let you construct
|
|
# FrameType directly, so just make one with compile
|
|
frame = tb.tb_frame
|
|
code = compile('__inspect_currentframe()', f.name, 'eval')
|
|
code = code.replace(co_name=frame.f_code.co_name)
|
|
# Python 3.11 only
|
|
if hasattr(frame.f_code, 'co_linetable'):
|
|
# We can't copy ALL of the metadata over, because you
|
|
# can cause Python to segfault this way. What exactly
|
|
# do we need? We need enough information for
|
|
# traceback to be able to print the exception
|
|
# correctly. Code reading Lib/traceback.py reveals
|
|
# that traceback calls code.co_positions() in order to
|
|
# get the augmented line/col numbers. Objects/codeobject.c,
|
|
# specifically _PyCode_InitAddressRange, reveals that
|
|
# this iterator is initialized from co_linetable and
|
|
# co_firstfileno. So copy these we must!
|
|
code = code.replace( # type: ignore[call-arg]
|
|
co_linetable=frame.f_code.co_linetable, # type: ignore[attr-defined]
|
|
co_firstlineno=frame.f_code.co_firstlineno, # type: ignore[attr-defined]
|
|
)
|
|
fake_frame = eval(
|
|
code,
|
|
frame.f_globals,
|
|
{
|
|
**frame.f_locals,
|
|
'__inspect_currentframe': inspect.currentframe
|
|
}
|
|
)
|
|
fake_tb = TracebackType(
|
|
None, fake_frame, tb.tb_lasti, tb.tb_lineno
|
|
)
|
|
stack.append(fake_tb)
|
|
else:
|
|
stack.append(tb)
|
|
|
|
tb = tb.tb_next
|
|
|
|
# Reconstruct the linked list
|
|
tb_next = None
|
|
for tb in reversed(stack):
|
|
tb.tb_next = tb_next
|
|
tb_next = tb
|
|
|
|
raise exc.with_traceback(tb_next)
|
|
|
|
def shorten_filename(fn):
|
|
"""
|
|
Shorten a source filepath, under the assumption that anything under torch/
|
|
directory is "obvious" and doesn't need to be shown to user.
|
|
"""
|
|
# Truncate torch/foo.py to foo.py
|
|
prefix = os.path.commonprefix([fn, os.path.join(os.path.dirname(os.path.dirname(__file__)), "")])
|
|
return fn[len(prefix):]
|
|
|
|
def format_frame(frame):
|
|
"""
|
|
Format a FrameSummary in a short way, without printing full absolute path
|
|
or code. The idea is the result fits on a single line.
|
|
"""
|
|
return f"{shorten_filename(frame.filename)}:{frame.lineno} in {frame.name}"
|
|
|
|
def format_traceback_short(tb):
|
|
"""
|
|
Format a TracebackType in a short way, printing only the inner-most frame.
|
|
"""
|
|
return format_frame(traceback.extract_tb(tb)[-1])
|