Skip to content

Conversation

avik-pal
Copy link

@avik-pal avik-pal commented Aug 13, 2025

fixes #759

  • Jacobian support for GPU Arrays has been restored
  • ForwardDiff.gradient now supports GPU Arrays

cc @ChrisRackauckas @devmotion

Copy link

codecov bot commented Aug 13, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.05%. Comparing base (463e830) to head (5b8ffab).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #760      +/-   ##
==========================================
+ Coverage   89.79%   90.05%   +0.25%     
==========================================
  Files          11       12       +1     
  Lines        1039     1066      +27     
==========================================
+ Hits          933      960      +27     
  Misses        106      106              

☔ View full report in Codecov by Sentry.
📢 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.

idxs = collect(
Iterators.drop(ForwardDiff.structural_eachindex(result), offset)
)[1:chunksize]
result[idxs] .= partial_fn.(Ref(dual), 1:chunksize)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this not have an inference issue due to losing static information about size? I would think this needs to be ntuple unless it can prove things about size.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would still be type-stable, it would just have dynamism in the function that would slow it down a bit during the broadcast.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the chunksize is already an Int, so I don't think we will have any benefit of using an ntuple

ChrisRackauckas added a commit that referenced this pull request Aug 14, 2025
Noted in #759 (comment), GPU is completely untested in ForwardDiff.jl, so this sets up the buildkite pipeline. I setup the backend and all, and just took a few tests from #760 to seed it. The point of this isn't really to be a comprehensive set of GPU tests but rather to update this repo to have the standard tools the other repos have so GPU doesn't regress again/more.
@avik-pal avik-pal force-pushed the ap/gpu_arrays branch 3 times, most recently from 19e8423 to da2efb7 Compare August 16, 2025 00:34
@avik-pal avik-pal force-pushed the ap/gpu_arrays branch 2 times, most recently from 2536221 to 11540a0 Compare August 18, 2025 23:03
devmotion
devmotion previously approved these changes Aug 19, 2025
@KristofferC
Copy link
Collaborator

KristofferC commented Aug 19, 2025

In #472, the seed! (etc) functions were written in a generic (non-typespecific) way that should have supported GPU arrays. This PR adds further specializations to seed! for some specific types by using extensions. But why does the previous approach not work anymore? That one seemed better since it supports non-fast scalar indexing for arrays that are not GPU arrays.

Has it been properly explored if the existing functions can be written in an alternative way that would support both fast and non-fast scalar arrays with the same generic code (which would avoid any new extensions)?

Edit: I had missed that #739 reverted some of #472.

@devmotion
Copy link
Member

Yes, on the master branch seeding is (again) performed without broadcasting. Depending on the structural array type the set of indices are not readily available in an allocation-free broadcastable form (e.g. set of uppertriangular indices for UpperTriangular) and hence I reverted the code back to iterating over indices - without considering that it would break GPU compatibility.

If we want to avoid these allocations (and the broadcasting overhead) for non-GPU arrays, I don't immediately see how this issue could be solved by a generic implementation. Possibly the amount of code duplication could be reduced by introducing a helper function or branch that based on the type of the input array switches between broadcasting and iterating (presumably defaulting to iteration?), but even in this case it would be necessary to add an extension that ensures that GPU arrays use broadcasting.

Alternatively, we could default to using broadcasting (with the additional overhead of collecting the indices), and - as an additional optimization - only use iteration for a handful of selected base array types such as Array, UpperTriangular{_,<:Matrix}, etc. This wouldn't require an extension, but if we add such optimizations we would still require both an implementation with and one without broadcasting.

What are your thoughts @KristofferC?

@avik-pal
Copy link
Author

bump on this

@avik-pal
Copy link
Author

avik-pal commented Sep 1, 2025

Testing this patch out with Lux.jl, it will still cause regressions in the cases where we have a wrapper over CuArray.

only use iteration for a handful of selected base array types such as Array

This seems like a good solution without causing regression on use-cases that was supported prior to #739. There's also ArrayInterface.fast_scalar_indexing but not sure how the maintainers feel about taking a dep on ArrayInterface.

@avik-pal avik-pal force-pushed the ap/gpu_arrays branch 2 times, most recently from d2e7730 to 6688848 Compare September 1, 2025 20:28
src/utils.jl Outdated
@@ -0,0 +1,15 @@
# overload for array types that
@inline supports_fast_scalar_indexing(::Array) = true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the @inline needed, did you encounter problems without it?

I think we might also want to extend this to

Suggested change
@inline supports_fast_scalar_indexing(::Array) = true
@inline supports_fast_scalar_indexing(::StridedArray) = true

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StridedArray is too broad here

julia> SubArray{Float64, 2, JLArray{Float64, 2}, Tuple{UnitRange{Int64}, UnitRange{Int64}}, false} <: StridedArray
true

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But is that only a problem with how JLArray is defined? Does it also cover views of CuArrays?

If StridedArray is problematic, another more generic alternative would be DenseArray.

@avik-pal avik-pal requested a review from devmotion September 1, 2025 21:32
@avik-pal
Copy link
Author

avik-pal commented Sep 4, 2025

bump on this

Copy link
Member

@devmotion devmotion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was hoping we could achieve it with a bit less code but maybe that's not possible.

Can you also add tests of #739 and #743 for these new branches?

fix: project toml for julia pre 1.9

fix: support gradient + more test coverage

chore: relax version

chore: remove 1.6 support and bump min version to 1.10

fix: apply suggestions from code review

Co-authored-by: David Widmann <[email protected]>
fix: use a struct instead of closure

fix: sizecheck

chore: remove GPUArraysCore

Co-authored-by: David Widmann <[email protected]>
fix: revert _take

chore: remove 1.8 checks

chore: remove 0.1

Co-authored-by: David Widmann <[email protected]>
@avik-pal
Copy link
Author

Can you also add tests of #739 and #743 for these new branches?

The GPUArray backends don't support broadcasting with wrapped arrays nicely, so those tests will mostly fail.

julia> using CUDA, LinearAlgebra

julia> x = LowerTriangular(cu(rand(Float32, 4, 4)))
4×4 LowerTriangular{Float32, CuArray{Float32, 2, CUDA.DeviceMemory}}:
 0.960887                       
 0.316333  0.612238              
 0.236091  0.209854  0.0883058    
 0.370694  0.732681  0.0111619  0.270063

julia> x[diagind(x)] .= 10

And they won't have un-assigned elements.

julia> CuArray{Float32}(undef, 2, 3)
2×3 CuArray{Float32, 2, CUDA.DeviceMemory}:
 -5.81535f-36  1.25147f7  -2.50125f-11
 -1.98624      1.84662     1.95155

julia> JLArray{Float32}(undef, 3, 4)
3×4 JLArray{Float32, 2}:
 6.771f-42  6.771f-42  9.42f-43  0.0
 6.771f-42  6.771f-42  0.0       0.0
 6.771f-42  6.771f-42  0.0       4.5657f-41

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.

Scalar indexing error when computing Jacobian with a CUDA.CuArray
4 participants