fix: keep only latest DeepSpeed checkpoint

pull/791/head
baihaochen 2026-06-03 21:35:47 +08:00
parent 0425c4f622
commit e9c93951cc
4 changed files with 83 additions and 2 deletions

View File

@ -27,6 +27,7 @@ from trainer.trainer_utils import (
add_model_profile_args,
apply_model_profile,
build_lm_config,
assert_no_training_output_collision,
init_model,
is_main_process,
get_cuda_peak_memory_gb,
@ -149,6 +150,7 @@ def parse_args():
parser.add_argument("--data_path", type=str, default="../dataset/sft_t2t_mini.jsonl")
parser.add_argument("--from_weight", default="pretrain", type=str)
parser.add_argument("--from_resume", default=0, type=int, choices=[0, 1])
parser.add_argument("--overwrite_output", default=0, type=int, choices=[0, 1])
parser.add_argument("--use_wandb", action="store_true")
parser.add_argument("--wandb_project", type=str, default="SearchLM-Full-SFT-DS")
parser.add_argument("--use_compile", default=0, type=int, choices=[0, 1])
@ -181,6 +183,14 @@ if __name__ == "__main__":
num_key_value_heads=args.num_key_value_heads,
intermediate_size=args.intermediate_size,
)
assert_no_training_output_collision(
lm_config,
weight=args.save_weight,
save_dir=args.save_dir,
checkpoint_dir="../checkpoints",
from_resume=args.from_resume,
overwrite=args.overwrite_output,
)
model, tokenizer = init_model(lm_config, args.from_weight, device=args.device)
if args.use_compile == 1:

View File

@ -27,6 +27,7 @@ from trainer.trainer_utils import (
add_model_profile_args,
apply_model_profile,
build_lm_config,
assert_no_training_output_collision,
init_model,
is_main_process,
get_cuda_peak_memory_gb,
@ -149,6 +150,7 @@ def parse_args():
parser.add_argument("--data_path", type=str, default="../dataset/pretrain_t2t_mini.jsonl")
parser.add_argument("--from_weight", default="none", type=str)
parser.add_argument("--from_resume", default=0, type=int, choices=[0, 1])
parser.add_argument("--overwrite_output", default=0, type=int, choices=[0, 1])
parser.add_argument("--use_wandb", action="store_true")
parser.add_argument("--wandb_project", type=str, default="SearchLM-Pretrain-DS")
parser.add_argument("--use_compile", default=0, type=int, choices=[0, 1])
@ -181,6 +183,14 @@ if __name__ == "__main__":
num_key_value_heads=args.num_key_value_heads,
intermediate_size=args.intermediate_size,
)
assert_no_training_output_collision(
lm_config,
weight=args.save_weight,
save_dir=args.save_dir,
checkpoint_dir="../checkpoints",
from_resume=args.from_resume,
overwrite=args.overwrite_output,
)
model, tokenizer = init_model(lm_config, args.from_weight, device=args.device)
if args.use_compile == 1:

View File

@ -30,6 +30,7 @@ from trainer.trainer_utils import (
add_model_profile_args,
apply_model_profile,
build_lm_config,
assert_no_training_output_collision,
init_model,
is_main_process,
get_cuda_peak_memory_gb,
@ -253,6 +254,7 @@ def parse_args():
parser.add_argument("--temperature", type=float, default=0.8)
parser.add_argument("--from_weight", default="full_sft_search", type=str)
parser.add_argument("--from_resume", default=0, type=int, choices=[0, 1])
parser.add_argument("--overwrite_output", default=0, type=int, choices=[0, 1])
parser.add_argument("--use_wandb", action="store_true")
parser.add_argument("--wandb_project", type=str, default="SearchShortQA-GRPO-DS")
parser.add_argument("--use_compile", default=0, type=int, choices=[0, 1])
@ -290,6 +292,14 @@ if __name__ == "__main__":
num_key_value_heads=args.num_key_value_heads,
intermediate_size=args.intermediate_size,
)
assert_no_training_output_collision(
lm_config,
weight=args.save_weight,
save_dir=args.save_dir,
checkpoint_dir="../checkpoints",
from_resume=args.from_resume,
overwrite=args.overwrite_output,
)
model, tokenizer = init_model(lm_config, args.from_weight, device=args.device)
if args.use_compile == 1:

