torch-remat 0.2.0__py3-none-any.whl

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.
@@ -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