Skip to content

Commit 748dc52

Browse files
author
逸磊
committed
add callback: mem_snapshot
1 parent 4d724b6 commit 748dc52

3 files changed

Lines changed: 62 additions & 1 deletion

File tree

swift/callbacks/mapping.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .deepspeed_elastic import DeepspeedElasticCallback, GracefulExitCallback
55
from .early_stop import EarlyStopCallback
66
from .lisa import LISACallback
7+
from .mem_snapshot import MemorySnapshotCallback
78
from .perf_log import PerfMetricsLogCallback
89

910
callbacks_map = {
@@ -13,5 +14,6 @@
1314
'early_stop': EarlyStopCallback,
1415
'graceful_exit': GracefulExitCallback,
1516
'lisa': LISACallback,
16-
'perf_log': PerfMetricsLogCallback
17+
'perf_log': PerfMetricsLogCallback,
18+
'mem_snapshot': MemorySnapshotCallback
1719
}

swift/callbacks/mem_snapshot.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from typing import TYPE_CHECKING
2+
import os
3+
import torch
4+
5+
if TYPE_CHECKING:
6+
from .base import Trainer, TrainingArguments
7+
8+
from swift import TrainerCallback, get_logger
9+
10+
logger = get_logger()
11+
12+
13+
class MemorySnapshotCallback(TrainerCallback):
14+
"""
15+
Record CUDA memory history and dump snapshot with specified interval steps.
16+
"""
17+
18+
def __init__(self, args: 'TrainingArguments', trainer: 'Trainer'):
19+
super().__init__(args, trainer)
20+
self.dump_interval = args.mem_snapshot_interval
21+
self.dump_path = args.mem_snapshot_path
22+
self._recording = False
23+
24+
def _dump_and_visualize(self, step: int, tag: str = ''):
25+
rank = int(os.environ.get("RANK", 0))
26+
raw = f'snapshot_step{step}_rank{rank}'
27+
pickle_path = os.path.join(self.dump_path, f'{raw}.pickle')
28+
html_path = os.path.join(self.dump_path, f'{raw}.html')
29+
30+
snapshot = torch.cuda.memory._snapshot()
31+
os.makedirs(os.path.dirname(os.path.abspath(pickle_path)), exist_ok=True)
32+
torch.cuda.memory._dump_snapshot(pickle_path)
33+
logger.info(f"{tag}CUDA memory snapshot dumped: {pickle_path}")
34+
35+
from torch.cuda._memory_viz import trace_plot
36+
html_content = trace_plot(snapshot)
37+
with open(html_path, 'w') as f:
38+
f.write(html_content)
39+
logger.info(f"{tag}CUDA memory html visualization saved: {html_path}")
40+
41+
def on_train_begin(self, args, state, control, **kwargs):
42+
if torch.cuda.is_available():
43+
torch.cuda.memory._record_memory_history(max_entries=100000)
44+
self._recording = True
45+
logger.info("CUDA memory history recording started")
46+
47+
def on_step_end(self, args, state, control, **kwargs):
48+
if self._recording and self.dump_interval and state.global_step % self.dump_interval == 0:
49+
self._dump_and_visualize(state.global_step, tag=f"step:{state.global_step}, ")
50+
51+
def on_train_end(self, args, state, control, **kwargs):
52+
if self._recording:
53+
self._dump_and_visualize(state.global_step, tag="[final] ")
54+
torch.cuda.memory._record_memory_history(enabled=None)
55+
self._recording = False

swift/trainers/arguments.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ class TrainArgumentsMixin:
159159
use_logits_to_keep: Optional[bool] = None
160160
ds3_gather_for_generation: bool = True
161161
resume_only_model: bool = False
162+
mem_snapshot_path: str = None
163+
mem_snapshot_interval: int = None
162164

163165
# plugins
164166
optimizer: Optional[str] = None
@@ -234,6 +236,8 @@ def _init_callbacks(self):
234236
fsdp_config = getattr(self, 'fsdp_config', {})
235237
if isinstance(fsdp_config, dict) and fsdp_config.get('activation_cpu_offload', False):
236238
self.callbacks.append('activation_cpu_offload')
239+
if self.mem_snapshot_path is not None and self.mem_snapshot_interval is not None:
240+
self.callbacks.append('mem_snapshot')
237241

238242
def __post_init__(self):
239243
if hasattr(self, 'output_dir'):

0 commit comments

Comments
 (0)