fix(chaos_optimizer): brake permanent-mutation bug + smooth-threshold generalization - #27
Merged
Conversation
…nary noise Diagnosed via instrumented probes on convergence_mnist_record.py: the spike brake was firing every ~150-350 batches on nothing worse than ordinary per-batch classification noise, unrelated to Hebbian plasticity (fires *more* often with hebb_type=None). Each trigger permanently multiplied d_numerator and d_max by brake_factor, so repeated harmless triggers ratcheted the step scale down with no way back - by epoch 25 on a 3k-subset probe, eff_lr had collapsed 500x and training had effectively frozen, matching the ~83-87% accuracy plateau seen in full 100-epoch runs. Replaced the permanent mutation with a transient brake_ceiling multiplier applied only at the point of the actual parameter update, leaving d/d_numerator/d_max bookkeeping untouched so the estimator keeps learning the true scale while suppressed. The ceiling relaxes geometrically back toward 1.0 every report_loss call, so an isolated spike heals in tens of steps while a fast cascade (genuine divergence) keeps compounding down. Also fixed a secondary bug: the post-spike variance reseed collapsed to 0.0, making the sigma test degenerate for ~20 calls after every fire. Validated: adder 2000 epochs (no divergence), record.py 25-epoch probe (d_max no longer decays, eff_lr recovers instead of collapsing), XOR seeds 42/123, MNIST-3k probe (brake never fires there, unaffected), full pytest suite (285/285). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirms the loss-spike brake fix (d516d3a) at full scale: convergence_mnist_record.py previously froze around epoch 15-20 (the 83-87% plateau the brake bug caused) and now trains cleanly through all 100 epochs, landing at 87.98% zero-config (peak 88.46%, epoch 86). LR set to None. The README's 90.14% "WORLD RECORD" banner predates this optimizer entirely (different scheduler/preset pipeline, since removed) and is left as-is with an added status note per explicit decision - not a regression target, since the script changed too much for a like-for-like comparison. Re-ran the rest of the suite under the fixed brake and refreshed numbers accordingly: MNIST 98.62->98.71%, MNIST Revive 98.54->98.70%, MNIST Tiny 95.15->95.58%, MNIST Scaled 97.38->98.01%, MNIST Embed 94.08->93.71% (within run-to-run noise), Skill Transfer speedup 3.0x->3.6x. Sine Wave/Latch/Stopwatch log excerpts refreshed. - convergence_skill_transfer.py: retuned add_epochs 500->250, mul_epochs 1500->500 - avoids overfitting the small model before transplant, giving a consistent clear win instead of a partial one. - convergence_sine_wave.py: EPOCHS 10000->6800 as a stopgap for a newly-observed late-training instability (loss explodes ~epoch 7900 under the fixed brake) - not yet root-caused, flagged in CHANGELOG as a known issue rather than silently patched over. - Version bumped to 2.6.1 (odyssnet/__init__.py, pyproject.toml, CITATION.cff, CHANGELOG.md) for the brake fix + claim-check fix. reverse_record.py's zero-config experiment stays uncommitted - unresolved, not part of this pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…brake constants The traction limit's rms0 include/exclude test was a hard cutoff sitting exactly on micro_quiet_warm's init std (1e-3) -- on convergence_mnist_record.py, the same seed produced a 16.8x different cap on CPU vs CUDA purely from which side of the cutoff a group's RNG-drawn rms0 landed on. Replaced with a smooth weight ramp (_anchor_weight) over the same boundary numbers; verified zero cap change on the CUDA-run configs the READMEs are actually measured on. Also exposes brake_sigma/brake_ratio/brake_ema_alpha as constructor params (previously unreachable class constants tuned against our own batch~16-32 examples), adds a bias-correction-style warmup for the brake's loss-variance estimate, and fixes Neurogenesis.expand() silently dropping a user's brake config on network growth. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ate_dict gap Review of the previous commit (16b8d98) caught three real defects the test suite had not: 1. The rms0/weight formula in the first attempt was non-monotone in the wrong direction -- a group's contribution to the traction cap could *decrease* as its own rms0 grew, so raising an init scale inside the fade ramp made training more restricted, backwards. Replaced with max(rms0, blend), which guarantees a partially-trusted group can never bind tighter than its own value. 2. That fix's first version blended toward a fixed reference (_TRUST_RMS_FLOOR_HIGH), which passed every test but silently moved convergence_mnist_record.py's own cap from 0.006 to 0.0025: an excluded near-zero group's fallback undercut memory_feedback's real, smaller anchor. Caught only by re-measuring the actual example configs, not by the suite. Now blends toward the smallest already-fully-trusted group's rms0 instead, which reproduces the old cap exactly whenever nothing sits mid-ramp (every bundled example, confirmed on both record.py and the adder) and disables the cap entirely only when literally every group is excluded (a lone all-zero-initialized parameter, no other family to anchor against -- test_adaptive_mode_converges_on_quadratic caught this one directly when an intermediate version regressed it). 3. brake_sigma/brake_ratio/brake_ema_alpha were exposed as plain instance attributes, which torch.optim.Optimizer.state_dict() doesn't serialize -- a save/load round trip would silently revert a customized brake to defaults. Moved into `defaults` so they ride the param groups like brake_factor already does. Re-validated end to end after these corrections: adder 2000 epochs and XOR 42/123 byte-identical to the prior run, record.py's 25-epoch probe identical (d_max flat 0.060141, eff_lr 0.005958), MNIST-3k identical (89.20%), full pytest 297/297. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates OdyssNet’s ChaosGrad (zero-config optimizer) to (1) fix a loss-spike brake behavior that could permanently suppress adaptive step scaling, and (2) generalize the traction-limit anchor selection from hard cutoffs to a smooth fade—aiming to remove discontinuities and improve stability across bundled examples and external user configurations.
Changes:
- Reworked the loss-spike brake to throttle a transient
brake_ceiling(applied-step multiplier) instead of mutating the D-adaptation estimator state, and exposed brake tuning parameters (brake_sigma,brake_ratio,brake_ema_alpha). - Replaced hard include/exclude logic in the traction-limit anchor (
trust_ratio) with a continuous_anchor_weight+ blended reference in_trust_cap. - Updated tests, examples, READMEs, and release metadata for the 2.6.1 release.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/training/test_chaos_optimizer.py | Adds regression and continuity tests for the smoothed traction anchor and the new brake-ceiling behavior. |
| odyssnet/training/chaos_optimizer.py | Implements brake_ceiling, brake warmup/relaxation, exposed brake params, and smooth trust-cap anchoring. |
| odyssnet/utils/neurogenesis.py | Preserves brake configuration (and brake_ceiling) when rebuilding ChaosGrad during expansion. |
| examples/advanced/convergence_mnist_record.py | Switches record example to true zero-config (LR = None). |
| examples/advanced/convergence_mnist_reverse_record.py | Removes explicit lr to use zero-config behavior. |
| examples/advanced/convergence_sine_wave.py | Reduces epoch count as a stopgap for documented late-training instability. |
| examples/advanced/convergence_skill_transfer.py | Retunes epoch counts to reflect updated transfer behavior and runtime expectations. |
| README.md | Refreshes benchmark numbers/log excerpts and adds a status note clarifying the MNIST record provenance vs current optimizer. |
| README_TR.md | Mirrors README benchmark/log updates in Turkish. |
| CHANGELOG.md | Adds 2.6.1 entry describing the optimizer fixes and related example/doc updates. |
| pyproject.toml | Bumps package version to 2.6.1. |
| odyssnet/init.py | Bumps __version__ to 2.6.1. |
| CITATION.cff | Updates citation version to 2.6.1. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+8
to
9
| version: 2.6.1 | ||
| date-released: 2025-12-12 |
Owner
Author
There was a problem hiding this comment.
It's by design. Invention date.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
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
Two rounds of fixes to
ChaosGrad, OdyssNet's zero-config optimizer, both driven by the same standard: fixed constants are fine (AdamW's betas are the precedent) when outcomes are insensitive to them, but not when they silently paper over a problem specific to our own bundled examples while leaving general library users exposed to the same failure mode unprotected.d516d3a): the brake was firing on ordinary per-batch noise (not real divergence) and permanently shrinking the D-adaptation estimator's own state on every trigger, with no recovery path — silently freezingconvergence_mnist_record.pytraining at a ~83-87% plateau. Replaced with a transientbrake_ceilingmultiplier that throttles only the applied step, decoupled from the estimator's bookkeeping, and relaxes back toward 1.0 every call.16b8d98,72b6ac7): the traction limit'srms0include/exclude test was a hard cutoff sitting exactly onmicro_quiet_warm's init std (one of this library's own bundled inits) — the same seed produced a 16.8x different cap on CPU vs CUDA purely from RNG noise landing on either side of the cutoff. Replaced with a smooth ramp. Also exposedbrake_sigma/brake_ratio/brake_ema_alphaas constructor parameters (previously unreachable class constants tuned against our own batch~16-32 examples) and fixedNeurogenesis.expand()silently dropping a user's brake config on network growth.An advisor-led review round (
72b6ac7) on top of the first smoothing attempt caught two real regressions the 293-test suite had already passed: a wrong-direction cap (raising an init scale inside the fade ramp made training more restricted) and a version of the fix that passed every test but silently movedrecord.py's own cap 0.006→0.0025 — only caught by re-measuring the actual example configs, not by re-running old tests. Also fixedbrake_sigma/ratio/ema_alphabeing plain instance attributes thatstate_dict()doesn't serialize (a save/load round-trip would've silently reverted a customized brake to defaults).Full detail on every fix, including exact before/after measurements, is in the
CHANGELOG.md[2.6.1]entry.Numbers moved (re-validated against real runs, not assumed)
convergence_mnist_record.py: previously froze mid-run (~83-87%), now trains cleanly through all 100 epochs, landing at 87.98% (peak 88.46%) zero-config. The README's 90.14% "WORLD RECORD" banner is deliberately left unchanged — it predates this optimizer entirely (a different, since-removed scheduler/preset pipeline), so it isn't a like-for-like regression target. Only a status note was added beneath it explaining the provenance.convergence_mnist_reverse_record.pymoved to zero-config (lrremoved) — this was in-flight, uncommitted WIP for most of this work and was finished/committed independently.Known open issue (not fixed here, documented)
convergence_sine_wave.pyshows a late-training loss explosion around epoch ~7900 of a 10000-epoch run, not yet root-caused.EPOCHSwas reduced to 6800 as a stopgap to avoid the window, not a fix — flagged in the CHANGELOG for future investigation.Test plan
pytest tests/ -q: 297/297 passing (added trust-cap continuity tests, a state_dict round-trip test for the brake config, and a Neurogenesis brake-forwarding test)convergence_mnist_record.py25-epoch diagnostic probe:d_maxflat,effective_lrsteady with brake recovering after each dip🤖 Generated with Claude Code