初始化项目,由ModelHub XC社区提供模型
Model: ayh015/myLightningOPD Source: Original Platform
This commit is contained in:
252
slime/backends/fsdp_utils/checkpoint.py
Normal file
252
slime/backends/fsdp_utils/checkpoint.py
Normal file
@@ -0,0 +1,252 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
import torch.distributed.checkpoint as dcp
|
||||
from torch.distributed.checkpoint.state_dict import get_state_dict, set_state_dict
|
||||
from torch.distributed.checkpoint.stateful import Stateful
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ModelState(Stateful):
|
||||
"""Wrapper for model state only."""
|
||||
|
||||
def __init__(self, model):
|
||||
self.model = model
|
||||
|
||||
def state_dict(self):
|
||||
model_state_dict, _ = get_state_dict(self.model, optimizers=[])
|
||||
return {"model": model_state_dict}
|
||||
|
||||
def load_state_dict(self, state_dict):
|
||||
set_state_dict(self.model, optimizers=[], model_state_dict=state_dict["model"], optim_state_dict=None)
|
||||
|
||||
|
||||
class OptimizerState(Stateful):
|
||||
"""Wrapper for optimizer state only."""
|
||||
|
||||
def __init__(self, model, optimizer):
|
||||
self.model = model
|
||||
self.optimizer = optimizer
|
||||
|
||||
def state_dict(self):
|
||||
_, optimizer_state_dict = get_state_dict(self.model, optimizers=self.optimizer)
|
||||
return {"optim": optimizer_state_dict}
|
||||
|
||||
def load_state_dict(self, state_dict):
|
||||
set_state_dict(
|
||||
self.model, optimizers=self.optimizer, model_state_dict=None, optim_state_dict=state_dict["optim"]
|
||||
)
|
||||
|
||||
|
||||
class LRSchedulerState(Stateful):
|
||||
"""Wrapper for LR scheduler state only."""
|
||||
|
||||
def __init__(self, lr_scheduler):
|
||||
self.lr_scheduler = lr_scheduler
|
||||
|
||||
def state_dict(self):
|
||||
return {"lr_scheduler": self.lr_scheduler.state_dict()}
|
||||
|
||||
def load_state_dict(self, state_dict):
|
||||
self.lr_scheduler.load_state_dict(state_dict["lr_scheduler"])
|
||||
|
||||
|
||||
def _read_checkpoint_metadata(path: Path) -> dict[str, Any]:
|
||||
if not path.exists():
|
||||
return {}
|
||||
try:
|
||||
return json.loads(path.read_text())
|
||||
except json.JSONDecodeError:
|
||||
logger.warning(f"Failed to parse checkpoint metadata at {path}")
|
||||
return {}
|
||||
|
||||
|
||||
def _write_checkpoint_metadata(path: Path, metadata: dict[str, Any]) -> None:
|
||||
tmp_path = path.with_suffix(path.suffix + ".tmp")
|
||||
tmp_path.write_text(json.dumps(metadata, indent=2, sort_keys=True))
|
||||
tmp_path.replace(path)
|
||||
|
||||
|
||||
def load(actor: Any) -> dict[str, Any] | None:
|
||||
"""Load checkpoint from disk.
|
||||
|
||||
Loads model weights and optionally optimizer state from separate directories.
|
||||
This allows loading weights without optimizer or deleting optimizer before loading.
|
||||
"""
|
||||
load_root = getattr(actor.args, "load", None)
|
||||
if load_root is None:
|
||||
return None
|
||||
|
||||
root_path = Path(load_root).expanduser()
|
||||
if not root_path.exists():
|
||||
logger.info(f"[FSDP] Checkpoint directory {root_path} not found; skipping load.")
|
||||
return None
|
||||
|
||||
target_step = getattr(actor.args, "ckpt_step", None)
|
||||
if target_step is None:
|
||||
tracker_file = root_path / "latest_checkpointed_iteration.txt"
|
||||
if not tracker_file.exists():
|
||||
logger.info(f"[FSDP] No tracker file at {tracker_file}; skipping load.")
|
||||
return None
|
||||
tracker_text = tracker_file.read_text().strip()
|
||||
target_step = int(tracker_text)
|
||||
|
||||
checkpoint_dir = root_path / f"iter_{target_step:07d}"
|
||||
model_dir = checkpoint_dir / "model"
|
||||
optimizer_dir = checkpoint_dir / "optimizer"
|
||||
lr_scheduler_dir = checkpoint_dir / "lr_scheduler"
|
||||
|
||||
if not model_dir.exists():
|
||||
logger.info(f"[FSDP] Model checkpoint {model_dir} not found; skipping load.")
|
||||
return None
|
||||
|
||||
# Load model weights (always)
|
||||
model_state = ModelState(actor.model)
|
||||
state_dict = {"model_state": model_state}
|
||||
|
||||
try:
|
||||
dcp.load(state_dict=state_dict, checkpoint_id=str(model_dir))
|
||||
logger.info(f"[FSDP] Loaded model from {model_dir}")
|
||||
except Exception as e:
|
||||
logger.error(f"[FSDP] Failed to load model from {model_dir}: {e}")
|
||||
return None
|
||||
|
||||
# Load optimizer state (optional)
|
||||
load_optimizer = not getattr(actor.args, "no_load_optim", False) and hasattr(actor, "optimizer")
|
||||
if load_optimizer and optimizer_dir.exists():
|
||||
optimizer_state = OptimizerState(actor.model, actor.optimizer)
|
||||
optim_state_dict = {"optim_state": optimizer_state}
|
||||
try:
|
||||
dcp.load(state_dict=optim_state_dict, checkpoint_id=str(optimizer_dir))
|
||||
logger.info(f"[FSDP] Loaded optimizer from {optimizer_dir}")
|
||||
except Exception as e:
|
||||
logger.warning(f"[FSDP] Failed to load optimizer from {optimizer_dir}: {e}")
|
||||
elif load_optimizer:
|
||||
logger.info(f"[FSDP] Optimizer checkpoint not found at {optimizer_dir}, skipping optimizer load.")
|
||||
|
||||
# Load LR scheduler state (optional)
|
||||
load_lr_scheduler = hasattr(actor, "lr_scheduler") and lr_scheduler_dir.exists()
|
||||
if load_lr_scheduler:
|
||||
lr_scheduler_state = LRSchedulerState(actor.lr_scheduler)
|
||||
lr_scheduler_state_dict = {"lr_scheduler_state": lr_scheduler_state}
|
||||
try:
|
||||
dcp.load(state_dict=lr_scheduler_state_dict, checkpoint_id=str(lr_scheduler_dir))
|
||||
logger.info(f"[FSDP] Loaded LR scheduler from {lr_scheduler_dir}")
|
||||
except Exception as e:
|
||||
logger.warning(f"[FSDP] Failed to load LR scheduler from {lr_scheduler_dir}: {e}")
|
||||
elif hasattr(actor, "lr_scheduler"):
|
||||
logger.info(f"[FSDP] LR scheduler checkpoint not found at {lr_scheduler_dir}, skipping LR scheduler load.")
|
||||
|
||||
rng_state = None
|
||||
rng_path = checkpoint_dir / "rng.pt"
|
||||
if rng_path.exists():
|
||||
rng_state = torch.load(rng_path, map_location="cpu")
|
||||
|
||||
metadata = _read_checkpoint_metadata(checkpoint_dir / "meta.json")
|
||||
|
||||
return {
|
||||
"rng": rng_state,
|
||||
"metadata": metadata,
|
||||
"iteration": target_step,
|
||||
}
|
||||
|
||||
|
||||
def finalize_load(actor: Any, checkpoint_payload: dict[str, Any] | None) -> None:
|
||||
if checkpoint_payload is None:
|
||||
dist.barrier()
|
||||
return
|
||||
|
||||
if checkpoint_payload.get("rng") is not None and not getattr(actor.args, "no_load_rng", False):
|
||||
rng_state = checkpoint_payload["rng"]
|
||||
if "torch" in rng_state:
|
||||
torch.set_rng_state(rng_state["torch"])
|
||||
if torch.cuda.is_available() and "cuda" in rng_state:
|
||||
torch.cuda.set_rng_state_all(rng_state["cuda"])
|
||||
|
||||
metadata = checkpoint_payload.get("metadata") or {}
|
||||
iteration = checkpoint_payload.get("iteration")
|
||||
if metadata:
|
||||
actor.global_step = int(metadata.get("global_step", actor.global_step))
|
||||
actor.micro_step = int(metadata.get("micro_step", actor.micro_step))
|
||||
next_rollout = metadata.get("next_rollout_id")
|
||||
if next_rollout is not None:
|
||||
actor.args.start_rollout_id = next_rollout
|
||||
elif iteration is not None:
|
||||
if getattr(actor.args, "start_rollout_id", None) is None:
|
||||
actor.args.start_rollout_id = iteration
|
||||
|
||||
torch.cuda.synchronize()
|
||||
dist.barrier()
|
||||
|
||||
|
||||
def save(actor: Any, iteration: int) -> None:
|
||||
"""Save checkpoint to disk.
|
||||
|
||||
Saves model weights and optimizer state to separate directories.
|
||||
This allows loading weights without optimizer or deleting optimizer before loading.
|
||||
"""
|
||||
torch.cuda.synchronize()
|
||||
|
||||
base_dir = Path(actor.args.save).expanduser()
|
||||
step_id = iteration + 1
|
||||
checkpoint_dir = base_dir / f"iter_{step_id:07d}"
|
||||
model_dir = checkpoint_dir / "model"
|
||||
optimizer_dir = checkpoint_dir / "optimizer"
|
||||
lr_scheduler_dir = checkpoint_dir / "lr_scheduler"
|
||||
|
||||
if dist.get_rank() == 0:
|
||||
checkpoint_dir.mkdir(parents=True, exist_ok=True)
|
||||
model_dir.mkdir(parents=True, exist_ok=True)
|
||||
optimizer_dir.mkdir(parents=True, exist_ok=True)
|
||||
lr_scheduler_dir.mkdir(parents=True, exist_ok=True)
|
||||
dist.barrier()
|
||||
|
||||
# Save model weights
|
||||
model_state = ModelState(actor.model)
|
||||
state_dict = {"model_state": model_state}
|
||||
dcp.save(state_dict, checkpoint_id=str(model_dir))
|
||||
|
||||
# Save optimizer state
|
||||
if hasattr(actor, "optimizer") and actor.optimizer is not None:
|
||||
optimizer_state = OptimizerState(actor.model, actor.optimizer)
|
||||
optim_state_dict = {"optim_state": optimizer_state}
|
||||
dcp.save(optim_state_dict, checkpoint_id=str(optimizer_dir))
|
||||
|
||||
# Save LR scheduler state
|
||||
if hasattr(actor, "lr_scheduler") and actor.lr_scheduler is not None:
|
||||
lr_scheduler_state = LRSchedulerState(actor.lr_scheduler)
|
||||
lr_scheduler_state_dict = {"lr_scheduler_state": lr_scheduler_state}
|
||||
dcp.save(lr_scheduler_state_dict, checkpoint_id=str(lr_scheduler_dir))
|
||||
|
||||
if dist.get_rank() == 0:
|
||||
rng_state = {"torch": torch.get_rng_state()}
|
||||
rng_state["cuda"] = torch.cuda.get_rng_state_all()
|
||||
torch.save(rng_state, checkpoint_dir / "rng.pt")
|
||||
|
||||
metadata = {
|
||||
"iteration": step_id,
|
||||
"rollout_id": iteration,
|
||||
"next_rollout_id": iteration + 1,
|
||||
"global_step": actor.global_step,
|
||||
"micro_step": actor.micro_step,
|
||||
"world_size": dist.get_world_size(),
|
||||
"timestamp": time.time(),
|
||||
}
|
||||
_write_checkpoint_metadata(checkpoint_dir / "meta.json", metadata)
|
||||
|
||||
tracker_file = base_dir / "latest_checkpointed_iteration.txt"
|
||||
tracker_file.write_text(str(step_id))
|
||||
logger.info(f"[FSDP] Saved checkpoint to {checkpoint_dir}")
|
||||
|
||||
dist.barrier()
|
||||
Reference in New Issue
Block a user