Skip to content

Accept SPV_KHR_bfloat16 SPIR-V on the CPU devices - #10

Open
maleadt wants to merge 2 commits into
mainfrom
tb/spirv-bfloat16
Open

maleadt wants to merge 2 commits into
mainfrom
tb/spirv-bfloat16

Conversation

@maleadt

@maleadt maleadt commented Nov 3, 2026

Copy link
Copy Markdown
Member

LLVM's SPIR-V back-end emits SPV_KHR_bfloat16 for any LLVM IR that uses bfloat, e.g. Julia's BFloat16 (Core.BFloat16 since Julia 1.11, always lowered to bfloat). The CPU devices don't enable the extension for the translator, so such modules fail to build with "input SPIR-V module uses extension 'SPV_KHR_bfloat16' which were disabled by --spirv-ext option". This PR makes the CPU devices accept and report it.

The extension adds a bfloat16 type for loads, stores and conversions. Arithmetic is not part of it; producers widen it to float.

  • Enabling it. +SPV_KHR_bfloat16 is added to HOST_DEVICE_SPV_EXTENSIONS when the translator reads it. The translator has had it since 21.1.0, and since 18.1.14, 19.1.8 and 20.1.3 on the older release branches, so with libLLVMSPIRV, configure checks its LLVMSPIRVExtensions.inc. A build that only has the llvm-spirv binary, which can't be queried, gets the extension from LLVM 21 on. With LLVM 20 and an older translator library, it is left out.
  • Reporting it. CL_DEVICE_SPIRV_EXTENSIONS_KHR lists SPV_KHR_bfloat16 and CL_DEVICE_SPIRV_CAPABILITIES_KHR lists BFloat16TypeKHR, as is done for SPV_KHR_float_controls2. The vendored spirv.hh gains the extension's three capabilities.
  • Conversion helpers. On x86-64 CPUs without native bfloat16 conversions, codegen calls __truncsfbf2 and __truncdfbf2 for fptrunc to bfloat. libgcc has them only since GCC 13. On Windows they can't be loaded into the JIT, the same problem as with the FP16 helpers. The first commit defines them for the JIT on x86-64. They work on the bits and return the bfloat16 in the low half of a float register, so building PoCL doesn't need __bf16 support. Converting from double rounds to float with round-to-odd first, so that the result is rounded only once. Compared with GCC 16's libgcc over 10^8 inputs (random bit patterns, values next to bfloat16 ties, overflow, NaN), they gave the same results for all.
  • Test. regression/spirv_bfloat16 runs a SPIR-V kernel that converts floats to bfloat16 (ties, overflow, infinities, signed zeros) and bfloat16 to float, and takes a bfloat16 kernel argument. When the translator supports the extension, a CPU device that doesn't report it fails the test instead of skipping it.

The translator deviates from the extension in two conversions, neither of which involves float:

  • OpFConvert between half and bfloat16 becomes an fptrunc between two 16-bit types, which fails verification. LLVM IR can't express that conversion, so LLVM's SPIR-V back-end never emits it.
  • OpFConvert from double to bfloat16 becomes a direct fptrunc double to bfloat, which rounds once. The extension says to convert through binary32, which rounds twice. For SPIR-V produced from LLVM's fptrunc double to bfloat, rounding once is what the source asked for.

Tested with LLVM 22.1.8 and translator 22: ctest passes on main (344/344) and on 7.2 (354/354), including the new test with the native CPU target and with POCL_LLVM_CPU_NAME=x86-64. The test fails when the new helper returns a wrong value, and when the extension is removed from HOST_DEVICE_SPV_EXTENSIONS. End to end with KernelAbstractions.jl on the 7.2 backport, storing bfloat16, converting in both directions and an axpy (arithmetic widened to float by the producer) all give the same results as on the host.

The 7.2 backport, as shipped in the Julia JLLs, is on tb/spirv-bfloat16-7.2.

On x86-64 CPUs without native bfloat16 conversions, LLVM lowers fptrunc
from float or double to bfloat to calls to __truncsfbf2 and __truncdfbf2.
The JIT resolves them from the process, but libgcc only has them since
GCC 13, and on Windows they can't be loaded, like the FP16 helpers.

Define them for the JIT on x86-64, the way the FP16 helpers are on
MinGW. They are written on the bits, returning a bfloat16 in the low half
of a float's register, so building PoCL doesn't need __bf16 support. The
double conversion rounds to float with round-to-odd first, so that it
rounds only once.
SPV_KHR_bfloat16 adds a bfloat16 floating-point type to SPIR-V for loads,
stores and conversions to and from other floating-point types. LLVM's
SPIR-V back-end needs it for any LLVM IR that uses `bfloat`, e.g. from
Julia's BFloat16, and the translator maps it back to `bfloat`.

The SPIR-V reader rejects modules using extensions that are not enabled
with --spirv-ext, so such modules failed to build. Enable the extension
for the CPU devices when the translator reads it: libLLVMSPIRV is checked
for it, since the 18-20 release branches only gained it in later point
releases, and the llvm-spirv binary is assumed to read it from LLVM 21
on. Report it and the BFloat16TypeKHR capability, added to the vendored
spirv.hh with the extension's other capabilities, through
CL_DEVICE_SPIRV_EXTENSIONS_KHR and CL_DEVICE_SPIRV_CAPABILITIES_KHR.
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