from __future__ import annotations from contextlib import contextmanager from pathlib import Path import sys from typing import Any, Iterator, Mapping import torch from torch import nn @contextmanager def _temporary_module_path(source_root: str | Path | None) -> Iterator[None]: if source_root is None: yield return path = str(Path(source_root).resolve()) sys.path.insert(0, path) try: yield finally: try: sys.path.remove(path) except ValueError: pass def _extract_state_dict(checkpoint: Any) -> Mapping[str, torch.Tensor]: if isinstance(checkpoint, Mapping): for key in ("model", "model_state", "state_dict"): candidate = checkpoint.get(key) if isinstance(candidate, Mapping): return candidate if checkpoint and all(isinstance(key, str) for key in checkpoint): return checkpoint raise ValueError("Checkpoint does not contain a model state dictionary.") def load_checkpoint( model: nn.Module, checkpoint_path: str | Path, *, strict: bool = True, map_location: str | torch.device = "cpu", source_root: str | Path | None = None, ) -> dict[str, Any]: """Load a trusted W-MAE checkpoint and return loading metadata. ``source_root`` is needed only for legacy checkpoints whose pickle payload references modules from the original repository, such as ``utils.YParams``. """ checkpoint_path = Path(checkpoint_path) if not checkpoint_path.is_file(): raise FileNotFoundError(f"Checkpoint does not exist: {checkpoint_path}") with _temporary_module_path(source_root): checkpoint = torch.load(checkpoint_path, map_location=map_location, weights_only=False) state_dict = _extract_state_dict(checkpoint) model_state = model.state_dict() missing = sorted(set(model_state) - set(state_dict)) unexpected = sorted(set(state_dict) - set(model_state)) shape_mismatches = { key: {"model": list(model_state[key].shape), "checkpoint": list(state_dict[key].shape)} for key in model_state.keys() & state_dict.keys() if tuple(model_state[key].shape) != tuple(state_dict[key].shape) } if strict and (missing or unexpected or shape_mismatches): raise RuntimeError( "Checkpoint is incompatible with the model: " f"missing={missing}, unexpected={unexpected}, shape_mismatches={shape_mismatches}" ) incompatible = model.load_state_dict(state_dict, strict=strict) return { "checkpoint_path": str(checkpoint_path), "strict": strict, "missing_keys": list(incompatible.missing_keys), "unexpected_keys": list(incompatible.unexpected_keys), "shape_mismatches": shape_mismatches, "checkpoint_keys": len(state_dict), }