Context and Device Management

This page documents the API for managing Level Zero drivers, devices, and contexts in oneAPI.jl.

Overview

oneAPI.jl uses task-local state to manage GPU resources. This allows different Julia tasks to work with different drivers, devices, or contexts without interfering with each other.

The typical hierarchy is:

  • Driver: Represents a Level Zero driver (usually one per GPU vendor/installation)
  • Device: Represents a physical GPU device
  • Context: Manages resources like memory allocations and streams

Driver Management

driver() -> ZeDriver

Get the current Level Zero driver for the calling task. If no driver has been explicitly set with driver!, returns the first available driver. The driver selection is task-local.

driver!(drv::ZeDriver)

Set the current Level Zero driver for the calling task. This also clears the current device selection, as devices are associated with specific drivers.

drivers() -> Vector{ZeDriver}

Return a list of all available Level Zero drivers.

Device Management

device() -> ZeDevice

Get the current Level Zero device for the calling task. If no device has been explicitly set with device!, returns the first available device for the current driver. The device selection is task-local.

device!(dev::ZeDevice) / device!(i::Int)

Set the current Level Zero device for the calling task. Can pass either a device object or a 1-based device index.

devices() -> Vector{ZeDevice} / devices(drv::ZeDriver)

Return a list of available Level Zero devices. Without arguments, returns devices for the current driver.

Context Management

context() -> ZeContext

Get the current Level Zero context for the calling task. If no context has been explicitly set with context!, returns a global context for the current driver. Contexts manage the lifetime of resources like memory allocations and streams.

context!(ctx::ZeContext)

Set the current Level Zero context for the calling task.

Streams

global_stream(ctx::ZeContext, dev::ZeDevice) -> oneStream

Get the stream all oneAPI.jl operations of the calling task target for the given context and device. A stream holds an in-order asynchronous immediate command list, to which kernel launches, copies and fills are appended and submitted as they happen, plus a lazily-created companion command queue used only for SYCL/oneMKL interop. Streams are cached per task and (context, device) pair.

global_queue(ctx::ZeContext, dev::ZeDevice) -> ZeCommandQueue

Get the calling task's companion command queue for the given context and device — the queue oneMKL work is enqueued on through SYCL interop. Julia-side kernels, copies and fills do not use it, so synchronizing this queue does not wait for them; use synchronize() or synchronize(global_stream(ctx, dev)) for that.

synchronize()

Block the host thread until all operations on the global stream for the current context and device have completed: both the immediate command list and, if one has been created, the companion command queue. synchronize(stream::oneStream) does the same for an explicit stream.

Example Workflow

using oneAPI

# List available drivers
drv_list = drivers()
println("Available drivers: ", length(drv_list))

# Select a specific driver
driver!(drv_list[1])

# List devices for current driver
dev_list = devices()
println("Available devices: ", length(dev_list))

# Select a specific device
device!(dev_list[1])

# Get the current context (created automatically)
ctx = context()

# Perform GPU operations...
a = oneArray(rand(Float32, 100))

# Wait for all operations to complete
synchronize()

Multi-Device Programming

You can use different devices in different Julia tasks:

using oneAPI

# Task 1: Use first device
Threads.@spawn begin
    device!(1)
    a = oneArray(rand(Float32, 100))
    # ... operations on device 1 ...
end

# Task 2: Use second device
Threads.@spawn begin
    device!(2)
    b = oneArray(rand(Float32, 100))
    # ... operations on device 2 ...
end