Forward-over-reverse: CallingConventionMismatchError parsing `idx_jl_getfield_aug` (type-unstable getfield of a Duplicated ODEProblem) — typeunstablerules.jl:1261
## Summary
Forward-over-reverse (nested Enzyme: outer `Forward` over inner `Reverse`) through an ODE `solve` — using **direct differentiation through the solver** (`sensealg = SensitivityADPassThrough()`, i.e. no continuous-adjoint) — fails with an internal Enzyme error while parsing the calling convention of its own type-unstable `getfield` rule:
```
CallingConventionMismatchError: Enzyme hit an internal error trying to parse the julia calling
convention definition for:
idx_jl_getfield_aug(::Val{@NamedTuple{1}}, ::SciMLBase.ODEProblem{Vector{Float64}, ...}, ...)
@ Enzyme.Compiler ~/.julia/packages/Enzyme/.../src/rules/typeunstablerules.jl:1261
expectLen != length(parameters(f))
{} addrspace(10)* ({} addrspace(10)*, {} addrspace(10)**, i32, {} addrspace(10)**)
expectLen=1
```
The inner reverse succeeds (gradient is correct); only the outer forward over it fails, when it differentiates a `getfield` of the `Duplicated` `ODEProblem` through the **type-unstable / generic-runtime** getfield rule `idx_jl_getfield_aug`, whose generated function signature Enzyme then can't match (`expectLen=1` vs the 4-parameter LLVM signature).
## MWE (SciMLSensitivity + OrdinaryDiffEq + DiffEqBase + Enzyme; manual nesting)
```julia
import SciMLSensitivity as SMS # loads the DiffEqBase Enzyme extension + SensitivityADPassThrough
import OrdinaryDiffEq as ODE
import DiffEqBase
import Enzyme
f(u, p, t) = -p[1] .* u
u0 = [1.0]
tspan = (0.0, 1.0)
ts = range(0.0, 1.0, length = 5)
function loss(p)
prob = ODE.ODEProblem(f, u0, tspan, p)
sol = ODE.solve(prob, ODE.Tsit5(); saveat = ts,
sensealg = DiffEqBase.SensitivityADPassThrough()) # differentiate THROUGH the solver
sum(abs2, Array(sol))
end
const RA_R = Enzyme.set_runtime_activity(Enzyme.Reverse)
const RA_F = Enzyme.set_runtime_activity(Enzyme.Forward)
@noinline function grad(p) # inner reverse — works
dp = zero(p)
Enzyme.autodiff(RA_R, Enzyme.Const(loss), Enzyme.Active, Enzyme.Duplicated(p, dp))
dp
end
grad([0.5]) # [-2.4402421907949057] (fine)
Enzyme.autodiff(RA_F, Enzyme.Const(p -> sum(grad(p))), # outer forward over the reverse gradient
Enzyme.Duplicated([0.5], [1.0]))
# CallingConventionMismatchError ... idx_jl_getfield_aug(::Val{@NamedTuple{1}}, ::ODEProblem, ...)
# @ typeunstablerules.jl:1261
```
## Reduction attempts (why no dependency-free MWE)
I tried to reduce this to an Enzyme-only MWE without success (~6 shapes): forward-over-reverse of a `getfield` on an immutable/mutable struct with an active `Vector` field plus an `::Any` field (ODEProblem-like); getfield returning the boxed `Any` field; and forcing the *dynamic* getfield path via `Base.inferencebarrier`, an `::Any`-returning getter closure, and `invokelatest`. **All return the correct derivative** — the type-stable and even the dynamically-dispatched `getfield`s I could construct don't land on the `idx_jl_getfield_aug` type-unstable-getfield rule the way the real `solve_up` reverse rule (getfielding a `Duplicated` `ODEProblem` inside `runtime_generic_*`) does. Filing with the SciML-stack MWE per maintainer preference; happy to keep reducing if a dependency-free repro is required.
## Versions
- Enzyme v0.13.173
- SciMLSensitivity v7.112.2, OrdinaryDiffEq v7.1.1 / OrdinaryDiffEqCore v4.5.0, DiffEqBase v7.6.0, SciMLBase v3.30.1
- Julia 1.11.9
## Context
Part of getting `SecondOrder(AutoEnzyme(Forward), AutoEnzyme(Reverse))` forward-over-reverse Hessians/HVPs working through SciMLSensitivity + OrdinaryDiffEq (SciML/SciMLSensitivity.jl#1427). There are two routes to that second derivative, and each currently hits a distinct Enzyme forward-over-reverse blocker:
- **continuous-adjoint** route → #3277 (`mixed activity for jl_new_struct` in `runtime_generic_fwd`);
- **direct-through-solver** route (this issue, `SensitivityADPassThrough`) → the `idx_jl_getfield_aug` calling-convention mismatch above.
(I used a `Float64` problem to get past the separate `Float32`-only `_ode_init` type-analysis blocker #3276.) Prior cleared blockers in this chain: #3135 → #3137, #3213, #3246 → #3251, #3253 → #3254, #3258 → #3261.
0 条评论