_trace.py 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500
  1. # mypy: allow-untyped-defs
  2. """Tracing.
  3. This module contains functionality to support the JIT's tracing frontend, notably:
  4. * torch.jit.trace
  5. * torch.jit.trace_module
  6. This is not intended to be imported directly; please use the exposed
  7. functionalities in `torch.jit`.
  8. """
  9. import contextlib
  10. import copy
  11. import functools
  12. import inspect
  13. import os
  14. import re
  15. import warnings
  16. from enum import Enum
  17. from typing import Any, Callable, Dict, List, Optional, Set, TypeVar
  18. from typing_extensions import ParamSpec
  19. import torch
  20. from torch._jit_internal import (
  21. _qualified_name,
  22. get_callable_argument_names,
  23. is_scripting,
  24. )
  25. from torch.autograd import function
  26. from torch.jit._script import _CachedForward, script, ScriptModule
  27. from torch.jit._state import _enabled, _python_cu
  28. from torch.nn import Module
  29. from torch.testing._comparison import default_tolerances
  30. _flatten = torch._C._jit_flatten
  31. _unflatten = torch._C._jit_unflatten
  32. R = TypeVar("R", covariant=True) # return type (always covariant)
  33. P = ParamSpec("P")
  34. def _create_interpreter_name_lookup_fn(frames_up=1):
  35. def _get_interpreter_name_for_var(var):
  36. frame = inspect.currentframe()
  37. if not frame:
  38. raise RuntimeError("failed to inspect frame")
  39. i = 0
  40. while i < frames_up + 1:
  41. frame = frame.f_back
  42. if not frame:
  43. raise RuntimeError("failed to get frame")
  44. i += 1
  45. f_locals = frame.f_locals
  46. f_globals = frame.f_globals
  47. for k, v in f_locals.items():
  48. if isinstance(v, torch.Tensor) and var is v:
  49. return k if k != "self" else ""
  50. return ""
  51. return _get_interpreter_name_for_var
  52. def _unique_state_dict(module, keep_vars=False):
  53. # since Parameter.detach() always creates a new torch.Tensor instance,
  54. # id(v) doesn't work with it. So we always get the Parameter or Buffer
  55. # as values, and deduplicate the params using Parameters and Buffers
  56. state_dict = module.state_dict(keep_vars=True)
  57. filtered_dict = type(state_dict)()
  58. seen_ids: Set[int] = set()
  59. for k, v in state_dict.items():
  60. if id(v) in seen_ids:
  61. continue
  62. seen_ids.add(id(v))
  63. if keep_vars:
  64. filtered_dict[k] = v
  65. else:
  66. filtered_dict[k] = v.detach()
  67. return filtered_dict
  68. class ONNXTracedModule(torch.nn.Module):
  69. def __init__(
  70. self,
  71. inner,
  72. strict=True,
  73. force_outplace=False,
  74. return_inputs=False,
  75. return_inputs_states=False,
  76. ):
  77. super().__init__()
  78. # inner may be a Module, or it may be an arbitrary callable
  79. # If it's a Module, we get its parameters automatically, which lets
  80. # us avoid a special casing functions versus modules.
  81. self.inner = inner
  82. self.strict = strict
  83. self._force_outplace = force_outplace
  84. self._return_inputs = return_inputs
  85. self._return_inputs_states = return_inputs_states
  86. def forward(self, *args: torch.Tensor):
  87. in_vars, in_desc = _flatten(args)
  88. # NOTE: use full state, because we need it for BatchNorm export
  89. # This differs from the compiler path, which doesn't support it at the moment.
  90. module_state = list(_unique_state_dict(self, keep_vars=True).values())
  91. ret_inputs = []
  92. inputs_states = []
  93. outs = []
  94. def wrapper(*args):
  95. in_args: List[torch.Tensor] = []
  96. for i in range(len(in_vars)):
  97. if not isinstance(args[i], torch.Tensor):
  98. raise RuntimeError("Expected Tensor argument")
  99. in_args.append(args[i])
  100. trace_inputs = _unflatten(in_args, in_desc)
  101. if self._return_inputs:
  102. ret_inputs.append(
  103. tuple(x.clone(memory_format=torch.preserve_format) for x in args)
  104. )
  105. if self._return_inputs_states:
  106. inputs_states.append(_unflatten(in_args, in_desc))
  107. outs.append(self.inner(*trace_inputs))
  108. if self._return_inputs_states:
  109. inputs_states[0] = (inputs_states[0], trace_inputs)
  110. out_vars, _ = _flatten(outs)
  111. if len(out_vars) == 1:
  112. return out_vars[0]
  113. else:
  114. return tuple(out_vars)
  115. graph, out = torch._C._create_graph_by_tracing(
  116. wrapper,
  117. in_vars + module_state,
  118. _create_interpreter_name_lookup_fn(),
  119. self.strict,
  120. self._force_outplace,
  121. )
  122. if self._return_inputs:
  123. return graph, outs[0], ret_inputs[0]
  124. if self._return_inputs_states:
  125. return graph, outs[0], inputs_states[0]
  126. else:
  127. return graph, outs[0]
  128. def _clone_inputs(args):
  129. def clone_input(a):
  130. if a is None:
  131. return None
  132. elif isinstance(a, torch.Tensor):
  133. # TODO: figure out one liner to .clone() and set requires_grad
  134. v = (
  135. a.detach()
  136. .clone(memory_format=None if a.is_mkldnn else torch.preserve_format)
  137. .requires_grad_(a.requires_grad)
  138. )
  139. if a.grad is not None:
  140. v.grad = clone_input(v.grad)
  141. return v
  142. else:
  143. return a.clone(memory_format=torch.preserve_format)
  144. return function._nested_map(
  145. lambda x: isinstance(x, torch.Tensor), clone_input, condition_msg="tensors"
  146. )(args)
  147. # This is purely for developer debugging. We are not going to advertise it.
  148. _JIT_TIME = os.environ.get("PYTORCH_JIT_TIME", False) # CUDA-only timing
  149. _JIT_DISABLE = os.environ.get("PYTORCH_JIT_DISABLE", False)
  150. _JIT_STATS = os.environ.get("PYTORCH_JIT_STATS", False)
  151. @contextlib.contextmanager
  152. def _time(trace_name, name, time=True):
  153. if (not _JIT_TIME and not time) or not torch.cuda.is_available():
  154. yield
  155. return
  156. stream = torch.cuda.current_stream()
  157. start = torch.cuda.Event(enable_timing=True)
  158. end = torch.cuda.Event(enable_timing=True)
  159. stream.record_event(start)
  160. try:
  161. yield
  162. finally:
  163. stream.record_event(end)
  164. end.synchronize()
  165. print(f"{trace_name} {name} time: {start.elapsed_time(end)} ms")
  166. def verify(model, args, loss_fn=torch.sum, devices=None):
  167. """
  168. Verify that a JIT compiled model has the same behavior as its uncompiled version along with its backwards pass.
  169. If your model returns multiple outputs,
  170. you must also specify a `loss_fn` to produce a loss for which
  171. the backwards will be computed.
  172. This function has side-effects (e.g., it executes your model / saves and loads
  173. parameters), so don't expect the model to come out exactly the same as what
  174. you passed in.
  175. Args:
  176. model (compiled torch.nn.Module or function): the module/function to be
  177. verified. The module/function definition MUST have been decorated with
  178. `@torch.jit.compile`.
  179. args (tuple or Tensor): the positional arguments to pass to the
  180. compiled function/module to be verified. A non-tuple is assumed to
  181. be a single positional argument to be passed to the model.
  182. loss_fn (function, optional): the loss function to be applied to
  183. the output of the model, before backwards is invoked. By default,
  184. we assume that a model returns a single result, and we :func:`torch.sum`
  185. before calling backwards; if this is inappropriate, you can pass your
  186. own loss function. Note that if a model returns a tuple of results,
  187. these are passed as separate positional arguments to `loss_fn`.
  188. devices (iterable of device IDs, optional): the GPU devices which the
  189. compiled module will be run on. This determines the RNG state we
  190. must save when running both compiled and uncompiled versions of the model.
  191. """
  192. # TODO: In principle, we track device information in our trace, so it
  193. # should be possible to check if our execution actually obeyed the 'devices'
  194. # the user provided.
  195. # TODO: Consider adding a utility function to torch.jit to test
  196. # for this case
  197. if not isinstance(model, torch._C.CompiledFunction): # type: ignore[attr-defined]
  198. raise TypeError(
  199. "Cannot verify an uncompiled module. Add @torch.jit.compile to compile it"
  200. )
  201. is_module = isinstance(model, Module)
  202. if not isinstance(args, tuple):
  203. args = (args,)
  204. saved_args = _clone_inputs(args)
  205. if is_module:
  206. saved_state = copy.deepcopy(model.state_dict())
  207. def run_fwd_bwd(args, force_trace=False, assert_compiled=False):
  208. params = list(model.parameters()) if is_module else []
  209. in_vars, _ = _flatten((args, params))
  210. # We use a special API to reset the trace and compile it from scratch.
  211. compiled_fn = model
  212. if force_trace:
  213. compiled_fn.clear_cache()
  214. if assert_compiled:
  215. hits = compiled_fn.hits
  216. out = model(*args)
  217. if assert_compiled and compiled_fn.hits == hits: # type: ignore[possibly-undefined]
  218. raise RuntimeError("failed to use the compiled function")
  219. if not isinstance(out, tuple):
  220. out = (out,)
  221. if loss_fn == torch.sum and len(out) != 1:
  222. raise ValueError(
  223. f"Model returns {len(out)} outputs, but default loss function "
  224. "(torch.sum) can only handle a single output"
  225. )
  226. out_vars, _ = _flatten(out)
  227. saved_outs = [
  228. v.detach().clone(memory_format=torch.preserve_format) for v in out_vars
  229. ]
  230. loss = loss_fn(*out)
  231. grads = torch.autograd.grad([loss], in_vars)
  232. # TODO: I'm not sure if the clone here is necessary but it is safer
  233. saved_grads = [
  234. v.detach().clone(memory_format=torch.preserve_format) for v in grads
  235. ]
  236. return (saved_outs, saved_grads)
  237. with torch.random.fork_rng(devices, _caller="torch.jit.verify"):
  238. uncompiled_outs, uncompiled_grads = run_fwd_bwd(args, force_trace=True)
  239. assert model.has_trace_for(*args)
  240. if is_module:
  241. model.load_state_dict(saved_state) # type: ignore[possibly-undefined]
  242. compiled_outs, compiled_grads = run_fwd_bwd(args, assert_compiled=True)
  243. _verify_equal(uncompiled_outs, compiled_outs)
  244. _verify_equal(uncompiled_grads, compiled_grads)
  245. def _verify_equal(xs, ys):
  246. for x, y in zip(xs, ys):
  247. if x.sub(y).abs().max() > 1e-6:
  248. raise RuntimeError("JIT and real computation mismatch")
  249. def indent(s):
  250. return "\n".join(["\t" + line for line in s.splitlines()])
  251. class TracingCheckError(Exception):
  252. def __init__(self, graph_diff_error, tensor_compare_error, extra_msg=None):
  253. self.message = "Tracing failed sanity checks!\n"
  254. if extra_msg is not None:
  255. self.message += extra_msg + "\n"
  256. if graph_diff_error is not None:
  257. self.message += "ERROR: Graphs differed across invocations!\n"
  258. self.message += indent(graph_diff_error) + "\n"
  259. if tensor_compare_error is not None:
  260. self.message += (
  261. "ERROR: Tensor-valued Constant nodes differed in value "
  262. "across invocations. This often indicates that the tracer has"
  263. " encountered untraceable code.\n"
  264. )
  265. self.message += indent(tensor_compare_error) + "\n"
  266. super().__init__(self.message)
  267. # Check the traced module against a set of user-provided validation inputs
  268. @torch.no_grad()
  269. def _check_trace(
  270. check_inputs,
  271. func,
  272. traced_func,
  273. check_tolerance,
  274. strict,
  275. force_outplace,
  276. is_trace_module,
  277. _module_class,
  278. example_inputs_is_kwarg=False,
  279. ):
  280. # Note: tracing is independent of optimizations, which consume the trace
  281. for inputs in check_inputs:
  282. if isinstance(inputs, torch.Tensor):
  283. inputs = (inputs,)
  284. if is_trace_module:
  285. copied_dict = {}
  286. for name, data in inputs.items():
  287. copied_dict[name] = _clone_inputs(data)
  288. check_mod = torch.jit.trace_module(
  289. getattr(func, "__self__", func),
  290. copied_dict,
  291. check_trace=False,
  292. strict=strict,
  293. _force_outplace=force_outplace,
  294. _module_class=_module_class,
  295. _compilation_unit=torch._C.CompilationUnit(),
  296. example_inputs_is_kwarg=example_inputs_is_kwarg,
  297. _store_inputs=False,
  298. )
  299. check_mod_func = check_mod._c._get_method(traced_func.name)
  300. inputs = inputs[traced_func.name]
  301. if (
  302. isinstance(inputs, (torch.Tensor))
  303. or isinstance(inputs, dict)
  304. and not example_inputs_is_kwarg
  305. ):
  306. inputs = (inputs,)
  307. else:
  308. if example_inputs_is_kwarg:
  309. check_mod = torch.jit.trace(
  310. func,
  311. check_trace=False,
  312. strict=strict,
  313. _force_outplace=force_outplace,
  314. _module_class=_module_class,
  315. example_kwarg_inputs=_clone_inputs(inputs),
  316. _store_inputs=False,
  317. )
  318. else:
  319. check_mod = torch.jit.trace(
  320. func,
  321. _clone_inputs(inputs),
  322. check_trace=False,
  323. strict=strict,
  324. _force_outplace=force_outplace,
  325. _module_class=_module_class,
  326. _store_inputs=False,
  327. )
  328. check_mod_func = check_mod
  329. def graph_diagnostic_info():
  330. mod_canonicalized = torch._C._jit_pass_canonicalize(traced_func.graph)
  331. torch._C._jit_pass_inline(mod_canonicalized)
  332. torch._C._jit_pass_erase_shape_information(mod_canonicalized)
  333. mod_str = str(mod_canonicalized)
  334. mod_str = re.sub(r"___torch_mangle_[0-9]+\.", "", mod_str)
  335. check_canonicalized = torch._C._jit_pass_canonicalize(check_mod_func.graph)
  336. torch._C._jit_pass_inline(check_canonicalized)
  337. torch._C._jit_pass_erase_shape_information(check_canonicalized)
  338. check_str = str(check_canonicalized)
  339. check_str = re.sub(r"___torch_mangle_[0-9]+\.", "", check_str)
  340. graph_diff_errors = None
  341. if mod_str != check_str:
  342. import difflib
  343. graph_diff = difflib.ndiff(
  344. mod_str.splitlines(True), check_str.splitlines(True)
  345. )
  346. graph_diff_errors = "Graph diff:\n" + indent("".join(graph_diff)) + "\n"
  347. for n_mod, n_check in zip(
  348. mod_canonicalized.nodes(), check_canonicalized.nodes()
  349. ):
  350. if str(n_mod) != str(n_check):
  351. graph_diff_errors += "First diverging operator:\n"
  352. node_diff = difflib.ndiff(
  353. str(n_mod).splitlines(True), str(n_check).splitlines(True)
  354. )
  355. source_printout = (
  356. "Node diff:\n" + indent("".join(node_diff)) + "\n"
  357. )
  358. mod_stack = n_mod.sourceRange()
  359. if mod_stack:
  360. source_printout += (
  361. "Trace source location:\n" + indent(mod_stack) + "\n"
  362. )
  363. check_stack = n_check.sourceRange()
  364. if check_stack:
  365. source_printout += (
  366. "Check source location:\n" + indent(check_stack) + "\n"
  367. )
  368. graph_diff_errors += source_printout
  369. break # For now, only print out the first pair of nodes that diverges
  370. tensor_compare_errors = None
  371. # Check Tensor-valued constant nodes
  372. for n_mod, n_check in zip(
  373. mod_canonicalized.nodes(), check_canonicalized.nodes()
  374. ):
  375. if n_mod.kind() != n_check.kind():
  376. break # Graphs have already diverged
  377. if n_mod.kind() == "prim::Constant" and not (
  378. n_mod.mustBeNone() or n_check.mustBeNone()
  379. ):
  380. if not n_mod.hasAttribute("value"):
  381. continue
  382. if n_mod.kindOf("value") != "t" or n_check.kindOf("value") != "t":
  383. continue
  384. mod_tensor_val = n_mod.t("value")
  385. check_tensor_val = n_check.t("value")
  386. try:
  387. torch.testing.assert_close(
  388. mod_tensor_val, check_tensor_val, equal_nan=True
  389. )
  390. except (RuntimeError, AssertionError) as e:
  391. if tensor_compare_errors is None:
  392. tensor_compare_errors = ""
  393. tensor_compare_errors += "Node:\n" + indent(str(n_mod)) + "\n"
  394. compare_stack = n_mod.sourceRange()
  395. if compare_stack:
  396. tensor_compare_errors += (
  397. "Source Location:\n" + indent(compare_stack) + "\n"
  398. )
  399. tensor_compare_errors += "Comparison exception: " + indent(
  400. str(e)
  401. )
  402. break # For now, only print the first diverging pair
  403. return graph_diff_errors, tensor_compare_errors
  404. def wrap_retval(x):
  405. return x if isinstance(x, tuple) else (x,)
  406. def run_mod_and_filter_tensor_outputs(mod, inputs, running_what):
  407. try:
  408. if isinstance(inputs, dict) and example_inputs_is_kwarg:
  409. outs = wrap_retval(mod(**inputs))
  410. else:
  411. outs = wrap_retval(mod(*_clone_inputs(inputs)))
  412. outs = [out for out in outs if isinstance(out, torch.Tensor)]
  413. return outs
  414. except Exception as e:
  415. graph_diff_errors, tensor_compare_errors = graph_diagnostic_info()
  416. msg = f"encountered an exception while running the {running_what} with test inputs.\nException:\n{indent(str(e))}"
  417. raise TracingCheckError(
  418. graph_diff_errors,
  419. tensor_compare_errors,
  420. extra_msg=msg,
  421. ) from e
  422. has_warned = [False]
  423. def maybe_warn_nondeterministic():
  424. if has_warned[0]:
  425. return
  426. has_warned[0] = True
  427. nondeterm_ops = [
  428. op for op in traced_func.graph.nodes() if op.isNondeterministic()
  429. ]
  430. if len(nondeterm_ops) > 0:
  431. nondeterministic_ops_warning = "Trace had nondeterministic nodes. "
  432. nondeterministic_ops_warning += (
  433. "Did you forget call .eval() on your model? Nodes:\n"
  434. )
  435. nondeterministic_ops_warning += "\n".join(
  436. [indent(str(op)) for op in nondeterm_ops][:20]
  437. )
  438. nondeterministic_ops_warning += (
  439. "\nThis may cause errors in trace checking. To disable trace checking,"
  440. " pass check_trace=False to torch.jit.trace()"
  441. )
  442. warnings.warn(
  443. nondeterministic_ops_warning, category=TracerWarning, stacklevel=5
  444. )
  445. def compare_outputs(original, reference, match_what):
  446. all_ok = True
  447. for i, (orig, ref) in enumerate(zip(original, reference)):
  448. try:
  449. if orig.is_quantized:
  450. orig = orig.dequantize()
  451. if ref.is_quantized:
  452. ref = ref.dequantize()
  453. if orig.is_mkldnn:
  454. orig = orig.to_dense()
  455. if ref.is_mkldnn:
  456. ref = ref.to_dense()
  457. if ref.is_complex() or orig.is_complex():
  458. torch.testing.assert_close(
  459. orig.to(torch.cdouble),
  460. ref.to(torch.cdouble),
  461. rtol=check_tolerance,
  462. atol=default_tolerances(orig, ref)[1],
  463. equal_nan=True,
  464. )
  465. else:
  466. if orig.is_mps or ref.is_mps:
  467. torch.testing.assert_close(
  468. orig.float(),
  469. ref.float(),
  470. rtol=check_tolerance,
  471. atol=default_tolerances(orig, ref)[1],
  472. equal_nan=True,
  473. )
  474. elif getattr(orig, "is_nested", None) or getattr(
  475. ref, "is_nested", None
  476. ):
  477. assert getattr(orig, "is_nested", None) == getattr(
  478. ref, "is_nested", None
  479. )
  480. for t_orig, t_ref in zip(orig.unbind(), ref.unbind()):
  481. torch.testing.assert_close(
  482. t_orig.double(),
  483. t_ref.double(),
  484. rtol=check_tolerance,
  485. atol=default_tolerances(t_orig, t_ref)[1],
  486. equal_nan=True,
  487. )
  488. else:
  489. torch.testing.assert_close(
  490. orig.double(),
  491. ref.double(),
  492. rtol=check_tolerance,
  493. atol=default_tolerances(orig, ref)[1],
  494. equal_nan=True,
  495. )
  496. except AssertionError as e:
  497. maybe_warn_nondeterministic()
  498. warnings.warn(
  499. "Output nr "
  500. + str(i + 1)
  501. + ". of the traced function does not match "
  502. "the corresponding output of the "
  503. + match_what
  504. + ". Detailed error:\n"
  505. + str(e),
  506. category=TracerWarning,
  507. stacklevel=4,
  508. )
  509. all_ok = False
  510. return all_ok
  511. traced_outs = run_mod_and_filter_tensor_outputs(traced_func, inputs, "trace")
  512. fn_outs = run_mod_and_filter_tensor_outputs(func, inputs, "Python function")
  513. if compare_outputs(traced_outs, fn_outs, "Python function"):
  514. check_outs = run_mod_and_filter_tensor_outputs(
  515. check_mod_func, inputs, "repeated trace"
  516. )
  517. compare_outputs(traced_outs, check_outs, "repeated trace")
  518. diag_info = graph_diagnostic_info()
  519. if any(info is not None for info in diag_info):
  520. raise TracingCheckError(*diag_info)
  521. class TracerWarning(Warning):
  522. @staticmethod
  523. def ignore_lib_warnings():
  524. # We ignore warnings from all submodules excluding the JIT, because we need them e.g. for _check_trace
  525. warnings.filterwarnings(
  526. "ignore", category=TracerWarning, module="torch.(?!jit)"
  527. )
  528. warnings.filterwarnings("ignore", "torch::jit::fuser::cuda")
  529. # We ignore the tracer warnings coming form inside the library, because all our shape
  530. # checks in nn will trigger them.
  531. TracerWarning.ignore_lib_warnings()
  532. torch._C._tracer_warn_use_python()
  533. def make_tuple(example_inputs):
  534. if isinstance(example_inputs, (torch.Tensor, dict)):
  535. return (example_inputs,)
  536. # done primarily so that weird iterables fail here and not pybind11 code
  537. if not isinstance(example_inputs, tuple):
  538. return tuple(example_inputs)
  539. return example_inputs
  540. def make_module(mod, _module_class, _compilation_unit):
  541. if isinstance(mod, ScriptModule):
  542. return mod
  543. elif torch._jit_internal.module_has_exports(mod):
  544. infer_methods_stubs_fn = torch.jit._recursive.make_stubs_from_exported_methods
  545. return torch.jit._recursive.create_script_module(
  546. mod, infer_methods_stubs_fn, share_types=False, is_tracing=True
  547. )
  548. else:
  549. if _module_class is None:
  550. _module_class = TopLevelTracedModule
  551. return _module_class(mod, _compilation_unit=_compilation_unit)
  552. def wrap_check_inputs(check_inputs):
  553. if check_inputs is None:
  554. return None
  555. return [{"forward": c} for c in check_inputs]
  556. def analyze_ts_result_with_export_result(export, trace):
  557. import torch.utils._pytree as pytree
  558. flat_export = pytree.tree_leaves(export)
  559. flat_trace = pytree.tree_leaves(trace)
  560. for orig, loaded in zip(flat_export, flat_trace):
  561. if orig.layout != loaded.layout:
  562. return False
  563. # mkldnn is not supported for torch.allclose
  564. if orig.layout == torch._mkldnn: # type: ignore[attr-defined]
  565. return True
  566. if type(orig) != type(loaded):
  567. return False
  568. if isinstance(orig, torch.Tensor):
  569. if not torch.allclose(orig, loaded):
  570. return False
  571. else:
  572. if orig != loaded:
  573. return False
  574. return True
  575. def _trace_impl(
  576. func,
  577. example_inputs=None,
  578. optimize=None,
  579. check_trace=True,
  580. check_inputs=None,
  581. check_tolerance=1e-5,
  582. strict=True,
  583. _force_outplace=False,
  584. _module_class=None,
  585. _compilation_unit=_python_cu,
  586. example_kwarg_inputs=None,
  587. _store_inputs=True,
  588. ):
  589. if isinstance(func, torch.jit.ScriptModule):
  590. # it is hard to trace it because the forward method on ScriptModule is already defined, so it
  591. # would result in an error.
  592. warnings.warn(
  593. "The input to trace is already a ScriptModule, tracing it is a no-op. Returning the object as is."
  594. )
  595. return func
  596. if isinstance(func, torch.nn.Module):
  597. if example_inputs is None:
  598. if isinstance(example_kwarg_inputs, dict):
  599. example_inputs = example_kwarg_inputs
  600. else:
  601. raise RuntimeError("example_kwarg_inputs should be a dict")
  602. return trace_module(
  603. func,
  604. {"forward": example_inputs},
  605. None,
  606. check_trace,
  607. wrap_check_inputs(check_inputs),
  608. check_tolerance,
  609. strict,
  610. _force_outplace,
  611. _module_class,
  612. example_inputs_is_kwarg=isinstance(example_kwarg_inputs, dict),
  613. _store_inputs=_store_inputs,
  614. )
  615. if (
  616. hasattr(func, "__self__")
  617. and isinstance(func.__self__, torch.nn.Module)
  618. and func.__name__ == "forward"
  619. ):
  620. if example_inputs is None:
  621. if isinstance(example_kwarg_inputs, dict):
  622. example_inputs = example_kwarg_inputs
  623. else:
  624. raise RuntimeError("example_kwarg_inputs should be a dict")
  625. return trace_module(
  626. func.__self__,
  627. {"forward": example_inputs},
  628. None,
  629. check_trace,
  630. wrap_check_inputs(check_inputs),
  631. check_tolerance,
  632. strict,
  633. _force_outplace,
  634. _module_class,
  635. example_inputs_is_kwarg=isinstance(example_kwarg_inputs, dict),
  636. _store_inputs=_store_inputs,
  637. )
  638. # Special case for common case of passing a single Tensor
  639. if (
  640. isinstance(example_inputs, (torch.Tensor, dict))
  641. and example_kwarg_inputs is None
  642. ):
  643. example_inputs = (example_inputs,)
  644. # done primarily so that weird iterables fail here and not pybind11 code
  645. elif example_kwarg_inputs is None and not isinstance(example_inputs, tuple):
  646. example_inputs = tuple(example_inputs)
  647. var_lookup_fn = _create_interpreter_name_lookup_fn(0)
  648. if hasattr(func, "__self__") and isinstance(func.__self__, torch.nn.Module):
  649. raise AttributeError(
  650. "trace doesn't support compiling individual module's functions.\n"
  651. "Please use trace_module"
  652. )
  653. name = _qualified_name(func)
  654. if isinstance(example_kwarg_inputs, dict):
  655. example_inputs = example_kwarg_inputs
  656. traced = torch._C._create_function_from_trace_with_dict(
  657. name,
  658. func,
  659. example_kwarg_inputs,
  660. var_lookup_fn,
  661. strict,
  662. _force_outplace,
  663. get_callable_argument_names(func),
  664. )
  665. else:
  666. traced = torch._C._create_function_from_trace(
  667. name,
  668. func,
  669. example_inputs,
  670. var_lookup_fn,
  671. strict,
  672. _force_outplace,
  673. get_callable_argument_names(func),
  674. )
  675. # Check the trace against new traces created from user-specified inputs
  676. if check_trace:
  677. if check_inputs is not None:
  678. _check_trace(
  679. check_inputs,
  680. func,
  681. traced,
  682. check_tolerance,
  683. strict,
  684. _force_outplace,
  685. False,
  686. _module_class,
  687. example_inputs_is_kwarg=isinstance(example_kwarg_inputs, dict),
  688. )
  689. else:
  690. _check_trace(
  691. [example_inputs],
  692. func,
  693. traced,
  694. check_tolerance,
  695. strict,
  696. _force_outplace,
  697. False,
  698. _module_class,
  699. example_inputs_is_kwarg=isinstance(example_kwarg_inputs, dict),
  700. )
  701. # Allow torch.compile() to inline
  702. traced._torchdynamo_inline = func # type: ignore[attr-defined]
  703. return traced
  704. class _ExportType(str, Enum):
  705. DIRECT_EXPORT = "DIRECT_EXPORT"
  706. TRACE_AND_EXPORT = "TRACE_AND_EXPORT"
  707. SOURCE_TO_SOURCE = "SOURCE_TO_SOURCE"
  708. def __str__(self) -> str:
  709. return self.value
  710. class _ExportOutcome(str, Enum):
  711. SUCCESS = "SUCCESS"
  712. FAILED_TO_EXPORT = "FAILED_TO_EXPORT"
  713. FAILED_TO_RUN = "FAILED_TO_RUN"
  714. ACCURACY_ERROR = "ACCURACY_ERROR"
  715. def __str__(self) -> str:
  716. return self.value
  717. def trace(
  718. func,
  719. example_inputs=None,
  720. optimize=None,
  721. check_trace=True,
  722. check_inputs=None,
  723. check_tolerance=1e-5,
  724. strict=True,
  725. _force_outplace=False,
  726. _module_class=None,
  727. _compilation_unit=_python_cu,
  728. example_kwarg_inputs=None,
  729. _store_inputs=True,
  730. ):
  731. r"""
  732. Trace a function and return an executable or :class:`ScriptFunction` that will be optimized using just-in-time compilation.
  733. Tracing is ideal for code that operates only on
  734. ``Tensor``\\s and lists, dictionaries, and
  735. tuples of ``Tensor``\\s.
  736. Using `torch.jit.trace` and `torch.jit.trace_module`, you can turn an
  737. existing module or Python function into a TorchScript
  738. :class:`ScriptFunction` or :class:`ScriptModule`. You must provide example
  739. inputs, and we run the function, recording the operations performed on all
  740. the tensors.
  741. * The resulting recording of a standalone function produces `ScriptFunction`.
  742. * The resulting recording of `nn.Module.forward` or `nn.Module` produces
  743. `ScriptModule`.
  744. This module also contains any parameters that the original
  745. module had as well.
  746. Warning:
  747. Tracing only correctly records functions and modules which are not data
  748. dependent (e.g., do not have conditionals on data in tensors) and do not have
  749. any untracked external dependencies (e.g., perform input/output or
  750. access global variables). Tracing only records operations done when the given
  751. function is run on the given tensors. Therefore, the returned
  752. `ScriptModule` will always run the same traced graph on any input. This
  753. has some important implications when your module is expected to run
  754. different sets of operations, depending on the input and/or the module
  755. state. For example,
  756. * Tracing will not record any control-flow like if-statements or loops.
  757. When this control-flow is constant across your module, this is fine
  758. and it often inlines the control-flow decisions. But sometimes the
  759. control-flow is actually part of the model itself. For instance, a
  760. recurrent network is a loop over the (possibly dynamic) length of an
  761. input sequence.
  762. * In the returned :class:`ScriptModule`, operations that have different
  763. behaviors in ``training`` and ``eval`` modes will always behave as if
  764. it is in the mode it was in during tracing, no matter which mode the
  765. `ScriptModule` is in.
  766. In cases like these, tracing would not be appropriate and
  767. :func:`scripting <torch.jit.script>` is a better choice. If you trace
  768. such models, you may silently get incorrect results on subsequent
  769. invocations of the model. The tracer will try to emit warnings when
  770. doing something that may cause an incorrect trace to be produced.
  771. Args:
  772. func (callable or torch.nn.Module): A Python function or `torch.nn.Module`
  773. that will be run with `example_inputs`. `func` arguments and return
  774. values must be tensors or (possibly nested) tuples that contain
  775. tensors. When a module is passed `torch.jit.trace`, only the
  776. ``forward`` method is run and traced (see :func:`torch.jit.trace
  777. <torch.jit.trace_module>` for details).
  778. Keyword arguments:
  779. example_inputs (tuple or torch.Tensor or None, optional): A tuple of example
  780. inputs that will be passed to the function while tracing.
  781. Default: ``None``. Either this argument or ``example_kwarg_inputs``
  782. should be specified. The resulting trace can be run with inputs of
  783. different types and shapes assuming the traced operations support those
  784. types and shapes. `example_inputs` may also be a single Tensor in which
  785. case it is automatically wrapped in a tuple. When the value is None,
  786. ``example_kwarg_inputs`` should be specified.
  787. check_trace (``bool``, optional): Check if the same inputs run through
  788. traced code produce the same outputs. Default: ``True``. You might want
  789. to disable this if, for example, your network contains non-
  790. deterministic ops or if you are sure that the network is correct despite
  791. a checker failure.
  792. check_inputs (list of tuples, optional): A list of tuples of input
  793. arguments that should be used to check the trace against what is
  794. expected. Each tuple is equivalent to a set of input arguments that
  795. would be specified in ``example_inputs``. For best results, pass in
  796. a set of checking inputs representative of the space of shapes and
  797. types of inputs you expect the network to see. If not specified,
  798. the original ``example_inputs`` are used for checking
  799. check_tolerance (float, optional): Floating-point comparison tolerance
  800. to use in the checker procedure. This can be used to relax the
  801. checker strictness in the event that results diverge numerically
  802. for a known reason, such as operator fusion.
  803. strict (``bool``, optional): run the tracer in a strict mode or not
  804. (default: ``True``). Only turn this off when you want the tracer to
  805. record your mutable container types (currently ``list``/``dict``)
  806. and you are sure that the container you are using in your
  807. problem is a ``constant`` structure and does not get used as
  808. control flow (if, for) conditions.
  809. example_kwarg_inputs (dict, optional): This parameter is a pack of keyword
  810. arguments of example inputs that will be passed to the function while
  811. tracing. Default: ``None``. Either this argument or ``example_inputs``
  812. should be specified. The dict will be unpacking by the arguments name
  813. of the traced function. If the keys of the dict don't not match with
  814. the traced function's arguments name, a runtime exception will be raised.
  815. Returns:
  816. If `func` is `nn.Module` or ``forward`` of `nn.Module`, `trace` returns
  817. a :class:`ScriptModule` object with a single ``forward`` method
  818. containing the traced code. The returned `ScriptModule` will
  819. have the same set of sub-modules and parameters as the original
  820. ``nn.Module``. If ``func`` is a standalone function, ``trace``
  821. returns `ScriptFunction`.
  822. Example (tracing a function):
  823. .. testcode::
  824. import torch
  825. def foo(x, y):
  826. return 2 * x + y
  827. # Run `foo` with the provided inputs and record the tensor operations
  828. traced_foo = torch.jit.trace(foo, (torch.rand(3), torch.rand(3)))
  829. # `traced_foo` can now be run with the TorchScript interpreter or saved
  830. # and loaded in a Python-free environment
  831. Example (tracing an existing module)::
  832. import torch
  833. import torch.nn as nn
  834. class Net(nn.Module):
  835. def __init__(self):
  836. super().__init__()
  837. self.conv = nn.Conv2d(1, 1, 3)
  838. def forward(self, x):
  839. return self.conv(x)
  840. n = Net()
  841. example_weight = torch.rand(1, 1, 3, 3)
  842. example_forward_input = torch.rand(1, 1, 3, 3)
  843. # Trace a specific method and construct `ScriptModule` with
  844. # a single `forward` method
  845. module = torch.jit.trace(n.forward, example_forward_input)
  846. # Trace a module (implicitly traces `forward`) and construct a
  847. # `ScriptModule` with a single `forward` method
  848. module = torch.jit.trace(n, example_forward_input)
  849. """
  850. if not _enabled:
  851. return func
  852. if optimize is not None:
  853. warnings.warn(
  854. "`optimize` is deprecated and has no effect. "
  855. "Use `with torch.jit.optimized_execution()` instead",
  856. FutureWarning,
  857. stacklevel=2,
  858. )
  859. from torch._utils_internal import (
  860. check_if_torch_exportable,
  861. log_torch_jit_trace_exportability,
  862. log_torchscript_usage,
  863. )
  864. log_torchscript_usage("trace")
  865. traced_func = _trace_impl(
  866. func,
  867. example_inputs,
  868. optimize,
  869. check_trace,
  870. check_inputs,
  871. check_tolerance,
  872. strict,
  873. _force_outplace,
  874. _module_class,
  875. _compilation_unit,
  876. example_kwarg_inputs,
  877. _store_inputs,
  878. )
  879. if check_if_torch_exportable():
  880. from torch._export.converter import TS2EPConverter
  881. from torch.export._trace import (
  882. _convert_ts_to_export_experimental,
  883. _process_jit_trace_inputs_for_export,
  884. )
  885. traced_func_for_export = _trace_impl(
  886. func,
  887. example_inputs=example_inputs,
  888. optimize=optimize,
  889. check_trace=False,
  890. check_inputs=check_inputs,
  891. check_tolerance=check_tolerance,
  892. strict=strict,
  893. _force_outplace=_force_outplace,
  894. _module_class=_module_class,
  895. _compilation_unit=_compilation_unit,
  896. example_kwarg_inputs=example_kwarg_inputs,
  897. _store_inputs=_store_inputs,
  898. )
  899. export_args, _ = _process_jit_trace_inputs_for_export(
  900. example_inputs, example_kwarg_inputs
  901. )
  902. def _log_exportability(func_to_export, export_func, export_args, export_type):
  903. try:
  904. ep_module = export_func(func_to_export, export_args)
  905. except Exception as e:
  906. log_torch_jit_trace_exportability(
  907. "trace",
  908. str(export_type),
  909. str(_ExportOutcome.FAILED_TO_EXPORT),
  910. str(e),
  911. )
  912. return
  913. try:
  914. export = ep_module(*export_args)
  915. except Exception as e:
  916. log_torch_jit_trace_exportability(
  917. "trace", str(export_type), str(_ExportOutcome.FAILED_TO_RUN), str(e)
  918. )
  919. return
  920. try:
  921. traced_result = func_to_export(*export_args)
  922. except Exception as e:
  923. _ = e
  924. log_torch_jit_trace_exportability(
  925. "trace", str(export_type), str(_ExportOutcome.SUCCESS), "succeeded"
  926. )
  927. return
  928. if not analyze_ts_result_with_export_result(export, traced_result):
  929. log_torch_jit_trace_exportability(
  930. "trace",
  931. str(export_type),
  932. str(_ExportOutcome.ACCURACY_ERROR),
  933. "accuracy error",
  934. )
  935. return
  936. log_torch_jit_trace_exportability(
  937. "trace", str(export_type), str(_ExportOutcome.SUCCESS), "succeeded"
  938. )
  939. def _direct_export_and_lower(func, export_args):
  940. return torch.export.export(func, export_args, strict=False).module()
  941. def _convert_ts_to_export_source_to_source(func, export_args):
  942. return TS2EPConverter(func, export_args).convert().module()
  943. # torch.jit.trace is noop when the original module is torch.jit.ScriptModule
  944. if not isinstance(traced_func_for_export, torch.jit.ScriptModule):
  945. _log_exportability(
  946. traced_func_for_export,
  947. _direct_export_and_lower,
  948. export_args,
  949. _ExportType.DIRECT_EXPORT,
  950. )
  951. _log_exportability(
  952. traced_func_for_export,
  953. _convert_ts_to_export_experimental,
  954. export_args,
  955. _ExportType.TRACE_AND_EXPORT,
  956. )
  957. _log_exportability(
  958. traced_func_for_export,
  959. _convert_ts_to_export_source_to_source,
  960. export_args,
  961. _ExportType.SOURCE_TO_SOURCE,
  962. )
  963. return traced_func
  964. _trace_module_map: Optional[Dict[Any, Any]] = None
  965. def trace_module(
  966. mod,
  967. inputs,
  968. optimize=None,
  969. check_trace=True,
  970. check_inputs=None,
  971. check_tolerance=1e-5,
  972. strict=True,
  973. _force_outplace=False,
  974. _module_class=None,
  975. _compilation_unit=_python_cu,
  976. example_inputs_is_kwarg=False,
  977. _store_inputs=True,
  978. ):
  979. """
  980. Trace a module and return an executable :class:`ScriptModule` that will be optimized using just-in-time compilation.
  981. When a module is passed to :func:`torch.jit.trace <torch.jit.trace>`, only
  982. the ``forward`` method is run and traced. With ``trace_module``, you can specify a dictionary of
  983. method names to example inputs to trace (see the ``inputs``) argument below.
  984. See :func:`torch.jit.trace <torch.jit.trace>` for more information on tracing.
  985. Args:
  986. mod (torch.nn.Module): A ``torch.nn.Module`` containing methods whose names are
  987. specified in ``inputs``. The given methods will be compiled
  988. as a part of a single `ScriptModule`.
  989. inputs (dict): A dict containing sample inputs indexed by method names in ``mod``.
  990. The inputs will be passed to methods whose names correspond to inputs'
  991. keys while tracing.
  992. ``{ 'forward' : example_forward_input, 'method2': example_method2_input}``
  993. Keyword arguments:
  994. check_trace (``bool``, optional): Check if the same inputs run through
  995. traced code produce the same outputs. Default: ``True``. You might want
  996. to disable this if, for example, your network contains non-
  997. deterministic ops or if you are sure that the network is correct despite
  998. a checker failure.
  999. check_inputs (list of dicts, optional): A list of dicts of input arguments that should be used
  1000. to check the trace against what is expected. Each tuple
  1001. is equivalent to a set of input arguments that would
  1002. be specified in ``inputs``. For best results, pass in a
  1003. set of checking inputs representative of the space of
  1004. shapes and types of inputs you expect the network to see.
  1005. If not specified, the original ``inputs`` are used for checking
  1006. check_tolerance (float, optional): Floating-point comparison tolerance to use in the checker procedure.
  1007. This can be used to relax the checker strictness in the event that
  1008. results diverge numerically for a known reason, such as operator fusion.
  1009. example_inputs_is_kwarg (``bool``, optional): This parameter indicate whether the example inputs is a pack
  1010. pack of keyword arguments. Default: ``False``.
  1011. Returns:
  1012. A :class:`ScriptModule` object with a single ``forward`` method containing the traced code.
  1013. When ``func`` is a ``torch.nn.Module``, the returned :class:`ScriptModule` will have the same set of
  1014. sub-modules and parameters as ``func``.
  1015. Example (tracing a module with multiple methods)::
  1016. import torch
  1017. import torch.nn as nn
  1018. class Net(nn.Module):
  1019. def __init__(self):
  1020. super().__init__()
  1021. self.conv = nn.Conv2d(1, 1, 3)
  1022. def forward(self, x):
  1023. return self.conv(x)
  1024. def weighted_kernel_sum(self, weight):
  1025. return weight * self.conv.weight
  1026. n = Net()
  1027. example_weight = torch.rand(1, 1, 3, 3)
  1028. example_forward_input = torch.rand(1, 1, 3, 3)
  1029. # Trace a specific method and construct `ScriptModule` with
  1030. # a single `forward` method
  1031. module = torch.jit.trace(n.forward, example_forward_input)
  1032. # Trace a module (implicitly traces `forward`) and construct a
  1033. # `ScriptModule` with a single `forward` method
  1034. module = torch.jit.trace(n, example_forward_input)
  1035. # Trace specific methods on a module (specified in `inputs`), constructs
  1036. # a `ScriptModule` with `forward` and `weighted_kernel_sum` methods
  1037. inputs = {'forward' : example_forward_input, 'weighted_kernel_sum' : example_weight}
  1038. module = torch.jit.trace_module(n, inputs)
  1039. """
  1040. if not _enabled:
  1041. return mod
  1042. if optimize is not None:
  1043. warnings.warn(
  1044. "`optimize` is deprecated and has no effect. "
  1045. "Use `with torch.jit.optimized_execution()` instead",
  1046. FutureWarning,
  1047. stacklevel=2,
  1048. )
  1049. var_lookup_fn = _create_interpreter_name_lookup_fn(0)
  1050. if not isinstance(mod, torch.nn.Module):
  1051. raise AttributeError("expected torch.nn.Module as the first argument")
  1052. if not isinstance(inputs, dict):
  1053. raise AttributeError("expected a dictionary of (method_name, input) pairs")
  1054. old_module_map = torch.jit._trace._trace_module_map
  1055. try:
  1056. trace_module_map: Dict[Any, Any] = {}
  1057. def register_submods(mod, prefix):
  1058. for name, child in mod.named_children():
  1059. submod_qualname = prefix + "." + name
  1060. trace_module_map[child] = submod_qualname
  1061. register_submods(child, submod_qualname)
  1062. trace_module_map["__module"] = mod
  1063. torch.jit._trace._trace_module_map = trace_module_map
  1064. register_submods(mod, "__module")
  1065. module = make_module(mod, _module_class, _compilation_unit)
  1066. for method_name, example_inputs in inputs.items():
  1067. if method_name == "forward":
  1068. # "forward" is a special case because we need to trace
  1069. # `Module.__call__`, which sets up some extra tracing, but uses
  1070. # argument names of the real `Module.forward` method.
  1071. func = mod
  1072. forward_method = getattr(mod, method_name)
  1073. argument_names = get_callable_argument_names(forward_method)
  1074. else:
  1075. func = getattr(mod, method_name)
  1076. argument_names = get_callable_argument_names(func)
  1077. if isinstance(example_inputs, dict) and example_inputs_is_kwarg:
  1078. # Raise exception when the user provided key names are not aligned with forward() method's arguments' name/
  1079. for key in example_inputs:
  1080. if key not in argument_names:
  1081. valid_arguments = "[" + ",".join(argument_names) + "]"
  1082. raise NameError(
  1083. f"""'{key}' is not in forward() method's arguments,
  1084. valid arguments name are {valid_arguments}"""
  1085. )
  1086. module._c._create_method_from_trace_with_dict(
  1087. method_name,
  1088. func,
  1089. example_inputs,
  1090. var_lookup_fn,
  1091. strict,
  1092. _force_outplace,
  1093. argument_names,
  1094. _store_inputs,
  1095. )
  1096. else:
  1097. example_inputs = make_tuple(example_inputs)
  1098. module._c._create_method_from_trace(
  1099. method_name,
  1100. func,
  1101. example_inputs,
  1102. var_lookup_fn,
  1103. strict,
  1104. _force_outplace,
  1105. argument_names,
  1106. _store_inputs,
  1107. )
  1108. check_trace_method = module._c._get_method(method_name)
  1109. # Check the trace against new traces created from user-specified inputs
  1110. if check_trace:
  1111. if check_inputs is not None:
  1112. _check_trace(
  1113. check_inputs,
  1114. func,
  1115. check_trace_method,
  1116. check_tolerance,
  1117. strict,
  1118. _force_outplace,
  1119. True,
  1120. _module_class,
  1121. example_inputs_is_kwarg=example_inputs_is_kwarg,
  1122. )
  1123. else:
  1124. _check_trace(
  1125. [inputs],
  1126. func,
  1127. check_trace_method,
  1128. check_tolerance,
  1129. strict,
  1130. _force_outplace,
  1131. True,
  1132. _module_class,
  1133. example_inputs_is_kwarg=example_inputs_is_kwarg,
  1134. )
  1135. finally:
  1136. torch.jit._trace._trace_module_map = old_module_map
  1137. return module
  1138. def is_tracing():
  1139. """Return a boolean value.
  1140. Returns ``True`` in tracing (if a function is called during the
  1141. tracing of code with ``torch.jit.trace``) and ``False`` otherwise.
  1142. """
  1143. if is_scripting():
  1144. return False
  1145. return torch._C._is_tracing()
  1146. class TracedModule(ScriptModule):
  1147. _disable_script_meta = True
  1148. def __init__(self, orig, id_set=None, _compilation_unit=None):
  1149. # XXX: orig can be a nn.Module or a function!
  1150. super().__init__()
  1151. assert isinstance(orig, torch.nn.Module)
  1152. # Copy a subset of `orig` to a temporary nn.Module.
  1153. # This is a way to customize what will actually get compiled by create_script_module
  1154. id_set = set()
  1155. # This allows us to preserve the original module's qualified name by defining a new
  1156. # type with the attribute _jit_override_qualname. In torch._jit_internal._qualified_name
  1157. # we have a special case that will look up this attribute to override whatever qualname
  1158. # we would get from the python type system
  1159. class QualnameWrapper(torch.nn.Module):
  1160. pass
  1161. QualnameWrapper._jit_override_qualname = torch._jit_internal._qualified_name( # type: ignore[attr-defined]
  1162. type(orig)
  1163. )
  1164. tmp_module = QualnameWrapper()
  1165. def check_unique(param):
  1166. if param in id_set:
  1167. raise ValueError(
  1168. "TracedModules don't support parameter sharing between modules"
  1169. )
  1170. id_set.add(param)
  1171. tmp_module.training = orig.training
  1172. for name, param in orig._parameters.items():
  1173. if param is not None:
  1174. tmp_module._parameters[name] = param
  1175. check_unique(param)
  1176. for name, buf in orig._buffers.items():
  1177. if buf is not None:
  1178. tmp_module._buffers[name] = buf
  1179. check_unique(buf)
  1180. for name, val in orig.__dict__.items():
  1181. if (
  1182. torch._C._jit_is_script_object(val)
  1183. and name not in orig._parameters
  1184. and name not in orig._buffers
  1185. ):
  1186. setattr(tmp_module, name, val)
  1187. if orig._backward_hooks:
  1188. raise ValueError(
  1189. "Modules that have backward hooks assigned can't be compiled: "
  1190. + str(orig)
  1191. )
  1192. for name, submodule in orig._modules.items():
  1193. if submodule is None:
  1194. continue
  1195. tmp_module._modules[name] = make_module(
  1196. submodule, TracedModule, _compilation_unit=None
  1197. )
  1198. script_module = torch.jit._recursive.create_script_module(
  1199. tmp_module, lambda module: (), share_types=False, is_tracing=True
  1200. )
  1201. self.__dict__["_name"] = type(orig).__name__
  1202. self.__dict__["_actual_script_module"] = script_module
  1203. for name in ("_parameters", "_buffers", "_modules", "training"):
  1204. delattr(self, name)
  1205. def forward(self, *args, **kwargs):
  1206. raise RuntimeError("Trace submodules cannot be called.")
  1207. def __getattr__(self, attr):
  1208. if "_actual_script_module" not in self.__dict__:
  1209. return super().__getattr__(attr)
  1210. return getattr(self._actual_script_module, attr)
  1211. def __setattr__(self, attr, value):
  1212. if "_actual_script_module" not in self.__dict__:
  1213. return super().__setattr__(attr, value)
  1214. setattr(self._actual_script_module, attr, value)
  1215. def _get_name(self):
  1216. return self._name
  1217. def extra_repr(self):
  1218. return f"original_name={self._name}"
  1219. class TopLevelTracedModule(TracedModule):
  1220. forward: Callable[..., Any] = _CachedForward() # type: ignore[assignment]
  1221. def _reconstruct(self, cpp_module):
  1222. """
  1223. Re-construct an instance of TopLevelTracedModule using an instance of a C++ module.
  1224. Args:
  1225. cpp_module: The C++ module that this TopLevelTracedModule will be rebuilt around.
  1226. """
  1227. self.__dict__["_actual_script_module"]._reconstruct(cpp_module)
  1228. def _script_if_tracing(fn: Callable[P, R]) -> Callable[P, R]:
  1229. @functools.wraps(fn)
  1230. def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
  1231. if not is_tracing():
  1232. # Not tracing, don't do anything
  1233. return fn(*args, **kwargs)
  1234. compiled_fn: Callable[P, R] = script(wrapper.__original_fn) # type: ignore[attr-defined]
  1235. return compiled_fn(*args, **kwargs)
  1236. wrapper.__original_fn = fn # type: ignore[attr-defined]
  1237. wrapper.__script_if_tracing_wrapper = True # type: ignore[attr-defined]
  1238. return wrapper
  1239. def _get_trace_graph(
  1240. f,
  1241. args=(),
  1242. kwargs=None,
  1243. strict=True,
  1244. _force_outplace=False,
  1245. return_inputs=False,
  1246. _return_inputs_states=False,
  1247. ):
  1248. """Return a tuple on tracing a function or model.
  1249. .. warning::
  1250. This function is internal-only and should only be used by the ONNX
  1251. exporter. If you are trying to get a graph through tracing, please go
  1252. through the public API instead::
  1253. trace = torch.jit.trace(nn.LSTMCell(), (input, hidden))
  1254. trace_graph = trace.graph
  1255. Trace a function or model, returning a tuple consisting of the both the
  1256. *trace* of an execution, as well as the original return value. If return_inputs,
  1257. also returns the trace inputs as part of the tuple
  1258. Tracing is guaranteed not to change the semantics of the function/module
  1259. that is traced.
  1260. Args:
  1261. f (torch.nn.Module or function): the function or module
  1262. to be traced.
  1263. args (tuple or Tensor): the positional arguments to pass to the
  1264. function/module to be traced. A non-tuple is assumed to
  1265. be a single positional argument to be passed to the model.
  1266. kwargs (dict): the keyword arguments to pass to the function/module
  1267. to be traced.
  1268. Example (trace a cell):
  1269. .. testcode::
  1270. trace = torch.jit.trace(nn.LSTMCell(), (input, hidden))
  1271. """
  1272. if kwargs is None:
  1273. kwargs = {}
  1274. if not isinstance(args, tuple):
  1275. args = (args,)
  1276. outs = ONNXTracedModule(
  1277. f, strict, _force_outplace, return_inputs, _return_inputs_states
  1278. )(*args, **kwargs)
  1279. return outs