Source code for helios.chkpt_migrator
import argparse
import pathlib
import platform
import torch
import tqdm
from helios import core
from ._version import __version__
from .model.model import _InternalStateKeys
from .trainer import TrainingState, _CheckpointKeys, register_trainer_types_for_safe_load
[docs]
def migrate_checkpoints_to_current_version(root: pathlib.Path) -> None:
"""
Migrate existing checkpoints from a previous version of Helios to the current version.
This function exists to provide backwards compatibility with checkpoints produced by
older versions of Helios.
Args:
root: the root where the checkpoints are stored.
"""
register_trainer_types_for_safe_load()
for chkpt_path in tqdm.tqdm(
list(root.glob("*.pth")), desc="Migrating checkpoints", unit="chkpt"
):
state = core.safe_torch_load(chkpt_path)
# 1.0 checkpoint changes
# =======================#
# Pre-v1.0, checkpoints didn't have a version key.
if _CheckpointKeys.VERSION not in state:
state[_CheckpointKeys.VERSION] = __version__
# 1.1 checkpoint changes
# =======================#
# Pre-v1.1, the TrainingState struct is saved as a dictionary, not the object
# itself.
if isinstance(state[_CheckpointKeys.TRAINING_STATE], dict):
state[_CheckpointKeys.TRAINING_STATE] = TrainingState(
**state[_CheckpointKeys.TRAINING_STATE]
)
# 2.0 checkpoint changes
# =======================#
# Pre-v2.0, all model state was stored under the "model" key. v2.0 added the
# notion of model-internal state, and user state is now saved under the "user"
# key. Therefore everything that was under the "model" key now gets mapped to the
# "user" sub-key.
if _InternalStateKeys.USER not in state[_CheckpointKeys.MODEL]:
state[_CheckpointKeys.MODEL] = {
_InternalStateKeys.USER: state[_CheckpointKeys.MODEL]
}
# Pre-v2.0, the log_path and run_path were stored as separate keys. These have now
# been moved under the "loggers" key.
if _CheckpointKeys.LOGGERS not in state:
loggers_state: dict[str, dict] = {}
if "log_path" in state:
loggers_state["root"] = {"log_file": state.pop("log_path")}
if "run_path" in state:
loggers_state["tensorboard"] = {"run_path": state.pop("run_path")}
state[_CheckpointKeys.LOGGERS] = loggers_state
# Pre-v2.0, checkpoints didn't include datamodule phase state. Default is an empty
# dict which maps to phase 0.
if _CheckpointKeys.DATAMODULE not in state:
state[_CheckpointKeys.DATAMODULE] = {}
torch.save(state, chkpt_path)
def _main() -> None:
parser = argparse.ArgumentParser(
description="Migration tool to convert checkpoints generated by versions of "
f"Helios prior to {__version__}"
)
parser.add_argument(
"root",
metavar="ROOT",
nargs=1,
type=str,
help="Root where the checkpoints are stored",
)
args = parser.parse_args()
root = args.root[0]
# Temporarily re-direct PosixPath to WindowsPath on Windows to avoid problems.
tmp: type[pathlib.WindowsPath] | type[pathlib.PosixPath] | None = None
if platform.system() == "Windows":
tmp = pathlib.PosixPath
pathlib.PosixPath = pathlib.WindowsPath # type: ignore[assignment, misc]
elif platform.system() == "Linux":
tmp = pathlib.WindowsPath
pathlib.WindowsPath = pathlib.PosixPath # type: ignore[assignment, misc]
migrate_checkpoints_to_current_version(pathlib.Path(root))
# Restore to default values.
if platform.system() == "Windows":
pathlib.PosixPath = tmp # type: ignore[assignment, misc]
elif platform.system() == "Linux":
pathlib.WindowsPath = tmp # type: ignore[assignment, misc]
if __name__ == "__main__":
_main()