Skip to content

Commit 2dbe13f

Browse files
michel2323claude
andcommitted
Gate the LTS workarounds behind a runtime ONEAPI_LTS flag (layers 1-2)
Make the Intel LTS-stack support opt-in so this branch can eventually merge into main without affecting the rolling stack: with the flag off, every LTS code path is bypassed and behavior matches upstream. Introduce oneL0.LTS[], resolved at the top of oneL0.__init__ from ONEAPI_LTS (default on for this branch; set ONEAPI_LTS=0 for the rolling-stack paths). Resolved before the driver-availability early returns so it is set even on a host without a functional GPU. Gate on it: - Layer 1 (behavior): the strided-reduction materialization and the coalesced-kernel dispatch in mapreducedim!; the command-queue registry (global_queue / synchronize_all_queues), the pre-free synchronize in release, and the queue-finalizer drain + handle-null in cmdqueue.jl. - Layer 2 (codegen): _compiler_config now selects the SPIRVCompilerTarget backend (:khronos translator vs :llvm back-end) and the SPIR-V extension list from the flag. GPUCompiler loads the tool lazily, so both SPIR-V JLLs can coexist as deps and the choice is made at compile time. Deferred to layer 3: driver-JLL selection (NEO/loader/headers/igc) via Preferences and the oneMKL support-library ABI. The oneMKL FFT GC-rooting is left as-is (already fixed upstream via GC.@preserve). Verified: precompiles and loads; ONEAPI_LTS unset/1 -> on, ONEAPI_LTS=0 -> off. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4fd200f commit 2dbe13f

6 files changed

Lines changed: 69 additions & 30 deletions

File tree

lib/level-zero/cmdqueue.jl

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,21 @@ mutable struct ZeCommandQueue
2020
zeCommandQueueCreate(ctx, dev, desc_ref, handle_ref)
2121
obj = new(handle_ref[], ctx, dev, ordinal)
2222
finalizer(obj) do obj
23-
# the queue may still have work in flight (nothing requires a task to
24-
# synchronize before dying), and zeCommandQueueDestroy does not wait for
25-
# it: on the LTS NEO stack the still-running work then faults as soon as
26-
# a referenced allocation is freed, getting the context banned. drain the
27-
# queue first; unchecked, as sync on a banned context returns an error.
28-
unchecked_zeCommandQueueSynchronize(obj, typemax(UInt64))
23+
if LTS[]
24+
# the queue may still have work in flight (nothing requires a task to
25+
# synchronize before dying), and zeCommandQueueDestroy does not wait for
26+
# it: on the LTS NEO stack the still-running work then faults as soon as
27+
# a referenced allocation is freed, getting the context banned. drain the
28+
# queue first; unchecked, as sync on a banned context returns an error.
29+
unchecked_zeCommandQueueSynchronize(obj, typemax(UInt64))
30+
end
2931
zeCommandQueueDestroy(obj)
30-
# mark the queue as destroyed: it can still be weakly reachable (e.g. from
31-
# the queue registry used by `synchronize_all_queues`), and synchronizing a
32-
# destroyed handle crashes in the driver.
33-
obj.handle = ze_command_queue_handle_t(C_NULL)
32+
if LTS[]
33+
# mark the queue as destroyed: it can still be weakly reachable (e.g. from
34+
# the queue registry used by `synchronize_all_queues`), and synchronizing a
35+
# destroyed handle crashes in the driver.
36+
obj.handle = ze_command_queue_handle_t(C_NULL)
37+
end
3438
end
3539
obj
3640
end

lib/level-zero/oneL0.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,26 @@ const functional = Ref{Bool}(false)
139139
const validation_layer = Ref{Bool}()
140140
const parameter_validation = Ref{Bool}()
141141

