_trace.py 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269
  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, Callable, 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().__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().__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. example_inputs_is_kwarg=False,
  271. ):
  272. # Note: tracing is independent of optimizations, which consume the trace
  273. for inputs in check_inputs:
  274. if isinstance(inputs, torch.Tensor):
  275. inputs = (inputs,)
  276. if is_trace_module:
  277. copied_dict = {}
  278. for name, data in inputs.items():
  279. copied_dict[name] = _clone_inputs(data)
  280. check_mod = torch.jit.trace_module(
  281. func.__self__ if hasattr(func, "__self__") else func,
  282. copied_dict,
  283. check_trace=False,
  284. strict=strict,
  285. _force_outplace=force_outplace,
  286. _module_class=_module_class,
  287. _compilation_unit=torch._C.CompilationUnit(),
  288. example_inputs_is_kwarg=example_inputs_is_kwarg,
  289. _store_inputs=False
  290. )
  291. check_mod_func = check_mod._c._get_method(traced_func.name)
  292. inputs = inputs[traced_func.name]
  293. if isinstance(inputs, (torch.Tensor)) or isinstance(inputs, dict) and not example_inputs_is_kwarg:
  294. inputs = (inputs,)
  295. else:
  296. if example_inputs_is_kwarg:
  297. check_mod = torch.jit.trace(
  298. func,
  299. check_trace=False,
  300. strict=strict,
  301. _force_outplace=force_outplace,
  302. _module_class=_module_class,
  303. example_kwarg_inputs=_clone_inputs(inputs),
  304. _store_inputs=False
  305. )
  306. else:
  307. check_mod = torch.jit.trace(
  308. func,
  309. _clone_inputs(inputs),
  310. check_trace=False,
  311. strict=strict,
  312. _force_outplace=force_outplace,
  313. _module_class=_module_class,
  314. _store_inputs=False
  315. )
  316. check_mod_func = check_mod
  317. def graph_diagnostic_info():
  318. mod_canonicalized = torch._C._jit_pass_canonicalize(traced_func.graph)
  319. torch._C._jit_pass_inline(mod_canonicalized)
  320. torch._C._jit_pass_erase_shape_information(mod_canonicalized)
  321. mod_str = str(mod_canonicalized)
  322. mod_str = re.sub(r"___torch_mangle_[0-9]+\.", "", mod_str)
  323. check_canonicalized = torch._C._jit_pass_canonicalize(check_mod_func.graph)
  324. torch._C._jit_pass_inline(check_canonicalized)
  325. torch._C._jit_pass_erase_shape_information(check_canonicalized)
  326. check_str = str(check_canonicalized)
  327. check_str = re.sub(r"___torch_mangle_[0-9]+\.", "", check_str)
  328. graph_diff_errors = None
  329. if mod_str != check_str:
  330. import difflib
  331. graph_diff = difflib.ndiff(
  332. mod_str.splitlines(True), check_str.splitlines(True)
  333. )
  334. graph_diff_errors = "Graph diff:\n" + indent("".join(graph_diff)) + "\n"
  335. for n_mod, n_check in zip(
  336. mod_canonicalized.nodes(), check_canonicalized.nodes()
  337. ):
  338. if str(n_mod) != str(n_check):
  339. graph_diff_errors += "First diverging operator:\n"
  340. node_diff = difflib.ndiff(
  341. str(n_mod).splitlines(True), str(n_check).splitlines(True)
  342. )
  343. source_printout = (
  344. "Node diff:\n" + indent("".join(node_diff)) + "\n"
  345. )
  346. mod_stack = n_mod.sourceRange()
  347. if mod_stack:
  348. source_printout += (
  349. "Trace source location:\n" + indent(mod_stack) + "\n"
  350. )
  351. check_stack = n_check.sourceRange()
  352. if check_stack:
  353. source_printout += (
  354. "Check source location:\n" + indent(check_stack) + "\n"
  355. )
  356. graph_diff_errors += source_printout
  357. break # For now, only print out the first pair of nodes that diverges
  358. tensor_compare_errors = None
  359. # Check Tensor-valued constant nodes
  360. for n_mod, n_check in zip(
  361. mod_canonicalized.nodes(), check_canonicalized.nodes()
  362. ):
  363. if n_mod.kind() != n_check.kind():
  364. break # Graphs have already diverged
  365. if n_mod.kind() == "prim::Constant" and not (
  366. n_mod.mustBeNone() or n_check.mustBeNone()
  367. ):
  368. if not n_mod.hasAttribute("value"):
  369. continue
  370. if n_mod.kindOf("value") != "t" or n_check.kindOf("value") != "t":
  371. continue
  372. mod_tensor_val = n_mod.t("value")
  373. check_tensor_val = n_check.t("value")
  374. try:
  375. torch.testing.assert_close(mod_tensor_val, check_tensor_val, equal_nan=True)
  376. except (RuntimeError, AssertionError) as e:
  377. if tensor_compare_errors is None:
  378. tensor_compare_errors = ""
  379. tensor_compare_errors += "Node:\n" + indent(str(n_mod)) + "\n"
  380. compare_stack = n_mod.sourceRange()
  381. if compare_stack:
  382. tensor_compare_errors += (
  383. "Source Location:\n" + indent(compare_stack) + "\n"
  384. )
  385. tensor_compare_errors += "Comparison exception: " + indent(
  386. str(e)
  387. )
  388. break # For now, only print the first diverging pair
  389. return graph_diff_errors, tensor_compare_errors
  390. def wrap_retval(x):
  391. return x if isinstance(x, tuple) else (x,)
  392. def run_mod_and_filter_tensor_outputs(mod, inputs, running_what):
  393. try:
  394. if isinstance(inputs, dict) and example_inputs_is_kwarg:
  395. outs = wrap_retval(mod(**inputs))
  396. else:
  397. outs = wrap_retval(mod(*_clone_inputs(inputs)))
  398. outs = [out for out in outs if isinstance(out, torch.Tensor)]
  399. return outs
  400. except Exception as e:
  401. graph_diff_errors, tensor_compare_errors = graph_diagnostic_info()
  402. msg = f"encountered an exception while running the {running_what} with test inputs.\nException:\n{indent(str(e))}"
  403. raise TracingCheckError(
  404. graph_diff_errors,
  405. tensor_compare_errors,
  406. extra_msg=msg,
  407. ) from e
  408. has_warned = [False]
  409. def maybe_warn_nondeterministic():
  410. if has_warned[0]:
  411. return
  412. has_warned[0] = True
  413. nondeterm_ops = [
  414. op for op in traced_func.graph.nodes() if op.isNondeterministic()
  415. ]
  416. if len(nondeterm_ops) > 0:
  417. nondeterministic_ops_warning = "Trace had nondeterministic nodes. "
  418. nondeterministic_ops_warning += (
  419. "Did you forget call .eval() on your model? Nodes:\n"
  420. )
  421. nondeterministic_ops_warning += "\n".join(
  422. [indent(str(op)) for op in nondeterm_ops][:20]
  423. )
  424. nondeterministic_ops_warning += (
  425. "\nThis may cause errors in trace checking. To disable trace checking,"
  426. " pass check_trace=False to torch.jit.trace()"
  427. )
  428. warnings.warn(
  429. nondeterministic_ops_warning, category=TracerWarning, stacklevel=5
  430. )
  431. def compare_outputs(original, reference, match_what):
  432. all_ok = True
  433. for i, (orig, ref) in enumerate(zip(original, reference)):
  434. try:
  435. if orig.is_quantized:
  436. orig = orig.dequantize()
  437. if ref.is_quantized:
  438. ref = ref.dequantize()
  439. if orig.is_mkldnn:
  440. orig = orig.to_dense()
  441. if ref.is_mkldnn:
  442. ref = ref.to_dense()
  443. if ref.is_complex() or orig.is_complex():
  444. torch.testing.assert_close(
  445. orig.to(torch.cdouble),
  446. ref.to(torch.cdouble),
  447. rtol=check_tolerance,
  448. atol=default_tolerances(orig, ref)[1],
  449. equal_nan=True,
  450. )
  451. else:
  452. if orig.is_mps or ref.is_mps:
  453. torch.testing.assert_close(
  454. orig.float(),
  455. ref.float(),
  456. rtol=check_tolerance,
  457. atol=default_tolerances(orig, ref)[1],
  458. equal_nan=True,
  459. )
  460. else:
  461. torch.testing.assert_close(
  462. orig.double(),
  463. ref.double(),
  464. rtol=check_tolerance,
  465. atol=default_tolerances(orig, ref)[1],
  466. equal_nan=True,
  467. )
  468. except AssertionError as e:
  469. maybe_warn_nondeterministic()
  470. warnings.warn(
  471. "Output nr "
  472. + str(i + 1)
  473. + ". of the traced function does not match "
  474. "the corresponding output of the "
  475. + match_what
  476. + ". Detailed error:\n"
  477. + str(e),
  478. category=TracerWarning,
  479. stacklevel=4,
  480. )
  481. all_ok = False
  482. return all_ok
  483. traced_outs = run_mod_and_filter_tensor_outputs(traced_func, inputs, "trace")
  484. fn_outs = run_mod_and_filter_tensor_outputs(func, inputs, "Python function")
  485. if compare_outputs(traced_outs, fn_outs, "Python function"):
  486. check_outs = run_mod_and_filter_tensor_outputs(
  487. check_mod_func, inputs, "repeated trace"
  488. )
  489. compare_outputs(traced_outs, check_outs, "repeated trace")
  490. diag_info = graph_diagnostic_info()
  491. if any(info is not None for info in diag_info):
  492. raise TracingCheckError(*diag_info)
  493. class TracerWarning(Warning):
  494. @staticmethod
  495. def ignore_lib_warnings():
  496. # We ignore warnings from all submodules excluding the JIT, because we need them e.g. for _check_trace
  497. warnings.filterwarnings(
  498. "ignore", category=TracerWarning, module="torch.(?!jit)"
  499. )
  500. # We ignore the tracer warnings coming form inside the library, because all our shape
  501. # checks in nn will trigger them.
  502. TracerWarning.ignore_lib_warnings()
  503. torch._C._tracer_warn_use_python()
  504. def make_tuple(example_inputs):
  505. if isinstance(example_inputs, (torch.Tensor, dict)):
  506. return (example_inputs,)
  507. # done primarily so that weird iterables fail here and not pybind11 code
  508. if not isinstance(example_inputs, tuple):
  509. return tuple(example_inputs)
  510. return example_inputs
  511. def make_module(mod, _module_class, _compilation_unit):
  512. if isinstance(mod, ScriptModule):
  513. return mod
  514. elif torch._jit_internal.module_has_exports(mod):
  515. infer_methods_stubs_fn = torch.jit._recursive.make_stubs_from_exported_methods
  516. return torch.jit._recursive.create_script_module(
  517. mod,
  518. infer_methods_stubs_fn,
  519. share_types=False,
  520. is_tracing=True
  521. )
  522. else:
  523. if _module_class is None:
  524. _module_class = TopLevelTracedModule
  525. return _module_class(mod, _compilation_unit=_compilation_unit)
  526. def wrap_check_inputs(check_inputs):
  527. if check_inputs is None:
  528. return None
  529. return [{"forward": c} for c in check_inputs]
  530. def trace(
  531. func,
  532. example_inputs=None,
  533. optimize=None,
  534. check_trace=True,
  535. check_inputs=None,
  536. check_tolerance=1e-5,
  537. strict=True,
  538. _force_outplace=False,
  539. _module_class=None,
  540. _compilation_unit=_python_cu,
  541. example_kwarg_inputs=None,
  542. _store_inputs=True
  543. ):
  544. """
  545. Trace a function and return an executable or :class:`ScriptFunction`
  546. that will be optimized using just-in-time compilation. Tracing is ideal for
  547. code that operates only on ``Tensor``\\s and lists, dictionaries, and
  548. tuples of ``Tensor``\\s.
  549. Using `torch.jit.trace` and `torch.jit.trace_module`, you can turn an
  550. existing module or Python function into a TorchScript
  551. :class:`ScriptFunction` or :class:`ScriptModule`. You must provide example
  552. inputs, and we run the function, recording the operations performed on all
  553. the tensors.
  554. * The resulting recording of a standalone function produces `ScriptFunction`.
  555. * The resulting recording of `nn.Module.forward` or `nn.Module` produces
  556. `ScriptModule`.
  557. This module also contains any parameters that the original
  558. module had as well.
  559. Warning:
  560. Tracing only correctly records functions and modules which are not data
  561. dependent (e.g., do not have conditionals on data in tensors) and do not have
  562. any untracked external dependencies (e.g., perform input/output or
  563. access global variables). Tracing only records operations done when the given
  564. function is run on the given tensors. Therefore, the returned
  565. `ScriptModule` will always run the same traced graph on any input. This
  566. has some important implications when your module is expected to run
  567. different sets of operations, depending on the input and/or the module
  568. state. For example,
  569. * Tracing will not record any control-flow like if-statements or loops.
  570. When this control-flow is constant across your module, this is fine
  571. and it often inlines the control-flow decisions. But sometimes the
  572. control-flow is actually part of the model itself. For instance, a
  573. recurrent network is a loop over the (possibly dynamic) length of an
  574. input sequence.
  575. * In the returned :class:`ScriptModule`, operations that have different
  576. behaviors in ``training`` and ``eval`` modes will always behave as if
  577. it is in the mode it was in during tracing, no matter which mode the
  578. `ScriptModule` is in.
  579. In cases like these, tracing would not be appropriate and
  580. :func:`scripting <torch.jit.script>` is a better choice. If you trace
  581. such models, you may silently get incorrect results on subsequent
  582. invocations of the model. The tracer will try to emit warnings when
  583. doing something that may cause an incorrect trace to be produced.
  584. Args:
  585. func (callable or torch.nn.Module): A Python function or `torch.nn.Module`
  586. that will be run with `example_inputs`. `func` arguments and return
  587. values must be tensors or (possibly nested) tuples that contain
  588. tensors. When a module is passed `torch.jit.trace`, only the
  589. ``forward`` method is run and traced (see :func:`torch.jit.trace
  590. <torch.jit.trace_module>` for details).
  591. Keyword arguments:
  592. example_inputs (tuple or torch.Tensor or None, optional): A tuple of example
  593. inputs that will be passed to the function while tracing.
  594. Default: ``None``. Either this argument or ``example_kwarg_inputs``
  595. should be specified. The resulting trace can be run with inputs of
  596. different types and shapes assuming the traced operations support those
  597. types and shapes. `example_inputs` may also be a single Tensor in which
  598. case it is automatically wrapped in a tuple. When the value is None,
  599. ``example_kwarg_inputs`` should be specified.
  600. check_trace (``bool``, optional): Check if the same inputs run through
  601. traced code produce the same outputs. Default: ``True``. You might want
  602. to disable this if, for example, your network contains non-
  603. deterministic ops or if you are sure that the network is correct despite
  604. a checker failure.
  605. check_inputs (list of tuples, optional): A list of tuples of input
  606. arguments that should be used to check the trace against what is
  607. expected. Each tuple is equivalent to a set of input arguments that
  608. would be specified in ``example_inputs``. For best results, pass in
  609. a set of checking inputs representative of the space of shapes and
  610. types of inputs you expect the network to see. If not specified,
  611. the original ``example_inputs`` are used for checking
  612. check_tolerance (float, optional): Floating-point comparison tolerance
  613. to use in the checker procedure. This can be used to relax the
  614. checker strictness in the event that results diverge numerically
  615. for a known reason, such as operator fusion.
  616. strict (``bool``, optional): run the tracer in a strict mode or not
  617. (default: ``True``). Only turn this off when you want the tracer to
  618. record your mutable container types (currently ``list``/``dict``)
  619. and you are sure that the container you are using in your
  620. problem is a ``constant`` structure and does not get used as
  621. control flow (if, for) conditions.
  622. example_kwarg_inputs (dict, optional): This parameter is a pack of keyword
  623. arguments of example inputs that will be passed to the function while
  624. tracing. Default: ``None``. Either this argument or ``example_inputs``
  625. should be specified. The dict will be unpacking by the arguments name
  626. of the traced function. If the keys of the dict don't not match with
  627. the traced function's arguments name, a runtime exception will be raised.
  628. Returns:
  629. If `func` is `nn.Module` or ``forward`` of `nn.Module`, `trace` returns
  630. a :class:`ScriptModule` object with a single ``forward`` method
  631. containing the traced code. The returned `ScriptModule` will
  632. have the same set of sub-modules and parameters as the original
  633. ``nn.Module``. If ``func`` is a standalone function, ``trace``
  634. returns `ScriptFunction`.
  635. Example (tracing a function):
  636. .. testcode::
  637. import torch
  638. def foo(x, y):
  639. return 2 * x + y
  640. # Run `foo` with the provided inputs and record the tensor operations
  641. traced_foo = torch.jit.trace(foo, (torch.rand(3), torch.rand(3)))
  642. # `traced_foo` can now be run with the TorchScript interpreter or saved
  643. # and loaded in a Python-free environment
  644. Example (tracing an existing module)::
  645. import torch
  646. import torch.nn as nn
  647. class Net(nn.Module):
  648. def __init__(self):
  649. super().__init__()
  650. self.conv = nn.Conv2d(1, 1, 3)
  651. def forward(self, x):
  652. return self.conv(x)
  653. n = Net()
  654. example_weight = torch.rand(1, 1, 3, 3)
  655. example_forward_input = torch.rand(1, 1, 3, 3)
  656. # Trace a specific method and construct `ScriptModule` with
  657. # a single `forward` method
  658. module = torch.jit.trace(n.forward, example_forward_input)
  659. # Trace a module (implicitly traces `forward`) and construct a
  660. # `ScriptModule` with a single `forward` method
  661. module = torch.jit.trace(n, example_forward_input)
  662. """
  663. if not _enabled:
  664. return func
  665. if optimize is not None:
  666. warnings.warn(
  667. "`optimize` is deprecated and has no effect. Use `with torch.jit.optimized_execution() instead"
  668. )
  669. if isinstance(func, torch.jit.ScriptModule):
  670. # it is hard to trace it because the forward method on ScriptModule is already defined, so it
  671. # would result in an error.
  672. warnings.warn(
  673. "The input to trace is already a ScriptModule, tracing it is a no-op. Returning the object as is."
  674. )
  675. return func
  676. if isinstance(func, torch.nn.Module):
  677. if example_inputs is None:
  678. if isinstance(example_kwarg_inputs, dict):
  679. example_inputs = example_kwarg_inputs
  680. else:
  681. raise RuntimeError("example_kwarg_inputs should be a dict")
  682. return trace_module(
  683. func,
  684. {"forward": example_inputs},
  685. None,
  686. check_trace,
  687. wrap_check_inputs(check_inputs),
  688. check_tolerance,
  689. strict,
  690. _force_outplace,
  691. _module_class,
  692. example_inputs_is_kwarg=isinstance(example_kwarg_inputs, dict),
  693. _store_inputs=_store_inputs
  694. )
  695. if (
  696. hasattr(func, "__self__")
  697. and isinstance(func.__self__, torch.nn.Module)
  698. and func.__name__ == "forward"
  699. ):
  700. if example_inputs is None:
  701. if isinstance(example_kwarg_inputs, dict):
  702. example_inputs = example_kwarg_inputs
  703. else:
  704. raise RuntimeError("example_kwarg_inputs should be a dict")
  705. return trace_module(
  706. func.__self__,
  707. {"forward": example_inputs},
  708. None,
  709. check_trace,
  710. wrap_check_inputs(check_inputs),
  711. check_tolerance,
  712. strict,
  713. _force_outplace,
  714. _module_class,
  715. example_inputs_is_kwarg=isinstance(example_kwarg_inputs, dict),
  716. _store_inputs=_store_inputs
  717. )
  718. # Special case for common case of passing a single Tensor
  719. if isinstance(example_inputs, (torch.Tensor, dict)) and example_kwarg_inputs is None:
  720. example_inputs = (example_inputs,)
  721. # done primarily so that weird iterables fail here and not pybind11 code
  722. elif example_kwarg_inputs is None and not isinstance(example_inputs, tuple):
  723. example_inputs = tuple(example_inputs)
  724. var_lookup_fn = _create_interpreter_name_lookup_fn(0)
  725. if hasattr(func, "__self__") and isinstance(func.__self__, torch.nn.Module):
  726. raise AttributeError(
  727. "trace doesn't support compiling individual module's functions.\n"
  728. "Please use trace_module"
  729. )
  730. name = _qualified_name(func)
  731. if isinstance(example_kwarg_inputs, dict):
  732. example_inputs = example_kwarg_inputs
  733. traced = torch._C._create_function_from_trace_with_dict(
  734. name,
  735. func,
  736. example_kwarg_inputs,
  737. var_lookup_fn,
  738. strict,
  739. _force_outplace,
  740. get_callable_argument_names(func)
  741. )
  742. else:
  743. traced = torch._C._create_function_from_trace(
  744. name,
  745. func,
  746. example_inputs,
  747. var_lookup_fn,
  748. strict,
  749. _force_outplace,
  750. get_callable_argument_names(func)
  751. )
  752. # Check the trace against new traces created from user-specified inputs
  753. if check_trace:
  754. if check_inputs is not None:
  755. _check_trace(
  756. check_inputs,
  757. func,
  758. traced,
  759. check_tolerance,
  760. strict,
  761. _force_outplace,
  762. False,
  763. _module_class,
  764. example_inputs_is_kwarg=isinstance(example_kwarg_inputs, dict),
  765. )
  766. else:
  767. _check_trace(
  768. [example_inputs],
  769. func,
  770. traced,
  771. check_tolerance,
  772. strict,
  773. _force_outplace,
  774. False,
  775. _module_class,
  776. example_inputs_is_kwarg=isinstance(example_kwarg_inputs, dict),
  777. )
  778. # Allow torch.compile() to inline
  779. traced._torchdynamo_inline = func # type: ignore[attr-defined]
  780. return traced
  781. _trace_module_map: Optional[Dict[Any, Any]] = None
  782. def trace_module(
  783. mod,
  784. inputs,
  785. optimize=None,
  786. check_trace=True,
  787. check_inputs=None,
  788. check_tolerance=1e-5,
  789. strict=True,
  790. _force_outplace=False,
  791. _module_class=None,
  792. _compilation_unit=_python_cu,
  793. example_inputs_is_kwarg=False,
  794. _store_inputs=True,
  795. ):
  796. """
  797. Trace a module and return an executable :class:`ScriptModule` that will be optimized
  798. using just-in-time compilation. When a module is passed to :func:`torch.jit.trace <torch.jit.trace>`, only
  799. the ``forward`` method is run and traced. With ``trace_module``, you can specify a dictionary of
  800. method names to example inputs to trace (see the ``inputs``) argument below.
  801. See :func:`torch.jit.trace <torch.jit.trace>` for more information on tracing.
  802. Args:
  803. mod (torch.nn.Module): A ``torch.nn.Module`` containing methods whose names are
  804. specified in ``inputs``. The given methods will be compiled
  805. as a part of a single `ScriptModule`.
  806. inputs (dict): A dict containing sample inputs indexed by method names in ``mod``.
  807. The inputs will be passed to methods whose names correspond to inputs'
  808. keys while tracing.
  809. ``{ 'forward' : example_forward_input, 'method2': example_method2_input}``
  810. Keyword arguments:
  811. check_trace (``bool``, optional): Check if the same inputs run through
  812. traced code produce the same outputs. Default: ``True``. You might want
  813. to disable this if, for example, your network contains non-
  814. deterministic ops or if you are sure that the network is correct despite
  815. a checker failure.
  816. check_inputs (list of dicts, optional): A list of dicts of input arguments that should be used
  817. to check the trace against what is expected. Each tuple
  818. is equivalent to a set of input arguments that would
  819. be specified in ``inputs``. For best results, pass in a
  820. set of checking inputs representative of the space of
  821. shapes and types of inputs you expect the network to see.
  822. If not specified, the original ``inputs`` are used for checking
  823. check_tolerance (float, optional): Floating-point comparison tolerance to use in the checker procedure.
  824. This can be used to relax the checker strictness in the event that
  825. results diverge numerically for a known reason, such as operator fusion.
  826. example_inputs_is_kwarg (``bool``, optional): This parameter indicate whether the example inputs is a pack
  827. pack of keyword arguments. Default: ``False``.
  828. Returns:
  829. A :class:`ScriptModule` object with a single ``forward`` method containing the traced code.
  830. When ``func`` is a ``torch.nn.Module``, the returned :class:`ScriptModule` will have the same set of
  831. sub-modules and parameters as ``func``.
  832. Example (tracing a module with multiple methods)::
  833. import torch
  834. import torch.nn as nn
  835. class Net(nn.Module):
  836. def __init__(self):
  837. super().__init__()
  838. self.conv = nn.Conv2d(1, 1, 3)
  839. def forward(self, x):
  840. return self.conv(x)
  841. def weighted_kernel_sum(self, weight):
  842. return weight * self.conv.weight
  843. n = Net()
  844. example_weight = torch.rand(1, 1, 3, 3)
  845. example_forward_input = torch.rand(1, 1, 3, 3)
  846. # Trace a specific method and construct `ScriptModule` with
  847. # a single `forward` method
  848. module = torch.jit.trace(n.forward, example_forward_input)
  849. # Trace a module (implicitly traces `forward`) and construct a
  850. # `ScriptModule` with a single `forward` method
  851. module = torch.jit.trace(n, example_forward_input)
  852. # Trace specific methods on a module (specified in `inputs`), constructs
  853. # a `ScriptModule` with `forward` and `weighted_kernel_sum` methods
  854. inputs = {'forward' : example_forward_input, 'weighted_kernel_sum' : example_weight}
  855. module = torch.jit.trace_module(n, inputs)
  856. """
  857. if not _enabled:
  858. return mod
  859. if optimize is not None:
  860. warnings.warn(
  861. "`optimize` is deprecated and has no effect. Use `with torch.jit.optimized_execution() instead"
  862. )
  863. var_lookup_fn = _create_interpreter_name_lookup_fn(0)
  864. if not isinstance(mod, torch.nn.Module):
  865. raise AttributeError("expected torch.nn.Module as the first argument")
  866. if not isinstance(inputs, dict):
  867. raise AttributeError("expected a dictionary of (method_name, input) pairs")
  868. old_module_map = torch.jit._trace._trace_module_map
  869. try:
  870. trace_module_map: Dict[Any, Any] = {}
  871. def register_submods(mod, prefix):
  872. for name, child in mod.named_children():
  873. submod_qualname = prefix + "." + name
  874. trace_module_map[child] = submod_qualname
  875. register_submods(child, submod_qualname)
  876. trace_module_map["__module"] = mod
  877. torch.jit._trace._trace_module_map = trace_module_map
  878. register_submods(mod, "__module")
  879. module = make_module(mod, _module_class, _compilation_unit)
  880. for method_name, example_inputs in inputs.items():
  881. if method_name == "forward":
  882. # "forward" is a special case because we need to trace
  883. # `Module.__call__`, which sets up some extra tracing, but uses
  884. # argument names of the real `Module.forward` method.
  885. func = mod
  886. forward_method = getattr(mod, method_name)
  887. argument_names = get_callable_argument_names(forward_method)
  888. else:
  889. func = getattr(mod, method_name)
  890. argument_names = get_callable_argument_names(func)
  891. if isinstance(example_inputs, dict) and example_inputs_is_kwarg:
  892. # Raise exception when the user provided key names are not aligned with forward() method's arguments' name/
  893. for key in example_inputs:
  894. if key not in argument_names:
  895. valid_arguments = "[" + ','.join(argument_names) + "]"
  896. raise NameError("""'{}' is not in forward() method's arguments,
  897. valid arguments name are {}""".format(key, valid_arguments))
  898. module._c._create_method_from_trace_with_dict(
  899. method_name,
  900. func,
  901. example_inputs,
  902. var_lookup_fn,
  903. strict,
  904. _force_outplace,
  905. argument_names,
  906. _store_inputs
  907. )
  908. else:
  909. example_inputs = make_tuple(example_inputs)
  910. module._c._create_method_from_trace(
  911. method_name,
  912. func,
  913. example_inputs,
  914. var_lookup_fn,
  915. strict,
  916. _force_outplace,
  917. argument_names,
  918. _store_inputs
  919. )
  920. check_trace_method = module._c._get_method(method_name)
  921. # Check the trace against new traces created from user-specified inputs
  922. if check_trace:
  923. if check_inputs is not None:
  924. _check_trace(
  925. check_inputs,
  926. func,
  927. check_trace_method,
  928. check_tolerance,
  929. strict,
  930. _force_outplace,
  931. True,
  932. _module_class,
  933. example_inputs_is_kwarg=example_inputs_is_kwarg,
  934. )
  935. else:
  936. _check_trace(
  937. [inputs],
  938. func,
  939. check_trace_method,
  940. check_tolerance,
  941. strict,
  942. _force_outplace,
  943. True,
  944. _module_class,
  945. example_inputs_is_kwarg=example_inputs_is_kwarg,
  946. )
  947. finally:
  948. torch.jit._trace._trace_module_map = old_module_map
  949. return module
  950. def is_tracing():
  951. """
  952. Returns ``True`` in tracing (if a function is called during the tracing of
  953. code with ``torch.jit.trace``) and ``False`` otherwise.
  954. """
  955. if is_scripting():
  956. return False
  957. return torch._C._is_tracing()
  958. class TracedModule(ScriptModule):
  959. _disable_script_meta = True
  960. def __init__(self, orig, id_set=None, _compilation_unit=None):
  961. # XXX: orig can be a nn.Module or a function!
  962. super().__init__()
  963. assert isinstance(orig, torch.nn.Module)
  964. # Copy a subset of `orig` to a temporary nn.Module.
  965. # This is a way to customize what will actually get compiled by create_script_module
  966. id_set = set()
  967. # This allows us to preserve the original module's qualified name by defining a new
  968. # type with the attribute _jit_override_qualname. In torch._jit_internal._qualified_name
  969. # we have a special case that will look up this attribute to override whatever qualname
  970. # we would get from the python type system
  971. class QualnameWrapper(torch.nn.Module):
  972. pass
  973. QualnameWrapper._jit_override_qualname = torch._jit_internal._qualified_name( # type: ignore[attr-defined]
  974. type(orig)
  975. )
  976. tmp_module = QualnameWrapper()
  977. def check_unique(param):
  978. if param in id_set:
  979. raise ValueError(
  980. "TracedModules don't support parameter sharing between modules"
  981. )
  982. id_set.add(param)
  983. tmp_module.training = orig.training
  984. for name, param in orig._parameters.items():
  985. if param is not None:
  986. tmp_module._parameters[name] = param
  987. check_unique(param)
  988. for name, buf in orig._buffers.items():
  989. if buf is not None:
  990. tmp_module._buffers[name] = buf
  991. check_unique(buf)
  992. for name, val in orig.__dict__.items():
  993. if (
  994. torch._C._jit_is_script_object(val)
  995. and name not in orig._parameters
  996. and name not in orig._buffers
  997. ):
  998. setattr(tmp_module, name, val)
  999. if orig._backward_hooks:
  1000. raise ValueError(
  1001. "Modules that have backward hooks assigned can't be compiled: "
  1002. + str(orig)
  1003. )
  1004. for name, submodule in orig._modules.items():
  1005. if submodule is None:
  1006. continue
  1007. tmp_module._modules[name] = make_module(
  1008. submodule, TracedModule, _compilation_unit=None
  1009. )
  1010. script_module = torch.jit._recursive.create_script_module(
  1011. tmp_module, lambda module: (), share_types=False, is_tracing=True
  1012. )
  1013. self.__dict__["_name"] = type(orig).__name__
  1014. self.__dict__["_actual_script_module"] = script_module
  1015. for name in ("_parameters", "_buffers", "_modules", "training"):
  1016. delattr(self, name)
  1017. def forward(self, *args, **kwargs):
  1018. raise RuntimeError("Trace submodules cannot be called.")
  1019. def __getattr__(self, attr):
  1020. if "_actual_script_module" not in self.__dict__:
  1021. return super().__getattr__(attr)
  1022. return getattr(self._actual_script_module, attr)
  1023. def __setattr__(self, attr, value):
  1024. if "_actual_script_module" not in self.__dict__:
  1025. return super().__setattr__(attr, value)
  1026. setattr(self._actual_script_module, attr, value)
  1027. def _get_name(self):
  1028. return self._name
  1029. def extra_repr(self):
  1030. return "original_name={}".format(self._name)
  1031. class TopLevelTracedModule(TracedModule):
  1032. forward: Callable[..., Any] = _CachedForward() # type: ignore[assignment]
  1033. def _reconstruct(self, cpp_module):
  1034. """
  1035. Re-construct an instance of TopLevelTracedModule using an instance of a C++ module.
  1036. Args:
  1037. cpp_module: The C++ module that this TopLevelTracedModule will be rebuilt around.
  1038. """
  1039. self.__dict__["_actual_script_module"]._reconstruct(cpp_module)
  1040. def _script_if_tracing(fn):
  1041. @functools.wraps(fn)
  1042. def wrapper(*args, **kwargs):
  1043. if not is_tracing():
  1044. # Not tracing, don't do anything
  1045. return fn(*args, **kwargs)
  1046. compiled_fn = script(wrapper.__original_fn) # type: ignore[attr-defined]
  1047. return compiled_fn(*args, **kwargs)
  1048. wrapper.__original_fn = fn # type: ignore[attr-defined]
  1049. wrapper.__script_if_tracing_wrapper = True # type: ignore[attr-defined]
  1050. return wrapper
  1051. def _get_trace_graph(f, args=(), kwargs=None, strict=True, _force_outplace=False,
  1052. return_inputs=False, _return_inputs_states=False):
  1053. """
  1054. .. warning::
  1055. This function is internal-only and should only be used by the ONNX
  1056. exporter. If you are trying to get a graph through tracing, please go
  1057. through the public API instead::
  1058. trace = torch.jit.trace(nn.LSTMCell(), (input, hidden))
  1059. trace_graph = trace.graph
  1060. Trace a function or model, returning a tuple consisting of the both the
  1061. *trace* of an execution, as well as the original return value. If return_inputs,
  1062. also returns the trace inputs as part of the tuple
  1063. Tracing is guaranteed not to change the semantics of the function/module
  1064. that is traced.
  1065. Args:
  1066. f (torch.nn.Module or function): the function or module
  1067. to be traced.
  1068. args (tuple or Tensor): the positional arguments to pass to the
  1069. function/module to be traced. A non-tuple is assumed to
  1070. be a single positional argument to be passed to the model.
  1071. kwargs (dict): the keyword arguments to pass to the function/module
  1072. to be traced.
  1073. Example (trace a cell):
  1074. .. testcode::
  1075. trace = torch.jit.trace(nn.LSTMCell(), (input, hidden))
  1076. """
  1077. if kwargs is None:
  1078. kwargs = {}
  1079. if not isinstance(args, tuple):
  1080. args = (args,)
  1081. outs = ONNXTracedModule(f, strict, _force_outplace, return_inputs, _return_inputs_states)(*args, **kwargs)
  1082. return outs