Skip to content

Commit 2857b03

Browse files
committed
Add IPC example with Level 0 provider
1 parent 9f1fd53 commit 2857b03

File tree

5 files changed

+297
-3
lines changed

5 files changed

+297
-3
lines changed

CMakeLists.txt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -436,9 +436,10 @@ endif()
436436
install(FILES ${CMAKE_SOURCE_DIR}/LICENSE.TXT
437437
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/doc/${PROJECT_NAME}/")
438438

439-
install(FILES examples/basic/gpu_shared_memory.c
440-
examples/basic/utils_level_zero.h examples/basic/basic.c
441-
DESTINATION "${CMAKE_INSTALL_DOCDIR}/examples")
439+
install(
440+
FILES examples/basic/gpu_shared_memory.c examples/basic/utils_level_zero.h
441+
examples/basic/basic.c examples/basic/ipc_level_zero.c
442+
DESTINATION "${CMAKE_INSTALL_DOCDIR}/examples")
442443

443444
# Add the include directory and the headers target to the install.
444445
install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/"

examples/CMakeLists.txt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,41 @@ else()
7070
"UMF_BUILD_LEVEL_ZERO_PROVIDER and UMF_BUILD_LIBUMF_POOL_DISJOINT "
7171
"to be turned ON - skipping")
7272
endif()
73+
74+
if(UMF_BUILD_GPU_EXAMPLES
75+
AND UMF_BUILD_LIBUMF_POOL_DISJOINT
76+
AND UMF_ENABLE_POOL_TRACKING
77+
AND UMF_BUILD_LEVEL_ZERO_PROVIDER)
78+
set(EXAMPLE_NAME umf_example_ipc_level_zero)
79+
80+
add_umf_executable(
81+
NAME ${EXAMPLE_NAME}
82+
SRCS basic/ipc_level_zero.c
83+
LIBS umf disjoint_pool ze_loader)
84+
85+
target_include_directories(
86+
${EXAMPLE_NAME}
87+
PRIVATE ${LEVEL_ZERO_INCLUDE_DIRS} ${UMF_CMAKE_SOURCE_DIR}/src/utils
88+
${UMF_CMAKE_SOURCE_DIR}/include)
89+
90+
target_link_directories(${EXAMPLE_NAME} PRIVATE ${LIBHWLOC_LIBRARY_DIRS})
91+
92+
add_test(
93+
NAME ${EXAMPLE_NAME}
94+
COMMAND ${EXAMPLE_NAME}
95+
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
96+
97+
set_tests_properties(${EXAMPLE_NAME} PROPERTIES LABELS "example")
98+
99+
if(WINDOWS)
100+
# append PATH to DLLs
101+
set_property(TEST ${EXAMPLE_NAME} PROPERTY ENVIRONMENT_MODIFICATION
102+
"${DLL_PATH_LIST}")
103+
endif()
104+
else()
105+
message(
106+
STATUS
107+
"IPC Level 0 example requires UMF_BUILD_GPU_EXAMPLES, "
108+
"UMF_BUILD_LEVEL_ZERO_PROVIDER, UMF_BUILD_LIBUMF_POOL_DISJOINT and "
109+
"UMF_ENABLE_POOL_TRACKING to be turned ON - skipping")
110+
endif()

examples/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,14 @@ cleans up and exits with an error status.
2727
* Level Zero headers and libraries
2828
* compatible GPU with installed driver
2929
* set UMF_BUILD_GPU_EXAMPLES, UMF_BUILD_LIBUMF_POOL_DISJOINT and UMF_BUILD_LEVEL_ZERO_PROVIDER CMake configuration flags to ON
30+
31+
## IPC example with Level Zero memory provider
32+
This example demonstrates how to use UMF IPC API. The example creates two
33+
memory pools of Level Zero device memory: the producer pool (where the buffer
34+
is allocated) and the consumer pool (where the IPC handle is mapped). To run
35+
and build this example Level Zero development package should be installed.
36+
37+
### Requirements
38+
* Level Zero headers and libraries
39+
* compatible GPU with installed driver
40+
* set UMF_BUILD_GPU_EXAMPLES, UMF_BUILD_LIBUMF_POOL_DISJOINT, UMF_BUILD_LEVEL_ZERO_PROVIDER and UMF_ENABLE_POOL_TRACKING CMake configuration flags to ON

examples/basic/ipc_level_zero.c

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
*
3+
* Copyright (C) 2024 Intel Corporation
4+
*
5+
* Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT.
6+
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
*
8+
*/
9+
10+
#include <stdio.h>
11+
12+
#include "umf/ipc.h"
13+
#include "umf/memory_pool.h"
14+
#include "umf/pools/pool_disjoint.h"
15+
#include "umf/providers/provider_level_zero.h"
16+
17+
#include "utils_level_zero.h"
18+
19+
int create_level_zero_pool(ze_context_handle_t context,
20+
ze_device_handle_t device,
21+
umf_memory_pool_handle_t *pool) {
22+
// setup params
23+
level_zero_memory_provider_params_t params = {0};
24+
params.level_zero_context_handle = context;
25+
params.level_zero_device_handle = device;
26+
params.memory_type = UMF_MEMORY_TYPE_DEVICE;
27+
// create Level Zero provider
28+
umf_memory_provider_handle_t provider = 0;
29+
umf_result_t umf_result = umfMemoryProviderCreate(
30+
umfLevelZeroMemoryProviderOps(), &params, &provider);
31+
if (umf_result != UMF_RESULT_SUCCESS) {
32+
fprintf(stderr, "Failed to create Level Zero memory provider!\n");
33+
return -1;
34+
}
35+
36+
// create pool
37+
umf_pool_create_flags_t flags = UMF_POOL_CREATE_FLAG_OWN_PROVIDER;
38+
umf_disjoint_pool_params_t disjoint_params = umfDisjointPoolParamsDefault();
39+
umf_result = umfPoolCreate(umfDisjointPoolOps(), provider, &disjoint_params,
40+
flags, pool);
41+
if (umf_result != UMF_RESULT_SUCCESS) {
42+
fprintf(stderr, "Failed to create pool!\n");
43+
return -1;
44+
}
45+
46+
return 0;
47+
}
48+
49+
int main(void) {
50+
uint32_t driver_idx = 0;
51+
ze_driver_handle_t driver = NULL;
52+
ze_device_handle_t device = NULL;
53+
ze_context_handle_t producer_context = NULL;
54+
ze_context_handle_t consumer_context = NULL;
55+
int ret = init_level_zero();
56+
if (ret != 0) {
57+
fprintf(stderr, "Failed to init Level 0!\n");
58+
return ret;
59+
}
60+
61+
ret = find_driver_with_gpu(&driver_idx, &driver);
62+
if (ret || driver == NULL) {
63+
fprintf(stderr, "Cannot find L0 driver with GPU device!\n");
64+
return ret;
65+
}
66+
67+
ret = create_context(driver, &producer_context);
68+
if (ret != 0) {
69+
fprintf(stderr, "Failed to create L0 context!\n");
70+
return ret;
71+
}
72+
73+
ret = create_context(driver, &consumer_context);
74+
if (ret != 0) {
75+
fprintf(stderr, "Failed to create L0 context!\n");
76+
return ret;
77+
}
78+
79+
ret = find_gpu_device(driver, &device);
80+
if (ret || device == NULL) {
81+
fprintf(stderr, "Cannot find GPU device!\n");
82+
return ret;
83+
}
84+
85+
// create producer pool
86+
umf_memory_pool_handle_t producer_pool = 0;
87+
ret = create_level_zero_pool(producer_context, device, &producer_pool);
88+
if (ret != 0) {
89+
fprintf(stderr, "Failed to create producer pool!\n");
90+
return ret;
91+
}
92+
93+
fprintf(stdout, "Producer pool created.\n");
94+
95+
void *initial_buf = umfPoolMalloc(producer_pool, 1024);
96+
if (!initial_buf) {
97+
fprintf(stderr, "Failed to allocate buffer from UMF pool!\n");
98+
return -1;
99+
}
100+
101+
fprintf(stdout, "Buffer allocated from the producer pool.\n");
102+
103+
umf_ipc_handle_t ipc_handle = NULL;
104+
size_t handle_size = 0;
105+
umf_result_t umf_result =
106+
umfGetIPCHandle(initial_buf, &ipc_handle, &handle_size);
107+
if (umf_result != UMF_RESULT_SUCCESS) {
108+
fprintf(stderr, "Failed to get IPC handle!\n");
109+
return -1;
110+
}
111+
112+
fprintf(stdout, "IPC handle obtained.\n");
113+
114+
// create consumer pool
115+
umf_memory_pool_handle_t consumer_pool = 0;
116+
ret = create_level_zero_pool(consumer_context, device, &consumer_pool);
117+
if (ret != 0) {
118+
fprintf(stderr, "Failed to create consumer pool!\n");
119+
return ret;
120+
}
121+
122+
fprintf(stdout, "Consumer pool created.\n");
123+
124+
void *mapped_buf = NULL;
125+
umf_result = umfOpenIPCHandle(consumer_pool, ipc_handle, &mapped_buf);
126+
if (umf_result != UMF_RESULT_SUCCESS) {
127+
fprintf(stderr, "Failed to open IPC handle!\n");
128+
return -1;
129+
}
130+
131+
fprintf(stdout, "IPC handle opened in the consumer pool.\n");
132+
133+
umf_result = umfPutIPCHandle(ipc_handle);
134+
if (umf_result != UMF_RESULT_SUCCESS) {
135+
fprintf(stderr, "Failed to put IPC handle!\n");
136+
return -1;
137+
}
138+
139+
fprintf(stdout, "IPC handle released in the producer pool.\n");
140+
141+
umf_result = umfCloseIPCHandle(mapped_buf);
142+
if (umf_result != UMF_RESULT_SUCCESS) {
143+
fprintf(stderr, "Failed to close IPC handle!\n");
144+
return -1;
145+
}
146+
147+
fprintf(stdout, "IPC handle closed in the consumer pool.\n");
148+
149+
umfFree(initial_buf);
150+
151+
umfPoolDestroy(producer_pool);
152+
umfPoolDestroy(consumer_pool);
153+
154+
ret = destroy_context(producer_context);
155+
if (ret != 0) {
156+
fprintf(stderr, "Failed to destroy L0 context!\n");
157+
return ret;
158+
}
159+
160+
ret = destroy_context(consumer_context);
161+
if (ret != 0) {
162+
fprintf(stderr, "Failed to destroy L0 context!\n");
163+
return ret;
164+
}
165+
return 0;
166+
}

scripts/docs_config/examples.rst

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,86 @@ in the UMF repository.
127127

128128
TODO
129129

130+
IPC example with Level Zero Memory Provider
131+
==============================================================================
132+
You can find the full example code in the `examples/basic/ipc_level_zero.c`_ file in the UMF repository.
133+
The example demonstrates how to use UMF :ref:`IPC API <ipc-api>`. For demostration purpose the example uses
134+
Level Zero memory provider to instantiate a pool. But the same flow will work with any memory provider that
135+
supports IPC capabilites.
136+
137+
Here we ommit describing how memory pools are created as its orthogonal to the IPC API usage. For more information
138+
on how to create memory pools refer to the previous examples. Also for simplification, our example is single process
139+
while :ref:`IPC API <ipc-api>` targeted for interprocess communication when IPC handle is created by one process
140+
to be used in another process.
141+
142+
To use :ref:`IPC API <ipc-api>` the `umf/ipc.h`_ header should be included.
143+
144+
.. code-block:: c
145+
146+
#include <umf/ipc.h>
147+
148+
To get IPC handle for the memory allocated by UMF the :any:`umfGetIPCHandle` function should be used.
149+
150+
.. code-block:: c
151+
152+
umf_ipc_handle_t ipc_handle = NULL;
153+
size_t handle_size = 0;
154+
umf_result_t umf_result = umfGetIPCHandle(initial_buf, &ipc_handle, &handle_size);
155+
156+
The :any:`umfGetIPCHandle` function requires only the memory pointer as an input parameter and internally determines
157+
the memory pool to which the memory region belongs. While in our example the :any:`umfPoolMalloc` function is called
158+
a few lines before the :any:`umfGetIPCHandle` function is called, in a real application, memory might be allocated even
159+
by a different library and the caller of the :any:`umfGetIPCHandle` function may not know the corresponding memory pool.
160+
161+
The :any:`umfGetIPCHandle` function returns the IPC handle and its size. The IPC handle is a byte-copyable opaque
162+
data structure. The :any:`umf_ipc_handle_t` type is defined as a pointer to a byte array. The size of the handle
163+
might be different for different memory provider types. The code snippet below demostrates how the IPC handle can
164+
be serialized for marshaling purposes.
165+
166+
.. code-block:: c
167+
168+
// Serialize IPC handle
169+
void *serialized_ipc_handle = malloc(handle_size);
170+
memcpy(serialized_ipc_handle, (void*)ipc_handle, handle_size);
171+
172+
.. note::
173+
The method of sending the IPC handle between processes is not defined by the UMF.
174+
175+
When the IPC handle is transfered
176+
to another process it can be opened by the :any:`umfOpenIPCHandle` function.
177+
178+
.. code-block:: c
179+
180+
void *mapped_buf = NULL;
181+
umf_result = umfOpenIPCHandle(consumer_pool, ipc_handle, &mapped_buf);
182+
183+
The :any:`umfOpenIPCHandle` function requires the memory pool handle and the IPC handle as input parameters. It mapps
184+
the handle to the current process address space and returns the pointer to the same memory region that was allocated
185+
in the producer process.
186+
187+
.. note::
188+
The virtual addresses of the memory region referred to by the IPC handle may not be the same in the producer and consumer processes.
189+
190+
To release IPC handle on the producer side the :any:`umfPutIPCHandle` function should be used.
191+
192+
.. code-block:: c
193+
194+
umf_result = umfPutIPCHandle(ipc_handle);
195+
196+
To close IPC handle on the cosuer side the :any:`umfCloseIPCHandle` function should be used.
197+
198+
.. code-block:: c
199+
200+
umf_result = umfCloseIPCHandle(mapped_buf);
201+
202+
The :any:`umfPutIPCHandle` function on the producer side might be called even before the :any:`umfCloseIPCHandle`
203+
function is called on the producer side. The memory mappings on the consumer side remains valid until
204+
the :any:`umfCloseIPCHandle` function is called.
205+
130206
.. _examples/basic/basic.c: https://github.com/oneapi-src/unified-memory-framework/blob/main/examples/basic/basic.c
131207
.. _examples/basic/gpu_shared_memory.c: https://github.com/oneapi-src/unified-memory-framework/blob/main/examples/basic/gpu_shared_memory.c
208+
.. _examples/basic/ipc_level_zero.c: https://github.com/oneapi-src/unified-memory-framework/blob/main/examples/basic/ipc_level_zero.c
132209
.. _README: https://github.com/oneapi-src/unified-memory-framework/blob/main/README.md#memory-pool-managers
210+
.. _umf/ipc.h: https://github.com/oneapi-src/unified-memory-framework/blob/main/include/umf/ipc.h
133211
.. _provider_os_memory.h: https://github.com/oneapi-src/unified-memory-framework/blob/main/include/umf/providers/provider_os_memory.h
134212
.. _pool_scalable.h: https://github.com/oneapi-src/unified-memory-framework/blob/main/include/umf/pools/pool_scalable.h

0 commit comments

Comments
 (0)