Skip to content

Commit d73a42b

Browse files
authored
[UR][L0v2] Implement IPC event sharing (#22445)
Adds the producer/consumer side of UR's IPC event extension on top of Level Zero's counter-based event IPC APIs (zeEventCounterBased{Create,Get/Open/CloseIpcHandle}). * Adjusted urEventCreateExp for ipc events on top of changes introduced by reusable-event feature. * IPC events are not pooled. The primary use case is reusable IPC events, where the user reuses one event many times, so pooling buys little. And sharing pooled events across processes is unsafe because a recycled event hands one process state another process is still relying on. * UR_DEVICE_INFO_IPC_EVENT_SUPPORT_EXP is gated on v2/Linux, BMG+, and latest driver - this might need to be revised in the future. Conformance tests signal the producer event via APIs introduced by the reusable-event extension. Assisted-By: Claude
1 parent 72b423a commit d73a42b

14 files changed

Lines changed: 482 additions & 39 deletions

File tree

unified-runtime/source/adapters/level_zero/device.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,6 +1396,19 @@ ur_result_t urDeviceGetInfo(
13961396
#else
13971397
return ReturnValue(false);
13981398
#endif
1399+
case UR_DEVICE_INFO_IPC_EVENT_SUPPORT_EXP: {
1400+
#if defined(UR_ADAPTER_LEVEL_ZERO_V2) && defined(__linux__)
1401+
constexpr uint32_t MinDriverBuild = 38646;
1402+
ZeStruct<ze_driver_properties_t> ZeDriverProperties;
1403+
ZE2UR_CALL(zeDriverGetProperties,
1404+
(Device->Platform->ZeDriver, &ZeDriverProperties));
1405+
const uint32_t DriverBuild = ZeDriverProperties.driverVersion & 0xFFFF;
1406+
return ReturnValue(static_cast<ur_bool_t>(Device->isBMGOrNewer() &&
1407+
DriverBuild >= MinDriverBuild));
1408+
#else
1409+
return ReturnValue(false);
1410+
#endif
1411+
}
13991412
case UR_DEVICE_INFO_ASYNC_BARRIER:
14001413
return ReturnValue(false);
14011414
case UR_DEVICE_INFO_HOST_PIPE_READ_WRITE_SUPPORT:

unified-runtime/source/adapters/level_zero/device.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,11 @@ struct ur_device_handle_t_ : ur_object {
209209
ZeDeviceIpVersionExt->ipVersion >= 0x030f0000);
210210
}
211211

212+
bool isBMGOrNewer() {
213+
return (ZeDeviceProperties->vendorId == 0x8086 &&
214+
ZeDeviceIpVersionExt->ipVersion >= 0x05004000);
215+
}
216+
212217
bool isIntegrated() {
213218
return (ZeDeviceProperties->flags & ZE_DEVICE_PROPERTY_FLAG_INTEGRATED);
214219
}

unified-runtime/source/adapters/level_zero/v2/common.hpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,14 @@ struct ze_handle_wrapper {
9595
bool ownZeHandle;
9696
};
9797

98+
#define HANDLE_WRAPPER_TYPE_NAMED(Alias, ZeHandleT, DestroyFunc) \
99+
inline constexpr char Alias##_destroyName[] = #DestroyFunc; \
100+
using Alias = \
101+
ze_handle_wrapper<::ZeHandleT, DestroyFunc, Alias##_destroyName>;
102+
103+
// Common case: the alias name matches the raw handle type name.
98104
#define HANDLE_WRAPPER_TYPE(ZeHandleT, DestroyFunc) \
99-
inline constexpr char ZeHandleT##_destroyName[] = #DestroyFunc; \
100-
using ZeHandleT = \
101-
ze_handle_wrapper<::ZeHandleT, DestroyFunc, ZeHandleT##_destroyName>;
105+
HANDLE_WRAPPER_TYPE_NAMED(ZeHandleT, ZeHandleT, DestroyFunc)
102106

103107
HANDLE_WRAPPER_TYPE(ze_kernel_handle_t, zeKernelDestroy)
104108
HANDLE_WRAPPER_TYPE(ze_event_handle_t, zeEventDestroy)
@@ -107,6 +111,12 @@ HANDLE_WRAPPER_TYPE(ze_context_handle_t, zeContextDestroy)
107111
HANDLE_WRAPPER_TYPE(ze_command_list_handle_t, zeCommandListDestroy)
108112
HANDLE_WRAPPER_TYPE(ze_image_handle_t, zeImageDestroy)
109113

114+
// Counter-based event IPC handle opened via zeEventCounterBasedOpenIpcHandle.
115+
// Same underlying handle type as ze_event_handle_t but a different teardown
116+
// function, hence a distinct alias.
117+
HANDLE_WRAPPER_TYPE_NAMED(ipc_event_handle_t, ze_event_handle_t,
118+
zeEventCounterBasedCloseIpcHandle)
119+
110120
template <typename RawHandle, ur_result_t (*retain)(RawHandle),
111121
ur_result_t (*release)(RawHandle)>
112122
struct ur_handle {

unified-runtime/source/adapters/level_zero/v2/event.cpp

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "queue_api.hpp"
1818
#include "queue_handle.hpp"
1919

20+
#include "../device.hpp"
2021
#include "../ur_interface_loader.hpp"
2122

2223
static uint64_t adjustEndEventTimestamp(uint64_t adjustedStartTimestamp,
@@ -122,7 +123,7 @@ void ur_event_handle_t_::setQueue(ur_queue_t_ *hQueue) {
122123
}
123124

124125
ur_event_handle_t_::ur_event_handle_t_(ur_context_handle_t hContext,
125-
v2::raii::ze_event_handle_t hZeEvent,
126+
event_variant hZeEvent,
126127
v2::event_flags_t flags)
127128
: ur_event_handle_t_(hContext, std::move(hZeEvent), flags, nullptr) {}
128129

@@ -186,6 +187,7 @@ struct event_teardown {
186187
}
187188

188189
void operator()(const v2::raii::ze_event_handle_t &) { delete event; }
190+
void operator()(const v2::raii::ipc_event_handle_t &) { delete event; }
189191
};
190192

191193
ur_result_t ur_event_handle_t_::release() {
@@ -204,6 +206,14 @@ bool ur_event_handle_t_::isProfilingEnabled() const {
204206
return flags & v2::EVENT_FLAGS_PROFILING_ENABLED;
205207
}
206208

209+
bool ur_event_handle_t_::isIpcCapable() const {
210+
return flags & v2::EVENT_FLAGS_IPC;
211+
}
212+
213+
bool ur_event_handle_t_::isIpcImported() const {
214+
return flags & v2::EVENT_FLAGS_IPC_IMPORTED;
215+
}
216+
207217
std::pair<uint64_t *, ze_event_handle_t>
208218
ur_event_handle_t_::getEventEndTimestampAndHandle() {
209219
return {profilingData.eventEndTimestampAddr(), getZeEvent()};
@@ -427,10 +437,10 @@ ur_result_t urEventCreateExp(ur_context_handle_t hContext,
427437
ur_device_handle_t hDevice,
428438
const ur_exp_event_desc_t *pEventDesc,
429439
ur_event_handle_t *phEvent) try {
430-
if (!hContext || !hDevice)
431-
return UR_RESULT_ERROR_INVALID_NULL_HANDLE;
432-
if (!pEventDesc || !phEvent)
433-
return UR_RESULT_ERROR_INVALID_NULL_POINTER;
440+
UR_ASSERT(hContext && hDevice, UR_RESULT_ERROR_INVALID_NULL_HANDLE);
441+
UR_ASSERT(pEventDesc && phEvent, UR_RESULT_ERROR_INVALID_NULL_POINTER);
442+
UR_ASSERT(!(pEventDesc->flags & UR_EXP_EVENT_FLAGS_MASK),
443+
UR_RESULT_ERROR_INVALID_ENUMERATION);
434444

435445
const v2::event_flags_t flags =
436446
v2::EVENT_FLAGS_COUNTER |
@@ -439,26 +449,18 @@ ur_result_t urEventCreateExp(ur_context_handle_t hContext,
439449
: 0) |
440450
(pEventDesc->flags & UR_EXP_EVENT_FLAG_IPC_EXP ? v2::EVENT_FLAGS_IPC : 0);
441451

442-
if (flags & v2::EVENT_FLAGS_IPC && flags & v2::EVENT_FLAGS_PROFILING_ENABLED)
443-
return UR_RESULT_ERROR_INVALID_ENUMERATION;
452+
UR_ASSERT(!(flags & v2::EVENT_FLAGS_IPC &&
453+
flags & v2::EVENT_FLAGS_PROFILING_ENABLED),
454+
UR_RESULT_ERROR_INVALID_VALUE);
444455

445456
auto eventPool =
446457
hContext->getReusableEventPoolCache().borrow(hDevice->Id.value(), flags);
447458
assert(eventPool);
448459

449-
if (!(flags & v2::EVENT_FLAGS_IPC)) {
450-
*phEvent = eventPool->allocate();
451-
return UR_RESULT_SUCCESS;
452-
}
453-
454-
v2::raii::cache_borrowed_event borrowed =
455-
eventPool->getProvider()->allocate();
456-
v2::raii::ze_event_handle_t ownedEvent(borrowed.release(),
457-
/*ownZeHandle=*/true);
458-
*phEvent = new ur_event_handle_t_(hContext, std::move(ownedEvent), flags);
459-
460-
// Handle IPC event creation specifics here...
461-
460+
// IPC events must not be recycled (their native handle may outlive this
461+
// process's reference), so they get a detached, self-owning event.
462+
*phEvent = (flags & v2::EVENT_FLAGS_IPC) ? eventPool->allocateDetached()
463+
: eventPool->allocate();
462464
return UR_RESULT_SUCCESS;
463465
} catch (...) {
464466
return exceptionToResult(std::current_exception());

unified-runtime/source/adapters/level_zero/v2/event.hpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,11 @@ struct ur_event_handle_t_ : ur_object {
6060
// - cache_borrowed_event: pooled event; it is returned to the pool.
6161
// - ze_event_handle_t: standalone/native event; the wrapper's ownZeHandle
6262
// flag controls whether zeEventDestroy runs on destruction.
63+
// - ipc_event_handle_t: opened from an IPC handle in another process;
64+
// torn down via zeEventCounterBasedCloseIpcHandle.
6365
using event_variant =
64-
std::variant<v2::raii::cache_borrowed_event, v2::raii::ze_event_handle_t>;
66+
std::variant<v2::raii::cache_borrowed_event, v2::raii::ze_event_handle_t,
67+
v2::raii::ipc_event_handle_t>;
6568

6669
ur_event_handle_t_(ur_context_handle_t hContext,
6770
v2::raii::cache_borrowed_event eventAllocation,
@@ -71,8 +74,7 @@ struct ur_event_handle_t_ : ur_object {
7174
ur_native_handle_t hNativeEvent,
7275
const ur_event_native_properties_t *pProperties);
7376

74-
ur_event_handle_t_(ur_context_handle_t hContext,
75-
v2::raii::ze_event_handle_t hZeEvent,
77+
ur_event_handle_t_(ur_context_handle_t hContext, event_variant hZeEvent,
7678
v2::event_flags_t flags);
7779

7880
// Set the queue and command that this event is associated with
@@ -105,6 +107,12 @@ struct ur_event_handle_t_ : ur_object {
105107
// Tells if this event comes from a pool that has profiling enabled.
106108
bool isProfilingEnabled() const;
107109

110+
// True for IPC-shareable events.
111+
bool isIpcCapable() const;
112+
113+
// True for events opened via urIPCOpenEventHandleExp.
114+
bool isIpcImported() const;
115+
108116
// Queue associated with this event. Can be nullptr (for native events)
109117
ur_queue_t_ *getQueue() const;
110118

unified-runtime/source/adapters/level_zero/v2/event_pool.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ ur_event_handle_t event_pool::allocate() {
4242
return event;
4343
}
4444

45+
ur_event_handle_t event_pool::allocateDetached() {
46+
TRACK_SCOPE_LATENCY("event_pool::allocateDetached");
47+
raii::ze_event_handle_t ownedEvent(provider->allocate().release(),
48+
/*ownZeHandle=*/true);
49+
return new ur_event_handle_t_(hContext, std::move(ownedEvent),
50+
provider->eventFlags());
51+
}
52+
4553
void event_pool::free(ur_event_handle_t event) {
4654
TRACK_SCOPE_LATENCY("event_pool::free");
4755

unified-runtime/source/adapters/level_zero/v2/event_pool.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ class event_pool {
4343
// Allocate an event from the pool. Thread safe.
4444
ur_event_handle_t allocate();
4545

46+
// Allocate a detached event that owns its underlying ze_event_handle_t and is
47+
// never returned to the pool for recycling, it is destroyed when released.
48+
// Used for events that must not be recycled. Thread safe.
49+
ur_event_handle_t allocateDetached();
50+
4651
// Free an event back to the pool. Thread safe.
4752
void free(ur_event_handle_t event);
4853

unified-runtime/source/adapters/level_zero/v2/event_provider.hpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,14 @@ using event_flags_t = uint32_t;
2424
enum event_flag_t {
2525
EVENT_FLAGS_COUNTER = UR_BIT(0),
2626
EVENT_FLAGS_PROFILING_ENABLED = UR_BIT(1),
27-
EVENT_FLAGS_IPC = UR_BIT(2)
27+
// IPC-shareable producer event.
28+
EVENT_FLAGS_IPC = UR_BIT(2),
29+
// Event opened from an IPC handle.
30+
EVENT_FLAGS_IPC_IMPORTED = UR_BIT(3),
2831
};
32+
// Number of flag bits that index into event_pool_cache.
33+
// EVENT_FLAGS_IPC_IMPORTED is excluded because imported events are created
34+
// directly.
2935
static constexpr size_t EVENT_FLAGS_USED_BITS = 3;
3036

3137
enum queue_type {

unified-runtime/source/adapters/level_zero/v2/event_provider_counter.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ static zex_counter_based_event_exp_flags_t createZeFlags(queue_type queueType,
4949
zeFlags |= ZEX_COUNTER_BASED_EVENT_FLAG_KERNEL_TIMESTAMP;
5050
}
5151

52+
if (flags & EVENT_FLAGS_IPC) {
53+
zeFlags |= ZEX_COUNTER_BASED_EVENT_FLAG_IPC;
54+
}
55+
5256
if (queueType == QUEUE_IMMEDIATE) {
5357
zeFlags |= ZEX_COUNTER_BASED_EVENT_FLAG_IMMEDIATE;
5458
}

unified-runtime/source/adapters/level_zero/v2/ipc_event.cpp

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,89 @@
77
//
88
//===----------------------------------------------------------------------===//
99

10+
#include <memory>
11+
1012
#include <unified-runtime/ur_api.h>
1113

14+
#include "../platform.hpp"
1215
#include "../ur_interface_loader.hpp"
16+
#include "context.hpp"
17+
#include "event.hpp"
1318

1419
namespace ur::level_zero {
1520

16-
ur_result_t urIPCGetEventHandleExp(ur_event_handle_t /*hEvent*/,
17-
void ** /*ppIPCEventHandleData*/,
18-
size_t * /*pIPCEventHandleDataSizeRet*/) {
19-
return UR_RESULT_ERROR_UNSUPPORTED_FEATURE;
21+
namespace {
22+
23+
// Fixed L0 IPC handle size; reported by Get and validated by Open.
24+
constexpr size_t kIpcEventHandleDataSize =
25+
sizeof(ze_ipc_event_counter_based_handle_t);
26+
27+
} // namespace
28+
29+
ur_result_t urIPCGetEventHandleExp(ur_event_handle_t hEvent,
30+
void **ppIPCEventHandleData,
31+
size_t *pIPCEventHandleDataSizeRet) try {
32+
UR_ASSERT(hEvent, UR_RESULT_ERROR_INVALID_NULL_HANDLE);
33+
UR_ASSERT(ppIPCEventHandleData && pIPCEventHandleDataSizeRet,
34+
UR_RESULT_ERROR_INVALID_NULL_POINTER);
35+
36+
std::shared_lock<ur_shared_mutex> lock(hEvent->Mutex);
37+
38+
UR_ASSERT(hEvent->isIpcCapable() && !hEvent->isIpcImported(),
39+
UR_RESULT_ERROR_INVALID_EVENT);
40+
UR_ASSERT(!hEvent->isProfilingEnabled() && !hEvent->isTimestamped(),
41+
UR_RESULT_ERROR_UNSUPPORTED_FEATURE);
42+
43+
auto handle = std::make_unique<ze_ipc_event_counter_based_handle_t>();
44+
ZE2UR_CALL(zeEventCounterBasedGetIpcHandle,
45+
(hEvent->getZeEvent(), handle.get()));
46+
47+
// Caller releases the buffer via urIPCPutEventHandleExp.
48+
*ppIPCEventHandleData = handle.release();
49+
*pIPCEventHandleDataSizeRet = kIpcEventHandleDataSize;
50+
return UR_RESULT_SUCCESS;
51+
} catch (...) {
52+
return exceptionToResult(std::current_exception());
2053
}
2154

2255
ur_result_t urIPCPutEventHandleExp(ur_context_handle_t /*hContext*/,
23-
void * /*pIPCEventHandleData*/) {
24-
return UR_RESULT_ERROR_UNSUPPORTED_FEATURE;
56+
void *pIPCEventHandleData) try {
57+
UR_ASSERT(pIPCEventHandleData, UR_RESULT_ERROR_INVALID_NULL_POINTER);
58+
// Free the buffer allocated by urIPCGetEventHandleExp via RAII.
59+
std::unique_ptr<ze_ipc_event_counter_based_handle_t> owner(
60+
static_cast<ze_ipc_event_counter_based_handle_t *>(pIPCEventHandleData));
61+
return UR_RESULT_SUCCESS;
62+
} catch (...) {
63+
return exceptionToResult(std::current_exception());
2564
}
2665

27-
ur_result_t urIPCOpenEventHandleExp(ur_context_handle_t /*hContext*/,
28-
const void * /*pIPCEventHandleData*/,
29-
size_t /*ipcEventHandleDataSize*/,
30-
ur_event_handle_t * /*phEvent*/) {
31-
return UR_RESULT_ERROR_UNSUPPORTED_FEATURE;
66+
ur_result_t urIPCOpenEventHandleExp(ur_context_handle_t hContext,
67+
const void *pIPCEventHandleData,
68+
size_t ipcEventHandleDataSize,
69+
ur_event_handle_t *phEvent) try {
70+
UR_ASSERT(hContext, UR_RESULT_ERROR_INVALID_NULL_HANDLE);
71+
UR_ASSERT(pIPCEventHandleData && phEvent,
72+
UR_RESULT_ERROR_INVALID_NULL_POINTER);
73+
UR_ASSERT(ipcEventHandleDataSize == kIpcEventHandleDataSize,
74+
UR_RESULT_ERROR_INVALID_VALUE);
75+
76+
// The driver consumes the handle by value.
77+
ze_ipc_event_counter_based_handle_t handle =
78+
*static_cast<const ze_ipc_event_counter_based_handle_t *>(
79+
pIPCEventHandleData);
80+
ze_event_handle_t hZeEvent = nullptr;
81+
ZE2UR_CALL(zeEventCounterBasedOpenIpcHandle,
82+
(hContext->getZeHandle(), handle, &hZeEvent));
83+
84+
// The ipc_event_handle_t variant runs zeEventCounterBasedCloseIpcHandle on
85+
// urEventRelease.
86+
*phEvent =
87+
new ur_event_handle_t_(hContext, v2::raii::ipc_event_handle_t{hZeEvent},
88+
v2::EVENT_FLAGS_COUNTER | v2::EVENT_FLAGS_IPC |
89+
v2::EVENT_FLAGS_IPC_IMPORTED);
90+
return UR_RESULT_SUCCESS;
91+
} catch (...) {
92+
return exceptionToResult(std::current_exception());
3293
}
3394

3495
} // namespace ur::level_zero

0 commit comments

Comments
 (0)