torch-remat 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- torch_remat-0.2.0/LICENSE +28 -0
- torch_remat-0.2.0/PKG-INFO +196 -0
- torch_remat-0.2.0/README.md +184 -0
- torch_remat-0.2.0/pyproject.toml +34 -0
- torch_remat-0.2.0/setup.cfg +4 -0
- torch_remat-0.2.0/torch_remat/__init__.py +37 -0
- torch_remat-0.2.0/torch_remat/_annotate.py +111 -0
- torch_remat-0.2.0/torch_remat/_api.py +1686 -0
- torch_remat-0.2.0/torch_remat/_compat.py +73 -0
- torch_remat-0.2.0/torch_remat/_oom.py +137 -0
- torch_remat-0.2.0/torch_remat/_placeholder.py +180 -0
- torch_remat-0.2.0/torch_remat/_pytree.py +178 -0
- torch_remat-0.2.0/torch_remat/_recompute_boundary.py +83 -0
- torch_remat-0.2.0/torch_remat/_region.py +355 -0
- torch_remat-0.2.0/torch_remat/_reporting.py +564 -0
- torch_remat-0.2.0/torch_remat/_saved_report.py +505 -0
- torch_remat-0.2.0/torch_remat/_trace.py +192 -0
- torch_remat-0.2.0/torch_remat/_types.py +263 -0
- torch_remat-0.2.0/torch_remat/_view.py +194 -0
- torch_remat-0.2.0/torch_remat.egg-info/PKG-INFO +196 -0
- torch_remat-0.2.0/torch_remat.egg-info/SOURCES.txt +22 -0
- torch_remat-0.2.0/torch_remat.egg-info/dependency_links.txt +1 -0
- torch_remat-0.2.0/torch_remat.egg-info/requires.txt +1 -0
- torch_remat-0.2.0/torch_remat.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
(c) Meta Platforms, Inc. and affiliates.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
6
|
+
are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this list
|
|
9
|
+
of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this
|
|
12
|
+
list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its contributors may
|
|
16
|
+
be used to endorse or promote products derived from this software without specific
|
|
17
|
+
prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
20
|
+
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
21
|
+
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
|
22
|
+
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
23
|
+
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
24
|
+
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
25
|
+
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
26
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
27
|
+
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
|
28
|
+
DAMAGE.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: torch_remat
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Explicit activation rematerialization helpers for PyTorch
|
|
5
|
+
Author: Meta Platforms, Inc.
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: torch>=2.10.0
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+
# torch_remat
|
|
14
|
+
|
|
15
|
+
`torch_remat` is a modern activation checkpointing implementation for PyTorch.
|
|
16
|
+
It takes the classic recipe of non-reentrant `torch.utils.checkpoint` (recompute
|
|
17
|
+
everything before backwards) and enhances it with the ability to mark regions
|
|
18
|
+
inside the recompute region to save their activations for backward, so they
|
|
19
|
+
don't have to be recomputed.
|
|
20
|
+
|
|
21
|
+
Compared with selective activation checkpointing (SAC), `torch_remat` allows you
|
|
22
|
+
to specify save/recompute decisions on regions of source code, instead of writing
|
|
23
|
+
a generic policy function that operates on a per-ATen operator basis. This
|
|
24
|
+
allows for finer-grained recompute policies (e.g., you can easily express that
|
|
25
|
+
one matmul should be saved while another should be recomputed). It's also
|
|
26
|
+
easier to use `torch_remat` with code that has many custom kernels (you don't
|
|
27
|
+
have to custom op'ify them), and the fact that `torch_remat` doesn't use a
|
|
28
|
+
`TorchDispatchMode` means CPU overhead is lower.
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
Put one checkpoint around the unit you want to replay, usually a transformer
|
|
33
|
+
block:
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import torch_remat as remat
|
|
37
|
+
|
|
38
|
+
# torch.utils.checkpoint.checkpoint compatible API:
|
|
39
|
+
# remat.checkpoint(**remat_kwargs)(func)(*args, **kwargs)
|
|
40
|
+
output = remat.checkpoint(region_name="layers.0")(block)(hidden_states)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
(`remat.checkpoint(block)(hidden_states)` is intentionally not supported,
|
|
44
|
+
because this phrasing of the API is ambiguous with `torch.utils.checkpoint`.)
|
|
45
|
+
|
|
46
|
+
Inside the block's `forward` method, annotate calls whose backward activations
|
|
47
|
+
should be saved with `remat.region(..., recompute=False)`, and annotate
|
|
48
|
+
outputs of save regions which will be needed for recompute with
|
|
49
|
+
`remat.recompute_needs_tensor` (you can also omit these and `torch_remat` will
|
|
50
|
+
tell you which ones you need to mark):
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
def forward(self, hidden_states):
|
|
54
|
+
x = self.attention_norm(hidden_states)
|
|
55
|
+
attn = remat.region(
|
|
56
|
+
self.attention,
|
|
57
|
+
"attention",
|
|
58
|
+
recompute=False,
|
|
59
|
+
)(x)
|
|
60
|
+
remat.recompute_needs_tensor(attn)
|
|
61
|
+
hidden_states = hidden_states + attn
|
|
62
|
+
|
|
63
|
+
x = self.ffn_norm(hidden_states)
|
|
64
|
+
moe = remat.region(
|
|
65
|
+
self.moe,
|
|
66
|
+
"moe",
|
|
67
|
+
recompute=False,
|
|
68
|
+
)(x)
|
|
69
|
+
remat.recompute_needs_tensor(moe)
|
|
70
|
+
return hidden_states + moe
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The interaction between recompute and save regions is somewhat subtle;
|
|
74
|
+
check [Mental model](docs/mental_model.md) for more details.
|
|
75
|
+
|
|
76
|
+
## State and side effects
|
|
77
|
+
|
|
78
|
+
Code that is replayed must behave consistently with the original forward, even
|
|
79
|
+
if saved region bodies are skipped during replay. In particular, if you rely
|
|
80
|
+
on mutable state in forwards (e.g., for RNG), you need to ensure you can
|
|
81
|
+
snapshot and restore this state. Code `remat.RecomputeStateHook` and pass it
|
|
82
|
+
with `recompute_state_hooks=`. The hook restores state at checkpoint entry and
|
|
83
|
+
at every non-recomputed function. Here is an example that takes care of
|
|
84
|
+
setting both a custom user RNG counter as well as standard PyTorch RNG state.
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from contextvars import ContextVar
|
|
88
|
+
|
|
89
|
+
rng_counter = ContextVar("rng_counter", default=0)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class CudaRNGStateHook:
|
|
93
|
+
def __init__(self, device):
|
|
94
|
+
self.device = device
|
|
95
|
+
|
|
96
|
+
def snapshot(self):
|
|
97
|
+
return torch.cuda.get_rng_state(self.device), rng_counter.get()
|
|
98
|
+
|
|
99
|
+
def restore(self, state):
|
|
100
|
+
cuda_rng_state, counter = state
|
|
101
|
+
torch.cuda.set_rng_state(cuda_rng_state, self.device)
|
|
102
|
+
rng_counter.set(counter)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
rng_hook = CudaRNGStateHook(hidden_states.device)
|
|
106
|
+
output = remat.checkpoint(
|
|
107
|
+
region_name="layers.0",
|
|
108
|
+
recompute_state_hooks=(rng_hook,),
|
|
109
|
+
)(block)(hidden_states)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
We don't provide a "stock" save/restore hook; in particular, `torch_remat`
|
|
113
|
+
doesn't support the `preserve_rng_state=True` kwarg that
|
|
114
|
+
`torch.utils.checkpoint` supports. The primary reason for this is that
|
|
115
|
+
`preserve_rng_state` is documented to also save CPU RNG state, but in modern
|
|
116
|
+
PyTorch code this is unnecessary (RNG should be sampled on-device) and
|
|
117
|
+
expensive (a 5KB allocation is needed to snapshot the CPU MT19937 state)--and
|
|
118
|
+
unlike `torch.utils.checkpoint`, we will repeatedly save/load RNG state many
|
|
119
|
+
times per a `remat.checkpoint`.
|
|
120
|
+
|
|
121
|
+
Separately, you can check whether other code is being replayed with
|
|
122
|
+
`remat.is_recomputing()`, for example to suppress forward-only logging or
|
|
123
|
+
metrics:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
if not remat.is_recomputing():
|
|
127
|
+
record_metric(value)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
TODO: We should offer a simple way of checking that the replay is bitwise
|
|
131
|
+
equivalent to the original.
|
|
132
|
+
|
|
133
|
+
## Diagnostics
|
|
134
|
+
|
|
135
|
+
`torch_remat` also comes with a number tools for understanding the recompute/save behavior
|
|
136
|
+
and memory usage of your program. Here are some things you can do:
|
|
137
|
+
|
|
138
|
+
**Trace the configured region hierarchy to see what is being saved/recomputed:**
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
with remat.collect_trace() as trace:
|
|
142
|
+
output = model(inputs)
|
|
143
|
+
print(trace.format())
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```text
|
|
147
|
+
torch_remat trace
|
|
148
|
+
scope [test_flag]
|
|
149
|
+
sin: save
|
|
150
|
+
cos: recompute
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Inspect retained activations from inside a checkpoint forward:**
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
if not remat.is_recomputing():
|
|
157
|
+
remat.print_current_memory_report()
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
layers.0: 28 B resident in 2 storage(s)
|
|
162
|
+
layers.0::attn.softmax: 28 B
|
|
163
|
+
12 B lse (3,) float32
|
|
164
|
+
16 B probs (4,) float32
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Inspect all live checkpoint regions plus saves reachable from a loss after
|
|
168
|
+
the full forward:**
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
remat.print_saved_tensors_report(loss)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
```text
|
|
175
|
+
saved for backward: 240 B resident -- 2 region(s) 192 B, outside regions 48 B
|
|
176
|
+
|
|
177
|
+
regions:
|
|
178
|
+
96 B x2 layer.0-1 (2 storages each)
|
|
179
|
+
|
|
180
|
+
outside regions: 48 B in 1 storage
|
|
181
|
+
48 B TanhBackward0 (x1)
|
|
182
|
+
|
|
183
|
+
region detail:
|
|
184
|
+
[x2: layer.0-1]
|
|
185
|
+
layer.0: 96 B resident in 2 storage(s)
|
|
186
|
+
layer.0::sq: 96 B
|
|
187
|
+
48 B y (output at idx 0) (3, 4) float32
|
|
188
|
+
48 B gf (3, 4) float32
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Allocation-site annotations and an attachable CUDA OOM observer are also
|
|
192
|
+
provided. See the [Diagnostics API](docs/api/diagnostics.md) for more details.
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
BSD 3-Clause License. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# torch_remat
|
|
2
|
+
|
|
3
|
+
`torch_remat` is a modern activation checkpointing implementation for PyTorch.
|
|
4
|
+
It takes the classic recipe of non-reentrant `torch.utils.checkpoint` (recompute
|
|
5
|
+
everything before backwards) and enhances it with the ability to mark regions
|
|
6
|
+
inside the recompute region to save their activations for backward, so they
|
|
7
|
+
don't have to be recomputed.
|
|
8
|
+
|
|
9
|
+
Compared with selective activation checkpointing (SAC), `torch_remat` allows you
|
|
10
|
+
to specify save/recompute decisions on regions of source code, instead of writing
|
|
11
|
+
a generic policy function that operates on a per-ATen operator basis. This
|
|
12
|
+
allows for finer-grained recompute policies (e.g., you can easily express that
|
|
13
|
+
one matmul should be saved while another should be recomputed). It's also
|
|
14
|
+
easier to use `torch_remat` with code that has many custom kernels (you don't
|
|
15
|
+
have to custom op'ify them), and the fact that `torch_remat` doesn't use a
|
|
16
|
+
`TorchDispatchMode` means CPU overhead is lower.
|
|
17
|
+
|
|
18
|
+
## Quick start
|
|
19
|
+
|
|
20
|
+
Put one checkpoint around the unit you want to replay, usually a transformer
|
|
21
|
+
block:
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
import torch_remat as remat
|
|
25
|
+
|
|
26
|
+
# torch.utils.checkpoint.checkpoint compatible API:
|
|
27
|
+
# remat.checkpoint(**remat_kwargs)(func)(*args, **kwargs)
|
|
28
|
+
output = remat.checkpoint(region_name="layers.0")(block)(hidden_states)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
(`remat.checkpoint(block)(hidden_states)` is intentionally not supported,
|
|
32
|
+
because this phrasing of the API is ambiguous with `torch.utils.checkpoint`.)
|
|
33
|
+
|
|
34
|
+
Inside the block's `forward` method, annotate calls whose backward activations
|
|
35
|
+
should be saved with `remat.region(..., recompute=False)`, and annotate
|
|
36
|
+
outputs of save regions which will be needed for recompute with
|
|
37
|
+
`remat.recompute_needs_tensor` (you can also omit these and `torch_remat` will
|
|
38
|
+
tell you which ones you need to mark):
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
def forward(self, hidden_states):
|
|
42
|
+
x = self.attention_norm(hidden_states)
|
|
43
|
+
attn = remat.region(
|
|
44
|
+
self.attention,
|
|
45
|
+
"attention",
|
|
46
|
+
recompute=False,
|
|
47
|
+
)(x)
|
|
48
|
+
remat.recompute_needs_tensor(attn)
|
|
49
|
+
hidden_states = hidden_states + attn
|
|
50
|
+
|
|
51
|
+
x = self.ffn_norm(hidden_states)
|
|
52
|
+
moe = remat.region(
|
|
53
|
+
self.moe,
|
|
54
|
+
"moe",
|
|
55
|
+
recompute=False,
|
|
56
|
+
)(x)
|
|
57
|
+
remat.recompute_needs_tensor(moe)
|
|
58
|
+
return hidden_states + moe
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The interaction between recompute and save regions is somewhat subtle;
|
|
62
|
+
check [Mental model](docs/mental_model.md) for more details.
|
|
63
|
+
|
|
64
|
+
## State and side effects
|
|
65
|
+
|
|
66
|
+
Code that is replayed must behave consistently with the original forward, even
|
|
67
|
+
if saved region bodies are skipped during replay. In particular, if you rely
|
|
68
|
+
on mutable state in forwards (e.g., for RNG), you need to ensure you can
|
|
69
|
+
snapshot and restore this state. Code `remat.RecomputeStateHook` and pass it
|
|
70
|
+
with `recompute_state_hooks=`. The hook restores state at checkpoint entry and
|
|
71
|
+
at every non-recomputed function. Here is an example that takes care of
|
|
72
|
+
setting both a custom user RNG counter as well as standard PyTorch RNG state.
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from contextvars import ContextVar
|
|
76
|
+
|
|
77
|
+
rng_counter = ContextVar("rng_counter", default=0)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class CudaRNGStateHook:
|
|
81
|
+
def __init__(self, device):
|
|
82
|
+
self.device = device
|
|
83
|
+
|
|
84
|
+
def snapshot(self):
|
|
85
|
+
return torch.cuda.get_rng_state(self.device), rng_counter.get()
|
|
86
|
+
|
|
87
|
+
def restore(self, state):
|
|
88
|
+
cuda_rng_state, counter = state
|
|
89
|
+
torch.cuda.set_rng_state(cuda_rng_state, self.device)
|
|
90
|
+
rng_counter.set(counter)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
rng_hook = CudaRNGStateHook(hidden_states.device)
|
|
94
|
+
output = remat.checkpoint(
|
|
95
|
+
region_name="layers.0",
|
|
96
|
+
recompute_state_hooks=(rng_hook,),
|
|
97
|
+
)(block)(hidden_states)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
We don't provide a "stock" save/restore hook; in particular, `torch_remat`
|
|
101
|
+
doesn't support the `preserve_rng_state=True` kwarg that
|
|
102
|
+
`torch.utils.checkpoint` supports. The primary reason for this is that
|
|
103
|
+
`preserve_rng_state` is documented to also save CPU RNG state, but in modern
|
|
104
|
+
PyTorch code this is unnecessary (RNG should be sampled on-device) and
|
|
105
|
+
expensive (a 5KB allocation is needed to snapshot the CPU MT19937 state)--and
|
|
106
|
+
unlike `torch.utils.checkpoint`, we will repeatedly save/load RNG state many
|
|
107
|
+
times per a `remat.checkpoint`.
|
|
108
|
+
|
|
109
|
+
Separately, you can check whether other code is being replayed with
|
|
110
|
+
`remat.is_recomputing()`, for example to suppress forward-only logging or
|
|
111
|
+
metrics:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
if not remat.is_recomputing():
|
|
115
|
+
record_metric(value)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
TODO: We should offer a simple way of checking that the replay is bitwise
|
|
119
|
+
equivalent to the original.
|
|
120
|
+
|
|
121
|
+
## Diagnostics
|
|
122
|
+
|
|
123
|
+
`torch_remat` also comes with a number tools for understanding the recompute/save behavior
|
|
124
|
+
and memory usage of your program. Here are some things you can do:
|
|
125
|
+
|
|
126
|
+
**Trace the configured region hierarchy to see what is being saved/recomputed:**
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
with remat.collect_trace() as trace:
|
|
130
|
+
output = model(inputs)
|
|
131
|
+
print(trace.format())
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```text
|
|
135
|
+
torch_remat trace
|
|
136
|
+
scope [test_flag]
|
|
137
|
+
sin: save
|
|
138
|
+
cos: recompute
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Inspect retained activations from inside a checkpoint forward:**
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
if not remat.is_recomputing():
|
|
145
|
+
remat.print_current_memory_report()
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```text
|
|
149
|
+
layers.0: 28 B resident in 2 storage(s)
|
|
150
|
+
layers.0::attn.softmax: 28 B
|
|
151
|
+
12 B lse (3,) float32
|
|
152
|
+
16 B probs (4,) float32
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Inspect all live checkpoint regions plus saves reachable from a loss after
|
|
156
|
+
the full forward:**
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
remat.print_saved_tensors_report(loss)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```text
|
|
163
|
+
saved for backward: 240 B resident -- 2 region(s) 192 B, outside regions 48 B
|
|
164
|
+
|
|
165
|
+
regions:
|
|
166
|
+
96 B x2 layer.0-1 (2 storages each)
|
|
167
|
+
|
|
168
|
+
outside regions: 48 B in 1 storage
|
|
169
|
+
48 B TanhBackward0 (x1)
|
|
170
|
+
|
|
171
|
+
region detail:
|
|
172
|
+
[x2: layer.0-1]
|
|
173
|
+
layer.0: 96 B resident in 2 storage(s)
|
|
174
|
+
layer.0::sq: 96 B
|
|
175
|
+
48 B y (output at idx 0) (3, 4) float32
|
|
176
|
+
48 B gf (3, 4) float32
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Allocation-site annotations and an attachable CUDA OOM observer are also
|
|
180
|
+
provided. See the [Diagnostics API](docs/api/diagnostics.md) for more details.
|
|
181
|
+
|
|
182
|
+
## License
|
|
183
|
+
|
|
184
|
+
BSD 3-Clause License. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "torch_remat"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Explicit activation rematerialization helpers for PyTorch"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "BSD-3-Clause"}
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Meta Platforms, Inc."},
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"torch>=2.10.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.packages.find]
|
|
20
|
+
include = ["torch_remat*"]
|
|
21
|
+
|
|
22
|
+
[tool.ruff]
|
|
23
|
+
line-length = 88
|
|
24
|
+
target-version = "py310"
|
|
25
|
+
|
|
26
|
+
[tool.ruff.lint]
|
|
27
|
+
select = ["E4", "E7", "E9", "F"]
|
|
28
|
+
|
|
29
|
+
[tool.ruff.format]
|
|
30
|
+
quote-style = "double"
|
|
31
|
+
|
|
32
|
+
[tool.pytest.ini_options]
|
|
33
|
+
testpaths = ["tests"]
|
|
34
|
+
python_files = ["*_test.py"]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# This source code is licensed under the BSD-style license found in the
|
|
5
|
+
# LICENSE file in the root directory of this source tree.
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from torch_remat._annotate import ( # noqa: F401
|
|
10
|
+
clear_memory_snapshot_cache,
|
|
11
|
+
memory_snapshot_annotate,
|
|
12
|
+
)
|
|
13
|
+
from torch_remat._api import ( # noqa: F401
|
|
14
|
+
checkpoint,
|
|
15
|
+
current_saved_tensor_info,
|
|
16
|
+
is_placeholder,
|
|
17
|
+
recompute_needs_tensor,
|
|
18
|
+
region,
|
|
19
|
+
save_for_backward,
|
|
20
|
+
saved_tensors_hooks,
|
|
21
|
+
)
|
|
22
|
+
from torch_remat._oom import ( # noqa: F401
|
|
23
|
+
discover_autograd_roots,
|
|
24
|
+
format_oom_saved_tensors_report,
|
|
25
|
+
oom_observer,
|
|
26
|
+
)
|
|
27
|
+
from torch_remat._region import is_recomputing, RecomputeStateHook # noqa: F401
|
|
28
|
+
from torch_remat._reporting import ( # noqa: F401
|
|
29
|
+
format_current_memory_report,
|
|
30
|
+
print_current_memory_report,
|
|
31
|
+
)
|
|
32
|
+
from torch_remat._saved_report import ( # noqa: F401
|
|
33
|
+
format_saved_tensors_report,
|
|
34
|
+
print_saved_tensors_report,
|
|
35
|
+
)
|
|
36
|
+
from torch_remat._trace import collect_trace, trace_scope # noqa: F401
|
|
37
|
+
from torch_remat._types import SavedTensorInfo, SavedTensorKind # noqa: F401
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# This source code is licensed under the BSD-style license found in the
|
|
5
|
+
# LICENSE file in the root directory of this source tree.
|
|
6
|
+
|
|
7
|
+
# pyre-strict
|
|
8
|
+
|
|
9
|
+
"""A default :data:`~torch_remat._reporting.Annotate` backed by the CUDA memory snapshot.
|
|
10
|
+
|
|
11
|
+
The saved-tensors report can tag each storage row with a caller-supplied label. By far the
|
|
12
|
+
most common label is *where the tensor was allocated*, which the CUDA caching allocator
|
|
13
|
+
already records (when memory-history recording is on). This module turns that snapshot into a
|
|
14
|
+
ready-made annotator -- the "which tensor is this" that a positional ``saved.<i>`` row cannot
|
|
15
|
+
give -- so callers don't reinvent the storage-pointer -> allocation-site mapping.
|
|
16
|
+
|
|
17
|
+
The snapshot walk is expensive, so the fetch is memoized (:func:`clear_memory_snapshot_cache`
|
|
18
|
+
resets it) and the built annotator indexes the snapshot once, reusing it across every
|
|
19
|
+
per-tensor lookup -- a whole report costs a single snapshot fetch.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import bisect
|
|
25
|
+
from typing import Any, Mapping
|
|
26
|
+
|
|
27
|
+
import torch
|
|
28
|
+
from torch_remat._reporting import Annotate
|
|
29
|
+
|
|
30
|
+
_snapshot_cache: Mapping[str, Any] | None = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _memory_snapshot(*, refresh: bool = False) -> Mapping[str, Any]:
|
|
34
|
+
"""Return the CUDA allocator memory snapshot, memoized after the first fetch.
|
|
35
|
+
|
|
36
|
+
``torch.cuda.memory._snapshot()`` walks the whole allocator and is expensive, so the
|
|
37
|
+
result is cached. Pass ``refresh=True`` (or call :func:`clear_memory_snapshot_cache`) to
|
|
38
|
+
re-fetch after more allocation. The snapshot carries allocation backtraces only while
|
|
39
|
+
memory-history recording is enabled.
|
|
40
|
+
"""
|
|
41
|
+
global _snapshot_cache
|
|
42
|
+
if refresh or _snapshot_cache is None:
|
|
43
|
+
_snapshot_cache = torch.cuda.memory._snapshot()
|
|
44
|
+
return _snapshot_cache
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def clear_memory_snapshot_cache() -> None:
|
|
48
|
+
"""Drop the memoized snapshot so the next annotator re-fetches (see :func:`_memory_snapshot`)."""
|
|
49
|
+
global _snapshot_cache
|
|
50
|
+
_snapshot_cache = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _alloc_site_from_frames(frames: list[dict[str, Any]] | None) -> str | None:
|
|
54
|
+
"""Pick a short ``file:line name`` label from an allocation backtrace (innermost first).
|
|
55
|
+
|
|
56
|
+
Prefer the innermost frame outside library internals (torch and torch_remat), which is
|
|
57
|
+
usually the model/kernel code that produced the tensor; fall back to the innermost frame
|
|
58
|
+
of any kind.
|
|
59
|
+
"""
|
|
60
|
+
for frame in frames or []:
|
|
61
|
+
filename = frame.get("filename", "")
|
|
62
|
+
if "/torch/" not in filename and "/torch_remat/" not in filename:
|
|
63
|
+
base = filename.rsplit("/", 1)[-1]
|
|
64
|
+
return f"{base}:{frame.get('line')} {frame.get('name', '')}".rstrip()
|
|
65
|
+
if frames:
|
|
66
|
+
frame = frames[0]
|
|
67
|
+
return f"{frame.get('filename', '?').rsplit('/', 1)[-1]}:{frame.get('line')}"
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def memory_snapshot_annotate(snapshot: Mapping[str, Any] | None = None) -> Annotate:
|
|
72
|
+
"""Return an :data:`Annotate` mapping a saved tensor's storage to its allocation site.
|
|
73
|
+
|
|
74
|
+
Each live allocator block carries its address and allocation backtrace, so a saved
|
|
75
|
+
tensor's storage pointer (which falls inside exactly one live block) resolves to the code
|
|
76
|
+
that allocated it. Pass the result as ``annotate=`` to
|
|
77
|
+
:func:`torch_remat.format_saved_tensors_report`.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
snapshot: A CUDA memory snapshot (``torch.cuda.memory._snapshot()``); defaults to the
|
|
81
|
+
cached :func:`_memory_snapshot`. The block index is built once here and reused by
|
|
82
|
+
every per-tensor lookup, so a whole report costs a single snapshot fetch.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
if snapshot is None:
|
|
86
|
+
snapshot = _memory_snapshot()
|
|
87
|
+
|
|
88
|
+
blocks: list[tuple[int, int, str]] = [] # (address, size, site), sorted by address
|
|
89
|
+
for segment in snapshot.get("segments", []):
|
|
90
|
+
for block in segment.get("blocks", []):
|
|
91
|
+
if block.get("state") != "active_allocated":
|
|
92
|
+
continue
|
|
93
|
+
site = _alloc_site_from_frames(block.get("frames"))
|
|
94
|
+
if site is not None:
|
|
95
|
+
blocks.append((block["address"], block["size"], site))
|
|
96
|
+
blocks.sort()
|
|
97
|
+
addrs = [address for address, _, _ in blocks]
|
|
98
|
+
|
|
99
|
+
def annotate(tensor: torch.Tensor) -> str | None:
|
|
100
|
+
try:
|
|
101
|
+
ptr = tensor.untyped_storage().data_ptr()
|
|
102
|
+
except Exception:
|
|
103
|
+
return None
|
|
104
|
+
index = bisect.bisect_right(addrs, ptr) - 1
|
|
105
|
+
if 0 <= index < len(blocks):
|
|
106
|
+
address, size, site = blocks[index]
|
|
107
|
+
if address <= ptr < address + size:
|
|
108
|
+
return site
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
return annotate
|