kempnerforge.checkpoint.manager¶
Checkpoint manager for distributed checkpointing.
Uses PyTorch Distributed Checkpoint (DCP) for model and optimizer state, which supports automatic resharding (save with N GPUs, load with M GPUs).
Non-distributed state (scheduler, dataloader, training meta, RNG) is saved
separately as a torch file written by rank 0 and broadcast from rank 0 on
load. Scheduler and training meta restore from that single copy on every
rank. Dataloader and RNG state are per-rank: every rank’s payload is gathered
into a versioned envelope at save time, and on an exact resume
(load(exact_resume=True)) each rank restores its own entry from the
broadcast (see kempnerforge.checkpoint.state). Any other load (a
load_path warm start, evaluation) restores rank 0’s single copy on every
rank. A weights-only warm start (exclude_keys=["optimizer", "train_state"])
reads no train_state.pt, so it restores neither and checks no topology.
Resharding model and optimizer state is therefore free, but an exact resume
is not: a rank’s loader cursor and RNG streams are valid only on the world
size and data-parallel layout that saved them, so an exact resume under a
changed topology raises on every rank. Setting
checkpoint.ignore_rank_state_mismatch discards the saved loader/RNG state
on every rank instead, leaving step, model, optimizer and scheduler to resume
onto the new topology while data order and random streams restart fresh.
Functions
|
Whether checkpoint |
Classes
Manages save/load/cleanup of distributed checkpoints. |
|
Records checkpoints with the logging backends, which this module never imports. |
- class kempnerforge.checkpoint.manager.CheckpointRegistrar[source]¶
Bases:
ProtocolRecords checkpoints with the logging backends, which this module never imports.
- checkpoint_fields(step)[source]¶
Fields each backend adds to its record of the checkpoint saved at
step, by backend.Called on rank 0 inside
save(), so it must answer from what it holds, never a server.
- register_checkpoint(step, directory, record)[source]¶
Register the checkpoint at
directoryoncelatestpoints at it.Called on rank 0 when the checkpoint is committed, so it must not block.
recordadds fields to a backend’s record in that checkpoint’smetadata.json, from any thread, later.
- __init__(*args, **kwargs)¶
- kempnerforge.checkpoint.manager.missing_train_state(ckpt_dir)[source]¶
Whether checkpoint
ckpt_dirholds optimizer state but notrain_state.pt.save()writestrain_state.ptwith the DCP shards (after them in a synchronous save, while an async save’s shards still flush), so such a checkpoint is a save that stopped before both were written, or one that lost the file, andload()refuses to resume it. A checkpoint without optimizer state, such as anhf-to-dcpimport, needs notrain_state.pt. The DCP.metadataofckpt_dir, or of eachpp{k}stage, is read only when the file is absent. One that cannot be read is logged and counted as holding no optimizer state, which leaves the load to report it.
- class kempnerforge.checkpoint.manager.CheckpointManager[source]¶
Bases:
objectManages save/load/cleanup of distributed checkpoints.
Each checkpoint is stored in a subdirectory:
{dir}/step_{N}/containing DCP shards and a non-distributed training state file.A
latestsymlink always points to the most recent checkpoint.- Parameters:
config – Checkpoint configuration.
model – The model (FSDP-wrapped or plain).
optimizer – The optimizer.
dp_info – This rank’s
(dp_rank, dp_size), the data partition its loader reads. Recorded with the per-rank loader/RNG state and checked on load, so a resume under a different data-parallel layout is refused even at the same world size. None records no layout and checks only the world size.weight_decay_scope – The run’s
optimizer.weight_decay_scope. When set, every save records it inmetadata.json, and a load that restores optimizer state on any rank raisesValueErroron every rank, before anything is loaded, when the checkpoint was saved under a different scope. A checkpoint that holds no optimizer state, such as a converted one or one saved before the first step, loads under any scope with a fresh optimizer. A checkpoint that does not record a scope counts as"exclude_norms_and_biases".lr_multipliers – The run’s
optimizer.lr_multipliers. When set, every save records a non-empty one inmetadata.json, with the patterns of its modules and, without pipeline parallelism, the module each optimizer param group names. A load of a checkpoint saved with other multipliers or patterns raisesValueErroron every rank when it restores the checkpoint’s optimizer state on any rank, before anything is loaded, or the scheduler state in itstrain_state.pt, before anything from that file is restored. A load that restores neither, such as a weights-only warm start (exclude_keys=["optimizer", "train_state"]), goes ahead without reading the record. A checkpoint that records none counts as saved without multipliers. One whosemetadata.jsoncannot be read, or holds an invalid record, is refused the same way when multipliers are set; without them it loads as one saved without multipliers, with a warning, and only a record of some is refused. With multipliers set, a load that restores optimizer state into param groups laid out otherwise than at the save, a group of another module at a position included, raises on every rank before anything is loaded. A restored scheduler state carries the saved schedule on in every group, a group of a module the checkpoint holds no group of at the saved peak times its multiplier (seerestore_train_state).module_patterns – Module name to fnmatch patterns, such as
VLMConfig.module_patterns. Required for every modulelr_multiplierslists.
- __init__(config, model, optimizer, process_group=None, pp_rank=None, dp_info=None, weight_decay_scope=None, lr_multipliers=None, module_patterns=None)[source]¶
- Parameters:
config (CheckpointConfig)
model (torch.nn.Module)
optimizer (torch.optim.Optimizer)
pp_rank (int | None)
weight_decay_scope (str | None)
- Return type:
None
- agree(error, what='Checkpoint save')[source]¶
Agree on the manager’s dedicated gloo group that every rank’s
whatsucceeded.See
agree_across_ranks. The group is not the default process group that DCP’s async-save background thread issues collectives on.- Parameters:
error (BaseException | None)
what (str)
- Return type:
None
- set_tracking_source(source)[source]¶
Record the logging runs
sourcereports in every latermetadata.json.sourcereturns one JSON-ready record per live run, and is called on rank 0 only, insidesave(), so it must return promptly. Thetrackingkey is omitted when it reports none, raises, or returns what JSON cannot write.Nonedetaches it.
- set_checkpoint_registrar(registrar)[source]¶
Record every later checkpoint with the logging backends through
registrar.Both of its methods run on rank 0 only.
checkpoint_fieldsis asked insidesave()when a backend has a live run, and its fields join that save’strackingrecords.register_checkpointgets each checkpoint once it is committed, with arecord(backend, fields)that later adds fields to that backend’s record, atomically and from any thread. A registrar that raises, or answers what a record cannot hold, costs only those fields or that registration, never the save or the commit.Nonedetaches it.- Parameters:
registrar (CheckpointRegistrar | None)
- Return type:
None
- save(step, tokens_seen=0, scheduler=None, dataloader=None, extra=None)[source]¶
Save a checkpoint at the given step.
Under expert parallelism each EP rank holds a copy of every weight outside the routed experts, and the copies can drift apart on GPU (#482). DCP writes each chunk that several ranks hold from one of them, by default the one with the least to write so far, which could join chunks of different copies into one tensor. An EP save writes each such chunk from its lowest rank instead, so the checkpoint holds one EP rank’s copy whole.
- wait()[source]¶
Block until any pending async checkpoint save completes.
Once the flush is durable, commit its deferred latest symlink + cleanup. The training loop calls this after the loop exits, so the final checkpoint’s latest is committed before process teardown. A flush that failed on any rank raises on every rank, and nothing is committed.
- Return type:
None
- flush_pending_save()[source]¶
Drain any in-flight async save before mutating model state.
Called from the FreezeStage transition hook in the training loop: when a transition fires at step S, any save started at step S-1 must have written
metadata.jsonwith the pre-transition spec before the transition flipsrequires_grad. Otherwisemetadata.jsonlands with the post-transition spec attached to the pre-transition shards.Also commits the deferred latest symlink for that save, so a transition (or any caller draining the queue) leaves latest pointed at the now-durable checkpoint. A flush that failed on any rank raises on every rank, and nothing is committed.
- Return type:
None
- peek_saved_step(path=None)[source]¶
Read
stepfrom a candidate checkpoint’s metadata.json.Returns
Noneif no checkpoint resolves or the metadata is missing/unreadable. Used by the training loop on resume to compute the expected freeze list (which depends onsaved_step) before callingload.
- load(path=None, scheduler=None, dataloader=None, exclude_keys=None, vlm_freeze_expected=None, exact_resume=False)[source]¶
Load a checkpoint and restore all state.
- Parameters:
path (str | None) – Checkpoint path. If None, loads from
config.load_pathor thelatestsymlink.scheduler (Any | None) – LR scheduler to restore.
dataloader (Any | None) – Stateful dataloader to restore.
exclude_keys (list[str] | None) – State to skip: the DCP keys
"model"and"optimizer"(e.g.["optimizer"]for fine-tuning), or["optimizer", "train_state"]for a weights-only warm start that loads the model alone and ignorestrain_state.pt(step, token count, scheduler, dataloader position, RNG) and the saved freeze metadata, so the new run starts at step 0 with its own schedule, data order and freeze."train_state"in any other combination raisesValueError.vlm_freeze_expected (list[dict[str, Any]] | None) – Canonical freeze metadata (output of
canonical_freeze_meta) for the current run’s VLMConfig. When both the saved metadata and this argument are set, a mismatch raisesValueErrorunless the checkpoint config hasignore_freeze_mismatch=True, in which case the load proceeds with a warning.exact_resume (bool) – True only when the run resumes from its own checkpoint (auto-resume). Each rank then restores its own dataloader/RNG state from the per-rank envelope, which must match this run’s world size and data-parallel layout. Every rank must pass the same value. False (a
load_pathwarm start, evaluation, any other load) restores the single-copyrng/dataloaderkeys (rank 0’s) on every rank with no topology check. The weights-onlyexclude_keyspair reads no train state: no rank restores loader/RNG state or enters the train-state broadcasts or the layout gather, and withexact_resumeit raises.
- Returns:
Tuple of (step, tokens_seen, extra) where extra contains any additional keys saved via
build_train_state(extra=...).- Raises:
FileNotFoundError – No checkpoint resolves although one was named, or the checkpoint holds optimizer state that this load restores but has no
train_state.pt. Raised before any state is loaded.ValueError –
exclude_keysis not a list of distinct DCP keys, names"train_state"outside["optimizer", "train_state"], or is that pair withexact_resume, raised before the path is resolved; the checkpoint was saved under another weight-decay scope, LR multipliers or patterns of their modules (see__init__); or, on an exact resume, its per-rank state does not fit this run’s topology andcheckpoint.ignore_rank_state_mismatchis off (seeexact_resume).RuntimeError – The saved optimizer state does not fit this run’s optimizers.
- Return type:
A load that restores optimizer state refuses it on every rank together, before any state is loaded: each rank checks its own part of the checkpoint, and one all-reduce on the manager’s gloo group, which every rank enters, has the other ranks raise a
RuntimeErrornaming the ranks that refused.
- apply_dataloader_state(dataloader)[source]¶
Apply any dataloader state stashed during load().
Training loops call load() before constructing the dataloader (since the dataloader depends on phase/annealing state that load() restores). This method applies the stashed state once the loader exists.
No-op if no state is pending, or if the loader does not support
load_state_dict(e.g., plain torch DataLoader for HF streaming).- Parameters:
dataloader (Any)
- Return type:
None