Skip to content

feat: seedable multi-start draws, alive-versus-step curve, and optional momentum reset - #1482

Merged
Jammy2211 merged 2 commits into
mainfrom
feature/clipper-validation-campaign
Aug 17, 2026
Merged

feat: seedable multi-start draws, alive-versus-step curve, and optional momentum reset#1482
Jammy2211 merged 2 commits into
mainfrom
feature/clipper-validation-campaign

Conversation

@Jammy2211

Copy link
Copy Markdown
Collaborator

Three MultiStartGradient additions 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 seedable

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
: a "multi-seed" study was silently a single-seed one.

The two streams are derived through SeedSequence rather than by offsetting the
seed. seed + stream would make seed 0's resurrection stream identical to seed
1's starting stream, so nominally independent seeds would share draws and
resurrection would replay the starting population.

seed=None keeps 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/mge hst — invisible before.

2. alive_history — the budget-independent survival curve

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.

3. reset_momentum_on_clip — optional, and measured NOT to pay off

Zeroes 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 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.

The measurement says don't use it (autolens_profiling#131, imaging/mge hst,
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-seed
reproducibility, cross-seed divergence, stream independence, alive_history
length/monotonicity, per-coordinate reset, and the params0/grad_sum
must-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

Jammy2211 and others added 2 commits August 16, 2026 18:54
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
@Jammy2211
Jammy2211 merged commit b0a79f0 into main Aug 17, 2026
3 checks passed
@Jammy2211
Jammy2211 deleted the feature/clipper-validation-campaign branch August 17, 2026 17:34
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.

1 participant