Skip to content

Fix MultiSteps.has_updated after rejected mini-steps - #1837

Open
Linxiushen wants to merge 1 commit into
google-deepmind:mainfrom
Linxiushen:codex/multisteps-update-event-20261025
Open

Linxiushen wants to merge 1 commit into
google-deepmind:mainfrom
Linxiushen:codex/multisteps-update-event-20261025

Conversation

@Linxiushen

Copy link
Copy Markdown

After an emitted MultiSteps update, rejecting the next mini-step preserves mini_step == 0 and gradient_step > 0. has_updated therefore returns True again even though that call returned zero updates and did not advance the optimizer. Callers gating logging, evaluation or checkpoint cadence on this predicate receive a false update event.

On unmodified main 9a1f892f52e28c18eec4d72b1001731cf20da532, CPU/JIT reproduction with MultiSteps(sgd(0.1), 2, should_skip_update_fn=skip_not_finite) gives:

Input mini-step Update has_updated
finite 0 False
finite -0.1 True
NaN, rejected 0 True (expected False)

Record the last skip decision independently of the arbitrary user-provided skip_state, and exclude rejected calls from has_updated. Counters, wrapped optimizer state and accumulated gradients still remain unchanged on rejection. This leaves the separate outgoing-update masking and accumulator-reset work in #1824 and #1834 untouched.

The 13 new cases cover eager/JIT execution, accumulation lengths 1 and 2, both built-in skip predicates, a custom predicate with empty diagnostic state, repeated rejection, partial accumulation, finite recovery, preserved optimizer state, and serialization of initial/emitted/skipped states.

Validation on Windows, Python 3.12, JAX 0.8.0, Flax 0.12.0 (CPU):

  • python -m pytest -q optax/transforms/_accumulation_test.py optax/transforms/_masking_test.py optax/transforms/_combining_test.py: 60 passed, 29 subtests passed.
  • Ruff on both changed files, Pyink on the added test range, repository license-header check and git diff --check pass.
  • The same original reproduction passes with the patch. No GPU/distributed execution or performance measurement was performed.

Compatibility: MultiStepsState gains a defaulted sixth boolean field, last_step_skipped. Existing five-argument construction and its JIT update work. The serialized PyTree schema changes: strict old Flax state dictionaries require explicit migration by adding last_step_skipped=False; the old checkpoint cannot reveal its last skip event. New-schema Flax roundtrips are tested; other checkpoint engines are untested.

AI assistance was used to develop and locally validate this change.

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