new-language_model / utils /checkpoint_utils.py
Raja-65's picture
Add ELF demo
6ab8280
Raw
History Blame Contribute Delete
8.3 kB
import logging
import os
import re
from typing import Any, Optional, Tuple
import torch
from utils.logging_utils import log_for_0, _process_index
from utils.train_utils import unwrap_model
def _local_path(path: str) -> str:
return os.path.abspath(os.path.expanduser(path))
def upload_output_dir_to_hf(output_dir: str, hf_repo_id: Optional[str], reason: str = "artifacts"):
if not hf_repo_id or _process_index() != 0:
return
folder_path = _local_path(output_dir)
if not os.path.isdir(folder_path):
log_for_0(f"HF upload skipped; output directory does not exist: {folder_path}",
level=logging.WARNING)
return
try:
from huggingface_hub import HfApi
repo_id = hf_repo_id.strip("/")
api = HfApi()
api.create_repo(repo_id, repo_type="model", exist_ok=True)
log_for_0(f"Uploading {reason} to HF: {repo_id}")
api.upload_folder(repo_id=repo_id, folder_path=folder_path, repo_type="model")
log_for_0(f"Uploaded {reason} to HF: {repo_id}")
except Exception as e:
log_for_0(f"Failed to upload {reason} to HF: {e}", level=logging.WARNING)
def _split_hf_path(path: str, min_parts: int) -> Optional[Tuple[str, str]]:
if "://" in path:
return None
if path.startswith(("/", ".", "~")):
return None
if os.path.exists(_local_path(path)):
return None
parts = path.split("/")
if len(parts) < min_parts:
return None
return "/".join(parts[:2]), "/".join(parts[2:])
def save_checkpoint(state, output_dir: str, step: int, hf_repo_id: str = None):
"""Save model checkpoint locally as a single `checkpoint_<step>` file."""
if _process_index() != 0:
return
ckpt_dir = _local_path(output_dir)
os.makedirs(ckpt_dir, exist_ok=True)
inner_model = unwrap_model(state.model)
grad_accum_buffers = {}
if getattr(state, "grad_accum_buffers", None):
for name, param in inner_model.named_parameters():
buf = state.grad_accum_buffers.get(id(param))
if buf is not None:
grad_accum_buffers[name] = buf.detach().cpu()
payload = {
"params": inner_model.state_dict(),
"ema_params1": state.ema_params1,
"opt_state": state.optimizer.state_dict(),
"lr_scheduler": state.lr_scheduler.state_dict() if state.lr_scheduler is not None else None,
"step": int(state.step),
"epoch": int(state.epoch),
"dropout_rng": (state.dropout_generator.get_state()
if state.dropout_generator is not None else None),
"grad_accum_buffers": grad_accum_buffers,
}
out_path = os.path.join(ckpt_dir, f"checkpoint_{step}")
log_for_0(f"Saving checkpoint to {out_path}")
torch.save(payload, out_path)
log_for_0(f"Checkpoint written to {out_path}")
upload_output_dir_to_hf(output_dir, hf_repo_id, reason="checkpoint")
def _checkpoint_step(checkpoint_name: str) -> int:
"""Extract the trailing checkpoint step from a name; -1 if absent."""
match = re.search(r"(\d+)$", checkpoint_name)
return int(match.group(1)) if match else -1
def find_all_checkpoints(ckpt_dir: str, prefix: str = "checkpoint_"):
"""Find local checkpoint paths in a directory, sorted by step ascending."""
ckpt_dir = _local_path(ckpt_dir)
if not os.path.isdir(ckpt_dir):
return []
names = sorted(
[f for f in os.listdir(ckpt_dir) if f.startswith(prefix)],
key=_checkpoint_step,
)
return [os.path.join(ckpt_dir, name) for name in names]
def find_latest_checkpoint(ckpt_dir: str, prefix: str = "checkpoint_"):
"""Return the latest local checkpoint path, or None."""
all_ckpts = find_all_checkpoints(ckpt_dir, prefix)
return all_ckpts[-1] if all_ckpts else None
def _download_hf_checkpoint(checkpoint_path: str) -> Optional[str]:
hf_path = _split_hf_path(checkpoint_path, min_parts=2)
if hf_path is None:
return None
repo_id, sub_path = hf_path
from huggingface_hub import snapshot_download
log_for_0(f"Downloading checkpoint from HF: {repo_id}" + (f"/{sub_path}" if sub_path else ""))
local_dir = snapshot_download(
repo_id=repo_id, repo_type="model",
allow_patterns=[f"{sub_path}/**"] if sub_path else None,
)
return os.path.join(local_dir, sub_path) if sub_path else local_dir
def _restore_checkpoint(checkpoint_path: str) -> Any:
"""Restore a checkpoint from a file or directory (latest inside dir)."""
local = _local_path(checkpoint_path)
resolved = local
if os.path.isdir(local):
latest = find_latest_checkpoint(local)
if latest is not None and os.path.isfile(latest):
resolved = latest
if os.path.isfile(resolved):
return torch.load(resolved, map_location="cpu")
return None
def _validate_checkpoint(ckpt: Any):
if ckpt is None:
raise ValueError("checkpoint restore returned None")
required_keys = ("params", "opt_state", "step", "epoch")
missing_keys = [key for key in required_keys if key not in ckpt]
if missing_keys:
raise ValueError(f"checkpoint restore missing keys: {missing_keys}")
def load_checkpoint(checkpoint_path: str, state) -> Tuple[Any, int]:
"""Load an ELF checkpoint.
Uses an existing local path first; otherwise tries HF and then local fallback.
"""
log_for_0(f"Loading ELF checkpoint from {checkpoint_path}...")
ckpt, loaded_from = None, None
errors = []
local_path = _local_path(checkpoint_path)
if os.path.exists(local_path):
try:
log_for_0(f"Loading local checkpoint from {local_path}...")
ckpt = _restore_checkpoint(local_path)
_validate_checkpoint(ckpt)
loaded_from = "local"
except Exception as e:
errors.append(f"local: {e}")
if ckpt is None:
try:
hf_path = _download_hf_checkpoint(checkpoint_path)
if hf_path:
log_for_0(f"Loading HF checkpoint from {hf_path}...")
ckpt = _restore_checkpoint(hf_path)
_validate_checkpoint(ckpt)
loaded_from = "HF"
except Exception as e:
errors.append(f"HF: {e}")
log_for_0(f"HF checkpoint restore failed ({e}); falling back to local path.")
if ckpt is None:
raise ValueError(
f"Failed to load checkpoint from {checkpoint_path}. Tried: {'; '.join(errors)}"
)
log_for_0(f"Loaded checkpoint keys: {list(ckpt.keys())}")
inner_model = unwrap_model(state.model)
inner_model.load_state_dict(ckpt["params"])
ema_src = ckpt.get("ema_params1", ckpt["params"])
device_map = {n: p.device for n, p in inner_model.named_parameters()}
for n, b in inner_model.named_buffers():
device_map.setdefault(n, b.device)
fallback_device = next(iter(device_map.values()), torch.device("cpu"))
state.ema_params1 = {
n: t.to(device_map.get(n, fallback_device)) for n, t in ema_src.items()
}
state.optimizer.load_state_dict(ckpt["opt_state"])
if state.lr_scheduler is not None and ckpt.get("lr_scheduler") is not None:
state.lr_scheduler.load_state_dict(ckpt["lr_scheduler"])
state.step = int(ckpt["step"])
state.epoch = int(ckpt["epoch"])
if ckpt.get("dropout_rng") is not None and state.dropout_generator is not None:
try:
state.dropout_generator.set_state(ckpt["dropout_rng"])
except Exception:
pass
if ckpt.get("grad_accum_buffers"):
buffers = ckpt["grad_accum_buffers"]
state.grad_accum_buffers = {}
param_ids = []
for name, param in inner_model.named_parameters():
if not param.requires_grad:
continue
param_ids.append(id(param))
saved = buffers.get(name)
state.grad_accum_buffers[id(param)] = (
saved.to(device=param.device, dtype=param.dtype)
if saved is not None else torch.zeros_like(param)
)
state.grad_accum_param_ids = tuple(param_ids)
step = int(ckpt["step"])
log_for_0(f"Loaded {loaded_from} checkpoint from step {step} (epoch {state.epoch})")
return state, step