parametrize.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867
  1. # mypy: allow-untyped-decorators
  2. # mypy: allow-untyped-defs
  3. import collections
  4. import copyreg
  5. from collections.abc import Sequence
  6. from contextlib import contextmanager
  7. from copy import deepcopy
  8. import torch
  9. from torch import Tensor
  10. from torch.__future__ import get_swap_module_params_on_conversion
  11. from torch.nn.modules.container import Module, ModuleDict, ModuleList
  12. from torch.nn.parameter import Parameter
  13. from torch.utils._python_dispatch import is_traceable_wrapper_subclass
  14. __all__ = [
  15. "cached",
  16. "ParametrizationList",
  17. "register_parametrization",
  18. "is_parametrized",
  19. "remove_parametrizations",
  20. "type_before_parametrizations",
  21. "transfer_parametrizations_and_params",
  22. ]
  23. _cache_enabled = 0
  24. _cache: dict[tuple[int, str], Tensor | None] = {}
  25. @contextmanager
  26. def cached():
  27. r"""Context manager that enables the caching system within parametrizations registered with :func:`register_parametrization`.
  28. The value of the parametrized objects is computed and cached the first time
  29. they are required when this context manager is active. The cached values are
  30. discarded when leaving the context manager.
  31. This is useful when using a parametrized parameter more than once in the forward pass.
  32. An example of this is when parametrizing the recurrent kernel of an RNN or when
  33. sharing weights.
  34. The simplest way to activate the cache is by wrapping the forward pass of the neural network
  35. .. code-block:: python
  36. import torch.nn.utils.parametrize as P
  37. ...
  38. with P.cached():
  39. output = model(inputs)
  40. in training and evaluation. One may also wrap the parts of the modules that use
  41. several times the parametrized tensors. For example, the loop of an RNN with a
  42. parametrized recurrent kernel:
  43. .. code-block:: python
  44. with P.cached():
  45. for x in xs:
  46. out_rnn = self.rnn_cell(x, out_rnn)
  47. """
  48. global _cache
  49. global _cache_enabled
  50. _cache_enabled += 1
  51. try:
  52. yield
  53. finally:
  54. _cache_enabled -= 1
  55. if not _cache_enabled:
  56. _cache = {}
  57. def _register_parameter_or_buffer(module, name, X) -> None:
  58. if isinstance(X, Parameter):
  59. module.register_parameter(name, X)
  60. else:
  61. module.register_buffer(name, X)
  62. def _maybe_set(dest: Tensor, src: Tensor) -> None:
  63. should_swap = (
  64. get_swap_module_params_on_conversion() or is_traceable_wrapper_subclass(dest)
  65. )
  66. if should_swap:
  67. if isinstance(dest, Parameter) and not isinstance(src, Parameter):
  68. src = Parameter(src, requires_grad=dest.requires_grad)
  69. torch.utils.swap_tensors(dest, src)
  70. else:
  71. dest.set_(src) # type: ignore[call-overload]
  72. class ParametrizationList(ModuleList):
  73. r"""A sequential container that holds and manages the original parameters or buffers of a parametrized :class:`torch.nn.Module`.
  74. It is the type of ``module.parametrizations[tensor_name]`` when ``module[tensor_name]``
  75. has been parametrized with :func:`register_parametrization`.
  76. If the first registered parametrization has a ``right_inverse`` that returns one tensor or
  77. does not have a ``right_inverse`` (in which case we assume that ``right_inverse`` is the identity),
  78. it will hold the tensor under the name ``original``.
  79. If it has a ``right_inverse`` that returns more than one tensor, these will be registered as
  80. ``original0``, ``original1``, ...
  81. .. warning::
  82. This class is used internally by :func:`register_parametrization`. It is documented
  83. here for completeness. It shall not be instantiated by the user.
  84. Args:
  85. modules (sequence): sequence of modules representing the parametrizations
  86. original (Parameter or Tensor): parameter or buffer that is parametrized
  87. unsafe (bool): a boolean flag that denotes whether the parametrization
  88. may change the dtype and shape of the tensor. Default: `False`
  89. Warning: the parametrization is not checked for consistency upon registration.
  90. Enable this flag at your own risk.
  91. """
  92. original: Tensor
  93. unsafe: bool
  94. def __init__(
  95. self,
  96. modules: Sequence[Module],
  97. original: Tensor | Parameter,
  98. unsafe: bool = False,
  99. ) -> None:
  100. # We require this because we need to treat differently the first parametrization
  101. # This should never throw, unless this class is used from the outside
  102. if len(modules) == 0:
  103. raise ValueError("ParametrizationList requires one or more modules.")
  104. super().__init__(modules)
  105. self.unsafe = unsafe
  106. # In plain words:
  107. # module.weight must keep its dtype and shape.
  108. # Furthermore, if there is no right_inverse or the right_inverse returns a tensor,
  109. # this should be of the same dtype as the original tensor
  110. #
  111. # We check that the following invariants hold:
  112. # X = module.weight
  113. # Y = param.right_inverse(X)
  114. # assert isinstance(Y, Tensor) or
  115. # (isinstance(Y, collections.abc.Sequence) and all(isinstance(t, Tensor) for t in Y))
  116. # Z = param(Y) if isinstance(Y, Tensor) else param(*Y)
  117. # # Consistency checks
  118. # assert X.dtype == Z.dtype and X.shape == Z.shape
  119. # # If it has one input, this allows to be able to use set_ to be able to
  120. # # move data to/from the original tensor without changing its id (which is what the
  121. # # optimizer uses to track parameters)
  122. # if isinstance(Y, Tensor)
  123. # assert X.dtype == Y.dtype
  124. # Below we use original = X, new = Y
  125. original_shape = original.shape
  126. original_dtype = original.dtype
  127. # Compute new
  128. with torch.no_grad():
  129. new = original
  130. for module in reversed(self): # type: ignore[call-overload]
  131. if hasattr(module, "right_inverse"):
  132. try:
  133. new = module.right_inverse(new) # type: ignore[operator]
  134. except NotImplementedError:
  135. pass
  136. # else, or if it throws, we assume that right_inverse is the identity
  137. if not isinstance(new, Tensor) and not isinstance(new, Sequence):
  138. raise ValueError(
  139. "'right_inverse' must return a Tensor or a Sequence of tensors (list, tuple...). "
  140. f"Got {type(new).__name__}"
  141. )
  142. # Set the number of original tensors
  143. self.is_tensor = isinstance(new, Tensor)
  144. self.ntensors = 1 if self.is_tensor else len(new)
  145. # Register the tensor(s)
  146. if self.is_tensor:
  147. # pyrefly: ignore [missing-attribute]
  148. if original.dtype != new.dtype:
  149. raise ValueError(
  150. "When `right_inverse` outputs one tensor, it may not change the dtype.\n"
  151. f"original.dtype: {original.dtype}\n"
  152. # pyrefly: ignore [missing-attribute]
  153. f"right_inverse(original).dtype: {new.dtype}"
  154. )
  155. # pyrefly: ignore [missing-attribute]
  156. if original.device != new.device:
  157. raise ValueError(
  158. "When `right_inverse` outputs one tensor, it may not change the device.\n"
  159. f"original.device: {original.device}\n"
  160. # pyrefly: ignore [missing-attribute]
  161. f"right_inverse(original).device: {new.device}"
  162. )
  163. # Set the original to original so that the user does not need to re-register the parameter
  164. # manually in the optimiser
  165. with torch.no_grad():
  166. # pyrefly: ignore [bad-argument-type]
  167. _maybe_set(original, new)
  168. _register_parameter_or_buffer(self, "original", original)
  169. else:
  170. for i, originali in enumerate(new):
  171. if not isinstance(originali, Tensor):
  172. raise ValueError(
  173. "'right_inverse' must return a Tensor or a Sequence of tensors "
  174. "(list, tuple...). "
  175. f"Got element {i} of the sequence with type {type(originali).__name__}."
  176. )
  177. # If the original tensor was a Parameter that required grad, we expect the user to
  178. # add the new parameters to the optimizer after registering the parametrization
  179. # (this is documented)
  180. if isinstance(original, Parameter):
  181. originali = Parameter(originali, original.requires_grad)
  182. originali.requires_grad_(original.requires_grad)
  183. _register_parameter_or_buffer(self, f"original{i}", originali)
  184. if not self.unsafe:
  185. # Consistency checks:
  186. # Since f : A -> B, right_inverse : B -> A, Z and original should live in B
  187. # Z = forward(right_inverse(original))
  188. Z = self()
  189. if not isinstance(Z, Tensor):
  190. raise ValueError(
  191. f"A parametrization must return a tensor. Got {type(Z).__name__}."
  192. )
  193. if Z.dtype != original_dtype:
  194. raise ValueError(
  195. "Registering a parametrization may not change the dtype of the tensor, unless `unsafe` flag is enabled.\n"
  196. f"unparametrized dtype: {original_dtype}\n"
  197. f"parametrized dtype: {Z.dtype}"
  198. )
  199. if Z.shape != original_shape:
  200. raise ValueError(
  201. "Registering a parametrization may not change the shape of the tensor, unless `unsafe` flag is enabled.\n"
  202. f"unparametrized shape: {original_shape}\n"
  203. f"parametrized shape: {Z.shape}"
  204. )
  205. def right_inverse(self, value: Tensor) -> None:
  206. r"""Call the ``right_inverse`` methods of the parametrizations in the inverse registration order.
  207. Then, it stores the result in ``self.original`` if ``right_inverse`` outputs one tensor
  208. or in ``self.original0``, ``self.original1``, ... if it outputs several.
  209. Args:
  210. value (Tensor): Value to which initialize the module
  211. """
  212. # All the exceptions in this function should almost never throw.
  213. # They could throw if, for example, right_inverse function returns a different
  214. # dtype when given a different input, which should most likely be caused by a
  215. # bug in the user's code
  216. with torch.no_grad():
  217. # See https://github.com/pytorch/pytorch/issues/53103
  218. for module in reversed(self): # type: ignore[call-overload]
  219. if hasattr(module, "right_inverse"):
  220. value = module.right_inverse(value) # type: ignore[operator]
  221. else:
  222. raise RuntimeError(
  223. f"parametrization {type(module).__name__} does not implement "
  224. "right_inverse."
  225. )
  226. if self.is_tensor:
  227. # These exceptions should only throw when a right_inverse function does not
  228. # return the same dtype for every input, which should most likely be caused by a bug
  229. if not isinstance(value, Tensor):
  230. raise ValueError(
  231. f"`right_inverse` should return a tensor. Got {type(value).__name__}"
  232. )
  233. if value.dtype != self.original.dtype:
  234. raise ValueError(
  235. f"The tensor returned by `right_inverse` has dtype {value.dtype} "
  236. f"while `original` has dtype {self.original.dtype}"
  237. )
  238. # We know that the result is going to have the same dtype
  239. _maybe_set(self.original, value)
  240. else:
  241. if not isinstance(value, collections.abc.Sequence):
  242. raise ValueError(
  243. "'right_inverse' must return a sequence of tensors. "
  244. f"Got {type(value).__name__}."
  245. )
  246. if len(value) != self.ntensors:
  247. raise ValueError(
  248. "'right_inverse' must return a sequence of tensors of length "
  249. f"{self.ntensors}. Got a sequence of length {len(value)}."
  250. )
  251. for i, tensor in enumerate(value):
  252. original_i = getattr(self, f"original{i}")
  253. if not isinstance(tensor, Tensor):
  254. raise ValueError(
  255. f"`right_inverse` must return a sequence of tensors. "
  256. f"Got element {i} of type {type(tensor).__name__}"
  257. )
  258. if original_i.dtype != tensor.dtype:
  259. raise ValueError(
  260. f"Tensor {i} returned by `right_inverse` has dtype {tensor.dtype} "
  261. f"while `original{i}` has dtype {original_i.dtype}"
  262. )
  263. _maybe_set(original_i, tensor)
  264. def forward(self) -> Tensor:
  265. if torch.jit.is_scripting():
  266. raise RuntimeError("Parametrization is not working with scripting.")
  267. # Unpack the originals for the first parametrization
  268. if self.is_tensor:
  269. x = self[0](self.original)
  270. else:
  271. originals = (getattr(self, f"original{i}") for i in range(self.ntensors))
  272. x = self[0](*originals)
  273. # It's not possible to call self[1:] here, so we have to be a bit more cryptic
  274. # Also we want to skip all non-integer keys
  275. curr_idx = 1
  276. while hasattr(self, str(curr_idx)):
  277. x = self[curr_idx](x)
  278. curr_idx += 1
  279. return x
  280. def _inject_new_class(module: Module) -> None:
  281. r"""Set up a module to be parametrized.
  282. This works by substituting the class of the module by a class
  283. that extends it to be able to inject a property
  284. Args:
  285. module (nn.Module): module into which to inject the property
  286. """
  287. cls = module.__class__
  288. def default_deepcopy(self, memo):
  289. # Just emulate a standard deepcopy procedure when __deepcopy__ doesn't exist in the current class.
  290. obj = memo.get(id(self), None)
  291. if obj is not None:
  292. return obj
  293. replica = self.__new__(self.__class__)
  294. memo[id(self)] = replica
  295. replica.__dict__ = deepcopy(self.__dict__, memo)
  296. # Also save all slots if they exist.
  297. slots_to_save = copyreg._slotnames(self.__class__) # type: ignore[attr-defined]
  298. for slot in slots_to_save:
  299. if hasattr(self, slot):
  300. setattr(replica, slot, deepcopy(getattr(self, slot), memo))
  301. return replica
  302. def getstate(self):
  303. raise RuntimeError(
  304. "Serialization of parametrized modules is only "
  305. "supported through state_dict(). See:\n"
  306. "https://pytorch.org/tutorials/beginner/saving_loading_models.html"
  307. "#saving-loading-a-general-checkpoint-for-inference-and-or-resuming-training"
  308. )
  309. dct = {"__getstate__": getstate}
  310. # We don't allow serialization of parametrized modules but should still allow deepcopying.
  311. # Default 'deepcopy' function invokes __deepcopy__ method instead of __getstate__ when it exists.
  312. if not hasattr(cls, "__deepcopy__"):
  313. dct["__deepcopy__"] = default_deepcopy # type: ignore[assignment]
  314. param_cls = type(
  315. f"Parametrized{cls.__name__}",
  316. (cls,),
  317. dct,
  318. )
  319. module.__class__ = param_cls
  320. def _inject_property(module: Module, tensor_name: str) -> None:
  321. r"""Injects a property into module[tensor_name].
  322. It assumes that the class in the module has already been modified from its
  323. original one using _inject_new_class and that the tensor under :attr:`tensor_name`
  324. has already been moved out
  325. Args:
  326. module (nn.Module): module into which to inject the property
  327. tensor_name (str): name of the name of the property to create
  328. """
  329. # We check the precondition.
  330. # This should never fire if register_parametrization is correctly implemented
  331. if hasattr(module, tensor_name):
  332. raise AssertionError(f"Module already has an attribute named '{tensor_name}'")
  333. @torch.jit.unused
  334. def get_cached_parametrization(parametrization) -> Tensor:
  335. global _cache
  336. key = (id(module), tensor_name)
  337. tensor = _cache.get(key)
  338. if tensor is None:
  339. tensor = parametrization()
  340. _cache[key] = tensor
  341. return tensor
  342. def get_parametrized(self) -> Tensor:
  343. if torch.jit.is_scripting():
  344. raise RuntimeError("Parametrization is not working with scripting.")
  345. parametrization = self.parametrizations[tensor_name]
  346. # pyrefly: ignore [redundant-condition]
  347. if _cache_enabled:
  348. if torch.jit.is_scripting():
  349. # Scripting
  350. raise RuntimeError(
  351. "Caching is not implemented for scripting. "
  352. "Either disable caching or avoid scripting."
  353. )
  354. elif torch._C._get_tracing_state() is not None:
  355. # Tracing
  356. raise RuntimeError(
  357. "Cannot trace a model while caching parametrizations."
  358. )
  359. else:
  360. return get_cached_parametrization(parametrization)
  361. else:
  362. # If caching is not active, this function just evaluates the parametrization
  363. return parametrization()
  364. def set_original(self, value: Tensor) -> None:
  365. if torch.jit.is_scripting():
  366. raise RuntimeError("Parametrization is not working with scripting.")
  367. self.parametrizations[tensor_name].right_inverse(value)
  368. setattr(module.__class__, tensor_name, property(get_parametrized, set_original))
  369. def register_parametrization(
  370. module: Module,
  371. tensor_name: str,
  372. parametrization: Module,
  373. *,
  374. unsafe: bool = False,
  375. ) -> Module:
  376. r"""Register a parametrization to a tensor in a module.
  377. Assume that ``tensor_name="weight"`` for simplicity. When accessing ``module.weight``,
  378. the module will return the parametrized version ``parametrization(module.weight)``.
  379. If the original tensor requires a gradient, the backward pass will differentiate
  380. through :attr:`parametrization`, and the optimizer will update the tensor accordingly.
  381. The first time that a module registers a parametrization, this function will add an attribute
  382. ``parametrizations`` to the module of type :class:`~ParametrizationList`.
  383. The list of parametrizations on the tensor ``weight`` will be accessible under
  384. ``module.parametrizations.weight``.
  385. The original tensor will be accessible under
  386. ``module.parametrizations.weight.original``.
  387. Parametrizations may be concatenated by registering several parametrizations
  388. on the same attribute.
  389. The training mode of a registered parametrization is updated on registration
  390. to match the training mode of the host module
  391. Parametrized parameters and buffers have an inbuilt caching system that can be activated
  392. using the context manager :func:`cached`.
  393. A :attr:`parametrization` may optionally implement a method with signature
  394. .. code-block:: python
  395. def right_inverse(self, X: Tensor) -> Union[Tensor, Sequence[Tensor]]
  396. This method is called on the unparametrized tensor when the first parametrization
  397. is registered to compute the initial value of the original tensor.
  398. If this method is not implemented, the original tensor will be just the unparametrized tensor.
  399. If all the parametrizations registered on a tensor implement `right_inverse` it is possible
  400. to initialize a parametrized tensor by assigning to it, as shown in the example below.
  401. It is possible for the first parametrization to depend on several inputs.
  402. This may be implemented returning a tuple of tensors from ``right_inverse``
  403. (see the example implementation of a ``RankOne`` parametrization below).
  404. In this case, the unconstrained tensors are also located under ``module.parametrizations.weight``
  405. with names ``original0``, ``original1``,...
  406. .. note::
  407. If unsafe=False (default) both the forward and right_inverse methods will be called
  408. once to perform a number of consistency checks.
  409. If unsafe=True, then right_inverse will be called if the tensor is not parametrized,
  410. and nothing will be called otherwise.
  411. .. note::
  412. In most situations, ``right_inverse`` will be a function such that
  413. ``forward(right_inverse(X)) == X`` (see
  414. `right inverse <https://en.wikipedia.org/wiki/Inverse_function#Right_inverses>`_).
  415. Sometimes, when the parametrization is not surjective, it may be reasonable
  416. to relax this.
  417. .. warning::
  418. If a parametrization depends on several inputs, :func:`~register_parametrization`
  419. will register a number of new parameters. If such parametrization is registered
  420. after the optimizer is created, these new parameters will need to be added manually
  421. to the optimizer. See :meth:`torch.Optimizer.add_param_group`.
  422. Args:
  423. module (nn.Module): module on which to register the parametrization
  424. tensor_name (str): name of the parameter or buffer on which to register
  425. the parametrization
  426. parametrization (nn.Module): the parametrization to register
  427. Keyword args:
  428. unsafe (bool): a boolean flag that denotes whether the parametrization
  429. may change the dtype and shape of the tensor. Default: `False`
  430. Warning: the parametrization is not checked for consistency upon registration.
  431. Enable this flag at your own risk.
  432. Raises:
  433. ValueError: if the module does not have a parameter or a buffer named :attr:`tensor_name`
  434. Examples:
  435. >>> # xdoctest: +REQUIRES(env:TORCH_DOCTEST_LAPACK)
  436. >>> import torch
  437. >>> import torch.nn as nn
  438. >>> import torch.nn.utils.parametrize as P
  439. >>>
  440. >>> class Symmetric(nn.Module):
  441. >>> def forward(self, X):
  442. >>> return X.triu() + X.triu(1).T # Return a symmetric matrix
  443. >>>
  444. >>> def right_inverse(self, A):
  445. >>> return A.triu()
  446. >>>
  447. >>> m = nn.Linear(5, 5)
  448. >>> P.register_parametrization(m, "weight", Symmetric())
  449. >>> print(torch.allclose(m.weight, m.weight.T)) # m.weight is now symmetric
  450. True
  451. >>> A = torch.rand(5, 5)
  452. >>> A = A + A.T # A is now symmetric
  453. >>> m.weight = A # Initialize the weight to be the symmetric matrix A
  454. >>> print(torch.allclose(m.weight, A))
  455. True
  456. >>> class RankOne(nn.Module):
  457. >>> def forward(self, x, y):
  458. >>> # Form a rank 1 matrix multiplying two vectors
  459. >>> return x.unsqueeze(-1) @ y.unsqueeze(-2)
  460. >>>
  461. >>> def right_inverse(self, Z):
  462. >>> # Project Z onto the rank 1 matrices
  463. >>> U, S, Vh = torch.linalg.svd(Z, full_matrices=False)
  464. >>> # Return rescaled singular vectors
  465. >>> s0_sqrt = S[0].sqrt().unsqueeze(-1)
  466. >>> return U[..., :, 0] * s0_sqrt, Vh[..., 0, :] * s0_sqrt
  467. >>>
  468. >>> linear_rank_one = P.register_parametrization(
  469. ... nn.Linear(4, 4), "weight", RankOne()
  470. ... )
  471. >>> print(torch.linalg.matrix_rank(linear_rank_one.weight).item())
  472. 1
  473. """
  474. parametrization.train(module.training)
  475. if is_parametrized(module, tensor_name):
  476. # Correctness checks.
  477. # If A is the space of tensors with shape and dtype equal to module.weight
  478. # we check that parametrization.forward and parametrization.right_inverse are
  479. # functions from A to A
  480. if not unsafe:
  481. Y = getattr(module, tensor_name)
  482. X = parametrization(Y)
  483. if not isinstance(X, Tensor):
  484. raise ValueError(
  485. f"A parametrization must return a tensor. Got {type(X).__name__}."
  486. )
  487. if X.dtype != Y.dtype:
  488. raise ValueError(
  489. "Registering a parametrization may not change the dtype of the tensor, unless the `unsafe` flag is enabled.\n"
  490. f"module.{tensor_name}.dtype: {Y.dtype}\n"
  491. f"parametrization(module.{tensor_name}).dtype: {X.dtype}"
  492. )
  493. if X.shape != Y.shape:
  494. raise ValueError(
  495. "Registering a parametrization may not change the shape of the tensor, unless the `unsafe` flag is enabled.\n"
  496. f"module.{tensor_name}.shape: {Y.shape}\n"
  497. f"parametrization(module.{tensor_name}).shape: {X.shape}"
  498. )
  499. if hasattr(parametrization, "right_inverse"):
  500. try:
  501. Z = parametrization.right_inverse(X) # type: ignore[operator]
  502. except NotImplementedError:
  503. pass
  504. else:
  505. if not isinstance(Z, Tensor):
  506. raise ValueError(
  507. f"parametrization.right_inverse must return a tensor. Got: {type(Z).__name__}"
  508. )
  509. if Z.dtype != Y.dtype:
  510. raise ValueError(
  511. "The tensor returned by parametrization.right_inverse must have the same dtype "
  512. f"as module.{tensor_name}, unless the `unsafe` flag is enabled.\n"
  513. f"module.{tensor_name}.dtype: {Y.dtype}\n"
  514. f"returned dtype: {Z.dtype}"
  515. )
  516. if Z.shape != Y.shape:
  517. raise ValueError(
  518. "The tensor returned by parametrization.right_inverse must have the same shape "
  519. f"as module.{tensor_name}, unless the `unsafe` flag is enabled.\n"
  520. f"module.{tensor_name}.shape: {Y.shape}\n"
  521. f"returned shape: {Z.shape}"
  522. )
  523. # else right_inverse is assumed to be the identity
  524. # add the new parametrization to the parametrization list
  525. if not isinstance(module.parametrizations, ModuleDict):
  526. raise AssertionError(
  527. f"Expected module.parametrizations to be a ModuleDict, "
  528. f"got {type(module.parametrizations).__name__}"
  529. )
  530. module.parametrizations[tensor_name].append(parametrization) # type: ignore[operator]
  531. # If unsafe was True in previous parametrization, keep it enabled
  532. module.parametrizations[tensor_name].unsafe |= unsafe # type: ignore[index, union-attr, operator]
  533. elif tensor_name in module._buffers or tensor_name in module._parameters:
  534. # Set the parametrization mechanism
  535. # Fetch the original buffer or parameter
  536. original = getattr(module, tensor_name)
  537. # We create this early to check for possible errors
  538. parametrizations = ParametrizationList(
  539. [parametrization], original, unsafe=unsafe
  540. )
  541. # Delete the previous parameter or buffer
  542. delattr(module, tensor_name)
  543. # If this is the first parametrization registered on the module,
  544. # we prepare the module to inject the property
  545. if not is_parametrized(module):
  546. # Change the class
  547. _inject_new_class(module)
  548. # Inject a ``ModuleDict`` into the instance under module.parametrizations
  549. module.parametrizations = ModuleDict()
  550. # Add a property into the class
  551. _inject_property(module, tensor_name)
  552. # Add a ParametrizationList
  553. if not isinstance(module.parametrizations, ModuleDict):
  554. raise AssertionError(
  555. f"Expected module.parametrizations to be a ModuleDict, "
  556. f"got {type(module.parametrizations).__name__}"
  557. )
  558. module.parametrizations[tensor_name] = parametrizations
  559. else:
  560. raise ValueError(
  561. f"Module '{module}' does not have a parameter, a buffer, or a "
  562. f"parametrized element with name '{tensor_name}'"
  563. )
  564. return module
  565. def is_parametrized(module: Module, tensor_name: str | None = None) -> bool:
  566. r"""Determine if a module has a parametrization.
  567. Args:
  568. module (nn.Module): module to query
  569. tensor_name (str, optional): name of the parameter in the module
  570. Default: ``None``
  571. Returns:
  572. ``True`` if :attr:`module` has a parametrization for the parameter named :attr:`tensor_name`,
  573. or if it has any parametrization when :attr:`tensor_name` is ``None``;
  574. otherwise ``False``
  575. """
  576. parametrizations = getattr(module, "parametrizations", None)
  577. if parametrizations is None or not isinstance(parametrizations, ModuleDict):
  578. return False
  579. if tensor_name is None:
  580. # Check that there is at least one parametrized buffer or Parameter
  581. return len(parametrizations) > 0
  582. else:
  583. return tensor_name in parametrizations
  584. def remove_parametrizations(
  585. module: Module,
  586. tensor_name: str,
  587. leave_parametrized: bool = True,
  588. ) -> Module:
  589. r"""Remove the parametrizations on a tensor in a module.
  590. - If ``leave_parametrized=True``, ``module[tensor_name]`` will be set to
  591. its current output. In this case, the parametrization shall not change the ``dtype``
  592. of the tensor.
  593. - If ``leave_parametrized=False``, ``module[tensor_name]`` will be set to
  594. the unparametrised tensor in ``module.parametrizations[tensor_name].original``.
  595. This is only possible when the parametrization depends on just one tensor.
  596. Args:
  597. module (nn.Module): module from which remove the parametrization
  598. tensor_name (str): name of the parametrization to be removed
  599. leave_parametrized (bool, optional): leave the attribute :attr:`tensor_name` parametrized.
  600. Default: ``True``
  601. Returns:
  602. Module: module
  603. Raises:
  604. ValueError: if ``module[tensor_name]`` is not parametrized
  605. ValueError: if ``leave_parametrized=False`` and the parametrization depends on several tensors
  606. """
  607. if not is_parametrized(module, tensor_name):
  608. raise ValueError(
  609. f"Module {module} does not have a parametrization on {tensor_name}"
  610. )
  611. # Fetch the original tensor
  612. if not isinstance(module.parametrizations, ModuleDict):
  613. raise AssertionError(
  614. f"Expected module.parametrizations to be a ModuleDict, "
  615. f"got {type(module.parametrizations).__name__}"
  616. )
  617. parametrizations = module.parametrizations[tensor_name]
  618. if parametrizations.is_tensor:
  619. original = parametrizations.original
  620. if not isinstance(original, torch.Tensor):
  621. raise AssertionError(
  622. f"Expected original to be a Tensor (is_tensor promised us a Tensor), "
  623. f"got {type(original).__name__}"
  624. )
  625. if leave_parametrized:
  626. with torch.no_grad():
  627. t = getattr(module, tensor_name)
  628. # We know they have the same dtype because we have checked this when registering the
  629. # parametrizations. As such, we can use set_
  630. # We do this so that the parameter does not to change the id()
  631. # This way the user does not need to update the optimizer
  632. with torch.no_grad():
  633. if type(original) is torch.Tensor:
  634. _maybe_set(original, t)
  635. else:
  636. try:
  637. _maybe_set(original, t)
  638. except RuntimeError as e:
  639. # TODO: Fix this for tensor subclasses that are parameters:
  640. # RuntimeError: set_storage is not allowed on a Tensor created from .data or .detach().
  641. raise RuntimeError(
  642. "Calling remove_parametrizations() with leave_parametrized=True "
  643. "for a parameter that is an instance of a tensor subclass requires "
  644. "set_() to be implemented correctly for the tensor subclass."
  645. "Alternatively, one can opt into the swap_tensors path"
  646. "Either set leave_parametrized=False or provide a working implementation"
  647. "for set_() in the tensor subclass or set "
  648. "torch.__future__.set_swap_module_params_on_conversion(True)."
  649. ) from e
  650. else:
  651. if leave_parametrized:
  652. # We cannot use no_grad because we need to know whether one or more
  653. # original tensors required grad
  654. t = getattr(module, tensor_name)
  655. # We'll have to trust the user to add it to the optimizer
  656. original = Parameter(t) if t.requires_grad else t
  657. else:
  658. raise ValueError(
  659. "Cannot leave unparametrized (`leave_parametrized=False`) a tensor "
  660. "that is parametrized in terms of a sequence of tensors."
  661. )
  662. # Delete the property that manages the parametrization
  663. delattr(module.__class__, tensor_name)
  664. # Delete the ParametrizationList
  665. del module.parametrizations[tensor_name]
  666. # Restore the parameter / buffer into the main class
  667. _register_parameter_or_buffer(module, tensor_name, original)
  668. # Roll back the parametrized class if no other buffer or parameter
  669. # is currently parametrized in this class
  670. if not is_parametrized(module):
  671. delattr(module, "parametrizations")
  672. # Restore class
  673. orig_cls = module.__class__.__bases__[0]
  674. module.__class__ = orig_cls
  675. return module
  676. def type_before_parametrizations(module: Module) -> type:
  677. r"""Return the module type before parametrizations were applied and if not, then it returns the module type.
  678. Args:
  679. module (nn.Module): module to get type of
  680. """
  681. if is_parametrized(module):
  682. return module.__class__.__bases__[0]
  683. else:
  684. return type(module)
  685. def transfer_parametrizations_and_params(
  686. from_module: Module,
  687. to_module: Module,
  688. tensor_name: str | None = None,
  689. ) -> Module:
  690. r"""Transfer parametrizations and the parameters they parametrize from :attr:`from_module` to :attr:`to_module`.
  691. If :attr:`tensor_name` is specified, only transfers the specified parameter, otherwise
  692. transfers all parametrized parameters. If those parameters do not exist in to_module, it will create them.
  693. Does nothing if from_module is not parametrized.
  694. Args:
  695. from_module (nn.Module): module to transfer from
  696. to_module (nn.Module): module to transfer to
  697. tensor_name (str, optional): parameter to transfer
  698. Returns:
  699. Module: to_module
  700. """
  701. if is_parametrized(from_module):
  702. if not isinstance(from_module.parametrizations, ModuleDict):
  703. raise AssertionError(
  704. f"Expected from_module.parametrizations to be a ModuleDict, "
  705. f"got {type(from_module.parametrizations).__name__}"
  706. )
  707. # get list of all params or the single param to transfer
  708. parameters_to_transfer: list | ModuleDict = (
  709. from_module.parametrizations if tensor_name is None else [tensor_name]
  710. )
  711. if not hasattr(parameters_to_transfer, "__iter__"):
  712. raise AssertionError(
  713. f"Expected parameters_to_transfer to be iterable, "
  714. f"got {type(parameters_to_transfer).__name__}"
  715. )
  716. for parameter_name in parameters_to_transfer:
  717. # initialize the to-be-transferred param in to_module if it doesn't exist already
  718. if not hasattr(to_module, parameter_name):
  719. setattr(
  720. to_module,
  721. parameter_name,
  722. Parameter(getattr(from_module, parameter_name)),
  723. )
  724. # apply the params's parametrizations to to_module
  725. for param_func in from_module.parametrizations[ # type: ignore[attr-defined]
  726. parameter_name
  727. ]:
  728. register_parametrization(to_module, parameter_name, param_func)
  729. if not isinstance(to_module.parametrizations, ModuleDict):
  730. raise AssertionError(
  731. f"Expected to_module.parametrizations to be a ModuleDict, "
  732. f"got {type(to_module.parametrizations).__name__}"
  733. )
  734. # make values match, original values can be stored in either original or
  735. # original0, original1..., need to check both cases
  736. if hasattr(from_module.parametrizations[parameter_name], "original"):
  737. to_module.parametrizations[
  738. parameter_name
  739. ].original = from_module.parametrizations[parameter_name].original
  740. else:
  741. num = 0
  742. orig_num = "original" + str(num)
  743. # loop through each original# until all values have been set
  744. while hasattr(from_module.parametrizations[parameter_name], orig_num):
  745. setattr(
  746. to_module.parametrizations[parameter_name],
  747. orig_num,
  748. getattr(from_module.parametrizations[parameter_name], orig_num),
  749. )
  750. num = num + 1
  751. orig_num = "original" + str(num)
  752. return to_module