Skip to content

Commit 836ceec

Browse files
[SYCL] native image handle support for LevelZero. (#8603)
New interop support for images for LevelZero. Includes make_image and interop_handle::get_native_mem. Tests are present in this PR. --------- Signed-off-by: Chris Perkins <[email protected]> Co-authored-by: Steffen Larsen <[email protected]>
1 parent cc57b8c commit 836ceec

32 files changed

+1059
-67
lines changed

sycl/doc/extensions/supported/sycl_ext_oneapi_backend_level_zero.md

+158-34
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ NOTE: By necessity, this specification exposes some details about the way SYCL i
1717
|2|Added support for the make_buffer() API.
1818
|3|Added device member to backend_input_t<backend::ext_oneapi_level_zero, queue>.
1919
|4|Change the definition of backend_input_t and backend_return_t for the queue object, which changes the API for make_queue and get_native (when applied to queue).
20+
|5|Added support for make_image() API.
2021

2122
NOTE: This extension is following SYCL 2020 backend specification. Prior API for interoperability with Level-Zero is marked
2223
as deprecated and will be removed in the next release.
@@ -43,15 +44,15 @@ There are multiple ways in which the Level-Zero backend can be selected by the u
4344
4445
### 3.1 Through an environment variable
4546
46-
The SYCL_DEVICE_FILTER environment variable limits the SYCL runtime to use only a subset of the system's devices.
47-
By using ```level_zero``` for backend in SYCL_DEVICE_FILTER you can select the use of Level-Zero as a SYCL backend.
47+
The ONEAPI_DEVICE_SELECTOR environment variable limits the SYCL runtime to use only a subset of the system's devices.
48+
By using ```level_zero``` for backend in ONEAPI_DEVICE_SELECTOR you can select the use of Level-Zero as a SYCL backend.
4849
For further details see here: <https://github.com/intel/llvm/blob/sycl/sycl/doc/EnvironmentVariables.md>.
4950
5051
### 3.2 Through a programming API
5152
5253
There is an extension that introduces a filtering device selection to SYCL described in
5354
[sycl\_ext\_oneapi\_filter\_selector](../supported/sycl_ext_oneapi_filter_selector.asciidoc).
54-
Similar to how SYCL_DEVICE_FILTER applies filtering to the entire process this device selector can be used to
55+
Similar to how SYCL_DEVICE_FILTER or ONEAPI_DEVICE_SELECTOR applies filtering to the entire process this device selector can be used to
5556
programmatically select the Level-Zero backend.
5657
5758
When neither the environment variable nor the filtering device selector are used, the implementation chooses
@@ -247,6 +248,28 @@ struct {
247248
```
248249
</td>
249250
</tr>
251+
<tr>
252+
<td>image</td>
253+
<td>
254+
255+
``` C++
256+
ze_image_handle_t
257+
```
258+
</td>
259+
<td>
260+
261+
``` C++
262+
struct {
263+
ze_image_handle_t ZeImageHandle;
264+
sycl::image_channel_order ChanOrder;
265+
sycl::image_channel_type ChanType;
266+
sycl::range<Dimensions> Range;
267+
ext::oneapi::level_zero::ownership Ownership{
268+
ext::oneapi::level_zero::ownership::transfer};
269+
}
270+
```
271+
</td>
272+
</tr>
250273
</table>
251274
252275
### 4.2 Obtaining of native Level-Zero handles from SYCL objects
@@ -264,7 +287,7 @@ It is currently supported for SYCL ```platform```, ```device```, ```context```,
264287
The ```get_native(queue)``` function returns either ```ze_command_queue_handle_t``` or ```ze_command_list_handle_t``` depending on the manner in which the input argument ```queue``` had been created. Queues created with the SYCL ```queue``` constructors have a default setting for whether they use command queues or command lists. The default and how it may be changed is documented in the description for the environment variable ```SYCL_PI_LEVEL_ZERO_USE_IMMEDIATE_COMMANDLISTS```. Queues created using ```make_queue()``` use either a command list or command queue depending on the input argument to ```make_queue``` and are not affected by the default for SYCL queues or the environment variable.
265288

266289
The ```sycl::get_native<backend::ext_oneapi_level_zero>```
267-
free-function is not supported for SYCL ```buffer``` class. The native backend object associated with the
290+
free-function is not supported for SYCL ```buffer``` or ```image``` class. The native backend object associated with the
268291
buffer can be obtained using interop_hande class as described in the core SYCL specification section
269292
4.10.2, "Class interop_handle".
270293
The pointer returned by ```get_native_mem<backend::ext_oneapi_level_zero>``` method of the ```interop_handle```
@@ -299,39 +322,39 @@ an application to create a SYCL object that encapsulates a corresponding Level-Z
299322
<td>
300323
301324
``` C++
302-
make_platform<backend::ext_oneapi_level_zero>(
303-
const backend_input_t<
304-
backend::ext_oneapi_level_zero, platform> &)
325+
template <backend Backend>
326+
platform make_platform(
327+
const backend_input_t<Backend, platform> &)
305328
```
306329
</td>
307330
<td>Constructs a SYCL platform instance from a Level-Zero <code>ze_driver_handle_t</code>. The SYCL execution environment contains a fixed number of platforms that are enumerated via <code>sycl::platform::get_platforms()</code>. Calling this function does not create a new platform. Rather it merely creates a <code>sycl::platform</code> object that is a copy of one of the platforms from that enumeration.</td>
308331
</tr><tr>
309332
<td>
310333

311334
``` C++
312-
make_device<backend::ext_oneapi_level_zero>(
313-
const backend_input_t<
314-
backend::ext_oneapi_level_zero, device> &)
335+
template <backend Backend>
336+
device make_device(
337+
const backend_input_t<Backend, device> &)
315338
```
316339
</td>
317340
<td>Constructs a SYCL device instance from a Level-Zero <code>ze_device_handle_t</code>. The SYCL execution environment for the Level Zero backend contains a fixed number of devices that are enumerated via <code>sycl::device::get_devices()</code> and a fixed number of sub-devices that are enumerated via <code>sycl::device::create_sub_devices(...)</code>. Calling this function does not create a new device. Rather it merely creates a <code>sycl::device</code> object that is a copy of one of the devices from those enumerations.</td>
318341
</tr><tr>
319342
<td>
320343
321344
``` C++
322-
make_context<backend::ext_oneapi_level_zero>(
323-
const backend_input_t<
324-
backend::ext_oneapi_level_zero, context> &)
345+
template <backend Backend>
346+
context make_context(
347+
const backend_input_t<Backend, context> &)
325348
```
326349
</td>
327350
<td>Constructs a SYCL context instance from a Level-Zero <code>ze_context_handle_t</code>. The context is created against the devices passed in <code>DeviceList</code> structure member. There must be at least one device given and all the devices must be from the same SYCL platform and thus from the same Level-Zero driver. The <code>Ownership</code> input structure member specifies if the SYCL runtime should take ownership of the passed native handle. The default behavior is to transfer the ownership to the SYCL runtime. See section 4.4 for details.</td>
328351
</tr><tr>
329352
<td>
330353

331354
``` C++
332-
make_queue<backend::ext_oneapi_level_zero>(
333-
const backend_input_t<
334-
backend::ext_oneapi_level_zero, queue> &,
355+
template <backend Backend>
356+
queue make_queue(
357+
const backend_input_t<Backend, queue> &,
335358
const context &Context)
336359
```
337360
</td>
@@ -346,9 +369,9 @@ the ```compute_index``` property which is built into the command queue or comman
346369
<td>
347370
348371
``` C++
349-
make_event<backend::ext_oneapi_level_zero>(
350-
const backend_input_t<
351-
backend::ext_oneapi_level_zero, event> &,
372+
template <backend Backend>
373+
event make_event(
374+
const backend_input_t<Backend, event> &,
352375
const context &Context)
353376
```
354377
</td>
@@ -357,11 +380,11 @@ make_event<backend::ext_oneapi_level_zero>(
357380
<td>
358381

359382
``` C++
360-
make_kernel_bundle<backend::ext_oneapi_level_zero,
361-
bundle_state::executable>(
362-
const backend_input_t<
363-
backend::ext_oneapi_level_zero,
364-
kernel_bundle<bundle_state::executable>> &,
383+
// State must be bundle_state::executable
384+
template <backend Backend, bundle_state State>
385+
kernel_bundle<State> make_kernel_bundle(
386+
const backend_input_t<Backend,
387+
kernel_bundle<State>> &,
365388
const context &Context)
366389
```
367390
</td>
@@ -383,9 +406,9 @@ interoperability <code>kernel_bundle</code> destructor is called.</td>
383406
<td>
384407
385408
``` C++
386-
make_kernel<backend::ext_oneapi_level_zero>(
387-
const backend_input_t<
388-
backend::ext_oneapi_level_zero, kernel> &,
409+
template <backend Backend>
410+
kernel make_kernel(
411+
const backend_input_t<Backend, kernel> &,
389412
const context &Context)
390413
```
391414
</td>
@@ -405,9 +428,15 @@ Level-Zero kernel</td>
405428
<td>
406429

407430
``` C++
408-
make_buffer(
409-
const backend_input_t<backend::ext_oneapi_level_zero,
410-
buffer<T, Dimensions, AllocatorT>> &,
431+
template <backend Backend,
432+
typename T, int Dimensions = 1,
433+
typename AllocatorT =
434+
buffer_allocator<std::remove_const_t<T>>>
435+
buffer<T, Dimensions, AllocatorT> make_buffer(
436+
const backend_input_t<Backend,
437+
buffer<T,
438+
Dimensions,
439+
AllocatorT>> &,
411440
const context &Context)
412441
```
413442
</td>
@@ -421,9 +450,15 @@ Synchronization rules for a buffer that is created with this API are described i
421450
<td>
422451
423452
``` C++
424-
make_buffer(
425-
const backend_input_t<backend::ext_oneapi_level_zero,
426-
buffer<T, Dimensions, AllocatorT>> &,
453+
template <backend Backend,
454+
typename T, int Dimensions = 1,
455+
typename AllocatorT =
456+
buffer_allocator<std::remove_const_t<T>>>
457+
buffer<T, Dimensions, AllocatorT> make_buffer(
458+
const backend_input_t<Backend,
459+
buffer<T,
460+
Dimensions,
461+
AllocatorT>> &,
427462
const context &Context, event AvailableEvent)
428463
```
429464
</td>
@@ -433,6 +468,94 @@ Construct a SYCL buffer instance from a pointer to a Level Zero memory allocatio
433468
description above for semantics and restrictions.
434469
The additional <code>AvailableEvent</code> argument must be a valid SYCL event. The instance of the SYCL buffer class template being constructed must wait for the SYCL event parameter to signal that the memory native handle is ready to be used.
435470
</tr>
471+
472+
<tr>
473+
<td>
474+
475+
``` C++
476+
template<backend Backend, int Dimensions = 1,
477+
typename AllocrT = sycl::image_allocator>
478+
image<Dimensions, AllocrT> make_image(
479+
const backend_input_t<Backend,
480+
image<Dimensions,
481+
AllocrT>> &backendObject,
482+
const context &targetContext);
483+
```
484+
</td>
485+
<td>This API is available starting with revision 5 of this specification.
486+
487+
Construct a SYCL image instance from a ze_image_handle_t.
488+
489+
Because LevelZero has no way of getting image information from an image, it must be provided. The <code>backend_input_t</code> is a struct type like so:
490+
``` C++
491+
struct type {
492+
ze_image_handle_t ZeImageHandle;
493+
sycl::image_channel_order ChanOrder;
494+
sycl::image_channel_type ChanType;
495+
sycl::range<Dimensions> Range;
496+
ext::oneapi::level_zero::ownership Ownership{
497+
ext::oneapi::level_zero::ownership::transfer};
498+
};
499+
```
500+
where the Range should be ordered (width), (width, height), or (width, height, depth) for 1D, 2D and 3D images respectively,
501+
with those values matching the dimensions used in the `ze_image_desc` that was used to create the `ze_image_handle_t` initially.
502+
Note that the range term ordering (width first, depth last) is true for SYCL 1.2.1 images that are supported here. But future classes like
503+
sampled_image and unsampled_image might have a different ordering.
504+
505+
Example Usage
506+
``` C++
507+
ze_image_handle_t ZeHImage;
508+
// ... user provided LevelZero ZeHImage image
509+
// handle gotten somehow (possibly zeImageCreate)
510+
511+
// the informational data that matches ZeHImage
512+
sycl::image_channel_order ChanOrder
513+
= sycl::image_channel_order::rgba;
514+
sycl::image_channel_type ChanType
515+
= sycl::image_channel_type::unsigned_int8;
516+
size_t width = 4;
517+
size_t height = 2;
518+
sycl::range<2> ImgRange_2D(width, height);
519+
520+
constexpr sycl::backend BE
521+
= sycl::backend::ext_oneapi_level_zero;
522+
sycl::backend_input_t<BE, sycl::image<2>> ImageInteropInput{
523+
ZeHImage,
524+
ChanOrder,
525+
ChanType,
526+
ImgRange_2D,
527+
sycl::ext::oneapi::level_zero::ownership::transfer };
528+
529+
sycl::image<2> Image_2D
530+
= sycl::make_image<BE, 2>(ImageInteropInput, Context);
531+
```
532+
533+
The image can only be used on the single device where it was created. This limitation may be relaxed in the future.
534+
The <code>Context</code> argument must be a valid SYCL context encapsulating a Level-Zero context, and the Level-Zero image must have been created on the same context. The created SYCL image can only be accessed from kernels that are submitted to a queue using this same context.
535+
The <code>Ownership</code> input structure member specifies if the SYCL runtime should take ownership of the passed native handle. The default behavior is to transfer the ownership to the SYCL runtime. See section 4.4 for details. If the behavior is "transfer" then the SYCL runtime is going to free the input Level-Zero memory allocation, meaning the memory will be freed when the ~image destructor fires. When using "transfer" the ~image destructor may not need to block. If the behavior is "keep", then the memory will not be freed by the ~image destructor, and the ~image destructor blocks until all work in the queues on the image have been completed. When using "keep" it is the responsibility of the caller to free the memory appropriately.
536+
</td>
537+
</tr>
538+
539+
<tr>
540+
<td>
541+
542+
``` C++
543+
template<backend Backend, int Dimensions = 1,
544+
typename AllocrT = sycl::image_allocator>
545+
image<Dimensions, AllocrT> make_image(
546+
const backend_input_t<Backend,
547+
image<Dimensions,
548+
AllocrT>> &backendObject,
549+
const context &targetContext, event availableEvent);
550+
```
551+
</td>
552+
<td>This API is available starting with revision 5 of this specification.
553+
554+
Construct a SYCL image instance from a pointer to a Level Zero memory allocation. Please refer to <code>make_image</code>
555+
description above for semantics and restrictions.
556+
The additional <code>AvailableEvent</code> argument must be a valid SYCL event. The instance of the SYCL image class template being constructed must wait for the SYCL event parameter to signal that the memory native handle is ready to be used.
557+
</td>
558+
</tr>
436559
</table>
437560

438561
NOTE: We shall consider adding other interoperability as needed, if possible.
@@ -509,4 +632,5 @@ The behavior of the SYCL buffer destructor depends on the Ownership flag. As wit
509632
|8|2022-01-06|Artur Gainullin|Introduced make_buffer() API
510633
|9|2022-05-12|Steffen Larsen|Added device member to queue input type
511634
|10|2022-08-18|Sergey Maslov|Moved free_memory device info query to be sycl_ext_intel_device_info extension
512-
|10|2023-03-14|Rajiv Deodhar|Added support for Level Zero immediate command lists
635+
|11|2023-03-14|Rajiv Deodhar|Added support for Level Zero immediate command lists
636+
|12|2023-04-06|Chris Perkins|Introduced make_image() API

sycl/include/sycl/backend.hpp

+15
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <sycl/context.hpp>
1515
#include <sycl/detail/backend_traits.hpp>
1616
#include <sycl/feature_test.hpp>
17+
#include <sycl/image.hpp>
1718
#if SYCL_BACKEND_OPENCL
1819
#include <sycl/detail/backend_traits_opencl.hpp>
1920
#endif
@@ -335,6 +336,20 @@ make_buffer(const typename backend_traits<Backend>::template input_type<
335336
AvailableEvent);
336337
}
337338

339+
template <backend Backend, int Dimensions = 1,
340+
typename AllocatorT = image_allocator>
341+
typename std::enable_if<detail::InteropFeatureSupportMap<Backend>::MakeImage ==
342+
true &&
343+
Backend != backend::ext_oneapi_level_zero,
344+
image<Dimensions, AllocatorT>>::type
345+
make_image(const typename backend_traits<Backend>::template input_type<
346+
image<Dimensions, AllocatorT>> &BackendObject,
347+
const context &TargetContext, event AvailableEvent = {}) {
348+
return image<Dimensions, AllocatorT>(
349+
detail::pi::cast<pi_native_handle>(BackendObject), TargetContext,
350+
AvailableEvent);
351+
}
352+
338353
template <backend Backend>
339354
kernel
340355
make_kernel(const typename backend_traits<Backend>::template input_type<kernel>

sycl/include/sycl/detail/backend_traits.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ template <backend Backend> struct InteropFeatureSupportMap {
2929
static constexpr bool MakeEvent = false;
3030
static constexpr bool MakeBuffer = false;
3131
static constexpr bool MakeKernel = false;
32+
static constexpr bool MakeKernelBundle = false;
33+
static constexpr bool MakeImage = false;
3234
};
3335
} // namespace detail
3436
} // __SYCL_INLINE_VER_NAMESPACE(_V1)

sycl/include/sycl/detail/backend_traits_level_zero.hpp

+22
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,27 @@ struct BackendReturn<backend::ext_oneapi_level_zero,
165165
using type = void *;
166166
};
167167

168+
template <int Dimensions, typename AllocatorT>
169+
struct BackendInput<backend::ext_oneapi_level_zero,
170+
image<Dimensions, AllocatorT>> {
171+
// LevelZero has no way of getting image description FROM a ZeImageHandle so
172+
// it must be provided.
173+
struct type {
174+
ze_image_handle_t ZeImageHandle;
175+
sycl::image_channel_order ChanOrder;
176+
sycl::image_channel_type ChanType;
177+
range<Dimensions> Range;
178+
ext::oneapi::level_zero::ownership Ownership{
179+
ext::oneapi::level_zero::ownership::transfer};
180+
};
181+
};
182+
183+
template <int Dimensions, typename AllocatorT>
184+
struct BackendReturn<backend::ext_oneapi_level_zero,
185+
image<Dimensions, AllocatorT>> {
186+
using type = ze_image_handle_t;
187+
};
188+
168189
template <> struct BackendReturn<backend::ext_oneapi_level_zero, queue> {
169190
using type =
170191
std::variant<ze_command_queue_handle_t, ze_command_list_handle_t>;
@@ -214,6 +235,7 @@ template <> struct InteropFeatureSupportMap<backend::ext_oneapi_level_zero> {
214235
static constexpr bool MakeKernelBundle = true;
215236
static constexpr bool MakeKernel = true;
216237
static constexpr bool MakeBuffer = true;
238+
static constexpr bool MakeImage = true;
217239
};
218240

219241
} // namespace detail

sycl/include/sycl/detail/backend_traits_opencl.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ template <> struct InteropFeatureSupportMap<backend::opencl> {
155155
static constexpr bool MakeBuffer = true;
156156
static constexpr bool MakeKernel = true;
157157
static constexpr bool MakeKernelBundle = true;
158+
static constexpr bool MakeImage = false;
158159
};
159160

160161
namespace pi {

sycl/include/sycl/detail/pi.def

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ _PI_API(piMemRelease)
6161
_PI_API(piMemBufferPartition)
6262
_PI_API(piextMemGetNativeHandle)
6363
_PI_API(piextMemCreateWithNativeHandle)
64+
_PI_API(piextMemImageCreateWithNativeHandle)
6465
// Program
6566
_PI_API(piProgramCreate)
6667
_PI_API(piclProgramCreateWithSource)

0 commit comments

Comments
 (0)