Skip to content

Thread ForwardDiff chunk size through FunctionWrapper compilation#1287

Merged
ChrisRackauckas merged 2 commits intoSciML:masterfrom
ChrisRackauckas-Claude:forwarddiff-chunksize-wrapper
Feb 22, 2026
Merged

Thread ForwardDiff chunk size through FunctionWrapper compilation#1287
ChrisRackauckas merged 2 commits intoSciML:masterfrom
ChrisRackauckas-Claude:forwarddiff-chunksize-wrapper

Conversation

@ChrisRackauckas-Claude
Copy link
Contributor

Summary

  • Add _forwarddiff_chunksize helper to read the new SciMLBase.forwarddiff_chunksize trait
  • Thread Val(CS) through get_concrete_problempromote_fwrapfun_iip for both ODE and DAE problems
  • Add parameterized dualgen(T, Val(CS)) in the ForwardDiff extension
  • Add 3-arg wrapfun_iip(ff, inputs, Val(CS)) that compiles FunctionWrapper variants with chunk=CS for u-related duals (Jacobian) and chunk=1 for t-related duals (time derivative)
  • Add 3-arg fallback wrapfun_iip(ff, inputs, ::Val) in norecompile.jl for when ForwardDiff extension isn't loaded

Context

DiffEqBase PR #1284 enabled FunctionWrapper wrapping for mass matrix ODEProblems. The FunctionWrapper variants were compiled with Dual{..., 1} (chunk=1 hardcoded in dualgen). When algorithms use a different chunk size (e.g. AutoForwardDiff(chunksize=3)), the solver creates Dual{..., 3} values but the wrapper only has Dual{..., 1} variants, causing NoFunctionWrapperFoundError.

The key insight is that u-related duals (Jacobian computation) use the algorithm's chunk size, while t-related duals (time derivative) always use chunk=1 since it's scalar differentiation. The 3-arg wrapfun_iip handles this by creating variants with different chunk sizes for the u-derivative vs t-derivative paths.

Depends on: SciML/SciMLBase.jl#1244

Test plan

  • Precompilation succeeds with the changes
  • Val(1) default preserves existing behavior when trait returns 0
  • Tested end-to-end with OrdinaryDiffEqRosenbrock DAE AD tests (8/8 pass)
  • Rosenbrock convergence tests still pass (853/853)
  • CI

🤖 Generated with Claude Code

Use the new `SciMLBase.forwarddiff_chunksize` trait to compile FunctionWrapper
variants with the algorithm's actual chunk size instead of hardcoding chunk=1.

Changes:
- Add `_forwarddiff_chunksize` helper in solve.jl
- Thread `Val(CS)` through `get_concrete_problem` → `promote_f` → `wrapfun_iip`
- Add parameterized `dualgen(T, Val(CS))` in ForwardDiff extension
- Add 3-arg `wrapfun_iip(ff, inputs, Val(CS))` in ForwardDiff extension
- Add 3-arg fallback `wrapfun_iip(ff, inputs, ::Val)` in norecompile.jl

This fixes NoFunctionWrapperFoundError when algorithms specify a non-default
chunk size (e.g. AutoForwardDiff(chunksize=3)) on mass matrix problems.

Requires SciMLBase >= 2.143.0.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
…ranching

Now that SciMLBase.forwarddiff_chunksize returns Val{N}(), dispatch on
it directly via _resolve_chunksize(::Val{0}) = Val(1) instead of runtime
Int comparison. Removes Val() wrapping at call sites since
_forwarddiff_chunksize already returns Val.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
@ChrisRackauckas-Claude
Copy link
Contributor Author

Updated to dispatch on Val from the trait directly instead of runtime branching.

Now that SciMLBase.forwarddiff_chunksize returns Val{N}(), the helper becomes:

_forwarddiff_chunksize(::Nothing) = Val(1)
_forwarddiff_chunksize(alg) = _resolve_chunksize(SciMLBase.forwarddiff_chunksize(alg))
_resolve_chunksize(::Val{0}) = Val(1)
_resolve_chunksize(v::Val) = v

No runtime Int comparison or Val() wrapping needed. Call sites simplified from Val(_forwarddiff_chunksize(alg)) to _forwarddiff_chunksize(alg).

@ChrisRackauckas
Copy link
Member

locally handled.

@ChrisRackauckas ChrisRackauckas merged commit 92bfd6b into SciML:master Feb 22, 2026
0 of 46 checks passed
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.

2 participants