142+
# Master switch for the Intel LTS-stack workarounds (driver/IGC quirks on the Aurora
143+
# LTS NEO 25.18 stack): the SPIR-V translator codegen path, strided-reduction
144+
# materialization, and command-queue drain-before-free. All such code paths are gated
145+
# on `LTS[]`, so with it disabled the package behaves exactly like the upstream rolling
146+
# stack. Default on for this branch (which pins the LTS toolchain); set `ONEAPI_LTS=0`
147+
# to exercise the rolling-stack paths. When merged upstream the default flips to false
148+
# and an LTS deployment opts in with `ONEAPI_LTS=1`.
149+
const LTS = Ref{Bool}(true)
150+
142151
function __init__()
143152
precompiling = ccall(:jl_generating_output, Cint, ()) != 0
144153
precompiling && return
145154

155+
# Resolve the LTS master switch up front, before the driver-availability early
156+
# returns below: it gates codegen and behavior and must be set even on hosts
157+
# without a functional GPU. Default on for this (LTS) branch; flip the "true"
158+
# default to "false" when merged onto the rolling stack. ONEAPI_LTS=0 forces the
159+
# rolling-stack code paths.
160+
LTS[] = lowercase(get(ENV, "ONEAPI_LTS", "true")) in ("1", "true", "yes", "on")
161+
146162
if Sys.iswindows()
147163
if Libdl.dlopen(libze_loader; throw_error=false) === nothing
148164
@error "The oneAPI Level Zero loader was not found. Please ensure the Intel GPU drivers are installed."

src/compiler/compilation.jl

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -187,14 +187,25 @@ end
187187
supports_fp16 = oneL0.module_properties(device()).fp16flags & oneL0.ZE_DEVICE_MODULE_FLAG_FP16 == oneL0.ZE_DEVICE_MODULE_FLAG_FP16
188188
supports_fp64 = oneL0.module_properties(device()).fp64flags & oneL0.ZE_DEVICE_MODULE_FLAG_FP64 == oneL0.ZE_DEVICE_MODULE_FLAG_FP64
189189

190-
# TODO: emit printf format strings in constant memory
191-
extensions = String[
192-
"SPV_EXT_relaxed_printf_string_address_space",
193-
"SPV_EXT_shader_atomic_float_add"
194-
]
190+
# SPIR-V codegen path. The Aurora LTS NEO/IGC runtime only accepts SPIR-V from the
191+
# Khronos translator and needs these extensions declared explicitly; the rolling stack
192+
# uses the LLVM SPIR-V back-end (which handles the extensions itself). GPUCompiler picks
193+
# the tool from the target's `backend` field and loads the JLL lazily, so both can be
194+
# listed as deps and the choice is made here at compile time.
195+
if oneL0.LTS[]
196+
backend = :khronos
197+
# TODO: emit printf format strings in constant memory
198+
extensions = String[
199+
"SPV_EXT_relaxed_printf_string_address_space",
200+
"SPV_EXT_shader_atomic_float_add"
201+
]
202+
else
203+
backend = :llvm
204+
extensions = String[]
205+
end
195206

196207
# create GPUCompiler objects
197-
target = SPIRVCompilerTarget(; extensions, supports_fp16, supports_fp64, kwargs...)
208+
target = SPIRVCompilerTarget(; backend, extensions, supports_fp16, supports_fp64, kwargs...)
198209
params = oneAPICompilerParams()
199210
CompilerConfig(target, params; kernel, name, always_inline)
200211
end

