exc.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803
  1. from __future__ import annotations
  2. """Exception handling and error reporting for TorchDynamo.
  3. This module provides a comprehensive set of exception classes and utilities for error
  4. handling in TorchDynamo. It includes:
  5. Base Exceptions:
  6. - TorchDynamoException: Base class for all TorchDynamo-specific exceptions
  7. - Various specialized subclasses for different error scenarios
  8. User Error Handling:
  9. - UserError: Exceptions for user-facing errors in TorchDynamo usage
  10. - UserErrorType: Enumeration of different categories of user errors
  11. - Formatted error messages with debugging information
  12. Observed Exceptions:
  13. - Classes for handling exceptions observed during tracing
  14. - Special handling for StopIteration, LookupError, etc.
  15. - Exception state management during compilation
  16. Error Formatting:
  17. - Stack trace filtering and formatting
  18. - Error message augmentation
  19. - Debugging utilities for error reporting
  20. """
  21. import json
  22. import logging
  23. import os
  24. import re
  25. import textwrap
  26. import typing
  27. from enum import auto, Enum
  28. from functools import lru_cache
  29. from pathlib import Path
  30. from traceback import extract_stack, format_exc, format_list, StackSummary
  31. from typing import Any, NoReturn, Optional, TYPE_CHECKING
  32. import torch._guards
  33. from torch._utils_internal import get_file_path_2
  34. from . import config
  35. from .utils import counters
  36. if TYPE_CHECKING:
  37. import types
  38. from torch._guards import CompileId
  39. from .output_graph import DynamoTracerOutput
  40. from .symbolic_convert import InstructionTranslatorBase
  41. from .types import DynamoFrameType
  42. def exportdb_error_message(case_name: str) -> str:
  43. return (
  44. "For more information about this error, see: "
  45. + "https://pytorch.org/docs/main/generated/exportdb/index.html#"
  46. + case_name.replace("_", "-")
  47. )
  48. log = logging.getLogger(__name__)
  49. graph_breaks_log = torch._logging.getArtifactLogger(__name__, "graph_breaks")
  50. class TorchDynamoException(RuntimeError):
  51. def __init__(self, *args: Any, **kwargs: Any) -> None:
  52. super().__init__(*args, **kwargs)
  53. self._torch_dynamo_tracer_output: Optional[DynamoTracerOutput] = None
  54. class InternalTorchDynamoError(TorchDynamoException):
  55. pass
  56. class ResumePrologueTracingError(TorchDynamoException):
  57. pass
  58. class RestartAnalysis(TorchDynamoException):
  59. restart_reason: Optional[str]
  60. def __init__(self, *args: Any, restart_reason: Optional[str] = None) -> None:
  61. self.restart_reason = restart_reason
  62. super().__init__(*args)
  63. class SpeculationRestartAnalysis(RestartAnalysis):
  64. pass
  65. class UnspecializeRestartAnalysis(RestartAnalysis):
  66. pass
  67. class CompileCollectiveRestartAnalysis(RestartAnalysis):
  68. pass
  69. class TensorifyScalarRestartAnalysis(RestartAnalysis):
  70. pass
  71. class SkipFrame(TorchDynamoException):
  72. pass
  73. class TorchRuntimeError(TorchDynamoException):
  74. pass
  75. class InvalidBackend(TorchDynamoException):
  76. def __init__(self, name: str) -> None:
  77. super().__init__(
  78. f"Invalid backend: {name!r}, see `torch._dynamo.list_backends()` for available backends."
  79. )
  80. class ResetRequired(TorchDynamoException):
  81. def __init__(self) -> None:
  82. super().__init__(
  83. textwrap.dedent(
  84. """
  85. Must call `torch._dynamo.reset()` before changing backends. Detected two calls to
  86. `torch.compile()` with a different backend compiler arguments.
  87. """
  88. )
  89. )
  90. class ShortenTraceback(TorchDynamoException):
  91. def __init__(
  92. self, *args: Any, first_useful_frame: Optional[types.FrameType], **kwargs: Any
  93. ) -> None:
  94. super().__init__(*args, **kwargs)
  95. self.first_useful_frame = first_useful_frame
  96. def remove_dynamo_frames(self) -> typing.Self:
  97. tb = self.__traceback__
  98. if self.first_useful_frame is None or tb is None or config.verbose:
  99. return self
  100. while tb.tb_frame is not self.first_useful_frame:
  101. tb = tb.tb_next
  102. assert tb is not None, "internal error, please report a bug"
  103. return self.with_traceback(tb)
  104. class BackendCompilerFailed(ShortenTraceback):
  105. def __init__(
  106. self,
  107. backend_fn: Any,
  108. inner_exception: Exception,
  109. first_useful_frame: Optional[types.FrameType],
  110. ) -> None:
  111. self.backend_name = getattr(backend_fn, "__name__", "?")
  112. self.inner_exception = inner_exception
  113. msg = f"backend={self.backend_name!r} raised:\n{type(inner_exception).__name__}: {inner_exception}"
  114. super().__init__(msg, first_useful_frame=first_useful_frame)
  115. class Unsupported(TorchDynamoException):
  116. def __init__(self, msg: str, *, case_name: Optional[str] = None) -> None:
  117. super().__init__(msg)
  118. self.real_stack = torch._guards.TracingContext.extract_stack()
  119. self.msg = msg
  120. self.category: Optional[str] = None
  121. self.add_to_stats()
  122. self.case_name: Optional[str] = case_name
  123. def remove_from_stats(self) -> None:
  124. assert self.category is not None
  125. counters[self.category][self.msg] -= 1
  126. if counters[self.category][self.msg] <= 0:
  127. del counters[self.category][self.msg]
  128. def add_to_stats(self, category: str = "unimplemented") -> None:
  129. self.category = category
  130. counters[category][self.msg] += 1
  131. class UnknownPropertiesDuringBackwardTrace(Unsupported):
  132. pass
  133. class RecompileError(TorchDynamoException):
  134. pass
  135. class ArgsMismatchError(Unsupported):
  136. def __init__(self, msg: str) -> None:
  137. super().__init__(msg)
  138. class AttributeMutationError(Unsupported):
  139. def __init__(self, msg: str) -> None:
  140. super().__init__(msg)
  141. class InfiniteGeneratorError(Unsupported):
  142. # Raised when the number of yielded values is greater than MAX_ITERATOR_LIMIT
  143. def __init__(self, msg: str) -> None:
  144. super().__init__(msg)
  145. class SideEffectsError(Unsupported):
  146. def __init__(self, msg: str) -> None:
  147. super().__init__(msg)
  148. class CondOpArgsMismatchError(ArgsMismatchError):
  149. """
  150. Internal error from cond() due to arguments mismatch.
  151. """
  152. def __init__(self, msg: str) -> None:
  153. super().__init__(msg)
  154. class UserErrorType(Enum):
  155. DYNAMIC_CONTROL_FLOW = auto()
  156. ANTI_PATTERN = auto()
  157. STANDARD_LIBRARY = auto()
  158. CONSTRAINT_VIOLATION = auto()
  159. DYNAMIC_DIM = auto()
  160. INVALID_INPUT = auto()
  161. INVALID_OUTPUT = auto()
  162. UNSUPPORTED_ALIASED_MUTATED_DYNAMIC_INPUTS = auto()
  163. class UserError(Unsupported):
  164. def __init__(
  165. self, error_type: UserErrorType, msg: str, case_name: Optional[str] = None
  166. ) -> None:
  167. """
  168. Type of errors that would be valid in Eager, but not supported in TorchDynamo.
  169. The error message should tell user about next actions.
  170. error_type: Type of user error
  171. msg: Actionable error message
  172. case_name: (Optional) Unique name (snake case) for the usage example in exportdb.
  173. """
  174. if case_name is not None:
  175. assert isinstance(case_name, str)
  176. if msg.endswith("."):
  177. msg += " "
  178. else:
  179. msg += "\n"
  180. msg += exportdb_error_message(case_name)
  181. super().__init__(msg)
  182. self.error_type = error_type
  183. self.message = msg
  184. class SkipCodeRecursiveException(TorchDynamoException):
  185. pass
  186. class RecompileLimitExceeded(Unsupported):
  187. pass
  188. class UnsafeScriptObjectError(TorchDynamoException):
  189. pass
  190. class UncapturedHigherOrderOpError(TorchDynamoException):
  191. def __init__(self, msg: str, real_stack: Optional[StackSummary] = None) -> None:
  192. super().__init__(msg)
  193. self.msg = msg
  194. self.real_stack = (
  195. real_stack
  196. if real_stack is not None
  197. else torch._guards.TracingContext.extract_stack()
  198. )
  199. class IncorrectUsage(Exception):
  200. pass
  201. # TODO: I'm a little uncertain about what error classification we should have
  202. # for this. This is potentially a user error, but regressions in
  203. # specialization in PyTorch proper could also trigger this problem
  204. class FailOnRecompileLimitHit(Exception):
  205. pass
  206. class PackageError(TorchDynamoException):
  207. pass
  208. class ObservedException(TorchDynamoException):
  209. # An exception observed during the tracing. This exception is used by Dynamo to handle exceptions.
  210. pass
  211. class ObservedUserStopIteration(ObservedException):
  212. # An UserStopIteration exception observed during the Dynamo tracing (e.g Dynamo tracing __next__)
  213. value: Optional[Any]
  214. # Reference `StopIteration_init` in CPython
  215. # https://github.com/python/cpython/blob/3.11/Objects/exceptions.c#L568-L584
  216. def __init__(self, *args: Any, **kwargs: Any) -> None:
  217. super().__init__("unhandled `raise StopIteration`")
  218. if len(args) > 0:
  219. self.value = args[0]
  220. else:
  221. self.value = None
  222. class ObservedLookupError(ObservedException):
  223. # A LookupError exception to be raised from inside Dynamo tracing. This can happen on __getitem__
  224. pass
  225. class ObservedIndexError(ObservedLookupError):
  226. # An IndexError exception to be raised from inside Dynamo tracing. This can happen on list __getitem__
  227. pass
  228. class ObservedKeyError(ObservedLookupError):
  229. # A KeyError exception to be raised from inside Dynamo tracing. This can happen on dict __getitem__
  230. pass
  231. class ObservedGeneratorExit(ObservedException):
  232. pass
  233. class ObservedAttributeError(ObservedException):
  234. # An AttributeError exception to be raised from inside Dynamo tracing. This can happen on user defined object __getattr__
  235. pass
  236. class ObservedRuntimeError(ObservedException):
  237. # A RuntimeError exception to be raised from inside Dynamo tracing. This can happen on generator.throw(..) method
  238. pass
  239. class ObservedNotImplementedError(ObservedException):
  240. pass
  241. class ObservedTypeError(ObservedException):
  242. # A TypeError exception to be raised from inside Dynamo tracing. This can happen on generator.send(..) method
  243. pass
  244. observed_exception_map = {
  245. StopIteration: ObservedUserStopIteration,
  246. LookupError: ObservedLookupError,
  247. IndexError: ObservedIndexError,
  248. GeneratorExit: ObservedGeneratorExit,
  249. KeyError: ObservedKeyError,
  250. AttributeError: ObservedAttributeError,
  251. RuntimeError: ObservedRuntimeError,
  252. NotImplementedError: ObservedNotImplementedError,
  253. TypeError: ObservedTypeError,
  254. }
  255. def get_dynamo_observed_exception(exc_type: type[Exception]) -> type[ObservedException]:
  256. if exc_type not in observed_exception_map:
  257. name = getattr(exc_type, "__name__", str(exc_type))
  258. observed_exception_map[exc_type] = type( # type: ignore[assignment]
  259. f"Observed{name}Error", (ObservedException,), {}
  260. )
  261. return observed_exception_map[exc_type]
  262. def raise_observed_exception(
  263. exc_type: type[Exception],
  264. tx: InstructionTranslatorBase,
  265. *,
  266. args: Optional[list[Any]] = None,
  267. kwargs: Optional[dict[str, Any]] = None,
  268. ) -> NoReturn:
  269. from .variables import BuiltinVariable
  270. # CPython here raises an exception. Since there is no python code, we have to manually setup the exception
  271. # stack and raise the exception.
  272. exception_vt = BuiltinVariable(exc_type).call_function(tx, args or [], kwargs or {}) # type: ignore[arg-type]
  273. tx.exn_vt_stack.set_current_exception(exception_vt) # type: ignore[arg-type]
  274. raise get_dynamo_observed_exception(exc_type)
  275. def handle_observed_exception(tx: Any) -> None:
  276. # This is essentially exception handling code, equivalent of this pseudo code
  277. #
  278. # try:
  279. # ... somebody raising StopIteration
  280. # except StopIteration
  281. # pass
  282. #
  283. # If this was going through the python code, we would have called exception_handler method, but FOR_ITER
  284. # handles the exception completely in CPython. For example for 3.11, the resulting bytecode is
  285. #
  286. #
  287. # 6 46 LOAD_GLOBAL 2 (StopIteration)
  288. # 58 RAISE_VARARGS 1
  289. # >> 60 PUSH_EXC_INFO
  290. # 7 62 LOAD_GLOBAL 2 (StopIteration)
  291. # 74 CHECK_EXC_MATCH
  292. # 76 POP_JUMP_FORWARD_IF_FALSE 3 (to 84)
  293. # 78 POP_TOP
  294. # 8 80 POP_EXCEPT
  295. #
  296. # Fortunately this translates to a simple pop from the exn_vt_stack
  297. tx.exn_vt_stack.clear_current_exception()
  298. # These exceptions are ok to fallback to eager/graph_break.
  299. exceptions_allowed_to_be_fallback = (
  300. torch._subclasses.fake_tensor.DataDependentOutputException,
  301. torch._subclasses.fake_tensor.DynamicOutputShapeException,
  302. torch._subclasses.fake_tensor.UnsupportedOperatorException,
  303. torch._subclasses.fake_tensor.UnsupportedFakeTensorException,
  304. torch._subclasses.fake_tensor.UnsupportedMutationAliasingException,
  305. )
  306. def unimplemented_with_warning(
  307. e: Exception, code: types.CodeType, msg: str
  308. ) -> NoReturn:
  309. # This function calls unimplemented internally and eventually graph breaks
  310. # or falls to eager. unimplemented itself does not print any user warnings,
  311. # i.e., its very silent. This helper function is intended when an error is
  312. # encountered in the torch.compile stack which is worth showing as warning
  313. # to the user. For example, if AOT Autograd backend fails with a fake tensor
  314. # exception, its ok to fallback to eager but not silently. Here, we can use
  315. # this function to log the message and the stack trace.
  316. graph_break_msg = format_error_msg_verbose(e, code)
  317. torch._logging.trace_structured(
  318. "artifact",
  319. metadata_fn=lambda: {
  320. "name": "dynamo_graph_break_reason",
  321. "encoding": "string",
  322. },
  323. payload_fn=lambda: graph_break_msg,
  324. )
  325. graph_breaks_log.debug("%s", graph_break_msg)
  326. log.warning(msg)
  327. unimplemented(msg, from_exc=e)
  328. _NOTHING = object()
  329. def unimplemented(
  330. msg: str, *, from_exc: Any = _NOTHING, case_name: Optional[str] = None
  331. ) -> NoReturn:
  332. assert msg != os.environ.get("BREAK", False)
  333. if from_exc is not _NOTHING:
  334. raise Unsupported(msg, case_name=case_name) from from_exc
  335. raise Unsupported(msg, case_name=case_name)
  336. def unimplemented_v2_with_warning(
  337. e: Exception,
  338. code: types.CodeType,
  339. gb_type: str,
  340. context: str,
  341. explanation: str,
  342. hints: list[str],
  343. ) -> NoReturn:
  344. # This function calls unimplemented internally and eventually graph breaks
  345. # or falls to eager. unimplemented itself does not print any user warnings,
  346. # i.e., its very silent. This helper function is intended when an error is
  347. # encountered in the torch.compile stack which is worth showing as warning
  348. # to the user. For example, if AOT Autograd backend fails with a fake tensor
  349. # exception, its ok to fallback to eager but not silently. Here, we can use
  350. # this function to log the message and the stack trace.
  351. graph_break_msg = format_error_msg_verbose(e, code)
  352. torch._logging.trace_structured(
  353. "artifact",
  354. metadata_fn=lambda: {
  355. "name": "dynamo_graph_break_reason",
  356. "encoding": "string",
  357. },
  358. payload_fn=lambda: graph_break_msg,
  359. )
  360. graph_breaks_log.debug("%s", graph_break_msg)
  361. unimplemented_v2(gb_type, context, explanation, hints, from_exc=e, log_warning=True)
  362. def format_graph_break_message(
  363. gb_type: str,
  364. context: str,
  365. explanation: str,
  366. hints: list[str],
  367. ) -> str:
  368. explanation = textwrap.indent(explanation, " ").lstrip()
  369. hints_str = "\n".join(
  370. " Hint: " + textwrap.indent(hint, " ").lstrip() for hint in hints
  371. )
  372. context = textwrap.indent(context, " ").lstrip()
  373. msg = f"""\
  374. {gb_type}
  375. Explanation: {explanation}
  376. {hints_str}
  377. Developer debug context: {context}
  378. """
  379. return msg
  380. @lru_cache(maxsize=1)
  381. def _load_gb_type_to_gb_id_map() -> dict[str, Any]:
  382. """
  383. Loads the gb_type to gb_id map from the graph break registry from JSON file with caching.
  384. Includes historical gb_type (mapping behavior of duplicate gb_types with different gb_ids is undefined).
  385. """
  386. try:
  387. script_dir = Path(__file__).resolve().parent
  388. registry_path = get_file_path_2(
  389. "", str(script_dir), "graph_break_registry.json"
  390. )
  391. with open(registry_path) as f:
  392. registry = json.load(f)
  393. except Exception as e:
  394. log.error("Error accessing the registry file: %s", e)
  395. registry = {}
  396. mapping = {}
  397. for k, v in registry.items():
  398. for entry in v:
  399. mapping[entry["Gb_type"]] = k
  400. return mapping
  401. def get_gbid_documentation_link(gb_type: str) -> Optional[str]:
  402. """
  403. Retrieves the GBID documentation link for a given graph break type.
  404. Args:
  405. gb_type: The graph break type to look up.
  406. Returns:
  407. A string containing the documentation URL if found, otherwise None.
  408. """
  409. GRAPH_BREAK_SITE_URL = (
  410. "https://meta-pytorch.github.io/compile-graph-break-site/gb/" # @lint-ignore
  411. )
  412. gb_type_to_gb_id_map = _load_gb_type_to_gb_id_map()
  413. if gb_type in gb_type_to_gb_id_map:
  414. return (
  415. f"{GRAPH_BREAK_SITE_URL}gb{gb_type_to_gb_id_map[gb_type].lstrip('GB')}.html"
  416. )
  417. return None
  418. # TODO replace old unimplemented later
  419. def unimplemented_v2(
  420. gb_type: str,
  421. context: str,
  422. explanation: str,
  423. hints: list[str],
  424. *,
  425. from_exc: Any = _NOTHING,
  426. log_warning: bool = False,
  427. ) -> NoReturn:
  428. """
  429. Called within dynamo to cause a graph break.
  430. Args:
  431. gb_type: Context-free graph break type. It should be a short string without any
  432. information specific to the tracing context (i.e. no dynamically-generated strings)
  433. context: Developer context for the graph break. It can contain tracing context/dynamic strings.
  434. explanation: User-facing context-dependent explanation for the graph break. Can be dynamic.
  435. hints: List of user-facing hints for the graph break.
  436. """
  437. msg = format_graph_break_message(gb_type, context, explanation, hints)
  438. documentation_link = get_gbid_documentation_link(gb_type)
  439. if documentation_link:
  440. msg += f"\n For more details about this graph break, please visit: {documentation_link}"
  441. if log_warning:
  442. log.warning(msg)
  443. if from_exc is not _NOTHING:
  444. raise Unsupported(msg) from from_exc
  445. raise Unsupported(msg)
  446. # KeyError has special handling for its args
  447. # see https://github.com/python/cpython/blob/3.11/Objects/exceptions.c#L2534 for details
  448. class KeyErrorMsg:
  449. def __init__(self, value: Any) -> None:
  450. self.value = value
  451. def __str__(self) -> str:
  452. return str(self.value)
  453. def __repr__(self) -> str:
  454. return self.__str__()
  455. def augment_exc_message(exc: Exception, msg: str = "\n", export: bool = False) -> None:
  456. import traceback
  457. exc.innermost_user_frame_summary = None # type: ignore[attr-defined]
  458. real_stack = get_real_stack(exc)
  459. if real_stack is not None and len(real_stack) > 0:
  460. exc.innermost_user_frame_summary = real_stack[-1] # type: ignore[attr-defined]
  461. msg += f"\nfrom user code:\n {''.join(traceback.format_list(real_stack))}"
  462. if config.replay_record_enabled and hasattr(exc, "record_filename"):
  463. msg += (
  464. f"\nLast frame execution written to {exc.record_filename}. To run only this frame while debugging, run\
  465. torch._dynamo.replay('{exc.record_filename}').\n"
  466. )
  467. if not config.verbose and hasattr(exc, "real_stack"):
  468. msg += (
  469. "\nSet TORCHDYNAMO_VERBOSE=1 for the internal stack trace "
  470. "(please do this especially if you're reporting a bug to PyTorch). "
  471. 'For even more developer context, set TORCH_LOGS="+dynamo"\n'
  472. )
  473. if hasattr(exc, "inner_exception") and hasattr(
  474. exc.inner_exception, "minifier_path"
  475. ):
  476. if hasattr(exc.inner_exception, "buck_command"):
  477. msg += (
  478. f"\nMinifier script written to {exc.inner_exception.minifier_path}. Run "
  479. f"this buck command to find the smallest traced graph "
  480. f"which reproduces this error: {exc.inner_exception.buck_command}\n"
  481. )
  482. else:
  483. msg += (
  484. f"\nMinifier script written to {exc.inner_exception.minifier_path}. Run "
  485. "this script to find the smallest traced graph which reproduces this error.\n"
  486. )
  487. old_msg = "" if len(exc.args) == 0 else str(exc.args[0])
  488. if isinstance(exc, KeyError):
  489. exc.args = (KeyErrorMsg(old_msg + msg),) + exc.args[1:]
  490. else:
  491. new_msg = old_msg + msg
  492. exc.args = (new_msg,) + exc.args[1:]
  493. def get_exc_message(
  494. e: Exception, compile_id: CompileId
  495. ) -> tuple[Optional[str], Optional[int]]:
  496. filename = None
  497. lineno = None
  498. if e.innermost_user_frame_summary is not None: # type: ignore[attr-defined]
  499. filename = e.innermost_user_frame_summary.filename # type: ignore[attr-defined]
  500. lineno = e.innermost_user_frame_summary.lineno # type: ignore[attr-defined]
  501. e.compile_id = compile_id # type: ignore[attr-defined]
  502. return filename, lineno
  503. def get_stack_above_dynamo() -> StackSummary:
  504. return filter_stack(extract_stack())
  505. def get_real_stack(
  506. exc: Exception, frame: Optional[DynamoFrameType] = None
  507. ) -> Optional[StackSummary]:
  508. real_stack = getattr(exc, "real_stack", None)
  509. if real_stack is None:
  510. return None
  511. # NB: it's possible for real_stack to be []; we still attempt to
  512. # report a stack anyway because the stack_above_dynamo may still
  513. # be useful for debugging
  514. if frame is not None:
  515. # NB: frame is PyInterpreterFrame on Python 3.11 and later,
  516. # not a TRUE frame object. You can't actually feed it
  517. # to traceback because it doesn't have enough information.
  518. # To solve this problem, we technically should just materialize
  519. # the frame, the same way _PyFrame_GetFrameObject would do
  520. # (but we cannot actually do this, because this populates
  521. # frame_obj field, which default eval frame doesn't like).
  522. #
  523. # Fortunately, in this case, we can hack it: there's no need
  524. # to actually use the truly top frame, we can just extract
  525. # from where we are right now and rely on filter_stack to
  526. # get rid of all the dynamo frames. For ease of testing
  527. # we apply this behavior to ALL Python versions
  528. stack_above_dynamo = get_stack_above_dynamo()
  529. else:
  530. stack_above_dynamo = StackSummary()
  531. return StackSummary.from_list(stack_above_dynamo + real_stack)
  532. # filter out all frames after entering dynamo
  533. def filter_stack(stack: StackSummary) -> StackSummary:
  534. user_stack = StackSummary()
  535. for frame in stack:
  536. if frame.filename is None:
  537. continue
  538. if "convert_frame" in frame.filename:
  539. break
  540. if "eval_frame" in frame.filename or (
  541. frame.line and "torch._dynamo.optimize(" in frame.line
  542. ):
  543. continue
  544. user_stack.append(frame)
  545. return user_stack
  546. def remove_resume_prefix(name: str) -> Optional[str]:
  547. from .resume_execution import TORCH_DYNAMO_RESUME_IN_PREFIX
  548. match = re.match(f"{TORCH_DYNAMO_RESUME_IN_PREFIX}_(\\w+)_at_\\d+", name)
  549. if match:
  550. return match.group(1)
  551. return None
  552. def collapse_resume_frames(stack: StackSummary) -> StackSummary:
  553. """
  554. When we graph break, we create a resume function and make a regular Python call
  555. to it, which gets intercepted by Dynamo. This behavior is normally shown in the
  556. traceback, which can be confusing to a user. So we can filter out resume frames
  557. for better traceback clarity.
  558. Example:
  559. File "..." line 3, in f
  560. <line 3>
  561. File "..." line 5, in torch_dynamo_resume_in_f_at_80
  562. <line 5>
  563. File "..." line 10, in torch_dynamo_resume_in_f_at_120
  564. <line 10>
  565. becomes
  566. File "..." line 10, in f
  567. <line 10>
  568. """
  569. new_stack = StackSummary()
  570. for frame in stack:
  571. if frame.filename is None:
  572. continue
  573. name = remove_resume_prefix(frame.name)
  574. if new_stack and name and new_stack[-1].name == name:
  575. new_stack[-1] = frame
  576. frame.name = name
  577. else:
  578. new_stack.append(frame)
  579. return new_stack
  580. def format_error_msg_verbose(
  581. exc: Exception,
  582. code: types.CodeType,
  583. record_filename: Optional[str] = None,
  584. frame: Optional[DynamoFrameType] = None,
  585. ) -> str:
  586. msg = (
  587. f"WON'T CONVERT {code.co_name} {code.co_filename} line {code.co_firstlineno}\n"
  588. )
  589. msg += "=" * 10 + " TorchDynamo Stack Trace " + "=" * 10 + "\n"
  590. msg += format_exc()
  591. real_stack = get_real_stack(exc, frame)
  592. if real_stack is not None:
  593. msg += (
  594. "\n"
  595. + "=" * 10
  596. + " The above exception occurred while processing the following code "
  597. + "=" * 10
  598. + "\n\n"
  599. )
  600. msg += "".join(format_list(real_stack))
  601. msg += "\n"
  602. msg += "=" * 10
  603. return msg
  604. def format_error_msg(
  605. exc: Exception,
  606. code: types.CodeType,
  607. record_filename: Optional[str] = None,
  608. frame: Optional[DynamoFrameType] = None,
  609. ) -> str:
  610. if config.verbose:
  611. return format_error_msg_verbose(exc, code, record_filename, frame)
  612. return f"WON'T CONVERT {code.co_name} {code.co_filename}\
  613. line {code.co_firstlineno} \ndue to: \n{format_exc()}"