Skip to content

Fix ModelCheckpoint manual-opt pre-step snapshot not applied when save is deferred to validation#21843

Open
ErenAta16 wants to merge 4 commits into
Lightning-AI:masterfrom
ErenAta16:fix/manual-opt-checkpoint-deferred-validation
Open

Fix ModelCheckpoint manual-opt pre-step snapshot not applied when save is deferred to validation#21843
ErenAta16 wants to merge 4 commits into
Lightning-AI:masterfrom
ErenAta16:fix/manual-opt-checkpoint-deferred-validation

Conversation

@ErenAta16

Copy link
Copy Markdown

Fixes #21842.

What does this PR do?

ModelCheckpoint's manual-optimization support (added in #21239 for #20947) saves the pre-optimization-step model state captured in pl_module.saved_models, so the checkpoint reflects the state that actually produced the monitored metric rather than whatever the weights happen to be after later optimizer.step() calls.

That swap only happened in on_train_batch_end, the immediate-save path. When the monitored metric is validation-only (monitor="val_loss" and similar, the common case for this feature since manual-optimization training loops rarely log training-time metrics they'd want to checkpoint on), the save gets deferred to on_validation_end instead, via _defer_save_until_validation. That deferred path saved with no swap at all, dumping the live, fully-trained, post-optimization weights, silently defeating the entire point of the feature for its most common use case.

This is a regression of #20947, reintroduced by #21239's own fix, since that PR's test only monitors a training-step metric (monitor="loss"), which is always available in on_train_batch_end and therefore never exercises the _defer_save_until_validation branch.

Fix

Extracted the swap-save-restore logic (previously inline in on_train_batch_end) into _save_checkpoint_with_manual_opt_state, called from both on_train_batch_end and on_validation_end's deferred branch, so the two call sites can't diverge again. on_validation_end only takes this path when not pl_module.automatic_optimization, automatic-optimization checkpointing is untouched since the live weights are already the correct state to save there.

Testing

Added test_model_checkpoint_manual_opt_deferred_validation in tests/tests_pytorch/callbacks/test_model_checkpoint_manual_opt.py, right next to the existing test_model_checkpoint_manual_opt. It monitors val_loss (validation-only) instead of loss (training-step), so it exercises the previously-broken deferred path.

Confirmed red/green:

  • Against the pre-fix code: fails with AssertionError: Checkpoint saved the live, post-optimization weights instead of a pre-opt snapshot.
  • Against the fix: passes.

Ran the full manual-opt and step-interval-val-metric suites plus the broader ModelCheckpoint test files:

tests/tests_pytorch/callbacks/test_model_checkpoint_manual_opt.py: 3 passed
tests/tests_pytorch/callbacks/test_model_checkpoint_step_interval_val_metric.py: 1 passed
tests/tests_pytorch/checkpointing/test_model_checkpoint.py
tests/tests_pytorch/callbacks/test_model_checkpoint_additional_cases.py
tests/tests_pytorch/callbacks/test_model_checkpoint_edge_cases.py: 135 passed, 2 skipped

The remaining 7 failures in the broader run (test_model_checkpoint_path, test_model_checkpoint_save_last[link], test_model_checkpoint_link_checkpoint*, test_checkpoint_repeated_strategy_extended) are pre-existing environment issues in my local setup (missing tensorboard/tensorboardX, and Windows without symlink privileges for the save_last="link" tests), confirmed by reproducing the exact same failures against unpatched master before making any change.

ErenAta16 added 2 commits July 17, 2026 19:03
… when save is deferred to validation

on_train_batch_end's manual-optimization branch correctly swaps in
pl_module.saved_models[latest_step] before saving when the monitored metric
is already available, then restores the live weights afterward. When the
monitored metric is validation-only, the save is deferred instead
(_defer_save_until_validation), and on_validation_end's deferred branch
performed the save with no swap at all, dumping the live, fully-trained,
post-optimization weights instead of the pre-optimization snapshot that
produced the monitored value. This defeated the entire point of the
saved_models mechanism added in Lightning-AI#21239 for the validation-monitor case,
which is the common case for this feature (monitor="val_loss" and similar).

Extracts the swap-save-restore logic (previously inline in
on_train_batch_end) into _save_checkpoint_with_manual_opt_state, shared by
both on_train_batch_end and on_validation_end's deferred branch, so the two
call sites can't diverge again.

Regression of Lightning-AI#20947, reintroduced by an incomplete fix in Lightning-AI#21239 whose own
test only covers a training-step monitor (never available at
on_train_batch_end, so it never exercises the deferred path).

Fixes Lightning-AI#21842.
…pshot

Monitors a validation-only metric (val_loss) instead of a training-step
metric, exercising the on_validation_end deferred-save path. Confirmed this
test fails against the pre-fix code (checkpoint matches the live weights)
and passes with the fix (checkpoint matches a pre-optimization snapshot).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ModelCheckpoint: manual-optimization pre-step snapshot is not applied when the save is deferred to on_validation_end

1 participant