py_layer.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. # Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import paddle
  15. from paddle.base import core
  16. __all__ = []
  17. def with_metaclass(meta, *bases):
  18. class impl(meta):
  19. def __new__(cls, name, temp_bases, attrs):
  20. return meta(name, bases, attrs)
  21. return type.__new__(impl, "impl", (), {})
  22. class PyLayerContext:
  23. """
  24. ``PyLayerContext`` can assist the :ref:`api_paddle_autograd_PyLayer` in implementing certain functionalities.
  25. Examples:
  26. .. code-block:: python
  27. >>> import paddle
  28. >>> from paddle.autograd import PyLayer
  29. >>> class cus_tanh(PyLayer):
  30. ... @staticmethod
  31. ... def forward(ctx, x):
  32. ... # ctx is a object of PyLayerContext.
  33. ... y = paddle.tanh(x)
  34. ... ctx.save_for_backward(y)
  35. ... return y
  36. ...
  37. ... @staticmethod
  38. ... def backward(ctx, dy):
  39. ... # ctx is a object of PyLayerContext.
  40. ... y, = ctx.saved_tensor()
  41. ... grad = dy * (1 - paddle.square(y))
  42. ... return grad
  43. """
  44. def save_for_backward(self, *tensors):
  45. """
  46. Saves given tensors that backward need. Use ``saved_tensor`` in the `backward` to get the saved tensors.
  47. Note:
  48. This API should be called at most once, and only inside `forward`.
  49. Args:
  50. tensors(list of Tensors): Tensors to be stored.
  51. Returns:
  52. None
  53. Examples:
  54. .. code-block:: python
  55. >>> import paddle
  56. >>> from paddle.autograd import PyLayer
  57. >>> class cus_tanh(PyLayer):
  58. ... @staticmethod
  59. ... def forward(ctx, x):
  60. ... # ctx is a context object that store some objects for backward.
  61. ... y = paddle.tanh(x)
  62. ... # Pass tensors to backward.
  63. ... ctx.save_for_backward(y)
  64. ... return y
  65. ...
  66. ... @staticmethod
  67. ... def backward(ctx, dy):
  68. ... # Get the tensors passed by forward.
  69. ... y, = ctx.saved_tensor()
  70. ... grad = dy * (1 - paddle.square(y))
  71. ... return grad
  72. """
  73. self.container = tensors
  74. def saved_tensor(self):
  75. """
  76. Get the tensors stored by ``save_for_backward``.
  77. Returns:
  78. list of Tensors or None: If context contains tensors stored by `save_for_backward`,
  79. then return these tensors, otherwise return None.
  80. Examples:
  81. .. code-block:: python
  82. >>> import paddle
  83. >>> from paddle.autograd import PyLayer
  84. >>> class cus_tanh(PyLayer):
  85. ... @staticmethod
  86. ... def forward(ctx, x):
  87. ... # ctx is a context object that store some objects for backward.
  88. ... y = paddle.tanh(x)
  89. ... # Pass tensors to backward.
  90. ... ctx.save_for_backward(y)
  91. ... return y
  92. ...
  93. ... @staticmethod
  94. ... def backward(ctx, dy):
  95. ... # Get the tensors passed by forward.
  96. ... y, = ctx.saved_tensor()
  97. ... grad = dy * (1 - paddle.square(y))
  98. ... return grad
  99. """
  100. return self.container
  101. def mark_not_inplace(self, *args):
  102. """
  103. Marks inputs as not inplace.
  104. This should be called at most once, only from inside the `forward` method,
  105. and all arguments should be Tensor inputs.
  106. If the Tensor returned by `forward` method is the same as the Tensor input of forward,
  107. and this Tensor is marked as not_inplace, then Paddle will help the user create a new Tensor as output.
  108. Thereby preventing the auto grad information of the input Tensor from being overwritten.
  109. Examples:
  110. .. code-block:: python
  111. >>> import paddle
  112. >>> class Exp(paddle.autograd.PyLayer):
  113. ... @staticmethod
  114. ... def forward(ctx, x):
  115. ... ctx.mark_not_inplace(x)
  116. ... return x
  117. ...
  118. ... @staticmethod
  119. ... def backward(ctx, grad_output):
  120. ... out = grad_output.exp()
  121. ... return out
  122. >>> paddle.seed(2023)
  123. >>> x = paddle.randn((1, 1))
  124. >>> x.stop_gradient = False
  125. >>> attn_layers = []
  126. >>> for idx in range(0, 2):
  127. ... attn_layers.append(Exp())
  128. >>> for step in range(0, 2):
  129. ... a = x
  130. ... for j in range(0,2):
  131. ... a = attn_layers[j].apply(x)
  132. ... a.backward()
  133. """
  134. self.not_inplace_tensors = args
  135. def mark_non_differentiable(self, *args):
  136. """
  137. Marks outputs as non-differentiable.
  138. This should be called at most once, only from inside the `forward` method,
  139. and all arguments should be tensor outputs.
  140. This will mark outputs as not requiring gradients, increasing the
  141. efficiency of backward computation. You still need to accept a gradient
  142. for each output in `backward`, but it's always going to
  143. be a zero tensor with the same shape as the shape of a corresponding
  144. output.
  145. Examples:
  146. .. code-block:: python
  147. >>> import paddle
  148. >>> from paddle.autograd import PyLayer
  149. >>> import numpy as np
  150. >>> class Tanh(PyLayer):
  151. ... @staticmethod
  152. ... def forward(ctx, x):
  153. ... a = x + x
  154. ... b = x + x + x
  155. ... ctx.mark_non_differentiable(a)
  156. ... return a, b
  157. ...
  158. ... @staticmethod
  159. ... def backward(ctx, grad_a, grad_b):
  160. ... assert np.equal(grad_a.numpy(), paddle.zeros([1]).numpy())
  161. ... assert np.equal(grad_b.numpy(), paddle.ones([1], dtype="float64").numpy())
  162. ... return grad_b
  163. >>> x = paddle.ones([1], dtype="float64")
  164. >>> x.stop_gradient = False
  165. >>> a, b = Tanh.apply(x)
  166. >>> b.sum().backward()
  167. """
  168. self.non_differentiable = args
  169. def set_materialize_grads(self, value: bool):
  170. """
  171. Sets whether to materialize output grad tensors. Default is True.
  172. This should be called only from inside the `forward` method.
  173. If True, undefined output grad tensors will be expanded to tensors full
  174. of zeros prior to calling the `backward` method.
  175. If False, undefined output grad tensors will be None.
  176. Examples:
  177. .. code-block:: python
  178. >>> import paddle
  179. >>> from paddle.autograd import PyLayer
  180. >>> import numpy as np
  181. >>> class Tanh(PyLayer):
  182. ... @staticmethod
  183. ... def forward(ctx, x):
  184. ... return x+x+x, x+x
  185. ...
  186. ... @staticmethod
  187. ... def backward(ctx, grad, grad2):
  188. ... assert np.equal(grad2.numpy(), paddle.zeros([1]).numpy())
  189. ... return grad
  190. >>> class Tanh2(PyLayer):
  191. ... @staticmethod
  192. ... def forward(ctx, x):
  193. ... ctx.set_materialize_grads(False)
  194. ... return x+x+x, x+x
  195. ...
  196. ... @staticmethod
  197. ... def backward(ctx, grad, grad2):
  198. ... assert grad2==None
  199. ... return grad
  200. >>> x = paddle.ones([1], dtype="float64")
  201. >>> x.stop_gradient = False
  202. >>> Tanh.apply(x)[0].backward()
  203. >>> x2 = paddle.ones([1], dtype="float64")
  204. >>> x2.stop_gradient = False
  205. >>> Tanh2.apply(x2)[0].backward()
  206. """
  207. self.materialize_grads = value
  208. class PyLayerBackward(core.eager.PyLayer, PyLayerContext):
  209. def backward(self, *args):
  210. return self._forward_cls.backward(self, *args)
  211. class PyLayerMeta(type):
  212. def __init__(cls, name, bases, attrs):
  213. cls._backward_function = type(
  214. name + '_backward', (PyLayerBackward,), {"_forward_cls": cls}
  215. )
  216. return super().__init__(name, bases, attrs)
  217. class PyLayer(with_metaclass(PyLayerMeta, core.eager.PyLayer, PyLayerContext)):
  218. """
  219. Paddle implements Python custom operators on the PaddlePaddle framework by creating a subclass of
  220. ``PyLayer``, which must comply with the following rules:
  221. 1. The subclass must contain static ``forward`` and ``backward`` functions, with the first argument being
  222. :ref:`api_paddle_autograd_PyLayerContext`. If a returned value in ``backward`` corresponds to a ``Tensor`` that
  223. requires gradients in ``forward``, the returned value must be a ``Tensor``.
  224. 2. Except for the first argument, other arguments of ``backward`` are gradients of the output ``Tensors``
  225. of ``forward``. Therefore, the number of input ``Tensor`` in ``backward`` must be the same as the number
  226. of output ``Tensor`` in ``forward``. If you need to use input ``Tensor`` from ``forward`` in ``backward``,
  227. you can save these ``Tensors`` by inputting them into :ref:`api_paddle_autograd_PyLayerContext`'s
  228. ``save_for_backward`` method and use them in ``backward`` later.
  229. 3. The output of ``backward`` can be ``Tensor`` or ``list/tuple(Tensor)``, which are gradients of the
  230. output ``Tensor`` of ``forward``. Therefore, the number of output ``Tensor`` in ``backward`` is the same
  231. as the number of input ``Tensor`` in ``forward``.
  232. After building the custom operator, apply it by running the ``apply`` method.
  233. Examples:
  234. .. code-block:: python
  235. >>> import paddle
  236. >>> from paddle.autograd import PyLayer
  237. >>> class cus_tanh(PyLayer):
  238. ... @staticmethod
  239. ... def forward(ctx, x):
  240. ... y = paddle.tanh(x)
  241. ... # Pass tensors to backward.
  242. ... ctx.save_for_backward(y)
  243. ... return y
  244. ...
  245. ... @staticmethod
  246. ... def backward(ctx, dy):
  247. ... # Get the tensors passed by forward.
  248. ... y, = ctx.saved_tensor()
  249. ... grad = dy * (1 - paddle.square(y))
  250. ... return grad
  251. >>> paddle.seed(2023)
  252. >>> data = paddle.randn([2, 3], dtype="float64")
  253. >>> data.stop_gradient = False
  254. >>> z = cus_tanh.apply(data)
  255. >>> z.mean().backward()
  256. >>> print(data.grad)
  257. Tensor(shape=[2, 3], dtype=float64, place=Place(cpu), stop_gradient=True,
  258. [[0.16604150, 0.05858341, 0.14051214],
  259. [0.15677770, 0.01564609, 0.02991660]])
  260. """
  261. @staticmethod
  262. def forward(ctx, *args, **kwargs):
  263. """
  264. It is to be overloaded by subclasses. It must accept a object of :ref:`api_paddle_autograd_PyLayerContext` as
  265. the first argument, followed by any number of arguments (tensors or other types).
  266. `None` can not be included in the returned result.
  267. Args:
  268. *args(tuple): input of PyLayer.
  269. **kwargs(dict): input of PyLayer.
  270. Returns:
  271. tensors or other types : output of PyLayer.
  272. Examples:
  273. .. code-block:: python
  274. >>> import paddle
  275. >>> from paddle.autograd import PyLayer
  276. >>> class cus_tanh(PyLayer):
  277. ... @staticmethod
  278. ... def forward(ctx, x):
  279. ... y = paddle.tanh(x)
  280. ... # Pass tensors to backward.
  281. ... ctx.save_for_backward(y)
  282. ... return y
  283. ...
  284. ... @staticmethod
  285. ... def backward(ctx, dy):
  286. ... # Get the tensors passed by forward.
  287. ... y, = ctx.saved_tensor()
  288. ... grad = dy * (1 - paddle.square(y))
  289. ... return grad
  290. """
  291. raise NotImplementedError(
  292. "You must implement the forward function for PyLayer."
  293. )
  294. @staticmethod
  295. def backward(ctx, *args):
  296. """
  297. This is a function to calculate the gradient. It is to be overloaded by subclasses.
  298. It must accept a object of :ref:`api_paddle_autograd_PyLayerContext` as the first
  299. argument, and the rest arguments are the gradient of forward's output tensors.
  300. Output tensors of backward are the gradient of forward's input tensors.
  301. Args:
  302. *args(tuple): The gradient of forward's output tensor(s).
  303. **kwargs(dict): The gradient of forward's output tensor(s).
  304. Returns:
  305. Tensor or list of Tensors: The gradient of forward's input tensor(s).
  306. Examples:
  307. .. code-block:: python
  308. >>> import paddle
  309. >>> from paddle.autograd import PyLayer
  310. >>> class cus_tanh(PyLayer):
  311. ... @staticmethod
  312. ... def forward(ctx, x):
  313. ... y = paddle.tanh(x)
  314. ... # Pass tensors to backward.
  315. ... ctx.save_for_backward(y)
  316. ... return y
  317. ...
  318. ... @staticmethod
  319. ... def backward(ctx, dy):
  320. ... # Get the tensors passed by forward.
  321. ... y, = ctx.saved_tensor()
  322. ... grad = dy * (1 - paddle.square(y))
  323. ... return grad
  324. """
  325. raise NotImplementedError(
  326. "You must implement the backward function for PyLayer."
  327. )
  328. def once_differentiable(backward):
  329. def wrapper(ctx, *args):
  330. with paddle.base.dygraph.no_grad():
  331. outputs = backward(ctx, *args)
  332. return outputs
  333. return wrapper