forked from MakazhanAlpamys/Soup
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfsdp.py
More file actions
262 lines (216 loc) · 7.62 KB
/
Copy pathfsdp.py
File metadata and controls
262 lines (216 loc) · 7.62 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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
"""FSDP2 (Fully Sharded Data Parallel) configuration templates.
FSDP2 is PyTorch's native distributed training solution, an alternative to DeepSpeed.
It shards model parameters, gradients, and optimizer states across GPUs with
tighter integration into PyTorch's autograd engine.
FSDP2 advantages over DeepSpeed:
- Native PyTorch (no external dependency)
- Better composability with torch.compile
- Simpler configuration for most use cases
- Built-in mixed precision via torch.amp
Requires: torch >= 2.2.0, accelerate >= 0.27.0
"""
from __future__ import annotations
import copy
# FSDP2 Full Shard: shards params + gradients + optimizer states (like ZeRO-3)
FSDP_FULL_SHARD = {
"fsdp": "full_shard auto_wrap",
"fsdp_config": {
"backward_prefetch": "backward_pre",
"forward_prefetch": True,
"use_orig_params": True,
"limit_all_gathers": True,
"sync_module_states": True,
},
}
# FSDP2 Shard Grad Op: shards gradients + optimizer states only (like ZeRO-2)
FSDP_SHARD_GRAD_OP = {
"fsdp": "shard_grad_op auto_wrap",
"fsdp_config": {
"backward_prefetch": "backward_pre",
"forward_prefetch": True,
"use_orig_params": True,
"limit_all_gathers": True,
"sync_module_states": True,
},
}
# FSDP2 Full Shard with CPU offload (memory-constrained setups)
FSDP_FULL_SHARD_OFFLOAD = {
"fsdp": "full_shard auto_wrap offload",
"fsdp_config": {
"backward_prefetch": "backward_pre",
"forward_prefetch": True,
"use_orig_params": True,
"limit_all_gathers": True,
"sync_module_states": True,
},
}
FSDP_CONFIGS = {
"full_shard": FSDP_FULL_SHARD,
"shard_grad": FSDP_SHARD_GRAD_OP,
"full_offload": FSDP_FULL_SHARD_OFFLOAD,
}
def get_fsdp_config(preset: str) -> dict:
"""Get FSDP config dict by preset name.
Args:
preset: One of 'full_shard', 'shard_grad', 'full_offload'.
Returns:
Deep copy of the FSDP config dict.
Raises:
ValueError: If preset is not recognized.
"""
if preset not in FSDP_CONFIGS:
raise ValueError(
f"Unknown FSDP config: {preset}. "
f"Options: {', '.join(FSDP_CONFIGS.keys())}"
)
return copy.deepcopy(FSDP_CONFIGS[preset])
def get_fsdp_training_args(preset: str) -> dict:
"""Get FSDP kwargs to pass to TrainingArguments.
Args:
preset: FSDP preset name.
Returns:
Dict of kwargs to unpack into TrainingArguments.
"""
config = get_fsdp_config(preset)
return {
"fsdp": config["fsdp"],
"fsdp_config": config["fsdp_config"],
}
def apply_fsdp_training_kwargs(
training_kwargs: dict,
fsdp_config: dict | None,
use_fsdp2_compile: bool,
) -> dict:
"""Mutate and return ``training_kwargs`` with FSDP + optional torch.compile.
Centralizes the "FSDP-block" logic from the trainer wrappers so it can
be unit-tested directly without mocking an entire model load.
Args:
training_kwargs: The dict being built for ``TrainingArguments``.
fsdp_config: Either ``None`` or a dict with keys ``fsdp`` /
``fsdp_config`` (as returned by :func:`get_fsdp_training_args`).
use_fsdp2_compile: Whether ``training.use_fsdp2_compile`` is set.
Returns:
The same ``training_kwargs`` dict (mutated in place).
Raises:
ValueError: If ``fsdp_config`` contains unexpected keys.
"""
if not fsdp_config:
return training_kwargs
allowed = {"fsdp", "fsdp_config"}
unexpected = set(fsdp_config.keys()) - allowed
if unexpected:
raise ValueError(f"Unexpected FSDP config keys: {unexpected}")
training_kwargs.update(fsdp_config)
if use_fsdp2_compile:
training_kwargs["torch_compile"] = True
return training_kwargs
def is_fsdp_available() -> bool:
"""Check if FSDP2 requirements are met (torch >= 2.2, accelerate >= 0.27)."""
try:
import torch
parts = torch.__version__.split(".")[:2]
torch_version = tuple(
int(p.split("+")[0].split("a")[0].split("b")[0].split("rc")[0])
for p in parts
)
if torch_version < (2, 2):
return False
except (ImportError, ValueError):
return False
try:
import accelerate # noqa: F401
return True
except ImportError:
return False
def validate_fsdp2_compile_config(
use_compile: bool,
fsdp_preset: str | None,
backend: str,
device: str,
deepspeed_config: str | None = None,
) -> list[str]:
"""Validate FSDP2 + ``torch.compile`` combination.
Args:
use_compile: Whether ``training.use_fsdp2_compile`` is enabled.
fsdp_preset: FSDP preset name, or None if FSDP is disabled.
backend: Training backend (transformers / unsloth / mlx).
device: Training device (cuda / cpu / mps).
deepspeed_config: DeepSpeed config path, or None. If set together with
``use_compile``, we reject — DeepSpeed owns its own compile path and
combining the two produces a cryptic runtime error.
Returns:
List of error messages. Empty list means valid.
"""
if not use_compile:
return []
errors: list[str] = []
if deepspeed_config:
errors.append(
"use_fsdp2_compile is incompatible with --deepspeed. "
"DeepSpeed owns its own torch.compile integration; "
"mixing the two crashes at runtime. Pick one."
)
if not fsdp_preset:
errors.append(
"use_fsdp2_compile requires FSDP to be enabled. "
"Pass --fsdp full_shard (or shard_grad / full_offload)."
)
if device != "cuda":
errors.append(
f"use_fsdp2_compile requires CUDA GPUs. Current device: {device}."
)
if backend != "transformers":
errors.append(
f"use_fsdp2_compile is only supported with backend=transformers "
f"(got {backend!r}); unsloth bakes its own compile path."
)
if fsdp_preset and not is_fsdp_available():
errors.append(
"use_fsdp2_compile requires torch >= 2.2.0 and accelerate >= 0.27.0. "
"Upgrade with: pip install -U torch accelerate"
)
return errors
def validate_fsdp_config(
fsdp_preset: str | None,
deepspeed_config: str | None,
backend: str,
device: str,
) -> list[str]:
"""Validate FSDP configuration and return error messages.
Args:
fsdp_preset: FSDP preset name, or None if not using FSDP.
deepspeed_config: DeepSpeed config path, or None.
backend: Training backend (transformers/unsloth).
device: Training device (cuda/cpu/mps).
Returns:
List of error messages. Empty list means valid.
"""
errors: list[str] = []
if not fsdp_preset:
return errors
if deepspeed_config:
errors.append(
"Cannot use FSDP and DeepSpeed together. Choose one: "
"--fsdp or --deepspeed."
)
if device != "cuda":
errors.append(
"FSDP requires CUDA GPUs. "
f"Current device: {device}."
)
if backend == "unsloth":
errors.append(
"FSDP is not compatible with the unsloth backend. "
"Use backend: transformers."
)
if not is_fsdp_available():
errors.append(
"FSDP2 requires torch >= 2.2.0 and accelerate >= 0.27.0. "
"Upgrade with: pip install -U torch accelerate"
)
if fsdp_preset not in FSDP_CONFIGS:
errors.append(
f"Unknown FSDP preset: {fsdp_preset}. "
f"Options: {', '.join(FSDP_CONFIGS.keys())}"
)
return errors