src/context.jl

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -225,17 +225,19 @@ function global_queue(ctx::ZeContext, dev::ZeDevice)
225225
# objects should track ownership, and not rely on implicit global state.
226226
get!(task_local_storage(), (:ZeCommandQueue, ctx, dev)) do
227227
queue = ZeCommandQueue(ctx, dev; flags = oneL0.ZE_COMMAND_QUEUE_FLAG_IN_ORDER)
228-
# disable finalizers while mutating the registry: a GC-driven finalizer on this
229-
# task could call back into `synchronize_all_queues` (the lock is reentrant) and
230-
# observe/mutate the registry mid-update.
231-
GC.enable_finalizers(false)
232-
try
233-
@lock queue_registry_lock begin
234-
push!(get!(Vector{Tuple{WeakRef,ZeCommandQueue}}, queue_registry, (ctx, dev)),
235-
(WeakRef(current_task()), queue))
228+
if oneL0.LTS[]
229+
# disable finalizers while mutating the registry: a GC-driven finalizer on this
230+
# task could call back into `synchronize_all_queues` (the lock is reentrant) and
231+
# observe/mutate the registry mid-update.
232+
GC.enable_finalizers(false)
233+
try
234+
@lock queue_registry_lock begin
235+
push!(get!(Vector{Tuple{WeakRef,ZeCommandQueue}}, queue_registry, (ctx, dev)),
236+
(WeakRef(current_task()), queue))
237+
end
238+
finally
239+
GC.enable_finalizers(true)
236240
end
237-
finally
238-
GC.enable_finalizers(true)
239241
end
240242
queue
241243
end
@@ -259,6 +261,9 @@ const queue_registry = Dict{Tuple{ZeContext,ZeDevice},Vector{Tuple{WeakRef,ZeCom
259261
# i.e., all queues whose in-flight work could possibly reference an allocation that is
260262
# about to be freed.
261263
function synchronize_all_queues(ctx::ZeContext, dev::Union{ZeDevice,Nothing})
264+
# only the LTS stack populates the queue registry (see `global_queue`); on the
265+
# rolling stack this is a no-op and `release` frees directly.
266+
oneL0.LTS[] || return
262267
queues = ZeCommandQueue[]
263268
stale = Tuple{WeakRef,ZeCommandQueue}[]
264269
GC.enable_finalizers(false)

src/mapreduce.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ function GPUArrays.mapreducedim!(f::F, op::OP, R::oneWrappedArray{T},
158158

159159
# Aurora LTS workaround (see `_dense_reduce_input` above): materialize strided inputs to a
160160
# dense array first so every global read in the reduction kernel is coalesced.
161-
if !_dense_reduce_input(A)
161+
if oneL0.LTS[] && !_dense_reduce_input(A)
162162
Acontig = Broadcast.materialize(Broadcast.broadcasted(f, A))
163163
return GPUArrays.mapreducedim!(identity, op, R, Acontig; init=init)
164164
end
@@ -189,7 +189,7 @@ function GPUArrays.mapreducedim!(f::F, op::OP, R::oneWrappedArray{T},
189189
# the contiguous leading dimension is NOT reduced (`size(Rreduce, 1) == 1`), use the
190190
# coalesced one-work-item-per-slice kernel, whose lanes read consecutive memory. Few-slice
191191
# reductions get less parallelism but stay correct; the common many-slice case is also fast.
192-
if size(Rreduce, 1) == 1
192+
if oneL0.LTS[] && size(Rreduce, 1) == 1
193193
items = clamp(length(Rother), 1, 256)
194194
groups = min(cld(length(Rother), items), 1024)
195195
@oneapi items=items groups=groups coalesced_mapreduce_device(

src/pool.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,11 @@ function release(buf::oneL0.AbstractBuffer)
8989
# That turns a GC-driven free of a dead array whose last kernel/copy hasn't retired
9090
# into a GPU pagefault, which gets the kernel context banned and makes every later
9191
# submission fail with ZE_RESULT_ERROR_UNKNOWN. Synchronize the queues that could
92-
# reference this buffer before freeing.
93-
synchronize_all_queues(oneL0.context(buf), oneL0.device(buf))
92+
# reference this buffer before freeing. (No-op on the rolling stack, which honors
93+
# BLOCKING_FREE.)
94+
if oneL0.LTS[]
95+
synchronize_all_queues(oneL0.context(buf), oneL0.device(buf))
96+
end
9497

9598
free(buf; policy=oneL0.ZE_DRIVER_MEMORY_FREE_POLICY_EXT_FLAG_BLOCKING_FREE)
9699

0 commit comments

Comments
 (0)