Coverage for cuda/core/_memory/_ipc.pyx: 64.10%
156 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-03 02:41 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-03 02:41 +0000
1# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2#
3# SPDX-License-Identifier: Apache-2.0
5cimport cpython
7from libc.stddef cimport size_t
8from cuda.bindings cimport cydriver
9from cuda.core._memory._buffer cimport Buffer, Buffer_check_open, Buffer_from_deviceptr_handle
10from cuda.core._memory._memory_pool cimport _MemPool, MP_check_open
11from cuda.core._stream cimport Stream, Stream_accept
12from cuda.core._resource_handles cimport (
13 DevicePtrHandle,
14 create_fd_handle,
15 create_mempool_handle_ipc,
16 deviceptr_import_ipc,
17 get_last_error,
18 as_cu,
19 as_py,
20)
22from cuda.core._utils.cuda_utils cimport HANDLE_RETURN
23from cuda.core._utils.cuda_utils import check_multiprocessing_start_method
25import multiprocessing
26import os
27import platform
28import uuid
29import weakref
31__all__ = []
34cdef object registry = weakref.WeakValueDictionary()
37cdef cydriver.CUmemAllocationHandleType IPC_HANDLE_TYPE = \
38 cydriver.CUmemAllocationHandleType.CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR \
39 if platform.system() == "Linux" else \
40 cydriver.CUmemAllocationHandleType.CU_MEM_HANDLE_TYPE_NONE
42cdef is_supported():
43 return IPC_HANDLE_TYPE != cydriver.CUmemAllocationHandleType.CU_MEM_HANDLE_TYPE_NONE 2m 3 4 a 5 b 6 7 8 9 ! e # f $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbg h ubvbwbxbybzbi j k l c d AbBbCbDbEbFbGbHbIbJbKbLbMbNbObPbn QbRbSbW TbUbo p
46cdef class IPCDataForBuffer:
47 """Data members related to sharing memory buffers via IPC."""
48 def __cinit__(self, IPCBufferDescriptor ipc_descriptor, bint is_mapped) -> None:
49 self._ipc_descriptor = ipc_descriptor 1abMNOPefstuvQXRYwZS0T1x2yzABCDEFGHghIJKLqrijklcdnop
50 self._is_mapped = is_mapped 1abMNOPefstuvQXRYwZS0T1x2yzABCDEFGHghIJKLqrijklcdnop
52 @property
53 def ipc_descriptor(self) -> IPCBufferDescriptor:
54 return self._ipc_descriptor 1abMNOPefstuvQXRYwZS0T1x2yzABCDEFGHghIJKLqrijklcdnop
56 @property
57 def is_mapped(self) -> bool:
58 return self._is_mapped 1stuvwxyzABCDEFGHIJKLqr
61cdef class IPCDataForMR:
62 """Data members related to sharing memory resources via IPC."""
63 def __cinit__(self, IPCAllocationHandle alloc_handle, bint is_mapped) -> None:
64 self._alloc_handle = alloc_handle 23 4 a 5 b 6 7 8 9 ! e # f $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbg h ubvbwbxbybzbi j k l c d AbBbCbDbEbFbGbHbIbJbKbLbMbNbObPbn QbRbSbW TbUbo p
65 self._is_mapped = is_mapped 23 4 a 5 b 6 7 8 9 ! e # f $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbg h ubvbwbxbybzbi j k l c d AbBbCbDbEbFbGbHbIbJbKbLbMbNbObPbn QbRbSbW TbUbo p
67 @property
68 def alloc_handle(self) -> IPCAllocationHandle:
69 return self._alloc_handle
71 @property
72 def is_mapped(self) -> bool:
73 return self._is_mapped
75 @property
76 def uuid(self) -> uuid.UUID | None:
77 return getattr(self._alloc_handle, 'uuid', None) 1abUVqrcd
80cdef class IPCBufferDescriptor:
81 """Serializable object describing a buffer that can be shared between processes.
83 Note
84 ----
85 The payload and ``size`` fields are controlled by the exporting peer.
86 Receivers must treat them as untrusted and import only through
87 :meth:`Buffer.from_ipc_descriptor`.
88 """
90 def __init__(self, *arg, **kwargs) -> None:
91 raise RuntimeError("IPCBufferDescriptor objects cannot be instantiated directly. Please use MemoryResource APIs.")
93 @staticmethod
94 def _init(reserved: bytes, size: int) -> IPCBufferDescriptor:
95 cdef IPCBufferDescriptor self = IPCBufferDescriptor.__new__(IPCBufferDescriptor) 2a b M N O P e f VbWbs t u v Q X R Y w Z S 0 T 1 x 2 y z A B C D E F G H g h I J K L q r i j k l c d Xbn Yb(bo )bp
96 self._payload = reserved 2a b M N O P e f VbWbs t u v Q X R Y w Z S 0 T 1 x 2 y z A B C D E F G H g h I J K L q r i j k l c d Xbn Yb(bo )bp
97 self._size = size 2a b M N O P e f VbWbs t u v Q X R Y w Z S 0 T 1 x 2 y z A B C D E F G H g h I J K L q r i j k l c d Xbn Yb(bo )bp
98 return self 2a b M N O P e f VbWbs t u v Q X R Y w Z S 0 T 1 x 2 y z A B C D E F G H g h I J K L q r i j k l c d Xbn Yb(bo )bp
100 def __reduce__(self) -> tuple[object, ...]:
101 return IPCBufferDescriptor._init, (self._payload, self._size) 2a b M N O P e f s t u v Q X R Y w Z S 0 T 1 x 2 y z A B C D E F G H g h I J K L q r i j k l c d (b)b
103 @property
104 def size(self) -> int:
105 return self._size 1n
107 cdef const void* payload_ptr(self) noexcept:
108 """Return the payload as a const void* for C API calls."""
109 return <const void*><const char*>(self._payload)
112cdef inline int IPCAllocationHandle_check_open(IPCAllocationHandle self) except -1:
113 if self._h_fd.get() == NULL: 21b2ba b M N O P e f U V s t u v 3bQ 4b5bR 6b7bw 8b9bS !b#bT $b%bx 'by z A B C D E F G H g h I J K L q r i j k l c d W
114 raise RuntimeError("IPCAllocationHandle has been closed") 1UV
115 return 0 2m 1b2ba b M N O P e f s t u v 3bQ 4b5bR 6b7bw 8b9bS !b#bT $b%bx 'by z A B C D E F G H g h I J K L q r i j k l c d W
118cdef class IPCAllocationHandle:
119 """Shareable handle to an IPC-enabled device memory pool."""
121 def __init__(self, *arg, **kwargs) -> None:
122 raise RuntimeError("IPCAllocationHandle objects cannot be instantiated directly. Please use MemoryResource APIs.")
124 @classmethod
125 def _init(cls, handle: int, uuid: uuid.UUID | None) -> IPCAllocationHandle: # no-cython-lint
126 cdef IPCAllocationHandle self = IPCAllocationHandle.__new__(cls) 23 4 a 5 b 6 7 8 9 ! e # f $ U % V ' ( ) *bZb* + , - . / : ; = ? @ [ ] ^ _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbg h ubvbwbxbybzbi j k l c d AbBbCbDbEbFbGbHbIbJbKbLbMbNbObPbn QbRbSbW TbUbo p
127 if handle < 0: 23 4 a 5 b 6 7 8 9 ! e # f $ U % V ' ( ) *bZb* + , - . / : ; = ? @ [ ] ^ _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbg h ubvbwbxbybzbi j k l c d AbBbCbDbEbFbGbHbIbJbKbLbMbNbObPbn QbRbSbW TbUbo p
128 raise ValueError(f"Invalid allocation handle (fd) {handle}: must be non-negative") 2*b
129 self._h_fd = create_fd_handle(handle) 23 4 a 5 b 6 7 8 9 ! e # f $ U % V ' ( ) Zb* + , - . / : ; = ? @ [ ] ^ _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbg h ubvbwbxbybzbi j k l c d AbBbCbDbEbFbGbHbIbJbKbLbMbNbObPbn QbRbSbW TbUbo p
130 self._uuid = uuid 23 4 a 5 b 6 7 8 9 ! e # f $ U % V ' ( ) Zb* + , - . / : ; = ? @ [ ] ^ _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbg h ubvbwbxbybzbi j k l c d AbBbCbDbEbFbGbHbIbJbKbLbMbNbObPbn QbRbSbW TbUbo p
131 return self 23 4 a 5 b 6 7 8 9 ! e # f $ U % V ' ( ) Zb* + , - . / : ; = ? @ [ ] ^ _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbg h ubvbwbxbybzbi j k l c d AbBbCbDbEbFbGbHbIbJbKbLbMbNbObPbn QbRbSbW TbUbo p
133 cpdef close(self):
134 """Close the handle."""
135 self._h_fd.reset() 2U V Zb
137 @property
138 def is_closed(self) -> bool:
139 """Whether this allocation handle has been closed."""
140 return self._h_fd.get() == NULL 2Zb
142 def __int__(self) -> int:
143 if self._h_fd.get() == NULL: 2U V Zb
144 raise ValueError( 2Zb
145 f"Cannot convert IPCAllocationHandle to int: the handle (id={id(self)}) is closed." 2Zb
146 )
147 return as_py(self._h_fd) 1UV
149 @property
150 def handle(self) -> int:
151 return as_py(self._h_fd) 21b2ba b M N O P e f s t u v 3bQ 4b5bR 6b7bw 8b9bS !b#bT $b%bx 'by z A B C D E F G H g h I J K L q r i j k l c d W
153 @property
154 def uuid(self) -> uuid.UUID:
155 return self._uuid 21b2ba b M N O P e f U V s t u v 3bQ 4b5bR 6b7bw 8b9bS !b#bT $b%bx 'by z A B C D E F G H g h I J K L q r i j k l c d W
158def _reduce_allocation_handle(alloc_handle: IPCAllocationHandle) -> tuple[object, ...]:
159 IPCAllocationHandle_check_open(alloc_handle) 21b2ba b M N O P e f s t u v 3bQ 4b5bR 6b7bw 8b9bS !b#bT $b%bx 'by z A B C D E F G H g h I J K L q r i j k l c d W
160 check_multiprocessing_start_method() 21b2ba b M N O P e f s t u v 3bQ 4b5bR 6b7bw 8b9bS !b#bT $b%bx 'by z A B C D E F G H g h I J K L q r i j k l c d W
161 df = multiprocessing.reduction.DupFd(alloc_handle.handle) 21b2ba b M N O P e f s t u v 3bQ 4b5bR 6b7bw 8b9bS !b#bT $b%bx 'by z A B C D E F G H g h I J K L q r i j k l c d W
162 return _reconstruct_allocation_handle, (type(alloc_handle), df, alloc_handle.uuid) 21b2ba b M N O P e f s t u v 3bQ 4b5bR 6b7bw 8b9bS !b#bT $b%bx 'by z A B C D E F G H g h I J K L q r i j k l c d W
165def _reconstruct_allocation_handle(cls: type, df: object, uuid: uuid.UUID | None) -> IPCAllocationHandle: # no-cython-lint
166 return cls._init(df.detach(), uuid)
169multiprocessing.reduction.register(IPCAllocationHandle, _reduce_allocation_handle)
172# Buffer IPC Implementation
173# -------------------------
174cdef IPCBufferDescriptor Buffer_get_ipc_descriptor(Buffer self):
175 Buffer_check_open(self) 2a b M N O P e f s t u v Q X R Y w Z S 0 T 1 x 2 y z A B C D E F G H g h I J K L q r i j k l c d Xbn Ybo p
176 if not self.memory_resource.is_ipc_enabled: 2a b M N O P e f s t u v Q X R Y w Z S 0 T 1 x 2 y z A B C D E F G H g h I J K L q r i j k l c d Xbn Ybo p
177 raise RuntimeError("Memory resource is not IPC-enabled") 2XbYb
178 cdef cydriver.CUmemPoolPtrExportData data
179 with nogil: 1abMNOPefstuvQXRYwZS0T1x2yzABCDEFGHghIJKLqrijklcdnop
180 HANDLE_RETURN( 1abMNOPefstuvQXRYwZS0T1x2yzABCDEFGHghIJKLqrijklcdnop
181 cydriver.cuMemPoolExportPointer(&data, as_cu(self._h_ptr)) 1abMNOPefstuvQXRYwZS0T1x2yzABCDEFGHghIJKLqrijklcdnop
182 )
183 cdef bytes data_b = cpython.PyBytes_FromStringAndSize( 1abMNOPefstuvQXRYwZS0T1x2yzABCDEFGHghIJKLqrijklcdnop
184 <char*>(data.reserved), sizeof(data.reserved)
185 )
186 return IPCBufferDescriptor._init(data_b, self.size) 1abMNOPefstuvQXRYwZS0T1x2yzABCDEFGHghIJKLqrijklcdnop
188cdef Buffer Buffer_from_ipc_descriptor(
189 cls, _MemPool mr, IPCBufferDescriptor ipc_descriptor, stream
190):
191 """Import a buffer that was exported from another process."""
192 MP_check_open(mr) 2VbWbXbYb
193 if not mr.is_ipc_enabled: 2VbWbXbYb
194 raise RuntimeError("Memory resource is not IPC-enabled") 2XbYb
195 cdef size_t payload_size = len(ipc_descriptor._payload) 2VbWb
196 cdef size_t expected_size = sizeof(cydriver.CUmemPoolPtrExportData) 2VbWb
197 if payload_size < expected_size: 2VbWb
198 raise ValueError( 2VbWb
199 f"IPC buffer descriptor payload is {payload_size} bytes; " 2VbWb
200 f"expected at least {expected_size}" 2VbWb
201 )
202 cdef Stream s = Stream_accept(stream)
203 cdef DevicePtrHandle h_ptr = deviceptr_import_ipc(
204 mr._h_pool,
205 ipc_descriptor.payload_ptr(),
206 s._h_stream
207 )
208 if not h_ptr:
209 HANDLE_RETURN(get_last_error())
210 cdef size_t mapped_size = 0
211 cdef size_t claimed_size = ipc_descriptor.size
212 with nogil:
213 HANDLE_RETURN(cydriver.cuPointerGetAttribute(
214 &mapped_size,
215 cydriver.CU_POINTER_ATTRIBUTE_RANGE_SIZE,
216 as_cu(h_ptr)))
217 if claimed_size > mapped_size:
218 h_ptr.reset()
219 raise ValueError(
220 f"IPC buffer descriptor size ({claimed_size}) exceeds "
221 f"mapped allocation extent ({mapped_size} bytes)"
222 )
223 return Buffer_from_deviceptr_handle(h_ptr, claimed_size, mr, ipc_descriptor)
226# _MemPool IPC Implementation
227# ---------------------------
229cdef _MemPool MP_from_allocation_handle(cls, alloc_handle):
230 if isinstance(alloc_handle, IPCAllocationHandle): 1UV
231 IPCAllocationHandle_check_open(<IPCAllocationHandle>alloc_handle) 1UV
233 # Quick exit for registry hits.
234 uuid = getattr(alloc_handle, 'uuid', None) # no-cython-lint
235 mr = registry.get(uuid)
236 if mr is not None:
237 if not isinstance(mr, cls):
238 raise TypeError(
239 f"Registry contains a {type(mr).__name__} for uuid "
240 f"{uuid}, but {cls.__name__} was requested")
241 MP_check_open(<_MemPool>mr)
242 return mr
244 # Ensure we have an allocation handle. Duplicate the file descriptor, if
245 # necessary.
246 if isinstance(alloc_handle, int):
247 fd = os.dup(alloc_handle)
248 try:
249 alloc_handle = IPCAllocationHandle._init(fd, None)
250 except:
251 os.close(fd)
252 raise
254 # Construct a new mempool.
255 cdef _MemPool self = <_MemPool>(cls.__new__(cls))
256 self._mempool_owned = True
257 cdef int ipc_fd = int(alloc_handle)
258 self._h_pool = create_mempool_handle_ipc(ipc_fd, IPC_HANDLE_TYPE)
259 if not self._h_pool:
260 HANDLE_RETURN(get_last_error())
261 raise RuntimeError(
262 f"Failed to import {cls.__name__} from an allocation handle: "
263 "cuda-core returned an empty memory pool handle without recording a CUDA error. "
264 "This is an internal cuda-core error; please report it with your CUDA driver, "
265 "CUDA Toolkit, and cuda-python versions."
266 )
267 self._ipc_data = IPCDataForMR(alloc_handle, True)
269 # Register it.
270 if uuid is not None:
271 registered = self.register(uuid)
272 assert registered is self
274 return self
277cdef _MemPool MP_from_registry(uuid):
278 cdef _MemPool mr
279 try: 20b
280 mr = registry[uuid] 20b
281 MP_check_open(mr)
282 return mr
283 except KeyError: 20b
284 raise RuntimeError(f"Memory resource {uuid} was not found") from None 20b
287cdef _MemPool MP_register(_MemPool self, uuid):
288 MP_check_open(self) 2U V 0b
289 existing = registry.get(uuid) 2U V 0b
290 if existing is not None: 2U V 0b
291 MP_check_open(<_MemPool>existing)
292 return existing
293 if not self.is_ipc_enabled: 2U V 0b
294 raise RuntimeError("Memory resource is not IPC-enabled") 20b
295 assert self.uuid is None or self.uuid == uuid 1UV
296 registry[uuid] = self 1UV
297 self._ipc_data._alloc_handle._uuid = uuid 1UV
298 return self 1UV
301cdef IPCAllocationHandle MP_export_mempool(_MemPool self):
302 # Note: This is Linux only (int for file descriptor)
303 MP_check_open(self) 23 4 a 5 b 6 7 8 9 ! e # f $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbg h ubvbwbxbybzbi j k l c d AbBbCbDbEbFbGbHbIbJbKbLbMbNbObPbn QbRbSbW TbUbo p
304 cdef int fd
305 with nogil: 23 4 a 5 b 6 7 8 9 ! e # f $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbg h ubvbwbxbybzbi j k l c d AbBbCbDbEbFbGbHbIbJbKbLbMbNbObPbn QbRbSbW TbUbo p
306 HANDLE_RETURN(cydriver.cuMemPoolExportToShareableHandle( 23 4 a 5 b 6 7 8 9 ! e # f $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbg h ubvbwbxbybzbi j k l c d AbBbCbDbEbFbGbHbIbJbKbLbMbNbObPbn QbRbSbW TbUbo p
307 &fd, as_cu(self._h_pool), IPC_HANDLE_TYPE, 0)
308 )
309 try: 23 4 a 5 b 6 7 8 9 ! e # f $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbg h ubvbwbxbybzbi j k l c d AbBbCbDbEbFbGbHbIbJbKbLbMbNbObPbn QbRbSbW TbUbo p
310 return IPCAllocationHandle._init(fd, uuid.uuid4()) 23 4 a 5 b 6 7 8 9 ! e # f $ % ' ( ) * + , - . / : ; = ? @ [ ] ^ _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbg h ubvbwbxbybzbi j k l c d AbBbCbDbEbFbGbHbIbJbKbLbMbNbObPbn QbRbSbW TbUbo p
311 except:
312 os.close(fd)
313 raise