Skip to content

Commit

Permalink
Preallocate GPU interpolant
Browse files Browse the repository at this point in the history
The interpolants we in `Interpolations.jl` are described by two arrays:
the knots and the coeffs. When `Adapt` is called on these interpolants,
CuArrays are allocated on the GPU. For large data, this is inefficient.

In this commit, I add a system to avoid these allocations. This is
accomplished by add a dictionary to `InterpolationsRegridder`. This
dictionary has keys that identify the size of the knots and coefficients
and values the adapted splines. When `regrid` is called, we check if we
have already allocated some suitable space in this dictionary, if not,
we create a new spline, if we do, we write in place.

This removes GPU allocations in the hot path (ie, the regridder is used
in a time evolution with always the same data and dimensions), while
also keeping the flexibility of reusing the same regridder with any
input data.
  • Loading branch information
Sbozzolo committed Jul 23, 2024
1 parent b8bc893 commit d39799c
Showing 1 changed file with 59 additions and 10 deletions.
69 changes: 59 additions & 10 deletions ext/InterpolationsRegridderExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ struct InterpolationsRegridder{
SPACE <: ClimaCore.Spaces.AbstractSpace,
FIELD <: ClimaCore.Fields.Field,
BC,
GITP,
} <: Regridders.AbstractRegridder

"""ClimaCore.Space where the output Field will be defined"""
Expand All @@ -22,6 +23,14 @@ struct InterpolationsRegridder{

"""Tuple of extrapolation conditions as accepted by Interpolations.jl"""
extrapolation_bc::BC

# This is needed because Adapt moves from CPU to GPU and allocates new memory.
"""Dictionary of preallocated areas of memory where to store the GPU interpolant (if
needed). Every time new data/dimensions are used in regrid, a new entry in the
dictionary is created. The keys of the dictionary a tuple of tuple
`(size(dimensions), size(data))`, with `dimensions` and `data` defined in `regrid`.
"""
_gpuitps::GITP
end

# Note, we swap Lat and Long! This is because according to the CF conventions longitude
Expand Down Expand Up @@ -75,13 +84,38 @@ function Regridders.InterpolationsRegridder(
"Number of boundary conditions does not match the number of dimensions",
)

# Let's figure out the type of _gpuitps by creating a simple spline
FT = ClimaCore.Spaces.undertype(target_space)
dimensions = ntuple(_ -> [zero(FT), one(FT)], num_dimensions)
data = zeros(FT, ntuple(_ -> 2, num_dimensions))
itp = _create_linear_spline(FT, data, dimensions, extrapolation_bc)
fake_gpuitp = Adapt.adapt(ClimaComms.array_type(target_space), itp)
gpuitps = Dict((size.(dimensions), size(data)) => fake_gpuitp)

return InterpolationsRegridder(
target_space,
coordinates,
extrapolation_bc
extrapolation_bc,
gpuitps,
)
end

"""
_create_linear_spline(regridder::InterpolationsRegridder, data, dimensions)
Create a linear spline for the given data on the given dimension (on the CPU).
"""
function _create_linear_spline(FT, data, dimensions, extrapolation_bc)
dimensions_FT = map(d -> FT.(d), dimensions)

# Make a linear spline
return Intp.extrapolate(
Intp.interpolate(dimensions_FT, FT.(data), Intp.Gridded(Intp.Linear())),
extrapolation_bc,
)
end


"""
regrid(regridder::InterpolationsRegridder, data, dimensions)::Field
Expand All @@ -91,16 +125,31 @@ This function is allocating.
"""
function Regridders.regrid(regridder::InterpolationsRegridder, data, dimensions)
FT = ClimaCore.Spaces.undertype(regridder.target_space)
dimensions_FT = map(d -> FT.(d), dimensions)

# Make a linear spline
itp = Intp.extrapolate(
Intp.interpolate(dimensions_FT, FT.(data), Intp.Gridded(Intp.Linear())),
regridder.extrapolation_bc,
)
itp =
_create_linear_spline(FT, data, dimensions, regridder.extrapolation_bc)

key = (size.(dimensions), size(data))

if haskey(regridder._gpuitps, key)
for (k, k_new) in zip(
regridder._gpuitps[key].itp.knots,
Adapt.adapt(
ClimaComms.array_type(regridder.target_space),
itp.itp.knots,
),
)
k .= k_new
end
regridder._gpuitps[key].itp.coefs .= Adapt.adapt(
ClimaComms.array_type(regridder.target_space),
itp.itp.coefs,
)
else
regridder._gpuitps[key] =
Adapt.adapt(ClimaComms.array_type(regridder.target_space), itp)
end

# Move it to GPU (if needed)
gpuitp = Adapt.adapt(ClimaComms.array_type(regridder.target_space), itp)
gpuitp = regridder._gpuitps[key]

return map(regridder.coordinates) do coord
gpuitp(totuple(coord)...)
Expand Down

0 comments on commit d39799c

Please sign in to comment.