Source code for kempnerforge.config.checkpoint

"""Checkpoint configuration."""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import StrEnum
from typing import Literal

from kempnerforge.config.registry import registry


[docs] class AsyncCheckpointMode(StrEnum): disabled = "disabled" async_ = "async" async_pinned = "async_with_pinned_mem"
[docs] @dataclass class DynamicCheckpointWindow: """A bounded step range with a registered checkpoint strategy. Inside ``[start, stop]`` the strategy decides which steps to save, and every such step is exempt from ``CheckpointConfig.keep_last_n`` retention. Outside the window the regular ``CheckpointConfig.interval`` cadence applies. ``"power2"`` (default) saves at ``start`` and at every ``start + 2^k`` while ``<= stop`` -- tight at the start of the window, doubling thereafter. New strategies register via ``@registry.register_dyn_ckpt_strategy(name)`` and become selectable by setting ``strategy``. """ start: int = 0 stop: int = 512 strategy: str = "power2" def __post_init__(self) -> None: if self.start < 0: raise ValueError("dyn_ckpt_window.start must be >= 0") if self.stop < self.start: raise ValueError("dyn_ckpt_window.stop must be >= start") known = registry.list_dyn_ckpt_strategies() if self.strategy not in known: raise ValueError( f"unknown dyn_ckpt_window.strategy {self.strategy!r}; registered: {known}" )
[docs] def is_milestone(self, step: int) -> bool: """True iff the configured strategy fires at ``step``.""" return registry.get_dyn_ckpt_strategy(self.strategy)(self, step)
@registry.register_dyn_ckpt_strategy("power2") def _power2_strategy(window: DynamicCheckpointWindow, step: int) -> bool: """Save at ``start`` and at every ``start + 2^k`` while ``<= stop``.""" if step < window.start or step > window.stop: return False offset = step - window.start return offset == 0 or (offset & (offset - 1)) == 0 WEIGHTS_ONLY_EXCLUDE = frozenset({"optimizer", "train_state"})
[docs] def is_weights_only(keys: list[str] | None) -> bool: """Whether an exclude list asks for the weights-only warm start.""" return keys is not None and set(keys) == WEIGHTS_ONLY_EXCLUDE
[docs] def validate_exclude_keys(keys: object, name: str) -> None: """Check an exclude list: a list of distinct DCP keys, ``"train_state"`` only in the pair.""" if not isinstance(keys, list): raise ValueError(f"{name} must be a list (got {keys!r})") if any(k not in ("model", "optimizer", "train_state") for k in keys): raise ValueError( f"{name} accepts only 'model', 'optimizer' and/or 'train_state' (got {keys!r})" ) if len(set(keys)) != len(keys): raise ValueError(f"{name} repeats a key (got {keys!r})") if "train_state" in keys and set(keys) != WEIGHTS_ONLY_EXCLUDE: raise ValueError( f"{name} with 'train_state' must be ['optimizer', 'train_state']: a weights-only " f"warm start loads the model alone (got {keys!r})" )
[docs] @dataclass class CheckpointConfig: """Checkpointing settings.""" dir: str = "checkpoints" interval: int = 1000 dyn_ckpt_window: DynamicCheckpointWindow | None = None async_mode: AsyncCheckpointMode = AsyncCheckpointMode.disabled keep_last_n: int = 3 load_path: str | None = None export_dtype: Literal["float32", "bfloat16"] = "bfloat16" exclude_from_loading: list[str] = field(default_factory=list) ignore_freeze_mismatch: bool = False ignore_rank_state_mismatch: bool = False def __post_init__(self) -> None: if self.interval <= 0: raise ValueError("interval must be positive") validate_exclude_keys(self.exclude_from_loading, "exclude_from_loading")
[docs] def should_save(self, step: int) -> bool: """Whether to write a checkpoint at ``step``. Inside ``dyn_ckpt_window``: the registered strategy decides (default ``"power2"`` saves at ``start`` and each ``start + 2^k`` while ``<= stop``). Outside the window: every ``interval`` steps. Dynamic milestones are exempt from ``keep_last_n`` (see ``CheckpointManager._cleanup``). """ w = self.dyn_ckpt_window if w is not None and w.start <= step <= w.stop: return w.is_milestone(step) return step % self.interval == 0
[docs] def is_dynamic_milestone(self, step: int) -> bool: """True if ``step`` is a milestone of the configured ``dyn_ckpt_window``. ``CheckpointManager._cleanup`` excludes these from ``keep_last_n`` so the dense early-window checkpoints survive a finite retention. """ return self.dyn_ckpt_window is not None and self.dyn_ckpt_window.is_milestone(step)