Skip to content

Add init keyword to sum and sum! rules - #870

Closed
Huyvux12 wants to merge 1 commit into
JuliaDiff:mainfrom
Huyvux12:fix/issue-868-sum-rules-init
Closed

Huyvux12 wants to merge 1 commit into
JuliaDiff:mainfrom
Huyvux12:fix/issue-868-sum-rules-init

Conversation

@Huyvux12

@Huyvux12 Huyvux12 commented Nov 7, 2026

Copy link
Copy Markdown

Closes #868

Problem

The sum/sum! rules did not accept the init keyword that the
corresponding Base methods accept, so a call matching a rule's
positional types but passing init threw a MethodError instead of
applying the rule (or falling back to AD). For example, on main:

frule((NoTangent(), ones(3)), sum, rand(3); init=0.0)  # MethodError
rrule(sum, rand(3); init=0.0)                           # MethodError

Fix

Add init to every sum/sum! rule in src/rulesets/Base/mapreduce.jl
(10 sites: frule/rrule for sum(::Tuple), sum(x; dims), sum!,
sum(f, ::Tuple), sum(f, ::AbstractArray) incl. the fast and general
paths, sum(abs2, …) and its config-dispatched wrapper). init is
non-differentiable — it gets no tangent, as the foldl rule already
does. For sum!, the keyword is passed through to the tangent too,
so init=false accumulates into ẏ exactly as it does into y.

One subtlety: the internal _InitialValue() sentinel (used when no
init is given) must never reach the @non_differentiable Bool
rules, which forward all keywords into Base.sum. The sum(f, ::Tuple)
rrule and the config sum(abs2, …) wrapper therefore only pass init
when a real one was given; regression tests cover the Bool-valued
case both with and without init.

No new rules for generic iterables (Generator, Dict, …) — those
keep falling back to AD, as the issue specifies.

Proof

Base commit: 30cf1f6b4c4f19c191b7a734c0d0369357c83860 (main).

Before/after on Julia 1.13.3 (repro script exercising all listed
sites, incl. the exact examples from the issue):

  • Before: 9 cases MethodError, 3 cases nothing via fallback.
  • After: all cases correct with verified values, e.g.
    frule((NoTangent(), ones(3)), sum, [1.0, 2.0, 3.0]; init=0.0) == (6.0, 3.0),
    rrule(sum, [1.0, 2.0, 3.0]; init=10.0)[1] == 16.0,
    sum! with init=false gives primal [13.0, 27.0] and tangent [3.0, 4.0].
    All no-init control cases unchanged.

Focused test suite (Pkg.test(; test_args=["rulesets/Base/mapreduce"]),
Julia 1.13.3): 2050 passed, 0 failed, 22 broken (pre-existing
@test_broken), 2072 total.
The full Pkg.test suite was not run
locally (very heavy); CI covers it, including Julia 1.10.

Tests added in test/rulesets/Base/mapreduce.jl: init variants for
sum(::Tuple), sum(x; dims), sum! (init=true/false), sum(abs2, …),
sum(f, ::Tuple), sum(f, xs) (fast and general paths, incl. dims +
init), a "sum with init (#868)" testset with the issue's exact
examples, and Bool-valued regression tests for the sentinel fix.
Note: plain tests use a Float64 init (finite differences are only
accurate to 1e-9 in one precision — Base.sum(x; dims=2, init::Float32)
returns Float32), while the @gpu comparisons use a Float32 init.

Notes for reviewers

The sum/sum! rules did not accept the init keyword that the Base
methods accept, so calls matching a rule's positional types but
passing init threw a MethodError instead of applying the rule.
This adds init (non-differentiable, no tangent, as in the foldl
rule) to all sum/sum! rules, and tests.
@mcabbott

mcabbott commented Nov 7, 2026

Copy link
Copy Markdown
Member

I think this will give silently wrong answers to this:

gradient((x,i) -> sum(x; init=i), [1,2,3.], 4.0)

instead of a MethodError. That might be why this never got written in the first place.

@codecov

codecov Bot commented Nov 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.66%. Comparing base (30cf1f6) to head (b9a82bb).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #870   +/-   ##
=======================================
  Coverage   96.65%   96.66%           
=======================================
  Files          28       28           
  Lines        3615     3625   +10     
=======================================
+ Hits         3494     3504   +10     
  Misses        121      121           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@devmotion

Copy link
Copy Markdown
Member

Yes, that's the general problem of non-differentiable keyword arguments: JuliaDiff/ChainRulesCore.jl#384

Also makes me realize that #868 was already discussed in #656

@devmotion

Copy link
Copy Markdown
Member

At least in case of the frules, the question is also whether these rules should exist at all (see also #867)

@Huyvux12 Huyvux12 closed this Nov 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sum rules do not match the signatures of the Base methods frule for sum doesn't work for Generator

3 participants