feat: seedable multi-start draws, alive-versus-step curve, and optional momentum reset - #1482
Merged
Merged
Conversation
Two additions the prior-support validation campaign (autolens_profiling#128, phase 2) cannot be run without. `seed` — the search's two random draws, the broad starting points and the resurrection redraw, were hardcoded to `default_rng(0)` / `default_rng(1)`. Seeding `random`/`numpy` reaches only the initializer, so every run of a model drew the SAME starting population and a "multi-seed" study was silently a single-seed one. `seed=None` keeps the historical fixed seeds exactly, so existing fits are bit-identical and the argument is purely additive. The two streams are derived through `SeedSequence` rather than by offsetting the seed: `seed + stream` would make seed 0's resurrection stream the same sequence as seed 1's starting stream, so nominally independent seeds would share draws and resurrection would replay the starting population. `alive_history` — the number of living lanes per step, written to `search_internal`. The existing lane counters are survival INTEGRALS: a dead lane keeps adding to them every subsequent step, so the same death curve reads ~60% at 150 steps and ~75% at 300, and two runs at different budgets cannot be compared on the scalar at all. The curve is the budget-independent quantity, and until now it existed only in the progress log at `iterations_per_log` cadence — visible to a human reading stdout, unavailable to any analysis. Scope: this seeds the draws THIS search owns, not the framework. The initializer and the sampler-owned generators remain unseedable; that wider gap is filed separately. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRG2X7Af8zdF3vnsWoiK4U
`reset_momentum_on_clip` zeroes the optimizer moments wherever the clipper just projected a coordinate back onto its prior box. Default False, so the clipping path is unchanged unless asked for. Projection alone leaves a lane holding the exact velocity that carried it out of the box, so the next step drives it into the same wall and it is re-projected onto the same bound indefinitely — counted alive, permanently pinned, still paying a full likelihood-and-gradient evaluation every step. The reset is per-coordinate: a lane clipped in one parameter keeps its momentum in the others. Moment fields are targeted BY NAME, not by shape. Shape matching is actively wrong here: Prodigy's `params0` and `grad_sum` carry the same `(n_starts, n_params)` shape as the moments, and `params0` anchors its learning-rate estimate — zeroing it would corrupt the step size for the rest of the run rather than resetting momentum. A regression test pins that. Measured (autolens_profiling#131, imaging/mge hst, 16x3000, fp64) the arm does NOT pay off and the campaign recommends against using it: same converged answer, but deaths 2 -> 2523, one MORE lane pinned, +39% wall on seed 0, and on seed 1 it gives back nearly all of plain clipping's gain (-120880.6 -> -137783.6). Shipped default-off so the measurement is reproducible, not because it is recommended. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRG2X7Af8zdF3vnsWoiK4U
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.
Three
MultiStartGradientadditions the prior-support validation campaign(autolens_profiling#131) could not be run without. All are additive and
default-off/None, so existing fits are bit-identical.
1.
seed— the multi-start draws were never seedableThe search's two random draws — the broad starting points and the resurrection
redraw — were hardcoded to
default_rng(0)/default_rng(1). Seedingrandom/numpyreaches only the initializer, so every run of a model drew thesame starting population: a "multi-seed" study was silently a single-seed one.
The two streams are derived through
SeedSequencerather than by offsetting theseed.
seed + streamwould make seed 0's resurrection stream identical to seed1's starting stream, so nominally independent seeds would share draws and
resurrection would replay the starting population.
seed=Nonekeeps the historical fixed seeds exactly.This matters more than it sounds. With the argument in place, identical
settings swing the answer 171,272 nats between seeds 0 and 1 on
imaging/mgehst — invisible before.2.
alive_history— the budget-independent survival curveThe number of living lanes per step, written to
search_internal.The existing lane counters are survival integrals: a dead lane keeps adding
to them every subsequent step, so the same death curve reads ~60% at 150 steps
and ~75% at 300, and two runs at different budgets cannot be compared on the
scalar at all. The curve is the budget-independent quantity, and until now it
existed only in the progress log at
iterations_per_logcadence — visible to ahuman reading stdout, unavailable to any analysis.
3.
reset_momentum_on_clip— optional, and measured NOT to pay offZeroes the optimizer moments wherever the clipper (#1477) just projected a
coordinate back onto its prior box. Default
False.Projection alone leaves a lane holding the exact velocity that carried it out of
the box, so the next step drives it into the same wall and it is re-projected
onto the same bound indefinitely — counted alive, permanently pinned, still
paying a full likelihood-and-gradient evaluation every step. The reset is
per-coordinate: a lane clipped in one parameter keeps its momentum in the others.
Moment fields are targeted by name, not by shape. Shape matching is actively
wrong here: Prodigy's
params0andgrad_sumcarry the same(n_starts, n_params)shape as the moments, andparams0anchors itslearning-rate estimate — zeroing it would corrupt the step size for the rest of
the run rather than resetting momentum. A regression test pins that.
The measurement says don't use it (autolens_profiling#131,
imaging/mgehst,16x3000, fp64): same converged answer, but deaths 2 → 2523, one more lane
pinned, +39% wall on seed 0, and on seed 1 it gives back nearly all of plain
clipping's gain (-120880.6 → -137783.6). It ships default-off so the measurement
is reproducible, not because it is recommended.
Tests
test/non_linear/search/mle/test_multi_start_gradient.py— same-seedreproducibility, cross-seed divergence, stream independence,
alive_historylength/monotonicity, per-coordinate reset, and the
params0/grad_summust-not-be-zeroed regression.
Follow-up
The campaign's diagnosis is that lanes reach the walls because box widths span
40x while the search steps in physical space with one global step scale — the
cause-side fix (per-parameter step scaling) is queued as a separate task, and
#1481 tracks which other searches need prior-support handling at all.
🤖 Generated with Claude Code