From 97ec0e54067df9183d89cb16715f1e4855ba12d8 Mon Sep 17 00:00:00 2001 From: Christian <28689358+christiangnrd@users.noreply.github.com> Date: Mon, 19 Oct 2026 17:55:21 -0300 Subject: [PATCH 1/6] [JLArrays] Support KA 0.10 With KernelAbstractions 0.10, JLArray kernels run on its POCL back-end. Drop the KA 0.9 code paths, including JLDeviceArray, and convert JLArrays to POCL device arrays. Leave `Ref`s to POCL's conversion after converting their contents, so that `Ref`s of types, as passed by `convert.(T, A)`, are handled too. POCL's SPIR-V back-end cannot lower 128-bit integers, so stop testing Int128 with JLArrays. --- lib/JLArrays/Project.toml | 2 +- lib/JLArrays/src/JLArrays.jl | 6 +----- lib/JLArrays/src/JLKernels.jl | 20 +++--------------- lib/JLArrays/src/array.jl | 11 +++------- lib/JLArrays/src/device/array.jl | 36 +++----------------------------- lib/JLArrays/src/sparse.jl | 18 ++++++++++------ test/runtests.jl | 4 ++++ test/testsuite/broadcasting.jl | 3 +++ 8 files changed, 30 insertions(+), 70 deletions(-) diff --git a/lib/JLArrays/Project.toml b/lib/JLArrays/Project.toml index 698fb8ebd..56474baa4 100644 --- a/lib/JLArrays/Project.toml +++ b/lib/JLArrays/Project.toml @@ -14,7 +14,7 @@ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [compat] Adapt = "2.0, 3.0, 4.0" GPUArrays = "12.1" -KernelAbstractions = "0.9" +KernelAbstractions = "0.10" LinearAlgebra = "1" Random = "1" SparseArrays = "1" diff --git a/lib/JLArrays/src/JLArrays.jl b/lib/JLArrays/src/JLArrays.jl index c6caf8524..d86e6c1a4 100644 --- a/lib/JLArrays/src/JLArrays.jl +++ b/lib/JLArrays/src/JLArrays.jl @@ -16,11 +16,7 @@ using Random import GPUArrays: dense_array_type -import KernelAbstractions - -@static if isdefined(JLArrays.KernelAbstractions, :POCL) # KA v0.10 - import KernelAbstractions: POCL -end +import KernelAbstractions: KernelAbstractions, POCL module AS diff --git a/lib/JLArrays/src/JLKernels.jl b/lib/JLArrays/src/JLKernels.jl index ca4d84d0f..483815b93 100644 --- a/lib/JLArrays/src/JLKernels.jl +++ b/lib/JLArrays/src/JLKernels.jl @@ -49,18 +49,8 @@ KernelAbstractions.allocate(::JLBackend, ::Type{T}, dims::Tuple) where T = JLArr return ndrange, workgroupsize, iterspace, dynamic end -@static if isdefined(KernelAbstractions, :isgpu) # KA v0.9 - KernelAbstractions.isgpu(b::JLBackend) = false -end - -@static if !isdefined(KernelAbstractions, :POCL) # KA v0.9 - function convert_to_cpu(obj::Kernel{JLBackend, W, N, F}) where {W, N, F} - return Kernel{typeof(KernelAbstractions.CPU(; static = obj.backend.static)), W, N, F}(KernelAbstractions.CPU(; static = obj.backend.static), obj.f) - end -else - function convert_to_cpu(obj::Kernel{JLBackend, W, N, F}) where {W, N, F} - return Kernel{typeof(KernelAbstractions.POCLBackend()), W, N, F}(KernelAbstractions.POCLBackend(), obj.f) - end +function convert_to_cpu(obj::Kernel{JLBackend, W, N, F}) where {W, N, F} + return Kernel{typeof(KernelAbstractions.POCLBackend()), W, N, F}(KernelAbstractions.POCLBackend(), obj.f) end function (obj::Kernel{JLBackend})(args...; ndrange=nothing, workgroupsize=nothing) @@ -76,10 +66,6 @@ end Adapt.adapt_storage(::JLBackend, a::Array) = Adapt.adapt(JLArrays.JLArray, a) Adapt.adapt_storage(::JLBackend, a::JLArrays.JLArray) = a -@static if !isdefined(KernelAbstractions, :POCL) # KA v0.9 - Adapt.adapt_storage(::KernelAbstractions.CPU, a::JLArrays.JLArray) = convert(Array, a) -else - Adapt.adapt_storage(::KernelAbstractions.POCLBackend, a::JLArrays.JLArray) = convert(Array, a) -end +Adapt.adapt_storage(::KernelAbstractions.POCLBackend, a::JLArrays.JLArray) = convert(Array, a) end diff --git a/lib/JLArrays/src/array.jl b/lib/JLArrays/src/array.jl index d47184a8b..31875c9d7 100644 --- a/lib/JLArrays/src/array.jl +++ b/lib/JLArrays/src/array.jl @@ -172,14 +172,9 @@ Adapt.adapt_storage(::Type{<:JLArray{T}}, xs::AbstractArray) where {T} = Adapt.adapt_storage(::Type{Array}, xs::JLArray) = convert(Array, xs) # adapt for the device -@static if !isdefined(JLArrays.KernelAbstractions, :POCL) # KA v0.9 - Adapt.adapt_storage(::Adaptor, x::JLArray{T,N}) where {T,N} = - JLDeviceArray{T,N}(x.data[], x.offset, x.dims) -else - function Adapt.adapt_storage(::Adaptor, x::JLArray{T,N}) where {T,N} - arr = typed_data(x) - Adapt.adapt_storage(POCL.KernelAdaptor([pointer(arr)]), arr) - end +function Adapt.adapt_storage(::Adaptor, x::JLArray{T,N}) where {T,N} + arr = typed_data(x) + Adapt.adapt_storage(POCL.KernelAdaptor([pointer(arr)]), arr) end ## conversions diff --git a/lib/JLArrays/src/device/array.jl b/lib/JLArrays/src/device/array.jl index 457697782..c5bcd210c 100644 --- a/lib/JLArrays/src/device/array.jl +++ b/lib/JLArrays/src/device/array.jl @@ -3,36 +3,6 @@ struct Adaptor end jlconvert(arg) = adapt(Adaptor(), arg) -# FIXME: add Ref to Adapt.jl (but make sure it doesn't cause ambiguities with CUDAnative's) -struct JlRefValue{T} <: Ref{T} - x::T -end -Base.getindex(r::JlRefValue) = r.x -Adapt.adapt_structure(to::Adaptor, r::Base.RefValue) = JlRefValue(adapt(to, r[])) - -## executed on-device - -# array type -@static if !isdefined(JLArrays.KernelAbstractions, :POCL) # KA v0.9 - struct JLDeviceArray{T, N} <: AbstractDeviceArray{T, N} - data::Vector{UInt8} - offset::Int - dims::Dims{N} - end - - Base.elsize(::Type{<:JLDeviceArray{T}}) where {T} = sizeof(T) - - Base.size(x::JLDeviceArray) = x.dims - Base.sizeof(x::JLDeviceArray) = Base.elsize(x) * length(x) - - Base.unsafe_convert(::Type{Ptr{T}}, x::JLDeviceArray{T}) where {T} = - convert(Ptr{T}, pointer(x.data)) + x.offset - - # conversion of untyped data to a typed Array - function typed_data(x::JLDeviceArray{T}) where {T} - unsafe_wrap(Array, pointer(x), x.dims) - end - - @inline Base.getindex(A::JLDeviceArray, index::Integer) = getindex(typed_data(A), index) - @inline Base.setindex!(A::JLDeviceArray, x, index::Integer) = setindex!(typed_data(A), x, index) -end +# convert what a `Ref` holds, but leave the `Ref` itself to POCL, which also handles `Ref`s +# of types (as passed by `convert.(T, A)`) +Adapt.adapt_structure(to::Adaptor, r::Base.RefValue) = Ref(adapt(to, r[])) diff --git a/lib/JLArrays/src/sparse.jl b/lib/JLArrays/src/sparse.jl index 032bd422f..c82c69b07 100644 --- a/lib/JLArrays/src/sparse.jl +++ b/lib/JLArrays/src/sparse.jl @@ -209,9 +209,15 @@ end ## adapt for the device -Adapt.adapt_structure(to::Adaptor, x::JLSparseMatrixCSC{Tv,Ti}) where {Tv,Ti} = -GPUSparseDeviceMatrixCSC{Tv,Ti,JLDeviceArray{Ti, 1}, JLDeviceArray{Tv, 1}, AS.Generic}(adapt(to, x.colPtr), adapt(to, x.rowVal), adapt(to, x.nzVal), x.dims, x.nnz) -Adapt.adapt_structure(to::Adaptor, x::JLSparseMatrixCSR{Tv,Ti}) where {Tv,Ti} = -GPUSparseDeviceMatrixCSR{Tv,Ti,JLDeviceArray{Ti, 1}, JLDeviceArray{Tv, 1}, AS.Generic}(adapt(to, x.rowPtr), adapt(to, x.colVal), adapt(to, x.nzVal), x.dims, x.nnz) -Adapt.adapt_structure(to::Adaptor, x::JLSparseVector{Tv,Ti}) where {Tv,Ti} = -GPUSparseDeviceVector{Tv,Ti,JLDeviceArray{Ti, 1}, JLDeviceArray{Tv, 1}, AS.Generic}(adapt(to, x.iPtr), adapt(to, x.nzVal), x.len, x.nnz) +function Adapt.adapt_structure(to::Adaptor, x::JLSparseMatrixCSC{Tv,Ti}) where {Tv,Ti} + colPtr, rowVal, nzVal = adapt(to, x.colPtr), adapt(to, x.rowVal), adapt(to, x.nzVal) + GPUSparseDeviceMatrixCSC{Tv,Ti,typeof(colPtr),typeof(nzVal),AS.Generic}(colPtr, rowVal, nzVal, x.dims, x.nnz) +end +function Adapt.adapt_structure(to::Adaptor, x::JLSparseMatrixCSR{Tv,Ti}) where {Tv,Ti} + rowPtr, colVal, nzVal = adapt(to, x.rowPtr), adapt(to, x.colVal), adapt(to, x.nzVal) + GPUSparseDeviceMatrixCSR{Tv,Ti,typeof(rowPtr),typeof(nzVal),AS.Generic}(rowPtr, colVal, nzVal, x.dims, x.nnz) +end +function Adapt.adapt_structure(to::Adaptor, x::JLSparseVector{Tv,Ti}) where {Tv,Ti} + iPtr, nzVal = adapt(to, x.iPtr), adapt(to, x.nzVal) + GPUSparseDeviceVector{Tv,Ti,typeof(iPtr),typeof(nzVal),AS.Generic}(iPtr, nzVal, x.len, x.nnz) +end diff --git a/test/runtests.jl b/test/runtests.jl index c2b9ea471..fc9d8f0c5 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -11,6 +11,10 @@ const init_worker_code = quote TestSuite.sparse_types(::Type{<:JLArray}) = (JLSparseVector, JLSparseMatrixCSC, JLSparseMatrixCSR) TestSuite.sparse_types(::Type{<:Array}) = (SparseVector, SparseMatrixCSC) + + # JLArray kernels run on POCL, whose SPIR-V back-end lacks 128-bit integers + TestSuite.supported_eltypes(::Type{<:JLArray}) = + filter(!=(Int128), TestSuite.supported_eltypes()) end const init_code = quote diff --git a/test/testsuite/broadcasting.jl b/test/testsuite/broadcasting.jl index e892c69fb..4df6e147a 100644 --- a/test/testsuite/broadcasting.jl +++ b/test/testsuite/broadcasting.jl @@ -44,6 +44,9 @@ function broadcasting(AT, eltypes) gres .= test_idx.(gidx, Base.RefValue(gy)) cres .= test_idx.(cidx, Base.RefValue(cy)) @test Array(gres) == cres + + # a `Ref` of a type, as passed by `convert.(T, A)` + @test compare(a -> convert.(ET, a), AT, rand(ET, N)) end @testset "Tuple" begin From 20025a5dc3f952b42edfc08154ab8ace743bd292 Mon Sep 17 00:00:00 2001 From: Christian <28689358+christiangnrd@users.noreply.github.com> Date: Tue, 20 Oct 2026 15:41:14 -0300 Subject: [PATCH 2/6] [JLArrays] Fully support KI backend and test Make JLBackend a KernelInterface back-end, like the GPU back-ends that moved to KernelAbstractions 0.10: it converts arguments to POCL's and forwards compilation, launches and device queries to the POCL back-end. Run the KernelAbstractions and KernelInterface testsuites on it, skipping the dense-to-sparse test, which JLArrays don't support, and the tests that only exercise KernelAbstractions' own CPU back-end. KA 0.10's CPU back-end has no static scheduling anymore, so this removes the `static` option of JLBackend. --- lib/JLArrays/Project.toml | 2 + lib/JLArrays/src/JLKernels.jl | 93 ++++++++++++++++++++--------------- lib/JLArrays/src/sparse.jl | 1 + test/Project.toml | 4 ++ test/runtests.jl | 26 ++++++++++ 5 files changed, 85 insertions(+), 41 deletions(-) diff --git a/lib/JLArrays/Project.toml b/lib/JLArrays/Project.toml index 56474baa4..f6fec772a 100644 --- a/lib/JLArrays/Project.toml +++ b/lib/JLArrays/Project.toml @@ -7,6 +7,7 @@ authors = ["Tim Besard "] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" +KernelInterface = "4ee993da-d684-4d17-a7dd-4e58e78d92bf" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" @@ -15,6 +16,7 @@ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" Adapt = "2.0, 3.0, 4.0" GPUArrays = "12.1" KernelAbstractions = "0.10" +KernelInterface = "0.4.2" LinearAlgebra = "1" Random = "1" SparseArrays = "1" diff --git a/lib/JLArrays/src/JLKernels.jl b/lib/JLArrays/src/JLKernels.jl index 483815b93..aae2cf7c1 100644 --- a/lib/JLArrays/src/JLKernels.jl +++ b/lib/JLArrays/src/JLKernels.jl @@ -3,8 +3,10 @@ module JLKernels using ..JLArrays using ..JLArrays: jlconvert -import KernelAbstractions -import KernelAbstractions: Kernel, StaticSize, DynamicSize, partition, launch_config +import KernelAbstractions: KernelAbstractions, POCLBackend +import KernelInterface as KI + +import GPUArrays import Adapt @@ -13,59 +15,68 @@ import Adapt export JLBackend -const MAXTHREADS = 256 +struct JLBackend <: KI.Backend end -struct JLBackend <: KernelAbstractions.GPU - static::Bool - JLBackend(;static::Bool=false) = new(static) -end +KI.get_backend(a::JLA) where JLA <: JLArray = JLBackend() +KI.get_backend(a::JLA) where JLA <: Union{JLSparseMatrixCSC, JLSparseMatrixCSR, JLSparseVector} = JLBackend() + +KI.allocate(::JLBackend, ::Type{T}, dims::Tuple) where T = JLArray{T}(undef, dims) + +KI.unsafe_free!(a::JLArray) = GPUArrays.unsafe_free!(a) -KernelAbstractions.get_backend(a::JLA) where JLA <: JLArray = JLBackend() -KernelAbstractions.get_backend(a::JLA) where JLA <: Union{JLSparseMatrixCSC, JLSparseMatrixCSR, JLSparseVector} = JLBackend() +## KernelInterface -function KernelAbstractions.mkcontext(kernel::Kernel{JLBackend}, I, _ndrange, iterspace, ::Dynamic) where Dynamic - return KernelAbstractions.CompilerMetadata{KernelAbstractions.ndrange(kernel), Dynamic}(I, _ndrange, iterspace) +# JLArrays are executed by POCL: convert arguments to their POCL-compatible (CPU) +# counterparts, and forward compilation and launches to the POCL backend. KernelAbstractions +# kernels are launched through these methods too. + +# POCL launches synchronously, and JLArrays live in host memory +KI.synchronize(::JLBackend) = nothing + +function KI.copyto!(::JLBackend, A, B) + length(A) == length(B) || + throw(ArgumentError("Arrays must match in length, got $(length(A)) and $(length(B))")) + Base.copyto!(A, B) + return A end -KernelAbstractions.allocate(::JLBackend, ::Type{T}, dims::Tuple) where T = JLArray{T}(undef, dims) - -@inline function launch_config(kernel::Kernel{JLBackend}, ndrange, workgroupsize) - if ndrange isa Integer - ndrange = (ndrange,) - end - if workgroupsize isa Integer - workgroupsize = (workgroupsize, ) - end - - if KernelAbstractions.workgroupsize(kernel) <: DynamicSize && workgroupsize === nothing - workgroupsize = (MAXTHREADS,) # Vectorization, 4x unrolling, minimal grain size - end - iterspace, dynamic = partition(kernel, ndrange, workgroupsize) - # partition checked that the ndrange's agreed - if KernelAbstractions.ndrange(kernel) <: StaticSize - ndrange = nothing - end - - return ndrange, workgroupsize, iterspace, dynamic +KI.argconvert(::JLBackend, arg) = KI.argconvert(POCLBackend(), jlconvert(arg)) + +# the POCL kernel, and the callable it was compiled from, which has to be kept alive since +# its converted form only holds pointers to the arrays it captures +struct JLKernel{K, F} + kernel::K + f::F end -function convert_to_cpu(obj::Kernel{JLBackend, W, N, F}) where {W, N, F} - return Kernel{typeof(KernelAbstractions.POCLBackend()), W, N, F}(KernelAbstractions.POCLBackend(), obj.f) +function KI.kernel_function(backend::JLBackend, f::F, tt::TT = Tuple{}; kwargs...) where {F, TT} + kernel = KI.kernel_function(POCLBackend(), jlconvert(f), tt; kwargs...) + kern = JLKernel(kernel, f) + return KI.Kernel{JLBackend, typeof(kern)}(backend, kern) end -function (obj::Kernel{JLBackend})(args...; ndrange=nothing, workgroupsize=nothing) - ndrange, workgroupsize, _, _ = launch_config(obj, ndrange, workgroupsize) +function KI.launch(obj::KI.Kernel{JLBackend}, groups::Dims{3}, items::Dims{3}, args::Tuple; kwargs...) # Use `map` rather than `jlconvert.(args)` to skip the broadcast # machinery (broadcasted/materialize/ntuple) that would otherwise # be specialized per unique arg-tuple type. - device_args = map(jlconvert, args) - new_obj = convert_to_cpu(obj) - new_obj(device_args...; ndrange, workgroupsize) + f = obj.kern.f + GC.@preserve f args KI.launch(obj.kern.kernel, groups, items, map(jlconvert, args); kwargs...) end -Adapt.adapt_storage(::JLBackend, a::Array) = Adapt.adapt(JLArrays.JLArray, a) -Adapt.adapt_storage(::JLBackend, a::JLArrays.JLArray) = a +KI.max_work_group_size(kernel::KI.Kernel{JLBackend}) = KI.max_work_group_size(kernel.kern.kernel) +KI.launch_configuration(kernel::KI.Kernel{JLBackend}; kwargs...) = + KI.launch_configuration(kernel.kern.kernel; kwargs...) +for f in (:functional, + :max_work_group_size, :max_work_group_dims, :max_num_groups, :sub_group_size, + :multiprocessor_count, :supports_subgroups, :supports_linear_subgroups, + :supports_independent_subgroups, :supports_partitionable_subgroups, + :supports_float64, :supports_atomics) + @eval KI.$f(::JLBackend) = KI.$f(POCLBackend()) +end +KI.supports_shuffle(::JLBackend, ::Type{T}) where {T} = KI.supports_shuffle(POCLBackend(), T) +KI.versioninfo(io::IO, ::JLBackend) = KI.versioninfo(io, POCLBackend()) -Adapt.adapt_storage(::KernelAbstractions.POCLBackend, a::JLArrays.JLArray) = convert(Array, a) +Adapt.adapt_storage(::JLBackend, a::Array) = Adapt.adapt(JLArray, a) +Adapt.adapt_storage(::JLBackend, a::JLArray) = a end diff --git a/lib/JLArrays/src/sparse.jl b/lib/JLArrays/src/sparse.jl index c82c69b07..2acca591c 100644 --- a/lib/JLArrays/src/sparse.jl +++ b/lib/JLArrays/src/sparse.jl @@ -153,6 +153,7 @@ function JLSparseMatrixCSC(xs::SparseMatrixCSC{Tv, Ti}) where {Ti, Tv} return JLSparseMatrixCSC{Tv, Ti}(colPtr, rowVal, nzVal, (xs.m, xs.n)) end JLSparseMatrixCSC(xs::SparseVector) = JLSparseMatrixCSC(SparseMatrixCSC(xs)) + Base.length(x::JLSparseMatrixCSC) = prod(x.dims) Base.size(x::JLSparseMatrixCSC) = x.dims diff --git a/test/Project.toml b/test/Project.toml index 7e816ed54..ab94d87b8 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,13 +1,17 @@ [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" GPUArrays = "0c68f7d7-f131-5f86-a1c3-88cf8149b2d7" +InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" JLArrays = "27aeb0d3-9eb9-45fb-866b-73c2ecf80fcb" JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" +KernelInterface = "4ee993da-d684-4d17-a7dd-4e58e78d92bf" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" ParallelTestRunner = "d3525ed8-44d0-4b2c-a655-542cee43accc" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/test/runtests.jl b/test/runtests.jl index fc9d8f0c5..b43820e8d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -30,6 +30,32 @@ for AT in (:JLArray, :Array), name in keys(TestSuite.tests) testsuite["$(AT)/$name"] = :(TestSuite.tests[$name]($AT)) end +testsuite["JLArray/kernelabstractions"] = quote + import KernelAbstractions + import KernelAbstractions as KA + using JLArrays.JLKernels + + include(joinpath(dirname(pathof(KernelAbstractions)), "..", "test", "testsuite.jl")) + + # JLArrays don't convert dense arrays to sparse ones, and the other two tests only run + # kernels on KernelAbstractions' own CPU back end + skip_tests = Set(["sparse", "CPU synchronization", "fallback test: callable types"]) + # JLArray kernels are compiled by POCL, like the CPU back end's, so check them the same way + Testsuite.testsuite(JLBackend, "CPU", KernelAbstractions.POCL, JLArray, + KernelAbstractions.POCL.CLDeviceArray; skip_tests) +end + +testsuite["JLArray/kernelinterface"] = quote + import KernelInterface + import KernelInterface as KI + using JLArrays.JLKernels + import Adapt + + include(joinpath(dirname(pathof(KernelInterface)), "..", "test", "testsuite.jl")) + + Testsuite.testsuite(JLBackend(), JLArray) +end + # captured variables that get boxed (`Core.Box`) lose their type if isdefined(Test, :detect_closure_boxes) testsuite["closure boxes"] = quote From 57f9c5e593846cc0672478f74c15bf3777923cc0 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Tue, 27 Oct 2026 15:04:09 +0100 Subject: [PATCH 3/6] [JLArrays] Compute kernel indices in Int, like POCL KernelAbstractions narrows `@index` computations to Int32 on every back-end but POCL, because on POCL the narrowing keeps LLVM from vectorizing the loops over work-items. JLArray kernels run on POCL, but JLBackend isn't a POCLBackend, so they got Int32 indices. Using Int speeds up a 2048x2048 element-wise kernel by about 7%. --- lib/JLArrays/src/JLKernels.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/JLArrays/src/JLKernels.jl b/lib/JLArrays/src/JLKernels.jl index aae2cf7c1..44f68cbb7 100644 --- a/lib/JLArrays/src/JLKernels.jl +++ b/lib/JLArrays/src/JLKernels.jl @@ -76,6 +76,10 @@ end KI.supports_shuffle(::JLBackend, ::Type{T}) where {T} = KI.supports_shuffle(POCLBackend(), T) KI.versioninfo(io::IO, ::JLBackend) = KI.versioninfo(io, POCLBackend()) +# KernelAbstractions computes indices in `Int32` unless the back-end is POCL, where narrowing +# them keeps LLVM from vectorizing the loops over work-items. JLArray kernels run on POCL too. +KernelAbstractions.narrow_index_type(::JLBackend) = Int + Adapt.adapt_storage(::JLBackend, a::Array) = Adapt.adapt(JLArray, a) Adapt.adapt_storage(::JLBackend, a::JLArray) = a From 7643317afd4e3e8f9a267b3e63949167a7f1a8e3 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Tue, 27 Oct 2026 15:04:53 +0100 Subject: [PATCH 4/6] [JLArrays] Convert kernel arguments without allocating A JLArray was converted to a POCL device array by wrapping its memory in an Array and adapting that with a fresh KernelAdaptor, allocating both on every conversion, which happens several times per argument per launch. Construct the device array from the JLArray's pointer instead. This reduces the allocations of `c .= a .+ b` from 1344 to 480 bytes. Keep rejecting isbits-union element types, which the wrapping used to reject: POCL's device arrays read their type tags after the data, where JLArrays don't store any. --- lib/JLArrays/src/array.jl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/JLArrays/src/array.jl b/lib/JLArrays/src/array.jl index 31875c9d7..58e0b5825 100644 --- a/lib/JLArrays/src/array.jl +++ b/lib/JLArrays/src/array.jl @@ -171,10 +171,14 @@ Adapt.adapt_storage(::Type{<:JLArray{T}}, xs::AbstractArray) where {T} = # adapt back to the CPU Adapt.adapt_storage(::Type{Array}, xs::JLArray) = convert(Array, xs) -# adapt for the device +# adapt for the device, where JLArrays are POCL arrays (constructed directly, rather than +# by adapting a wrapping `Array`, to avoid allocating on every launch) function Adapt.adapt_storage(::Adaptor, x::JLArray{T,N}) where {T,N} - arr = typed_data(x) - Adapt.adapt_storage(POCL.KernelAdaptor([pointer(arr)]), arr) + # POCL arrays of isbits unions read type tags after the data, which JLArrays don't store + Base.isbitsunion(T) && + throw(ArgumentError("JLArrays with isbits-union elements cannot be used in kernels")) + AS = POCL.AS.CrossWorkgroup + POCL.CLDeviceArray{T,N,AS}(x.dims, reinterpret(Core.LLVMPtr{T,AS}, pointer(x))) end ## conversions From d5b6086a3247f5ec2a11d3a7f6a2f82596de776c Mon Sep 17 00:00:00 2001 From: Christian <28689358+christiangnrd@users.noreply.github.com> Date: Tue, 20 Oct 2026 15:41:26 -0300 Subject: [PATCH 5/6] [JLArrays] Bump to 0.5 --- lib/JLArrays/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/JLArrays/Project.toml b/lib/JLArrays/Project.toml index f6fec772a..dff9d5184 100644 --- a/lib/JLArrays/Project.toml +++ b/lib/JLArrays/Project.toml @@ -1,6 +1,6 @@ name = "JLArrays" uuid = "27aeb0d3-9eb9-45fb-866b-73c2ecf80fcb" -version = "0.4.2" +version = "0.5" authors = ["Tim Besard "] [deps] From 784d123d40e549406b292c57a61bfb1fe6c1d5ed Mon Sep 17 00:00:00 2001 From: Christian Guinard <28689358+christiangnrd@users.noreply.github.com> Date: Wed, 21 Oct 2026 11:01:42 -0300 Subject: [PATCH 6/6] Increase CI timeout to 90 minutes. --- .github/workflows/Test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Test.yml b/.github/workflows/Test.yml index aa691a2a8..a7b7560a1 100644 --- a/.github/workflows/Test.yml +++ b/.github/workflows/Test.yml @@ -17,7 +17,7 @@ jobs: test: name: Julia ${{ matrix.version }} - ${{ matrix.os }} runs-on: ${{ matrix.os }} - timeout-minutes: 60 + timeout-minutes: 90 strategy: fail-fast: false matrix: