Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ GPUToolbox = "3.3.1"
KernelAbstractions = "0.10"
KernelInterface = "0.4"
LLVM = "10"
NEO_jll = "=26.18.38308"
NEO_jll = "=26.35.39758"
PrecompileTools = "1"
Preferences = "1"
SPIRVIntrinsics = "1.4"
Expand All @@ -56,7 +56,7 @@ SPIRV_LLVM_Translator_jll = "23"
SPIRV_Tools_jll = "2025.4.0"
SpecialFunctions = "1.3, 2"
julia = "1.10"
oneAPI_Level_Zero_Loader_jll = "1.29"
oneAPI_Level_Zero_Loader_jll = "1.32"
oneAPI_Support_jll = "0.11.0"

[extras]
Expand Down
4 changes: 2 additions & 2 deletions docs/src/lts.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ The LTS SPIR-V stack cannot translate the LLVM `bfloat` type in generic kernels:
that merely keeps a `BFloat16` value (`clamp!`, say) fails with an `InvalidIRError`, and
declaring the `SPV_KHR_bfloat16` extension crashes the LTS runtime outright. On LTS the
compiler is therefore configured with `supports_bfloat16 = false`, regardless of what the
device reports — `oneAPI._device_supports_bfloat16()` is a *hardware* capability check and
does not capture this limitation.
device reports — `oneAPI._device_supports_bfloat16()` only checks BFloat16 conversion
support and does not capture this limitation.

Consequently `BFloat16` is dropped from the element types exercised by the test suite, and
`examples/bfloat16.jl` exits early with a message. Other floating-point types are unaffected.
Expand Down
11 changes: 7 additions & 4 deletions docs/src/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,13 @@ them.
BFloat16 support.

**Solution**:
The LTS SPIR-V stack cannot translate the LLVM `bfloat` type in generic kernels; the
device-level check (`oneAPI._device_supports_bfloat16()`) reports hardware capability and
does not capture this. Use `Float16` or `Float32` on that stack — see
[Intel LTS Driver Stack](lts.md).
`BFloat16` arrays only need the device to support BFloat16 conversions
(`oneAPI._device_supports_bfloat16()`), but kernels that compute with `BFloat16` values need
IGC to accept the `SPV_KHR_bfloat16` SPIR-V extension. IGC only does so on Xe-HPG and newer
GPUs, excluding Meteor Lake, so such kernels fail on Xe-LP (Tiger Lake, Alder Lake, DG1,
...) and Meteor Lake. The LTS SPIR-V stack cannot translate the LLVM `bfloat` type in
generic kernels at all — see [Intel LTS Driver Stack](lts.md). Use `Float16` or `Float32`
there.

## Debugging

Expand Down
9 changes: 4 additions & 5 deletions examples/bfloat16.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ using oneAPI, Test
exit()
end

bfloat16_supported = oneAPI._device_supports_bfloat16()
bfloat16_supported = oneAPI._driver_supports_bfloat16_spirv()

@info "BFloat16 support: $bfloat16_supported"

Expand All @@ -14,10 +14,9 @@ if !bfloat16_supported
exit()
end

# _device_supports_bfloat16() reflects hardware conversion support, but the Aurora LTS
# SPIR-V stack (Khronos translator + NEO/IGC) cannot translate the `bfloat` LLVM type at
# all: even a load/store forces SPV_KHR_bfloat16, which the LTS runtime rejects. So bf16
# kernels can't be compiled there regardless of device support.
# The Aurora LTS SPIR-V stack (Khronos translator + NEO/IGC) cannot translate the `bfloat`
# LLVM type at all: even a load/store forces SPV_KHR_bfloat16, which the LTS runtime
# rejects. So bf16 kernels can't be compiled there regardless of device support.
if oneAPI.oneL0.LTS[]
@info "BFloat16 kernels are unsupported on the Aurora LTS SPIR-V stack, skipping."
exit()
Expand Down
16 changes: 15 additions & 1 deletion lib/level-zero/device.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export ZeDevice, properties, compute_properties, module_properties, float_atomic_properties, memory_properties, memory_access_properties, cache_properties, image_properties, p2p_properties
export ZeDevice, properties, compute_properties, module_properties, float_atomic_properties, ip_version, memory_properties, memory_access_properties, cache_properties, image_properties, p2p_properties

struct ZeDevice
handle::ze_device_handle_t
Expand Down Expand Up @@ -125,6 +125,20 @@ function float_atomic_properties(dev::ZeDevice)
)
end

function ip_version(dev::ZeDevice)
haskey(extension_properties(dev.driver), ZE_DEVICE_IP_VERSION_EXT_NAME) || return nothing

