-
Notifications
You must be signed in to change notification settings - Fork 34
Expand file tree
/
Copy pathcontext.jl
More file actions
310 lines (238 loc) · 8 KB
/
context.jl
File metadata and controls
310 lines (238 loc) · 8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# context management and global state
# to avoid CUDA-style implicit state, where operations can fail if they are accidentally
# executed in the wrong context, ownership should always be encoded in each object.
# the functions below should only be used to determine initial ownership.
# XXX: rework this -- it doesn't work well when altering the state
export driver, driver!, device, device!, context, context!, global_queue, synchronize, is_integrated
"""
driver() -> ZeDriver
Get the current Level Zero driver for the calling task. If no driver has been explicitly
set with [`driver!`](@ref), returns the first available driver.
The driver selection is task-local, allowing different Julia tasks to use different drivers.
# Examples
```julia
drv = driver()
println("Using driver: ", drv)
```
See also: `driver!`, `drivers`
"""
function driver()
get!(task_local_storage(), :ZeDriver) do
first(drivers())
end
end
"""
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.
The driver selection is task-local, allowing different Julia tasks to use different drivers.
# Arguments
- `drv::ZeDriver`: The driver to use for subsequent operations.
# Examples
```julia
drv = drivers()[2] # Select second available driver
driver!(drv)
```
See also: `driver`, `drivers`
"""
function driver!(drv::ZeDriver)
task_local_storage(:ZeDriver, drv)
delete!(task_local_storage(), :ZeDevice)
end
"""
device() -> ZeDevice
Get the current Level Zero device for the calling task. If no device has been explicitly
set with [`device!`](@ref), returns the first available device for the current driver.
The device selection is task-local, allowing different Julia tasks to use different devices.
# Examples
```julia
dev = device()
println("Using device: ", dev)
```
See also: `device!`, `devices`, `driver`
"""
function device()
get!(task_local_storage(), :ZeDevice) do
first(devices(driver()))
end
end
"""
device!(dev::ZeDevice)
device!(i::Int)
Set the current Level Zero device for the calling task.
The device selection is task-local, allowing different Julia tasks to use different devices.
# Arguments
- `dev::ZeDevice`: The device to use for subsequent operations.
- `i::Int`: Device index (1-based) from the list of available devices for the current driver.
# Examples
```julia
# Select by device object
dev = devices()[2]
device!(dev)
# Select by index
device!(2) # Select second device
```
See also: [`device`](@ref), [`devices`](@ref)
"""
function device!(drv::ZeDevice)
task_local_storage(:ZeDevice, drv)
end
function device!(i::Int)
devs = devices(driver())
if i < 1 || i > length(devs)
throw(ArgumentError("Invalid device index $i (must be between 1 and $(length(devs)))"))
end
return device!(devs[i])
end
"""
is_integrated(dev::ZeDevice=device()) -> Bool
Check if the given device is an integrated GPU (i.e., integrated with the host processor).
Integrated GPUs share memory with the CPU and are typically found in laptop and desktop
processors with integrated graphics.
# Arguments
- `dev::ZeDevice`: The device to check. Defaults to the current device.
# Returns
- `true` if the device is integrated, `false` otherwise (e.g., discrete GPU).
# Examples
```julia
if is_integrated()
println("Running on integrated graphics")
else
println("Running on discrete GPU")
end
# Check a specific device
dev = devices()[1]
is_integrated(dev)
```
See also: [`device`](@ref), [`devices`](@ref)
"""
function is_integrated(dev::ZeDevice=device())
props = oneL0.properties(dev)
return (props.flags & oneL0.ZE_DEVICE_PROPERTY_FLAG_INTEGRATED) != 0
end
const global_contexts = Dict{ZeDriver,ZeContext}()
"""
context() -> ZeContext
Get the current Level Zero context for the calling task. If no context has been explicitly
set with [`context!`](@ref), returns a global context for the current driver.
Contexts manage the lifetime of resources like memory allocations and command queues.
The context selection is task-local, but contexts themselves are cached globally per driver.
# Examples
```julia
ctx = context()
println("Using context: ", ctx)
```
See also: [`context!`](@ref), [`driver`](@ref)
"""
function context()
get!(task_local_storage(), :ZeContext) do
get!(global_contexts, driver()) do
ZeContext(driver())
end
end
end
"""
context!(ctx::ZeContext)
Set the current Level Zero context for the calling task.
The context selection is task-local, allowing different Julia tasks to use different contexts.
# Arguments
- `ctx::ZeContext`: The context to use for subsequent operations.
# Examples
```julia
ctx = ZeContext(driver())
context!(ctx)
```
See also: `context`, `ZeContext`
"""
function context!(ctx::ZeContext)
task_local_storage(:ZeContext, ctx)
end
"""
global_queue(ctx::ZeContext, dev::ZeDevice) -> ZeCommandQueue
Get the global command queue for the given context and device. This queue is used as the
default queue for executing operations, guaranteeing expected semantics when using a device
on a Julia task.
The queue is created with in-order execution flags, meaning commands are executed in the
order they are submitted. Queues are cached per task and (context, device) pair.
# Arguments
- `ctx::ZeContext`: The context for the command queue.
- `dev::ZeDevice`: The device for the command queue.
# Returns
- `ZeCommandQueue`: A cached command queue with in-order execution.
# Examples
```julia
ctx = context()
dev = device()
queue = global_queue(ctx, dev)
```
See also: `context`, `device`, `synchronize`
"""
function global_queue(ctx::ZeContext, dev::ZeDevice)
# NOTE: dev purposefully does not default to context() or device() to stress that
# objects should track ownership, and not rely on implicit global state.
get!(task_local_storage(), (:ZeCommandQueue, ctx, dev)) do
ZeCommandQueue(ctx, dev; flags = oneL0.ZE_COMMAND_QUEUE_FLAG_IN_ORDER)
end
end
"""
synchronize()
Block the host thread until all operations on the global command queue for the current
context and device have completed.
This is useful for timing operations or ensuring that GPU work has finished before
accessing results on the CPU.
# Examples
```julia
x = oneArray(rand(1000))
y = x .+ 1
synchronize() # Wait for GPU computation to complete
println("GPU work completed")
```
See also: [`global_queue`](@ref), [`context`](@ref), [`device`](@ref)
"""
function oneL0.synchronize()
oneL0.synchronize(global_queue(context(), device()))
end
# re-export and augment parts of oneL0 to make driver and device selection easier
export drivers, devices
"""
devices() -> Vector{ZeDevice}
devices(drv::ZeDriver) -> Vector{ZeDevice}
Return a list of available Level Zero devices. Without arguments, returns devices for
the current driver. With a driver argument, returns devices for that specific driver.
# Examples
```julia
# Get devices for current driver
devs = devices()
println("Found ", length(devs), " devices")
# Get devices for specific driver
drv = drivers()[1]
devs = devices(drv)
```
See also: `device`, `device!`, `drivers`
"""
oneL0.devices() = devices(driver())
## SYCL state
# XXX: including objects in the TLS key is bad for performance
export sycl_platform, sycl_device, sycl_context, sycl_queue
function sycl_platform(drv=driver())
get!(task_local_storage(), (:SYCLPlatform, drv)) do
syclPlatform(drv)
end
end
function sycl_device(dev=device())
get!(task_local_storage(), (:SYCLDevice, dev)) do
syclDevice(sycl_platform(), dev)
end
end
function sycl_context(ctx=context(), dev=device())
get!(task_local_storage(), (:SYCLContext, dev)) do
syclContext([sycl_device(dev)], ctx)
end
end
function sycl_queue(queue)
get!(task_local_storage(), (:SYCLQueue, queue.context, queue.device)) do
syclQueue(sycl_context(queue.context, queue.device),
sycl_device(queue.device),
global_queue(queue.context, queue.device))
end
end