"""Checkpoint lineage and compatibility validation.""" from __future__ import annotations from dataclasses import asdict from pathlib import Path from typing import Any import torch from .artifacts import environment_record, sha256_file, sha256_json from .model import ModelConfig def save_checkpoint( path: str | Path, model: torch.nn.Module, model_config: ModelConfig, split_path: str | Path, input_paths: list[str | Path], training_state: dict[str, Any], optimizer: torch.optim.Optimizer | None = None, ) -> None: output = Path(path) if output.exists(): raise FileExistsError(f"Refusing to overwrite checkpoint: {output}") output.parent.mkdir(parents=True, exist_ok=True) input_hashes = {str(Path(item).resolve()): sha256_file(item) for item in input_paths} payload = { "schema_version": 1, "model_class": type(model).__name__, "model_config": asdict(model_config), "model_config_sha256": sha256_json(asdict(model_config)), "model_state": model.state_dict(), "optimizer_state": optimizer.state_dict() if optimizer else None, "split_path": str(Path(split_path).resolve()), "split_sha256": sha256_file(split_path), "input_sha256": input_hashes, "training_state": training_state, "environment": environment_record(), } torch.save(payload, output) def load_checkpoint( path: str | Path, model: torch.nn.Module, split_path: str | Path, map_location: str | torch.device = "cpu", ) -> dict[str, Any]: payload = torch.load(path, map_location=map_location) if payload.get("schema_version") != 1: raise ValueError("Unsupported checkpoint schema") if payload["split_sha256"] != sha256_file(split_path): raise ValueError("Checkpoint was trained with a different split manifest") model.load_state_dict(payload["model_state"]) return payload