_utils.py 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117
  1. # mypy: allow-untyped-defs
  2. import copyreg
  3. import functools
  4. import importlib
  5. import logging
  6. import sys
  7. import traceback
  8. import warnings
  9. from collections import defaultdict
  10. from collections.abc import Callable
  11. from types import ModuleType
  12. from typing import Any, Generic, TYPE_CHECKING
  13. from typing_extensions import deprecated, ParamSpec
  14. import torch
  15. def _type(self, dtype=None, non_blocking=False, **kwargs):
  16. """Returns the type if `dtype` is not provided, else casts this object to
  17. the specified type.
  18. If this is already of the correct type, no copy is performed and the
  19. original object is returned.
  20. Args:
  21. dtype (type or string): The desired type
  22. non_blocking (bool): If ``True``, and the source is in pinned memory
  23. and destination is on the GPU or vice versa, the copy is performed
  24. asynchronously with respect to the host. Otherwise, the argument
  25. has no effect.
  26. **kwargs: For compatibility, may contain the key ``async`` in place of
  27. the ``non_blocking`` argument. The ``async`` arg is deprecated.
  28. """
  29. non_blocking = _get_async_or_non_blocking("type", non_blocking, kwargs)
  30. if dtype is None:
  31. return self.__module__ + "." + self.__class__.__name__
  32. if isinstance(dtype, str):
  33. dtype = _import_dotted_name(dtype)
  34. if dtype is type(self):
  35. return self
  36. if self.is_sparse:
  37. if not dtype.is_sparse:
  38. raise RuntimeError("Cannot cast sparse tensor to dense tensor")
  39. new_module_name = dtype.__module__.replace(".sparse", "")
  40. new_values_type_name = new_module_name + "." + dtype.__name__
  41. new_values = torch.Tensor._values(self).type(new_values_type_name, non_blocking)
  42. new_indices_type_name = new_module_name + ".LongTensor"
  43. new_indices = torch.Tensor._indices(self).type(
  44. new_indices_type_name, non_blocking
  45. )
  46. return dtype(new_indices, new_values, self.size())
  47. if dtype.is_sparse:
  48. raise RuntimeError("Cannot cast dense tensor to sparse tensor")
  49. return dtype(self.size()).copy_(self, non_blocking)
  50. def _to(self, device, non_blocking=False):
  51. """Returns a copy of this object in device memory.
  52. If this object is already on the correct device, then no copy is performed
  53. and the original object is returned.
  54. Args:
  55. device (int): The destination device.
  56. non_blocking (bool): If ``True`` and the source is in pinned memory,
  57. the copy will be asynchronous with respect to the host. Otherwise,
  58. the argument has no effect.
  59. """
  60. if self.device == device:
  61. return self
  62. if device.type == "cpu":
  63. pin_memory = non_blocking and self.device.type in (
  64. "cuda",
  65. torch._C._get_privateuse1_backend_name(),
  66. )
  67. untyped_storage = torch.empty(
  68. self.nbytes(), dtype=torch.uint8, device=device, pin_memory=pin_memory
  69. ).untyped_storage()
  70. untyped_storage.copy_(self, non_blocking)
  71. return untyped_storage
  72. device_module = getattr(torch, device.type, None)
  73. assert device_module is not None, (
  74. f"{device.type.upper()} device module is not loaded"
  75. )
  76. with device_module.device(device):
  77. if self.is_sparse and hasattr(device_module, "sparse"):
  78. new_type = getattr(device_module.sparse, self.__class__.__name__)
  79. indices = getattr(torch.Tensor._indices(self), device.type)(
  80. device, non_blocking
  81. )
  82. values = getattr(torch.Tensor._values(self), device.type)(
  83. device, non_blocking
  84. )
  85. return new_type(indices, values, self.size())
  86. else:
  87. assert not self.is_sparse, (
  88. f"sparse storage is not supported for {device.type.upper()} tensors"
  89. )
  90. untyped_storage = torch.UntypedStorage(self.size(), device=device)
  91. untyped_storage.copy_(self, non_blocking)
  92. return untyped_storage
  93. def _get_async_or_non_blocking(function_name, non_blocking, kwargs):
  94. """Return the non-blocking flag given the function name and kwargs.
  95. Args:
  96. function_name (str): the name of the function being used.
  97. non_blocking (bool): the default value.
  98. **kwargs (dict): the kwargs passed to the function.
  99. """
  100. if not kwargs:
  101. return non_blocking
  102. if len(kwargs) != 1 or "async" not in kwargs:
  103. message = "{}() got an unexpected keyword argument '{}'"
  104. argument = list(kwargs.keys()).pop()
  105. raise TypeError(message.format(function_name, argument))
  106. warnings.warn("'async' is deprecated; use 'non_blocking'", stacklevel=2)
  107. return kwargs["async"]
  108. def _get_restore_location(device):
  109. """Return the map_location location.
  110. Used for rebuild functions where the tensor device is distinct from the storage
  111. """
  112. map_location = torch.serialization._serialization_tls.map_location
  113. if map_location is None:
  114. return device
  115. else:
  116. if isinstance(map_location, dict):
  117. return map_location.get(device, device)
  118. elif isinstance(map_location, (str, torch.device)):
  119. return map_location
  120. else:
  121. assert callable(map_location)
  122. raise RuntimeError(
  123. "Callable map_location not supported with _rebuild_wrapper_subclass "
  124. "or _rebuild_device_tensor_from_numpy"
  125. )
  126. # Note [Don't serialize hooks]
  127. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  128. # Since time immemorial, we have serialized the backward hooks associated with
  129. # variables. This kind of half-worked--Python can pickle global functions
  130. # (but not closures!)--but there were problems.
  131. #
  132. # - It's fragile. If you serialize a backward hook into a saved
  133. # model, and then you rename the function associated with the hook,
  134. # now your saved model is broken and you can't load it anymore.
  135. #
  136. # - It's not actually used. The standard recommendation is to
  137. # serialize the *state_dict* of a model, not the model itself
  138. # (since this is more stable to code changes affecting the model
  139. # serialization), and the state dict saves "data" only, thus
  140. # stripping the backward hooks. In some cases, hooks are
  141. # essential to the well-functioning of a model (e.g., DDP),
  142. # but DDP already manages re-adding the hooks!
  143. #
  144. # - We didn't serialize them in many cases. Prior to #10220, we
  145. # were dropping backward hooks in ForkingPickler. We "fixed" this
  146. # to be convenient with other serialization sites, but lack of
  147. # serializing backward hooks wasn't actually the root cause of
  148. # the bug.
  149. #
  150. # With these cases in mind, we have decided that a better strategy
  151. # is to just NOT serialize hooks at all.
  152. #
  153. # Since this is a BC-breaking change, we should warn when we previously
  154. # serialized a hook, but no longer do so. This will be done by adding a special
  155. # sentinel property to hooks will be used to suppress this warning. If a hook
  156. # has the property _torch_serialize_ignore, we will not emit a warning if we
  157. # attempt to serialize a Tensor with this hook attached to it.
  158. #
  159. # By the way, when _backward_hooks is skipped, we must give an EMPTY
  160. # OrderedDict(), if you pass a None you'll run afoul #12219.
  161. # TODO: Once we decide to break serialization FC, `storage` no longer needs to
  162. # be a TypedStorage
  163. def _rebuild_tensor(storage, storage_offset, size, stride):
  164. # first construct a tensor with the correct dtype/device
  165. t = torch.empty((0,), dtype=storage.dtype, device=storage._untyped_storage.device)
  166. return t.set_(storage._untyped_storage, storage_offset, size, stride)
  167. def get_tensor_metadata(tensor):
  168. # Tensor's Metadata for serializing.
  169. # Currently, this only returns a dict[string, bool] specifying whether
  170. # `conj` or `neg` bit is set.
  171. assert isinstance(tensor, torch.Tensor)
  172. return torch._C._get_tensor_metadata(tensor) # type: ignore[attr-defined]
  173. def set_tensor_metadata(tensor, metadata):
  174. # See `get_tensor_metadata` above
  175. assert isinstance(metadata, dict)
  176. assert isinstance(tensor, torch.Tensor)
  177. torch._C._set_tensor_metadata(tensor, metadata) # type: ignore[attr-defined]
  178. def _restore_device_fake_mode(tensor):
  179. if torch._guards.detect_fake_mode(None) is not None:
  180. if tensor.untyped_storage()._fake_device is not None:
  181. device = _get_restore_location(tensor.untyped_storage()._fake_device)
  182. if not isinstance(device, torch.device):
  183. device = torch.device(device)
  184. tensor.fake_device = torch.device(device)
  185. return tensor
  186. def _rebuild_tensor_v2(
  187. storage,
  188. storage_offset,
  189. size,
  190. stride,
  191. requires_grad,
  192. backward_hooks,
  193. metadata=None,
  194. ):
  195. tensor = _rebuild_tensor(storage, storage_offset, size, stride)
  196. tensor.requires_grad = requires_grad
  197. if metadata:
  198. set_tensor_metadata(tensor, metadata)
  199. # NB: This line exists only for backwards compatibility; the
  200. # general expectation is that backward_hooks is an empty
  201. # OrderedDict. See Note [Don't serialize hooks]
  202. tensor._backward_hooks = backward_hooks
  203. tensor = _restore_device_fake_mode(tensor)
  204. return tensor
  205. def _rebuild_tensor_v3(
  206. storage,
  207. storage_offset,
  208. size,
  209. stride,
  210. requires_grad,
  211. backward_hooks,
  212. dtype,
  213. metadata=None,
  214. ):
  215. t = torch.empty(
  216. (0,),
  217. dtype=dtype,
  218. device=storage._untyped_storage.device,
  219. requires_grad=requires_grad,
  220. )
  221. t.set_(storage._untyped_storage, storage_offset, size, stride)
  222. if metadata:
  223. set_tensor_metadata(t, metadata)
  224. t._backward_hooks = backward_hooks
  225. t = _restore_device_fake_mode(t)
  226. return t
  227. _sparse_tensors_to_validate: list["torch.Tensor"] = []
  228. # In _legacy_load() in serialization.py we unpickle storages after the sparse
  229. # tensors have been already unpickled. Those storages contain data necessary for
  230. # validating sparse tensors: indices and values. That's why sparse tensors are
  231. # first unpickled without any validation, and then this function is called just
  232. # before _legacy_load() returns, so that all the sparse tensors can be validated
  233. # in bulk.
  234. #
  235. # The same procedure must be followed by _load() in serialization.py because due
  236. # to Pickler semantics, we have to use the same (non-validating) function for
  237. # unpickling sparse tensors, regardless of the caller.
  238. def _validate_loaded_sparse_tensors():
  239. if not torch.sparse.check_sparse_tensor_invariants().is_enabled():
  240. # Skip sparse tensor invariants validation for better
  241. # performance. See check_sparse_tensor_invariants
  242. # documentation for how to control sparse tensor invariants
  243. # checking.
  244. _sparse_tensors_to_validate.clear()
  245. return
  246. try:
  247. # We disable pinning check (see check_pinning=False below) to
  248. # avoid gh-153143. In fact, pinning check is unnecessary
  249. # anywhy when loading sparse data from external sources.
  250. for t in _sparse_tensors_to_validate:
  251. if t.layout is torch.sparse_coo:
  252. torch._validate_sparse_coo_tensor_args(
  253. t._indices(),
  254. t._values(),
  255. t.size(),
  256. t.is_coalesced(),
  257. check_pinning=False,
  258. )
  259. elif t.layout in {
  260. torch.sparse_csr,
  261. torch.sparse_csc,
  262. torch.sparse_bsr,
  263. torch.sparse_bsc,
  264. }:
  265. # TODO: Validation currently involves an expensive traversal
  266. # on CPU, which may include a device transfer.
  267. if t.layout in {torch.sparse_csr, torch.sparse_bsr}:
  268. compressed_indices, plain_indices = (
  269. t.crow_indices(),
  270. t.col_indices(),
  271. )
  272. else:
  273. compressed_indices, plain_indices = (
  274. t.ccol_indices(),
  275. t.row_indices(),
  276. )
  277. torch._validate_sparse_compressed_tensor_args(
  278. compressed_indices,
  279. plain_indices,
  280. t.values(),
  281. t.size(),
  282. t.layout,
  283. check_pinning=False,
  284. )
  285. else:
  286. raise NotImplementedError(
  287. f"_validate_loaded_sparse_tensors for layout `{t.layout}`"
  288. )
  289. finally:
  290. _sparse_tensors_to_validate.clear()
  291. def _rebuild_sparse_tensor(layout, data):
  292. """
  293. Rebuilds a sparse tensor from its sparse storage representation.
  294. Args:
  295. layout (str): The sparse storage layout of the tensor.
  296. data (tuple): The tensor's sparse storage representation.
  297. """
  298. if layout == torch.sparse_coo:
  299. if len(data) == 3:
  300. # For BC:
  301. indices, values, size = data
  302. is_coalesced = None
  303. else:
  304. indices, values, size, is_coalesced = data
  305. result = torch.sparse_coo_tensor(
  306. indices, values, size, check_invariants=False, is_coalesced=is_coalesced
  307. )
  308. _sparse_tensors_to_validate.append(result)
  309. return result
  310. elif layout in {
  311. torch.sparse_csr,
  312. torch.sparse_csc,
  313. torch.sparse_bsr,
  314. torch.sparse_bsc,
  315. }:
  316. compressed_indices, plain_indices, values, size = data
  317. result = torch.sparse_compressed_tensor(
  318. compressed_indices,
  319. plain_indices,
  320. values,
  321. size,
  322. layout=layout,
  323. check_invariants=False,
  324. )
  325. _sparse_tensors_to_validate.append(result)
  326. return result
  327. raise NotImplementedError(f"rebuilding sparse tensor for layout {layout}")
  328. def _rebuild_nested_tensor(buffer, sizes, strides, storage_offsets):
  329. return torch._nested_view_from_buffer(buffer, sizes, strides, storage_offsets)
  330. def _rebuild_device_tensor_from_cpu_tensor(data, dtype, device, requires_grad):
  331. device = _get_restore_location(device)
  332. tensor = data.to(dtype=dtype, device=device)
  333. tensor.requires_grad = requires_grad
  334. return tensor
  335. def _rebuild_device_tensor_from_numpy(data, dtype, device, requires_grad):
  336. device = _get_restore_location(device)
  337. tensor = torch.from_numpy(data).to(dtype=dtype, device=device)
  338. tensor.requires_grad = requires_grad
  339. return tensor
  340. # Should not be used, only here to be able to load Tensors serialized with older versions of pytorch
  341. _rebuild_xla_tensor = _rebuild_device_tensor_from_numpy
  342. def _rebuild_meta_tensor_no_storage(dtype, size, stride, requires_grad):
  343. return torch.empty_strided(
  344. size, stride, dtype=dtype, device="meta", requires_grad=requires_grad
  345. )
  346. def _rebuild_wrapper_subclass(
  347. cls,
  348. dtype,
  349. size,
  350. stride,
  351. storage_offset,
  352. layout,
  353. device,
  354. requires_grad,
  355. ):
  356. device = _get_restore_location(device)
  357. return torch.Tensor._make_wrapper_subclass(
  358. cls,
  359. size,
  360. strides=stride,
  361. dtype=dtype,
  362. storage_offset=storage_offset,
  363. layout=layout,
  364. device=device,
  365. requires_grad=requires_grad,
  366. )
  367. # TODO: Once we decide to break serialization FC, `storage` no longer needs to
  368. # be a TypedStorage
  369. def _rebuild_qtensor(
  370. storage,
  371. storage_offset,
  372. size,
  373. stride,
  374. quantizer_params,
  375. requires_grad,
  376. backward_hooks,
  377. ):
  378. qscheme = quantizer_params[0]
  379. if qscheme == torch.per_tensor_affine:
  380. _, scale, zero_point = quantizer_params
  381. tensor = torch._empty_affine_quantized(
  382. size,
  383. scale=scale,
  384. zero_point=zero_point,
  385. dtype=storage.dtype,
  386. device=storage.device,
  387. )
  388. elif qscheme in (torch.per_channel_affine, torch.per_channel_affine_float_qparams):
  389. _, scales, zero_points, axis = quantizer_params
  390. if type(scales) is list and type(zero_points) is list:
  391. if qscheme == torch.per_channel_affine:
  392. scales = torch.tensor(scales, dtype=torch.double, device=storage.device)
  393. zero_points = torch.tensor(
  394. zero_points, dtype=torch.long, device=storage.device
  395. )
  396. else:
  397. scales = torch.tensor(scales, dtype=torch.float, device=storage.device)
  398. zero_points = torch.tensor(
  399. zero_points, dtype=torch.float, device=storage.device
  400. )
  401. tensor = torch._empty_per_channel_affine_quantized(
  402. size,
  403. scales=scales,
  404. zero_points=zero_points,
  405. axis=axis,
  406. dtype=storage.dtype,
  407. device=storage.device,
  408. )
  409. else:
  410. raise RuntimeError(f"Can't deserialize quantized tensor with qscheme {qscheme}")
  411. tensor.set_(storage, storage_offset, size, stride)
  412. tensor.requires_grad = requires_grad
  413. # NB: This line exists only for backwards compatibility; the
  414. # general expectation is that backward_hooks is an empty
  415. # OrderedDict. See Note [Don't serialize hooks]
  416. tensor._backward_hooks = backward_hooks
  417. return tensor
  418. def _rebuild_parameter(data, requires_grad, backward_hooks):
  419. param = torch.nn.Parameter(data, requires_grad)
  420. # NB: This line exists only for backwards compatibility; the
  421. # general expectation is that backward_hooks is an empty
  422. # OrderedDict. See Note [Don't serialize hooks]
  423. param._backward_hooks = backward_hooks
  424. return param
  425. def _rebuild_parameter_with_state(data, requires_grad, backward_hooks, state):
  426. param = torch.nn.Parameter(data, requires_grad)
  427. # NB: This line exists only for backwards compatibility; the
  428. # general expectation is that backward_hooks is an empty
  429. # OrderedDict. See Note [Don't serialize hooks]
  430. param._backward_hooks = backward_hooks
  431. # Restore state on Parameter like python attr.
  432. param = _set_obj_state(param, state)
  433. return param
  434. def _get_obj_state(obj):
  435. # Get the state of the python subclass
  436. # This loosely mimics the function on the object class but since Tensor do not inherit
  437. # from it, we cannot call that function directly
  438. # https://github.com/python/cpython/blob/c83919bd635f4433f1c6ae8504996a9fe3c215e5/Objects/typeobject.c#L4891
  439. # Note that starting with Python 3.11, this `__getstate__` is always defined and thus
  440. # the else branch will never be taken.
  441. getstate_fn = getattr(obj, "__getstate__", None)
  442. if getstate_fn:
  443. state = getstate_fn()
  444. else:
  445. slots_to_save = copyreg._slotnames(obj.__class__) # type: ignore[attr-defined]
  446. if slots_to_save:
  447. state = (
  448. obj.__dict__,
  449. {
  450. name: getattr(obj, name)
  451. for name in slots_to_save
  452. if hasattr(obj, name)
  453. },
  454. )
  455. else:
  456. state = obj.__dict__
  457. return state
  458. def _set_obj_state(obj, state):
  459. if isinstance(state, tuple):
  460. if not len(state) == 2:
  461. raise RuntimeError(f"Invalid serialized state: {state}")
  462. dict_state = state[0]
  463. slots_state = state[1]
  464. else:
  465. dict_state = state
  466. slots_state = None
  467. # Starting with Python 3.11, the __dict__ attribute is lazily created
  468. # and is serialized as None when not needed.
  469. if dict_state:
  470. for k, v in dict_state.items():
  471. setattr(obj, k, v)
  472. if slots_state:
  473. for k, v in slots_state.items():
  474. setattr(obj, k, v)
  475. return obj
  476. def _import_dotted_name(name):
  477. components = name.split(".")
  478. obj = __import__(components[0])
  479. for component in components[1:]:
  480. obj = getattr(obj, component)
  481. return obj
  482. def _flatten_dense_tensors(tensors):
  483. """Flatten dense tensors into a contiguous 1D buffer. Assume tensors are of
  484. same dense type.
  485. Since inputs are dense, the resulting tensor will be a concatenated 1D
  486. buffer. Element-wise operation on this buffer will be equivalent to
  487. operating individually.
  488. Args:
  489. tensors (Iterable[Tensor]): dense tensors to flatten.
  490. Returns:
  491. A contiguous 1D buffer containing input tensors.
  492. """
  493. return torch._C._nn.flatten_dense_tensors(tensors)
  494. def _flatten_sparse_tensors(tensors):
  495. """Flatten sparse tensors into two contiguous 1D buffers, one of indices and
  496. one of values. Assume tensors are of same sparse type.
  497. Args:
  498. tensors (Iterable[Tensor]): sparse tensors to flatten.
  499. Returns:
  500. A tuple of two contiguous 1D buffers, one containing input tensors'
  501. indices and the other containing the values.
  502. """
  503. flat_indices = torch._C._nn.flatten_dense_tensors(
  504. [torch.Tensor._indices(t) for t in tensors]
  505. )
  506. flat_values = torch._C._nn.flatten_dense_tensors(
  507. [torch.Tensor._values(t) for t in tensors]
  508. )
  509. return flat_indices, flat_values
  510. def _unflatten_dense_tensors(flat, tensors):
  511. """View a flat buffer using the sizes of tensors. Assume that tensors are of
  512. same dense type, and that flat is given by _flatten_dense_tensors.
  513. Args:
  514. flat (Tensor): flattened dense tensors to unflatten.
  515. tensors (Iterable[Tensor]): dense tensors whose sizes will be used to
  516. unflatten flat.
  517. Returns:
  518. Unflattened dense tensors with sizes same as tensors and values from
  519. flat.
  520. """
  521. return torch._C._nn.unflatten_dense_tensors(flat, tensors)
  522. def _unflatten_sparse_tensors(flat, tensors):
  523. """View flat buffer (containing indices and values) using the sizes of
  524. tensors. Assume that tensors are of same sparse type, and that flat is given
  525. by _flatten_sparse_tensors.
  526. Args:
  527. flat (tuple(Tensor, Tensor)): flattened indices and values of sparse
  528. tensors to unflatten.
  529. tensors (Iterable[Tensor]): sparse tensors whose sizes will be used to
  530. unflatten flat.
  531. Returns:
  532. Unflattened sparse tensors with sizes same as tensors and values from
  533. flat.
  534. """
  535. flat_indices, flat_values = flat
  536. indices = torch._C._nn.unflatten_dense_tensors(
  537. flat_indices, [torch.Tensor._indices(t) for t in tensors]
  538. )
  539. values = torch._C._nn.unflatten_dense_tensors(
  540. flat_values, [torch.Tensor._values(t) for t in tensors]
  541. )
  542. outputs = []
  543. for t, i, v in zip(tensors, indices, values):
  544. outputs.append(t.new(i, v, t.size()))
  545. return tuple(outputs)
  546. def _reorder_tensors_as(tensors, ordered_tensors):
  547. """Assume that tensors are of same order as ordered_tensors within their
  548. types, e.g., from _take_tensors. Reorder them to be of same order as
  549. ordered_tensors.
  550. Args:
  551. tensors (Iterable[Tensor]): tensors to be reordered. They should be of
  552. the same order as ordered_tensors within their own types.
  553. ordered_tensors (Iterable[Tensor]): tensors whose order will be the
  554. reference.
  555. Returns:
  556. Ordered tuple of tensors with contents from tensors and order of
  557. ordered_tensors.
  558. """
  559. type_dict = defaultdict(list)
  560. for tensor in tensors:
  561. type_dict[tensor.type()].append(tensor)
  562. type_dict_ = {t: iter(coll) for t, coll in type_dict.items()}
  563. return tuple(next(type_dict_[tensor.type()]) for tensor in ordered_tensors)
  564. def _take_tensors(tensors, size_limit):
  565. """Group tensors into chunks. This generator yields a chunk at each time,
  566. each containing tensors of same type up to certain byte limit in total size.
  567. Args:
  568. tensors (Sequence): A sequence of tensors to be separated into chunks.
  569. size_limit (int): The limit of each chunk in bytes.
  570. Yields:
  571. Blocks of tensors of same type and within size_limit. The yielded
  572. tensors are only ordered as the original sequence within its types.
  573. """
  574. buf_dict: defaultdict[str, list] = defaultdict(lambda: [[], 0])
  575. for tensor in tensors:
  576. t = tensor.type()
  577. if tensor.is_sparse:
  578. indices = torch.Tensor._indices(tensor)
  579. values = torch.Tensor._values(tensor)
  580. size = (
  581. indices.numel() * indices.element_size()
  582. + values.numel() * values.element_size()
  583. )
  584. else:
  585. size = tensor.numel() * tensor.element_size()
  586. buf_and_size = buf_dict[t]
  587. if buf_and_size[1] + size > size_limit and buf_and_size[1] > 0:
  588. yield buf_and_size[0]
  589. buf_and_size = buf_dict[t] = [[], 0]
  590. buf_and_size[0].append(tensor) # pyrefly: ignore [missing-attribute]
  591. buf_and_size[1] += size # pyrefly: ignore [unsupported-operation]
  592. for buf, _ in buf_dict.values():
  593. if len(buf) > 0:
  594. yield buf
  595. # annotation decorator to get annotations in a way that is compatible
  596. # with both Python 2 and 3
  597. def annotate(ret, **kwargs):
  598. def dec(fun):
  599. fun.__annotations__ = dict(kwargs)
  600. fun.__annotations__["return"] = ret
  601. return fun
  602. return dec
  603. def render_call(fn, args, kwargs):
  604. str_fn = torch.overrides.resolve_name(fn)
  605. if str_fn is None:
  606. str_fn = str(fn)
  607. str_args: list[str] = []
  608. with torch._tensor_str.printoptions(threshold=0, edgeitems=0):
  609. str_args.extend(repr(a) for a in args)
  610. str_args.extend(f"{k}={repr(v)}" for k, v in kwargs.items())
  611. r = f"{str_fn}({', '.join(str_args)})"
  612. return r
  613. # NOTE [ Python Traceback Reference Cycle Problem ]
  614. #
  615. # When using sys.exc_info(), it is important to **not** store the exc_info[2],
  616. # which is the traceback, because otherwise you will run into the traceback
  617. # reference cycle problem, i.e., the traceback holding reference to the frame,
  618. # and the frame (which holds reference to all the object in its temporary scope)
  619. # holding reference the traceback.
  620. class KeyErrorMessage(str):
  621. r"""str subclass that returns itself in repr"""
  622. __slots__ = ()
  623. def __repr__(self):
  624. return self
  625. class ExceptionWrapper:
  626. r"""Wraps an exception plus traceback to communicate across threads"""
  627. def __init__(self, exc_info=None, where="in background"):
  628. # It is important that we don't store exc_info, see
  629. # NOTE [ Python Traceback Reference Cycle Problem ]
  630. if exc_info is None:
  631. exc_info = sys.exc_info()
  632. self.exc_type = exc_info[0]
  633. # pyrefly: ignore [not-iterable]
  634. self.exc_msg = "".join(traceback.format_exception(*exc_info))
  635. self.where = where
  636. def reraise(self):
  637. r"""Reraises the wrapped exception in the current thread"""
  638. # Format a message such as: "Caught ValueError in DataLoader worker
  639. # process 2. Original Traceback:", followed by the traceback.
  640. msg = f"Caught {self.exc_type.__name__} {self.where}.\nOriginal {self.exc_msg}" # pyrefly: ignore [missing-attribute]
  641. if self.exc_type is KeyError:
  642. # KeyError calls repr() on its argument (usually a dict key). This
  643. # makes stack traces unreadable. It will not be changed in Python
  644. # (https://bugs.python.org/issue2651), so we work around it.
  645. msg = KeyErrorMessage(msg)
  646. elif getattr(self.exc_type, "message", None):
  647. # Some exceptions have first argument as non-str but explicitly
  648. # have message field
  649. # pyrefly: ignore [not-callable]
  650. raise self.exc_type(
  651. # pyrefly: ignore [unexpected-keyword]
  652. message=msg
  653. )
  654. try:
  655. exception = self.exc_type(msg) # pyrefly: ignore [not-callable]
  656. except Exception:
  657. # If the exception takes multiple arguments or otherwise can't
  658. # be constructed, don't try to instantiate since we don't know how to
  659. raise RuntimeError(msg) from None
  660. raise exception
  661. def _get_available_device_type():
  662. if torch.cuda.is_available():
  663. return "cuda"
  664. if torch.backends.mps.is_available():
  665. return "mps"
  666. if hasattr(torch, "xpu") and torch.xpu.is_available(): # type: ignore[attr-defined]
  667. return "xpu"
  668. if hasattr(torch, "mtia") and torch.mtia.is_available():
  669. return "mtia"
  670. custom_backend_name = torch._C._get_privateuse1_backend_name()
  671. custom_device_mod = getattr(torch, custom_backend_name, None)
  672. if custom_device_mod and custom_device_mod.is_available():
  673. return custom_backend_name
  674. # add more available device types here
  675. return None
  676. def _get_device_attr(get_member):
  677. device_type = _get_available_device_type()
  678. if device_type and device_type.lower() == "cuda":
  679. return get_member(torch.cuda)
  680. if device_type and device_type.lower() == "mps":
  681. return get_member(torch.mps)
  682. if device_type and device_type.lower() == "xpu":
  683. return get_member(torch.xpu) # type: ignore[attr-defined]
  684. if device_type and device_type.lower() == "mtia":
  685. return get_member(torch.mtia)
  686. if device_type == torch._C._get_privateuse1_backend_name():
  687. return get_member(getattr(torch, device_type))
  688. # add more available device types here
  689. return None
  690. def _get_current_device_index():
  691. # current device index
  692. return _get_device_attr(lambda m: m.current_device())
  693. def _get_all_device_indices():
  694. # all device index
  695. return _get_device_attr(lambda m: list(range(m.device_count())))
  696. def _get_devices_properties(device_ids):
  697. # all device properties
  698. return [_get_device_attr(lambda m: m.get_device_properties(i)) for i in device_ids]
  699. def get_current_device_index() -> int:
  700. r"""Checks if there are CUDA devices available and
  701. returns the device index of the current default CUDA device.
  702. Returns -1 in case there are no CUDA devices available.
  703. Arguments: ``None``
  704. """
  705. if torch.cuda.device_count() > 0:
  706. return torch.cuda.current_device()
  707. return -1
  708. def _get_device_index(
  709. device: Any,
  710. optional: bool = False,
  711. allow_cpu: bool = False,
  712. ) -> int:
  713. r"""Gets the device index from :attr:`device`, which can be a torch.device
  714. object, a Python integer, or ``None``.
  715. If :attr:`device` is a torch.device object, returns the device index if it
  716. has index. Note that for a device without a specified index,
  717. i.e., ``torch.device('xxx')``, this will return the current default
  718. device of that type if :attr:`optional` is ``True``. If :attr:`allow_cpu` is ``True``,
  719. CPU devices will be accepted and ``-1`` will be returned in this case.
  720. If :attr:`device` is a Python integer, it is returned as is.
  721. If :attr:`device` is ``None``, this will return the current default
  722. device of the supported runtime platform if :attr:`optional` is ``True``.
  723. i.e., the current default CUDA device will be returned if CUDA runtime is supported.
  724. """
  725. if isinstance(device, str):
  726. device = torch.device(device)
  727. device_idx: int | None = None
  728. if isinstance(device, torch.device):
  729. if not allow_cpu and device.type == "cpu":
  730. raise ValueError(f"Expected a non cpu device, but got: {device}")
  731. device_idx = -1 if device.type == "cpu" else device.index
  732. if isinstance(device, int):
  733. device_idx = device
  734. if device_idx is None:
  735. if optional:
  736. # The eager API _get_current_device_index uses `lambda` functions which are
  737. # not supported in JIT and hence not scriptable. The JIT equivalent API to get
  738. # the current device index is `get_current_device_index()` which can
  739. # be scripted. We use is_scripting to check the mode we are in and call the
  740. # appropriate API.
  741. if torch.jit.is_scripting():
  742. device_idx = get_current_device_index()
  743. else:
  744. device_idx = _get_current_device_index()
  745. else:
  746. raise ValueError(
  747. f"Expected a torch.device with a specified index or an integer, but got:{device}"
  748. )
  749. return device_idx
  750. def _handle_complex(tensor):
  751. """
  752. Returns a real view of a tensor if complex dtype else just the tensor
  753. need to check if a UninitializedParameter because otherwise checking is_complex is an error for a LazyModule
  754. """
  755. return (
  756. torch.view_as_real(tensor)
  757. if not isinstance(tensor, torch.nn.UninitializedParameter)
  758. and tensor.is_complex()
  759. else tensor
  760. )
  761. def _element_size(dtype):
  762. """
  763. Returns the element size for a dtype, in bytes
  764. """
  765. if not isinstance(dtype, torch.dtype):
  766. raise RuntimeError(f"expected torch.dtype, but got {type(dtype)}")
  767. if dtype.is_complex:
  768. return torch.finfo(dtype).bits >> 2
  769. elif dtype.is_floating_point:
  770. return torch.finfo(dtype).bits >> 3
  771. elif dtype == torch.bool:
  772. # NOTE: torch.bool is not supported in torch.iinfo()
  773. return 1
  774. else:
  775. return torch.iinfo(dtype).bits >> 3
  776. class _ClassPropertyDescriptor:
  777. def __init__(self, fget, fset=None):
  778. self.fget = fget
  779. def __get__(self, instance, owner=None):
  780. if owner is None:
  781. owner = type(instance)
  782. return self.fget.__get__(instance, owner)()
  783. def classproperty(func):
  784. if not isinstance(func, (classmethod, staticmethod)):
  785. func = classmethod(func)
  786. return _ClassPropertyDescriptor(func)
  787. if TYPE_CHECKING:
  788. # TorchScript does not support `@deprecated`
  789. # This is a workaround to avoid breaking TorchScript
  790. @deprecated(
  791. "`torch._utils.is_compiling` is deprecated. Use `torch.compiler.is_compiling` instead.",
  792. category=FutureWarning,
  793. )
  794. def is_compiling() -> bool:
  795. return torch.compiler.is_compiling()
  796. else:
  797. def is_compiling() -> bool:
  798. """
  799. Indicates whether we are tracing/compiling with torch.compile() or torch.export().
  800. """
  801. warnings.warn( # use `warnings.warn` instead of `@deprecated`
  802. "`torch._utils.is_compiling` is deprecated. Use `torch.compiler.is_compiling` instead.",
  803. # FutureWarning, # TorchScript does not support Warning type
  804. stacklevel=2,
  805. )
  806. return torch.compiler.is_compiling()
  807. def _functionalize_sync(t):
  808. # This code lives in python instead of C++ since conditioning on a certain python subclass
  809. # is much more of a pain in C++.
  810. from torch._subclasses.functional_tensor import FunctionalTensor
  811. if isinstance(t, FunctionalTensor):
  812. # If a FunctionalTensorMode is active while syncing, we don't want it to intercept any ops that get called
  813. # when we sync our inner tensor.
  814. # Why?
  815. # (1) If there are input mutations in the graph, then they will be re-applied during
  816. # AOTAutograd when we call _sync() from inside of our functionalization kernels.
  817. # (2) _sync() causes us to regenerate our updated the tensor from the updated base,
  818. # which dispatches to a bunch of view ops
  819. # (3) The input to these view ops is our inner FunctionalTensorWrapper
  820. # (since the sync was called from C++), not the python FunctionalTensor
  821. # (4) if a python FunctionalTensorMode is active, it will complain when it intercepts
  822. # the view op, since it will see an input that is a C++ FunctionalTensorWrapper
  823. # (aka a normal torch.Tensor) instead of a python `FunctionalTensor).
  824. maybe_functional_mode = torch._C._unset_dispatch_mode(
  825. torch._C._TorchDispatchModeKey.FUNCTIONAL
  826. )
  827. try:
  828. torch._functionalize_sync(t.elem) # type: ignore[attr-defined]
  829. finally:
  830. if maybe_functional_mode is not None:
  831. torch._C._set_dispatch_mode(maybe_functional_mode)
  832. else:
  833. torch._functionalize_sync(t) # type: ignore[attr-defined]
  834. @functools.lru_cache(2)
  835. def _get_device_module(device_type: str):
  836. device_module = getattr(torch, device_type, None)
  837. if device_module is None:
  838. raise RuntimeError(
  839. f"Device '{device_type}' does not have a corresponding module registered as 'torch.{device_type}'."
  840. )
  841. return device_module
  842. def _dummy_type(name: str) -> type:
  843. def get_err_fn(is_init: bool):
  844. def err_fn(obj, *args, **kwargs):
  845. if is_init:
  846. class_name = obj.__class__.__name__
  847. else:
  848. class_name = obj.__name__
  849. raise RuntimeError(f"Tried to instantiate dummy base class {class_name}")
  850. return err_fn
  851. return type(
  852. name, (object,), {"__init__": get_err_fn(True), "__new__": get_err_fn(False)}
  853. )
  854. class _LazySeedTracker:
  855. # Since seeding is memory-less, only track the latest seed.
  856. # Note: `manual_seed_all` followed by `manual_seed` overwrites
  857. # the seed on current device. We track the order of **latest**
  858. # calls between these two API.
  859. def __init__(self):
  860. self.manual_seed_all_cb = None
  861. self.manual_seed_cb = None
  862. self.call_order = []
  863. def queue_seed_all(self, cb, traceback):
  864. self.manual_seed_all_cb = (cb, traceback) # pyrefly: ignore [bad-assignment]
  865. # update seed_all to be latest
  866. self.call_order = [self.manual_seed_cb, self.manual_seed_all_cb]
  867. def queue_seed(self, cb, traceback):
  868. self.manual_seed_cb = (cb, traceback) # pyrefly: ignore [bad-assignment]
  869. # update seed to be latest
  870. self.call_order = [self.manual_seed_all_cb, self.manual_seed_cb]
  871. def get_calls(self) -> list:
  872. return self.call_order
  873. logger = logging.getLogger(__name__)
  874. P = ParamSpec("P")
  875. class CallbackRegistry(Generic[P]):
  876. def __init__(self, name: str):
  877. self.name = name
  878. self.callback_list: list[Callable[P, None]] = []
  879. def add_callback(self, cb: Callable[P, None]) -> None:
  880. self.callback_list.append(cb)
  881. def fire_callbacks(self, *args: P.args, **kwargs: P.kwargs) -> None:
  882. for cb in self.callback_list:
  883. try:
  884. cb(*args, **kwargs)
  885. except Exception:
  886. logger.exception(
  887. "Exception in callback for %s registered with gpu trace", self.name
  888. )
  889. def try_import(module_name: str) -> ModuleType | None:
  890. # Implementation based on
  891. # https://docs.python.org/3/library/importlib.html#checking-if-a-module-can-be-imported
  892. if (module := sys.modules.get(module_name, None)) is not None:
  893. return module
  894. if (spec := importlib.util.find_spec(module_name)) is not None:
  895. module = importlib.util.module_from_spec(spec)
  896. sys.modules[module_name] = module
  897. # https://docs.python.org/3/library/importlib.html#importlib.machinery.ModuleSpec.loader
  898. # "The finder should always set this attribute"
  899. assert spec.loader is not None, "The loader attribute should always be set"
  900. spec.loader.exec_module(module)
  901. return module
  902. return None
  903. # IMPORT_MAPPING and NAME_MAPPING are adapted from https://github.com/python/cpython/blob/main/Lib/_compat_pickle.py
  904. # for use in the weights_only Unpickler.
  905. IMPORT_MAPPING = {
  906. "__builtin__": "builtins",
  907. "copy_reg": "copyreg",
  908. "Queue": "queue",
  909. "repr": "reprlib",
  910. "_abcoll": "collections.abc",
  911. # Non-mutual mappings.
  912. "UserDict": "collections",
  913. "UserList": "collections",
  914. "UserString": "collections",
  915. "whichdb": "dbm",
  916. "StringIO": "io",
  917. "cStringIO": "io",
  918. }
  919. # This contains rename rules that are easy to handle. We ignore the more
  920. # complex stuff (e.g. mapping the names in the urllib and types modules).
  921. # These rules should be run before import names are fixed.
  922. NAME_MAPPING = {
  923. ("__builtin__", "xrange"): ("builtins", "range"),
  924. ("__builtin__", "reduce"): ("functools", "reduce"),
  925. ("__builtin__", "intern"): ("sys", "intern"),
  926. ("__builtin__", "unichr"): ("builtins", "chr"),
  927. ("__builtin__", "unicode"): ("builtins", "str"),
  928. ("__builtin__", "long"): ("builtins", "int"),
  929. ("itertools", "izip"): ("builtins", "zip"),
  930. ("itertools", "imap"): ("builtins", "map"),
  931. ("itertools", "ifilter"): ("builtins", "filter"),
  932. ("itertools", "ifilterfalse"): ("itertools", "filterfalse"),
  933. ("itertools", "izip_longest"): ("itertools", "zip_longest"),
  934. ("UserDict", "IterableUserDict"): ("collections", "UserDict"),
  935. ("UserList", "UserList"): ("collections", "UserList"),
  936. ("UserString", "UserString"): ("collections", "UserString"),
  937. # Non-mutual mappings.
  938. ("__builtin__", "basestring"): ("builtins", "str"),
  939. ("exceptions", "StandardError"): ("builtins", "Exception"),
  940. ("UserDict", "UserDict"): ("collections", "UserDict"),
  941. }