_utils.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. import torch
  2. from typing import Optional, List, DefaultDict, Any
  3. import warnings
  4. from collections import defaultdict
  5. import sys
  6. import traceback
  7. def _type(self, dtype=None, non_blocking=False, **kwargs):
  8. """Returns the type if `dtype` is not provided, else casts this object to
  9. the specified type.
  10. If this is already of the correct type, no copy is performed and the
  11. original object is returned.
  12. Args:
  13. dtype (type or string): The desired type
  14. non_blocking (bool): If ``True``, and the source is in pinned memory
  15. and destination is on the GPU or vice versa, the copy is performed
  16. asynchronously with respect to the host. Otherwise, the argument
  17. has no effect.
  18. **kwargs: For compatibility, may contain the key ``async`` in place of
  19. the ``non_blocking`` argument. The ``async`` arg is deprecated.
  20. """
  21. non_blocking = _get_async_or_non_blocking('type', non_blocking, kwargs)
  22. if dtype is None:
  23. return self.__module__ + '.' + self.__class__.__name__
  24. if isinstance(dtype, str):
  25. dtype = _import_dotted_name(dtype)
  26. if dtype == type(self):
  27. return self
  28. if self.is_sparse:
  29. if not dtype.is_sparse:
  30. raise RuntimeError("Cannot cast sparse tensor to dense tensor")
  31. new_module_name = dtype.__module__.replace('.sparse', '')
  32. new_values_type_name = new_module_name + '.' + dtype.__name__
  33. new_values = torch.Tensor._values(self).type(new_values_type_name, non_blocking)
  34. new_indices_type_name = new_module_name + '.LongTensor'
  35. new_indices = torch.Tensor._indices(self).type(new_indices_type_name, non_blocking)
  36. return dtype(new_indices, new_values, self.size())
  37. if dtype.is_sparse:
  38. raise RuntimeError("Cannot cast dense tensor to sparse tensor")
  39. return dtype(self.size()).copy_(self, non_blocking)
  40. def _cuda(self, device=None, non_blocking=False, **kwargs):
  41. """Returns a copy of this object in CUDA memory.
  42. If this object is already in CUDA memory and on the correct device, then
  43. no copy is performed and the original object is returned.
  44. Args:
  45. device (int): The destination GPU id. Defaults to the current device.
  46. non_blocking (bool): If ``True`` and the source is in pinned memory,
  47. the copy will be asynchronous with respect to the host. Otherwise,
  48. the argument has no effect.
  49. **kwargs: For compatibility, may contain the key ``async`` in place of
  50. the ``non_blocking`` argument.
  51. """
  52. non_blocking = _get_async_or_non_blocking('cuda', non_blocking, kwargs)
  53. if self.is_cuda:
  54. if device is None:
  55. device = torch.cuda.current_device()
  56. if self.get_device() == device:
  57. return self
  58. else:
  59. if device is None:
  60. device = -1
  61. with torch.cuda.device(device):
  62. if self.is_sparse:
  63. new_type = getattr(torch.cuda.sparse, self.__class__.__name__)
  64. indices = torch.Tensor._indices(self).cuda(device, non_blocking)
  65. values = torch.Tensor._values(self).cuda(device, non_blocking)
  66. return new_type(indices, values, self.size())
  67. else:
  68. return torch._UntypedStorage(self.size(), device=torch.device('cuda')).copy_(self, non_blocking)
  69. def _get_async_or_non_blocking(function_name, non_blocking, kwargs):
  70. if not kwargs:
  71. return non_blocking
  72. if len(kwargs) != 1 or 'async' not in kwargs:
  73. message = "{}() got an unexpected keyword argument '{}'"
  74. argument = list(kwargs.keys()).pop()
  75. raise TypeError(message.format(function_name, argument))
  76. warnings.warn("'async' is deprecated; use 'non_blocking'")
  77. return kwargs['async']
  78. # Note [Don't serialize hooks]
  79. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  80. # Since time immemorial, we have serialized the backward hooks associated with
  81. # variables. This kind of half-worked--Python can pickle global functions
  82. # (but not closures!)--but there were problems.
  83. #
  84. # - It's fragile. If you serialize a backward hook into a saved
  85. # model, and then you rename the function associated with the hook,
  86. # now your saved model is broken and you can't load it anymore.
  87. #
  88. # - It's not actually used. The standard recommendation is to
  89. # serialize the *state_dict* of a model, not the model itself
  90. # (since this is more stable to code changes affecting the model
  91. # serialization), and the state dict saves "data" only, thus
  92. # stripping the the backward hooks. In some cases, hooks are
  93. # essential to the well-functioning of a model (e.g., DDP),
  94. # but DDP already manages readding the hooks!
  95. #
  96. # - We didn't serialize them in many cases. Prior to #10220, we
  97. # were dropping backward hooks in ForkingPickler. We "fixed" this
  98. # to be convenient with other serialization sites, but lack of
  99. # serializing backward hooks wasn't actually the root cause of
  100. # the bug.
  101. #
  102. # With these cases in mind, we have decided that a better strategy
  103. # is to just NOT serialize hooks at all.
  104. #
  105. # Since this is a BC-breaking change, we should warn when we previously
  106. # serialized a hook, but no longer do so. This will be done by adding a special
  107. # sentinel property to hooks will be used to suppress this warning. If a hook
  108. # has the property _torch_serialize_ignore, we will not emit a warning if we
  109. # attempt to serialize a Tensor with this hook attached to it.
  110. #
  111. # By the way, when _backward_hooks is skipped, we must give an EMPTY
  112. # OrderedDict(), if you pass a None you'll run afoul #12219.
  113. # TODO: Once we decide to break serialization FC, `storage` no longer needs to
  114. # be a _TypedStorage
  115. def _rebuild_tensor(storage, storage_offset, size, stride):
  116. # first construct a tensor with the correct dtype/device
  117. t = torch.tensor([], dtype=storage.dtype, device=storage._untyped().device)
  118. return t.set_(storage._untyped(), storage_offset, size, stride)
  119. def _rebuild_tensor_v2(storage, storage_offset, size, stride, requires_grad, backward_hooks):
  120. tensor = _rebuild_tensor(storage, storage_offset, size, stride)
  121. tensor.requires_grad = requires_grad
  122. # NB: This line exists only for backwards compatibility; the
  123. # general expectation is that backward_hooks is an empty
  124. # OrderedDict. See Note [Don't serialize hooks]
  125. tensor._backward_hooks = backward_hooks
  126. return tensor
  127. _sparse_tensors_to_validate: List["torch.Tensor"] = []
  128. # In _legacy_load() in serialization.py we unpickle storages after the sparse
  129. # tensors have been already unpickled. Those storages contain data necessary for
  130. # validating sparse tensors: indices and values. That's why sparse tensors are
  131. # first unpickled without any validation, and then this function is called just
  132. # before _legacy_load() returns, so that all the sparse tensors can be validated
  133. # in bulk.
  134. #
  135. # The same procedure must be followed by _load() in serialization.py because due
  136. # to Pickler semantics, we have to use the same (non-validating) function for
  137. # unpickling sparse tensors, regardless of the caller.
  138. def _validate_loaded_sparse_tensors():
  139. try:
  140. for t in _sparse_tensors_to_validate:
  141. if t.is_sparse:
  142. torch._validate_sparse_coo_tensor_args(t._indices(), t._values(),
  143. t.size())
  144. elif t.is_sparse_csr:
  145. # TODO: Validation currently involves an expensive traversal
  146. # on CPU, which may include a device transfer.
  147. torch._validate_sparse_csr_tensor_args(t.crow_indices(), t.col_indices(),
  148. t.values(), t.size())
  149. else:
  150. raise NotImplementedError(
  151. '_validate_loaded_sparse_tensors for layout `%s`' % (t.layout))
  152. finally:
  153. _sparse_tensors_to_validate.clear()
  154. def _rebuild_sparse_tensor(layout, data):
  155. if layout == torch.sparse_coo:
  156. indices, values, size = data
  157. result = torch._sparse_coo_tensor_unsafe(indices, values, size)
  158. _sparse_tensors_to_validate.append(result)
  159. return result
  160. raise NotImplementedError("rebuilding sparse tensor for layout %s" % (layout))
  161. def _rebuild_sparse_csr_tensor(layout, data):
  162. if layout == torch.sparse_csr:
  163. crow_indices, col_indices, values, size = data
  164. result = torch._sparse_csr_tensor_unsafe(crow_indices, col_indices, values, size)
  165. _sparse_tensors_to_validate.append(result)
  166. return result
  167. raise NotImplementedError("rebuilding sparse tensor for layout %s" % (layout))
  168. def _rebuild_device_tensor_from_numpy(data, dtype, device, requires_grad):
  169. tensor = torch.from_numpy(data).to(dtype=dtype, device=device)
  170. tensor.requires_grad = requires_grad
  171. return tensor
  172. # Should not be used, only here to be able to load Tensors serialized with older versions of pytorch
  173. _rebuild_xla_tensor = _rebuild_device_tensor_from_numpy
  174. def _rebuild_meta_tensor_no_storage(dtype, size, stride, requires_grad):
  175. return torch.empty_strided(size, stride, dtype=dtype, device='meta', requires_grad=requires_grad)
  176. def _rebuild_wrapper_subclass(cls, dtype, size, stride, storage_offset, layout, device, requires_grad):
  177. return torch.Tensor._make_wrapper_subclass( # type: ignore[attr-defined]
  178. cls, size, strides=stride, storage_offset=storage_offset, layout=layout,
  179. device=device, requires_grad=requires_grad)
  180. # TODO: Once we decide to break serialization FC, `storage` no longer needs to
  181. # be a _TypedStorage
  182. def _rebuild_qtensor(storage, storage_offset, size, stride, quantizer_params, requires_grad, backward_hooks):
  183. qscheme = quantizer_params[0]
  184. if qscheme == torch.per_tensor_affine:
  185. _, scale, zero_point = quantizer_params
  186. tensor = torch._empty_affine_quantized(size, scale=scale, zero_point=zero_point, dtype=storage.dtype)
  187. elif qscheme in (torch.per_channel_affine, torch.per_channel_affine_float_qparams):
  188. _, scales, zero_points, axis = quantizer_params
  189. if type(scales) is list and type(zero_points) is list:
  190. if qscheme == torch.per_channel_affine:
  191. scales = torch.tensor(scales, dtype=torch.double)
  192. zero_points = torch.tensor(zero_points, dtype=torch.long)
  193. else:
  194. scales = torch.tensor(scales, dtype=torch.float)
  195. zero_points = torch.tensor(zero_points, dtype=torch.float)
  196. tensor = torch._empty_per_channel_affine_quantized(
  197. size, scales=scales, zero_points=zero_points, axis=axis, dtype=storage.dtype)
  198. else:
  199. raise RuntimeError("Can't deserialize quantized tensor with qscheme {}".format(qscheme))
  200. tensor.set_(storage, storage_offset, size, stride)
  201. tensor.requires_grad = requires_grad
  202. # NB: This line exists only for backwards compatibility; the
  203. # general expectation is that backward_hooks is an empty
  204. # OrderedDict. See Note [Don't serialize hooks]
  205. tensor._backward_hooks = backward_hooks
  206. return tensor
  207. def _rebuild_parameter(data, requires_grad, backward_hooks):
  208. param = torch.nn.Parameter(data, requires_grad)
  209. # NB: This line exists only for backwards compatibility; the
  210. # general expectation is that backward_hooks is an empty
  211. # OrderedDict. See Note [Don't serialize hooks]
  212. param._backward_hooks = backward_hooks
  213. return param
  214. def _import_dotted_name(name):
  215. components = name.split('.')
  216. obj = __import__(components[0])
  217. for component in components[1:]:
  218. obj = getattr(obj, component)
  219. return obj
  220. # Taken from python 3.5 docs
  221. def _accumulate(iterable, fn=lambda x, y: x + y):
  222. 'Return running totals'
  223. # _accumulate([1,2,3,4,5]) --> 1 3 6 10 15
  224. # _accumulate([1,2,3,4,5], operator.mul) --> 1 2 6 24 120
  225. it = iter(iterable)
  226. try:
  227. total = next(it)
  228. except StopIteration:
  229. return
  230. yield total
  231. for element in it:
  232. total = fn(total, element)
  233. yield total
  234. def _flatten_dense_tensors(tensors):
  235. """Flatten dense tensors into a contiguous 1D buffer. Assume tensors are of
  236. same dense type.
  237. Since inputs are dense, the resulting tensor will be a concatenated 1D
  238. buffer. Element-wise operation on this buffer will be equivalent to
  239. operating individually.
  240. Args:
  241. tensors (Iterable[Tensor]): dense tensors to flatten.
  242. Returns:
  243. A contiguous 1D buffer containing input tensors.
  244. """
  245. return torch._C._nn.flatten_dense_tensors(tensors)
  246. def _flatten_sparse_tensors(tensors):
  247. """Flatten sparse tensors into two contiguous 1D buffers, one of indices and
  248. one of values. Assume tensors are of same sparse type.
  249. Args:
  250. tensors (Iterable[Tensor]): sparse tensors to flatten.
  251. Returns:
  252. A tuple of two contiguous 1D buffers, one containing input tensors'
  253. indices and the other containing the values.
  254. """
  255. flat_indices = torch._C._nn.flatten_dense_tensors([torch.Tensor._indices(t) for t in tensors])
  256. flat_values = torch._C._nn.flatten_dense_tensors([torch.Tensor._values(t) for t in tensors])
  257. return flat_indices, flat_values
  258. def _unflatten_dense_tensors(flat, tensors):
  259. """View a flat buffer using the sizes of tensors. Assume that tensors are of
  260. same dense type, and that flat is given by _flatten_dense_tensors.
  261. Args:
  262. flat (Tensor): flattened dense tensors to unflatten.
  263. tensors (Iterable[Tensor]): dense tensors whose sizes will be used to
  264. unflatten flat.
  265. Returns:
  266. Unflattened dense tensors with sizes same as tensors and values from
  267. flat.
  268. """
  269. return torch._C._nn.unflatten_dense_tensors(flat, tensors)
  270. def _unflatten_sparse_tensors(flat, tensors):
  271. """View flat buffer (containing indices and values) using the sizes of
  272. tensors. Assume that tensors are of same sparse type, and that flat is given
  273. by _flatten_sparse_tensors.
  274. Args:
  275. flat (tuple(Tensor, Tensor)): flattened indices and values of sparse
  276. tensors to unflatten.
  277. tensors (Iterable[Tensor]): sparse tensors whose sizes will be used to
  278. unflatten flat.
  279. Returns:
  280. Unflattened sparse tensors with sizes same as tensors and values from
  281. flat.
  282. """
  283. flat_indices, flat_values = flat
  284. indices = torch._C._nn.unflatten_dense_tensors(flat_indices, [torch.Tensor._indices(t) for t in tensors])
  285. values = torch._C._nn.unflatten_dense_tensors(flat_values, [torch.Tensor._values(t) for t in tensors])
  286. outputs = []
  287. for t, i, v in zip(tensors, indices, values):
  288. outputs.append(t.new(i, v, t.size()))
  289. return tuple(outputs)
  290. def _reorder_tensors_as(tensors, ordered_tensors):
  291. """Assume that tensors are of same order as ordered_tensors within their
  292. types, e.g., from _take_tensors. Reorder them to be of same order as
  293. ordered_tensors.
  294. Args:
  295. tensors (Iterable[Tensor]): tensors to be reordered. They should be of
  296. the same order as ordered_tensors within their own types.
  297. ordered_tensors (Iterable[Tensor]): tensors whose order will be the
  298. reference.
  299. Returns:
  300. Ordered tuple of tensors with contents from tensors and order of
  301. ordered_tensors.
  302. """
  303. type_dict = defaultdict(list)
  304. for tensor in tensors:
  305. type_dict[tensor.type()].append(tensor)
  306. type_dict_ = {t: iter(coll) for t, coll in type_dict.items()}
  307. return tuple(next(type_dict_[tensor.type()]) for tensor in ordered_tensors)
  308. def _take_tensors(tensors, size_limit):
  309. """Group tensors into chunks. This generator yields a chunk at each time,
  310. each containing tensors of same type up to certain byte limit in total size.
  311. Args:
  312. tensors (Sequence): A sequence of tensors to be separated into chunks.
  313. size_limit (int): The limit of each chunk in bytes.
  314. Yields:
  315. Blocks of tensors of same type and within size_limit. The yielded
  316. tensors are only ordered as the original sequence within its types.
  317. """
  318. buf_dict: DefaultDict[str, List] = defaultdict(lambda: [[], 0])
  319. for tensor in tensors:
  320. t = tensor.type()
  321. if tensor.is_sparse:
  322. indices = torch.Tensor._indices(tensor)
  323. values = torch.Tensor._values(tensor)
  324. size = indices.numel() * indices.element_size() + values.numel() * values.element_size()
  325. else:
  326. size = tensor.numel() * tensor.element_size()
  327. buf_and_size = buf_dict[t]
  328. if buf_and_size[1] + size > size_limit and buf_and_size[1] > 0:
  329. yield buf_and_size[0]
  330. buf_and_size = buf_dict[t] = [[], 0]
  331. buf_and_size[0].append(tensor)
  332. buf_and_size[1] += size
  333. for buf, _ in buf_dict.values():
  334. if len(buf) > 0:
  335. yield buf
  336. # annotation decorator to get annotations in a way that is compatible
  337. # with both Python 2 and 3
  338. def annotate(ret, **kwargs):
  339. def dec(fun):
  340. fun.__annotations__ = dict(kwargs)
  341. fun.__annotations__['return'] = ret
  342. return fun
  343. return dec
  344. # NOTE [ Python Traceback Reference Cycle Problem ]
  345. #
  346. # When using sys.exc_info(), it is important to **not** store the exc_info[2],
  347. # which is the traceback, because otherwise you will run into the traceback
  348. # reference cycle problem, i.e., the traceback holding reference to the frame,
  349. # and the frame (which holds reference to all the object in its temporary scope)
  350. # holding reference the traceback.
  351. class KeyErrorMessage(str):
  352. r"""str subclass that returns itself in repr"""
  353. def __repr__(self):
  354. return self
  355. class ExceptionWrapper(object):
  356. r"""Wraps an exception plus traceback to communicate across threads"""
  357. def __init__(self, exc_info=None, where="in background"):
  358. # It is important that we don't store exc_info, see
  359. # NOTE [ Python Traceback Reference Cycle Problem ]
  360. if exc_info is None:
  361. exc_info = sys.exc_info()
  362. self.exc_type = exc_info[0]
  363. self.exc_msg = "".join(traceback.format_exception(*exc_info))
  364. self.where = where
  365. def reraise(self):
  366. r"""Reraises the wrapped exception in the current thread"""
  367. # Format a message such as: "Caught ValueError in DataLoader worker
  368. # process 2. Original Traceback:", followed by the traceback.
  369. msg = "Caught {} {}.\nOriginal {}".format(
  370. self.exc_type.__name__, self.where, self.exc_msg)
  371. if self.exc_type == KeyError:
  372. # KeyError calls repr() on its argument (usually a dict key). This
  373. # makes stack traces unreadable. It will not be changed in Python
  374. # (https://bugs.python.org/issue2651), so we work around it.
  375. msg = KeyErrorMessage(msg)
  376. elif getattr(self.exc_type, "message", None):
  377. # Some exceptions have first argument as non-str but explicitly
  378. # have message field
  379. raise self.exc_type(message=msg)
  380. try:
  381. exception = self.exc_type(msg)
  382. except TypeError:
  383. # If the exception takes multiple arguments, don't try to
  384. # instantiate since we don't know how to
  385. raise RuntimeError(msg) from None
  386. raise exception
  387. def _get_available_device_type():
  388. if torch.cuda.is_available():
  389. return "cuda"
  390. # add more available device types here
  391. return None
  392. def _get_device_attr(get_member):
  393. device_type = _get_available_device_type()
  394. if device_type and device_type.lower() == "cuda":
  395. return get_member(torch.cuda)
  396. # add more available device types here
  397. return None
  398. def _get_current_device_index():
  399. # current device index
  400. return _get_device_attr(lambda m: m.current_device())
  401. def _get_all_device_indices():
  402. # all device index
  403. return _get_device_attr(lambda m: list(range(m.device_count())))
  404. def _get_devices_properties(device_ids):
  405. # all device properties
  406. return [_get_device_attr(lambda m: m.get_device_properties(i)) for i in device_ids]
  407. def get_current_device_index() -> int:
  408. r"""Checks if there are CUDA devices available and
  409. returns the device index of the current default CUDA device.
  410. Returns -1 in case there are no CUDA devices available.
  411. Arguments: ``None``
  412. """
  413. if torch.cuda.device_count() > 0:
  414. return torch.cuda.current_device()
  415. return -1
  416. def _get_device_index(device: Any, optional: bool = False, allow_cpu: bool = False) -> int:
  417. r"""Gets the device index from :attr:`device`, which can be a torch.device
  418. object, a Python integer, or ``None``.
  419. If :attr:`device` is a torch.device object, returns the device index if it
  420. has index. Note that for a device without a specified index,
  421. i.e., ``torch.device('xxx')``, this will return the current default
  422. device of that type if :attr:`optional` is ``True``. If :attr:`allow_cpu` is ``True``,
  423. CPU devices will be accepted and ``-1`` will be returned in this case.
  424. If :attr:`device` is a Python integer, it is returned as is.
  425. If :attr:`device` is ``None``, this will return the current default
  426. device of the supported runtime platform if :attr:`optional` is ``True``.
  427. i.e., the current default CUDA device will be returned if CUDA runtime is supported.
  428. """
  429. if isinstance(device, str):
  430. device = torch.device(device)
  431. device_idx: Optional[int] = None
  432. if isinstance(device, torch.device):
  433. if not allow_cpu and device.type == 'cpu':
  434. raise ValueError('Expected a non cpu device, but got: {}'.format(device))
  435. device_idx = -1 if device.type == 'cpu' else device.index
  436. if isinstance(device, int):
  437. device_idx = device
  438. if device_idx is None:
  439. if optional:
  440. # The eager API _get_current_device_index uses `lambda` functions which are
  441. # not supported in JIT and hence not scriptable. The JIT equivalent API to get
  442. # the current device index is `get_current_device_index()` which can
  443. # be scripted. We use is_scripting to check the mode we are in and call the
  444. # appropriate API.
  445. if torch.jit.is_scripting():
  446. device_idx = get_current_device_index()
  447. else:
  448. device_idx = _get_current_device_index()
  449. else:
  450. raise ValueError('Expected a torch.device with a specified index '
  451. 'or an integer, but got:{}'.format(device))
  452. return device_idx
  453. def _handle_complex(tensor):
  454. """
  455. Returns a real view of a tensor if complex dtype else just the tensor
  456. need to check if a UninitializedParameter because otherwise checking is_complex is an error for a LazyModule
  457. """
  458. return torch.view_as_real(tensor) if not isinstance(tensor,
  459. torch.nn.UninitializedParameter) and tensor.is_complex() else tensor
  460. def _element_size(dtype):
  461. """
  462. Returns the element size for a dtype, in bytes
  463. """
  464. if not isinstance(dtype, torch.dtype):
  465. raise RuntimeError(f'expected torch.dtype, but got {type(dtype)}')
  466. if dtype.is_complex:
  467. return torch.finfo(dtype).bits >> 2
  468. elif dtype.is_floating_point:
  469. return torch.finfo(dtype).bits >> 3
  470. elif dtype == torch.bool:
  471. # NOTE: torch.bool is not supported in torch.iinfo()
  472. return 1
  473. else:
  474. return torch.iinfo(dtype).bits >> 3
  475. class _ClassPropertyDescriptor:
  476. def __init__(self, fget, fset=None):
  477. self.fget = fget
  478. def __get__(self, instance, owner=None):
  479. if owner is None:
  480. owner = type(instance)
  481. return self.fget.__get__(instance, owner)()
  482. def classproperty(func):
  483. if not isinstance(func, (classmethod, staticmethod)):
  484. func = classmethod(func)
  485. return _ClassPropertyDescriptor(func)