Skip to content

Make floating-point atomics work on the LLVM back-end path - #595

Merged
michel2323 merged 4 commits into
mainfrom
fix-atomics-main
Aug 1, 2026
Merged

Make floating-point atomics work on the LLVM back-end path#595
michel2323 merged 4 commits into
mainfrom
fix-atomics-main

Conversation

@michel2323

Copy link
Copy Markdown
Member

On discrete GPUs (tested on an Arc A750, NEO 26.18), oneAPI.atomic_add!/atomic_sub! on Float32 fail with 4 errors in device/intrinsics: the atomic intrinsics fall back to an unsupported runtime call (unsupported call to an unknown function (call to gpu_malloc)). Two changes fix this:

  • pin SPIRVIntrinsics to the atomic-float-ops revision, whose atomic intrinsics lower properly (pinned by commit SHA so the resolution is reproducible);
  • declare SPV_EXT_shader_atomic_float_add (plus the relaxed-printf extension) for the LLVM SPIR-V back-end — extensions must be declared to permit the corresponding instructions during translation, and without the declaration the atomic float instructions fail with LLVM ERROR: The atomic float instruction requires the following SPIR-V extension: SPV_EXT_shader_atomic_float_add.

Also relaxes the onemkl version test to accept oneMKL 2025.2+, so the suite works against a locally built support library (and the Aurora LTS toolchain, where this originates).

With this, device/intrinsics passes 116/116 and onemkl 1048 + 48 broken on the A750. Extracted from the aurora-lts branch so it can land on main independently.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Your PR no longer requires formatting changes. Thank you for your contribution!

@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.15%. Comparing base (61eaa51) to head (c487a8e).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #595   +/-   ##
=======================================
  Coverage   79.15%   79.15%           
=======================================
  Files          50       50           
  Lines        3391     3391           
=======================================
  Hits         2684     2684           
  Misses        707      707           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@michel2323

Copy link
Copy Markdown
Member Author

This needs to be merged first: JuliaGPU/OpenCL.jl#453

@michel2323
michel2323 force-pushed the fix-atomics-main branch 4 times, most recently from b19a837 to 2e57b7b Compare July 31, 2026 19:48
Two pieces, without which oneAPI.atomic_add!/atomic_sub! on Float32
fail on discrete GPUs (4 errors in device/intrinsics on an Arc A750):

- use the SPIRVIntrinsics atomic-float-ops revision, whose atomic
  intrinsics lower properly instead of falling back to an unsupported
  runtime call ("unsupported call to an unknown function gpu_malloc");

- declare SPV_EXT_shader_atomic_float_add (and the relaxed-printf
  extension) for the LLVM SPIR-V back-end: extensions must be declared
  to permit the corresponding instructions during translation, and
  without them the atomic float instructions fail to translate.
@michel2323
michel2323 enabled auto-merge (squash) August 1, 2026 20:36
@michel2323
michel2323 merged commit 54637d7 into main Aug 1, 2026
5 checks passed
@michel2323
michel2323 deleted the fix-atomics-main branch August 1, 2026 22:49
michel2323 referenced this pull request Aug 3, 2026
Aurora and other large deployments run Intel's long-term-servicing branch of
the Compute Runtime rather than the rolling release oneAPI.jl targets. That
branch predates a number of driver and IGC fixes, several of which corrupt
results silently rather than raise an error. Add the workarounds behind a
single opt-in switch, oneL0.LTS[], resolved from ONEAPI_LTS at load time and
defaulting to off, so the rolling stack behaves exactly as before.

Gated on that switch:

* SPIR-V codegen goes through the Khronos translator instead of LLVM's
  SPIR-V back-end, whose output the LTS NEO/IGC runtime does not accept
  (#491). Both JLLs stay dependencies; GPUCompiler resolves the tool from
  the target's `backend` field, so the choice is made at compile time.

* BFloat16 is forced off. The LTS SPIR-V stack cannot codegen native bfloat
  in generic kernels -- a kernel that merely keeps a bfloat value fails with
  InvalidIRError, and declaring SPV_KHR_bfloat16 crashes the runtime.
  _device_supports_bfloat16() reports hardware capability and does not
  capture this, so the test suite and examples/bfloat16.jl gate on the
  switch instead.

* Reductions avoid the strided-read miscompile that silently breaks
  `sum(transpose(x))`, `a == transpose(b)` and `ishermitian`. Non-dense
  inputs are materialized before reducing, and reductions that keep the
  contiguous leading dimension use a coalesced one-work-item-per-slice
  kernel. Reductions that also reduce dim 1 keep a contiguous innermost
  axis and are left alone; an Int32 regression test covers that boundary.

* Buffers are freed only after draining the queues that may still reference
  them. LTS NEO advertises ZE_extension_memory_free_policies but ignores
  BLOCKING_FREE, so a GC-driven free of in-flight work faults and bans the
  context, surfacing later as a ZE_RESULT_ERROR_UNKNOWN at an unrelated
  call. A per-(context, device) registry tracks every queue -- including the
  replacement KA.priority! installs -- and the queue finalizer drains before
  destroying, with a bounded wait so a task that dies mid-submission cannot
  hang finalization.

Independently of that switch, ONEAPI_SYNC_EACH_SUBMISSION=1 synchronizes
after every command-list submission, working around a dropped-tail
corruption seen when several processes oversubscribe a single tile. It costs
roughly 3x throughput and is off by default, with getter, setter and scoped
forms available at runtime.

Also, not specific to the LTS stack: dlopen the NEO driver by full path
during __init__ so libsycl's bundled Level Zero loader finds it in-process.
Setting LD_LIBRARY_PATH there never could -- glibc captures it once at
process startup -- so it only ever served spawned worker processes.

The self-hosted Aurora runner is configured to exercise the LTS path, with
buildkite continuing to cover the rolling stack, and docs/src/lts.md
documents both switches, what they change, and what they cost.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant