From 6d9e3a4c6cd2abc53da1161fcf3dc645cefee68e Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Thu, 5 Nov 2026 20:24:57 +0100 Subject: [PATCH] Test BFloat16 arithmetic against exactly rounded references The arithmetic tests rounded their references with the host's `BFloat16` conversion inside a vectorizable `map`. LLVM 19 to 22 (Julia 1.13) miscompiles that for AArch64 targets without BF16: an `<8 x bfloat>` operation takes its round-to-even bit from an operand rather than the result, so some ties round the wrong way. Round them with `bf16_nearest` instead, as the conversion tests already do. `bf16_nearest` kept 8 significant bits, which is too many for bfloat16 subnormals, so subnormal quotients were rounded twice. Round those to multiples of 2^-133. PoCL's LLVM 22 has the same bug on the device side, so the tests still fail on such targets with the current pocl_standalone_jll (#949). --- test/bfloat16.jl | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/test/bfloat16.jl b/test/bfloat16.jl index b8acef640..946bca9be 100644 --- a/test/bfloat16.jl +++ b/test/bfloat16.jl @@ -35,9 +35,16 @@ end @inbounds y[i] = convert(eltype(y), x[i]) end -# the correctly rounded value, computed without the host's `BFloat16` conversions (on Julia -# 1.11, vectorized ones crash LLVM on AVX512-BF16 hosts) -bf16_nearest(x) = BFloat16(Float32(BigFloat(x; precision = 8))) +# the correctly rounded value, without relying on the host's `BFloat16` rounding: on Julia +# 1.11, vectorized ones crash LLVM on AVX512-BF16 hosts, and LLVM 19 to 22 (Julia 1.13) rounds +# ties wrongly in vectorized ones for AArch64 targets without BF16 (#949) +function bf16_nearest(x) + b = BigFloat(x; precision = 256) + # 8 significant bits, except for subnormals, which are multiples of 2^-133 + r = abs(b) < floatmin(Float32) ? round(b * big(2.0)^133) / big(2.0)^133 : + BigFloat(b; precision = 8) + return BFloat16(Float32(r)) +end const bf16_supported = !isdefined(Core, :BFloat16) || "SPV_KHR_bfloat16" in POCL.device().spirv_extensions @@ -71,7 +78,7 @@ if bf16_supported a = BFloat16(1.5) bf16_axpy!(backend)(out, a, x; ndrange = n) # widened to `float`, multiplied and added separately (each rounded to bfloat) - @test isequal(Array(out), map((x, y) -> BFloat16(Float32(BFloat16(Float32(a) * Float32(x))) + Float32(y)), hx, hy)) + @test isequal(Array(out), map((x, y) -> bf16_nearest(Float32(bf16_nearest(Float32(a) * Float32(x))) + Float32(y)), hx, hy)) end @testset "arithmetic" begin @@ -80,7 +87,7 @@ if bf16_supported o = Array(out) # (references in `Float32`: on Julia 1.11, the host miscompiles vectorized `BFloat16` # operations such as `copysign`) - f32(op) = (xs...) -> BFloat16(op(Float32.(xs)...)) + f32(op) = (xs...) -> bf16_nearest(op(Float32.(xs)...)) @test isequal(o[1, :], map(f32(/), hx, hy)) @test isequal(o[2, :], map(f32(-), hx, hy)) @test isequal(o[3, :], map(f32(min), hx, hy))