Add opt-in support for Intel's LTS driver stack (ONEAPI_LTS) - #574
Merged
Conversation
Contributor
|
Your PR requires formatting changes to meet the project's style guidelines. Click here to view the suggested changes.diff --git a/test/level-zero.jl b/test/level-zero.jl
index bbf8f46..101b7c6 100644
--- a/test/level-zero.jl
+++ b/test/level-zero.jl
@@ -210,21 +210,21 @@ pool = ZeEventPool(ctx, 2)
signal_event = pool[1]
wait_event = pool[2]
-# This is a submit-then-signal pattern: the kernel is gated on `wait_event`, which is
-# only signaled *after* submission. The ONEAPI_SYNC_EACH_SUBMISSION=1 workaround (Aurora
-# LTS) makes `execute!` block in zeCommandQueueSynchronize right after submitting, which
-# would deadlock here since the kernel cannot retire before `wait_event` is signaled. No
-# production code path submits event-gated work, so disable the workaround just here.
-oneL0.sync_each_submission(false) do
- execute!(queue) do list
- append_launch!(list, kernel, 1, signal_event, wait_event)
- end
- @test !Base.isdone(signal_event)
-
- signal(wait_event)
- synchronize(queue)
- @test Base.isdone(signal_event)
-end
+ # This is a submit-then-signal pattern: the kernel is gated on `wait_event`, which is
+ # only signaled *after* submission. The ONEAPI_SYNC_EACH_SUBMISSION=1 workaround (Aurora
+ # LTS) makes `execute!` block in zeCommandQueueSynchronize right after submitting, which
+ # would deadlock here since the kernel cannot retire before `wait_event` is signaled. No
+ # production code path submits event-gated work, so disable the workaround just here.
+ oneL0.sync_each_submission(false) do
+ execute!(queue) do list
+ append_launch!(list, kernel, 1, signal_event, wait_event)
+ end
+ @test !Base.isdone(signal_event)
+
+ signal(wait_event)
+ synchronize(queue)
+ @test Base.isdone(signal_event)
+ end
end
|
michel2323
force-pushed
the
aurora-lts-2523.40
branch
from
June 12, 2026 14:28
eec2785 to
991d29e
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #574 +/- ##
==========================================
+ Coverage 79.76% 80.24% +0.47%
==========================================
Files 50 50
Lines 3391 3487 +96
==========================================
+ Hits 2705 2798 +93
- Misses 686 689 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
michel2323
force-pushed
the
aurora-lts-2523.40
branch
from
June 24, 2026 13:26
32d11d7 to
b13cc3e
Compare
michel2323
force-pushed
the
aurora-lts-2523.40
branch
2 times, most recently
from
July 7, 2026 13:41
434bd6c to
f158ea6
Compare
michel2323
force-pushed
the
aurora-lts-2523.40
branch
2 times, most recently
from
July 22, 2026 18:13
d7f0d35 to
ddaa418
Compare
michel2323
force-pushed
the
aurora-lts-2523.40
branch
7 times, most recently
from
August 1, 2026 02:12
7973eca to
f9435c1
Compare
ONEAPI_LTS)
michel2323
force-pushed
the
aurora-lts-2523.40
branch
from
August 2, 2026 01:45
f9435c1 to
085f5e7
Compare
Aurora ships the Intel "LTS" GPU software stack rather than the rolling release upstream targets, and needs a set of driver/IGC workarounds that must not affect anyone else. Introduce oneL0.LTS[] as the single switch those workarounds gate on, so with it off the package behaves exactly like upstream. Default OFF, matching upstream: an LTS deployment opts in with ONEAPI_LTS=1. Resolved at the top of oneL0.__init__, before the driver-availability early returns, since it gates codegen and must be set even on a host without a functional GPU. Route it through a parse_env_bool helper that accepts on/off/yes/no/1/0/ true/false and warns on an unrecognized value, so a typo does not silently select the wrong stack.
michel2323
force-pushed
the
aurora-lts-2523.40
branch
from
August 3, 2026 13:54
085f5e7 to
3db74fe
Compare
The LTS NEO/IGC runtime does not accept the output of the LLVM SPIR-V back-end (#491) and needs SPIR-V extensions declared explicitly, which the back-end handles itself. Select the codegen path from oneL0.LTS[]: :khronos (translator) on the LTS stack, :llvm on the rolling stack. GPUCompiler resolves the tool from the target's `backend` field through a LazyModule that looks the JLL up in Base.loaded_modules, so both JLLs are listed as deps and loaded, and the choice is made at compile time. Keep SPIRV_LLVM_Backend_jll: with it neither a dep nor loaded, the first @oneapi launch on the rolling stack fails in mcgen and no kernel compiles at all. Preserve the rolling-stack behavior exactly: supports_bfloat16 stays _device_supports_bfloat16(), SPV_KHR_bfloat16 is declared when the runtime advertises it, and finish_ir! otherwise lowers bfloat->i16. Force bf16 off on the LTS branch, whose SPIR-V stack cannot codegen native bfloat in generic kernels (clamp! fails with InvalidIRError, and declaring SPV_KHR_bfloat16 crashes the LTS runtime). The precompile workload mirrors the same choice, resolving ONEAPI_LTS directly from the environment since oneL0.__init__ has not run yet.
The LTS IGC silently miscompiles strided (non-coalesced) global reads
inside the reduction kernels, producing wrong results with no error --
e.g. `a == transpose(b)`, `sum(transpose(x))`, `ishermitian`. Add two
complementary guards, both gated on oneL0.LTS[]:
- A coalesced one-work-item-per-slice kernel for when the contiguous
leading dimension is not reduced (size(Rreduce, 1) == 1, e.g.
sum(A; dims=2)), whose lanes read consecutive memory. Its group size
comes from launch_configuration(kernel) rather than a hardcoded 256,
which can exceed the kernel's max work-group size and fail the launch.
- Materialize strided / non-dense inputs (Transpose, Adjoint,
PermutedDimsArray, SubArray, ...) to a dense array before reducing,
via _dense_reduce_input. Only recurse when materialization actually
produced a dense oneArray: a plain host AbstractArray materializes to
another host array that still fails the predicate, which would recurse
forever (StackOverflowError) instead of raising the usual
kernel-conversion error.
Reductions that also reduce dim 1 (e.g. dims=(1,3)) keep a contiguous
innermost axis and need no workaround; the miscompile bites only when
adjacent lanes land a full stride apart on every lane. Covered by an Int32
regression test -- exact and associative, so it is immune to the Float32
accumulation-order rounding that makes large GPU/CPU sums differ for
unrelated reasons.
On the LTS NEO stack (25.18) freeing a buffer does not drain queues that
still have work in flight referencing it: the in-flight kernel then faults
and the context is banned, surfacing later as a ZE_RESULT_ERROR_UNKNOWN at
an unrelated op. global_queue is task-local, so a test file's task can also
die with work still queued.
- Register every queue in a per-(context,device) registry that holds it
strongly, keyed by a weak reference to the owning task. A WeakRef to
the queue would be cleared in the same GC cycle that queues its
finalizer, hiding it from release exactly when its in-flight work
still references buffers being freed.
- Register from both global_queue and KA.priority!, via a shared
register_queue! helper. priority! replaces the task-local queue with a
directly constructed one; unregistered, all work after
priority!(backend, :high) ran on a queue release() never synchronizes
-- the exact use-after-free the registry exists to prevent.
- Before any BLOCKING_FREE, synchronize all queues that could reference
the buffer. Synchronize outside the registry lock with finalizers
disabled, skip already-finalized queues, and retire queues of dead
tasks.
- In the queue finalizer, drain then destroy, and null the handle so a
concurrent synchronize_all_queues skips it. Bound the drain rather
than waiting forever, so a task that dies mid-submission cannot hang
the finalizer indefinitely.
All gated on oneL0.LTS[]; the rolling stack keeps upstream behavior.
The Aurora LTS NEO stack intermittently drops the tail of a command list, silently corrupting results. Add an off-by-default workaround (ONEAPI_SYNC_EACH_SUBMISSION=1) that synchronizes the queue after every command-list submission, exposed as a sync_each_submission() getter, a sync_each_submission!(enable) setter, and a scoped sync_each_submission(f, enable) do-block form. Use the scoped form to disable it around the submit-then-signal block in test/level-zero.jl: that block launches a kernel gated on a wait_event only signaled after submission, so synchronizing on submission deadlocks waiting for a kernel that cannot retire yet. No production path submits event-gated work, so the workaround stays safe everywhere it matters. The flag shares parse_env_bool with ONEAPI_LTS, so a deployment writing ONEAPI_SYNC_EACH_SUBMISSION=on gets the workaround rather than silently running without it.
Setting ENV["LD_LIBRARY_PATH"] inside __init__ cannot affect the running
process's dlopen search paths: glibc captures LD_LIBRARY_PATH once at
process startup, so the stated purpose -- letting libsycl's bundled ze_lib
find NEO's libze_intel_gpu in-process -- was never served for the current
process, only for child processes that inherit the environment.
dlopen the NEO driver by full path in __init__ so it is resident before
libsycl loads; a later dlopen("libze_intel_gpu.so.1") by libsycl's bundled
loader then resolves to it by soname without a path search. Keep extending
LD_LIBRARY_PATH, but correct the comment to say it only covers spawned
worker processes. Required when no system NEO is installed.
The LTS SPIR-V stack (Khronos translator + NEO/IGC) cannot codegen native
bfloat in generic kernels: any kernel that keeps a bfloat value fails with
InvalidIRError, and declaring SPV_KHR_bfloat16 crashes the LTS runtime.
_device_supports_bfloat16() is a hardware check and does not capture that.
- Gate the bf16 eltypes push in the testsuite on !oneL0.LTS[], so
BFloat16 is exercised as a generic element type only off the LTS
stack. Storage, conversions and oneMKL bf16 are unaffected.
- Skip examples/bfloat16.jl on the LTS stack, alongside the existing
old-Julia and unsupported-device guards. The example uses BFloat16 as
storage only, but even a bfloat load/store forces the bfloat LLVM type
into the module, which the LTS translator cannot emit -- so it fails
with "Failed to translate LLVM code to SPIR-V" regardless of device
support.
michel2323
force-pushed
the
aurora-lts-2523.40
branch
from
August 3, 2026 14:05
3db74fe to
f179e28
Compare
Set up the GitHub Actions job for the Aurora LTS self-hosted runner:
- Opt into the LTS code paths (ONEAPI_LTS=1), per-worker GPU spreading
(ONEAPI_TEST_SPREAD_GPUS=1), and the per-submission synchronize
workaround (ONEAPI_SYNC_EACH_SUBMISSION=1).
- Build liboneapi_support.so from deps/src before julia-buildpkg, since
the registered oneAPI_Support_jll artifact lags behind the wrappers on
this branch, and verify oneAPI_Support_jll actually resolves to it --
a failed build would otherwise silently fall back to the JLL and
report green.
- Run the tests through `julia -C native,-avx512fp16`. Under concurrent
oneMKL load the Sapphire Rapids native AVX512-FP16 path silently
miscomputes *host* Float16 (e.g. the GPUArrays broadcast reference),
failing tests even though the GPU result is correct. julia-runtest
cannot pass a cpu-target, so invoke Pkg.test() directly.
Add a dedicated page covering the ONEAPI_LTS switch: the Khronos SPIR-V translator replacing the LLVM SPIR-V back-end, the BFloat16 limitation, the strided-reduction and free-synchronization workarounds, and the separate ONEAPI_SYNC_EACH_SUBMISSION option. Link it from the README, index, installation and troubleshooting pages. Also fix a comment pointing at ISSUE_dropped_tail.md, which is not in the repository, to reference the new page instead.
The PR checks box showed "self-runner (ubuntu-latest, 1, x64)", which says nothing about which stack ran and misreports the runner. Name it explicitly so it reads as the LTS counterpart to buildkite's rolling-stack build.
michel2323
force-pushed
the
aurora-lts-2523.40
branch
from
August 3, 2026 15:32
4f70da5 to
b8e72c4
Compare
The self-hosted job ran no coverage instrumentation and uploaded nothing, so Codecov saw only buildkite's rolling-stack run. Every LTS-gated branch is unreachable there, which is why this branch reports 38% patch coverage against 65 missing lines that are almost entirely `if oneL0.LTS[]` bodies. Codecov merges uploads per commit, so reporting from both stacks covers both sides of those branches.
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.
Intel's LTS driver releases, as deployed on Aurora (NEO 25.18 /
2523.40) and other data center deployments, differ enough from the rolling stack that oneAPI.jl currently fails to compile kernels, silently corrupts reductions, and gets contexts banned on them. This PR adds the workarounds needed to run there, each gated behind a master switch.The switch
ONEAPI_LTS(oneL0.LTS[]), resolved from the environment at the top ofoneL0.__init__, defaults to off. With it off every code path below is skipped and the package behaves exactly as onmain, so this PR is a no-op for anyone on the rolling stack. An LTS deployment opts in withONEAPI_LTS=1.A second, independent switch,
ONEAPI_SYNC_EACH_SUBMISSION(also default off), covers one workaround with a large throughput cost; see below.Both are parsed by a shared
parse_env_boolaccepting1/true/yes/onand0/false/no/off, which warns rather than silently falling back to the default on an unrecognized value.What is gated on
ONEAPI_LTSsrc/compiler/compilation.jl)backend = :khronosand declareSPV_EXT_relaxed_printf_string_address_space/SPV_EXT_shader_atomic_float_add; the rolling stack keeps:llvmbfloatin generic kernels (clamp!→InvalidIRError), and declaringSPV_KHR_bfloat16segfaults NEOsupports_bfloat16 = false; the test suite andexamples/bfloat16.jlskip to matchsrc/mapreduce.jl)sum(A; dims=2),a == transpose(b),ishermitiansrc/pool.jl,src/context.jl)ZE_extension_memory_free_policiesbut ignoresBLOCKING_FREE, so a GC-driven free of a buffer with work in flight pagefaults and bans the contextsynchronize_all_queuesbeforefreelib/level-zero/cmdqueue.jl)zeCommandQueueDestroydoes not wait for in-flight work; the same fault-and-ban followsONEAPI_SYNC_EACH_SUBMISSIONUnder heavy multi-process oversubscription of a single tile, a whole-queue
zeCommandQueueSynchronizedoes not reliably retire the tail of an earlier, separately submitted command list: the last work-items of a kernel or the last elements of a copy silently go missing. Synchronizing after every submission eliminates it at roughly 3× throughput cost, so it is a separate opt-in rather than folded intoONEAPI_LTS.oneL0.sync_each_submission(f, enable)scopes it temporarily, which is needed for submit-then-signal patterns that would otherwise deadlock.Not gated (applies to both stacks)
src/oneAPI.jl:dlopenthe NEO driver by full path at init, so that libsycl's bundled Level Zero loader resolves it by soname later. Required when no system NEO is installed. The adjacentLD_LIBRARY_PATHextension only ever affected child processes.src/compiler/precompile.jl: the precompile workload mirrors the runtime backend choice, so precompilation warms the pipeline that will actually be used.SPIRV_LLVM_Translator_jllbecomes a dependency — GPUCompiler resolves the tool fromBase.loaded_modules, so both back-ends must be loaded for either path to work, and is reported byversioninfo.CI
The self-hosted Aurora runner sets
ONEAPI_LTS=1andONEAPI_SYNC_EACH_SUBMISSION=1, buildsliboneapi_support.sofromdeps/src(and verifies the preference actually took effect, rather than silently testing the registered JLL), and disables AVX512-FP16 host codegen via-C native,-avx512fp16: under concurrent oneMKL load the native FP16 path miscomputes host Float16 reference values on these Sapphire Rapids nodes, failing tests whose GPU result is correct.The buildkite runner is unchanged and gets the defaults, so it keeps exercising the non-LTS
:llvmback-end path.Tests
test/array.jlgains a "strided mixed reductions" regression test using exactInt32sums (immune to Float32 accumulation-order rounding) across several shapes anddimscombinations, covering both the reductions routed to the coalesced kernel and the mixed ones that deliberately stay on the workgroup-per-slice kernel.