Skip to content

Add floating-point atomic add/sub and min/max intrinsics#453

Open
michel2323 wants to merge 6 commits into
JuliaGPU:mainfrom
michel2323:atomic-float-ops
Open

Add floating-point atomic add/sub and min/max intrinsics#453
michel2323 wants to merge 6 commits into
JuliaGPU:mainfrom
michel2323:atomic-float-ops

Conversation

@michel2323

@michel2323 michel2323 commented Jul 1, 2026

Copy link
Copy Markdown
Member

Float32 atomic_add!/atomic_sub! emit OpenCL-style builtins that lower to the SPV_EXT_shader_atomic_float_add EXT instructions. Float64 atomic add, and float min/max for both types, use cmpxchg fallbacks since the corresponding native extensions aren't available in the oneAPI set.

Float32 atomic_add!/atomic_sub! emit OpenCL-style builtins that lower to
the SPV_EXT_shader_atomic_float_add EXT instructions. Float64 atomic add,
and float min/max for both types, use cmpxchg fallbacks since the
corresponding native extensions aren't available in the LTS set.
@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.45455% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 80.93%. Comparing base (45a91e3) to head (9a955fd).

Files with missing lines Patch % Lines
src/compiler/capabilities.jl 90.90% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #453      +/-   ##
==========================================
+ Coverage   80.62%   80.93%   +0.31%     
==========================================
  Files          13       13              
  Lines         898      918      +20     
==========================================
+ Hits          724      743      +19     
- Misses        174      175       +1     

☔ 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

The errors come from #452

@simeonschaub

Copy link
Copy Markdown
Member

Could we use an approach like in #447 to query whether the device supports SPV_EXT_shader_atomic_float_add and use the fallback cmpxchg loop only when necessary?

The compare-and-swap loops compared floats with `==`: a stored NaN
never compares equal, so the loop spins forever, and a failed exchange
is mistaken for a successful one when -0.0 compares equal to 0.0.
Compare the raw bits instead.
The OpenCL-style atomic_add/atomic_sub builtins with float arguments
are only lowered to the EXT atomic-float instructions by the LLVM
SPIR-V backend; the Khronos translator keeps them as external calls,
which fail to link. Emit a __spirv_AtomicFAddEXT wrapper builtin
instead, like the integer atomics (SPIR-V has no float subtraction,
so atomic_sub! adds the negated value).
Native floating-point atomics are an optional device capability
(cl_ext_float_atomics), so select between the EXT instructions and a
compare-and-swap fallback at compile time, based on what the target
device reports:

- register fp32/fp64 atomic add and min/max features, detected through
  the cl_ext_float_atomics capability bitfields (requiring both the
  global- and local-memory bits, since the intrinsics cover both
  address spaces)
- in SPIRVIntrinsics, provide native (`atomic_add_native!`, ...) and
  CAS-loop (`atomic_add_fallback!`, ...) versions of the float atomics,
  defaulting the public functions to the fallback; OpenCL.jl overrides
  them to pick the native instruction where supported via `has_feature`,
  which folds at compile time
- allow the SPV_EXT_shader_atomic_float_add/_min_max SPIR-V extensions
  by default on devices that support them, and mask the min/max
  features on the OpenCL C program backend, where spirv2clc does not
  implement the corresponding capabilities

This makes Float64 add and float min/max use native atomics on devices
that support them (previously hardwired to the CAS loop), and gives
Float32 add/sub a fallback on devices that don't (previously they
required native support unconditionally).
`@atomic` on float arrays went through the generic value-comparing CAS
loop; route +, -, min and max through the atomic intrinsics instead, so
they use native instructions where the device supports them. Extend the
atomics testsuite to cover float min/max.
The package gained new atomic intrinsics that OpenCL.jl relies on.
@michel2323

Copy link
Copy Markdown
Member Author

I registered fp32/fp64 atomic_add and atomic_min_max features, detected through the cl_ext_float_atomics capability bitfields (requiring both the global- and local-memory bits, since the intrinsics cover both address spaces). SPIRVIntrinsics now carries a _native! and a _fallback! version of each op, with the public functions defaulting to the compare-and-swap loop and OpenCL.jl overriding them to pick the native instruction via has_feature, which folds at compile time. The compiler config also enables the corresponding SPIR-V extensions automatically on devices that report support, so no explicit extensions kwarg is needed anymore.

Making the selection device-driven shook out a few issues, split into separate commits:

  • The OpenCL-style builtin names turned out to lower only on the LLVM SPIR-V backend for atomic_add, and on neither backend for min/max — hard link failures on pocl, which advertises cl_ext_float_atomics. The native atomics are now emitted as __spirv_AtomicF{Add,Min,Max}EXT wrapper builtins like the integer atomics, which both backends handle. Net effect: Float64 add, and float min/max also get native instructions where supported, and Float32 add/sub gains a fallback where not.
  • spirv2clc doesn't implement the float min/max capabilities, so those features are masked on the OpenCL C source backend, and kernels take the CAS fallback there.
  • The CAS fallback loops now compare raw bits instead of float values, because == spins forever on a stored NaN and mistakes a failed exchange for success when -0.0 == 0.0.
  • @atomic float +/-/min/max now route through these intrinsics instead of the generic value-comparing CAS loop.

@simeonschaub

Copy link
Copy Markdown
Member

SPV_EXT_shader_atomic_float_min_max at least also has support for fp16, and for atomic add there is SPV_EXT_shader_atomic_float16_add. Should we add those as well while we're at it?

Overall, this looks great to me! Pinging @vchuravy regarding what the story for KA.PoCLBackend should look like here, since we probably want to use the native versions wherever possible as well there, ideally without duplicating all the logic in OpenCL.jl

Comment on lines +125 to +126
# the loops compare raw bits, not values: `==` on floats would spin forever on a stored NaN,
# and would treat a failed exchange as successful when -0.0 compares equal to 0.0.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not just use === for the comparison instead of ==?

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.

2 participants