A friendlier wrapper for performing activation checkpointing. Compared to the PyTorch version, this version: - wraps an nn.Module, so that all subsequent calls will use checkpointing - handles keyword arguments in the forward - handles non-Tensor outputs from the for
(
module: nn.Module,
offload_to_cpu: bool = False,
)
| 273 | |
| 274 | |
| 275 | def checkpoint_wrapper( |
| 276 | module: nn.Module, |
| 277 | offload_to_cpu: bool = False, |
| 278 | ) -> nn.Module: |
| 279 | """ |
| 280 | A friendlier wrapper for performing activation checkpointing. |
| 281 | Compared to the PyTorch version, this version: |
| 282 | - wraps an nn.Module, so that all subsequent calls will use checkpointing |
| 283 | - handles keyword arguments in the forward |
| 284 | - handles non-Tensor outputs from the forward |
| 285 | - supports offloading activations to CPU |
| 286 | Usage:: |
| 287 | checkpointed_module = checkpoint_wrapper(my_module, offload_to_cpu=True) |
| 288 | a, b = checkpointed_module(x, y=3, z=torch.Tensor([1])) |
| 289 | To understand the benefits of checkpointing and the `offload_to_cpu` flag, |
| 290 | let's divide activations into 2 types: inner activations and outer |
| 291 | activations w.r.t. the checkpointed modules. The inner ones are saved |
| 292 | by activation checkpointing, the outer ones are saved by offload_to_cpu. |
| 293 | In terms of GPU memory savings: |
| 294 | - When inner ones are large in size and outer ones are small, |
| 295 | checkpointing helps a lot, offload_to_cpu may help a little. |
| 296 | - When inner ones are small and outer ones are large, |
| 297 | checkpointing helps little, offload_to_cpu helps a lot. |
| 298 | - When both inner and outer are large, both help and the |
| 299 | benefit is additive. |
| 300 | ..Note:: |
| 301 | The first and last layers are not likely to benefit from the `offload_to_cpu` flag |
| 302 | because (1) there are typically other references to the first layer's input, so |
| 303 | the GPU memory won't be freed; (2) the input to the last layer is immediately |
| 304 | used by the backward pass and won't result in memory savings. |
| 305 | Args: |
| 306 | module (nn.Module): |
| 307 | The module to be wrapped |
| 308 | offload_to_cpu (bool): |
| 309 | Whether to offload activations to CPU. |
| 310 | Returns: |
| 311 | (nn.Module): |
| 312 | Wrapped module |
| 313 | """ |
| 314 | # Patch the batchnorm layers in case there are any in this module. |
| 315 | patch_batchnorm(module) |
| 316 | |
| 317 | # The use of weakref here is to prevent creating a ref cycle: m -> m.forward -> m. |
| 318 | # When such cycle exists, gc won't collect the module when the module is freed. |
| 319 | # That causes GPU memory to be leaked. See the unit test for how we catch that. |
| 320 | # |
| 321 | # We prefer this over a class wrapper since the class wrapper would have to |
| 322 | # proxy a lot of fields and methods. |
| 323 | module.forward = functools.partial( # type: ignore |
| 324 | _checkpointed_forward, type(module).forward, weakref.ref(module), offload_to_cpu |
| 325 | ) |
| 326 | return module |
| 327 | |
| 328 | |
| 329 | def _checkpointed_forward( |
no test coverage detected