_trace.py 45 KB

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