Skip to content

Guard solve_up Enzyme reverse rule against runtime-activity aliased shadows#3740

Open
SebastianM-C wants to merge 3 commits into
SciML:masterfrom
SebastianM-C:smc/enzyme_fix
Open

Guard solve_up Enzyme reverse rule against runtime-activity aliased shadows#3740
SebastianM-C wants to merge 3 commits into
SciML:masterfrom
SebastianM-C:smc/enzyme_fix

Conversation

@SebastianM-C

Copy link
Copy Markdown
Member

While looking into reducing SciML/SciMLSensitivity.jl#1477 with Claude, I found a potential bug in the Enzyme rule under set_runtime_activity(Reverse) when the u0 is aliased in remake (which happens because SII.setsym_oop returns the prob.u0 if there are no changes). The fix that Claude proposed was to skip accumulation when the shadow aliases the primal, exactly as if it were Const.

@ChrisRackauckas Would this be appropriate or should we always copy in SII.setsym_oop (or both)?

Checklist

  • Appropriate tests were added
  • Any code changes were done in a way that does not break public API
  • All documentation related to code changes were updated
  • The new code follows the
    contributor guidelines, in particular the SciML Style Guide and
    COLPRAC.
  • Any new documentation only uses public API

Additional context

Add any other context about the problem here.

…hadows

Under `set_runtime_activity`, a runtime-inactive value reaches the rule as
Duplicated/MixedDuplicated whose shadow IS the primal (dval === val). The
reverse rule for `DiffEqBase.solve_up` accumulated every non-Const cotangent
into `ptr.dval` unconditionally, so e.g. the du0 cotangent was broadcast-added
into the caller's primal u0 whenever the solved problem's u0 aliased an array
reachable from a Const argument (the common `setsym_oop`/`remake` pattern in
MTK loss functions). The first gradient call returned correct results while
silently corrupting the Const problem's u0; subsequent calls were garbage.

Skip accumulation when the shadow aliases the primal — a runtime-inactive
value accumulates nowhere, exactly as if it were Const.

Found while reducing SciML/SciMLSensitivity.jl#1477 (failure mode B).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ChrisRackauckas

Copy link
Copy Markdown
Member

Seems reasonable but this should definitely get a test.

Tests the runtime-activity scenario fixed in the previous commit: a loss that
reuses the Const problem's own u0 via remake must keep correct gradients across
repeated calls without mutating the primal problem, and genuinely active u0
must still receive its cotangent. Verified against ForwardDiff with a real
SciMLSensitivity adjoint (GaussAdjoint); on the unpatched rule the first call
silently corrupts prob.u0 and the second call's gradient is garbage.

Runs in the Downstream group (adds SciMLSensitivity and Enzyme to that test
environment), guarded out on prerelease Julia like DiffEqBaseEnzymeExt itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@SebastianM-C

Copy link
Copy Markdown
Member Author

Sure, but where would that go? From what I see SciMLSensitivity related testing is done via downstream. Should I PR the test to SciMLSensitivity?

@ChrisRackauckas

Copy link
Copy Markdown
Member

either there or the downstream ad test group

# corrupting subsequent calls. Skip them: a runtime-inactive value
# accumulates nowhere, exactly as if it were `Const`.
if ptr isa MixedDuplicated
ptr.dval[] === ptr.val && continue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in ee3b14d

@wsmoses

wsmoses commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

this is a likely culrprit for errors, like mentioned earlier. Anything we can do to get this merged?

@SebastianM-C

SebastianM-C commented Jun 24, 2026

Copy link
Copy Markdown
Member Author

I added a test, but I'm not sure if it's in the right place, there is also an test/ad/ad_tests.jl which loads SciMLSensitivity, but that would run on OrdinaryDiffEq changes, not DiffEqBase ones from what I understand (and it also does not run on julia 1.12).

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.

3 participants