Skip to content

Early FWW Wrap MTK-Generated SciMLFunctions in SciMLProblems#4426

Closed
ChrisRackauckas-Claude wants to merge 2 commits intoSciML:masterfrom
ChrisRackauckas-Claude:autospecialize-wrap
Closed

Early FWW Wrap MTK-Generated SciMLFunctions in SciMLProblems#4426
ChrisRackauckas-Claude wants to merge 2 commits intoSciML:masterfrom
ChrisRackauckas-Claude:autospecialize-wrap

Conversation

@ChrisRackauckas-Claude
Copy link
Copy Markdown

Summary

  • When AutoSpecialize is active and the problem is IIP, wraps GeneratedFunctionWrapper inside NonlinearFunction with NonlinearSolveBase.AutoSpecializeCallable at construction time
  • This erases the concrete function type (RuntimeGeneratedFunction hashes) so different MTK models reuse compiled NonlinearSolve code
  • Wrapping happens at NonlinearProblem, NonlinearLeastSquaresProblem, and SCCNonlinearProblem sub-problem construction
  • Uses NonlinearSolveBase.wrapfun_iip so the ForwardDiff extension adds dual-aware signatures, staying in sync with the solver
  • ODEFunction wrapping is unchanged — DiffEqBase.promote_f handles it at solve time
  • Includes FWW v1 compat bump for ModelingToolkitBase (from Allow FunctionWrappersWrappers v1 in ModelingToolkitBase #4424)

Context

PR #4335 switched MTK to AutoSpecialize by default. For ODEs, DiffEqBase.promote_f wraps in FWW at solve time. But for initialization NonlinearProblems, NonlinearSolveBase.maybe_wrap_nonlinear_f skipped wrapping because the parameters are MTKParameters (not Vector{Float64}). This meant each MTK model triggered recompilation through NonlinearSolve for initialization.

This PR fixes that by wrapping eagerly at construction time, when u0 and p types are known.

Verified locally

  • Init NonlinearProblem f.f is AutoSpecializeCallable containing FunctionWrappersWrapper
  • Type erasure works: different models produce identical f.f types ✓
  • remake preserves wrapping ✓
  • All solves succeed with correct results ✓

Dependencies

Test plan

  • CI passes
  • Initialization tests pass with FWW wrapping active

🤖 Generated with Claude Code

When AutoSpecialize is active and the problem is IIP, wrap the
GeneratedFunctionWrapper inside NonlinearFunction with
NonlinearSolveBase.AutoSpecializeCallable at problem construction time.
This erases the concrete function type (RuntimeGeneratedFunction hashes)
so that different MTK models sharing the same state/parameter structure
reuse compiled NonlinearSolve code instead of recompiling per model.

Wrapping happens at:
- NonlinearProblem construction (after process_SciMLProblem returns u0, p)
- NonlinearLeastSquaresProblem construction (same)
- SCCNonlinearProblem sub-problem construction

Uses NonlinearSolveBase.wrapfun_iip which the ForwardDiff extension
overrides with dual-aware signatures, keeping in sync with the solver's
tag standardization.

ODEFunction wrapping is NOT changed — DiffEqBase.promote_f already
handles that at solve time.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
When AutoSpecialize is active and u0/p/t are available, wrap f with
DiffEqBase.wrapfun_iip at ODEFunction construction time. This erases
the GeneratedFunctionWrapper type early, before DiffEqBase.promote_f
runs at solve time.

Requires DiffEqBase >= 6.215.0 (SciML/OrdinaryDiffEq.jl#3335) which
guards promote_f against double-wrapping already-wrapped functions.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
@ChrisRackauckas ChrisRackauckas changed the title Wrap NonlinearFunction with FWW at construction time for norecompile Early FWW Wrap MTK-Generated SciMLFunctions in SciMLProblems Apr 3, 2026
@ChrisRackauckas
Copy link
Copy Markdown
Member

Requires SciML/NonlinearSolve.jl#891 to actually work.

@ChrisRackauckas-Claude
Copy link
Copy Markdown
Author

Benchmark: MTK construction-time FWW wrapping vs solver-time wrapping

Compared the construction-time FWW wrapping in this PR against the baseline (AutoSpecialize set but no construction-time wrapping, relying on solver-side wrapping).

Setup

10 pendulum DAE models with nonlinear algebraic constraint (sin(θ)^2 + cos(θ)^2 + λ - 2 = 0), solved sequentially. Julia 1.11.9.

Results

No MTK wrap With MTK wrap
Init f.f type GeneratedFunctionWrapper AutoSpecializeCallable
Init types identical true true
Model 1 3.773s 3.725s
Models 2-10 avg 0.667s 0.668s
Total (10) 9.78s 9.74s

Analysis

No measurable difference for this benchmark. Both paths report identical init f.f types across models because MTK generates structurally identical RuntimeGeneratedFunction hashes for models with the same initialization equation structure — the RGF hashes depend on the symbolic expression body, not variable names. So there's no recompilation to save in this case.

The construction-time FWW wrapping would only produce a measurable benefit for models with genuinely different initialization equations (different nonlinear structure, not just different parameters/variable names).

Where the win IS measurable

Pure NonlinearProblem benchmark with unique function types (not MTK, manually created structs F1-F5):

FWW-Wrapped (AutoSpecialize) Baseline
NLProb 1 (warmup) 0.06ms 0.11ms
NLProb 2-5 avg ~200ms ~1090ms

~5.5x speedup per new function type when function types actually differ.

AutoSpecialize vs FullSpecialize (the meaningful comparison)

AutoSpecialize FullSpecialize
Model 1 3.75s 2.58s
Models 2-10 avg 0.69s 1.92s
Total (10) 9.95s 19.9s

2.8x speedup per subsequent model, 2x total wall time. This is the real win from AutoSpecialize — the ODE solver path reuses compiled code via DiffEqBase.promote_f. The ~0.69s remaining per-model cost is dominated by OverrideInitData closures and other per-model overhead, not NonlinearSolve dispatch.

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