Skip to content

Mkphuthi/training helpers [WIP]#817

Open
mkphuthi wants to merge 12 commits into
materialyzeai:mainfrom
mkphuthi:mkphuthi/training_helpers
Open

Mkphuthi/training helpers [WIP]#817
mkphuthi wants to merge 12 commits into
materialyzeai:mainfrom
mkphuthi:mkphuthi/training_helpers

Conversation

@mkphuthi

Copy link
Copy Markdown

Summary

Major changes:

  • feature 1: Added some scripts to train and finetune models with flexibility to change datasets, schedulers and optimizers.

    • I have found that finetuning can be unstable with the default CosineAnnealingLR scheduler, so this flexibility allows using LR warmup and another scheduler such as ReduceLROnPlateau. Other best practices can be integrated as we find them
    • The training script is driven entirely by a config yaml/json with documented examples provided
    • The trainers load either pymatgen jsons or ASE-readable files and allows the user to specify the stress unit so conversion is done internally
  • fix 1: The scheduler was stepping twice per epoch, this is the only change that affects the original source. It does not affect trained models but does point out that every model that has been trained so far has actually been stepped for half as many epochs as the reported number

Todos

  • Right now the training script is run by directly calling them from the scripts directory. Perhaps a cleaner implementation would be to integrate with the cli so you can call "mgl train config.yaml" or "mgl finetune config.yaml" but this requires a more significant refactor

Checklist

  • Train a FP to test

mkphuthi and others added 12 commits June 4, 2026 18:44
Scaffold scripts/train/_common.py shared by the upcoming train.py and
finetune.py scripts. Loads a YAML/JSON config and pymatgen-serialized
structure JSONs ({"structures": [...], "labels": {...}}) via monty.loadfn,
builds MGLDataset(s) (separate train/val/test files or a single file with
frac_list split) and MGLDataLoaders. Uses MatGL's public backend-dispatched
APIs so the scripts follow MATGL_BACKEND.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
train.py selects a model (M3GNet/CHGNet/TensorNet/SO3Net/QET) from config,
fits per-element AtomRef offsets on the training set, and trains a
PotentialLightningModule. Adds the supporting _common helpers:
compute_element_refs, build_potential_module, build_logger (csv/wandb),
and build_trainer (LearningRateMonitor, inference_mode=False).

build_datasets now also returns the resolved element table so callers don't
reach into dataset internals. load_config coerces ruamel YAML scalars to plain
Python types (they otherwise break checkpoint pickling), and test runs against
the in-memory module rather than ckpt_path="best".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
finetune.py loads a pre-trained potential via matgl.load_model (Hub name or
local model dir), reuses its element table and per-element energy offsets, and
continues training on the configured datasets. Shares all dataset / dataloader
/ trainer plumbing with train.py via _common.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
build_optimizer / build_scheduler construct the optimizer and an epoch-based
LR scheduler from config: any torch.optim.lr_scheduler class via
scheduler/scheduler_args (default CosineAnnealingLR), with an optional linear
warmup of warmup_epochs prepended through SequentialLR.

TrainingLightningModule (PotentialLightningModule subclass) returns an
epoch-interval scheduler config and no-ops on_train_epoch_end so the LR
advances exactly once per epoch instead of matgl's default double-step, and
wires a monitor metric for ReduceLROnPlateau. warmup is rejected for
ReduceLROnPlateau since SequentialLR cannot chain a metric-based scheduler.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
build_callbacks adds a ModelCheckpoint (enabled by default) that tracks
metric_to_track and always writes last.ckpt. resume_ckpt_path resolves
config['resume_from'] (an explicit .ckpt path or "last") and is passed as
ckpt_path to trainer.fit in both train.py and finetune.py, restoring full
training state (weights, optimizer, scheduler, epoch). These .ckpt files are
separate from the deployable model written by model.save() at the end.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
config_example.yaml documents every recognised key for train.py / finetune.py.
tests/scripts/train/test_training_scripts.py covers config-scalar coercion,
pymatgen-JSON dataset loading, stress-aware collation, warmup+StepLR stepping
exactly once per epoch, the warmup/ReduceLROnPlateau guard, and a
checkpoint-then-resume round trip. A tiny TensorNet trains for 1-2 epochs on
CPU so the module runs in a few seconds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
main removed DGL and all backend-selection machinery, so the module docstring
no longer references MATGL_BACKEND; imports still go through MatGL's public APIs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
These scripts are specific to PES (energy/forces/stress) models built on
PotentialLightningModule. Renaming scopes them as the PES variants and leaves
room for structure-wise property variants (e.g. formation energy via
ModelLightningModule) later. Updates docstrings, the example config comments,
and the test's script-import calls accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nfigs

