Coverage for cuda/core/_memory/_ipc.pyx: 73.76%
221 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-16 01:23 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-16 01:23 +0000
1# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2#
3# SPDX-License-Identifier: Apache-2.0
5from __future__ import annotations
7cimport cpython
9from libc.stddef cimport size_t
10from cuda.bindings cimport cydriver
11from cuda.core._memory._buffer cimport Buffer, Buffer_check_open, Buffer_from_deviceptr_handle
12from cuda.core._memory._memory_pool cimport _MemPool, MP_check_open
13from cuda.core._stream cimport Stream, Stream_accept
14from cuda.core._rt cimport (
15 DevicePtrHandle,
16 create_fd_handle,
17 create_mempool_handle_ipc,
18 deviceptr_import_ipc,
19 get_last_error,
20 as_cu,
21 as_py,
22)
24from cuda.core._utils.cuda_utils cimport HANDLE_RETURN
25from cuda.core._utils.cuda_utils import check_multiprocessing_start_method
27import multiprocessing
28import operator
29import os
30import platform
31import uuid
32import weakref
33from typing import TYPE_CHECKING, Iterable
35if TYPE_CHECKING:
36 from cuda.core.typing import VirtualMemoryHandleType
38__all__ = []
41cdef object registry = weakref.WeakValueDictionary()
44cdef cydriver.CUmemAllocationHandleType IPC_HANDLE_TYPE = \
45 cydriver.CUmemAllocationHandleType.CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR \
46 if platform.system() == "Linux" else \
47 cydriver.CUmemAllocationHandleType.CU_MEM_HANDLE_TYPE_NONE
49cdef is_supported():
50 return IPC_HANDLE_TYPE != cydriver.CUmemAllocationHandleType.CU_MEM_HANDLE_TYPE_NONE 2obpbe qbf rbsbtbubvbi wbj xb2 ybzba AbBbb CbDbEbFbGbHbIbJbKbLbMbNbObPbQbRbSbTbUbVbWbXbYbZb0b1b2b3b4b5b6b7b8b9b!b#b$b%b'b(b)b*bk l +b,b-b.b/b:bm n o p g h ;b=b?b@b[b]b^b_b`b{b|b}b~bacbcccr dcecfc{ gchcs t
53cdef class IPCDataForBuffer:
54 """Data members related to sharing memory buffers via IPC."""
55 def __cinit__(self, IPCBufferDescriptor ipc_descriptor, bint is_mapped) -> None:
56 self._ipc_descriptor = ipc_descriptor 2e f x y z A i j a B C D E F G H fbI gbJ hbK ibL jbM kbN O P Q R S T U V W k l X Y Z 0 u v 1 | bb} c d ~ cbabm n o p g h r s t
57 self._is_mapped = is_mapped 2e f x y z A i j a B C D E F G H fbI gbJ hbK ibL jbM kbN O P Q R S T U V W k l X Y Z 0 u v 1 | bb} c d ~ cbabm n o p g h r s t
59 @property
60 def ipc_descriptor(self) -> IPCBufferDescriptor:
61 return self._ipc_descriptor 2e f x y z A i j a B C D E F G H fbI gbJ hbK ibL jbM kbN O P Q R S T U V W k l X Y Z 0 u v m n o p g h r s t
63 @property
64 def is_mapped(self) -> bool:
65 return self._is_mapped 2bb~ ab
68cdef class IPCDataForMR:
69 """Data members related to sharing memory resources via IPC."""
70 def __cinit__(self, IPCAllocationHandle alloc_handle, bint is_mapped) -> None:
71 self._alloc_handle = alloc_handle 2obpbe qbf rbsbtbubvbi wbj xb2 ybzba AbBbb CbDbEbFbGbHbIbJbKbLbMbNbObPbQbRbSbTbUbVbWbXbYbZb0b1b2b3b4b5b6b7b8b9b!b#b$b%b'b(b)b*bk l +b,b-b.b/b:bm n o p g h ;b=b?b@b[b]b^b_b`b{b|b}b~bacbcccr dcecfc{ gchcs t
72 self._is_mapped = is_mapped 2obpbe qbf rbsbtbubvbi wbj xb2 ybzba AbBbb CbDbEbFbGbHbIbJbKbLbMbNbObPbQbRbSbTbUbVbWbXbYbZb0b1b2b3b4b5b6b7b8b9b!b#b$b%b'b(b)b*bk l +b,b-b.b/b:bm n o p g h ;b=b?b@b[b]b^b_b`b{b|b}b~bacbcccr dcecfc{ gchcs t
74 @property
75 def alloc_handle(self) -> IPCAllocationHandle:
76 return self._alloc_handle
78 @property
79 def is_mapped(self) -> bool:
80 return self._is_mapped
82 @property
83 def uuid(self) -> uuid.UUID | None:
84 return getattr(self._alloc_handle, 'uuid', None) 1ef45abuvgh
87cdef class IPCBufferDescriptor:
88 """Serializable object describing a buffer that can be shared between processes.
90 Note
91 ----
92 The payload and ``size`` fields are controlled by the exporting peer.
93 Receivers must treat them as untrusted and import only through
94 :meth:`Buffer.from_ipc_descriptor`.
95 """
97 def __init__(self, *arg, **kwargs) -> None:
98 raise RuntimeError("IPCBufferDescriptor objects cannot be instantiated directly. Please use MemoryResource APIs.") 2Ec
100 @staticmethod
101 def _init(reserved: bytes, size: int) -> IPCBufferDescriptor:
102 cdef IPCBufferDescriptor self = IPCBufferDescriptor.__new__(IPCBufferDescriptor) 2e f x y z A i j a mbnblcB C D E F G H fbI gbJ hbK ibL jbM kbN O P Q R S T U V W k l X Y Z 0 u v q m n o p g h icr jcBcs Cct
103 self._payload = reserved 2e f x y z A i j a mbnblcB C D E F G H fbI gbJ hbK ibL jbM kbN O P Q R S T U V W k l X Y Z 0 u v q m n o p g h icr jcBcs Cct
104 self._size = size 2e f x y z A i j a mbnblcB C D E F G H fbI gbJ hbK ibL jbM kbN O P Q R S T U V W k l X Y Z 0 u v q m n o p g h icr jcBcs Cct
105 return self 2e f x y z A i j a mbnblcB C D E F G H fbI gbJ hbK ibL jbM kbN O P Q R S T U V W k l X Y Z 0 u v q m n o p g h icr jcBcs Cct
107 def __reduce__(self) -> tuple[object, ...]:
108 return IPCBufferDescriptor._init, (self._payload, self._size) 2e f x y z A i j B C D E F G H fbI gbJ hbK ibL jbM kbN O P Q R S T U V W k l X Y Z 0 u v m n o p g h BcCc
110 @property
111 def size(self) -> int:
112 return self._size 23 a 1 | bbw c dbd r
114 cdef const void* payload_ptr(self) noexcept:
115 """Return the payload as a const void* for C API calls."""
116 return <const void*><const char*>(self._payload) 1a
119cdef inline int IPCAllocationHandle_check_open(IPCAllocationHandle self) except -1:
120 if self._h_fd.get() == NULL: 2ncoce f x y 6 z A 3 i j 7 2 4 5 b B C 8 D E 9 F G ! pcH qcrcI sctcJ ucvcK wcxcL yczcM Ac# $ % ' ( ) N O * P Q + R S , T U - V W k l . / X Y : Z 0 ; u v = ? @ [ w c m n ] ^ o p _ ` g h {
121 raise RuntimeError("IPCAllocationHandle has been closed") 145c
122 return 0 2ncoce f x y 6 z A 3 i j 7 2 b B C 8 D E 9 F G ! pcH qcrcI sctcJ ucvcK wcxcL yczcM Ac# $ % ' ( ) N O * P Q + R S , T U - V W k l . / X Y : Z 0 ; u v = ? @ [ w c m n ] ^ o p _ ` g h {
125cdef class IPCAllocationHandle:
126 """Shareable OS handle to an IPC-enabled memory pool or to a physical allocation
127 of a :class:`VirtualMemoryResource` buffer."""
129 def __init__(self, *arg, **kwargs) -> None:
130 raise RuntimeError("IPCAllocationHandle objects cannot be instantiated directly. Please use MemoryResource APIs.") 2Ec
132 @classmethod
133 def _init(cls, handle: int, uuid: uuid.UUID | None) -> IPCAllocationHandle: # no-cython-lint
134 cdef IPCAllocationHandle self = IPCAllocationHandle.__new__(cls) 2obpbe qbf rbsbtb6 ubvb3 i wbj xb7 2 4 yb5 zba AbBbDckcb CbDb8 EbFb9 GbHb! IbJbKbLbMbNbObPbQbRbSbTbUbVbWbXbYbZb0b1b2b3b4b5b6b7b# $ % ' ( ) 8b9b* !b#b+ $b%b, 'b(b- )b*bk l . / +b,b: -b.b; /b:b= ? 1 | lb@ [ } w c dbd ~ cbebq abm n ] ^ o p _ ` g h ;b=b?b@b[b]b^b_b`b{b|b}b~bacbcccr dcecfc{ gchcs t
135 if handle < 0: 2obpbe qbf rbsbtb6 ubvb3 i wbj xb7 2 4 yb5 zba AbBbDckcb CbDb8 EbFb9 GbHb! IbJbKbLbMbNbObPbQbRbSbTbUbVbWbXbYbZb0b1b2b3b4b5b6b7b# $ % ' ( ) 8b9b* !b#b+ $b%b, 'b(b- )b*bk l . / +b,b: -b.b; /b:b= ? 1 | lb@ [ } w c dbd ~ cbebq abm n ] ^ o p _ ` g h ;b=b?b@b[b]b^b_b`b{b|b}b~bacbcccr dcecfc{ gchcs t
136 raise ValueError(f"Invalid allocation handle (fd) {handle}: must be non-negative") 2Dc
137 self._h_fd = create_fd_handle(handle) 2obpbe qbf rbsbtb6 ubvb3 i wbj xb7 2 4 yb5 zba AbBbkcb CbDb8 EbFb9 GbHb! IbJbKbLbMbNbObPbQbRbSbTbUbVbWbXbYbZb0b1b2b3b4b5b6b7b# $ % ' ( ) 8b9b* !b#b+ $b%b, 'b(b- )b*bk l . / +b,b: -b.b; /b:b= ? 1 | lb@ [ } w c dbd ~ cbebq abm n ] ^ o p _ ` g h ;b=b?b@b[b]b^b_b`b{b|b}b~bacbcccr dcecfc{ gchcs t
138 self._uuid = uuid 2obpbe qbf rbsbtb6 ubvb3 i wbj xb7 2 4 yb5 zba AbBbkcb CbDb8 EbFb9 GbHb! IbJbKbLbMbNbObPbQbRbSbTbUbVbWbXbYbZb0b1b2b3b4b5b6b7b# $ % ' ( ) 8b9b* !b#b+ $b%b, 'b(b- )b*bk l . / +b,b: -b.b; /b:b= ? 1 | lb@ [ } w c dbd ~ cbebq abm n ] ^ o p _ ` g h ;b=b?b@b[b]b^b_b`b{b|b}b~bacbcccr dcecfc{ gchcs t
139 return self 2obpbe qbf rbsbtb6 ubvb3 i wbj xb7 2 4 yb5 zba AbBbkcb CbDb8 EbFb9 GbHb! IbJbKbLbMbNbObPbQbRbSbTbUbVbWbXbYbZb0b1b2b3b4b5b6b7b# $ % ' ( ) 8b9b* !b#b+ $b%b, 'b(b- )b*bk l . / +b,b: -b.b; /b:b= ? 1 | lb@ [ } w c dbd ~ cbebq abm n ] ^ o p _ ` g h ;b=b?b@b[b]b^b_b`b{b|b}b~bacbcccr dcecfc{ gchcs t
141 cpdef close(self):
142 """Close the handle."""
143 self._h_fd.reset() 22 4 5 kc1 c
145 @property
146 def is_closed(self) -> bool:
147 """Whether this allocation handle has been closed."""
148 return self._h_fd.get() == NULL 2kc
150 def __int__(self) -> int:
151 if self._h_fd.get() == NULL: 24 5 a kcb 1 | lb} w c dbd ~ cbebab
152 raise ValueError( 2kc1
153 f"Cannot convert IPCAllocationHandle to int: the handle (id={id(self)}) is closed." 2kc1
154 )
155 return as_py(self._h_fd) 24 5 a b 1 | lb} w c dbd ~ cbebab
157 @property
158 def handle(self) -> int:
159 return as_py(self._h_fd) 2ncoce f x y 6 z A 3 i j 7 2 a B C 8 D E 9 F G ! pcH qcrcI sctcJ ucvcK wcxcL yczcM Ac# $ % ' ( ) N O * P Q + R S , T U - V W k l . / X Y : Z 0 ; u v = ? @ [ w m n ] ^ o p _ ` g h {
161 @property
162 def uuid(self) -> uuid.UUID:
163 return self._uuid 2ncoce f x y 6 z A 3 i j 7 2 4 5 a b B C 8 D E 9 F G ! pcH qcrcI sctcJ ucvcK wcxcL yczcM Ac# $ % ' ( ) N O * P Q + R S , T U - V W k l . / X Y : Z 0 ; u v = ? @ [ w m n ] ^ o p _ ` g h {
166def _reduce_allocation_handle(IPCAllocationHandle alloc_handle not None: IPCAllocationHandle) -> tuple[object, ...]:
167 IPCAllocationHandle_check_open(alloc_handle) 2ncoce f x y 6 z A 3 i j 7 2 B C 8 D E 9 F G ! pcH qcrcI sctcJ ucvcK wcxcL yczcM Ac# $ % ' ( ) N O * P Q + R S , T U - V W k l . / X Y : Z 0 ; u v = ? @ [ w m n ] ^ o p _ ` g h {
168 check_multiprocessing_start_method() 2ncoce f x y 6 z A 3 i j 7 2 B C 8 D E 9 F G ! pcH qcrcI sctcJ ucvcK wcxcL yczcM Ac# $ % ' ( ) N O * P Q + R S , T U - V W k l . / X Y : Z 0 ; u v = ? @ [ w m n ] ^ o p _ ` g h {
169 df = multiprocessing.reduction.DupFd(alloc_handle.handle) 2ncoce f x y 6 z A 3 i j 7 2 B C 8 D E 9 F G ! pcH qcrcI sctcJ ucvcK wcxcL yczcM Ac# $ % ' ( ) N O * P Q + R S , T U - V W k l . / X Y : Z 0 ; u v = ? @ [ w m n ] ^ o p _ ` g h {
170 return _reconstruct_allocation_handle, (type(alloc_handle), df, alloc_handle.uuid) 2ncoce f x y 6 z A 3 i j 7 2 B C 8 D E 9 F G ! pcH qcrcI sctcJ ucvcK wcxcL yczcM Ac# $ % ' ( ) N O * P Q + R S , T U - V W k l . / X Y : Z 0 ; u v = ? @ [ w m n ] ^ o p _ ` g h {
173def _reconstruct_allocation_handle(cls: type, df: object, uuid: uuid.UUID | None) -> IPCAllocationHandle: # no-cython-lint
174 return cls._init(df.detach(), uuid) 12
177multiprocessing.reduction.register(IPCAllocationHandle, _reduce_allocation_handle)
180cdef class VirtualMemoryIPCBufferDescriptor(IPCBufferDescriptor):
181 """Serializable object describing a :class:`VirtualMemoryBuffer` that can be shared between processes.
183 The descriptor holds one exported POSIX file descriptor per physical
184 allocation that backs the buffer, in address order (``handles``, each an
185 :class:`IPCAllocationHandle` that the descriptor owns and closes when it
186 is released), with each allocation's size (``sizes``) and the buffer's
187 size (``size``, which may be smaller than their sum). ``handle_type`` is
188 ``"posix_fd"``, the only handle type cuda.core shares today; the rules
189 below describe file descriptors.
191 A descriptor pins the physical memory while it exists, in every process
192 that holds one, and nothing else does: the exporting buffer keeps no
193 descriptor, and an imported buffer does not keep the one it came from. A
194 descriptor parked in a ``multiprocessing`` ``Queue`` or sent to a ``Pool``
195 pins the memory in the sender until the receiver has unpickled it; a
196 buffer passed as a ``Process`` argument pins it until the ``Process``
197 object is released, because the child receives the file descriptors when
198 it is spawned.
200 Two transports exist. ``multiprocessing`` (a ``Queue``, a ``Pipe``,
201 ``Process`` arguments, or a ``Pool``) pickles the descriptor, or a buffer,
202 and duplicates the file descriptors into the receiving process; plain
203 ``pickle`` cannot carry them and raises. A process with its own file
204 descriptor passing sends ``fds``, ``sizes``, and ``size`` itself, and the
205 receiver rebuilds the descriptor with :meth:`from_fds`.
207 Note
208 ----
209 The sizes are controlled by the exporting peer. Receivers must treat them
210 as untrusted and import only through :meth:`Buffer.from_ipc_descriptor`,
211 which checks what it can and fails instead of mapping a size the driver
212 rejects.
213 """
215 @staticmethod
216 def _from_exports(
217 handle_type: int, chunk_sizes: tuple[int, ...], handles: tuple, size: int
218 ) -> VirtualMemoryIPCBufferDescriptor:
219 cdef VirtualMemoryIPCBufferDescriptor self = VirtualMemoryIPCBufferDescriptor.__new__( 26 3 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? 1 | bblb@ [ } w c dbd ~ cbebq ab] ^ _ `
220 VirtualMemoryIPCBufferDescriptor)
221 self._payload = b"" 26 3 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? 1 | bblb@ [ } w c dbd ~ cbebq ab] ^ _ `
222 self._size = size 26 3 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? 1 | bblb@ [ } w c dbd ~ cbebq ab] ^ _ `
223 self._handle_type = handle_type 26 3 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? 1 | bblb@ [ } w c dbd ~ cbebq ab] ^ _ `
224 self._chunk_sizes = tuple(chunk_sizes) 26 3 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? 1 | bblb@ [ } w c dbd ~ cbebq ab] ^ _ `
225 self._handles = tuple(handles) 26 3 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? 1 | bblb@ [ } w c dbd ~ cbebq ab] ^ _ `
226 return self 26 3 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? 1 | bblb@ [ } w c dbd ~ cbebq ab] ^ _ `
228 @classmethod
229 def from_fds(
230 cls,
231 fds: Iterable[int | IPCAllocationHandle],
232 sizes: Iterable[int],
233 *,
234 size: int | None = None,
235 ) -> VirtualMemoryIPCBufferDescriptor:
236 """Build a descriptor from POSIX file descriptors received outside ``multiprocessing``.
238 The exporter sends ``fds``, ``sizes``, and ``size`` of its descriptor
239 through a transport of its own (a Unix socket with ``SCM_RIGHTS``, for
240 example); the receiver rebuilds the descriptor here and imports it
241 with :meth:`Buffer.from_ipc_descriptor`. The result's ``handle_type``
242 is ``"posix_fd"``, the only handle type a file descriptor can carry.
244 Parameters
245 ----------
246 fds : Iterable[int | IPCAllocationHandle]
247 One handle per physical allocation, in address order, as the
248 exporter's ``fds`` listed them. An ``int`` is duplicated: the
249 caller keeps ownership of the original and closes it. An
250 :class:`IPCAllocationHandle` is shared and closes with its last
251 holder.
252 sizes : Iterable[int]
253 The exporter's ``sizes``, one per handle.
254 size : int, optional
255 The exporter's ``size``, at most the sum of ``sizes``; the sum by
256 default.
258 Raises
259 ------
260 ValueError
261 If the counts differ, a size is not positive, or ``size`` exceeds
262 the sum of ``sizes``.
263 RuntimeError
264 If an :class:`IPCAllocationHandle` is closed.
265 OSError
266 If an ``int`` is not an open file descriptor.
267 """
268 cdef int driver_type = <int>cydriver.CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR 1cd
269 cdef tuple sizes_t = tuple(operator.index(n) for n in sizes) 1cd
270 cdef list given = list(fds) 1cd
271 if len(given) != len(sizes_t): 1cd
272 raise ValueError(f"{len(given)} handles for {len(sizes_t)} sizes; one size per handle is required") 1c
273 for n in sizes_t: 1cd
274 if n <= 0: 1cd
275 raise ValueError(f"allocation sizes must be positive, got {n}") 1c
276 total = sum(sizes_t) 1cd
277 size = total if size is None else operator.index(size) 1cd
278 if size < 0 or size > total: 1cd
279 raise ValueError(f"size {size} is outside 0..{total}, the sum of the allocation sizes") 1c
280 cdef list handles = [] 1cd
281 cdef int dup
282 for item in given: 1cd
283 if isinstance(item, IPCAllocationHandle): 1cd
284 IPCAllocationHandle_check_open(<IPCAllocationHandle>item) 1c
285 handles.append(item) 1c
286 continue 1c
287 dup = os.dup(operator.index(item)) 1cd
288 try: 1d
289 handle = IPCAllocationHandle._init(dup, None) 1d
290 except: # noqa: E722 rollback-then-raise: the duplicate is not owned yet
291 os.close(dup)
292 raise
293 # The handle owns the duplicate now; if append fails, the handle closes it once.
294 handles.append(handle) 1d
295 return VirtualMemoryIPCBufferDescriptor._from_exports(driver_type, sizes_t, tuple(handles), size) 1cd
297 @property
298 def handle_type(self) -> VirtualMemoryHandleType:
299 """The handle type the exporter used; the importing resource must be configured with the same."""
300 from cuda.core._memory._virtual_memory_resource import VirtualMemoryResourceOptions 1cd
302 for spec, value in VirtualMemoryResourceOptions._handle_types.items(): 1cd
303 if spec is not None and int(value) == self._handle_type: 1cd
304 return spec 1cd
305 raise ValueError(f"the descriptor carries an unknown handle type ({self._handle_type})")
307 @property
308 def sizes(self) -> tuple[int, ...]:
309 """Size of each physical allocation, in address order; the sum is at least ``size``."""
310 return self._chunk_sizes 2bblbc dbd
312 @property
313 def handles(self) -> tuple[IPCAllocationHandle, ...]:
314 """The exported handles, one :class:`IPCAllocationHandle` per allocation, in address order.
316 The descriptor owns them and closes them when it is released.
317 """
318 return self._handles 2bbc d
320 @property
321 def fds(self) -> tuple[int, ...]:
322 """The exported file descriptors as integers, for a transport of your own.
324 They belong to this descriptor: send or duplicate them while it is
325 alive, because each one is closed when the descriptor is released.
326 Keep the descriptor in a variable while you use them; the numbers
327 from ``buf.ipc_descriptor.fds`` alone may already be closed.
328 """
329 return tuple(int(handle) for handle in self._handles) 2bblb} c dbd eb
331 def __reduce__(self) -> tuple[object, ...]:
332 # multiprocessing duplicates the IPCAllocationHandle fds into the receiver; plain pickle raises.
333 return VirtualMemoryIPCBufferDescriptor._from_exports, ( 26 3 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? bb@ [ w eb] ^ _ `
334 self._handle_type, self._chunk_sizes, self._handles, self._size) 26 3 7 8 9 ! # $ % ' ( ) * + , - . / : ; = ? bb@ [ w eb] ^ _ `
337# Buffer IPC Implementation
338# -------------------------
339cdef IPCBufferDescriptor Buffer_get_ipc_descriptor(Buffer self):
340 Buffer_check_open(self) 2e f x y z A i j a B C D E F G H fbI gbJ hbK ibL jbM kbN O P Q R S T U V W k l X Y Z 0 u v q m n o p g h icr jcs t
341 if not self.memory_resource.is_ipc_enabled: 2e f x y z A i j a B C D E F G H fbI gbJ hbK ibL jbM kbN O P Q R S T U V W k l X Y Z 0 u v q m n o p g h icr jcs t
342 raise RuntimeError("Memory resource is not IPC-enabled") 2icjc
343 if not isinstance(self.memory_resource, _MemPool): 2e f x y z A i j a B C D E F G H fbI gbJ hbK ibL jbM kbN O P Q R S T U V W k l X Y Z 0 u v q m n o p g h r s t
344 # VirtualMemoryBuffer overrides ipc_descriptor; a plain Buffer wrapped
345 # with Buffer.from_handle(mr=<VirtualMemoryResource>) lands here.
346 raise TypeError( 1q
347 f"a Buffer that {type(self.memory_resource).__name__} did not allocate cannot be shared") 1q
348 cdef cydriver.CUmemPoolPtrExportData data
349 with nogil: 2e f x y z A i j a B C D E F G H fbI gbJ hbK ibL jbM kbN O P Q R S T U V W k l X Y Z 0 u v m n o p g h r s t
350 HANDLE_RETURN( 2e f x y z A i j a B C D E F G H fbI gbJ hbK ibL jbM kbN O P Q R S T U V W k l X Y Z 0 u v m n o p g h r s t
351 cydriver.cuMemPoolExportPointer(&data, as_cu(self._h_ptr)) 2e f x y z A i j a B C D E F G H fbI gbJ hbK ibL jbM kbN O P Q R S T U V W k l X Y Z 0 u v m n o p g h r s t
352 )
353 cdef bytes data_b = cpython.PyBytes_FromStringAndSize( 2e f x y z A i j a B C D E F G H fbI gbJ hbK ibL jbM kbN O P Q R S T U V W k l X Y Z 0 u v m n o p g h r s t
354 <char*>(data.reserved), sizeof(data.reserved)
355 )
356 return IPCBufferDescriptor._init(data_b, self.size) 2e f x y z A i j a B C D E F G H fbI gbJ hbK ibL jbM kbN O P Q R S T U V W k l X Y Z 0 u v m n o p g h r s t
358cdef Buffer Buffer_from_ipc_descriptor(
359 cls, object mr, IPCBufferDescriptor ipc_descriptor, stream
360):
361 """Import a buffer that was exported from another process."""
362 if isinstance(ipc_descriptor, VirtualMemoryIPCBufferDescriptor): 2a mbnblc1 | bb} c d ~ cbq abicjc
363 # Imported here rather than cimported: _virtual_memory_resource
364 # cimports this module.
365 from cuda.core._memory._virtual_memory_resource import VirtualMemoryResource 21 | bb} c d ~ cbq ab
366 if not isinstance(mr, VirtualMemoryResource): 21 | bb} c d ~ cbq ab
367 raise TypeError( 1q
368 "this descriptor was exported from a VirtualMemoryResource buffer and must be " 1q
369 f"imported with a VirtualMemoryResource, not {type(mr).__name__}") 1q
370 return mr._import_ipc_buffer(ipc_descriptor, stream) 21 | bb} c d ~ cbq ab
371 if not isinstance(mr, _MemPool): 2a mbnblcq icjc
372 raise TypeError( 2lcq
373 "this descriptor was exported from a memory pool buffer and must be imported with a " 2lcq
374 f"DeviceMemoryResource or PinnedMemoryResource, not {type(mr).__name__}") 2lcq
375 cdef _MemPool pool = <_MemPool>mr 2a mbnbicjc
376 MP_check_open(pool) 2a mbnbicjc
377 if not mr.is_ipc_enabled: 2a mbnbicjc
378 raise RuntimeError("Memory resource is not IPC-enabled") 2icjc
379 cdef size_t payload_size = len(ipc_descriptor._payload) 2a mbnb
380 cdef size_t expected_size = sizeof(cydriver.CUmemPoolPtrExportData) 2a mbnb
381 if payload_size < expected_size: 2a mbnb
382 raise ValueError( 2mbnb
383 f"IPC buffer descriptor payload is {payload_size} bytes; " 2mbnb
384 f"expected at least {expected_size}" 2mbnb
385 )
386 cdef Stream s = Stream_accept(stream) 1a
387 cdef DevicePtrHandle h_ptr = deviceptr_import_ipc( 1a
388 pool._h_pool,
389 ipc_descriptor.payload_ptr(),
390 s._h_stream
391 )
392 if not h_ptr: 1a
393 HANDLE_RETURN(get_last_error())
394 cdef size_t mapped_size = 0 1a
395 cdef size_t claimed_size = ipc_descriptor.size 1a
396 with nogil: 1a
397 HANDLE_RETURN(cydriver.cuPointerGetAttribute( 1a
398 &mapped_size,
399 cydriver.CU_POINTER_ATTRIBUTE_RANGE_SIZE,
400 as_cu(h_ptr)))
401 if claimed_size > mapped_size: 1a
402 h_ptr.reset()
403 raise ValueError(
404 f"IPC buffer descriptor size ({claimed_size}) exceeds "
405 f"mapped allocation extent ({mapped_size} bytes)"
406 )
407 return Buffer_from_deviceptr_handle(h_ptr, claimed_size, pool, ipc_descriptor) 1a
410# _MemPool IPC Implementation
411# ---------------------------
413cdef _MemPool MP_from_allocation_handle(cls, alloc_handle):
414 if isinstance(alloc_handle, IPCAllocationHandle): 145ab
415 IPCAllocationHandle_check_open(<IPCAllocationHandle>alloc_handle) 145b
417 # Quick exit for registry hits.
418 uuid = getattr(alloc_handle, 'uuid', None) # no-cython-lint 1ab
419 mr = registry.get(uuid) 1ab
420 if mr is not None: 1ab
421 if not isinstance(mr, cls): 1b
422 raise TypeError(
423 f"Registry contains a {type(mr).__name__} for uuid "
424 f"{uuid}, but {cls.__name__} was requested")
425 MP_check_open(<_MemPool>mr) 1b
426 return mr 1b
428 # Ensure we have an allocation handle. Duplicate the file descriptor, if
429 # necessary.
430 if isinstance(alloc_handle, int): 1ab
431 fd = os.dup(alloc_handle) 1a
432 try: 1a
433 alloc_handle = IPCAllocationHandle._init(fd, None) 1a
434 except:
435 os.close(fd)
436 raise
438 # Construct a new mempool.
439 cdef _MemPool self = <_MemPool>(cls.__new__(cls)) 1ab
440 self._mempool_owned = True 1ab
441 cdef int ipc_fd = int(alloc_handle) 1ab
442 self._h_pool = create_mempool_handle_ipc(ipc_fd, IPC_HANDLE_TYPE) 1ab
443 if not self._h_pool: 1ab
444 HANDLE_RETURN(get_last_error())
445 raise RuntimeError(
446 f"Failed to import {cls.__name__} from an allocation handle: "
447 "cuda-core returned an empty memory pool handle without recording a CUDA error. "
448 "This is an internal cuda-core error; please report it with your CUDA driver, "
449 "CUDA Toolkit, and cuda-python versions."
450 )
451 self._ipc_data = IPCDataForMR(alloc_handle, True) 1ab
453 # Register it.
454 if uuid is not None: 1ab
455 registered = self.register(uuid) 1b
456 assert registered is self 1b
458 return self 1ab
461cdef _MemPool MP_from_registry(uuid):
462 cdef _MemPool mr
463 try: 2b mc
464 mr = registry[uuid] 2b mc
465 MP_check_open(mr) 1b
466 return mr 1b
467 except KeyError: 2mc
468 raise RuntimeError(f"Memory resource {uuid} was not found") from None 2mc
471cdef _MemPool MP_register(_MemPool self, uuid):
472 MP_check_open(self) 24 5 b mc
473 existing = registry.get(uuid) 24 5 b mc
474 if existing is not None: 24 5 b mc
475 MP_check_open(<_MemPool>existing) 1b
476 return existing 1b
477 if not self.is_ipc_enabled: 24 5 b mc
478 raise RuntimeError("Memory resource is not IPC-enabled") 2mc
479 assert self.uuid is None or self.uuid == uuid 145b
480 registry[uuid] = self 145b
481 self._ipc_data._alloc_handle._uuid = uuid 145b
482 return self 145b
485cdef IPCAllocationHandle MP_export_mempool(_MemPool self):
486 # Note: This is Linux only (int for file descriptor)
487 MP_check_open(self) 2obpbe qbf rbsbtbubvbi wbj xb2 ybzba AbBbb CbDbEbFbGbHbIbJbKbLbMbNbObPbQbRbSbTbUbVbWbXbYbZb0b1b2b3b4b5b6b7b8b9b!b#b$b%b'b(b)b*bk l +b,b-b.b/b:bm n o p g h ;b=b?b@b[b]b^b_b`b{b|b}b~bacbcccr dcecfc{ gchcs t
488 cdef int fd
489 with nogil: 2obpbe qbf rbsbtbubvbi wbj xb2 ybzba AbBbb CbDbEbFbGbHbIbJbKbLbMbNbObPbQbRbSbTbUbVbWbXbYbZb0b1b2b3b4b5b6b7b8b9b!b#b$b%b'b(b)b*bk l +b,b-b.b/b:bm n o p g h ;b=b?b@b[b]b^b_b`b{b|b}b~bacbcccr dcecfc{ gchcs t
490 HANDLE_RETURN(cydriver.cuMemPoolExportToShareableHandle( 2obpbe qbf rbsbtbubvbi wbj xb2 ybzba AbBbb CbDbEbFbGbHbIbJbKbLbMbNbObPbQbRbSbTbUbVbWbXbYbZb0b1b2b3b4b5b6b7b8b9b!b#b$b%b'b(b)b*bk l +b,b-b.b/b:bm n o p g h ;b=b?b@b[b]b^b_b`b{b|b}b~bacbcccr dcecfc{ gchcs t
491 &fd, as_cu(self._h_pool), IPC_HANDLE_TYPE, 0)
492 )
493 try: 2obpbe qbf rbsbtbubvbi wbj xb2 ybzba AbBbb CbDbEbFbGbHbIbJbKbLbMbNbObPbQbRbSbTbUbVbWbXbYbZb0b1b2b3b4b5b6b7b8b9b!b#b$b%b'b(b)b*bk l +b,b-b.b/b:bm n o p g h ;b=b?b@b[b]b^b_b`b{b|b}b~bacbcccr dcecfc{ gchcs t
494 return IPCAllocationHandle._init(fd, uuid.uuid4()) 2obpbe qbf rbsbtbubvbi wbj xb2 ybzba AbBbb CbDbEbFbGbHbIbJbKbLbMbNbObPbQbRbSbTbUbVbWbXbYbZb0b1b2b3b4b5b6b7b8b9b!b#b$b%b'b(b)b*bk l +b,b-b.b/b:bm n o p g h ;b=b?b@b[b]b^b_b`b{b|b}b~bacbcccr dcecfc{ gchcs t
495 except:
496 os.close(fd)
497 raise