Make solver resume work and keep the training history - #29
Open
marisbasha wants to merge 2 commits into
Open
marisbasha wants to merge 2 commits into
marisbasha wants to merge 2 commits into
Conversation
Reopening a network directory: MultiTaskSolver put delete_if_exists into the config it passes to NetworkDir. datamate stores that key when it creates the directory but removes it from the passed config when the directory exists, so opening the same directory again with the same config raised FileExistsError (incompatible config). The flag is now passed through datamate's delete_if_exists context instead, imported from datamate or, for datamate < 1.0, from datamate.directory. The context is only entered when deletion is requested, so an enclosing delete_if_exists context still applies. train-single: the FileExistsError above also stopped an accidental rerun of a trained network from training over its checkpoints. The script now raises a FileExistsError that names resume=true and delete_if_exists=true when the directory already has checkpoints and resume is not set. recover(): the method called resolve_checkpoints with four arguments against a one-argument signature (TuragaLab#23), then read checkpoint.index, checkpoints.index and checkpoints.path, none of which exist. It now resolves "best" through best_checkpoint_default_fn and an int as a position into the sorted checkpoints, e.g. -1 for the last one. Resumed iteration: checkpoints store the last completed iteration, self.iteration - 1, and recover() assigned it back unchanged. A resume repeated one iteration, and resuming from the initial checkpoint set the iteration to -1, where the scheduler picked the final learning rate. recover() now restores self.iteration as the stored value plus one. Training history: train() started the loss and activity lists empty on every call and then overwrote dir.loss, dir.activity* and dir.loss_<task>, so continuing training or resuming kept only the iterations of the last call. The lists now start from the stored history up to the current iteration.
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Legacy directories remain unable to resume, and checkpoint bookkeeping becomes inconsistent after recovering a non-latest checkpoint.
Review effort: Balanced
Findings: 2
Open (2)
What changed in this PR
Fixes solver resume behavior and preserves training history across continued runs.
Changes:
- Corrects checkpoint recovery and iteration restoration.
- Preserves prior loss and activity history.
- Adds CLI safeguards and resume regression tests.
| File | Description |
|---|---|
flyvis/solver.py |
Updates directory handling, history retention, and recovery. |
flyvis_cli/training/train_single.py |
Prevents accidental checkpoint overwrite. |
tests/test_solver.py |
Tests continued training and recovery. |
tests/test_train_single.py |
Tests CLI resume behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…current Older directories: directories created before the previous commit store delete_if_exists in their config, so opening them with the same config still raised FileExistsError. MultiTaskSolver now opens such a directory by path when the stored flag is the only difference to the passed config. Any other difference still raises. Checkpoint index: checkpoint() incremented _last_chkpt_ind and _curr_chkpt_ind separately. After recovering an older or the best checkpoint, the next checkpoint was saved under the last index but recorded the current index as the recovered one plus one, and a later recover() of that index returned early. The current index is now set to the newly saved checkpoint.
This branch has not been deployed
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.

Summary
Fixes #23.
flyvis train-single ... resume=truefailed in three places: reopening the network directory, the rest ofrecover()after the call from the issue, and the restored iteration. Continuing training also overwrote the stored loss history.Changes
Reopening a network directory
MultiTaskSolverputdelete_if_existsinto the config it passes toNetworkDir. datamate stores the key when it creates the directory. When the directory already exists, it removes the key from the passed config before comparing. So opening the same directory again with the same config raisedFileExistsError(incompatible config: passed=['-delete_if_exists'], stored=['+delete_if_exists: False']). This happens with datamate 0.2.7 and 1.0.The flag is now passed through datamate's
delete_if_existscontext instead of the config. The context is imported fromdatamate, or fromdatamate.directoryfor datamate < 1.0. It is only entered when deletion is requested, so an enclosingdelete_if_existscontext still applies as before.Directories created before this change still have the key stored, and datamate always drops it from the passed config of an existing directory. When the stored flag is the only difference to the passed config,
MultiTaskSolvernow opens such a directory by path. Any other difference still raises.Rerunning train-single on a trained network
The
FileExistsErrorabove also stopped an accidental rerun of a trained network. Without it, the same command would open the directory and train from iteration 0 over the existing checkpoints.train_single.pynow raises aFileExistsErrorwhen the directory already has checkpoints andresumeis not set. The message namesresume=trueanddelete_if_exists=true.recover()
Besides the four-argument
resolve_checkpointscall reported in #23, the body readcheckpoint.index,checkpoints.indexandcheckpoints.path, none of which exist. So fixing only the call failed on the next line.recover()now resolves"best"withbest_checkpoint_default_fn, and an int as a position into the sorted checkpoints. So-1is the last one, as used by train-single.Restored iteration
Checkpoints store the last completed iteration,
self.iteration - 1, andrecover()assigned it back unchanged. A resume repeated one iteration. Resuming from the initial checkpoint set the iteration to -1, where the scheduler picked the final learning rate.recover()now setsself.iterationto the stored value plus one, and to 0 for checkpoints without an iteration, such as the pretrained ones.Checkpoint index after recovery
checkpoint()incremented_last_chkpt_indand_curr_chkpt_indseparately. After recovering an older or the best checkpoint, the next checkpoint was saved under the last index but recorded the current index as the recovered one plus one. A laterrecover()of that index then returned early. The current index is now set to the newly saved checkpoint.Training history
train()started the loss and activity lists empty on every call. It then overwrotedir.loss,dir.activity,dir.activity_min,dir.activity_maxanddir.loss_<task>at each checkpoint. Continuing training with a largern_iters, or resuming, kept only the iterations of the last call, andEnsembleView.training_lossshowed partial curves. The lists now start from the stored history up to the current iteration.Testing
Four new tests in
tests/test_solver.pytrain a small solver on the mock Sintel data.n_itersto 4, and trains again. It checks thatdir.losshas 4 entries and that the first 2 are unchanged."best", and checks the iteration, checkpoint index and network parameters. It then continues to 4 iterations, and checks thatdelete_if_exists=Truestill clears the directory.delete_if_existsin its config, as earlier versions did. It checks thatMultiTaskSolveropens it with the same config, and still raises for a differentn_iters.A new
tests/test_train_single.pyrunstrain_single.pyas a subprocess on the mock data. It writes the initial checkpoint withtrain=false checkpoint_only=true. It checks that the same command withoutresumeis refused, then thatresume=truetrains ton_iterswith the full loss history.All five fail on main. On the branch they pass with datamate 1.0.0, 0.2.7 and the current datamate main. With the CLI check removed, the subprocess test fails because the rerun trains over the checkpoints.
I also ran train-single by hand on the mock data, with each run stopped at its third checkpoint and then resumed with
resume=true. Three cases: a new directory, a directory recreated withdelete_if_exists=true, and a new directory created withdelete_if_exists=true. On main every resume fails, withFileExistsErroror theTypeErrorfrom #23. On the branch each resumes from the last checkpoint and finishes with the full loss history. That holds with datamate 1.0.0, 0.2.7 and the current datamate main. A run started with main's train-single, which storesdelete_if_exists: false, also resumes on the branch with datamate 1.0.0 and 0.2.7.Full suite (
-m "not require_download and not require_large_download and not gpu",test_examples.pyandtest_sintel.pyexcluded): 196 passed, 12 skipped on main; 201 passed, 12 skipped on the branch, with datamate 1.0.0 and with 0.2.7.require_downloadtests against the pretrained models: 21 passed on both.tests/test_sintel.py, run offline with the mock data: 14 passed on both.ruff checkandruff format(0.5.5) are clean on the changed files.