@@ -4,6 +4,30 @@ struct oneAPICompilerParams <: AbstractCompilerParams end
44const oneAPICompilerConfig = CompilerConfig{SPIRVCompilerTarget, oneAPICompilerParams}
55const oneAPICompilerJob = CompilerJob{SPIRVCompilerTarget,oneAPICompilerParams}
66
7+ """
8+ oneAPIResults
9+
10+ Cached compilation results for a oneAPI kernel job, managed by
11+ `GPUCompiler.cached_results`. Fields are populated through the compile pipeline:
12+ `image` (SPIR-V bytes) + `entry` after codegen, and `kernels` after the session-local
13+ link onto a Level Zero module. The first two are session-portable (cached through
14+ precompilation); `kernels` is session-local and never populated during precompilation.
15+ `image === nothing` identifies a job that has not been compiled yet.
16+
17+ `kernels` is a small linear cache of `(ZeContext, ZeDevice, ZeKernel)` tuples. The cache
18+ partition already covers everything that affects codegen via `GPUCompiler.cache_owner`, so
19+ the only runtime-visible dimensions left are the Level Zero context and device that own the
20+ linked `ZeKernel` (a `ZeModule` is built for a specific `(context, device)` pair). A linear
21+ scan with `===`/`==` is fastest in the common case (n=1) and stays cheap for the rare
22+ workload that bounces between a handful of contexts or devices.
23+ """
24+ mutable struct oneAPIResults
25+ image:: Union{Nothing, Vector{UInt8}} # SPIR-V binary
26+ entry:: Union{Nothing, String}
27+ kernels:: Vector{Tuple{ZeContext, ZeDevice, ZeKernel}} # session-local; linear-scanned
28+ oneAPIResults () = new (nothing , nothing , Tuple{ZeContext, ZeDevice, ZeKernel}[])
29+ end
30+
731GPUCompiler. runtime_module (:: oneAPICompilerJob ) = oneAPI
832
933GPUCompiler. method_table_view (job:: oneAPICompilerJob ) =
@@ -52,9 +76,13 @@ function GPUCompiler.finish_ir!(job::oneAPICompilerJob, mod::LLVM.Module,
5276
5377 # When the device supports BFloat16 but the SPIR-V runtime doesn't accept
5478 # SPV_KHR_bfloat16, lower all bfloat types to i16 so the translator can
55- # handle the module without the extension.
79+ # handle the module without the extension. Both conditions are read from the
80+ # (device-independent) compiler config so this stays valid without a live
81+ # device: `_compiler_config` sets `supports_bfloat16` from the device and adds
82+ # the `SPV_KHR_bfloat16` extension iff the driver's SPIR-V runtime accepts it.
83+ target = job. config. target
5684 if @static (isdefined (Core, :BFloat16 ) && isdefined (LLVM, :BFloatType )) &&
57- _device_supports_bfloat16 () && ! _driver_supports_bfloat16_spirv ( )
85+ target . supports_bfloat16 && ! occursin ( " SPV_KHR_bfloat16 " , target . extensions )
5886 lower_bfloat_to_i16! (mod)
5987 end
6088
@@ -266,24 +294,13 @@ function eliminate_bf16_bitcasts!(mod::LLVM.Module, T_bf16::LLVMType, T_i16::LLV
266294end
267295
268296
269- # # compiler implementation (cache, configure, compile, and link)
270-
271- # cache of compilation caches, per device
272- const _compiler_caches = Dict {ZeDevice, Dict{Any, Any}} ()
273- function compiler_cache (dev:: ZeDevice )
274- cache = get (_compiler_caches, dev, nothing )
275- if cache === nothing
276- cache = Dict {Any, Any} ()
277- _compiler_caches[dev] = cache
278- end
279- return cache
280- end
297+ # # compiler implementation (configure, compile, and link)
281298
282299# cache of compiler configurations, per device (but additionally configurable via kwargs)
283300const _toolchain = Ref {Any} ()
284301const _compiler_configs = Dict {UInt, oneAPICompilerConfig} ()
285302function compiler_config (dev; kwargs... )
286- h = hash (dev, hash (kwargs))
303+ h = hash (dev. driver , hash (dev, hash ( kwargs) ))
287304 config = get (_compiler_configs, h, nothing )
288305 if config === nothing
289306 config = _compiler_config (dev; kwargs... )
@@ -292,10 +309,10 @@ function compiler_config(dev; kwargs...)
292309 return config
293310end
294311# Whether the driver's SPIR-V runtime accepts the SPV_KHR_bfloat16 extension.
295- function _driver_supports_bfloat16_spirv ()
312+ function _driver_supports_bfloat16_spirv (dev = device () )
296313 return @static if isdefined (Core, :BFloat16 )
297314 haskey (
298- oneL0. extension_properties (driver () ),
315+ oneL0. extension_properties (dev . driver),
299316 oneL0. ZE_BFLOAT16_CONVERSIONS_EXT_NAME
300317 )
301318 else
@@ -304,26 +321,29 @@ function _driver_supports_bfloat16_spirv()
304321end
305322
306323@noinline function _compiler_config (dev; kernel= true , name= nothing , always_inline= false , kwargs... )
307- supports_fp16 = oneL0. module_properties (device ()). fp16flags & oneL0. ZE_DEVICE_MODULE_FLAG_FP16 == oneL0. ZE_DEVICE_MODULE_FLAG_FP16
308- supports_fp64 = oneL0. module_properties (device ()). fp64flags & oneL0. ZE_DEVICE_MODULE_FLAG_FP64 == oneL0. ZE_DEVICE_MODULE_FLAG_FP64
324+ properties = oneL0. module_properties (dev)
325+ supports_fp16 = properties. fp16flags & oneL0. ZE_DEVICE_MODULE_FLAG_FP16 == oneL0. ZE_DEVICE_MODULE_FLAG_FP16
326+ supports_fp64 = properties. fp64flags & oneL0. ZE_DEVICE_MODULE_FLAG_FP64 == oneL0. ZE_DEVICE_MODULE_FLAG_FP64
309327 # Allow BFloat16 in IR if the device supports it (even if the SPIR-V runtime doesn't
310328 # advertise the extension). We lower bfloat→i16 in finish_ir! when needed.
311- supports_bfloat16 = _device_supports_bfloat16 ()
329+ supports_bfloat16 = _device_supports_bfloat16 (dev )
312330
313331 extensions = String[]
314332 # Only add the SPIR-V extension if the runtime actually supports it
315- if _driver_supports_bfloat16_spirv ()
333+ if _driver_supports_bfloat16_spirv (dev )
316334 push! (extensions, " SPV_KHR_bfloat16" )
317335 end
336+ extensions_str = join (map (ext -> " +$ext " , extensions), " ," )
318337
319338 # create GPUCompiler objects
320- target = SPIRVCompilerTarget (; extensions, supports_fp16, supports_fp64, supports_bfloat16, kwargs... )
339+ target = SPIRVCompilerTarget (; extensions= extensions_str , supports_fp16, supports_fp64, supports_bfloat16, kwargs... )
321340 params = oneAPICompilerParams ()
322341 CompilerConfig (target, params; kernel, name, always_inline)
323342end
324343
325- # compile to executable machine code
326- function compile (@nospecialize (job:: CompilerJob ))
344+ # run inference + LLVM codegen + SPIR-V emission. returns `(image, entry)`, both
345+ # session-portable so they survive precompilation when stored on a cached `CodeInstance`.
346+ function compile_to_obj (@nospecialize (job:: CompilerJob ))
327347 # TODO : on 1.9, this actually creates a context. cache those.
328348 asm, meta = JuliaContext () do ctx
329349 GPUCompiler. compile (:obj , job)
@@ -332,10 +352,8 @@ function compile(@nospecialize(job::CompilerJob))
332352 (image= asm, entry= LLVM. name (meta. entry))
333353end
334354
335- # link into an executable kernel
336- function link (@nospecialize (job:: CompilerJob ), compiled)
337- ctx = context ()
338- dev = device ()
339- mod = ZeModule (ctx, dev, compiled. image)
340- kernels (mod)[compiled. entry]
355+ # link the SPIR-V bytes into a session-local `ZeKernel` on the given context and device.
356+ function link_kernel (image:: Vector{UInt8} , entry:: String , ctx:: ZeContext , dev:: ZeDevice )
357+ mod = ZeModule (ctx, dev, image)
358+ kernels (mod)[entry]
341359end
0 commit comments