forked from MakazhanAlpamys/Soup
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgradient_ckpt.py
More file actions
194 lines (159 loc) · 7.18 KB
/
Copy pathgradient_ckpt.py
File metadata and controls
194 lines (159 loc) · 7.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
"""Gradient checkpointing tiers — selective / medium / full / auto.
Gradient checkpointing trades compute for memory by re-computing activations
during the backward pass instead of storing them. Tiers control *how much*
re-computation happens:
- ``False`` / ``None`` — disabled (no memory savings)
- ``True`` / ``"full"`` — every transformer block (~30% slow, biggest save)
- ``"medium"`` — every other block (balance)
- ``"selective"`` — attention only (~10% slow, modest save)
- ``"auto"`` — pick based on detected VRAM headroom
``resolve_gradient_checkpointing`` returns a kwargs-dict suitable for
``TrainingArguments(**kwargs)``. Granularity (medium / selective) is a separate
concept — query ``resolve_granularity`` for the chosen tier so callers can
install the correct downstream hooks without polluting HF's kwargs surface.
"""
from __future__ import annotations
from typing import Any, Union
TierLike = Union[bool, str, None]
# Heuristic VRAM thresholds (GB) for tier=auto. Tuned for a 7-8B LoRA run at
# bf16 + 4bit quant + max_length≈4k.
# Below 24 GB: full checkpoint (largest memory savings).
# 24-80 GB: medium (every other block) — balance.
# >80 GB: selective (attention only) — minimize slowdown.
AUTO_FULL_THRESHOLD_GB = 24.0
AUTO_SELECTIVE_THRESHOLD_GB = 80.0
def resolve_granularity(
tier: TierLike, gpu_memory_gb: float | None = None,
) -> str | None:
"""Return the granularity string the wrapper should install hooks for.
One of: ``"full"`` | ``"medium"`` | ``"selective"`` | ``None`` (disabled).
``"auto"`` is resolved to full/medium/selective based on ``gpu_memory_gb``.
"""
if not tier:
return None
if tier is True or tier == "full":
return "full"
if tier in ("medium", "selective"):
return tier # type: ignore[return-value]
if tier == "auto":
if gpu_memory_gb is None:
return "full"
if gpu_memory_gb < AUTO_FULL_THRESHOLD_GB:
return "full"
if gpu_memory_gb <= AUTO_SELECTIVE_THRESHOLD_GB:
return "medium"
return "selective"
return None
def resolve_gradient_checkpointing(
tier: TierLike, gpu_memory_gb: float | None = None,
) -> dict[str, Any]:
"""Resolve a gradient_checkpointing setting into TrainingArguments kwargs.
Only returns keys that HuggingFace's ``TrainingArguments`` actually accepts.
Granularity (medium/selective) is not represented here; query
``resolve_granularity`` for that.
Args:
tier: TrainingConfig.gradient_checkpointing value (bool or tier string).
gpu_memory_gb: GPU memory (GB) used by ``"auto"`` tier. If None, falls
back to full checkpointing on auto.
Returns:
Dict of kwargs suitable for ``TrainingArguments(**kwargs)``:
- ``gradient_checkpointing`` (bool)
- ``gradient_checkpointing_kwargs`` (dict)
"""
granularity = resolve_granularity(tier, gpu_memory_gb=gpu_memory_gb)
if granularity is None:
return {}
# All granularities use HF's standard non-reentrant checkpointing at the
# TrainingArguments level. Selective / medium installation happens inside
# the wrapper via torch-level hooks (deferred to v0.28.1 wiring), without
# leaking markers into HF's kwargs surface.
return {
"gradient_checkpointing": True,
"gradient_checkpointing_kwargs": {"use_reentrant": False},
}
def install_selective_hooks(model, granularity: str) -> int:
"""Install selective / medium gradient-checkpoint hooks on transformer
blocks (#44, v0.33.0).
Iterates the model's named modules looking for transformer-block-shaped
children, then wraps their ``forward`` with
``torch.utils.checkpoint.checkpoint`` based on the granularity:
- ``"selective"``: only attention sub-modules (looks for ``self_attn`` /
``attention`` in the module name)
- ``"medium"``: every second transformer block
- ``"full"``: every transformer block (kept for symmetry; HF's native
``gradient_checkpointing`` already handles full — this path is a
manual fallback for backends that don't expose that toggle)
Args:
model: a torch ``nn.Module`` (typically a HuggingFace model).
granularity: one of ``"selective"`` / ``"medium"`` / ``"full"``.
Returns:
Number of modules that received a hook. Zero is a meaningful signal
— caller should fall back to HF's native ``gradient_checkpointing``.
Raises:
ValueError: when ``granularity`` is not recognised.
Notes:
- Pure best-effort; the function never raises on a missing torch
dependency at call site (it imports inside).
- We do NOT undo earlier hooks. The trainer wrapper is expected to
call this once per ``self.model`` instance before training starts.
"""
if granularity not in {"selective", "medium", "full"}:
raise ValueError(
f"granularity must be one of selective/medium/full, "
f"got {granularity!r}"
)
try:
import torch.utils.checkpoint as ckpt_mod
except ImportError:
return 0
layer_index = 0
hooked = 0
def _wrap(module):
original_forward = module.forward
def _checkpointed_forward(*args, **kwargs):
return ckpt_mod.checkpoint(
original_forward, *args, use_reentrant=False, **kwargs,
)
module.forward = _checkpointed_forward
for name, module in model.named_modules():
# Heuristic: HF transformer blocks are named like
# `model.layers.<i>` (LLaMA), `transformer.h.<i>` (GPT-2),
# `model.decoder.layers.<i>` (T5/Bart). We match on a numeric suffix.
parts = name.rsplit(".", 1)
if len(parts) != 2 or not parts[-1].isdigit():
continue
if granularity == "full":
_wrap(module)
hooked += 1
elif granularity == "medium":
if layer_index % 2 == 0:
_wrap(module)
hooked += 1
layer_index += 1
elif granularity == "selective":
# Find children whose name contains attention markers.
for child_name, child in module.named_modules():
lc = child_name.lower()
if "attn" in lc or "attention" in lc:
_wrap(child)
hooked += 1
return hooked
def describe_tier(tier: TierLike, gpu_memory_gb: float | None = None) -> str:
"""Return a short human-readable description of the selected tier."""
if not tier:
return "off"
if tier is True or tier == "full":
return "full (every block)"
if tier == "medium":
return "medium (every other block)"
if tier == "selective":
return "selective (attention only)"
if tier == "auto":
if gpu_memory_gb is None:
return "auto → full (unknown VRAM)"
if gpu_memory_gb < AUTO_FULL_THRESHOLD_GB:
return f"auto → full (VRAM {gpu_memory_gb:.0f}GB)"
if gpu_memory_gb <= AUTO_SELECTIVE_THRESHOLD_GB:
return f"auto → medium (VRAM {gpu_memory_gb:.0f}GB)"
return f"auto → selective (VRAM {gpu_memory_gb:.0f}GB)"
return str(tier)