fix: avoid duplicate function calls with FiniteDiff - #1061
Merged
gdalle merged 7 commits intoAug 15, 2026
Conversation
…ward prep
prepare_pushforward_nokwarg allocated a Base.RefValue{Bool} plus an
eager copy(y)/similar(y) on every call, even when
prepare_pushforward_same_point is never used, breaking the
zero-allocation benchmark test. Replace the mutable Ref with an
immutable, type-parameterized same_point::Val{SP} field, and defer
allocating f_in to prepare_pushforward_same_point, which now returns
a new prep instead of mutating the old one in place.
This also fixes a latent bug where copyto!(prep.f_in, ...) would
throw for immutable one-arg outputs (e.g. SVector), since copyto!
requires a mutable destination.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1061 +/- ##
===========================================
- Coverage 97.39% 86.88% -10.52%
===========================================
Files 142 139 -3
Lines 8259 8240 -19
===========================================
- Hits 8044 7159 -885
- Misses 215 1081 +866
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The Val-based rewrite applied to twoarg.jl in the previous commit
broke the two-arg Jacobian-via-pushforward hot path: DI.jacobian
calls prepare_pushforward_same_point on every single jacobian() call
(not just once at prepare_jacobian time), and the two-arg case relies
on that call mutating a buffer preallocated once in
prepare_pushforward_nokwarg, so repeated jacobian() calls stay
allocation-free. Returning a fresh prep/buffer on every same-point
call (as the Val-based rewrite did) reintroduces an allocation on
every jacobian() call instead, which is what broke the
"Benchmarking sparse" CI job (Scenario{:jacobian,:in} diffsquare!).
The two-arg prepare_pushforward_nokwarg's eager Ref/buffer allocation
was never actually the cause of any CI failure (no test measures
prepare_pushforward alone for two-arg functions), unlike the one-arg
case fixed in the previous commit, where f(x) allocates its own
output on every call regardless, so switching to a fresh
Val-based prep there is free. Two-arg f! is in-place and doesn't
allocate on its own, so the persistent, mutated buffer is what makes
repeated same-point calls free; keep that design here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
gdalle
approved these changes
Aug 15, 2026
gdalle
left a comment
Member
There was a problem hiding this comment.
Codecov failures are unrelated. Design is kind of ugly but it gets the job done, and should speed up every sparse Jacobian by a factor 2 if I'm not mistaken
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.
This PR was opened by a coding agent, please disregard it until @gdalle has reviewed it
Summary
Supersedes #1054. Makes
DI.prepare_pushforward_samepointcallf/f!once and reuses that value insideFiniteDiff.finite_difference_jvp/finite_difference_jvp!, instead of calling it again — partial fix for SciML/OrdinaryDiffEq.jl#4162.That change alone made plain
prepare_pushforward(never followed by_same_point) allocate unconditionally, breaking the FiniteDiff zero-allocation CI test. Fixed by:Ref(false)+ eager buffer with an immutable, type-parameterizedsame_point::Val{SP}flag, allocated only insideprepare_pushforward_same_point. Also fixes a latent bug wherecopyto!into an immutable output (e.g.SVector) would throw.prepare_pushforward_same_pointcalls) — needed so the sparse-Jacobian hot path, which callsprepare_pushforward_same_pointon everyjacobian()call, stays allocation-free.All FiniteDiff CI jobs (Bench + Back, 1.10/1.12) pass.
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com