Skip to content

fix: proper checkpoint restore weights-only vs full training state#240

Open
Mani212005 wants to merge 1 commit into
mllam:mainfrom
Mani212005:model_weights_and_full_training_state
Open

fix: proper checkpoint restore weights-only vs full training state#240
Mani212005 wants to merge 1 commit into
mllam:mainfrom
Mani212005:model_weights_and_full_training_state

Conversation

@Mani212005

@Mani212005 Mani212005 commented Feb 23, 2026

Copy link
Copy Markdown
Contributor

Describe your changes

Replaces the fragile on_load_checkpoint optimizer hack in ARModel with proper PyTorch
Lightning patterns for checkpoint loading. This separates two distinct behaviours:

  • Weights-only (--load without --restore_opt): calls ModelClass.load_from_checkpoint(path, ...)
    to instantiate the model, then trainer.fit(model, ckpt_path=None) — epoch/step/scheduler reset to 0.
  • Full resume (--load + --restore_opt): uses the normal constructor, then
    trainer.fit(model, ckpt_path=path) — restores optimizer, epoch, LR scheduler, and callbacks.

ar_model.py

  • Removed self.restore_opt = args.restore_opt attribute.
  • Removed the on_load_checkpoint block that manually replaced optimizer/scheduler/epoch state — kept only the key-migration logic for older checkpoints.

train_model.py

  • Updated --restore_opt help text to say "full training state" (not just "optimizer state").
  • Added assertion: --restore_opt requires --load (fails fast with a clear message).

test_checkpoint_restore.py (new)

  • test_restore_opt_without_load_raises — assertion fires without --load.
  • test_weights_only_uses_load_from_checkpointload_from_checkpoint called, ckpt_path=None in fit.
  • test_restore_opt_passes_ckpt_path — normal constructor used, ckpt_path passed to fit.
  • test_eval_always_passes_ckpt_pathckpt_path always passed to test.
  • test_no_load_creates_fresh_model — no --load, normal constructor, ckpt_path=None.

Other test files

  • Removed the now-unused restore_opt = False from ModelArgs in test_training.py,
    test_clamping.py, test_plotting.py, and test_datasets.py.

Behaviour Table

CLI flags Model instantiation trainer.fit ckpt_path
(none) ModelClass(args, ...) None
--load PATH load_from_checkpoint(PATH, ...) None
--load PATH --restore_opt ModelClass(args, ...) PATH
--eval test --load PATH load_from_checkpoint(PATH, ...) N/A (trainer.test)

Issue Link

Closes #131

Type of change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📖 Documentation (Addition or improvements to documentation)

Checklist before requesting a review

  • My branch is up-to-date with the target branch - if not update your fork with the changes from the target branch (use pull with --rebase option if possible).
  • I have performed a self-review of my code
  • For any new/modified functions/classes I have added docstrings that clearly describe its purpose, expected inputs and returned values
  • I have placed in-line comments to clarify the intent of any hard-to-understand passages of my code
  • I have updated the README to cover introduced code changes
  • I have added tests that prove my fix is effective or that my feature works
  • I have given the PR a name that clearly describes the change, written in imperative form (context).
  • I have requested a reviewer and an assignee (assignee is responsible for merging). This applies only if you have write access to the repo, otherwise feel free to tag a maintainer to add a reviewer and assignee.

Checklist for reviewers

Each PR comes with its own improvements and flaws. The reviewer should check the following:

  • the code is readable
  • the code is well tested
  • the code is documented (including return types and parameters)
  • the code is easy to maintain

Author checklist after completed review

  • I have added a line to the CHANGELOG describing this change, in a section
    reflecting type of change (add section where missing):
    • added: when you have added new functionality
    • changed: when default behaviour of the code has been changed
    • fixes: when your contribution fixes a bug
    • maintenance: when your contribution is relates to repo maintenance, e.g. CI/CD or documentation

Checklist for assignee

  • PR is up to date with the base branch
  • the tests pass
  • (if the PR is not just maintenance/bugfix) the PR is assigned to the next milestone. If it is not, propose it for a future milestone.
  • author has added an entry to the changelog (and designated the change as added, changed, fixed or maintenance)
  • Once the PR is ready to be merged, squash commits and merge the PR.

@Mani212005

Copy link
Copy Markdown
Contributor Author

@observingClouds @joeloskarsson Kindly review this code. If you think something should be implemented differently. Please let me know

@joeloskarsson joeloskarsson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I think this looks really great. Had a couple small thoughts that I commented on here

Comment thread tests/test_checkpoint_restore.py Outdated
Comment thread neural_lam/train_model.py Outdated
@sadamov
sadamov requested a review from joeloskarsson March 16, 2026 04:28
@sadamov sadamov mentioned this pull request Mar 16, 2026
14 tasks
@sadamov sadamov added the bug Something isn't working label Apr 13, 2026
@joeloskarsson joeloskarsson self-assigned this Apr 18, 2026

@joeloskarsson joeloskarsson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for taking so long to look at this again, it drowned below a lot of other things in my inbox :/
Anyway, I think this looks pretty good, just had one documentation suggestion. And unfortunately (since I took so long here) I think the branch needs to be updated with all the recent changes to main, before we can merge. In particular make sure that the changelog entry ends up in the right place with all the additions there.

Comment thread neural_lam/train_model.py Outdated
@Mani212005

Copy link
Copy Markdown
Contributor Author

No worries :)
I would do the required changes and push the updates

