Fix ModelCheckpoint manual-opt pre-step snapshot not applied when save is deferred to validation#21843
Open
ErenAta16 wants to merge 4 commits into
Open
Conversation
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).
ErenAta16
requested review from
ethanwharris,
justusschock and
tchaton
as code owners
July 17, 2026 16:06
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 inpl_module.saved_models, so the checkpoint reflects the state that actually produced the monitored metric rather than whatever the weights happen to be after lateroptimizer.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 toon_validation_endinstead, 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 inon_train_batch_endand therefore never exercises the_defer_save_until_validationbranch.Fix
Extracted the swap-save-restore logic (previously inline in
on_train_batch_end) into_save_checkpoint_with_manual_opt_state, called from bothon_train_batch_endandon_validation_end's deferred branch, so the two call sites can't diverge again.on_validation_endonly takes this path whennot 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_validationintests/tests_pytorch/callbacks/test_model_checkpoint_manual_opt.py, right next to the existingtest_model_checkpoint_manual_opt. It monitorsval_loss(validation-only) instead ofloss(training-step), so it exercises the previously-broken deferred path.Confirmed red/green:
AssertionError: Checkpoint saved the live, post-optimization weights instead of a pre-opt snapshot.Ran the full manual-opt and step-interval-val-metric suites plus the broader
ModelCheckpointtest files: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 (missingtensorboard/tensorboardX, and Windows without symlink privileges for thesave_last="link"tests), confirmed by reproducing the exact same failures against unpatchedmasterbefore making any change.