_cxx_pytree.py 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141
  1. """
  2. Contains utility functions for working with nested python data structures.
  3. A *pytree* is Python nested data structure. It is a tree in the sense that
  4. nodes are Python collections (e.g., list, tuple, dict) and the leaves are
  5. Python values. Furthermore, a pytree should not contain reference cycles.
  6. pytrees are useful for working with nested collections of Tensors. For example,
  7. one can use `tree_map` to map a function over all Tensors inside some nested
  8. collection of Tensors and `tree_leaves` to get a flat list of all Tensors
  9. inside some nested collection. pytrees are helpful for implementing nested
  10. collection support for PyTorch APIs.
  11. """
  12. import functools
  13. import sys
  14. import types
  15. from collections.abc import Callable, Iterable, Mapping
  16. from typing import Any, overload, TypeAlias, TypeVar
  17. from typing_extensions import deprecated, Self, TypeIs
  18. import torch.utils._pytree as python_pytree
  19. from torch.torch_version import TorchVersion as _TorchVersion
  20. from torch.utils._pytree import (
  21. is_namedtuple,
  22. is_namedtuple_class,
  23. is_namedtuple_instance,
  24. is_structseq,
  25. is_structseq_class,
  26. is_structseq_instance,
  27. KeyEntry,
  28. )
  29. # Do not try to import `optree` package if the static version check already fails.
  30. if not python_pytree._cxx_pytree_dynamo_traceable:
  31. raise ImportError(
  32. f"{__name__} depends on `optree>={python_pytree._optree_minimum_version}`, "
  33. "which is an optional dependency of PyTorch. "
  34. "To use it, please upgrade your optree package via "
  35. "`python3 -m pip install --upgrade optree`"
  36. )
  37. import optree
  38. from optree import PyTreeSpec # direct import for type annotations
  39. __all__ = [
  40. "PyTree",
  41. "Context",
  42. "FlattenFunc",
  43. "UnflattenFunc",
  44. "DumpableContext",
  45. "ToDumpableContextFn",
  46. "FromDumpableContextFn",
  47. "PyTreeSpec",
  48. "TreeSpec",
  49. "LeafSpec",
  50. "keystr",
  51. "key_get",
  52. "register_pytree_node",
  53. "tree_is_leaf",
  54. "tree_flatten",
  55. "tree_flatten_with_path",
  56. "tree_unflatten",
  57. "tree_iter",
  58. "tree_leaves",
  59. "tree_leaves_with_path",
  60. "tree_structure",
  61. "tree_map",
  62. "tree_map_with_path",
  63. "tree_map_",
  64. "tree_map_only",
  65. "tree_map_only_",
  66. "tree_all",
  67. "tree_any",
  68. "tree_all_only",
  69. "tree_any_only",
  70. "treespec_dumps",
  71. "treespec_loads",
  72. "treespec_pprint",
  73. "is_namedtuple",
  74. "is_namedtuple_class",
  75. "is_namedtuple_instance",
  76. "is_structseq",
  77. "is_structseq_class",
  78. "is_structseq_instance",
  79. ]
  80. # In-tree installation may have VCS-based versioning. Update the previous static version.
  81. python_pytree._optree_version = _TorchVersion(optree.__version__) # type: ignore[attr-defined]
  82. __TORCH_DICT_SESSION = optree.dict_insertion_ordered(True, namespace="torch")
  83. __TORCH_DICT_SESSION.__enter__() # enable globally and permanently
  84. T = TypeVar("T")
  85. S = TypeVar("S")
  86. U = TypeVar("U")
  87. R = TypeVar("R")
  88. TreeSpec: TypeAlias = PyTreeSpec
  89. Context = Any
  90. PyTree = Any
  91. FlattenFunc = Callable[[PyTree], tuple[list[Any], Context]]
  92. UnflattenFunc = Callable[[Iterable[Any], Context], PyTree]
  93. OpTreeUnflattenFunc = Callable[[Context, Iterable[Any]], PyTree]
  94. DumpableContext = Any # Any json dumpable text
  95. ToDumpableContextFn = Callable[[Context], DumpableContext]
  96. FromDumpableContextFn = Callable[[DumpableContext], Context]
  97. KeyPath = tuple[KeyEntry, ...]
  98. FlattenWithKeysFunc = Callable[[PyTree], tuple[list[tuple[KeyEntry, Any]], Any]]
  99. def _reverse_args(func: UnflattenFunc) -> OpTreeUnflattenFunc:
  100. @functools.wraps(func)
  101. def wrapped(*args: Any, **kwargs: Any) -> Any:
  102. return func(*reversed(args), **kwargs)
  103. return wrapped
  104. def register_pytree_node(
  105. cls: type[Any],
  106. flatten_fn: FlattenFunc,
  107. unflatten_fn: UnflattenFunc,
  108. *,
  109. serialized_type_name: str | None = None,
  110. to_dumpable_context: ToDumpableContextFn | None = None,
  111. from_dumpable_context: FromDumpableContextFn | None = None,
  112. flatten_with_keys_fn: FlattenWithKeysFunc | None = None,
  113. ) -> None:
  114. """Register a container-like type as pytree node.
  115. Args:
  116. cls (type): A Python type to treat as an internal pytree node.
  117. flatten_fn (callable): A function to be used during flattening, taking an instance of
  118. ``cls`` and returning a pair, with (1) an iterable for the children to be flattened
  119. recursively, and (2) some hashable auxiliary data to be stored in the treespec and to be
  120. passed to the ``unflatten_fn``.
  121. unflatten_fn (callable): A function taking two arguments: the auxiliary data that was
  122. returned by ``flatten_fn`` and stored in the treespec, and the unflattened children.
  123. The function should return an instance of ``cls``.
  124. serialized_type_name (str, optional): A keyword argument used to specify the fully
  125. qualified name used when serializing the tree spec.
  126. to_dumpable_context (callable, optional): An optional keyword argument to custom specify how
  127. to convert the context of the pytree to a custom json dumpable representation. This is
  128. used for json serialization, which is being used in :mod:`torch.export` right now.
  129. from_dumpable_context (callable, optional): An optional keyword argument to custom specify
  130. how to convert the custom json dumpable representation of the context back to the
  131. original context. This is used for json deserialization, which is being used in
  132. :mod:`torch.export` right now.
  133. Example::
  134. >>> # xdoctest: +SKIP
  135. >>> # Registry a Python type with lambda functions
  136. >>> register_pytree_node(
  137. ... set,
  138. ... lambda s: (sorted(s), None, None),
  139. ... lambda children, _: set(children),
  140. ... )
  141. """
  142. if flatten_with_keys_fn is not None:
  143. raise NotImplementedError("KeyPaths are not yet supported in cxx_pytree.")
  144. _private_register_pytree_node(
  145. cls,
  146. flatten_fn,
  147. unflatten_fn,
  148. serialized_type_name=serialized_type_name,
  149. to_dumpable_context=to_dumpable_context,
  150. from_dumpable_context=from_dumpable_context,
  151. )
  152. python_pytree._private_register_pytree_node(
  153. cls,
  154. flatten_fn,
  155. unflatten_fn,
  156. serialized_type_name=serialized_type_name,
  157. to_dumpable_context=to_dumpable_context,
  158. from_dumpable_context=from_dumpable_context,
  159. )
  160. @deprecated(
  161. "`torch.utils._cxx_pytree._register_pytree_node` is deprecated. "
  162. "Please use `torch.utils._cxx_pytree.register_pytree_node` instead.",
  163. category=FutureWarning,
  164. )
  165. def _register_pytree_node(
  166. cls: type[Any],
  167. flatten_fn: FlattenFunc,
  168. unflatten_fn: UnflattenFunc,
  169. *,
  170. serialized_type_name: str | None = None,
  171. to_dumpable_context: ToDumpableContextFn | None = None,
  172. from_dumpable_context: FromDumpableContextFn | None = None,
  173. ) -> None:
  174. """Register a container-like type as pytree node for the C++ pytree only.
  175. The ``namespace`` argument is used to avoid collisions that occur when different libraries
  176. register the same Python type with different behaviors. It is recommended to add a unique prefix
  177. to the namespace to avoid conflicts with other libraries. Namespaces can also be used to specify
  178. the same class in different namespaces for different use cases.
  179. .. warning::
  180. For safety reasons, a ``namespace`` must be specified while registering a custom type. It is
  181. used to isolate the behavior of flattening and unflattening a pytree node type. This is to
  182. prevent accidental collisions between different libraries that may register the same type.
  183. Args:
  184. cls (type): A Python type to treat as an internal pytree node.
  185. flatten_fn (callable): A function to be used during flattening, taking an instance of
  186. ``cls`` and returning a pair, with (1) an iterable for the children to be flattened
  187. recursively, and (2) some hashable auxiliary data to be stored in the treespec and to be
  188. passed to the ``unflatten_fn``.
  189. unflatten_fn (callable): A function taking two arguments: the auxiliary data that was
  190. returned by ``flatten_fn`` and stored in the treespec, and the unflattened children.
  191. The function should return an instance of ``cls``.
  192. serialized_type_name (str, optional): A keyword argument used to specify the fully
  193. qualified name used when serializing the tree spec.
  194. to_dumpable_context (callable, optional): An optional keyword argument to custom specify how
  195. to convert the context of the pytree to a custom json dumpable representation. This is
  196. used for json serialization, which is being used in :mod:`torch.export` right now.
  197. from_dumpable_context (callable, optional): An optional keyword argument to custom specify
  198. how to convert the custom json dumpable representation of the context back to the
  199. original context. This is used for json deserialization, which is being used in
  200. :mod:`torch.export` right now.
  201. """
  202. _private_register_pytree_node(
  203. cls,
  204. flatten_fn,
  205. unflatten_fn,
  206. serialized_type_name=serialized_type_name,
  207. to_dumpable_context=to_dumpable_context,
  208. from_dumpable_context=from_dumpable_context,
  209. )
  210. def _private_register_pytree_node(
  211. cls: type[Any],
  212. flatten_fn: FlattenFunc,
  213. unflatten_fn: UnflattenFunc,
  214. *,
  215. serialized_type_name: str | None = None,
  216. to_dumpable_context: ToDumpableContextFn | None = None,
  217. from_dumpable_context: FromDumpableContextFn | None = None,
  218. ) -> None:
  219. """This is an internal function that is used to register a pytree node type
  220. for the C++ pytree only. End-users should use :func:`register_pytree_node`
  221. instead.
  222. """
  223. # TODO(XuehaiPan): remove this condition when we make Python pytree out-of-box support
  224. # PyStructSequence types
  225. if not optree.is_structseq_class(cls):
  226. optree.register_pytree_node(
  227. cls,
  228. flatten_fn,
  229. _reverse_args(unflatten_fn),
  230. namespace="torch",
  231. )
  232. def _is_pytreespec_instance(
  233. obj: Any,
  234. /,
  235. ) -> TypeIs[TreeSpec | python_pytree.PyTreeSpec]:
  236. if isinstance(obj, (TreeSpec, python_pytree.PyTreeSpec)):
  237. return True
  238. if "torch._dynamo.polyfills.pytree" in sys.modules:
  239. # The PyTorch Dynamo pytree module is not always available, so we check if it is loaded.
  240. # If the PyTorch Dynamo pytree module is loaded, we can check if the treespec
  241. # is an instance of the PyTorch Dynamo TreeSpec class.
  242. import torch._dynamo.polyfills.pytree as dynamo_pytree
  243. return isinstance(obj, dynamo_pytree.PyTreeSpec)
  244. return False
  245. def treespec_leaf() -> TreeSpec:
  246. """Make a treespec representing a leaf node."""
  247. return optree.treespec_leaf(none_is_leaf=True, namespace="torch")
  248. def treespec_tuple(iterable: Iterable[TreeSpec] = (), /) -> TreeSpec:
  249. """Make a tuple treespec from an iterable of child treespecs."""
  250. return optree.treespec_tuple(iterable, none_is_leaf=True, namespace="torch")
  251. def treespec_dict(
  252. mapping: Mapping[Any, TreeSpec] | Iterable[tuple[Any, TreeSpec]] = (),
  253. /,
  254. **kwargs: TreeSpec,
  255. ) -> TreeSpec:
  256. """Make a dict treespec from a dict of child treespecs."""
  257. return optree.treespec_dict(
  258. mapping,
  259. **kwargs,
  260. none_is_leaf=True,
  261. namespace="torch",
  262. )
  263. def tree_is_leaf(
  264. tree: PyTree,
  265. is_leaf: Callable[[PyTree], bool] | None = None,
  266. ) -> bool:
  267. """Check if a pytree is a leaf.
  268. >>> tree_is_leaf(1)
  269. True
  270. >>> tree_is_leaf(None)
  271. True
  272. >>> tree_is_leaf([1, 2, 3])
  273. False
  274. >>> tree_is_leaf((1, 2, 3), is_leaf=lambda x: isinstance(x, tuple))
  275. True
  276. >>> tree_is_leaf({"a": 1, "b": 2, "c": 3})
  277. False
  278. >>> tree_is_leaf({"a": 1, "b": 2, "c": None})
  279. False
  280. Args:
  281. tree (pytree): A pytree to check if it is a leaf node.
  282. is_leaf (callable, optional): An extra leaf predicate function that will be called at each
  283. flattening step. The function should have a single argument with signature
  284. ``is_leaf(node) -> bool``. If it returns :data:`True`, the whole subtree being treated
  285. as a leaf. Otherwise, the default pytree registry will be used to determine a node is a
  286. leaf or not. If the function is not specified, the default pytree registry will be used.
  287. Returns:
  288. A boolean indicating if the pytree is a leaf node.
  289. """
  290. return optree.tree_is_leaf(
  291. tree,
  292. is_leaf=is_leaf,
  293. none_is_leaf=True,
  294. namespace="torch",
  295. )
  296. def tree_flatten(
  297. tree: PyTree,
  298. is_leaf: Callable[[PyTree], bool] | None = None,
  299. ) -> tuple[list[Any], TreeSpec]:
  300. """Flatten a pytree.
  301. See also :func:`tree_unflatten`.
  302. The flattening order (i.e., the order of elements in the output list) is deterministic,
  303. corresponding to a left-to-right depth-first tree traversal.
  304. >>> tree = {"b": (2, [3, 4]), "a": 1, "c": None, "d": 5}
  305. >>> tree_flatten(tree)
  306. ([2, 3, 4, 1, None, 5], PyTreeSpec({'b': (*, [*, *]), 'a': *, 'c': *, 'd': *}, NoneIsLeaf, namespace='torch'))
  307. >>> tree_flatten(1)
  308. ([1], PyTreeSpec(*, NoneIsLeaf, namespace='torch'))
  309. >>> tree_flatten(None)
  310. ([None], PyTreeSpec(*, NoneIsLeaf, namespace='torch'))
  311. >>> from collections import OrderedDict
  312. >>> tree = OrderedDict([("b", (2, [3, 4])), ("a", 1), ("c", None), ("d", 5)])
  313. >>> tree_flatten(tree)
  314. ([2, 3, 4, 1, None, 5], PyTreeSpec(OrderedDict({'b': (*, [*, *]), 'a': *, 'c': *, 'd': *}), NoneIsLeaf, namespace='torch'))
  315. Args:
  316. tree (pytree): A pytree to flatten.
  317. is_leaf (callable, optional): An extra leaf predicate function that will be called at each
  318. flattening step. The function should have a single argument with signature
  319. ``is_leaf(node) -> bool``. If it returns :data:`True`, the whole subtree being treated
  320. as a leaf. Otherwise, the default pytree registry will be used to determine a node is a
  321. leaf or not. If the function is not specified, the default pytree registry will be used.
  322. Returns:
  323. A pair ``(leaves, treespec)`` where the first element is a list of leaf values and the
  324. second element is a treespec representing the structure of the pytree.
  325. """
  326. return optree.tree_flatten( # type: ignore[return-value]
  327. tree,
  328. is_leaf=is_leaf,
  329. none_is_leaf=True,
  330. namespace="torch",
  331. )
  332. def tree_unflatten(leaves: Iterable[Any], treespec: TreeSpec) -> PyTree:
  333. """Reconstruct a pytree from the treespec and the leaves.
  334. The inverse of :func:`tree_flatten`.
  335. >>> tree = {"b": (2, [3, 4]), "a": 1, "c": None, "d": 5}
  336. >>> leaves, treespec = tree_flatten(tree)
  337. >>> tree == tree_unflatten(leaves, treespec)
  338. True
  339. Args:
  340. leaves (iterable): The list of leaves to use for reconstruction. The list must match the
  341. number of leaves of the treespec.
  342. treespec (TreeSpec): The treespec to reconstruct.
  343. Returns:
  344. The reconstructed pytree, containing the ``leaves`` placed in the structure described by
  345. ``treespec``.
  346. """
  347. if not _is_pytreespec_instance(treespec):
  348. if not _is_pytreespec_instance(leaves):
  349. raise TypeError(
  350. f"Expected `treespec` to be an instance of "
  351. f"PyTreeSpec but got item of type {type(treespec)}."
  352. )
  353. # Allow passing the PyTreeSpec instance as the first argument
  354. # pyrefly: ignore [bad-assignment]
  355. leaves, treespec = treespec, leaves
  356. return treespec.unflatten(leaves)
  357. def tree_iter(
  358. tree: PyTree,
  359. is_leaf: Callable[[PyTree], bool] | None = None,
  360. ) -> Iterable[Any]:
  361. """Get an iterator over the leaves of a pytree.
  362. See also :func:`tree_flatten`.
  363. >>> tree = {"b": (2, [3, 4]), "a": 1, "c": None, "d": 5}
  364. >>> list(tree_iter(tree))
  365. [2, 3, 4, 1, None, 5]
  366. >>> list(tree_iter(1))
  367. [1]
  368. >>> list(tree_iter(None))
  369. [None]
  370. Args:
  371. tree (pytree): A pytree to flatten.
  372. is_leaf (callable, optional): An extra leaf predicate function that will be called at each
  373. flattening step. The function should have a single argument with signature
  374. ``is_leaf(node) -> bool``. If it returns :data:`True`, the whole subtree being treated
  375. as a leaf. Otherwise, the default pytree registry will be used to determine a node is a
  376. leaf or not. If the function is not specified, the default pytree registry will be used.
  377. Returns:
  378. An iterator over the leaf values.
  379. """
  380. return optree.tree_iter(
  381. tree,
  382. is_leaf=is_leaf,
  383. none_is_leaf=True,
  384. namespace="torch",
  385. )
  386. def tree_leaves(
  387. tree: PyTree,
  388. is_leaf: Callable[[PyTree], bool] | None = None,
  389. ) -> list[Any]:
  390. """Get the leaves of a pytree.
  391. See also :func:`tree_flatten`.
  392. >>> tree = {"b": (2, [3, 4]), "a": 1, "c": None, "d": 5}
  393. >>> tree_leaves(tree)
  394. [2, 3, 4, 1, None, 5]
  395. >>> tree_leaves(1)
  396. [1]
  397. >>> tree_leaves(None)
  398. [None]
  399. Args:
  400. tree (pytree): A pytree to flatten.
  401. is_leaf (callable, optional): An extra leaf predicate function that will be called at each
  402. flattening step. The function should have a single argument with signature
  403. ``is_leaf(node) -> bool``. If it returns :data:`True`, the whole subtree being treated
  404. as a leaf. Otherwise, the default pytree registry will be used to determine a node is a
  405. leaf or not. If the function is not specified, the default pytree registry will be used.
  406. Returns:
  407. A list of leaf values.
  408. """
  409. return optree.tree_leaves(
  410. tree,
  411. is_leaf=is_leaf,
  412. none_is_leaf=True,
  413. namespace="torch",
  414. )
  415. def tree_structure(
  416. tree: PyTree,
  417. is_leaf: Callable[[PyTree], bool] | None = None,
  418. ) -> TreeSpec:
  419. """Get the treespec for a pytree.
  420. See also :func:`tree_flatten`.
  421. >>> tree = {"b": (2, [3, 4]), "a": 1, "c": None, "d": 5}
  422. >>> tree_structure(tree)
  423. PyTreeSpec({'b': (*, [*, *]), 'a': *, 'c': *, 'd': *}, NoneIsLeaf, namespace='torch')
  424. >>> tree_structure(1)
  425. PyTreeSpec(*, NoneIsLeaf, namespace='torch')
  426. >>> tree_structure(None)
  427. PyTreeSpec(*, NoneIsLeaf, namespace='torch')
  428. Args:
  429. tree (pytree): A pytree to flatten.
  430. is_leaf (callable, optional): An extra leaf predicate function that will be called at each
  431. flattening step. The function should have a single argument with signature
  432. ``is_leaf(node) -> bool``. If it returns :data:`True`, the whole subtree being treated
  433. as a leaf. Otherwise, the default pytree registry will be used to determine a node is a
  434. leaf or not. If the function is not specified, the default pytree registry will be used.
  435. Returns:
  436. A treespec object representing the structure of the pytree.
  437. """
  438. return optree.tree_structure( # type: ignore[return-value]
  439. tree,
  440. is_leaf=is_leaf,
  441. none_is_leaf=True,
  442. namespace="torch",
  443. )
  444. def tree_map(
  445. func: Callable[..., Any],
  446. tree: PyTree,
  447. *rests: PyTree,
  448. is_leaf: Callable[[PyTree], bool] | None = None,
  449. ) -> PyTree:
  450. """Map a multi-input function over pytree args to produce a new pytree.
  451. See also :func:`tree_map_`.
  452. >>> tree_map(lambda x: x + 1, {"x": 7, "y": (42, 64)})
  453. {'x': 8, 'y': (43, 65)}
  454. >>> tree_map(lambda x: x is None, {"x": 7, "y": (42, 64), "z": None})
  455. {'x': False, 'y': (False, False), 'z': True}
  456. If multiple inputs are given, the structure of the tree is taken from the first input;
  457. subsequent inputs need only have ``tree`` as a prefix:
  458. >>> tree_map(lambda x, y: [x] + y, [5, 6], [[7, 9], [1, 2]])
  459. [[5, 7, 9], [6, 1, 2]]
  460. Args:
  461. func (callable): A function that takes ``1 + len(rests)`` arguments, to be applied at the
  462. corresponding leaves of the pytrees.
  463. tree (pytree): A pytree to be mapped over, with each leaf providing the first positional
  464. argument to function ``func``.
  465. rests (tuple of pytree): A tuple of pytrees, each of which has the same structure as
  466. ``tree`` or has ``tree`` as a prefix.
  467. is_leaf (callable, optional): An extra leaf predicate function that will be called at each
  468. flattening step. The function should have a single argument with signature
  469. ``is_leaf(node) -> bool``. If it returns :data:`True`, the whole subtree being treated
  470. as a leaf. Otherwise, the default pytree registry will be used to determine a node is a
  471. leaf or not. If the function is not specified, the default pytree registry will be used.
  472. Returns:
  473. A new pytree with the same structure as ``tree`` but with the value at each leaf given by
  474. ``func(x, *xs)`` where ``x`` is the value at the corresponding leaf in ``tree`` and ``xs``
  475. is the tuple of values at corresponding nodes in ``rests``.
  476. """
  477. return optree.tree_map(
  478. func,
  479. tree,
  480. *rests,
  481. is_leaf=is_leaf,
  482. none_is_leaf=True,
  483. namespace="torch",
  484. )
  485. def tree_map_(
  486. func: Callable[..., Any],
  487. tree: PyTree,
  488. *rests: PyTree,
  489. is_leaf: Callable[[PyTree], bool] | None = None,
  490. ) -> PyTree:
  491. """Like :func:`tree_map`, but do an inplace call on each leaf and return the original tree.
  492. See also :func:`tree_map`.
  493. Args:
  494. func (callable): A function that takes ``1 + len(rests)`` arguments, to be applied at the
  495. corresponding leaves of the pytrees.
  496. tree (pytree): A pytree to be mapped over, with each leaf providing the first positional
  497. argument to function ``func``.
  498. rests (tuple of pytree): A tuple of pytrees, each of which has the same structure as
  499. ``tree`` or has ``tree`` as a prefix.
  500. is_leaf (callable, optional): An extra leaf predicate function that will be called at each
  501. flattening step. The function should have a single argument with signature
  502. ``is_leaf(node) -> bool``. If it returns :data:`True`, the whole subtree being treated
  503. as a leaf. Otherwise, the default pytree registry will be used to determine a node is a
  504. leaf or not. If the function is not specified, the default pytree registry will be used.
  505. Returns:
  506. The original ``tree`` with the value at each leaf is given by the side-effect of function
  507. ``func(x, *xs)`` (not the return value) where ``x`` is the value at the corresponding leaf
  508. in ``tree`` and ``xs`` is the tuple of values at values at corresponding nodes in ``rests``.
  509. """
  510. return optree.tree_map_(
  511. func,
  512. tree,
  513. *rests,
  514. is_leaf=is_leaf,
  515. none_is_leaf=True,
  516. namespace="torch",
  517. )
  518. Type2 = tuple[type[T], type[S]]
  519. Type3 = tuple[type[T], type[S], type[U]]
  520. TypeAny = type[Any] | tuple[type[Any], ...] | types.UnionType
  521. Fn2 = Callable[[T | S], R]
  522. Fn3 = Callable[[T | S | U], R]
  523. Fn = Callable[[T], R]
  524. FnAny = Callable[[Any], R]
  525. MapOnlyFn = Callable[[T], Callable[[Any], Any]]
  526. # These specializations help with type inference on the lambda passed to this
  527. # function
  528. @overload
  529. def map_only(type_or_types_or_pred: type[T], /) -> MapOnlyFn[Fn[T, Any]]: ...
  530. @overload
  531. def map_only(type_or_types_or_pred: Type2[T, S], /) -> MapOnlyFn[Fn2[T, S, Any]]: ...
  532. @overload
  533. def map_only(
  534. type_or_types_or_pred: Type3[T, S, U], /
  535. ) -> MapOnlyFn[Fn3[T, S, U, Any]]: ...
  536. # This specialization is needed for the implementations below that call
  537. @overload
  538. def map_only(type_or_types_or_pred: TypeAny, /) -> MapOnlyFn[FnAny[Any]]: ...
  539. @overload
  540. def map_only(
  541. type_or_types_or_pred: Callable[[Any], bool], /
  542. ) -> MapOnlyFn[FnAny[Any]]: ...
  543. def map_only(
  544. type_or_types_or_pred: TypeAny | Callable[[Any], bool], /
  545. ) -> MapOnlyFn[FnAny[Any]]:
  546. """
  547. Suppose you are writing a tree_map over tensors, leaving everything
  548. else unchanged. Ordinarily you would have to write:
  549. def go(t):
  550. if isinstance(t, Tensor):
  551. return ...
  552. else:
  553. return t
  554. With this function, you only need to write:
  555. @map_only(Tensor)
  556. def go(t):
  557. return ...
  558. You can also directly use 'tree_map_only'
  559. """
  560. if isinstance(type_or_types_or_pred, (type, tuple, types.UnionType)):
  561. def pred(x: Any) -> bool:
  562. return isinstance(x, type_or_types_or_pred) # type: ignore[arg-type]
  563. elif callable(type_or_types_or_pred):
  564. pred = type_or_types_or_pred # type: ignore[assignment]
  565. else:
  566. raise TypeError("Argument must be a type, a tuple of types, or a callable.")
  567. def wrapper(func: Callable[[T], Any]) -> Callable[[Any], Any]:
  568. @functools.wraps(func)
  569. def wrapped(x: T) -> Any:
  570. if pred(x):
  571. return func(x)
  572. return x
  573. return wrapped
  574. return wrapper
  575. @overload
  576. def tree_map_only(
  577. type_or_types_or_pred: type[T],
  578. /,
  579. func: Fn[T, Any],
  580. tree: PyTree,
  581. is_leaf: Callable[[PyTree], bool] | None = None,
  582. ) -> PyTree: ...
  583. @overload
  584. def tree_map_only(
  585. type_or_types_or_pred: Type2[T, S],
  586. /,
  587. func: Fn2[T, S, Any],
  588. tree: PyTree,
  589. is_leaf: Callable[[PyTree], bool] | None = None,
  590. ) -> PyTree: ...
  591. @overload
  592. def tree_map_only(
  593. type_or_types_or_pred: Type3[T, S, U],
  594. /,
  595. func: Fn3[T, S, U, Any],
  596. tree: PyTree,
  597. is_leaf: Callable[[PyTree], bool] | None = None,
  598. ) -> PyTree: ...
  599. @overload
  600. def tree_map_only(
  601. type_or_types_or_pred: TypeAny,
  602. /,
  603. func: FnAny[Any],
  604. tree: PyTree,
  605. is_leaf: Callable[[PyTree], bool] | None = None,
  606. ) -> PyTree: ...
  607. @overload
  608. def tree_map_only(
  609. type_or_types_or_pred: Callable[[Any], bool],
  610. /,
  611. func: FnAny[Any],
  612. tree: PyTree,
  613. is_leaf: Callable[[PyTree], bool] | None = None,
  614. ) -> PyTree: ...
  615. def tree_map_only(
  616. type_or_types_or_pred: TypeAny | Callable[[Any], bool],
  617. /,
  618. func: FnAny[Any],
  619. tree: PyTree,
  620. is_leaf: Callable[[PyTree], bool] | None = None,
  621. ) -> PyTree:
  622. return tree_map(map_only(type_or_types_or_pred)(func), tree, is_leaf=is_leaf)
  623. @overload
  624. def tree_map_only_(
  625. type_or_types_or_pred: type[T],
  626. /,
  627. func: Fn[T, Any],
  628. tree: PyTree,
  629. is_leaf: Callable[[PyTree], bool] | None = None,
  630. ) -> PyTree: ...
  631. @overload
  632. def tree_map_only_(
  633. type_or_types_or_pred: Type2[T, S],
  634. /,
  635. func: Fn2[T, S, Any],
  636. tree: PyTree,
  637. is_leaf: Callable[[PyTree], bool] | None = None,
  638. ) -> PyTree: ...
  639. @overload
  640. def tree_map_only_(
  641. type_or_types_or_pred: Type3[T, S, U],
  642. /,
  643. func: Fn3[T, S, U, Any],
  644. tree: PyTree,
  645. is_leaf: Callable[[PyTree], bool] | None = None,
  646. ) -> PyTree: ...
  647. @overload
  648. def tree_map_only_(
  649. type_or_types_or_pred: TypeAny,
  650. /,
  651. func: FnAny[Any],
  652. tree: PyTree,
  653. is_leaf: Callable[[PyTree], bool] | None = None,
  654. ) -> PyTree: ...
  655. @overload
  656. def tree_map_only_(
  657. type_or_types_or_pred: Callable[[Any], bool],
  658. /,
  659. func: FnAny[Any],
  660. tree: PyTree,
  661. is_leaf: Callable[[PyTree], bool] | None = None,
  662. ) -> PyTree: ...
  663. def tree_map_only_(
  664. type_or_types_or_pred: TypeAny | Callable[[Any], bool],
  665. /,
  666. func: FnAny[Any],
  667. tree: PyTree,
  668. is_leaf: Callable[[PyTree], bool] | None = None,
  669. ) -> PyTree:
  670. return tree_map_(map_only(type_or_types_or_pred)(func), tree, is_leaf=is_leaf)
  671. def tree_all(
  672. pred: Callable[[Any], bool],
  673. tree: PyTree,
  674. is_leaf: Callable[[PyTree], bool] | None = None,
  675. ) -> bool:
  676. flat_args = tree_iter(tree, is_leaf=is_leaf)
  677. return all(map(pred, flat_args))
  678. def tree_any(
  679. pred: Callable[[Any], bool],
  680. tree: PyTree,
  681. is_leaf: Callable[[PyTree], bool] | None = None,
  682. ) -> bool:
  683. flat_args = tree_iter(tree, is_leaf=is_leaf)
  684. return any(map(pred, flat_args))
  685. @overload
  686. def tree_all_only(
  687. type_or_types: type[T],
  688. /,
  689. pred: Fn[T, bool],
  690. tree: PyTree,
  691. is_leaf: Callable[[PyTree], bool] | None = None,
  692. ) -> bool: ...
  693. @overload
  694. def tree_all_only(
  695. type_or_types: Type2[T, S],
  696. /,
  697. pred: Fn2[T, S, bool],
  698. tree: PyTree,
  699. is_leaf: Callable[[PyTree], bool] | None = None,
  700. ) -> bool: ...
  701. @overload
  702. def tree_all_only(
  703. type_or_types: Type3[T, S, U],
  704. /,
  705. pred: Fn3[T, S, U, bool],
  706. tree: PyTree,
  707. is_leaf: Callable[[PyTree], bool] | None = None,
  708. ) -> bool: ...
  709. def tree_all_only(
  710. type_or_types: TypeAny,
  711. /,
  712. pred: FnAny[bool],
  713. tree: PyTree,
  714. is_leaf: Callable[[PyTree], bool] | None = None,
  715. ) -> bool:
  716. flat_args = tree_iter(tree, is_leaf=is_leaf)
  717. return all(pred(x) for x in flat_args if isinstance(x, type_or_types))
  718. @overload
  719. def tree_any_only(
  720. type_or_types: type[T],
  721. /,
  722. pred: Fn[T, bool],
  723. tree: PyTree,
  724. is_leaf: Callable[[PyTree], bool] | None = None,
  725. ) -> bool: ...
  726. @overload
  727. def tree_any_only(
  728. type_or_types: Type2[T, S],
  729. /,
  730. pred: Fn2[T, S, bool],
  731. tree: PyTree,
  732. is_leaf: Callable[[PyTree], bool] | None = None,
  733. ) -> bool: ...
  734. @overload
  735. def tree_any_only(
  736. type_or_types: Type3[T, S, U],
  737. /,
  738. pred: Fn3[T, S, U, bool],
  739. tree: PyTree,
  740. is_leaf: Callable[[PyTree], bool] | None = None,
  741. ) -> bool: ...
  742. def tree_any_only(
  743. type_or_types: TypeAny,
  744. /,
  745. pred: FnAny[bool],
  746. tree: PyTree,
  747. is_leaf: Callable[[PyTree], bool] | None = None,
  748. ) -> bool:
  749. flat_args = tree_iter(tree, is_leaf=is_leaf)
  750. return any(pred(x) for x in flat_args if isinstance(x, type_or_types))
  751. def broadcast_prefix(
  752. prefix_tree: PyTree,
  753. full_tree: PyTree,
  754. is_leaf: Callable[[PyTree], bool] | None = None,
  755. ) -> list[Any]:
  756. """Return a list of broadcasted leaves in ``prefix_tree`` to match the number of leaves in ``full_tree``.
  757. If a ``prefix_tree`` is a prefix of a ``full_tree``, this means the ``full_tree`` can be
  758. constructed by replacing the leaves of ``prefix_tree`` with appropriate **subtrees**.
  759. This function returns a list of leaves with the same size as ``full_tree``. The leaves are
  760. replicated from ``prefix_tree``. The number of replicas is determined by the corresponding
  761. subtree in ``full_tree``.
  762. >>> broadcast_prefix(1, [1, 2, 3])
  763. [1, 1, 1]
  764. >>> broadcast_prefix([1, 2, 3], [1, 2, 3])
  765. [1, 2, 3]
  766. >>> broadcast_prefix([1, 2, 3], [1, 2, 3, 4])
  767. Traceback (most recent call last):
  768. ...
  769. ValueError: list arity mismatch; expected: 3, got: 4; list: [1, 2, 3, 4].
  770. >>> broadcast_prefix([1, 2, 3], [1, 2, (3, 4)])
  771. [1, 2, 3, 3]
  772. >>> broadcast_prefix([1, 2, 3], [1, 2, {"a": 3, "b": 4, "c": (None, 5)}])
  773. [1, 2, 3, 3, 3, 3]
  774. Args:
  775. prefix_tree (pytree): A pytree with the same structure as a prefix of ``full_tree``.
  776. full_tree (pytree): A pytree with the same structure as a suffix of ``prefix_tree``.
  777. is_leaf (callable, optional): An extra leaf predicate function that will be called at each
  778. flattening step. The function should have a single argument with signature
  779. ``is_leaf(node) -> bool``. If it returns :data:`True`, the whole subtree being treated
  780. as a leaf. Otherwise, the default pytree registry will be used to determine a node is a
  781. leaf or not. If the function is not specified, the default pytree registry will be used.
  782. Returns:
  783. A list of leaves in ``prefix_tree`` broadcasted to match the number of leaves in ``full_tree``.
  784. """
  785. result: list[Any] = []
  786. def add_leaves(x: Any, subtree: PyTree) -> None:
  787. subtreespec = tree_structure(subtree, is_leaf=is_leaf)
  788. result.extend([x] * subtreespec.num_leaves)
  789. tree_map_(
  790. add_leaves,
  791. prefix_tree,
  792. full_tree,
  793. is_leaf=is_leaf,
  794. )
  795. return result
  796. # Broadcasts a pytree to the provided TreeSpec and returns the flattened
  797. # values. If this is not possible, then this function returns None.
  798. #
  799. # For example, given pytree=0 and spec=TreeSpec(list, None, [LeafSpec(), LeafSpec()]),
  800. # would return [0, 0]. This is useful for part of the vmap implementation:
  801. # a user can pass in vmap(fn, in_dims)(*inputs). `in_dims` should be
  802. # broadcastable to the tree structure of `inputs` and we use
  803. # _broadcast_to_and_flatten to check this.
  804. def _broadcast_to_and_flatten(
  805. tree: PyTree,
  806. treespec: TreeSpec,
  807. is_leaf: Callable[[PyTree], bool] | None = None,
  808. ) -> list[Any] | None:
  809. if not _is_pytreespec_instance(treespec):
  810. raise TypeError(
  811. f"Expected `treespec` to be an instance of "
  812. f"PyTreeSpec but got item of type {type(treespec)}."
  813. )
  814. full_tree = tree_unflatten([0] * treespec.num_leaves, treespec)
  815. try:
  816. return broadcast_prefix(tree, full_tree, is_leaf=is_leaf)
  817. except ValueError:
  818. return None
  819. def treespec_dumps(treespec: TreeSpec, protocol: int | None = None) -> str:
  820. """Serialize a treespec to a JSON string."""
  821. if not _is_pytreespec_instance(treespec):
  822. raise TypeError(
  823. f"Expected `treespec` to be an instance of "
  824. f"PyTreeSpec but got item of type {type(treespec)}."
  825. )
  826. dummy_tree = tree_unflatten([0] * treespec.num_leaves, treespec)
  827. orig_treespec = python_pytree.tree_structure(dummy_tree)
  828. return python_pytree.treespec_dumps(orig_treespec, protocol=protocol)
  829. @functools.lru_cache
  830. def treespec_loads(serialized: str) -> TreeSpec:
  831. """Deserialize a treespec from a JSON string."""
  832. orig_treespec = python_pytree.treespec_loads(serialized)
  833. dummy_tree = python_pytree.tree_unflatten(
  834. [0] * orig_treespec.num_leaves,
  835. orig_treespec,
  836. )
  837. treespec = tree_structure(dummy_tree)
  838. return treespec
  839. class _DummyLeaf:
  840. def __repr__(self) -> str:
  841. return "*"
  842. def treespec_pprint(treespec: TreeSpec) -> str:
  843. dummy_tree = tree_unflatten(
  844. [_DummyLeaf() for _ in range(treespec.num_leaves)],
  845. treespec,
  846. )
  847. return repr(dummy_tree)
  848. class LeafSpecMeta(type(TreeSpec)): # type: ignore[misc]
  849. def __instancecheck__(self, instance: object) -> bool:
  850. return _is_pytreespec_instance(instance) and instance.is_leaf()
  851. @deprecated(
  852. "`isinstance(treespec, LeafSpec)` is deprecated, "
  853. "use `isinstance(treespec, TreeSpec)` and `treespec.is_leaf()` instead.",
  854. category=FutureWarning,
  855. )
  856. class LeafSpec(TreeSpec, metaclass=LeafSpecMeta): # type: ignore[misc,final]
  857. def __new__(cls) -> Self:
  858. return treespec_leaf() # type: ignore[return-value]
  859. def tree_flatten_with_path(
  860. tree: PyTree,
  861. is_leaf: Callable[[PyTree], bool] | None = None,
  862. ) -> tuple[list[tuple[KeyPath, Any]], TreeSpec]:
  863. """Flattens a pytree like :func:`tree_flatten`, but also returns each leaf's key path.
  864. Args:
  865. tree: a pytree to flatten. If it contains a custom type, that type must be
  866. registered with an appropriate `tree_flatten_with_path_fn` when registered
  867. with :func:`register_pytree_node`.
  868. is_leaf: An extra leaf predicate function that will be called at each
  869. flattening step. The function should have a single argument with signature
  870. ``is_leaf(node) -> bool``. If it returns :data:`True`, the whole subtree being treated
  871. as a leaf. Otherwise, the default pytree registry will be used to determine a node is a
  872. leaf or not. If the function is not specified, the default pytree registry will be used.
  873. Returns:
  874. A tuple where the first element is a list of (key path, leaf) pairs, and the
  875. second element is a :class:`TreeSpec` representing the structure of the flattened
  876. tree.
  877. """
  878. raise NotImplementedError("KeyPaths are not yet supported in cxx_pytree.")
  879. def tree_leaves_with_path(
  880. tree: PyTree,
  881. is_leaf: Callable[[PyTree], bool] | None = None,
  882. ) -> list[tuple[KeyPath, Any]]:
  883. """Gets the leaves of a pytree like ``tree_leaves`` and returns each leaf's key path.
  884. Args:
  885. tree: a pytree. If it contains a custom type, that type must be
  886. registered with an appropriate `tree_flatten_with_path_fn` when registered
  887. with :func:`register_pytree_node`.
  888. is_leaf: An extra leaf predicate function that will be called at each
  889. flattening step. The function should have a single argument with signature
  890. ``is_leaf(node) -> bool``. If it returns :data:`True`, the whole subtree being treated
  891. as a leaf. Otherwise, the default pytree registry will be used to determine a node is a
  892. leaf or not. If the function is not specified, the default pytree registry will be used.
  893. Returns:
  894. A list of (key path, leaf) pairs.
  895. """
  896. raise NotImplementedError("KeyPaths are not yet supported in cxx_pytree.")
  897. def tree_map_with_path(
  898. func: Callable[..., Any],
  899. tree: PyTree,
  900. *rests: PyTree,
  901. is_leaf: Callable[[PyTree], bool] | None = None,
  902. ) -> PyTree:
  903. """Like :func:`tree_map`, but the provided callable takes an additional key path argument.
  904. Args:
  905. func: A function that takes ``2 + len(rests)`` arguments, to be applied at the
  906. corresponding leaves of the pytrees. The first positional argument
  907. to ``func`` is the key path of the leaf in question. The second
  908. positional argument is the value of the leaf.
  909. tree: A pytree to be mapped over, with each leaf providing the first positional
  910. argument to function ``func``.
  911. rests: A tuple of pytrees, each of which has the same structure as
  912. ``tree`` or has ``tree`` as a prefix.
  913. is_leaf: An extra leaf predicate function that will be called at each
  914. flattening step. The function should have a single argument with signature
  915. ``is_leaf(node) -> bool``. If it returns :data:`True`, the whole subtree being treated
  916. as a leaf. Otherwise, the default pytree registry will be used to determine a node is a
  917. leaf or not. If the function is not specified, the default pytree registry will be used.
  918. Returns
  919. A new pytree with the same structure as ``tree`` but with the value at each leaf given by
  920. ``func(keypath, x, *xs)`` where ``keypath`` is the key path at the
  921. corresponding leaf in ``tree``, ``x`` is the value at that leaf, and
  922. ``xs`` is the tuple of values at corresponding nodes in ``rests``.
  923. """
  924. raise NotImplementedError("KeyPaths are not yet supported in cxx_pytree.")
  925. def keystr(kp: KeyPath) -> str:
  926. """Given a key path, return a pretty-printed representation."""
  927. raise NotImplementedError("KeyPaths are not yet supported in cxx_pytree.")
  928. def key_get(obj: Any, kp: KeyPath) -> Any:
  929. """Given an object and a key path, return the value at the key path."""
  930. raise NotImplementedError("KeyPaths are not yet supported in cxx_pytree.")
  931. with python_pytree._NODE_REGISTRY_LOCK:
  932. # pyrefly: ignore [bad-assignment]
  933. python_pytree._cxx_pytree_imported = True
  934. args, kwargs = (), {} # type: ignore[var-annotated]
  935. for args, kwargs in python_pytree._cxx_pytree_pending_imports:
  936. _private_register_pytree_node(*args, **kwargs)
  937. python_pytree._cxx_pytree_pending_imports.clear()
  938. del args, kwargs