__init__.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. import builtins
  2. import copy
  3. import dataclasses
  4. import inspect
  5. import io
  6. import os
  7. import sys
  8. import typing
  9. import warnings
  10. from enum import auto, Enum
  11. from typing import (
  12. Any,
  13. Callable,
  14. Dict,
  15. Iterator,
  16. List,
  17. Optional,
  18. Tuple,
  19. Type,
  20. TYPE_CHECKING,
  21. Union,
  22. )
  23. import torch
  24. import torch.utils._pytree as pytree
  25. from torch.fx._compatibility import compatibility
  26. from torch.fx.passes.infra.pass_base import PassResult
  27. from torch.fx.passes.infra.pass_manager import PassManager
  28. from torch.utils._pytree import (
  29. FlattenFunc,
  30. FromDumpableContextFn,
  31. ToDumpableContextFn,
  32. UnflattenFunc,
  33. )
  34. if TYPE_CHECKING:
  35. # Import the following modules during type checking to enable code intelligence features,
  36. # Do not import unconditionally, as they import sympy and importing sympy is very slow
  37. from torch.fx.experimental.symbolic_shapes import StrictMinMaxConstraint
  38. __all__ = [
  39. "Constraint",
  40. "Dim",
  41. "ExportBackwardSignature",
  42. "ExportGraphSignature",
  43. "ExportedProgram",
  44. "ModuleCallEntry",
  45. "ModuleCallSignature",
  46. "dims",
  47. "dynamic_dim",
  48. "export",
  49. "load",
  50. "register_dataclass",
  51. "save",
  52. "unflatten",
  53. "FlatArgsAdapter",
  54. "UnflattenedModule",
  55. ]
  56. from .dynamic_shapes import Constraint, Dim, dims, dynamic_dim, ShapesCollection
  57. from .exported_program import ExportedProgram, ModuleCallEntry, ModuleCallSignature
  58. from .graph_signature import ExportBackwardSignature, ExportGraphSignature
  59. from .unflatten import FlatArgsAdapter, unflatten, UnflattenedModule
  60. PassType = Callable[[torch.fx.GraphModule], Optional[PassResult]]
  61. def export(
  62. mod: torch.nn.Module,
  63. args: Tuple[Any, ...],
  64. kwargs: Optional[Dict[str, Any]] = None,
  65. *,
  66. dynamic_shapes: Optional[Union[Dict[str, Any], Tuple[Any], List[Any]]] = None,
  67. strict: bool = True,
  68. preserve_module_call_signature: Tuple[str, ...] = (),
  69. ) -> ExportedProgram:
  70. """
  71. :func:`export` takes an arbitrary Python callable (an nn.Module, a function or
  72. a method) along with example inputs, and produces a traced graph representing
  73. only the Tensor computation of the function in an Ahead-of-Time (AOT) fashion,
  74. which can subsequently be executed with different inputs or serialized. The
  75. traced graph (1) produces normalized operators in the functional ATen operator set
  76. (as well as any user-specified custom operators), (2) has eliminated all Python control
  77. flow and data structures (with certain exceptions), and (3) records the set of
  78. shape constraints needed to show that this normalization and control-flow elimination
  79. is sound for future inputs.
  80. **Soundness Guarantee**
  81. While tracing, :func:`export()` takes note of shape-related assumptions
  82. made by the user program and the underlying PyTorch operator kernels.
  83. The output :class:`ExportedProgram` is considered valid only when these
  84. assumptions hold true.
  85. Tracing makes assumptions on the shapes (not values) of input tensors.
  86. Such assumptions must be validated at graph capture time for :func:`export`
  87. to succeed. Specifically:
  88. - Assumptions on static shapes of input tensors are automatically validated without additional effort.
  89. - Assumptions on dynamic shape of input tensors require explicit specification
  90. by using the :func:`Dim` API to construct dynamic dimensions and by associating
  91. them with example inputs through the ``dynamic_shapes`` argument.
  92. If any assumption can not be validated, a fatal error will be raised. When that happens,
  93. the error message will include suggested fixes to the specification that are needed
  94. to validate the assumptions. For example :func:`export` might suggest the
  95. following fix to the definition of a dynamic dimension ``dim0_x``, say appearing in the
  96. shape associated with input ``x``, that was previously defined as ``Dim("dim0_x")``::
  97. dim = Dim("dim0_x", max=5)
  98. This example means the generated code requires dimension 0 of input ``x`` to be less
  99. than or equal to 5 to be valid. You can inspect the suggested fixes to dynamic dimension
  100. definitions and then copy them verbatim into your code without needing to change the
  101. ``dynamic_shapes`` argument to your :func:`export` call.
  102. Args:
  103. mod: We will trace the forward method of this module.
  104. args: Example positional inputs.
  105. kwargs: Optional example keyword inputs.
  106. dynamic_shapes:
  107. An optional argument where the type should either be:
  108. 1) a dict from argument names of ``f`` to their dynamic shape specifications,
  109. 2) a tuple that specifies dynamic shape specifications for each input in original order.
  110. If you are specifying dynamism on keyword args, you will need to pass them in the order that
  111. is defined in the original function signature.
  112. The dynamic shape of a tensor argument can be specified as either
  113. (1) a dict from dynamic dimension indices to :func:`Dim` types, where it is
  114. not required to include static dimension indices in this dict, but when they are,
  115. they should be mapped to None; or (2) a tuple / list of :func:`Dim` types or None,
  116. where the :func:`Dim` types correspond to dynamic dimensions, and static dimensions
  117. are denoted by None. Arguments that are dicts or tuples / lists of tensors are
  118. recursively specified by using mappings or sequences of contained specifications.
  119. strict: When enabled (default), the export function will trace the program through
  120. TorchDynamo which will ensure the soundness of the resulting graph. Otherwise, the
  121. exported program will not validate the implicit assumptions baked into the graph and
  122. may cause behavior divergence between the original model and the exported one. This is
  123. useful when users need to workaround bugs in the tracer, or simply want incrementally
  124. enable safety in their models. Note that this does not affect the resulting IR spec
  125. to be different and the model will be serialized in the same way regardless of what value
  126. is passed here.
  127. WARNING: This option is experimental and use this at your own risk.
  128. Returns:
  129. An :class:`ExportedProgram` containing the traced callable.
  130. **Acceptable input/output types**
  131. Acceptable types of inputs (for ``args`` and ``kwargs``) and outputs include:
  132. - Primitive types, i.e. ``torch.Tensor``, ``int``, ``float``, ``bool`` and ``str``.
  133. - Dataclasses, but they must be registered by calling :func:`register_dataclass` first.
  134. - (Nested) Data structures comprising of ``dict``, ``list``, ``tuple``, ``namedtuple`` and
  135. ``OrderedDict`` containing all above types.
  136. """
  137. from ._trace import _export
  138. if not isinstance(mod, torch.nn.Module):
  139. raise ValueError(
  140. f"Expected `mod` to be an instance of `torch.nn.Module`, got {type(mod)}."
  141. )
  142. return _export(
  143. mod,
  144. args,
  145. kwargs,
  146. dynamic_shapes,
  147. strict=strict,
  148. preserve_module_call_signature=preserve_module_call_signature,
  149. pre_dispatch=True,
  150. )
  151. def save(
  152. ep: ExportedProgram,
  153. f: Union[str, os.PathLike, io.BytesIO],
  154. *,
  155. extra_files: Optional[Dict[str, Any]] = None,
  156. opset_version: Optional[Dict[str, int]] = None,
  157. ) -> None:
  158. """
  159. .. warning::
  160. Under active development, saved files may not be usable in newer versions
  161. of PyTorch.
  162. Saves an :class:`ExportedProgram` to a file-like object. It can then be
  163. loaded using the Python API :func:`torch.export.load <torch.export.load>`.
  164. Args:
  165. ep (ExportedProgram): The exported program to save.
  166. f (Union[str, os.PathLike, io.BytesIO): A file-like object (has to
  167. implement write and flush) or a string containing a file name.
  168. extra_files (Optional[Dict[str, Any]]): Map from filename to contents
  169. which will be stored as part of f.
  170. opset_version (Optional[Dict[str, int]]): A map of opset names
  171. to the version of this opset
  172. Example::
  173. import torch
  174. import io
  175. class MyModule(torch.nn.Module):
  176. def forward(self, x):
  177. return x + 10
  178. ep = torch.export.export(MyModule(), (torch.randn(5),))
  179. # Save to file
  180. torch.export.save(ep, 'exported_program.pt2')
  181. # Save to io.BytesIO buffer
  182. buffer = io.BytesIO()
  183. torch.export.save(ep, buffer)
  184. # Save with extra files
  185. extra_files = {'foo.txt': b'bar'.decode('utf-8')}
  186. torch.export.save(ep, 'exported_program.pt2', extra_files=extra_files)
  187. """
  188. from torch._export import save
  189. if not isinstance(ep, ExportedProgram):
  190. raise TypeError(
  191. f"The 'ep' parameter must be an instance of 'ExportedProgram', got '{type(ep).__name__}' instead."
  192. )
  193. save(ep, f, extra_files=extra_files, opset_version=opset_version)
  194. def load(
  195. f: Union[str, os.PathLike, io.BytesIO],
  196. *,
  197. extra_files: Optional[Dict[str, Any]] = None,
  198. expected_opset_version: Optional[Dict[str, int]] = None,
  199. ) -> ExportedProgram:
  200. """
  201. .. warning::
  202. Under active development, saved files may not be usable in newer versions
  203. of PyTorch.
  204. Loads an :class:`ExportedProgram` previously saved with
  205. :func:`torch.export.save <torch.export.save>`.
  206. Args:
  207. ep (ExportedProgram): The exported program to save.
  208. f (Union[str, os.PathLike, io.BytesIO): A file-like object (has to
  209. implement write and flush) or a string containing a file name.
  210. extra_files (Optional[Dict[str, Any]]): The extra filenames given in
  211. this map would be loaded and their content would be stored in the
  212. provided map.
  213. expected_opset_version (Optional[Dict[str, int]]): A map of opset names
  214. to expected opset versions
  215. Returns:
  216. An :class:`ExportedProgram` object
  217. Example::
  218. import torch
  219. import io
  220. # Load ExportedProgram from file
  221. ep = torch.export.load('exported_program.pt2')
  222. # Load ExportedProgram from io.BytesIO object
  223. with open('exported_program.pt2', 'rb') as f:
  224. buffer = io.BytesIO(f.read())
  225. buffer.seek(0)
  226. ep = torch.export.load(buffer)
  227. # Load with extra files.
  228. extra_files = {'foo.txt': ''} # values will be replaced with data
  229. ep = torch.export.load('exported_program.pt2', extra_files=extra_files)
  230. print(extra_files['foo.txt'])
  231. print(ep(torch.randn(5)))
  232. """
  233. from torch._export import load
  234. return load(
  235. f, extra_files=extra_files, expected_opset_version=expected_opset_version
  236. )
  237. def register_dataclass(
  238. cls: Type[Any],
  239. *,
  240. serialized_type_name: Optional[str] = None,
  241. ) -> None:
  242. """
  243. Registers a dataclass as a valid input/output type for :func:`torch.export.export`.
  244. Args:
  245. cls: the dataclass type to register
  246. serialized_type_name: The serialized name for the dataclass. This is
  247. required if you want to serialize the pytree TreeSpec containing this
  248. dataclass.
  249. Example::
  250. @dataclass
  251. class InputDataClass:
  252. feature: torch.Tensor
  253. bias: int
  254. class OutputDataClass:
  255. res: torch.Tensor
  256. torch.export.register_dataclass(InputDataClass)
  257. torch.export.register_dataclass(OutputDataClass)
  258. def fn(o: InputDataClass) -> torch.Tensor:
  259. res = res=o.feature + o.bias
  260. return OutputDataClass(res=res)
  261. ep = torch.export.export(fn, (InputDataClass(torch.ones(2, 2), 1), ))
  262. print(ep)
  263. """
  264. from torch._export.utils import register_dataclass_as_pytree_node
  265. return register_dataclass_as_pytree_node(
  266. cls, serialized_type_name=serialized_type_name
  267. )