Repository navigation
Conversation
Contributor
|
Your PR requires formatting changes to meet the project's style guidelines. Click here to view the suggested changes.diff --git a/lib/level-zero/event.jl b/lib/level-zero/event.jl
index d805cf1..be21064 100644
--- a/lib/level-zero/event.jl
+++ b/lib/level-zero/event.jl
@@ -36,8 +36,8 @@ mutable struct ZeEvent
handle::ze_event_handle_t
pool::ZeEventPool
- function ZeEvent(pool, index::Integer; signal=0, wait=0)
- desc_ref = Ref(ze_event_desc_t(; index=index-1, signal, wait))
+ function ZeEvent(pool, index::Integer; signal = 0, wait = 0)
+ desc_ref = Ref(ze_event_desc_t(; index = index - 1, signal, wait))
handle_ref = Ref{ze_event_handle_t}()
zeEventCreate(pool, desc_ref, handle_ref)
obj = new(handle_ref[], pool)
diff --git a/lib/level-zero/synchronization.jl b/lib/level-zero/synchronization.jl
index 5d52351..40e3996 100644
--- a/lib/level-zero/synchronization.jl
+++ b/lib/level-zero/synchronization.jl
@@ -36,7 +36,8 @@ gcsafe_synchronize(queue::ZeCommandQueue) =
queue::ze_command_queue_handle_t, typemax(UInt64)::UInt64)::ze_result_t
gcsafe_synchronize(event::ZeEvent) =
@gcsafe_ccall libze_loader.zeEventHostSynchronize(
- event::ze_event_handle_t, typemax(UInt64)::UInt64)::ze_result_t
+ event::ze_event_handle_t, typemax(UInt64)::UInt64
+)::ze_result_t
"""
nonblocking_synchronize(list_or_queue)
diff --git a/src/compiler/compilation.jl b/src/compiler/compilation.jl
index f8aa0cb..9bdf27d 100644
--- a/src/compiler/compilation.jl
+++ b/src/compiler/compilation.jl
@@ -1,7 +1,7 @@
## gpucompiler interface implementation
Base.@kwdef struct oneAPICompilerParams <: AbstractCompilerParams
- sub_group_size::Union{Nothing,Int} = nothing
+ sub_group_size::Union{Nothing, Int} = nothing
end
const oneAPICompilerConfig = CompilerConfig{SPIRVCompilerTarget, oneAPICompilerParams}
@@ -298,8 +298,10 @@ function _atomics_capabilities(dev, properties; supports_fp16, supports_fp64)
)
end
-@noinline function _compiler_config(dev; kernel=true, name=nothing, always_inline=false,
- sub_group_size=nothing, kwargs...)
+@noinline function _compiler_config(
+ dev; kernel = true, name = nothing, always_inline = false,
+ sub_group_size = nothing, kwargs...
+ )
properties = oneL0.module_properties(dev)
supports_fp16 = properties.fp16flags & oneL0.ZE_DEVICE_MODULE_FLAG_FP16 == oneL0.ZE_DEVICE_MODULE_FLAG_FP16
supports_fp64 = properties.fp64flags & oneL0.ZE_DEVICE_MODULE_FLAG_FP64 == oneL0.ZE_DEVICE_MODULE_FLAG_FP64
diff --git a/src/oneAPIKernels.jl b/src/oneAPIKernels.jl
index 664aed0..151eabf 100644
--- a/src/oneAPIKernels.jl
+++ b/src/oneAPIKernels.jl
@@ -113,7 +113,7 @@ struct oneAPIKernel{F, H <: oneAPI.HostKernel}
host::H
end
-function KI.kernel_function(backend::oneAPIBackend, f::F, tt::TT=Tuple{}; name = nothing, kwargs...) where {F,TT}
+function KI.kernel_function(backend::oneAPIBackend, f::F, tt::TT = Tuple{}; name = nothing, kwargs...) where {F, TT}
# compile for the sub-group width that `KI.sub_group_size` promises
sub_group_size = KI.sub_group_size(backend)
if get(kwargs, :sub_group_size, sub_group_size) != sub_group_size
@@ -125,12 +125,14 @@ function KI.kernel_function(backend::oneAPIBackend, f::F, tt::TT=Tuple{}; name =
zefunction(kernel_convert(f), tt; name, backend.always_inline, kwargs...)
end
kern = oneAPIKernel(f, host)
- KI.Kernel{oneAPIBackend, typeof(kern)}(backend, kern)
+ return KI.Kernel{oneAPIBackend, typeof(kern)}(backend, kern)
end
# XXX: calling a `HostKernel` takes the arguments as varargs, so this splats them
-function KI.launch(obj::KI.Kernel{oneAPIBackend}, groups::Dims{3}, items::Dims{3},
- args::Tuple; kwargs...)
+function KI.launch(
+ obj::KI.Kernel{oneAPIBackend}, groups::Dims{3}, items::Dims{3},
+ args::Tuple; kwargs...
+ )
# KernelInterface has validated the launch geometry
if haskey(kwargs, :items) || haskey(kwargs, :groups)
throw(ArgumentError("KernelInterface kernels take `numgroups`, `workgroupsize` or `ndrange`, not `items` or `groups`"))
@@ -186,7 +188,7 @@ function device_limits(dev::oneAPI.oneL0.ZeDevice = device())
limits = get!(task_local_storage(), :oneAPIDeviceLimits) do
Dict{oneAPI.oneL0.ZeDevice, DeviceLimits}()
end::Dict{oneAPI.oneL0.ZeDevice, DeviceLimits}
- get!(limits, dev) do
+ return get!(limits, dev) do
props = oneAPI.oneL0.compute_properties(dev)
module_props = oneAPI.oneL0.module_properties(dev)
# the sub-group width that `kernel_function` compiles for: 32, like a CUDA warp, if
@@ -194,12 +196,14 @@ function device_limits(dev::oneAPI.oneL0.ZeDevice = device())
# 64, so that `KI.sub_group_ballot` (a `UInt64` mask) covers every lane.
sg_sizes = filter(<=(64), props.subGroupSizes)
sub_group_size = 32 in sg_sizes ? 32 : maximum(sg_sizes; init = 0)
- (; max_work_group_size = props.maxTotalGroupSize,
- max_work_group_dims = (props.maxGroupSizeX, props.maxGroupSizeY, props.maxGroupSizeZ),
- max_num_groups = (props.maxGroupCountX, props.maxGroupCountY, props.maxGroupCountZ),
- sub_group_size,
- supports_float16 = module_props.flags & oneAPI.oneL0.ZE_DEVICE_MODULE_FLAG_FP16 != 0,
- supports_float64 = module_props.flags & oneAPI.oneL0.ZE_DEVICE_MODULE_FLAG_FP64 != 0)
+ (;
+ max_work_group_size = props.maxTotalGroupSize,
+ max_work_group_dims = (props.maxGroupSizeX, props.maxGroupSizeY, props.maxGroupSizeZ),
+ max_num_groups = (props.maxGroupCountX, props.maxGroupCountY, props.maxGroupCountZ),
+ sub_group_size,
+ supports_float16 = module_props.flags & oneAPI.oneL0.ZE_DEVICE_MODULE_FLAG_FP16 != 0,
+ supports_float64 = module_props.flags & oneAPI.oneL0.ZE_DEVICE_MODULE_FLAG_FP64 != 0,
+ )
end
end
KI.max_work_group_size(::oneAPIBackend)::Int = device_limits().max_work_group_size
@@ -277,7 +281,8 @@ end
else
SPIRVIntrinsics.atomic_work_item_fence(
SPIRVIntrinsics.LOCAL_MEM_FENCE | SPIRVIntrinsics.GLOBAL_MEM_FENCE,
- SPIRVIntrinsics.memory_order_acq_rel, SPIRVIntrinsics.memory_scope_sub_group)
+ SPIRVIntrinsics.memory_order_acq_rel, SPIRVIntrinsics.memory_scope_sub_group
+ )
end
end
@@ -361,11 +366,15 @@ function KI.record_event(::oneAPIBackend)
ctx = oneAPI.context()
dev = oneAPI.device()
- pool = oneAPI.oneL0.ZeEventPool(ctx, 1;
- flags = oneAPI.oneL0.ZE_EVENT_POOL_FLAG_HOST_VISIBLE)
- ev = oneAPI.oneL0.ZeEvent(pool, 1;
- signal = oneAPI.oneL0.ZE_EVENT_SCOPE_FLAG_HOST,
- wait = oneAPI.oneL0.ZE_EVENT_SCOPE_FLAG_HOST)
+ pool = oneAPI.oneL0.ZeEventPool(
+ ctx, 1;
+ flags = oneAPI.oneL0.ZE_EVENT_POOL_FLAG_HOST_VISIBLE
+ )
+ ev = oneAPI.oneL0.ZeEvent(
+ pool, 1;
+ signal = oneAPI.oneL0.ZE_EVENT_SCOPE_FLAG_HOST,
+ wait = oneAPI.oneL0.ZE_EVENT_SCOPE_FLAG_HOST
+ )
@lock pending_events_lock begin
filter!(!Base.isdone, pending_events)
diff --git a/src/utils.jl b/src/utils.jl
index 05bceb8..ce3dcac 100644
--- a/src/utils.jl
+++ b/src/utils.jl
@@ -31,13 +31,15 @@ function versioninfo(io::IO=stdout)
get_module(name::Symbol) = (name, getfield(oneAPI, name))
function get_module(pkg::Tuple{String, String})
id = Base.PkgId(Base.UUID(pkg[1]), pkg[2])
- (pkg[2], get(Base.loaded_modules, id, nothing))
+ return (pkg[2], get(Base.loaded_modules, id, nothing))
end
println(io, "Julia packages:")
println(io, "- oneAPI.jl: $(Base.pkgversion(oneAPI))")
- for pkg in [:GPUArrays, :GPUCompiler, ("63c18a36-062a-441e-b654-da1e3ab1ce7c", "KernelAbstractions"),
- :KernelInterface, :LLVM, :SPIRVIntrinsics]
+ for pkg in [
+ :GPUArrays, :GPUCompiler, ("63c18a36-062a-441e-b654-da1e3ab1ce7c", "KernelAbstractions"),
+ :KernelInterface, :LLVM, :SPIRVIntrinsics,
+ ]
name, mod = get_module(pkg)
isnothing(mod) || println(io, "- $(name): $(Base.pkgversion(mod))")
end
diff --git a/test/execution.jl b/test/execution.jl
index 19d71ae..3369cac 100644
--- a/test/execution.jl
+++ b/test/execution.jl
@@ -41,20 +41,20 @@ end
end
-function store_max_sub_group_size(a)
- @inbounds a[1] = get_max_sub_group_size()
- return
-end
+ function store_max_sub_group_size(a)
+ @inbounds a[1] = get_max_sub_group_size()
+ return
+ end
-@testset "sub-group size" begin
- a = oneArray{UInt32}(undef, 1)
- sizes = oneL0.compute_properties(device()).subGroupSizes
- for sub_group_size in sizes
- @oneapi items=64 sub_group_size store_max_sub_group_size(a)
- @test Array(a)[1] == sub_group_size
+ @testset "sub-group size" begin
+ a = oneArray{UInt32}(undef, 1)
+ sizes = oneL0.compute_properties(device()).subGroupSizes
+ for sub_group_size in sizes
+ @oneapi items = 64 sub_group_size store_max_sub_group_size(a)
+ @test Array(a)[1] == sub_group_size
+ end
+ @test_throws ArgumentError @oneapi sub_group_size = maximum(sizes) + 1 dummy()
end
- @test_throws ArgumentError @oneapi sub_group_size=maximum(sizes)+1 dummy()
-end
@testset "inference" begin
diff --git a/test/kernelabstractions.jl b/test/kernelabstractions.jl
index e3f3b56..68fa9dd 100644
--- a/test/kernelabstractions.jl
+++ b/test/kernelabstractions.jl
@@ -6,12 +6,12 @@ include(joinpath(dirname(pathof(KernelAbstractions)), "..", "test", "testsuite.j
skip_tests=Set([
"sparse",
"Convert", # Need to opt out of i128
- "Random", # oneAPI doesn't support Random's default RNG in kernels yet
- # these run kernels on KernelAbstractions' POCL-based CPU back-end
- "CPU synchronization",
- "fallback test: callable types",
+ "Random", # oneAPI doesn't support Random's default RNG in kernels yet
+ # these run kernels on KernelAbstractions' POCL-based CPU back-end
+ "CPU synchronization",
+ "fallback test: callable types",
])
-Testsuite.testsuite(()->oneAPIBackend(), "oneAPI", oneAPI, oneArray, oneDeviceArray; skip_tests)
+Testsuite.testsuite(() -> oneAPIBackend(), "oneAPI", oneAPI, oneArray, oneDeviceArray; skip_tests)
KA.@kernel function store_global_linear!(A)
I = KA.@index(Global, Linear)
@@ -28,7 +28,7 @@ end
@testset "launch configuration" begin
backend = oneAPIBackend()
- function select(kernel, ndrange, workgroupsize=nothing)
+ function select(kernel, ndrange, workgroupsize = nothing)
ndrange, workgroupsize, iterspace, _ = KA.launch_config(kernel, ndrange, workgroupsize)
KA.select_launch(kernel, workgroupsize, iterspace)
end
@@ -40,20 +40,22 @@ end
# which doesn't need divisions to compute the index of a dynamic N-d range
A = oneAPI.zeros(Int, 64, 32, 16)
- ir = sprint(io -> oneAPI.@device_code_llvm io=io kernel(A; ndrange=size(A)))
+ ir = sprint(io -> oneAPI.@device_code_llvm io = io kernel(A; ndrange = size(A)))
@test !occursin(r"\b[us](div|rem) ", ir)
@test Array(A) == LinearIndices(A)
# tuning for more work-groups (the testsuite above uses the default)
- Testsuite.launch_testsuite(()->oneAPIBackend(; prefer_blocks=true), oneArray)
+ Testsuite.launch_testsuite(() -> oneAPIBackend(; prefer_blocks = true), oneArray)
# iteration spaces that don't fit 32 bits use 64-bit indices
kernel = store_last_index!(backend)
A = oneAPI.zeros(Int, 2)
- for (dims, launch) in (((2^16 + 1, 2^15), KA.NDLaunch{Int}()),
- ((2^11 + 1, 2^10, 2^10, 1), KA.LinearLaunch{Int}()))
+ for (dims, launch) in (
+ ((2^16 + 1, 2^15), KA.NDLaunch{Int}()),
+ ((2^11 + 1, 2^10, 2^10, 1), KA.LinearLaunch{Int}()),
+ )
@test select(kernel, dims) === launch
- kernel(A; ndrange=dims)
+ kernel(A; ndrange = dims)
@test Array(A) == [prod(dims), dims[2]]
end
end
@@ -69,14 +71,14 @@ end
@testset "tuning" begin
# tuning receives the number of work-items; prefer_blocks launches more, smaller groups
A = oneAPI.zeros(Int, 1024)
- kernel = KI.@launch oneAPIBackend() launch=false ki_store_index!(A)
- items = KI.launch_configuration(kernel; nitems=1024).workgroupsize
+ kernel = KI.@launch oneAPIBackend() launch = false ki_store_index!(A)
+ items = KI.launch_configuration(kernel; nitems = 1024).workgroupsize
@test items <= KI.max_work_group_size(kernel)
- kernel = KI.@launch oneAPIBackend(; prefer_blocks=true) launch=false ki_store_index!(A)
- fewer = KI.launch_configuration(kernel; nitems=1024).workgroupsize
+ kernel = KI.@launch oneAPIBackend(; prefer_blocks = true) launch = false ki_store_index!(A)
+ fewer = KI.launch_configuration(kernel; nitems = 1024).workgroupsize
@test fewer < items
# ... but not for a bound on the work-group size alone
- @test KI.launch_configuration(kernel; max_work_group_size=1024).workgroupsize == items
- KI.@launch oneAPIBackend(; prefer_blocks=true) ndrange=length(A) ki_store_index!(A)
+ @test KI.launch_configuration(kernel; max_work_group_size = 1024).workgroupsize == items
+ KI.@launch oneAPIBackend(; prefer_blocks = true) ndrange = length(A) ki_store_index!(A)
@test Array(A) == 1:1024
end
diff --git a/test/kernelinterface.jl b/test/kernelinterface.jl
index 3acfec5..1ee2403 100644
--- a/test/kernelinterface.jl
+++ b/test/kernelinterface.jl
@@ -33,15 +33,15 @@ end
workgroupsize = (width + 1, 2)
n = prod(workgroupsize)
num, sizes, max, id, lane = (oneArray{UInt32}(undef, n) for _ in 1:5)
- KI.@launch backend workgroupsize=workgroupsize ki_subgroup_kernel(num, sizes, max, id, lane)
+ KI.@launch backend workgroupsize = workgroupsize ki_subgroup_kernel(num, sizes, max, id, lane)
@test all(==(3), Array(num))
@test all(==(width), Array(max))
- @test Array(sizes) == [i < 2width ? width : 2 for i in 0:n-1]
- @test Array(id) == [div(i, width) + 1 for i in 0:n-1]
- @test Array(lane) == [rem(i, width) + 1 for i in 0:n-1]
+ @test Array(sizes) == [i < 2width ? width : 2 for i in 0:(n - 1)]
+ @test Array(id) == [div(i, width) + 1 for i in 0:(n - 1)]
+ @test Array(lane) == [rem(i, width) + 1 for i in 0:(n - 1)]
# the width is fixed
- @test_throws ArgumentError KI.@launch backend launch=false sub_group_size=(width == 16 ? 8 : 16) ki_subgroup_kernel(num, sizes, max, id, lane)
+ @test_throws ArgumentError KI.@launch backend launch = false sub_group_size = (width == 16 ? 8 : 16) ki_subgroup_kernel(num, sizes, max, id, lane)
end
function ki_fill!(A)
@@ -54,15 +54,15 @@ end
@testset "launch keywords" begin
A = oneAPI.zeros(Int, 4)
- kernel = KI.@launch oneAPIBackend() launch=false ki_fill!(A)
+ kernel = KI.@launch oneAPIBackend() launch = false ki_fill!(A)
# oneAPI's launch options are passed on
- kernel(A; ndrange=4, queue=oneAPI.global_stream(oneAPI.context(), oneAPI.device()))
+ kernel(A; ndrange = 4, queue = oneAPI.global_stream(oneAPI.context(), oneAPI.device()))
@test Array(A) == 1:4
# but not ones that would override the launch geometry
- @test_throws ArgumentError kernel(A; ndrange=4, items=8)
- @test_throws ArgumentError kernel(A; ndrange=4, groups=2)
+ @test_throws ArgumentError kernel(A; ndrange = 4, items = 8)
+ @test_throws ArgumentError kernel(A; ndrange = 4, groups = 2)
end
@testset "versioninfo" begin |
This was referenced Sep 30, 2026
Closed
Closed
maleadt
force-pushed
the
ka-0.10
branch
2 times, most recently
from
October 10, 2026 16:56
941a7d9 to
414c888
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #653 +/- ##
==========================================
- Coverage 80.56% 79.58% -0.98%
==========================================
Files 52 51 -1
Lines 3961 3953 -8
==========================================
- Hits 3191 3146 -45
- Misses 770 807 +37 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
In preparation for KernelAbstractions becoming a weak dependency.
Like the spill size, cache the kernel's `maxGroupSize` in the `ZeKernel`, so that `launch_configuration` (and the KernelInterface launch validation, which checks every explicit work-group size against it) don't query the kernel properties, which allocates, on every launch.
KernelAbstractions 0.10 builds on KernelInterface, which defines what a back end provides: memory and device management, compiling and launching kernels, and the device-side intrinsics. KernelAbstractions then launches `@kernel` kernels itself on any KernelInterface back end, which replaces oneAPI's copy of that launch path: partitioning the ndrange, building the kernel's context, and tuning the work-group size. `oneAPIBackend` now implements KernelInterface, and oneAPI depends on it instead of on KernelAbstractions. What KernelAbstractions still needs from a back end moves to an extension: the `MArray` behind `@private`, the Adapt rule for `@Const`, and adapting to KernelAbstractions' CPU back end. `prefer_blocks` now applies in `KI.launch_configuration`, which receives the number of work-items. The index queries are computed in the requested type with `% T`, which needs SPIRVIntrinsics 1.1.3 to truncate the 3-D built-ins without producing illegal vector types. `KI.launch` rejects `items` and `groups`, which would override the launch geometry that KernelInterface validated, and refuses to launch a kernel on another context or device than the one it was compiled for. `KI.kernel_function` converts the callable to compile it, and the kernel keeps the original alive, as the converted form of a closure only holds pointers to the arrays it captures. Co-authored-by: Christian Guinard <28689358+christiangnrd@users.noreply.github.com>
The `sub_group_size` compiler keyword sets the `intel_reqd_sub_group_size` metadata of the kernel. By default, the compiler still chooses the sub-group size, and a size the device doesn't support is an error.
Implement the sub-group queries, `sub_group_barrier` and `shfl_down`, and report their support to KernelInterface, which requires kernels to execute with a fixed sub-group width: `kernel_function` compiles for 32 work-items, like a CUDA warp, if the device supports that width, and for its widest one otherwise. Shuffles of `Float16` and `Float64` values depend on the device supporting those types. KernelInterface leaves unspecified how work-items are grouped into sub-groups; Intel GPUs form them from consecutive linear work-item indices, so the last sub-group of a work-group can be partial, which a test checks. Co-authored-by: Christian Guinard <28689358+christiangnrd@users.noreply.github.com>
Implement `KI.record_event` with an event signaled on the task's stream, and `KI.wait_event` by waiting for it on the host, cooperatively, like `synchronize` does. `KernelAbstractions.@spawn` uses them to order a new task's work after the work its parent had queued, without synchronizing the parent; the default is a full synchronization. The wait happens on the host, not by making the waiting task's stream wait on the device, because a Level Zero event must not be destroyed while a command list still references it, and the stream would reference it until it executes the wait. For the same reason, recorded events are kept alive until they have been signaled, independently of the recording task, which may end before that. KernelInterface's testsuite checks them once `record_event` returns an event. Co-authored-by: Christian Guinard <28689358+christiangnrd@users.noreply.github.com>
`KI.versioninfo(oneAPIBackend())` prints `oneAPI.versioninfo()`.
KernelAbstractions now backs @Private with stack memory from GPUCompiler.alloca, defined as an overlay in GPUCompiler's shared method table, so drop our StaticArrays-based Scratchpad override and with it the StaticArrays dependency. This relies on our compiler consulting the shared method table, which oneAPI.jl#660 implements.
Implement `shfl`, `shfl_up`, `shfl_down` and `shfl_xor` for the types SPIR-V shuffles natively (leaving others to KernelInterface's word and field fallbacks), the votes with the native ballot, and the native reductions and scans where they have Julia's semantics: `+` on integers and floats, and `min`/`max` on integers. `shfl_down` and `shfl_up` return the work-item's own value past the sub-group width, which SPIR-V leaves undefined. Intel's GPU compiler forms sub-groups from consecutive work-items, and they execute independently, so report both `supports_linear_subgroups` and `supports_independent_subgroups`. Only widths of up to 64 are used, so that the ballot covers every lane.
Report `KI.supports_partitionable_subgroups`: an Intel GPU sub-group is the SIMD lanes of one EU thread, and the shuffles and ballot that the width functions use are `OpGroupNonUniform*` instructions over the active lanes. A sub-group `OpControlBarrier` has to be executed by the whole sub-group, so `KI.sub_group_barrier(width)` for a segment narrower than the sub-group is a sub-group-scope memory barrier on local and global memory instead.
With ONEAPI_SYNC_EACH_SUBMISSION, as on Aurora, every launch waits for the device, so the event tests' timing check fails.
michel2323
added a commit
that referenced
this pull request
Oct 23, 2026
The KernelAbstractions back-end returned the hardware global id for `@index(Global, Linear)`. That only equals the linear index into `ndrange` when the work-group size divides every dimension of it; otherwise KernelAbstractions pads the range and kernels skip elements and write out of bounds. GPUArrays' `getindex`, `permutedims` and `rmul!` kernels all rely on this index, so e.g. `x[:, 1:2]` on a 513x3 matrix returned zeros, `x[:, 2, :, :]` on a (64, 4, 500, B) array was wrong for every B >= 2, and `rmul!` on a (64, 1000, 16) ComplexF32 array page-faulted. Map the id through the iteration space instead, as the CPU back-end and CUDA.jl do, and add regression tests for padded N-d ranges with automatic and explicit work-group sizes plus the reported `getindex`, `permutedims` and `rmul!` cases. Backport to the 2.9 series (KernelAbstractions 0.9 / GPUArrays 11); main gets this through the KernelInterface back-end (#653). Analysis and patch by Wilkie Olin-Ammentorp (@wilkieolin). Co-authored-by: Wilkie Olin-Ammentorp <10157823+wilkieolin@users.noreply.github.com> Fixes #509
michel2323
added a commit
that referenced
this pull request
Oct 23, 2026
The KernelAbstractions back-end returned the hardware global id for `@index(Global, Linear)`. That only equals the linear index into `ndrange` when the work-group size divides every dimension of it; otherwise KernelAbstractions pads the range and kernels skip elements and write out of bounds. GPUArrays' `getindex`, `permutedims` and `rmul!` kernels all rely on this index, so e.g. `x[:, 1:2]` on a 513x3 matrix returned zeros, `x[:, 2, :, :]` on a (64, 4, 500, B) array was wrong for every B >= 2, and `rmul!` on a (64, 1000, 16) ComplexF32 array page-faulted. Map the id through the iteration space instead, as the CPU back-end and CUDA.jl do, and add regression tests for padded N-d ranges with automatic and explicit work-group sizes plus the reported `getindex`, `permutedims` and `rmul!` cases. Backport to the 2.9 series (KernelAbstractions 0.9 / GPUArrays 11); main gets this through the KernelInterface back-end (#653). Analysis and patch by Wilkie Olin-Ammentorp (@wilkieolin). Fixes #509 Co-authored-by: Wilkie Olin-Ammentorp <10157823+wilkieolin@users.noreply.github.com>
michel2323
added a commit
that referenced
this pull request
Oct 27, 2026
The KernelAbstractions back-end returned the hardware global id for `@index(Global, Linear)`. That only equals the linear index into `ndrange` when the work-group size divides every dimension of it; otherwise KernelAbstractions pads the range and kernels skip elements and write out of bounds. GPUArrays' `getindex`, `permutedims` and `rmul!` kernels all rely on this index, so e.g. `x[:, 1:2]` on a 513x3 matrix returned zeros, `x[:, 2, :, :]` on a (64, 4, 500, B) array was wrong for every B >= 2, and `rmul!` on a (64, 1000, 16) ComplexF32 array page-faulted. Map the id through the iteration space instead, as the CPU back-end and CUDA.jl do, and add regression tests for padded N-d ranges with automatic and explicit work-group sizes plus the reported `getindex`, `permutedims` and `rmul!` cases. Backport to the 2.9 series (KernelAbstractions 0.9 / GPUArrays 11); main gets this through the KernelInterface back-end (#653). Analysis and patch by Wilkie Olin-Ammentorp (@wilkieolin). Fixes #509 Co-authored-by: Wilkie Olin-Ammentorp <10157823+wilkieolin@users.noreply.github.com>
michel2323
added a commit
that referenced
this pull request
Oct 27, 2026
The KernelAbstractions back-end returned the hardware global id for `@index(Global, Linear)`. That only equals the linear index into `ndrange` when the work-group size divides every dimension of it; otherwise KernelAbstractions pads the range and kernels skip elements and write out of bounds. GPUArrays' `getindex`, `permutedims` and `rmul!` kernels all rely on this index, so e.g. `x[:, 1:2]` on a 513x3 matrix returned zeros, `x[:, 2, :, :]` on a (64, 4, 500, B) array was wrong for every B >= 2, and `rmul!` on a (64, 1000, 16) ComplexF32 array page-faulted. Map the id through the iteration space instead, as the CPU back-end and CUDA.jl do, and add regression tests for padded N-d ranges with automatic and explicit work-group sizes plus the reported `getindex`, `permutedims` and `rmul!` cases. Backport to the 2.9 series (KernelAbstractions 0.9 / GPUArrays 11); main gets this through the KernelInterface back-end (#653). Analysis and patch by Wilkie Olin-Ammentorp (@wilkieolin). Fixes #509 Co-authored-by: Wilkie Olin-Ammentorp <10157823+wilkieolin@users.noreply.github.com>
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.
KernelAbstractions 0.10 moves its back-end API into a separate package, KernelInterface. A back end implements KernelInterface: allocating and copying memory, selecting devices, compiling and launching kernels, and the device-side intrinsics. KernelAbstractions then launches
@kernelkernels the same way on every back end (JuliaGPU/KernelAbstractions.jl#801). This PR ports oneAPI.jl to that model in one step, the way JuliaGPU/CUDA.jl#3314 does for CUDA. It supersedes #624, which added KernelInterface 0.3 next to the KernelAbstractions 0.9 back end, and theintrinsicsbranch, which ported an earlier KernelInterface to KernelAbstractions 0.10.oneAPIBackendnow implements KernelInterface, and oneAPI.jl depends on KernelInterface instead of KernelAbstractions. That makes the KernelInterface layer usable on its own, without KernelAbstractions' macros:KernelAbstractions becomes a weak dependency. Its extension only provides what KernelAbstractions still needs from a back end: the
MArraybehind@private, the Adapt rule behind@Const, and adapting arrays to KernelAbstractions' CPU back end. oneAPI's own copy of the KernelAbstractions launch path goes away: partitioning the ndrange, building the kernel's context, tuning the work-group size, and calling the kernel.oneAPIBackend(; prefer_blocks=true)still spreads small launches over more work-groups; that now happens inKI.launch_configuration, which receives the number of work-items to cover. There is no equivalent of CUDA'smaxthreadshint for kernels with a static work-group size, since oneAPI's compiler has no such option.Kernels behave the same, but they are launched differently. KernelAbstractions now launches them on a 3-D grid, and computes
@indexin 32 bits when the iteration space fits (JuliaGPU/KernelAbstractions.jl#797), so a kernel over a 3-Dndrangedoesn't need divisions to compute its index; the test suite checks that in the generated code. With a KernelAbstractions kernel that copies aFloat32array, on the Iris Xe of a Tiger Lake laptop (median of 7-8 runs, each the minimum of 200 host-timed launches; this GPU is memory-bound and its timings vary by about 5% between runs):@index(Global, Cartesian)@index(Global, Linear)@index(Global, Cartesian)@index(Global, Linear)Unlike on CUDA, the index computation hardly matters on this GPU: the kernel times are within the noise of main. For the odd-sized range, both main and this PR are about 20% slower than the same copy launched over
ndrange=length(A), independent of the work-group shape; JuliaGPU/KernelAbstractions.jl#814 tracks that. The launches themselves got cheaper.KI.launchrejectsitemsandgroups, which would override the launch geometry KernelInterface has validated, but passes oneAPI's other launch options on, and refuses to launch a kernel on another context or device than the one it was compiled for:KernelInterface 0.4 passes the kernel arguments to the back end as a tuple (JuliaGPU/KernelAbstractions.jl#811). oneAPI has no launch path that takes a tuple, like CUDA got in JuliaGPU/CUDA.jl#3309, so
KI.launchsplats them into theHostKernelcall for now; the launch overhead above includes that.The first commits prepare for the port:
versioninfoonly lists KernelAbstractions when it is loaded, a kernel's maximum group size is cached so that launches don't query it, andsynchronizebecomes cooperative, as KernelInterface requires: it busy-waits briefly and then blocks in the driver on a separate thread, so other tasks keep running (synchronize(; blocking=true)restores the old behavior). Then comes the port itself. The commits after it implement parts of KernelInterface that are optional:@oneapi sub_group_size=Ncompiles a kernel for a given sub-group size. KernelInterface kernels are compiled for a fixed one, 32 if the device supports it, which enables KernelInterface's sub-group queries, barrier andshfl_down;KI.record_eventandKI.wait_event, whichKernelAbstractions.@spawnuses to order a new task's work after the work its parent had queued, without synchronizing the parent. The new task waits for the event on the host (cooperatively), not on the device, because a Level Zero event must not be destroyed while a command list still references it;KI.versioninfo, which printsoneAPI.versioninfo().Some limitations remain. KernelAbstractions converts the arguments twice, once to determine the argument types to compile for and once when launching;
kernel_convertis supposed to be pure, so this only shows with a conversion that has side effects. KernelAbstractions' newRandomtests are skipped, as oneAPI doesn't support Random's default RNG in kernels yet. WithONEAPI_SYNC_EACH_SUBMISSION(the Aurora LTS workaround), the wait after each kernel launch is cooperative, but the one after other submissions, such as copies, still blocks the thread.This needs a breaking release, since it requires KernelAbstractions 0.10 and KernelInterface 0.4, which aren't registered yet, and an AcceleratedKernels release that supports them. Until then, the last commit takes all three from their development branches through
[sources]. Because Pkg doesn't accept[sources]for a weak dependency, and AcceleratedKernels pulls KernelAbstractions in anyway, that commit also makes KernelAbstractions a regular dependency again. Julia 1.10 and 1.11 don't pick up[sources]for the tests, so CI develops the packages explicitly there, and the documentation is built with Julia 1.12. That commit is dropped before merging.