SciML / SciML/ComplementaritySolve.jl

CUDA solver paths error with ForwardDiff >= 1: seed! scalar-indexes GPU dual arrays

Open
#65 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Julia
Stars
3
Forks
2
Avg merge
7h 44m
Merged PRs (30d)
7

Description

Summary

With ForwardDiff ≥ 1 in the environment, every solver that computes jacobians through ForwardDiff (e.g. NonlinearReformulation / BokhovenIterativeAlgorithm with the default SimpleNewtonRaphson() nonlinear solver, whose default AD is AutoForwardDiff) errors on CUDA arrays with:

Scalar indexing is disallowed.
Invocation of getindex resulted in scalar indexing of a GPU array.
...
[6] seed!(duals::CuArray{ForwardDiff.Dual{...}}, x::CuArray{Float32, 1, ...}, seeds::Tuple{...})
  @ ForwardDiff .../ForwardDiff/src/apiutils.jl:95
[7] vector_mode_dual_eval!
[8] vector_mode_jacobian
...
[11] compute_jacobian!! @ SimpleNonlinearSolve/src/utils.jl:153

This is what made the CUDA Core CI job fail on #63 (55 errored tests) once the test environment started resolving ForwardDiff 1.4 (previously held back at 0.10.39 by ParametricMCPs).

Root cause (upstream, ForwardDiff)

ForwardDiff 1.x rewrote all four seed! methods in src/apiutils.jl to use scalar setindex! loops over structural_eachindex:

for (i, idx) in zip(1:N, structural_eachindex(duals, x))
    duals[idx] = Dual{T,V,N}(x[idx], seeds[i])
end

ForwardDiff 0.10 used broadcast-based seeding, which worked on GPU arrays:

dual_inds = 1:N
duals[dual_inds] .= Dual{T,V,N}.(view(x, dual_inds), seeds)

So this is an upstream ForwardDiff regression for GPU arrays, not something specific to this package. The last green CUDA Core run on main (2026-04-13) resolved ForwardDiff 0.10.39; with the dependency updates in #63 the environment resolves ForwardDiff 1.4 (required by NonlinearSolve ≥ 4.17), and the GPU jacobian paths break.

Verified workaround (not applied)

Broadcast-based seed! overloads for GPUArraysCore.AbstractGPUArray{<:Dual} restore the 0.10 behavior and make the full CUDA Core suite pass (verified on the GPU CI runners in #63 before being reverted, and locally on JLArrays in both vector and chunk mode). This was prototyped as type piracy in #63 (commit d1d3d70) and removed per review (c3a8fa9, 06b7c1d). The proper home for that fix is ForwardDiff itself (e.g. a GPUArraysCore package extension upstream).

Note ForwardDiff.gradient on GPU arrays has an additional scalar-indexing site in the gradient extraction path (extract_gradient_chunk!), so fixing seed! alone covers jacobians (what this package's solvers use) but not gradients.

Consequences here until fixed upstream

  • The CUDA Core CI job fails when the environment resolves ForwardDiff ≥ 1 (which is forced once NonlinearSolve ≥ 4.17 is in the environment).
  • GPU usage of ForwardDiff-jacobian-based solvers requires ForwardDiff 0.10, which is incompatible with NonlinearSolve ≥ 4.17 / NNlib ≥ 0.9.32 / CUDA ≥ 6.

🤖 Generated with Claude Code

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with ForwardDiff/src/apiutils.jl, then inspect the failing call from SimpleNonlinearSolve/src/utils.jl:153 and the CUDA Core CI failures from #63. Confirm the GPU jacobian path and determine the appropriate upstream integration point for GPU-compatible seeding. Done means the supported dependency environment passes the CUDA Core solver tests without type piracy in this package.

Written by the indexing model from the issue text.

Assessment

Tech stack
julia
Domain
backend, ci-cd
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.