ITADN

MethodError: no method matching MixedDuplicated(::T, ::T) — runtime_generic_augfwd / create_activity_wrapper passes plain shadow where Base.RefValue{T} is required

#3126OpenChrisRackauckas-Claude 创建于 2026-05-26
## Description `Enzyme.gradient(set_runtime_activity(Reverse), Const(loss), tunables)` throws a `MethodError: no method matching MixedDuplicated(::T, ::T)` when the loss returns (and is evaluated through) a value whose type has `FunctionWrappersWrappers.FunctionWrappersWrapper` deep in its type parameters — e.g. an `ODESolution` whose `ODEFunction` carries a `FunctionWrappersWrapper` (the default for `@mtkbuild`/MTK-built systems). The root site is `Enzyme.Compiler.create_activity_wrapper` (`src/rules/jitrules.jl:14`): ```julia elseif aref == MixedState if Width == 1 return quote Base.@_inline_meta MixedDuplicated(primarg, shadowarg) # <-- here end ``` This emits `MixedDuplicated(primarg, shadowarg)` where `shadowarg` is the same shape as `primarg`. But `EnzymeCore.MixedDuplicated`'s only constructor requires `dx::Base.RefValue{T}`: ```julia # EnzymeCore.jl struct MixedDuplicated{T} <: Annotation{T} ... @inline MixedDuplicated(x::T1, dx::Base.RefValue{T1}, check::Bool=true) where {T1} = new{T1}(x, dx) end ``` So the same `(primarg, shadowarg)` pair that works for the `Duplicated` branch a few lines below blows up in the `MixedState` branch. The `runtime_generic_augfwd` path needs to box `shadowarg` into a `Ref{T}` (or take a separate path) before calling `MixedDuplicated`. This is the underlying wall that keeps surfacing through SciML's MTK-Enzyme stack — every workaround we apply downstream eventually trips into the same constructor. ## MWE (SciML stack) I tried for ~30 min to reduce this to a `FunctionWrappersWrappers`-only MWE without success — the trigger seems to require both (a) a value with FWW in deep type params *and* (b) Enzyme's MixedActivity classification, which fires off the SciML solve dispatch chain. Pure `FunctionWrappersWrapper` + a simple struct does not promote anything to `MixedDuplicated`, so the bad call site is never hit. Filing with the smallest SciML-stack reproducer I have: ```julia using ModelingToolkit, OrdinaryDiffEq, Enzyme using ModelingToolkit: t_nounits as t, D_nounits as D using SciMLSensitivity import SciMLStructures as SS using SymbolicIndexingInterface @parameters a b @variables x(t) y(t) eqs = [ D(x) ~ a * x + y, 0 ~ x^2 - b * y, # algebraic constraint -> ODESolution carries FWW deep in type params ] @mtkbuild sys = ODESystem(eqs, t) prob = ODEProblem(sys, [x => 1.0], (0.0, 1.0), [a => -0.5, b => 2.0], guesses = [y => 1.0]) tunables, repack, _ = SS.canonicalize(SS.Tunable(), parameter_values(prob)) # Forward solve is fine sol = solve(prob, Rodas5P()) loss = let prob = prob, repack = repack p -> begin new_prob = remake(prob; p = repack(p)) sol = solve(new_prob, Rodas5P(); abstol = 1e-8, reltol = 1e-6) sum(sol) end end grad = Enzyme.gradient(set_runtime_activity(Enzyme.Reverse), Enzyme.Const(loss), tunables) ``` ## Error ``` MethodError: no method matching MixedDuplicated(::ODESolution{Float64, 2, Vector{Vector{Float64}}, Nothing, Nothing, Vector{Float64}, Vector{Vector{Vector{Float64}}}, Nothing, ODEProblem{Vector{Float64}, Tuple{Float64, Float64}, true, MTKParameters{Vector{Float64}, Vector{Float64}, Tuple{}, Tuple{}, Tuple{}, Tuple{}}, ODEFunction{true, SciMLBase.AutoSpecialize, FunctionWrappersWrappers.FunctionWrappersWrapper{ Tuple{ FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{Float64}, Vector{Float64}, MTKParameters{...}, Float64}}, FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{ForwardDiff.Dual{...}}, Vector{ForwardDiff.Dual{...}}, MTKParameters{...}, Float64}}, FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{ForwardDiff.Dual{...}}, Vector{Float64}, MTKParameters{...}, ForwardDiff.Dual{...}}}, FunctionWrappers.FunctionWrapper{Nothing, Tuple{Vector{ForwardDiff.Dual{...}}, Vector{ForwardDiff.Dual{...}}, MTKParameters{...}, ForwardDiff.Dual{...}}} }, FunctionWrappersWrappers.AllowNonIsBits, FunctionWrappersWrappers.SingleCacheStorage }, ... }, ... }, Rodas5P{...}, OrdinaryDiffEqCore.InterpolationData{...}, ... }, ::ODESolution{Float64, 2, ...}) # <-- shadow arg passed as same type, NOT Base.RefValue{...} Closest candidates are: MixedDuplicated(::T1, ::Base.RefValue{T1}) where T1 MixedDuplicated(::T1, ::Base.RefValue{T1}, ::Bool) where T1 ``` Both `primarg` and `shadowarg` are passed as `::ODESolution{...}`, but the constructor needs `(::T, ::Base.RefValue{T})`. ## Expected behavior Either: - `create_activity_wrapper` boxes `shadowarg` into a `Ref` before constructing `MixedDuplicated` (the simple fix), or - a correct gradient gets returned (matches `FiniteDifferences`). ## Versions - Julia 1.12.4 - Enzyme.jl v0.13.150 - EnzymeCore v0.8.x - FunctionWrappersWrappers.jl v1.9.1 - ModelingToolkit / SciMLBase / OrdinaryDiffEq / SciMLSensitivity (current master) ## Related - This is the same dispatch site as #2994 (`Fix1` throws `MethodError on Duplicated(::Base.Fix1, ::Base.RefValue{...})`), but symmetric: there the *runtime* call was emitting a `Ref` where a plain value was expected (`Duplicated` branch); here the *generated wrapper* emits a plain value where a `Ref` is expected (`MixedDuplicated` branch). They look like two sides of the same Ref-vs-plain-shadow confusion in `runtime_generic_augfwd`. - Possibly related: #3021 (`MixedReturnException` in `runtime_generic_augfwd` with `NonlinearSolvePolyAlgorithm`), #2712 (higher-order: handle mixed duplicated on llvm side), #2677 (`test_reverse` with `MixedDuplicated`). ## Downstream context (SciML) - Originally reported in SciML/SciMLSensitivity.jl#1359 (MTK DAE + GaussAdjoint/EnzymeVJP). - Workaround tracking: SciML/SciMLSensitivity.jl#1455, SciML/SciMLSensitivity.jl#1456 (re-routing tests through `Enzyme.autodiff` with `Duplicated` instead of `set_runtime_activity` + `Const`). - `NonlinearSolve` had the same wall and worked around it by adding `store_original::Val{false}` to the polyalg so the returned `NonlinearSolution`'s `original` field no longer carries the `@generated`-heavy union of sub-algorithm caches (SciML/NonlinearSolve.jl#938). The equivalent surgery for `ODESolution` (strip / box the type-parameter-heavy components of the interpolant) is what the SciML-side fix would look like, but the underlying Enzyme constructor mismatch is what we'd really like to resolve. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
6 条评论