Fix sum(::AbstractVectorOfArray) over-constraining the element type (#595)#597
Merged
ChrisRackauckas merged 1 commit intoMay 30, 2026
Conversation
…ciML#595) `Base.sum(VA::AbstractVectorOfArray{T})` asserted `sum(sum, VA.u)::T`, forcing the result to equal the recursive element type `T`. That is wrong whenever summation does not return the element type: - integer promotion: `sum(VectorOfArray([Int8[1,2,3]]))` produces an `Int64`, so `::Int8` threw a `TypeError` (a regression with no AD involved); - ReverseDiff: the eltype is `TrackedReal{V,D,<:TrackedArray}` (elements carry their origin array) but a computed sum is `TrackedReal{V,D,Nothing}`, so the assertion threw, breaking `ReverseDiff.gradient(x -> sum(VectorOfArray([x])), x)`. The assertion was originally added (69798c3) to stop Julia 1.10's inference from widening the self-recursive `sum(::AbstractVectorOfArray)` to `Any` on deeply nested VectorOfArrays. Removing it restores correctness but reintroduces that inference regression. Instead, assert the type `sum` actually produces, i.e. the `Base.add_sum` accumulator type `Base.promote_op(Base.add_sum, T, T)`. This still pins the return type for inference (so `@inferred sum(VA[VA[zeros(4,4)]])` keeps passing on the LTS) while being correct for promoting and AD eltypes. Tested on Julia 1.10, 1.11, 1.12 and 1.13-rc1. Adds regression tests: Int8/Float32 promotion in interface_tests.jl and the ReverseDiff MWE from the issue in adjoints.jl (ReverseDiff added to test target). Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
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.
Fixes #595.
Problem
sum(::AbstractVectorOfArray)was defined as:The
::Tassertion forces the result to equal the recursive element typeT. That is wrong whenever summation does not return the element type:The reported ReverseDiff failure. For
VectorOfArray([tunables])underReverseDiff.gradient,T = TrackedReal{Float64,Float64,<:TrackedArray}— each element carries its originTrackedArrayin the third type parameter. A computed sum, however, is a freshTrackedReal{Float64,Float64,Nothing}(no origin), so the assertion throws:It is not AD-specific.
sum(VectorOfArray([Int8[1,2,3]]))also throwsTypeError: expected Int8, got a value of type Int64— ordinary integer promotion (Int8 → Int64) trips the same assertion.Why the assertion existed
It was added in 69798c3 to stop Julia 1.10 LTS inference from widening the self-recursive
sum(::AbstractVectorOfArray)(aVectorOfArraymay containVectorOfArrays) toAny, which is what@inferred sum(VA[VA[zeros(4,4)]])guards against. Simply deleting::Trestores correctness but reintroduces that inference regression (verified: the nested case infersAnyagain).Fix
Assert the type
sumactually produces — theBase.add_sumaccumulator type — instead of the element type:add_sumis the exact reduction operatorBase.sumuses, sopromote_op(add_sum, T, T)isInt64forInt8,Float64forFloat64, andTrackedReal{…,Nothing}for ReverseDiff.promote_opis computed by inference over the scalar eltype (no VoA recursion), so it still pins the return type and keeps@inferredgreen on the LTS, while being correct for promoting and AD eltypes. If inference cannot determineadd_sum(::T,::T)it returnsAny, degrading gracefully to a no-op assertion (never wrong).Tests
test/interface_tests.jl:Int8/Float32promotion (sum(VA[Int8[1,2,3]]) === Int64(6), …) plus@inferred.test/adjoints.jl: the ReverseDiff MWE from the issue (single- and nested-VoA gradients).ReverseDiffadded to the test target (it is already a[weakdeps]/compat entry).Verified locally on Julia 1.10.11, 1.11.9, 1.12.6, and 1.13.0-rc1 — all correctness and inference checks pass. Real test files pass: Partitions 115/115, Interface 139/139, Adjoint 13/13. Runic check is clean.
🤖 Generated with Claude Code