- Load ASE-readable trajectory files (.extxyz/.traj/...) in addition to
  pymatgen JSON; dispatch by extension in load_structures_labels
- Add stress_unit config option (GPa default, eV/A3 supported); convert
  dataset stresses to MatGL's GPa convention on load, warning when the
  unit is assumed
- Split config_example.yaml into config_train_example.yaml and
  config_finetune_example.yaml with commented optimizer alternatives
- Expand training-script tests: ASE loading, stress conversion, optimizer
  building, single-file split, finetune + train main() smoke tests

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the LR-schedule correctness out of the training script's
TrainingLightningModule subclass and into matgl's MatglLightningModuleMixin:

- configure_optimizers now returns an explicit lr_scheduler config
  (interval=epoch, frequency=1, monitor for ReduceLROnPlateau) instead of
  the ([opt],[sched]) list form, and the manual on_train_epoch_end step is
  removed -- the scheduler now advances exactly once per epoch instead of
  twice
- Add lr_scheduler_interval / lr_scheduler_monitor args to
  ModelLightningModule and PotentialLightningModule
- Drop TrainingLightningModule/set_optim from scripts/train/_common.py;
  build_potential_module now feeds the optimizer/scheduler/monitor straight
  into PotentialLightningModule
- Add regression tests (once-per-epoch stepping, config shape, plateau
  monitor); update the exact energies in test_tensornet_training, which
  shifted with the corrected schedule

Saved/deployed models are unaffected (the Lightning module is never
serialized); only retraining dynamics change. Documented in changes.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When no scheduler is named, build_scheduler now falls back to the same
CosineAnnealingLR MatGL uses in configure_optimizers: T_max=decay_steps
(default 1000) and eta_min=lr*decay_alpha (default 0.01), instead of the
previous T_max=max_epochs / eta_min=0 parameterisation. Example configs
document the default plus the decay_steps/decay_alpha knobs; tests updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mkphuthi mkphuthi requested review from kenko911 and shyuep as code owners June 30, 2026 17:58
@shyuep

shyuep commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

This is fine, but don't we already have a MGLPotentialTrainer? I suggest we work from that and add the config.yaml and CLI.

@shyuep

shyuep commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

🤖 Automated PR review by Claude (posted via @shyuep). Based on static analysis of the diff; no code was executed locally. Note: PR is marked [WIP], so this is preliminary.

Config-driven training scripts + LR-scheduler fix. Well-structured and thoroughly documented. Main comments:

  • The LR-scheduler double-step fix is real and correct. Returning ([opt],[sched]) while also stepping in on_train_epoch_end did advance the schedule twice/epoch. Moving to the explicit lr_scheduler config (interval/frequency, monitor only for ReduceLROnPlateau) is the right fix.
  • ⚠️ Backward-compat / behavior change for all existing users, not just the new scripts: with the double-step removed, default CosineAnnealingLR(T_max=decay_steps) now spans decay_steps epochs instead of decay_steps/2. Retrained models will differ. The changelog documents this and gives the decay_steps/2 workaround — good — but please call it out prominently in release notes since it silently changes existing training runs.
  • 🔹 Per @shyuep's comment: reconcile with the existing MGLPotentialTrainer before adding a parallel training path — worth confirming these scripts build on it rather than duplicating dataset/trainer plumbing.
  • 🔹 lr_scheduler_monitor default "val_Total_Loss" only matters for ReduceLROnPlateau; confirm that metric key is actually logged during validation, else plateau scheduling will error.
  • 🔹 No CI checks are reported on this branch — get lint + tests green (incl. the new tests/scripts/train/ and tests/utils/test_training.py) before this leaves WIP.

Nice work overall; the scheduler bug fix alone is valuable.

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.

2 participants