Repository navigation
Support lazy arrays such as MappedArrays as read-only sources - #137
Merged
Merged
Conversation
maleadt
marked this pull request as draft
October 1, 2026 12:25
This was referenced Oct 1, 2026
maleadt
force-pushed
the
tb/lazy-sources
branch
from
October 22, 2026 10:09
e341a59 to
79ae594
Compare
maleadt
force-pushed
the
tb/lazy-sources
branch
from
October 23, 2026 13:45
79ae594 to
9dbaa7f
Compare
Backend resolution asked every array that is not a range, CartesianIndices or LinearIndices (or a Base view, reshape or permutation of one) for its backend with KernelAbstractions' `get_backend`, which recurses through `parent` and throws when it reaches a range or a tuple of parents. So `AK.sum(mappedarray(abs2, 1:n))`, and every other operation on a MappedArrays.jl array over ranges or Cartesian indices, threw "Implement get_backend" without an explicit `backend`, and `mappedarray(+, A, B)` threw a MethodError. With `backend` given, reductions, predicates, findall and searchsorted already worked on such arrays, since an `isbits` mapped array is passed to kernels as it is. But the operations that allocate a result (map, accumulate, reverse, sort, sortperm) used `similar(v)`, which for a wrapper without its own `similar` is Base's host `Array`, so their kernels got a host array. Copies of such wrappers (`sort`, the keys of the merge sort permutation, the host finish of small reductions) went through `copy`, `copyto!` or `Array`, whose fallbacks scalar-index device arrays. Now a wrapper whose parents are not a single array with memory votes like the arrays it wraps, so a wrapper of only ranges does not vote, as Base's wrappers of ranges already did not; other arrays are still asked with `get_backend`. A wrapper that gets Base's `similar` fallback, or wraps no memory, has its results allocated on `backend`, and is copied with a kernel there; other wrappers keep their own `copy`, so an OffsetArray's copy keeps its axes. Copies into existing arrays and to the host also use a kernel for wrappers that GPUArrays (its wrappers of device arrays) and Base (wrappers of host memory) do not copy, since Base's fallbacks index device memory element by element. Lazy arrays over ranges are not evaluated on the host either, since their function may read device arrays it captures. A lazy array of device arrays also needs an Adapt.jl rule for its type to reach kernels. MappedArrays.jl has none, so `mappedarray(f, ::CuArray)` still only works on the host; that rule belongs in MappedArrays.jl. The tests use a lazy wrapper with such a rule for the device path. Supersedes #23, which allocated the 1D reduction's scratch on the given backend instead of with `similar(src)`, and whose range and mapped-array sums are among the tests. Co-authored-by: Dan Stahlke <dan@stahlke.org>
maleadt
force-pushed
the
tb/lazy-sources
branch
from
October 26, 2026 19:45
9dbaa7f to
274b5e1
Compare
maleadt
marked this pull request as ready for review
October 26, 2026 19:45
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
AcceleratedKernels has always been meant to work on unmaterialised inputs: you can call
AK.anyovereachindex(x)instead ofx, or sum a range on the GPU without allocating it. That works for ranges andCartesianIndices, but not for lazy arrays built on top of them, such as those from MappedArrays.jl. This PR makes those work, and supersedes #23, which was written against the old API with a positionalbackend.What fails on main
Without a
backend, every operation throws as soon as it sees a mapped array over a range. AK works out the backend by asking each array, and KernelAbstractions'get_backendfollowsparentdown to the range, which it doesn't know about. A mapped array of several arrays has a tuple as itsparent, which fails differently:If you pass
backend=CUDABackend(), reductions,any/all/count,findallandsearchsorted*already work, because such a mapped array isisbitsand gets passed to the kernel as it is. The operations that allocate a result still fail, though.map,accumulate,reverse,sortandsortpermallocated it withsimilar(v), and a wrapper without its ownsimilarmethod gets Base's fallback, which is a hostArray. The kernel then receives a host array:There were also copies that went through
copy,copyto!orArray:sortcopies its input,MergeSort'ssortpermcopies the keys, and small reductions finish on the host. For a wrapper of device memory, the generic fallbacks of those functions index the GPU array one element at a time, which is disallowed.What this PR changes
parent, or a tuple of them for a mapped array of several arrays. A lazy array over ranges orCartesianIndicestherefore doesn't determine the backend, just as a reshaped range already didn't. A wrapper whose parents end in a single array with memory is still asked withget_backend(so its own method, if any, is used), and arrays that resolved before resolve the same way.similarfallback, results are allocated on the resolved backend. Everything else is still allocated withsimilar.sortcopies a wrapper whose results are allocated on the backend (see above) with a small AK kernel, because Base'scopywould give a hostArray. Every other wrapper keeps its owncopy, so an OffsetArray's copy keeps its axes. Copies into existing buffers (MergeSort'ssortpermkeys) and to the host (small reductions) also use the kernel for wrappers that neither GPUArrays nor Base copies. GPUArrays handles its own wrappers of device arrays, and Base handles wrappers of host memory. A lazy array over ranges is also not evaluated on the host when the backend is a GPU, because its function may read device arrays that it captures. On the host backend, and for every other input, copies work as they did before.The same calls on this branch (CUDA, RTX 5080):
(Outputs are condensed onto one line.) Without a
backend, the first two run on the host. The docs section on thebackendkeyword now describes wrappers and lazy arrays.What still doesn't work:
mappedarray(f, ::CuArray)A lazy array that wraps device arrays has to be converted for the device before a kernel can use it. That conversion is done with Adapt.jl, and MappedArrays.jl doesn't define Adapt rules (it has no dependencies at all), so the
CuArrayinside isn't converted:AK can't fix this generically, because Adapt has no structural fallback. Defining the rules from an AK extension would be type piracy, and those methods would clash with MappedArrays' own once it adds them. The fix belongs in MappedArrays.jl: an Adapt extension with an
adapt_structuremethod for each mapped array type, adapting both the function and the wrapped arrays and keeping the element type. With that in place, the rest of this PR already handles such arrays. The tests demonstrate it with a small lazy wrapper type that does have an Adapt rule. With it, every operation works on a lazy array of the backend's arrays, with or withoutbackend. Mapped arrays of host arrays already work on the host.Tests
There is a new
test/generic/lazy.jl, which adds MappedArrays and Adapt as test dependencies. It runs every read-only operation (reductions whole and alongdims,mapreducedim!,any/all/count,findallwith lazy sources and lazyitems,accumulate/cumsumincludingdims,map/map!,reverse,sort,sortpermincludingMergeSort's key copy, andsearchsortedfirst!/searchsortedlast!for both arguments) over:CartesianIndicesand of several ranges, with and withoutbackend;similar), plus small reductions that finish on the host;copy: one without an Adapt rule, and one with offset axes whose sorted copy keeps them;get_backend, and mixed backends.It also includes #23's range and mapped-array sums, and its
CartesianIndicesreduction, which reported anInvalidIRErrorand already works on main.Run against main's
src,lazy.jlhas 18 errors out of 223 tests on the host. On this branch, the full suite passes on CPU, CUDA (RTX 5080) and OpenCL (PoCL) together (103,166 tests), andlazy.jlwithsortandreducealso passes on Julia 1.10.The change is additive: an input that resolved, allocated and copied correctly before still does. It doesn't touch anything GPUArrays relies on, so it could go into 0.5 or a later 0.5.x.
Supersedes #23. Thanks to @dstahlke for the original PR and the tests it is built on.