ext/Mooncake: bridge copyto!(::ComponentArray, ::Mooncake.Tangent)#369
Merged
ChrisRackauckas merged 2 commits intoMay 20, 2026
Merged
Conversation
`Mooncake.tangent_type(ComponentVector{P,Vector{P},Axes}) ===
Mooncake.Tangent{@NamedTuple{data::Vector{P}, axes::NoTangent}}`.
That tangent is not an `AbstractArray`, so the generic
`Base.copyto!(::AbstractArray, ::Any)` fallback tries to iterate it and
throws `MethodError: no method matching iterate(::Mooncake.Tangent{…})`.
Optimization.jl + AutoMooncake + ComponentArrays hits this path because
`DifferentiationInterface.value_and_gradient!(::AutoMooncake, …)` writes
the gradient into a user-supplied `grad` buffer with an unconditional
`copyto!(grad, new_grad)`. The plain-`Vector` case works only because
Mooncake's tangent_type for `Vector{Float64}` is `Vector{Float64}` itself.
Define `Base.copyto!` for the flat-Array-backed `ComponentArray` case so
the tangent's `data` field flows directly into the ComponentArray's
underlying storage. SubArray-backed CVs are unaffected (the same kind of
overload could be added there once a concrete failure surfaces).
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Contributor
|
I'm not sure that this is the correct fix. I think since Mooncake v0.5.25 (more specifically since chalk-lab/Mooncake.jl#1103), every package that defines types with "friendly tangents" needs to add some overloads. See e.g. the similar discussion around StaticArrays:
as well as more general background:
|
…atrix test
A `ComponentVector` obtained from `getproperty(::ComponentVector, ::Symbol)` on a
nested parent is `<:SubArray`-backed, so Mooncake's `tangent_type` for it nests
an inner `Tangent` that mirrors the SubArray's `(parent, indices, offset1,
stride1)` fields. The flat-Array `copyto!` overload doesn't dispatch on that
shape, so `DI.value_and_gradient!` would still throw a `MethodError: iterate`.
Add a parallel overload for the SubArray-backed CV tangent shape, symmetric to
the existing `_increment_subarray_fdata!` block. As with that path, the copy is
only well-defined when the view fully covers its parent (the SubArray indices
are not recoverable from Mooncake tangent shape alone); the partial-cover case
throws a clear `ArgumentError` rather than silently producing wrong gradients.
Also add a `ComponentMatrix` copyto! regression test — `Matrix{P} <: Array{P}`
already matches the flat-Array signature, this just locks in the behavior.
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Member
|
It probably could be handled more generically, but with the current development progression I have been suggested to go this direction, and this patch at least unblocks users for today. If things change in the core repo then we can remove the patch. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Bridge
copyto!(::ComponentArray, ::Mooncake.Tangent)for both the flat-Array-backed and SubArray-backedComponentVectortangent shapes so Mooncake gradients can be written back into aComponentArray-shaped buffer.The bug
Root cause
DifferentiationInterface.value_and_gradient!(::AutoMooncake, …)writes the gradient into the user-suppliedgradbuffer with an unconditionalcopyto!(grad, new_grad)(DI'sDifferentiationInterfaceMooncakeExt/onearg.jl:153). For a plainVector{Float64}primal this works becauseMooncake.tangent_type(Vector{Float64}) === Vector{Float64}, so the source is an array. For aComponentArrayprimal, however,Mooncake.tangent_typeis aMooncake.Tangentstruct — not anAbstractArray— so the genericBase.copyto!(::AbstractArray, ::Any)fallback drops into the iterate-based path atabstractarray.jl:934and throwsMethodErrorforiterate.DI's twoarg and forward call sites already handle the "tangent ≠ primal-shape" case via
dy isa tangent_type(Y) ? dy : _copy_to_output!!(prep.dy_righttype, dy). Thevalue_and_gradient!path is the one missed case; a parallel issue could be filed against DI to apply the same pattern there. Until that lands, definingcopyto!for the (ComponentArray,Mooncake.Tangent) pairs fully unblocks the user-visible failure in Optimization.jl + AutoMooncake + ComponentArrays.Coverage
I probed the obvious variations against this branch. Results:
ComponentVectorComponentArray(u=…, p=ComponentArray(…)))ComponentMatrix(2D backing)Matrix{P} <: Array{P}— overload (a) covers it; regression test addedFloat32CV_FloatLikecovers itArgumentError(symmetric to existing_increment_subarray_fdata!guard); test addedDI.gradient(f, prep, ::AutoMooncake, ::CV)Mooncake.Tangent, not a CV — outside CA's reach, needs DI/Mooncake-level fixOptimizationProblem(optf, parent_cv.u)whereuis a flat 1D componentparent_cv.ureturns a rawSubArray, not a CV — Mooncake-on-SubArrays issue, outside CA's scope(7) is a latent DI contract issue: the non-mutating
DI.gradientpath runs_copy_output(new_grad)and returns the deep-copied Tangent, so the caller gets back a type that doesn't match the input's shape. The right fix is in DI (mirror the_copy_to_output!!pattern) or Mooncake's_copy_outputdispatch. Mentioning here for visibility but deliberately not addressing in this PR.(8) requires a Mooncake-side
copyto!(::Vector, ::Mooncake.Tangent{…SubArray-tangent…})since the destination isn't aComponentArrayat all once the wrapper is dropped.What this PR does
ext/ComponentArraysMooncakeExt.jl: adds twoBase.copyto!overloads —(ComponentArray{P,N,<:Array{P}}, Tangent{@NamedTuple{data::Array{P}, axes::NoTangent}})→ copiessrc.fields.dataintogetdata(dest).(ComponentArray{P,N,<:AbstractArray{P}}, Tangent{@NamedTuple{data::Tangent{@NamedTuple{parent::Vector{P}, indices::NoTangent, offset1::NoTangent, stride1::NoTangent}}, axes::NoTangent}})→ copiessrc.fields.data.fields.parentintogetdata(dest), guarded by alength(parent) == length(getdata(dest))full-cover check that throws a clearArgumentErroron partial-cover views (symmetric to the existing_increment_subarray_fdata!block).test/autodiff/autodiff_tests.jl: adds four targeted tests in theMooncaketestset — flat CV,ComponentMatrix, full-cover SubArray-backed CV, and@test_throws ArgumentErrorfor partial-cover SubArray-backed CV.Project.toml: bumpsversion = "0.15.39".Local verification
Autodifftest group passes withMooncaketestset at 15/15 (was 7/7 onmain).End-to-end with the dev'd
ComponentArrays:Previous CI on the (a)-only commit was 18/18 green (all matrix entries for Julia 1, lts, pre × Core/Autodiff/Downstream/Reactant/GPU/nopre, plus Runic, build, doc-build).
Test plan
Coregroup passesAutodiffgroup passes (newcopyto!(::CV, ::Mooncake.Tangent)cases intest/autodiff/autodiff_tests.jl)Downstreamgroup passesReactantgroup passesnopregroup passes