Skip to content

Commit 332a673

Browse files
Add AutoEP checkpoint resume validation (#1014)
* Add AutoEP checkpoint resume validation Add a Moonlight AutoEP training experiment that compares native and Universal checkpoint resume loss, including the checkpoint/resume support needed by the finetune demo. Signed-off-by: Jin, Youzhi <youzhi.jin@intel.com> * add README Signed-off-by: iLeGend <youzhi.jin@intel.com> --------- Signed-off-by: Jin, Youzhi <youzhi.jin@intel.com> Signed-off-by: iLeGend <youzhi.jin@intel.com> Co-authored-by: Hongwei Chen <33092912+hwchen2017@users.noreply.github.com>
1 parent 16e01fe commit 332a673

3 files changed

Lines changed: 361 additions & 6 deletions

File tree

‎training/deepspeed_finetune_demo/README.md‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,49 @@ With AutoEP, each rank holds a different expert shard. The training script saves
113113

114114
Use `convert_ds_to_hf.py` to merge all shards back into a standard HuggingFace model.
115115

116+
## Validate AutoEP checkpoint resume
117+
118+
`run_autoep_affine_ir_checkpoint_experiment.sh` compares the loss from uninterrupted
119+
training with loss after resuming from both a native DeepSpeed checkpoint and a
120+
Universal checkpoint. It runs each path through step 100, saves a native
121+
checkpoint at step 50, converts that checkpoint to Universal format, and compares
122+
the losses for steps 51-100.
123+
124+
Run the experiment on a Linux host with the required GPUs and dependencies
125+
installed. Set `DEEPSPEED_REPO` to the root of a DeepSpeed checkout that provides
126+
`deepspeed/checkpoint/ds_to_universal.py`. The model and dataset must already be
127+
cached because Transformers and Datasets offline mode are enabled by default:
128+
129+
```bash
130+
cd training/deepspeed_finetune_demo
131+
export DEEPSPEED_REPO=/path/to/DeepSpeed
132+
./run_autoep_affine_ir_checkpoint_experiment.sh
133+
```
134+
135+
The script defaults to 8 GPUs, AutoEP size 8, ZeRO stage 2, the
136+
`moonshotai/Moonlight-16B-A3B` model, and the `tatsu-lab/alpaca` dataset. Override
137+
these settings with environment variables as needed:
138+
139+
```bash
140+
NUM_GPUS=8 AUTOEP_SIZE=8 ZERO_STAGE=2 \
141+
MODEL_NAME=moonshotai/Moonlight-16B-A3B \
142+
DATASET_NAME=tatsu-lab/alpaca \
143+
OUTPUT_ROOT=/path/to/experiment \
144+
./run_autoep_affine_ir_checkpoint_experiment.sh
145+
```
146+
147+
`DEEPSPEED_LAUNCHER` defaults to `ds`; set it to `deepspeed` if that is the
148+
launcher available in your environment. To use GPUs selected by DeepSpeed's
149+
`--include` option, set `GPU_INCLUDE` (for example, `GPU_INCLUDE=localhost:0,1`).
150+
To allow downloads instead of using cached model and dataset files, set
151+
`TRANSFORMERS_OFFLINE=0 HF_DATASETS_OFFLINE=0`.
152+
153+
The output directory contains the run logs, generated DeepSpeed configs,
154+
`loss_comparison.csv` with per-step losses, and `loss_comparison.txt` with the
155+
maximum and mean absolute loss differences from the uninterrupted baseline. A
156+
successful run means both resume paths produced comparable losses; inspect the
157+
logs and differences when diagnosing a mismatch.
158+
116159
## HumanEval results
117160

118161
| Model | HumanEval (base) | HumanEval+ |

‎training/deepspeed_finetune_demo/finetune_llama.py‎

Lines changed: 132 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import argparse
55
from datasets import load_dataset
66
from torch.utils.data import DataLoader, DistributedSampler
7-
from transformers import AutoModelForCausalLM, AutoTokenizer, default_data_collator
7+
from transformers import AutoConfig, AutoModelForCausalLM, AutoTokenizer, default_data_collator
88
from transformers.integrations.deepspeed import HfDeepSpeedConfig
99
import json
1010
import random
@@ -28,6 +28,15 @@ def set_seed(seed):
2828

2929

3030
DATASET_REGISTRY = {
31+
"tatsu-lab/alpaca": {
32+
"split": "train",
33+
"preprocessor": "alpaca",
34+
"field_map": {
35+
"instruction": "instruction",
36+
"input": "input",
37+
"output": "output",
38+
},
39+
},
3140
"sahil2801/CodeAlpaca-20k": {
3241
"split": "train",
3342
"preprocessor": "alpaca",
@@ -251,9 +260,81 @@ def _save_weights(model_engine, tokenizer, output_dir, step, keep_last=2):
251260
print_r(0, f"Saved checkpoint to {ckpt_dir}")
252261

253262

263+
def _save_deepspeed_checkpoint(model_engine, output_dir, step, epoch, step_in_epoch):
264+
"""Save model, optimizer, scheduler, and resume position for exact continuation."""
265+
tag = f"step_{step}"
266+
client_state = {
267+
"global_step": step,
268+
"epoch": epoch,
269+
"step_in_epoch": step_in_epoch,
270+
}
271+
model_engine.save_checkpoint(output_dir, tag=tag, client_state=client_state)
272+
print_r(0, f"Saved DeepSpeed checkpoint to {os.path.join(output_dir, tag)}")
273+
274+
275+
def _load_deepspeed_checkpoint(model_engine, checkpoint_dir, tag):
276+
load_path, client_state = model_engine.load_checkpoint(checkpoint_dir, tag=tag)
277+
if load_path is None:
278+
raise RuntimeError(f"DeepSpeed checkpoint was not loaded: {checkpoint_dir}/{tag}")
279+
return client_state or {}
280+
281+
282+
def _first_tensor(value):
283+
if torch.is_tensor(value):
284+
return value
285+
if isinstance(value, (tuple, list)):
286+
for item in value:
287+
tensor = _first_tensor(item)
288+
if tensor is not None:
289+
return tensor
290+
if isinstance(value, dict):
291+
for item in value.values():
292+
tensor = _first_tensor(item)
293+
if tensor is not None:
294+
return tensor
295+
return None
296+
297+
298+
def _register_nonfinite_hooks(model):
299+
state = {"found": False}
300+
handles = []
301+
302+
def make_hook(name):
303+
def hook(_module, _inputs, output):
304+
if state["found"] or dist.get_rank() != 0:
305+
return
306+
tensor = _first_tensor(output)
307+
if tensor is not None and not torch.isfinite(tensor).all().item():
308+
state["found"] = True
309+
max_value = tensor.float().abs().max().item()
310+
print_r(0, f"First non-finite module output: {name}, shape={tuple(tensor.shape)}, abs_max={max_value}")
311+
return hook
312+
313+
for name, module in model.named_modules():
314+
handles.append(module.register_forward_hook(make_hook(name)))
315+
return handles
316+
317+
318+
def _reset_rotary_embeddings(model):
319+
for module in model.modules():
320+
rotary_emb = getattr(module, "rotary_emb", None)
321+
if rotary_emb is None or not hasattr(rotary_emb, "inv_freq"):
322+
continue
323+
inv_freq = 1.0 / (
324+
rotary_emb.base
325+
** (torch.arange(0, rotary_emb.dim, 2, dtype=torch.float32) / rotary_emb.dim)
326+
)
327+
rotary_emb.register_buffer("inv_freq", inv_freq, persistent=False)
328+
rotary_emb.max_seq_len_cached = None
329+
330+
254331
def main(args):
255332
logging.basicConfig(level=logging.INFO, filename="pytorch_log.txt")
256333
set_seed(args.seed)
334+
# Moonlight's checked-in modeling file imports this legacy helper.
335+
from transformers.utils import import_utils
336+
if not hasattr(import_utils, "is_torch_fx_available"):
337+
import_utils.is_torch_fx_available = lambda: True
257338

258339
# override batch size in ds_config
259340
with open(args.deepspeed_config, "r") as f:
@@ -266,6 +347,10 @@ def main(args):
266347
tokenizer = AutoTokenizer.from_pretrained(args.model_name, trust_remote_code=True)
267348
if tokenizer.pad_token is None:
268349
tokenizer.pad_token = tokenizer.eos_token
350+
model_config = AutoConfig.from_pretrained(args.model_name, trust_remote_code=True)
351+
if (isinstance(model_config.rope_scaling, dict)
352+
and model_config.rope_scaling.get("rope_type") == "default"):
353+
model_config.rope_scaling = None
269354

270355
try:
271356
import flash_attn
@@ -274,10 +359,12 @@ def main(args):
274359
attn_impl = None
275360
model = AutoModelForCausalLM.from_pretrained(
276361
args.model_name,
362+
config=model_config,
277363
torch_dtype=torch.bfloat16,
278364
trust_remote_code=True,
279365
attn_implementation=attn_impl,
280366
)
367+
_reset_rotary_embeddings(model)
281368
model.config.use_cache = False
282369
model.gradient_checkpointing_enable()
283370

@@ -347,6 +434,8 @@ def main(args):
347434

348435
train_sampler = DistributedSampler(
349436
tokenized_train_dataset,
437+
num_replicas=dist.get_world_size(),
438+
rank=dist.get_rank(),
350439
shuffle=True,
351440
seed=args.seed,
352441
)
@@ -360,7 +449,16 @@ def main(args):
360449
)
361450

362451
model_engine.train()
452+
nonfinite_hooks = _register_nonfinite_hooks(model_engine.module) if args.debug_nonfinite else []
363453
global_step = 0
454+
start_epoch = 0
455+
start_step_in_epoch = 0
456+
if args.resume_tag is not None:
457+
client_state = _load_deepspeed_checkpoint(model_engine, args.resume_dir or args.output_dir, args.resume_tag)
458+
global_step = int(client_state.get("global_step", 0))
459+
start_epoch = int(client_state.get("epoch", 0))
460+
start_step_in_epoch = int(client_state.get("step_in_epoch", 0))
461+
print_r(0, f"Resumed checkpoint at global step {global_step}")
364462
total_time = 0
365463
total_count = 0
366464

@@ -388,11 +486,13 @@ def main(args):
388486
wandb.init(project="deepspeed_finetune_demo", name=args.wandb_name)
389487

390488
global_samples = 0
391-
for epoch in range(args.num_train_epochs):
489+
for epoch in range(start_epoch, args.num_train_epochs):
392490
print_r(0, f"Starting epoch {epoch + 1}/{args.num_train_epochs}")
393491
train_dataloader.sampler.set_epoch(epoch)
394492

395493
for step, batch in enumerate(train_dataloader):
494+
if epoch == start_epoch and step < start_step_in_epoch:
495+
continue
396496
if prof != None and global_step == args.profile_start:
397497
prof.start()
398498
if prof != None and global_step - args.profile_start == args.profile_steps:
@@ -409,9 +509,18 @@ def main(args):
409509
batch = {k: v.to(model_engine.device) for k, v in batch.items()}
410510
outputs = model_engine(**batch)
411511
loss = outputs.loss
512+
if global_step == 0 and dist.get_rank() == 0:
513+
valid_labels = int((batch["labels"][:, :-1] != -100).sum().item())
514+
finite_params = all(torch.isfinite(param).all().item() for param in model_engine.module.parameters())
515+
finite_logits = torch.isfinite(outputs.logits).all().item()
516+
logits_max = outputs.logits.float().abs().max().item()
517+
print_r(0, f"First batch valid shifted labels: {valid_labels}, finite params: {finite_params}, "
518+
f"finite logits: {finite_logits}, logits abs max: {logits_max:.4g}, "
519+
f"finite loss: {torch.isfinite(loss).item()}")
412520

413521
model_engine.backward(loss)
414522
model_engine.step()
523+
global_step += 1
415524
global_samples += model_engine.train_batch_size()
416525

417526
step_time = time.time() - step_start_time
@@ -460,8 +569,9 @@ def main(args):
460569
and global_step % args.checkpoint_steps == 0
461570
and save_checkpoint_p
462571
):
463-
_save_weights(model_engine, tokenizer, args.output_dir, global_step)
464-
global_step += 1
572+
if not args.skip_weight_export:
573+
_save_weights(model_engine, tokenizer, args.output_dir, global_step)
574+
_save_deepspeed_checkpoint(model_engine, args.output_dir, global_step, epoch, step + 1)
465575
if prof != None:
466576
prof.step()
467577
if args.max_steps > 0 and global_step >= args.max_steps:
@@ -473,11 +583,16 @@ def main(args):
473583
if args.max_steps > 0 and global_step >= args.max_steps:
474584
break
475585

586+
for handle in nonfinite_hooks:
587+
handle.remove()
588+
476589
if args.bench_start >= 0 and args.bench_steps > 0:
477590
print_r(0, f"Average iteration time = {total_time / total_count}")
478591

479-
if save_checkpoint_p:
480-
_save_weights(model_engine, tokenizer, args.output_dir, global_step)
592+
if save_checkpoint_p and args.save_final_checkpoint:
593+
if not args.skip_weight_export:
594+
_save_weights(model_engine, tokenizer, args.output_dir, global_step)
595+
_save_deepspeed_checkpoint(model_engine, args.output_dir, global_step, epoch, step + 1)
481596

482597
print_r(0, "Training complete!")
483598

@@ -520,6 +635,17 @@ def main(args):
520635
"--checkpoint_steps", type=int, default=0,
521636
help="Save a checkpoint every N steps (0 disables); keeps last 2",
522637
)
638+
parser.add_argument(
639+
"--resume_tag", type=str, default=None,
640+
help="DeepSpeed checkpoint tag to resume from",
641+
)
642+
parser.add_argument(
643+
"--resume_dir", type=str, default=None,
644+
help="Directory containing resume_tag (defaults to output_dir)",
645+
)
646+
parser.add_argument("--skip_weight_export", action="store_true")
647+
parser.add_argument("--save_final_checkpoint", action="store_true")
648+
parser.add_argument("--debug_nonfinite", action="store_true")
523649
parser.add_argument(
524650
"--eval_batch_size", type=int, default=4, help="Eval batch size per rank"
525651
)

0 commit comments

Comments
 (0)