ITADN

Forward-over-reverse: CallingConventionMismatchError parsing `idx_jl_getfield_aug` (type-unstable getfield of a Duplicated ODEProblem) — typeunstablerules.jl:1261

#3280OpenChrisRackauckas-Claude 创建于 2026-07-05
## 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 条评论