Snapshot the fit parameters, so an interrupted fit is not a lost fit - #155
Open
davidwalter2 wants to merge 1 commit into
Open
Snapshot the fit parameters, so an interrupted fit is not a lost fit#155davidwalter2 wants to merge 1 commit into
davidwalter2 wants to merge 1 commit into
Conversation
Everything a fit has learned lives in one in-memory vector until the output
file is written at the very end. A fit that is killed, hits a wall clock
limit, or dies on the way to its output therefore leaves nothing at all
behind, however close to the minimum it had got -- and the fits this matters
for are the ones that run for days.
Write that vector to disk instead. A snapshot is deliberately the smallest
thing load_fitresult accepts, the parameter values and their names, so it
resumes through the existing path with no new format and no change to how
results are read:
rabbit_fit.py input.hdf5 -o out/ --externalPostfit snapshot.hdf5
rabbit_fit.py input.hdf5 -o out/ --externalPostfit snapshot.hdf5 --noFit
No covariance is stored: it does not exist until the Hessian is computed,
and load_fitresult already treats it as optional. Bin-by-bin parameters are
re-profiled on load.
Snapshots are written when the minimizer is interrupted (SIGINT/SIGTERM),
when it fails, and when it converges -- the last covering the stretch
between the minimum and the output being closed, which on a large model
means the Hessian, the impacts and the postfit histograms. --snapshotInterval
adds periodic ones; it is off by default and the other three do not depend
on it.
Two details are load-bearing rather than incidental.
The values written are physical. Under preconditioning the minimiser's
iterate is in internal coordinates, and a snapshot of those would load
without complaint and be silently wrong. Conversion happens when the point
is recorded rather than when it is written, which also closes a window that
would otherwise be minutes wide: during a preconditioner rebuild the stored
transform no longer matches the stored iterate.
The write is atomic. Snapshots exist for processes dying at moments they did
not choose, and that includes mid-write; writing in place would leave a
truncated file where a good one used to be, making the safety net the thing
that destroys the result.
The signal handler writes immediately rather than asking the minimizer to
stop at the next iteration. On a large model an iteration can run for hours,
and a scheduler that sends SIGTERM follows it with SIGKILL long before then.
It then restores the previous handler and re-raises, so the process still
dies as the sender intended, with the right exit status.
rabbit.snapshot imports neither the fitter nor TensorFlow, which is what
makes it usable from a signal handler and testable in a subprocess that
starts in seconds rather than minutes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018rYVNwxQvuUYz5z9wt6ddb
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.
Independent of #153 and #154 — branched from
main, one new module plus smallhooks.
The problem
Everything a fit has learned lives in one in-memory vector until the output
file is written at the very end. A fit that is killed, hits a wall clock limit,
or dies on the way to its output leaves nothing at all behind, however close
to the minimum it had got. That is cheap for a five-minute fit and expensive for
one that has been running for days.
What this adds
rabbit/snapshot.pywrites that vector to disk. A snapshot is deliberately thesmallest thing
load_fitresultaccepts — the parameter values and their names —so it resumes through the path that already exists, with no new format and no
change to how results are read:
No covariance is stored: it does not exist until the Hessian is computed, and
load_fitresultalready treats it as optional. Bin-by-bin parameters arere-profiled on load. A snapshot is a few hundred kB.
When one is written
killon a fit someone decided to stop--snapshotInterval, off by defaultThe first three do not depend on
--snapshotInterval. Nothing is written unless--snapshotFileor--snapshotIntervalis given; with an interval and nofilename it defaults to
<outdir>/<outname>_snapshot.hdf5.Three details that are load-bearing
The values written are physical. Under preconditioning the minimizer's
iterate is in internal coordinates, and a snapshot of those would load without
complaint and be silently wrong. Conversion happens when the point is recorded
rather than when it is written, which also closes a window that would otherwise
be minutes wide: during a preconditioner rebuild the stored transform no longer
matches the stored iterate.
The write is atomic. Snapshots exist for processes dying at moments they did
not choose, and that includes mid-write. Writing in place would leave a truncated
file where a good one used to be, making the safety net the thing that destroys
the result. Each snapshot goes to a temporary file in the same directory
(
os.replaceis only atomic within a filesystem) and is moved into place.The signal handler writes immediately rather than asking the minimizer to
stop at the next iteration. On a large model an iteration can run for hours, and
a scheduler that sends SIGTERM follows it with SIGKILL long before then — a
cooperative stop would arrive too late to be the safety net this is for. The
handler then restores the previous handler and re-raises, so the process still
dies as the sender intended and with the right exit status; the fit's own
control flow is unchanged.
Notes
rabbit.snapshotimports neither the fitter nor TensorFlow. That is what makesit usable from a signal handler and testable in a subprocess that starts in
seconds instead of minutes (importing TensorFlow currently costs over two
minutes on a loaded machine).
The diff to
fitter.pylooks larger than it is: ignoring whitespace it is 33lines added and 1 removed, the rest being the minimizer loop reindenting into a
withblock.This does not checkpoint the minimizer's internal state — trust radius, Lanczos
basis, preconditioner — so a resumed fit restarts its trust region at the
snapshot point. Near a minimum that is cheap; mid-descent it costs some
re-convergence.
Testing
tests/test_snapshot.pycovers the round trip through the realload_fitresult(so it fails if the layout ever drifts from what--externalPostfitaccepts), that the preconditioner is undone, that a rebuilttransform does not retroactively remap an older iterate, that a failed write
leaves the previous snapshot intact and no temporary file behind, the interval
logic, and — in a subprocess — that SIGTERM leaves a correct snapshot on disk
and the process still dies by SIGTERM. 74 tests pass on this branch.
🤖 Generated with Claude Code
https://claude.ai/code/session_018rYVNwxQvuUYz5z9wt6ddb