You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Phase 1 of the multi-start gradient v2 promotion (autolens_workspace_developer#101). Promotes learning-rate-free / optax.contrib rule support into af.AbstractMultiStartGradient by resolving rules from optax.contrib, moving optimizer state from a stacked batch to per-start jax.vmap init/update (so lr-free rules' global scalar estimates no longer couple across starts), guarding steps with optax.apply_if_finite, and adding af.MultiStartProdigy. The headline #101 result: on the MGE cell Prodigy is bit-identical to hand-tuned Adam (+31787.84) with no learning rate. The restart-on-death recovery layer is deferred to Phase 2 (built on this PR's vmapped state).
Plan
Resolve the optax rule from optax, falling back to optax.contrib (unlocks prodigy, dadapt, mechanic, dog/dowg, momo, schedule-free).
Refactor optimizer state: stacked (n_starts, ndim) → per-start jax.vmap(opt.init) / vmapped update. Elementwise rules (Adam/ADABelief/Lion) stay numerically identical; lr-free rules stop sharing one global scalar across starts.
Wrap the rule in optax.apply_if_finite as an in-step NaN guard; forward the value= kwarg for rules that need the loss (momo). Bump the optax pin to >=0.2.5.
Add af.MultiStartProdigy (learning-rate-free) and export it from autofit/__init__.py.
Numpy-only unit tests (contrib rule resolution, Prodigy defaults, dict round-trip); JAX MGE validation (prodigy == adam, per-start state independence) in autofit_workspace_test.
Detailed implementation plan
Affected Repositories
PyAutoFit (primary)
autofit_workspace_test (JAX validation, follow-on within the same task)
autofit/non_linear/search/mle/multi_start_gradient/search.py — add a rule-resolver helper: getattr(optax, m, None) or getattr(optax.contrib, m), raising a clear error if neither has it. Replace both getattr(optax, self.optax_method)(self.learning_rate) sites (resume branch ~L193, fresh branch ~L204).
Same file, _fit: replace stacked opt_state = optimizer.init(params) and optimizer.update(grads, opt_state, params) with jax.vmaped init and a vmapped update step; wrap the resolved rule in optax.apply_if_finite(rule, max_consecutive_errors=...). Branch the update on whether the rule consumes value= (momo) — mirror pix_lr_free.py's needs_value / step_update. Preserve the search_internal resume path (opt_state is now a vmapped pytree — verify optax.tree_utils.tree_get round-trips).
Handle the learning-rate-free case: rules like prodigy take no learning_rate positional — resolver/subclass must construct them without forcing an lr (a MultiStartProdigy._default_learning_rate = None + rule-build indirection).
Add class MultiStartProdigy(AbstractMultiStartGradient) with optax_method = "prodigy"; export in autofit/__init__.py next to the existing three.
pyproject.toml — bump jax = [..., "optax"] to "optax>=0.2.5".
test_autofit/non_linear/search/mle/test_multi_start_gradient.py — numpy-only: Prodigy defaults/optax_method, contrib rule resolution, dict round-trip for Prodigy. Keep JAX out of the library suite.
autofit_workspace_test/scripts/searches/ — add a MultiStartProdigy.py JAX validation mirroring MultiStartAdam.py; assert prodigy reaches the Adam optimum on the MGE cell and that per-start state is independent.
python -m pytest test_autofit/non_linear/search/mle/ (numpy). JAX MGE parity in autofit_workspace_test. Existing Adam/ADABelief/Lion results must be unchanged (elementwise rules are vmap-invariant).
@PyAutoFit
Difficulty: medium
Autonomy: supervised
Priority: normal
Status: draft
Phase 1 of the multi-start gradient v2 promotion (#101). The restart-on-death
(resurrection) recovery layer is Phase 2
(multistart_resurrection_restart_on_death.md), which builds on the per-start
vmapped state introduced here.
Promote the optax.contrib / learning-rate-free rule support from
autolens_workspace_developer#101 into af.AbstractMultiStartGradient
(autofit/non_linear/search/mle/multi_start_gradient/search.py):
optax.contrib rule resolution. The current getattr(optax, self.optax_method) misses optax.contrib (prodigy et al.).
Resolve the rule from optax then fall back to optax.contrib.
Per-start vmapped optimizer state. The current stacked-(n_starts, ndim)
state init COUPLES the lr-free rules' global scalar estimates (prodigy /
dadapt d, DoG max_dist, mechanic scale, momo Polyak) across starts.
Init/update the optimizer per start via jax.vmap (elementwise rules — the
existing Adam/ADABelief/Lion — are numerically unaffected; the Remove use of sym link from Emcee, Dynesty. #101 benchmark
showed no per-eval cost). This is the load-bearing refactor Phase 2 also
depends on.
optax.apply_if_finite in-step guard. Wrap the rule so a single
non-finite step zeroes the update instead of NaN-poisoning (optax >= 0.2.5
forwards momo's value= kwarg). Bump the optax pin in pyproject.toml to optax>=0.2.5. (Resurrection in Phase 2 is the recovery layer on top of
this guard, which alone only latches at the cliff edge.)
af.MultiStartProdigy. Add the concrete search — on the MGE cell prodigy
is bit-identical to hand-tuned adam (+31787.84) with NO learning rate, the
headline Remove use of sym link from Emcee, Dynesty. #101 result. Export it from autofit/__init__.py alongside the
existing MultiStart* classes.
Wiring reference: autolens_workspace_developer/searches_minimal/pix_lr_free.py
(the per-start jax.vmap(opt.init) / vmapped update, the needs_value branch
for momo's value=). Evidence: searches_minimal/lr_free_findings.md
(#101, Phase 1 — MGE cell table).
Unit tests numpy-only (config knobs, rule resolution incl. contrib, dict
round-trip, Prodigy defaults); JAX validation (prodigy == adam on the MGE cell,
vmapped-state independence) in autofit_workspace_test per the established split.
Existing Adam/ADABelief/Lion results must be unchanged.
Overview
Phase 1 of the multi-start gradient v2 promotion (autolens_workspace_developer#101). Promotes learning-rate-free /
optax.contribrule support intoaf.AbstractMultiStartGradientby resolving rules fromoptax.contrib, moving optimizer state from a stacked batch to per-startjax.vmapinit/update (so lr-free rules' global scalar estimates no longer couple across starts), guarding steps withoptax.apply_if_finite, and addingaf.MultiStartProdigy. The headline #101 result: on the MGE cell Prodigy is bit-identical to hand-tuned Adam (+31787.84) with no learning rate. The restart-on-death recovery layer is deferred to Phase 2 (built on this PR's vmapped state).Plan
optax, falling back tooptax.contrib(unlocks prodigy, dadapt, mechanic, dog/dowg, momo, schedule-free).(n_starts, ndim)→ per-startjax.vmap(opt.init)/ vmappedupdate. Elementwise rules (Adam/ADABelief/Lion) stay numerically identical; lr-free rules stop sharing one global scalar across starts.optax.apply_if_finiteas an in-step NaN guard; forward thevalue=kwarg for rules that need the loss (momo). Bump theoptaxpin to>=0.2.5.af.MultiStartProdigy(learning-rate-free) and export it fromautofit/__init__.py.Detailed implementation plan
Affected Repositories
Branch Survey
Suggested branch:
feature/multistart-contrib-vmapped-state(worktree conflict check: clear)Implementation Steps
autofit/non_linear/search/mle/multi_start_gradient/search.py— add a rule-resolver helper:getattr(optax, m, None) or getattr(optax.contrib, m), raising a clear error if neither has it. Replace bothgetattr(optax, self.optax_method)(self.learning_rate)sites (resume branch ~L193, fresh branch ~L204)._fit: replace stackedopt_state = optimizer.init(params)andoptimizer.update(grads, opt_state, params)withjax.vmaped init and a vmapped update step; wrap the resolved rule inoptax.apply_if_finite(rule, max_consecutive_errors=...). Branch the update on whether the rule consumesvalue=(momo) — mirrorpix_lr_free.py'sneeds_value/step_update. Preserve thesearch_internalresume path (opt_state is now a vmapped pytree — verifyoptax.tree_utils.tree_getround-trips).learning_ratepositional — resolver/subclass must construct them without forcing an lr (aMultiStartProdigy._default_learning_rate = None+ rule-build indirection).class MultiStartProdigy(AbstractMultiStartGradient)withoptax_method = "prodigy"; export inautofit/__init__.pynext to the existing three.pyproject.toml— bumpjax = [..., "optax"]to"optax>=0.2.5".test_autofit/non_linear/search/mle/test_multi_start_gradient.py— numpy-only: Prodigy defaults/optax_method, contrib rule resolution, dict round-trip for Prodigy. Keep JAX out of the library suite.autofit_workspace_test/scripts/searches/— add aMultiStartProdigy.pyJAX validation mirroringMultiStartAdam.py; assert prodigy reaches the Adam optimum on the MGE cell and that per-start state is independent.Key Files
autofit/non_linear/search/mle/multi_start_gradient/search.py— rule resolver, vmapped state, apply_if_finite guard,MultiStartProdigy.autofit/__init__.py— exportMultiStartProdigy.pyproject.toml—optax>=0.2.5.test_autofit/non_linear/search/mle/test_multi_start_gradient.py— numpy unit tests.autofit_workspace_test/scripts/searches/MultiStartProdigy.py— JAX MGE validation.autolens_workspace_developer/searches_minimal/pix_lr_free.py,lr_free_findings.md.Testing
python -m pytest test_autofit/non_linear/search/mle/(numpy). JAX MGE parity in autofit_workspace_test. Existing Adam/ADABelief/Lion results must be unchanged (elementwise rules are vmap-invariant).Original Prompt
Click to expand starting prompt
Multi-start gradient search v2 — optax.contrib rules + per-start vmapped state
Type: feature
Target: autofit
Repos:
Difficulty: medium
Autonomy: supervised
Priority: normal
Status: draft
Phase 1 of the multi-start gradient v2 promotion (#101). The restart-on-death
(resurrection) recovery layer is Phase 2
(
multistart_resurrection_restart_on_death.md), which builds on the per-startvmapped state introduced here.
Promote the optax.contrib / learning-rate-free rule support from
autolens_workspace_developer#101 into
af.AbstractMultiStartGradient(
autofit/non_linear/search/mle/multi_start_gradient/search.py):getattr(optax, self.optax_method)missesoptax.contrib(prodigy et al.).Resolve the rule from
optaxthen fall back tooptax.contrib.(n_starts, ndim)state init COUPLES the lr-free rules' global scalar estimates (prodigy /
dadapt
d, DoGmax_dist, mechanic scale, momo Polyak) across starts.Init/update the optimizer per start via
jax.vmap(elementwise rules — theexisting Adam/ADABelief/Lion — are numerically unaffected; the Remove use of sym link from Emcee, Dynesty. #101 benchmark
showed no per-eval cost). This is the load-bearing refactor Phase 2 also
depends on.
optax.apply_if_finitein-step guard. Wrap the rule so a singlenon-finite step zeroes the update instead of NaN-poisoning (optax >= 0.2.5
forwards momo's
value=kwarg). Bump theoptaxpin inpyproject.tomltooptax>=0.2.5. (Resurrection in Phase 2 is the recovery layer on top ofthis guard, which alone only latches at the cliff edge.)
af.MultiStartProdigy. Add the concrete search — on the MGE cell prodigyis bit-identical to hand-tuned adam (+31787.84) with NO learning rate, the
headline Remove use of sym link from Emcee, Dynesty. #101 result. Export it from
autofit/__init__.pyalongside theexisting
MultiStart*classes.Wiring reference:
autolens_workspace_developer/searches_minimal/pix_lr_free.py(the per-start
jax.vmap(opt.init)/ vmappedupdate, theneeds_valuebranchfor momo's
value=). Evidence:searches_minimal/lr_free_findings.md(#101, Phase 1 — MGE cell table).
Unit tests numpy-only (config knobs, rule resolution incl. contrib, dict
round-trip, Prodigy defaults); JAX validation (prodigy == adam on the MGE cell,
vmapped-state independence) in autofit_workspace_test per the established split.
Existing Adam/ADABelief/Lion results must be unchanged.