View File

@ -8,6 +8,7 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
import json
import random
import math
import shutil
import numpy as np
import torch
import torch.distributed as dist
@ -80,11 +81,16 @@ def unwrap_model(model):
return getattr(raw_model, "_orig_mod", raw_model)
def get_model_weight_path(lm_config, save_dir="../out", weight="model", epoch=None, step=None):
model_suffix = get_model_suffix(lm_config)
step_suffix = f"_epoch{epoch}_step{step}" if epoch is not None and step is not None else ""
return f"{save_dir}/{weight}_{lm_config.hidden_size}{model_suffix}{step_suffix}.pth"
def save_model_weights(lm_config, model, save_dir="../out", weight="model", dtype=torch.float16):
"""保存一份轻量推理权重;DeepSpeed完整训练状态由 save_checkpoint 单独保存。"""
os.makedirs(save_dir, exist_ok=True)
model_suffix = get_model_suffix(lm_config)
ckp_path = f"{save_dir}/{weight}_{lm_config.hidden_size}{model_suffix}.pth"
ckp_path = get_model_weight_path(lm_config, save_dir=save_dir, weight=weight)
raw_model = unwrap_model(model)
state_dict = raw_model.state_dict()
state_dict = {k: v.detach().to(dtype).cpu() for k, v in state_dict.items()}
@ -218,6 +224,48 @@ def _candidate_deepspeed_tags(lm_config, weight, save_dir="../checkpoints"):
return dedup
def _prune_old_deepspeed_step_tags(lm_config, weight, keep_tag, save_dir="../checkpoints"):
if not os.path.exists(save_dir):
return
base_tag = get_deepspeed_tag(lm_config, weight)
prefix = f"{base_tag}_epoch"
for name in os.listdir(save_dir):
if name == keep_tag or not name.startswith(prefix):
continue
path = os.path.join(save_dir, name)
if os.path.isdir(path):
shutil.rmtree(path, ignore_errors=True)
def assert_no_training_output_collision(lm_config, weight, save_dir="../out", checkpoint_dir="../checkpoints", from_resume=0, overwrite=0):
if int(from_resume) == 1 or int(overwrite) == 1:
return
base_tag = get_deepspeed_tag(lm_config, weight)
latest_weight = get_model_weight_path(lm_config, save_dir=save_dir, weight=weight)
latest_file = get_deepspeed_latest_file(lm_config, weight, checkpoint_dir)
existing = []
if os.path.exists(latest_weight):
existing.append(latest_weight)
if os.path.exists(latest_file):
existing.append(latest_file)
if os.path.exists(os.path.join(checkpoint_dir, base_tag)):
existing.append(os.path.join(checkpoint_dir, base_tag))
if os.path.exists(checkpoint_dir):
prefix = f"{base_tag}_epoch"
for name in os.listdir(checkpoint_dir):
if name.startswith(prefix):
existing.append(os.path.join(checkpoint_dir, name))
break
if not existing:
return
raise RuntimeError(
"检测到同名训练产物,已阻止覆盖。"
f" save_weight={weight}, hidden_size={lm_config.hidden_size}, existing={existing}. "
"如果是继续训练,请加 --from_resume 1;如果是新实验,请换一个 --save_weight;"
"如果确认要覆盖/复用旧目录,请显式加 --overwrite_output 1。"
)
def save_deepspeed_checkpoint(model_engine, lm_config, weight, epoch=0, step=0, wandb=None, save_dir="../checkpoints", **kwargs):
"""DeepSpeed checkpoint 必须所有 rank 都调用,不能只在 rank0 保存。"""
os.makedirs(save_dir, exist_ok=True)
@ -237,7 +285,10 @@ def save_deepspeed_checkpoint(model_engine, lm_config, weight, epoch=0, step=0,
model_engine.save_checkpoint(save_dir, tag=tag, client_state=client_state)
if is_main_process():
_write_deepspeed_latest_tag(lm_config, weight, tag, save_dir)
_prune_old_deepspeed_step_tags(lm_config, weight, keep_tag=tag, save_dir=save_dir)
Logger(f"DeepSpeed checkpoint saved: {save_dir}/{tag}")
if dist.is_initialized():
dist.barrier()
return tag