props_ref = Ref(ze_device_properties_t())
ip_props_ref = Ref(ze_device_ip_version_ext_t())
GC.@preserve props_ref ip_props_ref begin
link_extensions(props_ref, ip_props_ref)
zeDeviceGetProperties(dev, props_ref)
end

ip = ip_props_ref[].ipVersion
return ip == 0 ? nothing : ip
end

function memory_properties(dev::ZeDevice)
count_ref = Ref{UInt32}(0)
zeDeviceGetMemoryProperties(dev, count_ref, C_NULL)
Expand Down
139 changes: 17 additions & 122 deletions src/compiler/compilation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -88,21 +88,6 @@ function GPUCompiler.finish_ir!(job::oneAPICompilerJob, mod::LLVM.Module,
Tuple{CompilerJob{SPIRVCompilerTarget}, typeof(mod), typeof(entry)},
job, mod, entry)

# When the device supports BFloat16 but the SPIR-V runtime doesn't accept
# SPV_KHR_bfloat16, lower all bfloat types to i16 so the back-end/translator can
# handle the module without the extension. Both conditions are read from the
# (device-independent) compiler config so this stays valid without a live
# device: `_compiler_config` sets `supports_bfloat16` from the device and adds
# the `SPV_KHR_bfloat16` extension iff the driver's SPIR-V runtime accepts it.
# Rolling stack only: bf16 is forced off on the LTS stack
# (`supports_bfloat16 = false`, see `_compiler_config`), so this pass never
# runs there.
target = job.config.target
if isdefined(Core, :BFloat16) &&
target.supports_bfloat16 && !occursin("SPV_KHR_bfloat16", target.extensions)
lower_bfloat_to_i16!(mod)
end

return entry
end

Expand Down Expand Up @@ -152,100 +137,6 @@ function uses_heap(mod::LLVM.Module, T_state::LLVMType, heap_field::Integer)
return false
end

# Lower bfloat types to i16 in the LLVM IR.
# This is needed when the device supports BFloat16 but the SPIR-V runtime/translator
# doesn't support SPV_KHR_bfloat16. Since sizeof(bfloat)==sizeof(i16)==2, the memory
# layout is identical.
#
# TODO: Julia 1.12's Core.BFloat16 is a bare primitive (no Float32 conversion, no
# arithmetic), so fptrunc/fpext instructions never appear in practice. If Julia adds
# BFloat16 conversion methods in the future, this pass should be extended to handle
# fptrunc float→bfloat and fpext bfloat→float, either via inline RNE bit manipulation
# or calls to __devicelib_ConvertFToBF16INTEL / __devicelib_ConvertBF16ToFINTEL.
function lower_bfloat_to_i16!(mod::LLVM.Module)
T_bf16 = LLVM.BFloatType()
T_i16 = LLVM.Int16Type()

# Phase 1: Eliminate all bitcasts between i16 and bfloat (same bit width).
eliminate_bf16_bitcasts!(mod, T_bf16, T_i16)

# Phase 2: Replace remaining bfloat GEPs, loads, and stores with i16 equivalents.
@dispose builder = IRBuilder() begin
for f in mod.functions
isdeclaration(f) && continue
for bb in f.blocks
to_replace = LLVM.Instruction[]
for inst in bb.instructions
if inst isa LLVM.GetElementPtrInst
inst.source_element_type == T_bf16 && push!(to_replace, inst)
elseif inst isa LLVM.LoadInst
inst.value_type == T_bf16 && push!(to_replace, inst)
elseif inst isa LLVM.StoreInst
inst.value_operand.value_type == T_bf16 && push!(to_replace, inst)
end
end

for inst in to_replace
position!(builder, LLVM.before(inst))

if inst isa LLVM.GetElementPtrInst
ptr = inst.pointer_operand
new_gep = if inst.inbounds
LLVM.inbounds_gep!(builder, T_i16, ptr, inst.indices)
else
LLVM.gep!(builder, T_i16, ptr, inst.indices)
end
LLVM.replace_uses!(inst, new_gep)
elseif inst isa LLVM.LoadInst
ptr = inst.pointer_operand
new_load = LLVM.load!(builder, T_i16, ptr)
LLVM.replace_uses!(inst, new_load)
elseif inst isa LLVM.StoreInst
LLVM.store!(builder, inst.value_operand, inst.pointer_operand)
end

erase!(inst)
end
end
end
end

return true
end

