-
Notifications
You must be signed in to change notification settings - Fork 114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use Adapt.jl to change storage and element type #2212
base: main
Are you sure you want to change the base?
Conversation
Review checklistThis checklist is meant to assist creators of PRs (to let them know what reviewers will typically look for) and reviewers (to guide them in a structured review process). Items do not need to be checked explicitly for a PR to be eligible for merging. Purpose and scope
Code quality
Documentation
Testing
Performance
Verification
Created with ❤️ by the Trixi.jl community. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be close to final when tests are added
In order to eventually support GPU computation we need to use Adapt.jl to allow GPU backend packages to swap out host-array types like `CuArray` with device-side types like `CuDeviceArray`. Additionally this will allow us to change the element type of a simulation by using `adapt(Array{Float32}`. Co-authored-by: Lars Christmann <[email protected]> Co-authored-by: Benedict Geihe <[email protected]>
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2212 +/- ##
==========================================
- Coverage 96.42% 95.86% -0.56%
==========================================
Files 490 491 +1
Lines 39426 39518 +92
==========================================
- Hits 38015 37881 -134
- Misses 1411 1637 +226
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Could you please adapt the t8code
stuff as well? It causes the remaining failing CI tests like
LoadError: MethodError: Cannot `convert` an object of type Vector{Vector{Int64}} to an object of type Trixi.VecOfArrays{Vector{Int64}}
Closest candidates are:
convert(::Type{T}, ::T) where T
@ Base Base.jl:84
(::Type{Trixi.VecOfArrays{T}} where T<:AbstractArray)(::Any)
@ Trixi ~/work/Trixi.jl/Trixi.jl/src/auxiliary/vector_of_arrays.jl:12
Stacktrace:
[1] setproperty!(x::Trixi.P4estMPICache{Vector{Float64}, Vector{Int64}}, f::Symbol, v::Vector{Vector{Int64}})
@ Base ./Base.jl:40
[2] init_mpi_cache!(mpi_cache::Trixi.P4estMPICache{Vector{Float64}, Vector{Int64}}, mesh::Trixi.T8codeMesh{2, Float64, Static.True, 4, 4}, mpi_mesh_info::@NamedTuple{mpi_mortars::Trixi.P4estMPIMortarContainer{2, Float64, Float64, 3, 4, 5, Array{Float64, 5}, Vector{Float64}, Array}, mpi_interfaces::Trixi.P4estMPIInterfaceContainer{2, Float64, 4, Array{Float64, 4}, Vector{Int64}, Vector{Tuple{Symbol, Symbol}}, Vector{Float64}, Array}, global_mortar_ids::Vector{UInt128}, global_interface_ids::Vector{UInt128}, neighbor_ranks_mortar::Vector{Vector{Int64}}, neighbor_ranks_interface::Vector{Int64}}, nvars::Int64, nnodes::Int64, uEltype::Type)
@ Trixi ~/work/Trixi.jl/Trixi.jl/src/solvers/dgsem_t8code/dg_parallel.jl:93
mutable struct UnstructuredSortedBoundaryTypes{N, BCs <: NTuple{N, Any}} | ||
mutable struct UnstructuredSortedBoundaryTypes{N, BCs <: NTuple{N, Any}, | ||
Vec <: AbstractVector{<:Integer}} | ||
boundary_condition_types::BCs # specific boundary condition type(s), e.g. BoundaryConditionDirichlet | ||
boundary_indices::NTuple{N, Vector{Int}} # integer vectors containing global boundary indices | ||
boundary_indices::NTuple{N, Vec} # integer vectors containing global boundary indices | ||
boundary_dictionary::Dict{Symbol, Any} # boundary conditions as set by the user in the elixir file | ||
boundary_symbol_indices::Dict{Symbol, Vector{Int}} # integer vectors containing global boundary indices per boundary identifier | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aqua.jl complains about unbound type parameters here, see https://github.com/trixi-framework/Trixi.jl/actions/runs/13117124962/job/36594056579?pr=2212#step:7:6391
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ja, das verstehe ich nicht ganz.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like JuliaTesting/Aqua.jl#86.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See also JuliaTesting/Aqua.jl#265 (comment), which is basically exactly the case here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm I tried a couple of ways, but I am not sure how to fix this false positive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is no way, you can set unbound_args = false
for Aqua.jl tests so that we can proceed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a few minor comments/suggestions, then this should be ready to merge (once the other convo by Hendrik is resolved as well)
# The first type parameter must be `Array` by default and if | ||
# `Adapt.adapt_structure(to, container)` is called then this must be | ||
# `typeof(to)`. This is used in downstream code, e.g. for calling | ||
# `wrap_array` with an appropriate type after `resize!`ing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand this comment, in particular
The first type parameter must be
Array
by default and ifAdapt.adapt_structure(to, container)
is called then this must betypeof(to)
.
What is "this" referring to? To me, the sentence roughly reads like "the first type parameter must by Array
and Array
must always be the result of typeof(to)
", which is very likely not the actual intent 😅
Could you please clarify a little?
function unsafe_wrap_or_alloc(to, vec, size) | ||
if length(vec) == 0 | ||
return similar(vec, size) | ||
else | ||
return unsafe_wrap(to, pointer(vec), size) | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
function unsafe_wrap_or_alloc(to, vec, size) | |
if length(vec) == 0 | |
return similar(vec, size) | |
else | |
return unsafe_wrap(to, pointer(vec), size) | |
end | |
end | |
function unsafe_wrap_or_alloc(to, vector, size) | |
if length(vector) == 0 | |
return similar(vector, size) | |
else | |
return unsafe_wrap(to, pointer(vector), size) | |
end | |
end |
To not shadow Base.vec
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fixed the suggestion above. It's not necessary, from my point of view, to avoid shadowing here, but it's fine to be explicit and verbose.
struct VecOfArrays{T <: AbstractArray} | ||
arrays::Vector{T} | ||
end | ||
Base.getindex(v::VecOfArrays, i::Int) = Base.getindex(v.arrays, i) | ||
Base.IndexStyle(v::VecOfArrays) = Base.IndexStyle(v.arrays) | ||
Base.size(v::VecOfArrays) = Base.size(v.arrays) | ||
Base.length(v::VecOfArrays) = Base.length(v.arrays) | ||
Base.eltype(v::VecOfArrays{T}) where {T} = T | ||
function Adapt.adapt_structure(to, v::VecOfArrays) | ||
return VecOfArrays([Adapt.adapt(to, arr) for arr in v.arrays]) | ||
end | ||
function Base.convert(::Type{<:VecOfArrays}, v::Vector{<:AbstractArray}) | ||
VecOfArrays(v) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
struct VecOfArrays{T <: AbstractArray} | |
arrays::Vector{T} | |
end | |
Base.getindex(v::VecOfArrays, i::Int) = Base.getindex(v.arrays, i) | |
Base.IndexStyle(v::VecOfArrays) = Base.IndexStyle(v.arrays) | |
Base.size(v::VecOfArrays) = Base.size(v.arrays) | |
Base.length(v::VecOfArrays) = Base.length(v.arrays) | |
Base.eltype(v::VecOfArrays{T}) where {T} = T | |
function Adapt.adapt_structure(to, v::VecOfArrays) | |
return VecOfArrays([Adapt.adapt(to, arr) for arr in v.arrays]) | |
end | |
function Base.convert(::Type{<:VecOfArrays}, v::Vector{<:AbstractArray}) | |
VecOfArrays(v) | |
end | |
struct VectorOfArrays{T <: AbstractArray} | |
arrays::Vector{T} | |
end | |
Base.getindex(v::VectorOfArrays, i::Int) = Base.getindex(v.arrays, i) | |
Base.IndexStyle(v::VectorOfArrays) = Base.IndexStyle(v.arrays) | |
Base.size(v::VectorOfArrays) = Base.size(v.arrays) | |
Base.length(v::VectorOfArrays) = Base.length(v.arrays) | |
Base.eltype(v::VectorOfArrays{T}) where {T} = T | |
function Adapt.adapt_structure(to, v::VectorOfArrays) | |
return VectorOfArrays([Adapt.adapt(to, arr) for arr in v.arrays]) | |
end | |
function Base.convert(::Type{<:VectorOfArrays}, v::Vector{<:AbstractArray}) | |
VectorOfArrays(v) | |
end |
We try to avoid abbreviations unless really necessary for sanity 😬 Also, it would be consistent with the file name.
Note, however, that I do not have a super strong opinion here, thus if you deem VecOfArrays
to be better, we can stay like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just saw that RecursiveArrayTools.jl seems to already have a VectorOfArrays
type (https://github.com/trixi-framework/Trixi.jl/pull/2150/files#diff-6058d487915aa254cfe1a5f8da6315b8c1fef4da04df38b0089a599803e28e5aR63). Should we use that one instead?
If not, then of course we should use a different name for our own data structure, and keeping VecOfArrays
as the name seems reasonable to me. In that case, it might be good to leave a short note in the comment why we do not want to use the SciML package's type for future reference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using RecursiveArrayTools: VectorOfArray
is added in #2150 for DGMulti
support
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I saw that. I was just wondering whether their data type could (or should) be used instead of rolling our own.
elements.node_coordinates = unsafe_wrap(ArrayType, pointer(_node_coordinates), | ||
(n_dims, ntuple(_ -> n_nodes, n_dims)..., | ||
capacity)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't we need unsafe_wrap_or_alloc
here?
uArray <: DenseArray{uEltype, NDIMSP2}, | ||
IdsMatrix <: DenseMatrix{Int}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the restriction to dense types? (just curious)
end | ||
|
||
@inline function ninterfaces(interfaces::P4estInterfaceContainer) | ||
size(interfaces.neighbor_ids, 2) | ||
end | ||
@inline Base.ndims(::P4estInterfaceContainer{NDIMS}) where {NDIMS} = NDIMS | ||
@inline function Base.eltype(::P4estInterfaceContainer{NDIMS, uEltype}) where {NDIMS, | ||
uEltype} | ||
uEltype |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uEltype | |
return uEltype |
We like to be explicit for the sake of less experienced users...
# TODO: Vector of Vector type data structure does not work on GPUs, | ||
# must be redesigned. This skeleton implementation here just exists just | ||
# for compatibility with the rest of the KA.jl solver code |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wasn't this solved by the VecOfArrays
/VectorOfArrays
type? That is, is the comment still relevant? If yes, maybe add one sentence why VecOfArrays
does not solve the issue?
mpi_neighbor_interfaces = VecOfArrays(_mpi_neighbor_interfaces) | ||
mpi_neighbor_mortars = VecOfArrays(_mpi_neighbor_mortars) | ||
mpi_send_buffers = VecOfArrays(_mpi_send_buffers) | ||
mpi_recv_buffers = VecOfArrays(_mpi_recv_buffers) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of this indirection here, would it make sense to already return the proper VecOfArrays
type from the init_mpi_...
functions?
In order to eventually support GPU computation we need
to use Adapt.jl to allow GPU backend packages to swap
out host-array types like
CuArray
with device-side typeslike
CuDeviceArray
.Additionally this will allow us to change the element type
of a simulation by using
adapt(Array{Float32}
.Co-authored-by: Lars Christmann [email protected]
Co-authored-by: Benedict Geihe [email protected]