docs / specification / xpu

Float atomics: lock-free parallel reductions

A reduction — sum, max, min over data many threads produce — needs every thread to fold its value into one shared accumulator without losing updates to a race. The atomic read-modify-write is that primitive: KernelBuffer<float32> exposes it directly.

out.atomicAdd(i, v);   // *(out+i) += v   atomically; returns the old value
out.atomicMax(i, v);   // *(out+i) = max(*(out+i), v)
out.atomicMin(i, v);   // *(out+i) = min(*(out+i), v)

Each is one hardware instruction — the whole point is that a thousand threads can hit the same address at once and the result is still correct.

The canonical use: fold N threads into one slot

@Kernel
public static void reduce(KernelBuffer<float32> out, KernelBuffer<float32> in, uint32 n) {
    uint32 i = KernelThread.globalIdX();
    if (i < n) {
        out.atomicAdd(0, in[i]);   // parallel sum   -> out[0]
        out.atomicMax(1, in[i]);   // parallel max   -> out[1]
        out.atomicMin(2, in[i]);   // parallel min   -> out[2]
    }
}

Every thread races to out[0..2]; the atomics serialize just those updates, so out[0] ends as the exact sum regardless of thread order. This is the histogram / scatter-add / loss-accumulation pattern — common in scientific reductions and ML (gradient accumulation, soft-assignment counts).

How it lowers — same source, every backend

BackendLowering
Vulkan (RADV, etc.)OpAtomicFAddEXT / OpAtomicFMinEXT / OpAtomicFMaxEXT under SPV_EXT_shader_atomic_float_add / _min_max, with Device scope + AcquireRelease memory order.
AMD / NVIDIAthe native global FP atomic (global_atomic_add_f32, red.add.f32), relaxed system scope.
CPUatomicrmw — a lock/cmpxchg loop where the hardware lacks a native FP atomic.

The Vulkan memory model is stricter than the others: it rejects CrossDevice scope and SequentiallyConsistent (and relaxed-with-storage-class) semantics on these ops, so the Vulkan path emits Device-scoped AcquireRelease atomics. Cajeta handles that in the lowering seam — you just write atomicAdd.

Numerical note

Floating-point addition is not associative, so a concurrent atomicAdd sum can differ in the last bits run-to-run when the addends are arbitrary floats. It is exact and order-independent only when every partial sum stays integer-valued and below 2^24 (the f32 integer-exact range) — which is why the Tour’s reduction (inputs 0..255) is bit-exact. For large or fractional data, treat the sum as correct to floating-point rounding, not bit-reproducible. atomicMax/atomicMin are always order-independent.

Why not the alternatives?

AlternativeWhat it forces
A plain out[0] = out[0] + in[i] from every threadA data race — concurrent read-modify-writes lose updates; the sum comes out low and non-deterministic.
A tree reduction in shared memory + one writerMore code and a barrier; the right call for a dense reduction, but overkill when threads scatter into many bins (a histogram) — that is exactly what atomics are for.
Per-thread partial sums + a second passAn extra buffer and kernel launch; atomics collapse it to one pass.

Rules. Float atomics are methods on KernelBuffer<float32> (f32, v1): atomicAdd, atomicMin, atomicMax of (index, value), returning the old value. They are device-only (inside an @Kernel) and require a writable buffer. On Vulkan they need SPV_EXT_shader_atomic_float_add / _min_max (RADV exposes both for f32). Runnable end to end in samples/tour/gpu (the float atomics section). See CajetaXPU.md for the kernel surface. Integer atomics (atomicAdd/Sub/Min/Max/And/Or/Xor/Exchange/CompareExchange) and shared-memory (LDS) atomics have since shipped — see IntegerAtomics.md; f16/f64 float atomics remain follow-ons (cajeta-xpu-plan.md Stage 9).

Memory order. An optional compile-time MemoryOrder trailing arg applies to float atomics too (out.atomicAdd(0, v, MemoryOrder.Relaxed)) — see IntegerAtomics.md. Because Vulkan’s OpAtomicF*EXT rejects relaxed-with-storage-class semantics (above), Vulkan clamps Relaxed/SeqCst → AcqRel for float atomics; CPU/AMD/NVPTX honour the requested order.

Source: docs/specification/xpu/FloatAtomics.md · 2 min read · 534 words