# Iteratively eliminate bitcasts between i16 and bfloat (same bit representation).
function eliminate_bf16_bitcasts!(mod::LLVM.Module, T_bf16::LLVMType, T_i16::LLVMType)
changed = true
while changed
changed = false
for f in mod.functions
isdeclaration(f) && continue
for bb in f.blocks
to_delete = LLVM.Instruction[]
for inst in bb.instructions
if inst isa LLVM.BitCastInst
src = inst.operands[1]
src_ty = src.value_type
dst_ty = inst.value_type
if (src_ty == T_i16 && dst_ty == T_bf16) ||
(src_ty == T_bf16 && dst_ty == T_i16) ||
(src_ty == dst_ty)
LLVM.replace_uses!(inst, src)
push!(to_delete, inst)
changed = true
end
end
end
for inst in to_delete
erase!(inst)
end
end
end
end
return
end


## compiler implementation (configure, compile, and link)

# cache of compiler configurations, per device (but additionally configurable via kwargs)
Expand All @@ -261,15 +152,22 @@ function compiler_config(dev; kwargs...)
end
end
# Whether the driver's SPIR-V runtime accepts the SPV_KHR_bfloat16 extension.
#
# XXX: this mirrors IGC's platform table, because IGC terminates the process when a module
# uses an extension it doesn't accept on the device, and Level Zero has no query for
# the accepted extensions (intel/intel-graphics-compiler#452). IGC 2.41 accepts
# SPV_KHR_bfloat16 on Xe-HPG and newer cores except Meteor Lake (`SPIRVExtensions.td`;
# 2.34 behaves the same), which corresponds to graphics IP 12.55 and up, excluding 12.70
# and 12.71 (MTL, and the ARL-U/S parts that share its IP). Re-check with every IGC bump.
# ZE_extension_bfloat16_conversions is no substitute: it covers the conversion
# instructions of SPV_INTEL_bfloat16_conversion, which NEO 26.35 advertises everywhere.
function _driver_supports_bfloat16_spirv(dev=device())
return @static if isdefined(Core, :BFloat16)
haskey(
oneL0.extension_properties(dev.driver),
oneL0.ZE_BFLOAT16_CONVERSIONS_EXT_NAME
)
else
false
end
@static isdefined(Core, :BFloat16) || return false
oneL0.LTS[] && return false
ip = oneL0.ip_version(dev)
ip === nothing && return false
arch_release = (Int(ip >> 22), Int((ip >> 14) & 0xff))
return arch_release >= (12, 55) && arch_release ∉ ((12, 70), (12, 71))
end

# The atomic operations that GPUCompiler may select SPIR-V instructions for; the others are
Expand Down Expand Up @@ -328,11 +226,8 @@ end
supports_bfloat16 = false
else
backend = :llvm
# Allow BFloat16 in IR if the device supports it (even if the SPIR-V runtime doesn't
# advertise the extension); finish_ir! lowers bfloat->i16 when the extension is absent.
supports_bfloat16 = _device_supports_bfloat16(dev)
# Only declare the SPIR-V extension when the runtime actually accepts it.
if _driver_supports_bfloat16_spirv(dev)
supports_bfloat16 = _driver_supports_bfloat16_spirv(dev)
if supports_bfloat16
push!(extensions, "SPV_KHR_bfloat16")
end
end
Expand Down
13 changes: 7 additions & 6 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,12 @@ init_worker_code = quote
append!(eltypes, [Float64, ComplexF64])
end
@static if isdefined(Core, :BFloat16)
# Exercising BFloat16 as a generic element type needs (1) hardware support,
# (2) a non-LTS stack — the Aurora LTS SPIR-V stack (Khronos translator +
# NEO/IGC) cannot codegen native `bfloat` in generic kernels: any kernel that
# keeps a bfloat value (e.g. clamp!) fails with InvalidIRError, and declaring
# SPV_KHR_bfloat16 crashes the LTS runtime — and (3) a working CPU reference
# Exercising BFloat16 as a generic element type needs (1) a device on which IGC
# accepts SPV_KHR_bfloat16, (2) a non-LTS stack — the Aurora LTS SPIR-V stack
# (Khronos translator + NEO/IGC) cannot codegen native `bfloat` in generic
# kernels: any kernel that keeps a bfloat value (e.g. clamp!) fails with
# InvalidIRError, and declaring SPV_KHR_bfloat16 crashes the LTS runtime — and
# (3) a working CPU reference
# path, which requires more host-side BFloat16 support than BFloat16s.jl
# currently implements (e.g. the div/rem family throws "rem not defined for
# BFloat16"). Probe the operations the testsuite relies on, so the element
Expand All @@ -111,7 +112,7 @@ init_worker_code = quote
catch
false
end
const bfloat16_supported = oneAPI._device_supports_bfloat16() &&
const bfloat16_supported = oneAPI._driver_supports_bfloat16_spirv() &&
!oneL0.LTS[] && bfloat16_host_support()
if bfloat16_supported
push!(eltypes, Core.BFloat16)
Expand Down
Loading