diff --git a/Project.toml b/Project.toml index 20c2df022..1c57f59a7 100644 --- a/Project.toml +++ b/Project.toml @@ -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" @@ -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] diff --git a/docs/src/lts.md b/docs/src/lts.md index e2d4ba7d7..ac26d250f 100644 --- a/docs/src/lts.md +++ b/docs/src/lts.md @@ -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. diff --git a/docs/src/troubleshooting.md b/docs/src/troubleshooting.md index 22ffaf39b..e03a9f777 100644 --- a/docs/src/troubleshooting.md +++ b/docs/src/troubleshooting.md @@ -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 diff --git a/examples/bfloat16.jl b/examples/bfloat16.jl index 581bd0f3e..2a60a1977 100644 --- a/examples/bfloat16.jl +++ b/examples/bfloat16.jl @@ -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" @@ -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() diff --git a/lib/level-zero/device.jl b/lib/level-zero/device.jl index 8b1e9f783..1d0febb7c 100644 --- a/lib/level-zero/device.jl +++ b/lib/level-zero/device.jl @@ -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 @@ -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) diff --git a/src/compiler/compilation.jl b/src/compiler/compilation.jl index f8aa0cb0c..6ec7af690 100644 --- a/src/compiler/compilation.jl +++ b/src/compiler/compilation.jl @@ -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 @@ -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) @@ -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 @@ -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 diff --git a/test/runtests.jl b/test/runtests.jl index 09a7f6c75..ea4dce3da 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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 @@ -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)