Comment thread neural_lam/train_model.py Outdated
Comment on lines +254 to +256
# In tests parse_args may be patched with MagicMock. Normalize the flag
# to avoid truthy MagicMock attributes changing control flow.
load_training_state = bool(vars(args).get("load_training_state", False))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully follow this, and this seems quite hard to read now. It would be better to not adapt the code to the testing framework. If testing does not work without this then we need to reconsider the testing approach, not change the actual codebase IMO.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the refactoring in #208 make this easier? It should relieve some need for mocking argparse, and it will be merged before this PR.

@Mani212005

Copy link
Copy Markdown
Contributor Author

Sorry for taking such a long time to revert. I’ve been busy. would update the required changes on weekend.
Thanks!

@joeloskarsson

Copy link
Copy Markdown
Collaborator

#208 is merged now, which means 1) sadly some conflicts and this needing to be adapted, but also 2) maybe #240 (comment) will be easier to sort out?

@sadamov
sadamov force-pushed the model_weights_and_full_training_state branch from 0ef9cf3 to 73333bd Compare June 6, 2026 19:00
sadamov added a commit to Mani212005/neural-lam that referenced this pull request Jun 6, 2026
…llam#240)

Re-applies @Mani212005's PR mllam#240 onto current main. The original PR was
pre-mllam#208 and pre-mllam#641; this version applies the same Lightning-native
pattern to current main with a much smaller surface area because the
machinery is already in place via `load_forecaster_module_from_checkpoint`
(introduced as part of the mllam#208 refactor).

Source change:
- Remove the `if not self.restore_opt: ...` fresh-optimizer hack from
  `ForecasterModule.on_load_checkpoint`. The hack only reset the
  optimizer state and silently left epoch / scheduler / callbacks
  restored from the checkpoint, which is not what `--restore_opt=False`
  is supposed to mean.
- In `train_model.py`, branch on `args.restore_opt` for the `--load`
  path: weights-only goes through `load_forecaster_module_from_checkpoint`
  + `trainer.fit(model)` (no `ckpt_path`), full-resume keeps
  `trainer.fit(..., ckpt_path=args.load)`.

Skipped the 239-line tests/test_checkpoint_restore.py from the original
PR: it targeted the pre-mllam#208 API and would require a full rewrite. The
existing `tests/test_training.py` smoke tests pass on the new code path.

Verified: 18 training-related tests pass locally (test_training.py,
test_cli.py, test_train_model_warnings.py).

Co-Authored-By: Mani <Mani212005@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…llam#240)

Re-applies @Mani212005's PR mllam#240 onto current main, picking up the
naming/rename agreed with @joeloskarsson in the review thread.

Source changes:

- Rename CLI flag `--restore_opt` -> `--load_training_state` per
  @joeloskarsson's review comment (the old name was misleading because
  the flag controls epoch / scheduler / callbacks too, not just the
  optimizer). The internal `ForecasterModule(restore_opt=...)` keyword
  stays for now to keep legacy ARModel checkpoints loading via the
  `args=` shim.
- Remove the `if not self.restore_opt: ...` fresh-optimizer hack from
  `ForecasterModule.on_load_checkpoint`. The hack only reset the
  optimizer state and silently left epoch / scheduler / callbacks
  restored from the checkpoint.
- In `train_model.py`, branch on `args.load_training_state` for the
  `--load` path: weights-only goes through
  `load_forecaster_module_from_checkpoint` + `trainer.fit(model)` with
  no `ckpt_path`, full-resume keeps `trainer.fit(..., ckpt_path=args.load)`.
- Add CLI assertion that `--load_training_state` requires `--load`.

Test changes:
- Drop the 239-line tests/test_checkpoint_restore.py from the original
  PR (targeted pre-mllam#208 API; @joeloskarsson also flagged it as adapting
  code to fit tests rather than testing real behaviour).
- Add `test_load_training_state_requires_load` covering the new CLI
  assertion (parser.error -> SystemExit before any datastore loading).
- Update tests/test_train_model_warnings.py mocks to set
  `load_training_state=False` explicitly (MagicMock returns truthy by
  default, which would trip the new assertion).

Co-Authored-By: Mani <Mani212005@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sadamov
sadamov force-pushed the model_weights_and_full_training_state branch from 73333bd to ccfbbd7 Compare June 6, 2026 19:09

@sadamov sadamov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rebased on main which made this PR much lighter. @Mani212005 and @joeloskarsson please have a look if this is still in line with your original intent.

Comment thread CHANGELOG.md
Comment on lines +73 to +74
- Fix `--load` to truly restore weights only by default: instead of overriding the optimizer state inside `on_load_checkpoint` (which left epoch / scheduler / callbacks inherited from the checkpoint), `train_model.py` now reconstructs the model via `ForecasterModule.load_from_checkpoint` and calls `trainer.fit(model)` with no `ckpt_path`, so all non-weight training state starts fresh. Also rename the CLI flag from `--restore_opt` to `--load_training_state` to reflect that it controls more than just the optimizer (epoch / scheduler / callbacks too), and assert it requires `--load` [\#240](https://github.com/mllam/neural-lam/pull/240) @Mani212005

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please simplify this changelog entry. The point is to get an idea of the change at a glance, not for this to be a full description of the change done (that's what the PR description is for 😄).

@joeloskarsson joeloskarsson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! Yes, I am also happy with this now. Just added a small comment about simplifying the changelog, which can be done before merging this (does not affect code).

@joeloskarsson joeloskarsson added enhancement New feature or request ready Review complete - proposed for milestone and removed bug Something isn't working labels Jun 11, 2026
@joeloskarsson joeloskarsson added this to the v0.7.0 (proposed) milestone Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request ready Review complete - proposed for milestone

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Properly handle difference between restoring only model weights and full training state

3 participants