KernelInterface - #624
Closed
christiangnrd wants to merge 9 commits into
Closed
KernelInterface#624christiangnrd wants to merge 9 commits into
christiangnrd wants to merge 9 commits into
Conversation
christiangnrd
marked this pull request as draft
August 22, 2026 18:35
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/synchronization.jl b/lib/level-zero/synchronization.jl
index 809a03b..0143c93 100644
--- a/lib/level-zero/synchronization.jl
+++ b/lib/level-zero/synchronization.jl
@@ -28,22 +28,24 @@ Base.isdone(queue::ZeCommandQueue) =
# the blocking synchronization, marked GC-safe so that it doesn't keep the GC from running
gcsafe_synchronize(list::ZeImmediateCommandList) =
@gcsafe_ccall libze_loader.zeCommandListHostSynchronize(
- list::ze_command_list_handle_t, typemax(UInt64)::UInt64)::ze_result_t
+ list::ze_command_list_handle_t, typemax(UInt64)::UInt64
+)::ze_result_t
gcsafe_synchronize(queue::ZeCommandQueue) =
@gcsafe_ccall libze_loader.zeCommandQueueSynchronize(
- queue::ze_command_queue_handle_t, typemax(UInt64)::UInt64)::ze_result_t
+ queue::ze_command_queue_handle_t, typemax(UInt64)::UInt64
+)::ze_result_t
## bidirectional channel
# custom, unbuffered channel that supports returning a value to the sender
# without the need for a second channel
-struct BidirectionalChannel{I,O} <: AbstractChannel{I}
+struct BidirectionalChannel{I, O} <: AbstractChannel{I}
cond_take::Threads.Condition # waiting for data to become available
cond_put::Threads.Condition # waiting for a writeable slot
cond_ret::Threads.Condition # waiting for a data to be returned
- function BidirectionalChannel{I,O}() where {I,O}
+ function BidirectionalChannel{I, O}() where {I, O}
lock = ReentrantLock()
cond_put = Threads.Condition(lock)
cond_take = Threads.Condition(lock)
@@ -53,9 +55,9 @@ struct BidirectionalChannel{I,O} <: AbstractChannel{I}
end
Base.put!(c::BidirectionalChannel{I}, v) where {I} = put!(c, convert(I, v))
-function Base.put!(c::BidirectionalChannel{I,O}, v::I) where {I,O}
+function Base.put!(c::BidirectionalChannel{I, O}, v::I) where {I, O}
lock(c)
- try
+ return try
# wait for a slot to be available
while isempty(c.cond_take)
Base.wait(c.cond_put)
@@ -71,9 +73,9 @@ function Base.put!(c::BidirectionalChannel{I,O}, v::I) where {I,O}
end
end
-function Base.take!(f::Base.Callable, c::BidirectionalChannel{I,O}) where {I,O}
+function Base.take!(f::Base.Callable, c::BidirectionalChannel{I, O}) where {I, O}
lock(c)
- try
+ return try
# notify the producer that we're ready to accept a value
notify(c.cond_put, nothing, false, false)
@@ -123,7 +125,7 @@ end
## slow path: synchronize on a separate thread
const MAX_SYNC_THREADS = 4
-const sync_channels = Array{BidirectionalChannel{SyncObject,ze_result_t}}(undef, MAX_SYNC_THREADS)
+const sync_channels = Array{BidirectionalChannel{SyncObject, ze_result_t}}(undef, MAX_SYNC_THREADS)
const sync_channel_cursor = Threads.Atomic{UInt32}(1)
const sync_channel_lock = Base.ReentrantLock()
@@ -135,6 +137,7 @@ function synchronization_worker(data)
# wait for work
take!(gcsafe_synchronize, chan)
end
+ return
end
@noinline function create_synchronization_worker(i)
@@ -146,7 +149,7 @@ end
# should be safe to assign before threads are running;
# any user will just submit work that makes it block
- sync_channels[i] = BidirectionalChannel{SyncObject,ze_result_t}()
+ sync_channels[i] = BidirectionalChannel{SyncObject, ze_result_t}()
# we don't know what the size of uv_thread_t is, so reserve enough space
tid = Ref{NTuple{32, UInt8}}(ntuple(i -> 0, 32))
diff --git a/src/compiler/compilation.jl b/src/compiler/compilation.jl
index 7305e04..3a9808f 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}
@@ -281,7 +281,7 @@ function _driver_supports_bfloat16_spirv(dev=device())
end
end
-@noinline function _compiler_config(dev; kernel=true, name=nothing, always_inline=false, sub_group_size=32, kwargs...)
+@noinline function _compiler_config(dev; kernel = true, name = nothing, always_inline = false, sub_group_size = 32, 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/context.jl b/src/context.jl
index 6c9e188..36f0cd0 100644
--- a/src/context.jl
+++ b/src/context.jl
@@ -401,7 +401,7 @@ println("GPU work completed")
See also: [`global_stream`](@ref), [`context`](@ref), [`device`](@ref)
"""
-function oneL0.synchronize(s::oneStream; blocking::Bool=false)
+function oneL0.synchronize(s::oneStream; blocking::Bool = false)
sync = blocking ? oneL0.synchronize : oneL0.nonblocking_synchronize
sync(s.list)
q = s.queue
@@ -412,8 +412,8 @@ function oneL0.synchronize(s::oneStream; blocking::Bool=false)
return
end
-function oneL0.synchronize(; blocking::Bool=false)
- oneL0.synchronize(global_stream(context(), device()); blocking)
+function oneL0.synchronize(; blocking::Bool = false)
+ return oneL0.synchronize(global_stream(context(), device()); blocking)
end
# Julia → MKL ordering: everything Julia appended to the task's immediate list must be
diff --git a/src/oneAPIKernels.jl b/src/oneAPIKernels.jl
index 7346df2..4bd05f8 100644
--- a/src/oneAPIKernels.jl
+++ b/src/oneAPIKernels.jl
@@ -73,7 +73,7 @@ end
KI.argconvert(::oneAPIBackend, arg) = kernel_convert(arg)
-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)
kern = if sub_group_size > 0
@@ -81,7 +81,7 @@ function KI.kernel_function(backend::oneAPIBackend, f::F, tt::TT=Tuple{}; name =
else
zefunction(f, tt; name, backend.always_inline, kwargs...)
end
- KI.Kernel{oneAPIBackend, typeof(kern)}(backend, kern)
+ return KI.Kernel{oneAPIBackend, typeof(kern)}(backend, kern)
end
function KI.launch(obj::KI.Kernel{oneAPIBackend}, groups::Dims{3}, items::Dims{3}, args::Vararg{Any, N}; kwargs...) where {N}
@@ -114,19 +114,21 @@ 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: the width `@oneapi` defaults
# to if the device supports it, and 0 if the device has no sub-groups
sg_sizes = 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
@@ -134,7 +136,7 @@ KI.max_work_group_dims(::oneAPIBackend)::NTuple{3, Int} = device_limits().max_wo
KI.max_num_groups(::oneAPIBackend)::NTuple{3, Int} = device_limits().max_num_groups
KI.sub_group_size(::oneAPIBackend)::Int = device_limits().sub_group_size
function KI.multiprocessor_count(::oneAPIBackend)::Int
- oneAPI.oneL0.properties(device()).numSlices
+ return oneAPI.oneL0.properties(device()).numSlices
end
KI.supports_subgroups(::oneAPIBackend) = device_limits().sub_group_size > 0
@@ -199,7 +201,7 @@ end
sub_group_barrier(SPIRVIntrinsics.LOCAL_MEM_FENCE | SPIRVIntrinsics.GLOBAL_MEM_FENCE)
end
-@device_override function KI.shfl_down(val::T, offset::Integer) where T
+@device_override function KI.shfl_down(val::T, offset::Integer) where {T}
sub_group_shuffle(val, get_sub_group_local_id() + offset)
end
diff --git a/src/oneAPIKernelsOld.jl b/src/oneAPIKernelsOld.jl
index 854e8ab..a9ecfbb 100644
--- a/src/oneAPIKernelsOld.jl
+++ b/src/oneAPIKernelsOld.jl
@@ -49,7 +49,7 @@ end
## Memory Operations
function KA.copyto!(::oneAPIBackend, A, B)
- copyto!(A, B)
+ return copyto!(A, B)
# TODO: Address device to host copies in jl being synchronizing
end
@@ -75,11 +75,13 @@ end
## Kernel Launch
function KA.mkcontext(kernel::KA.Kernel{oneAPIBackend}, _ndrange, iterspace)
- KA.CompilerMetadata{KA.ndrange(kernel), KA.DynamicCheck}(_ndrange, iterspace)
+ return KA.CompilerMetadata{KA.ndrange(kernel), KA.DynamicCheck}(_ndrange, iterspace)
end
-function KA.mkcontext(kernel::KA.Kernel{oneAPIBackend}, I, _ndrange, iterspace,
- ::Dynamic) where Dynamic
- KA.CompilerMetadata{KA.ndrange(kernel), Dynamic}(I, _ndrange, iterspace)
+function KA.mkcontext(
+ kernel::KA.Kernel{oneAPIBackend}, I, _ndrange, iterspace,
+ ::Dynamic
+ ) where {Dynamic}
+ return KA.CompilerMetadata{KA.ndrange(kernel), Dynamic}(I, _ndrange, iterspace)
end
function KA.launch_config(kernel::KA.Kernel{oneAPIBackend}, ndrange, workgroupsize)
@@ -87,7 +89,7 @@ function KA.launch_config(kernel::KA.Kernel{oneAPIBackend}, ndrange, workgroupsi
ndrange = (ndrange,)
end
if workgroupsize isa Integer
- workgroupsize = (workgroupsize, )
+ workgroupsize = (workgroupsize,)
end
# partition checked that the ndrange's agreed
@@ -96,7 +98,7 @@ function KA.launch_config(kernel::KA.Kernel{oneAPIBackend}, ndrange, workgroupsi
end
iterspace, dynamic = if KA.workgroupsize(kernel) <: KA.DynamicSize &&
- workgroupsize === nothing
+ workgroupsize === nothing
# use ndrange as preliminary workgroupsize for autotuning
# (clamped to 1, since an empty ndrange cannot serve as a workgroup size)
KA.partition(kernel, ndrange, max.(ndrange, 1))
@@ -116,7 +118,7 @@ function threads_to_workgroupsize(threads, ndrange)
end
end
-function (obj::KA.Kernel{oneAPIBackend})(args...; ndrange=nothing, workgroupsize=nothing)
+function (obj::KA.Kernel{oneAPIBackend})(args...; ndrange = nothing, workgroupsize = nothing)
backend = KA.backend(obj)
ndrange, workgroupsize, iterspace, dynamic = KA.launch_config(obj, ndrange, workgroupsize)
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 330a161..4df4759 100644
--- a/test/execution.jl
+++ b/test/execution.jl
@@ -756,7 +756,7 @@ end
@testset "cooperative synchronize" begin
a = oneArray{UInt32}(undef, 64)
- slow(iters) = @oneapi items=64 slow_kernel(a, UInt32(iters))
+ slow(iters) = @oneapi items = 64 slow_kernel(a, UInt32(iters))
slow(1)
synchronize()
# warm up the slow path of `synchronize`: compiling it would end the calibration early
@@ -785,7 +785,7 @@ end
# blocking synchronization is still available
slow(1)
- @test synchronize(; blocking=true) === nothing
+ @test synchronize(; blocking = true) === nothing
end
############################################################################################ |
christiangnrd
force-pushed
the
interface
branch
5 times, most recently
from
August 22, 2026 19:54
fa0662b to
8bb33ae
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #624 +/- ##
==========================================
- Coverage 80.73% 80.56% -0.18%
==========================================
Files 56 57 +1
Lines 4060 4147 +87
==========================================
+ Hits 3278 3341 +63
- Misses 782 806 +24 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
christiangnrd
force-pushed
the
interface
branch
2 times, most recently
from
September 3, 2026 19:12
261d87f to
e6e9102
Compare
christiangnrd
force-pushed
the
interface
branch
from
September 11, 2026 19:42
a9a0ec1 to
32e0901
Compare
christiangnrd
force-pushed
the
interface
branch
4 times, most recently
from
September 22, 2026 20:07
19fba83 to
b312ab0
Compare
christiangnrd
marked this pull request as ready for review
September 22, 2026 20:09
christiangnrd
force-pushed
the
interface
branch
4 times, most recently
from
September 26, 2026 18:50
4f40b47 to
dd7431b
Compare
In preparation for KernelAbstractions becoming a weak dependency.
The `sub_group_size` compiler keyword sets the `intel_reqd_sub_group_size` metadata of the kernel, defaulting to 32.
Implement KernelInterface next to the KernelAbstractions back end, which moves to oneAPIKernelsOld.jl until the port to KernelAbstractions 0.10. The per-dimension launch limits (`max_work_group_dims`, `max_num_groups`) come from the Level Zero compute properties. Every auto-sized launch queries them, so they are cached per device. The KernelInterface test suite runs as the `kernelinterface` test. Its events test is skipped when every submission synchronizes (the Aurora LTS workaround, ONEAPI_SYNC_EACH_SUBMISSION), as the ordering it checks is not observable then. Co-authored-by: Tim Besard <tim.besard@gmail.com>
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.
- subtype `KI.Backend`, and implement `KI.launch` instead of the call method: KernelInterface now validates the launch geometry itself; - implement the four primitive index queries with `% T`, and let KernelInterface derive the global ones; the sub-group queries take a type; - `max_work_group_size(kernel)` is the kernel's legal maximum, and `launch_configuration` the recommended size; - `supports_subgroups`/`supports_shuffle` replace `shfl_down_types`, and `supports_float64` and shuffles of `Float16`/`Float64` depend on the active device. `kernel_function` compiles for the sub-group width that `sub_group_size` reports, as KernelInterface now guarantees; - `kernel_function` keeps the backend it was given, so `always_inline` applies; - `copyto!` checks the lengths and returns the destination, `device!` returns `nothing`, and `device(backend, A)` and `unsafe_free!` are implemented; - launching a kernel on another device than it was compiled for throws; - drop `zeros`/`ones`, which KernelInterface implements generically. The testsuite now skips its events test by itself for back ends that record events by synchronizing, as this one does, so the explicit skip is gone. The typed index queries need SPIRVIntrinsics 1.1.3, which truncates the 3-D built-ins without producing illegal vector types. KernelInterface 0.3 isn't registered yet, so take it from its branch with `[sources]`, and develop it on Julia 1.10, which ignores `[sources]`.
`synchronize` blocked the calling thread in the driver until the work had completed, so no other task could run on it in the meantime, and no other thread could run the GC either. KernelInterface requires `synchronize` to be cooperative. Wait like CUDA.jl does: busy-wait on a non-blocking query first, which keeps the latency of short operations low, and then block in the driver (GC-safe) on one of a few dedicated threads, while the calling task waits for it without blocking the scheduler. This applies to `synchronize()` and `synchronize(::oneStream)`, i.e., to user code, KernelAbstractions, KernelInterface and the synchronizing copies; `synchronize(; blocking=true)` restores the old behavior. The command list and queue methods, which also run from finalizers, keep blocking.
Julia doesn't specialize a method on `args...` that it only passes through, which made every launch through KernelInterface's generic launch dispatch dynamically (+1.2 µs and +1 kB per launch on CUDA).
KernelInterface 0.3 passes the number of work-items to launch as `nitems`, separately from the bound on the work-group size.
With `ONEAPI_SYNC_EACH_SUBMISSION` set, as on Aurora's LTS stack, every kernel launch blocked the thread in the driver until the kernel had completed. `synchronize` then had nothing left to wait for, so no other task ran in the meantime, which failed the cooperative synchronization test there. Wait for the launch cooperatively, like `synchronize` does. Also warm up the slow path of `synchronize` in that test. Otherwise, in a fresh process, compiling it makes the first calibration step exceed its time budget, and the kernel is then too short to observe other tasks.
Member
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.
Do not merge until KernelInterface has been reviewed and interface fully decided