Coverage for cuda/core/_memory/_virtual_memory_resource.pyx: 73.78%
473 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
7from libc.stdint cimport uintptr_t
8from libc.string cimport memset
9from libcpp.vector cimport vector
11from cuda.bindings cimport cydriver
12from cuda.core._memory._buffer cimport (
13 Buffer,
14 Buffer_check_open,
15 Buffer_from_deviceptr_handle,
16 MemoryResource,
17)
18from cuda.core._memory._ipc cimport (
19 IPCAllocationHandle,
20 IPCDataForBuffer,
21 VirtualMemoryIPCBufferDescriptor,
22)
23from cuda.core._rt cimport (
24 ContextHandle,
25 DevicePtrHandle,
26 MemAllocationHandle,
27 StreamHandle,
28 VaMappingHandle,
29 VaReservationHandle,
30 VmmRangeHandle,
31 as_cu,
32 create_context_bound_legacy_stream,
33 create_mem_allocation_handle,
34 create_va_mapping_handle,
35 create_va_reservation_handle,
36 create_vmm_range,
37 deallocation_stream,
38 deviceptr_create_vmm,
39 get_last_error,
40 get_primary_context,
41 import_mem_allocation_handle,
42 mem_allocation_is_imported,
43 mem_allocation_size,
44 set_deallocation_stream,
45 va_mapping_allocation,
46 vmm_range,
47 vmm_range_mappings,
48 vmm_range_total,
49)
50from cuda.core._stream cimport Stream, Stream_accept, Stream_handle_is_default_token, Stream_is_default_token
51from cuda.core._utils.cuda_utils cimport HANDLE_RETURN, check_or_create_options
53import os
54import platform
55from dataclasses import dataclass, field
56from multiprocessing import context as _mp_context
57from typing import TYPE_CHECKING, Iterable
59from cuda.core._device import Device
60from cuda.core._utils.cuda_utils import driver
61from cuda.core._utils.version import BUILD_CUDA_MAJOR
62from cuda.core.typing import (
63 DevicePointerType,
64 VirtualMemoryAccessType,
65 VirtualMemoryAllocationType,
66 VirtualMemoryGranularityType,
67 VirtualMemoryHandleType,
68 VirtualMemoryLocationType,
69)
71if TYPE_CHECKING:
72 from cuda.core.graph import GraphBuilder
74__all__ = ["VirtualMemoryBuffer", "VirtualMemoryResource", "VirtualMemoryResourceOptions"]
76# Location types whose physical backing lives in host memory. Shared by
77# VirtualMemoryResource.__init__ and is_host_accessible so the two cannot drift.
78_HOST_LOCATION_TYPES = frozenset(
79 {
80 VirtualMemoryLocationType.HOST,
81 VirtualMemoryLocationType.HOST_NUMA,
82 VirtualMemoryLocationType.HOST_NUMA_CURRENT,
83 }
84)
86# Handle types whose exported handles cuda.core can carry to another process.
87# POSIX file descriptors travel through multiprocessing on Linux. Win32 KMT
88# handles have no transport here, and fabric handles are not supported yet.
89_IPC_HANDLE_TYPES = frozenset({VirtualMemoryHandleType.POSIX_FD}) if platform.system() == "Linux" else frozenset()
92@dataclass
93class VirtualMemoryResourceOptions:
94 """A configuration object for the VirtualMemoryResource
95 Stores configuration information which tells the resource how to use the CUDA VMM APIs
97 Attributes
98 ----------
99 allocation_type: :obj:`~_memory.VirtualMemoryAllocationType` | str
100 Controls the type of allocation.
101 location_type: :obj:`~_memory.VirtualMemoryLocationType` | str
102 Controls the location of the allocation.
103 handle_type: :obj:`~_memory.VirtualMemoryHandleType` | str
104 Export handle type for the physical allocation, and the switch for
105 sharing buffers with other processes (see
106 :attr:`VirtualMemoryResource.is_ipc_enabled`). Use ``"posix_fd"`` on
107 Linux if you plan to import/export the allocation. Use `None` if you
108 don't need an exportable handle. ``location_type="host"`` requires
109 `None`.
110 gpu_direct_rdma: bool
111 Hint that the allocation should be GDR-capable (if supported).
112 granularity: :obj:`~_memory.VirtualMemoryGranularityType` | str
113 Controls granularity query and size rounding.
114 addr_hint: int
115 A (optional) virtual address hint to try to reserve at. Setting it to 0 lets the CUDA driver decide.
116 addr_align: int
117 Alignment for the VA reservation. If `None`, use the queried granularity.
118 peers: Iterable[int]
119 Extra device IDs that should be granted access in addition to ``device``.
120 self_access: :obj:`~_memory.VirtualMemoryAccessType` | None | str
121 Access flags for the owning device.
122 peer_access: :obj:`~_memory.VirtualMemoryAccessType` | None | str
123 Access flags for peers.
124 """
126 allocation_type: VirtualMemoryAllocationType = VirtualMemoryAllocationType.PINNED
127 location_type: VirtualMemoryLocationType = VirtualMemoryLocationType.DEVICE
128 handle_type: VirtualMemoryHandleType = VirtualMemoryHandleType.POSIX_FD
129 granularity: VirtualMemoryGranularityType = VirtualMemoryGranularityType.RECOMMENDED
130 gpu_direct_rdma: bool = False
131 addr_hint: int | None = 0
132 addr_align: int | None = None
133 peers: Iterable[int] = field(default_factory=tuple)
134 self_access: VirtualMemoryAccessType = VirtualMemoryAccessType.READ_WRITE
135 peer_access: VirtualMemoryAccessType = VirtualMemoryAccessType.READ_WRITE
137 _a = driver.CUmemAccess_flags
138 _access_flags = { # noqa: RUF012
139 VirtualMemoryAccessType.READ_WRITE: _a.CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
140 VirtualMemoryAccessType.READ: _a.CU_MEM_ACCESS_FLAGS_PROT_READ,
141 None: 0,
142 }
143 _h = driver.CUmemAllocationHandleType
144 _handle_types = { # noqa: RUF012
145 None: _h.CU_MEM_HANDLE_TYPE_NONE,
146 VirtualMemoryHandleType.POSIX_FD: _h.CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR,
147 VirtualMemoryHandleType.WIN32_KMT: _h.CU_MEM_HANDLE_TYPE_WIN32_KMT,
148 VirtualMemoryHandleType.FABRIC: _h.CU_MEM_HANDLE_TYPE_FABRIC,
149 }
150 _g = driver.CUmemAllocationGranularity_flags
151 _granularity = { # noqa: RUF012
152 VirtualMemoryGranularityType.RECOMMENDED: _g.CU_MEM_ALLOC_GRANULARITY_RECOMMENDED,
153 VirtualMemoryGranularityType.MINIMUM: _g.CU_MEM_ALLOC_GRANULARITY_MINIMUM,
154 }
155 _l = driver.CUmemLocationType
156 _location_type = { # noqa: RUF012
157 VirtualMemoryLocationType.DEVICE: _l.CU_MEM_LOCATION_TYPE_DEVICE,
158 VirtualMemoryLocationType.HOST: _l.CU_MEM_LOCATION_TYPE_HOST,
159 VirtualMemoryLocationType.HOST_NUMA: _l.CU_MEM_LOCATION_TYPE_HOST_NUMA,
160 VirtualMemoryLocationType.HOST_NUMA_CURRENT: _l.CU_MEM_LOCATION_TYPE_HOST_NUMA_CURRENT,
161 }
162 _t = driver.CUmemAllocationType
163 # CUDA 13 added MANAGED to CUmemAllocationType. The CUDA 12 build has no such member.
164 _allocation_type = {VirtualMemoryAllocationType.PINNED: _t.CU_MEM_ALLOCATION_TYPE_PINNED} # noqa: RUF012
165 if BUILD_CUDA_MAJOR >= 13:
166 _allocation_type[VirtualMemoryAllocationType.MANAGED] = _t.CU_MEM_ALLOCATION_TYPE_MANAGED
168 @staticmethod
169 def _access_to_flags(spec: VirtualMemoryAccessType | None) -> int:
170 flags = VirtualMemoryResourceOptions._access_flags.get(spec) 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m N H t u - x 9 8 7 Ib. * 5 n
171 if flags is None: 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m N H t u - x 9 8 7 Ib. * 5 n
172 raise ValueError(f"Unknown access spec: {spec!r}") 2Ib
173 return flags # type: ignore[no-any-return] 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
175 @staticmethod
176 def _allocation_type_to_driver(spec: VirtualMemoryAllocationType) -> int:
177 alloc_type = VirtualMemoryResourceOptions._allocation_type.get(spec) 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m N H t u - x 9 8 7 Jb. * 5 n
178 if alloc_type is None: 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m N H t u - x 9 8 7 Jb. * 5 n
179 raise ValueError(f"Unsupported allocation_type: {spec!r}") 2Jb
180 return alloc_type # type: ignore[no-any-return] 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
182 @staticmethod
183 def _location_type_to_driver(spec: VirtualMemoryLocationType) -> int:
184 loc_type = VirtualMemoryResourceOptions._location_type.get(spec) 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m N H t u - x 9 8 7 Kb. * 5 n
185 if loc_type is None: 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m N H t u - x 9 8 7 Kb. * 5 n
186 raise ValueError(f"Unsupported location_type: {spec!r}") 2Kb
187 return loc_type # type: ignore[no-any-return] 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
189 @staticmethod
190 def _handle_type_to_driver(spec: VirtualMemoryHandleType | None) -> int:
191 if spec == "win32": 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c / g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m : N H t u - x 9 8 7 MbHb. * 5 n
192 raise NotImplementedError("win32 is currently not supported, please reach out to the CUDA Python team") 2Mb
193 handle_type = VirtualMemoryResourceOptions._handle_types.get(spec) 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c / g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m : N H t u - x 9 8 7 Hb. * 5 n
194 if handle_type is None: 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c / g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m : N H t u - x 9 8 7 Hb. * 5 n
195 raise ValueError(f"Unsupported handle_type: {spec!r}") 2Hb
196 return handle_type # type: ignore[no-any-return] 1'q(WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%m:NHtu-x987.*5n
198 @staticmethod
199 def _granularity_to_driver(spec: VirtualMemoryGranularityType) -> int:
200 granularity = VirtualMemoryResourceOptions._granularity.get(spec) 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m N H t u - x 9 8 7 Lb. * 5 n
201 if granularity is None: 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m N H t u - x 9 8 7 Lb. * 5 n
202 raise ValueError(f"Unsupported granularity: {spec!r}") 2Lb
203 return granularity # type: ignore[no-any-return] 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
206cdef inline size_t _align_up(size_t size, size_t gran) except? 0:
207 """Round ``size`` up to a multiple of ``gran``.
209 Raises ``OverflowError`` instead of wrapping when the rounded size does not
210 fit in ``size_t``.
211 """
212 if size > <size_t>-1 - (gran - 1): 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
213 raise OverflowError(f"size {size} rounded up to the {gran}-byte granularity does not fit in size_t") 15
214 return (size + gran - 1) // gran * gran 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
217cdef bint _stream_is_capturing(cydriver.CUstream s) except -1:
218 cdef cydriver.CUstreamCaptureStatus cap_status
219 IF CUDA_CORE_BUILD_MAJOR >= 13:
220 HANDLE_RETURN(cydriver.cuStreamGetCaptureInfo(s, &cap_status, NULL, NULL, NULL, NULL, NULL)) 1X+,!vw%
221 ELSE:
222 HANDLE_RETURN(cydriver.cuStreamGetCaptureInfo(s, &cap_status, NULL, NULL, NULL, NULL))
223 return cap_status == cydriver.CU_STREAM_CAPTURE_STATUS_ACTIVE 1X+,!vw%
226cdef int _raise_last_error() except -1:
227 """Raise the status a handle factory recorded when it returned empty."""
228 HANDLE_RETURN(get_last_error()) 1ecN
229 raise RuntimeError(
230 "internal cuda.core error, please report: a virtual memory handle factory "
231 "returned an empty handle without recording a CUDA error"
232 )
235cdef inline int _attach_note(exc, str detail) except -1:
236 # PEP 678 notes (Python 3.11+) keep the detail separable from the message;
237 # older interpreters get it appended to the message instead.
238 add_note = getattr(exc, "add_note", None) 1eca
239 if add_note is not None: 1eca
240 add_note(detail) 1eca
241 else:
242 exc.args = (f"{exc.args[0]} ({detail})", *exc.args[1:])
243 return 0 1eca
246cdef int _raise_last_error_noting(str detail) except -1:
247 """Raise the status a handle factory recorded, with ``detail`` attached as a note."""
248 try: 1ec
249 _raise_last_error() 1ec
250 except Exception as exc: 1ec
251 _attach_note(exc, detail) 1ec
252 raise 1ec
253 return 0
256cdef int _check_noting(cydriver.CUresult status, str detail) except -1:
257 """``HANDLE_RETURN(status)``, with ``detail`` attached to the raised error as a note."""
258 if status == cydriver.CUDA_SUCCESS: 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ
259 return 0 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ
260 try: 1a
261 HANDLE_RETURN(status) 1a
262 except Exception as exc: 1a
263 _attach_note(exc, detail) 1a
264 raise 1a
265 return 0
268cdef str _describe_location(cydriver.CUmemLocation loc):
269 """Name a driver memory location for an error message."""
270 if loc.type == cydriver.CU_MEM_LOCATION_TYPE_DEVICE:
271 return f"device {loc.id}"
272 if loc.type == cydriver.CU_MEM_LOCATION_TYPE_HOST:
273 return "host memory"
274 if loc.type == cydriver.CU_MEM_LOCATION_TYPE_HOST_NUMA:
275 return f"host NUMA node {loc.id}"
276 if loc.type == cydriver.CU_MEM_LOCATION_TYPE_HOST_NUMA_CURRENT:
277 return "the current host NUMA node"
278 return f"location type {<int>loc.type}"
281cdef class VirtualMemoryBuffer(Buffer):
282 """A :class:`Buffer` returned by :class:`VirtualMemoryResource`.
284 The buffer owns its address reservations, physical allocations and
285 mappings through its device pointer handle; closing it is the only way
286 to release them. A buffer returned by
287 :meth:`VirtualMemoryResource.modify_allocation` aliases the buffer it
288 was grown from: the two share their physical memory, and that memory is
289 freed when the last buffer that maps it closes.
290 """
292 @property
293 def ipc_descriptor(self) -> VirtualMemoryIPCBufferDescriptor:
294 """A new descriptor for sharing this buffer with other processes.
296 Every access exports the physical allocations that back the buffer
297 again and returns a new descriptor that owns one file descriptor per
298 allocation; the file descriptors close when the descriptor is
299 released. Keep the descriptor in a variable while you use its
300 ``fds``: the numbers from ``buf.ipc_descriptor.fds`` alone may
301 already be closed. A descriptor pins the physical memory while it
302 exists, in every process that holds one, and nothing else does: the
303 buffer itself keeps no descriptor, so it pins nothing beyond its own
304 mapping.
305 Pickling the buffer through ``multiprocessing`` builds a transient
306 descriptor, so the sender holds no file descriptors between sends;
307 a buffer passed as a ``Process`` argument is the exception, because
308 the child receives the file descriptors when it is spawned, after
309 pickling, so that descriptor lives until the ``Process`` object is
310 released. Requires a resource whose ``handle_type`` can be shared; see
311 :attr:`VirtualMemoryResource.is_ipc_enabled`. Import with
312 :meth:`Buffer.from_ipc_descriptor` and a
313 :class:`VirtualMemoryResource` of the receiving process, or send the
314 buffer itself.
316 Raises
317 ------
318 RuntimeError
319 If the resource is not IPC-enabled, or the buffer contains memory
320 imported from another process (an import, or a buffer
321 :meth:`VirtualMemoryResource.modify_allocation` derived from
322 one): such memory cannot be exported again. Forward the descriptor
323 it was imported from, or copy the data into a buffer this process
324 allocated. The check happens before any driver call.
325 """
326 Buffer_check_open(self) 1YKZ0234SLMec/ghaijkdbfBClIJ
327 return _export_ipc_descriptor(self) 1YKZ0234SLMec/ghaijkdbfBClIJ
329 def __reduce__(self) -> tuple[object, ...]:
330 # Unpickling performs a live import from the descriptor; only deserialize
331 # Buffers from a trusted principal. Must not serialize the parent's stream.
332 Buffer_check_open(self) 1W1TUVOPQzALMRDEbfBFG
333 desc = _export_ipc_descriptor(self) 1W1TUVOPQzALMRDEbfBFG
334 popen = _mp_context.get_spawning_popen() 1W1TUVOPQzALMRDEBFG
335 if popen is not None: 1W1TUVOPQzALMRDEBFG
336 # A Queue, Pipe, or Pool duplicates the file descriptors while this
337 # transient descriptor is alive. A spawned Process does not: it
338 # records their numbers now and hands them to the child when it is
339 # created, after pickling has finished. Keep the descriptor on the
340 # Popen until then; the Popen dies with the Process object.
341 vars(popen).setdefault("_cuda_core_descriptors_until_spawned", []).append(desc) 1TUVzALDE
342 return Buffer._reduce_helper, (self.memory_resource, desc) 1W1TUVOPQzALMRDEBFG
344 def close(self, stream: Stream | GraphBuilder | None = None) -> None:
345 """Release this buffer's share of its address range.
347 Each mapping, reservation and physical allocation goes away when the
348 last buffer that holds it closes. Virtual memory is unmapped
349 synchronously, so this call waits for the work queued on the
350 deallocation stream before it releases this buffer's share; a buffer
351 released by the garbage collector waits at that point instead. The
352 unmap cannot be captured. When the stream this close uses, given or recorded, is not
353 a default stream and is capturing, the call raises and leaves the
354 buffer open. A default stream is checked when the range is released
355 instead: if synchronizing it would disturb a capture in its context,
356 the release reports a :class:`CUDAWarning` and unmaps without
357 synchronizing that stream.
359 Parameters
360 ----------
361 stream : :obj:`~_stream.Stream` | :obj:`~graph.GraphBuilder`, optional
362 If given, replaces the recorded deallocation stream, as for
363 :meth:`Buffer.close`.
364 """
365 cdef Stream s
366 cdef StreamHandle h
367 if not self._h_ptr: 1'q(OSPQzALMRDec/ghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987*5n
368 return 1zA
369 if stream is not None: 1'q(OSPQzALMRDec/ghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987*5n
370 s = Stream_accept(stream) 1!
371 h = s._h_stream 1!
372 else:
373 # Empty when no stream was recorded (host-located memory).
374 h = deallocation_stream(self._h_ptr) 1'q(OSPQzALMRDec/ghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987*5n
375 # Default-stream tokens are checked by the range deleter under their
376 # bound context; a real stream can be checked here and refused.
377 if h and not Stream_handle_is_default_token(as_cu(h)) and _stream_is_capturing(as_cu(h)): 1'q(OSPQzALMRDec/ghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987*5n
378 raise RuntimeError( 1!%
379 "cannot close a VirtualMemoryResource buffer on a capturing stream: "
380 "virtual memory deallocation is synchronous and cannot be captured"
381 )
382 Buffer.close(self, stream) 1'q(OSPQzALMRDec/ghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987*5n
385cdef class VirtualMemoryResource(MemoryResource):
386 """Create a device memory resource that uses the CUDA VMM APIs to allocate memory.
388 Parameters
389 ----------
390 device_id : Device | int
391 Device for which a memory resource is constructed.
393 config : VirtualMemoryResourceOptions, optional
394 A configuration object for the VirtualMemoryResource
397 Warning
398 -------
399 This is a low-level API that is provided only for convenience. Make sure you fully understand
400 how CUDA Virtual Memory Management works before using this. Other MemoryResource subclasses
401 in cuda.core should already meet the common needs.
403 A shared buffer's descriptor comes from the exporting process and is not trusted:
404 :meth:`Buffer.from_ipc_descriptor` checks what it can, and the driver rejects a size that
405 does not match the exported allocation. Unpickling a shared buffer performs a live import,
406 so unpickle buffers only from a trusted principal. A descriptor pins the physical memory
407 while it exists; release it when the receiver has imported it.
409 Notes
410 -----
411 Every buffer this resource returns is a :class:`VirtualMemoryBuffer` that
412 owns its address reservations, physical allocations and mappings; closing
413 the buffer releases them. :meth:`deallocate` is not involved in that path.
415 Virtual memory is unmapped synchronously, so closing a buffer waits for
416 the work queued on its deallocation stream. A buffer released by the
417 garbage collector waits at that point instead. To control when the wait
418 happens, close the buffer explicitly or record an idle stream with
419 :meth:`Buffer.set_deallocation_stream`.
421 Buffers can be shared with other processes when ``config.handle_type``
422 is ``"posix_fd"`` (Linux), the only handle type cuda.core shares today;
423 ``handle_type`` is the switch and :attr:`is_ipc_enabled` the query. The
424 rules that follow describe POSIX file descriptors.
425 :attr:`Buffer.ipc_descriptor` exports the physical allocations that back
426 a buffer, one file descriptor each, into a new descriptor on every
427 access; :meth:`Buffer.from_ipc_descriptor` imports a descriptor with a
428 ``VirtualMemoryResource`` of the receiving process for the device that
429 owns the memory; and a buffer sent through ``multiprocessing`` does both.
430 Plain ``pickle`` cannot carry the file descriptors; a process with its
431 own file descriptor passing sends the descriptor's ``fds``, ``sizes``,
432 and ``size`` and rebuilds it with
433 ``VirtualMemoryIPCBufferDescriptor.from_fds()``. The importing resource
434 maps the memory for its device and the devices in its ``peers`` option,
435 with its own access options. A descriptor pins the physical memory while
436 it exists, in every process that holds one, and nothing else does. A
437 buffer that contains imported memory (an import, or anything
438 :meth:`modify_allocation` derived from one) cannot be exported again;
439 forward the descriptor it came from, or copy into a buffer you own.
440 """
442 cdef:
443 readonly object device
444 readonly object config
445 object __weakref__
447 def __init__(self, device_id: Device | int, config: VirtualMemoryResourceOptions | None = None) -> None:
448 self.device = Device(device_id) 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n
449 self.config = check_or_create_options( 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n
450 VirtualMemoryResourceOptions, config, "VirtualMemoryResource options", keep_none=False 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n
451 )
452 if self.config.location_type in _HOST_LOCATION_TYPES: 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n
453 self.device = None 2vb; = - x ? xbyb9
454 if self.device is not None and not self.device.properties.virtual_memory_management_supported: 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n
455 raise RuntimeError("VirtualMemoryResource requires CUDA VMM API support")
456 self._check_config(self.config) 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n
458 def __reduce__(self) -> tuple[object, ...]:
459 # The resource holds no driver object that other processes must share;
460 # the receiving process rebuilds an equivalent one from the device and
461 # the options. This is what lets a Buffer pickle as (resource, descriptor).
462 return type(self), (self.device.device_id if self.device is not None else 0, self.config) 2NbW Y K 1 Z 0 T ObU PbV QbO S P Q z A L M R D E B CbF G I J
464 cdef int _check_config(self, object cfg) except -1:
465 """Reject options the driver would reject, before any driver call.
467 Shared by ``__init__`` and ``modify_allocation``, so a per-call
468 configuration is held to the same rules as the resource's own.
469 """
470 if cfg.location_type != self.config.location_type: 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n
471 raise ValueError( 17
472 f"config.location_type {str(cfg.location_type)!r} does not match the resource's " 17
473 f"{str(self.config.location_type)!r}; the location of a buffer cannot change" 17
474 )
475 # The driver rejects an exportable handle type for HOST memory only;
476 # the NUMA location types may carry one.
477 if cfg.location_type == VirtualMemoryLocationType.HOST and cfg.handle_type is not None: 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n
478 raise ValueError( 1?9
479 'virtual memory with location_type="host" cannot have an exportable handle type; '
480 "pass handle_type=None"
481 )
482 # The handle type is part of the resource's sharing contract, like the
483 # location: every chunk of a buffer must be exportable the same way.
484 if cfg.handle_type != self.config.handle_type: 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n
485 raise ValueError( 16
486 f"config.handle_type {str(cfg.handle_type)!r} does not match the resource's " 16
487 f"{str(self.config.handle_type)!r}; the handle type of a buffer's chunks cannot change" 16
488 )
489 if cfg.gpu_direct_rdma and self.device is not None and not self.device.properties.gpu_direct_rdma_supported: 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n
490 raise RuntimeError("GPU Direct RDMA is not supported on this device")
491 return 0 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n
493 cdef int _fill_prop(self, object cfg, cydriver.CUmemAllocationProp* prop) except -1:
494 # The location comes from the resource; the rest may come from a
495 # per-call configuration.
496 cdef int alloc_type = int(VirtualMemoryResourceOptions._allocation_type_to_driver(cfg.allocation_type)) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
497 cdef int loc_type = int(VirtualMemoryResourceOptions._location_type_to_driver(self.config.location_type)) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
498 cdef int handle_type = int(VirtualMemoryResourceOptions._handle_type_to_driver(cfg.handle_type)) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
499 memset(prop, 0, sizeof(cydriver.CUmemAllocationProp)) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
500 prop.type = <cydriver.CUmemAllocationType>alloc_type 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
501 prop.location.type = <cydriver.CUmemLocationType>loc_type 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
502 prop.location.id = self.device.device_id if self.device is not None else -1 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
503 prop.allocFlags.gpuDirectRDMACapable = 1 if cfg.gpu_direct_rdma else 0 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
504 prop.requestedHandleTypes = <cydriver.CUmemAllocationHandleType>handle_type 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
505 prop.win32HandleMetaData = NULL 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
506 return 0 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
508 cdef int _fill_access(
509 self, object cfg, const cydriver.CUmemAllocationProp* prop,
510 vector[cydriver.CUmemAccessDesc]& descs,
511 ) except -1:
512 cdef cydriver.CUmemAccessDesc d
513 cdef int owner_flags = int(VirtualMemoryResourceOptions._access_to_flags(cfg.self_access)) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
514 cdef int peer_flags = int(VirtualMemoryResourceOptions._access_to_flags(cfg.peer_access)) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
515 if owner_flags: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
516 memset(&d, 0, sizeof(d)) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsX#$op+,!vw%mNHtu-x987.*5n
517 d.location.type = prop.location.type 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsX#$op+,!vw%mNHtu-x987.*5n
518 d.location.id = prop.location.id 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsX#$op+,!vw%mNHtu-x987.*5n
519 d.flags = <cydriver.CUmemAccess_flags>owner_flags 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsX#$op+,!vw%mNHtu-x987.*5n
520 descs.push_back(d) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsX#$op+,!vw%mNHtu-x987.*5n
521 if peer_flags: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
522 for peer_dev in cfg.peers: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
523 memset(&d, 0, sizeof(d))
524 d.location.type = cydriver.CU_MEM_LOCATION_TYPE_DEVICE
525 d.location.id = int(peer_dev)
526 d.flags = <cydriver.CUmemAccess_flags>peer_flags
527 descs.push_back(d)
528 return 0 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
530 cdef size_t _granularity(self, object cfg, const cydriver.CUmemAllocationProp* prop) except? 0:
531 cdef size_t gran = 0 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
532 cdef int flag = int(VirtualMemoryResourceOptions._granularity_to_driver(cfg.granularity)) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
533 with nogil: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
534 HANDLE_RETURN(cydriver.cuMemGetAllocationGranularity( 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
535 &gran, prop, <cydriver.CUmemAllocationGranularity_flags>flag))
536 return gran 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
538 cdef int _record_deallocation_stream(self, const DevicePtrHandle& h_ptr, Stream s) except -1:
539 cdef StreamHandle h
540 cdef ContextHandle h_ctx
541 if s is not None and not Stream_is_default_token(s): 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
542 h = s._h_stream 11+,vw
543 elif self.device is None: 1'q(WYKZ0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op!%mNHtu-x987.*5n
544 # Host-located memory needs no context to free, and a default-stream
545 # token has no context to bind to: record nothing.
546 return 0 1-x9
547 else:
548 # Bind the legacy default-stream token to the device's primary
549 # context so the free is ordered correctly no matter what is current
550 # then, and so allocate() never depends on a current context.
551 # get_primary_context keeps its own cache; nothing is cached here.
552 h_ctx = get_primary_context(self.device.device_id) 1'q(WYKZ0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op!%mNHtu87.*5n
553 if not h_ctx: 1'q(WYKZ0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op!%mNHtu87.*5n
554 _raise_last_error()
555 h = create_context_bound_legacy_stream(h_ctx) 1'q(WYKZ0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op!%mNHtu87.*5n
556 if not h: 1'q(WYKZ0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op!%mNHtu87.*5n
557 _raise_last_error()
558 HANDLE_RETURN(set_deallocation_stream(h_ptr, h)) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu87.*5n
559 return 0 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu87.*5n
561 cdef int _copy_deallocation_stream(self, const DevicePtrHandle& dst, const DevicePtrHandle& src) except -1:
562 cdef StreamHandle h = deallocation_stream(src) 1qcghaikdbrsyXopvwmHtuxn
563 if h: 1qcghaikdbrsyXopvwmHtuxn
564 HANDLE_RETURN(set_deallocation_stream(dst, h)) 1qcghaikdbrsyXopvwmHtun
565 return 0 1qcghaikdbrsyXopvwmHtuxn
567 def allocate(self, size_t size, *, stream: Stream | GraphBuilder | None = None) -> VirtualMemoryBuffer:
568 """
569 Allocate a buffer of the given size using CUDA virtual memory.
571 Parameters
572 ----------
573 size : int
574 The size in bytes of the buffer to allocate. It is rounded up to the
575 allocation granularity; the returned buffer reports the rounded size.
576 stream : :obj:`~_stream.Stream` | :obj:`~graph.GraphBuilder`, optional
577 Keyword-only. The allocation itself is synchronous. A real stream is
578 recorded as the buffer's deallocation stream and synchronized when
579 the buffer closes; with `None` or a default-stream token the legacy
580 default stream of the resource's device is recorded instead. A
581 host-located resource records no default stream: its buffers close
582 without a synchronization unless a real stream was given.
584 Returns
585 -------
586 VirtualMemoryBuffer
587 A buffer that owns its reservation, physical allocation and mapping.
589 Raises
590 ------
591 CUDAError
592 If any CUDA driver API call fails during allocation. Nothing is
593 left allocated when this method raises.
594 OverflowError
595 If ``size`` rounded up to the granularity does not fit in ``size_t``.
596 """
597 cdef Stream s = None 1'q(WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
598 if stream is not None: 1'q(WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
599 s = Stream_accept(stream) 1'qWYK1Z0T2U3V4OSPQzALMRFGIJX+,vw
600 return self._allocate(self.config, size, s) 1'q(WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
602 cdef Buffer _allocate(self, object cfg, size_t size, Stream s):
603 """Allocate ``size`` bytes with ``cfg``; ``s`` is the accepted stream or None."""
604 cdef cydriver.CUmemAllocationProp prop
605 cdef vector[cydriver.CUmemAccessDesc] descs
606 cdef size_t gran, aligned, addr_align
607 cdef cydriver.CUdeviceptr hint
608 cdef MemAllocationHandle h_alloc
609 cdef VaReservationHandle h_res
610 cdef VaMappingHandle h_map
611 cdef vector[VaMappingHandle] mappings
612 cdef VmmRangeHandle rng
613 cdef DevicePtrHandle h_ptr
615 if size == 0: 1'q(WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
616 # Nothing to reserve or map: a VMM box over an empty range, so the
617 # buffer is a VirtualMemoryBuffer like every other. A real stream is
618 # still recorded so that a later grow inherits it; a default-stream
619 # token is not, which keeps this path free of driver calls.
620 h_ptr = deviceptr_create_vmm(0, create_vmm_range(mappings)) 1/X
621 if s is not None and not Stream_is_default_token(s): 1/X
622 HANDLE_RETURN(set_deallocation_stream(h_ptr, s._h_stream)) 1X
623 return Buffer_from_deviceptr_handle(h_ptr, 0, self, None, VirtualMemoryBuffer) 1/X
625 self._fill_prop(cfg, &prop) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
626 self._fill_access(cfg, &prop, descs) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
627 gran = self._granularity(cfg, &prop) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
628 aligned = _align_up(size, gran) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
629 addr_align = cfg.addr_align or gran 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
630 hint = cfg.addr_hint or 0 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
632 # Every handle below is a local: if a later step fails, the locals die
633 # in reverse order and undo everything created so far.
634 with nogil: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
635 h_alloc = create_mem_allocation_handle(aligned, prop, descs.data(), descs.size()) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
636 if not h_alloc: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
637 _raise_last_error()
638 with nogil: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
639 h_res = create_va_reservation_handle(aligned, addr_align, hint) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
640 if not h_res: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
641 _raise_last_error()
642 with nogil: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
643 h_map = create_va_mapping_handle(as_cu(h_res), h_alloc, h_res) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
644 if not h_map: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
645 _raise_last_error()
647 mappings.push_back(h_map) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
648 rng = create_vmm_range(mappings) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
649 h_ptr = deviceptr_create_vmm(as_cu(h_res), rng) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
650 if not h_ptr: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
651 _raise_last_error()
652 self._record_deallocation_stream(h_ptr, s) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
653 return Buffer_from_deviceptr_handle(h_ptr, aligned, self, None, VirtualMemoryBuffer) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n
655 def modify_allocation(
656 self, Buffer buf not None: Buffer, size_t new_size, config: VirtualMemoryResourceOptions | None = None
657 ) -> VirtualMemoryBuffer:
658 """
659 Grow a buffer of this resource to at least ``new_size`` bytes.
661 The buffer passed in stays open and usable, and is never returned. The
662 returned buffer aliases it: both map the same physical memory, which is
663 freed when the last of the two closes. When the driver can extend the
664 address range in place, the returned buffer has the same pointer;
665 otherwise it has a new one and the existing contents are reachable
666 through both. Closing the returned buffer never closes ``buf``. A
667 buffer grown from an imported buffer maps imported memory and cannot
668 be exported again.
670 Concurrent calls on buffers that alias one another are safe.
672 Parameters
673 ----------
674 buf : VirtualMemoryBuffer
675 A buffer returned by :meth:`allocate` or by this method.
676 new_size : int
677 The requested total size in bytes; rounded up to the granularity.
678 config : VirtualMemoryResourceOptions, optional
679 Configuration for the new physical memory chunk only. Existing
680 chunks keep the access they were created with, and the resource's
681 own configuration is unchanged. It must name the resource's
682 ``location_type`` and ``handle_type`` and passes the same checks
683 as the constructor.
684 When ``buf`` already covers ``new_size`` there is no new chunk, so
685 ``config`` has no effect. This method never changes the access of
686 memory that is already mapped.
688 Returns
689 -------
690 VirtualMemoryBuffer
691 A new buffer of at least ``new_size`` and at least ``buf.size``
692 bytes. When ``buf`` already covers the request, the result is a
693 full alias of it and no driver call is made.
695 Raises
696 ------
697 TypeError
698 If ``buf`` did not come from this resource.
699 ValueError
700 If ``config`` names a different location or handle type than the
701 resource, or the constructor would reject it.
702 RuntimeError
703 If ``buf`` is closed, or ``config`` requests GPUDirect RDMA on a
704 device without support.
705 OverflowError
706 If ``new_size`` rounded up to the granularity does not fit in
707 ``size_t``.
708 CUDAError
709 If a driver call fails. ``buf`` is untouched when this method raises.
710 """
711 cdef Buffer b
712 cdef DevicePtrHandle h_in
713 cdef size_t in_size
714 cdef VmmRangeHandle rng, rng_new
715 cdef object cfg
716 cdef cydriver.CUmemAllocationProp prop
717 cdef vector[cydriver.CUmemAccessDesc] descs
718 cdef vector[VaMappingHandle] mappings
719 cdef size_t gran, req, total, add, addr_align, offset, i
720 cdef cydriver.CUdeviceptr base, base_new
721 cdef MemAllocationHandle h_alloc, a
722 cdef VaReservationHandle h_res, h_res_new
723 cdef VaMappingHandle h_map, m2
724 cdef DevicePtrHandle h_ptr_new
725 cdef object new_buf
727 # Every VirtualMemoryBuffer handle comes from deviceptr_create_vmm, so
728 # the class check is what makes vmm_range() below valid.
729 if not isinstance(buf, VirtualMemoryBuffer): 2q c g h a i k d b 6 r s y X o p v w m N H t u x 9 8 Db7 5 n
730 raise TypeError(f"buf must be a VirtualMemoryBuffer, got {type(buf).__name__}") 28 Db
731 b = <Buffer>buf 1qcghaikdb6rsyXopvwmNHtux9875n
732 Buffer_check_open(b) 1qcghaikdb6rsyXopvwmNHtux9875n
733 # Read the input's handle and size once. The handle copy keeps the
734 # input's box alive for the rest of the call, so a close from another
735 # thread (unsupported; see concurrency.rst) defers the release instead
736 # of emptying what this call reads.
737 h_in = b._h_ptr 1qcghaikdb6rsyXopvwmNHtux9875n
738 in_size = b._size 1qcghaikdb6rsyXopvwmNHtux9875n
739 if b.memory_resource is not self: 1qcghaikdb6rsyXopvwmNHtux9875n
740 raise TypeError("buf was not allocated by this VirtualMemoryResource") 18
741 cfg = self.config if config is None else check_or_create_options( 1qcghaikdb6rsyXopvwmNHtux975n
742 VirtualMemoryResourceOptions, config, "VirtualMemoryResource options", keep_none=False 16p97
743 )
744 self._check_config(cfg) 1qcghaikdb6rsyXopvwmNHtux975n
745 if in_size == 0: 1qcghaikdbrsyXopvwmNHtux5n
746 # An empty buffer maps nothing; the request is a fresh allocation
747 # that inherits the stream the empty buffer recorded, if any.
748 new_buf = self._allocate(cfg, new_size, None) 1X
749 self._copy_deallocation_stream((<Buffer>new_buf)._h_ptr, h_in) 1X
750 return new_buf 1X
751 rng = vmm_range(h_in) 1qcghaikdbrsyopvwmNHtux5n
753 self._fill_prop(cfg, &prop) 1qcghaikdbrsyopvwmNHtux5n
754 self._fill_access(cfg, &prop, descs) 1qcghaikdbrsyopvwmNHtux5n
755 gran = self._granularity(cfg, &prop) 1qcghaikdbrsyopvwmNHtux5n
756 req = _align_up(new_size, gran) 1qcghaikdbrsyopvwmNHtux5n
757 total = vmm_range_total(rng) 1qcghaikdbrsyopvwmNHtuxn
758 base = as_cu(h_in) 1qcghaikdbrsyopvwmNHtuxn
760 if req <= total: 1qcghaikdbrsyopvwmNHtuxn
761 # The range already maps the request: a new buffer over the same
762 # range, at least as large as the input, with no driver call. The
763 # result is a distinct object, so closing it never closes ``buf``.
764 h_ptr_new = deviceptr_create_vmm(base, rng) 1bom
765 if not h_ptr_new: 1bom
766 _raise_last_error()
767 self._copy_deallocation_stream(h_ptr_new, h_in) 1bom
768 return Buffer_from_deviceptr_handle( 1bom
769 h_ptr_new, req if req > in_size else in_size, self, None, VirtualMemoryBuffer) 1bom
771 # The new chunk is a whole number of granules; the result covers it.
772 add = _align_up(req - total, gran) 1qcghaikdbrsyopvwmNHtuxn
773 req = total + add 1qcghaikdbrsyopvwmNHtuxn
774 # The result's range is built from a copy of the input's mappings; the
775 # input's range is immutable and never changes.
776 mappings = vmm_range_mappings(rng) 1qcghaikdbrsyopvwmNHtuxn
778 # Grow in place: reserve the range right after the current one. The
779 # driver raises alignment 0 to its default, so the hint is well formed.
780 with nogil: 1qcghaikdbrsyopvwmNHtuxn
781 h_res = create_va_reservation_handle(add, 0, base + total) 1qcghaikdbrsyopvwmNHtuxn
782 if not h_res: 1qcghaikdbrsyopvwmNHtuxn
783 get_last_error() # the probe may fail; that is not an error here 1N
784 elif as_cu(h_res) != base + total: 1qcghaikdbrsyopvwmHtuxn
785 h_res.reset() # granted elsewhere: free it and move instead 1qcghaikdbrsyopvwmtuxn
786 else:
787 with nogil: 1abmHn
788 h_alloc = create_mem_allocation_handle(add, prop, descs.data(), descs.size()) 1abmHn
789 if not h_alloc: 1abmHn
790 _raise_last_error()
791 with nogil: 1abmHn
792 h_map = create_va_mapping_handle(base + total, h_alloc, h_res) 1abmHn
793 if not h_map: 1abmHn
794 _raise_last_error()
795 mappings.push_back(h_map) 1abmHn
796 rng_new = create_vmm_range(mappings) 1abmHn
797 h_ptr_new = deviceptr_create_vmm(base, rng_new) 1abmHn
798 if not h_ptr_new: 1abmHn
799 _raise_last_error()
800 self._copy_deallocation_stream(h_ptr_new, h_in) 1abmHn
801 return Buffer_from_deviceptr_handle(h_ptr_new, req, self, None, VirtualMemoryBuffer) 1abmHn
803 # Move: a new range that maps every existing allocation, then the new one.
804 # The allocations are shared with the input buffer's range.
805 addr_align = cfg.addr_align or gran 1qcghaikdbrsyopvwmNtuxn
806 with nogil: 1qcghaikdbrsyopvwmNtuxn
807 h_res_new = create_va_reservation_handle(req, addr_align, 0) 1qcghaikdbrsyopvwmNtuxn
808 if not h_res_new: 1qcghaikdbrsyopvwmNtuxn
809 _raise_last_error() 1N
810 base_new = as_cu(h_res_new) 1qcghaikdbrsyopvwmtuxn
811 offset = 0 1qcghaikdbrsyopvwmtuxn
812 for i in range(mappings.size()): 1qcghaikdbrsyopvwmtuxn
813 a = va_mapping_allocation(mappings[i]) 1qcghaikdbrsyopvwmtuxn
814 with nogil: 1qcghaikdbrsyopvwmtuxn
815 m2 = create_va_mapping_handle(base_new + offset, a, h_res_new) 1qcghaikdbrsyopvwmtuxn
816 if not m2: 1qcghaikdbrsyopvwmtuxn
817 _raise_last_error()
818 offset += mem_allocation_size(a) 1qcghaikdbrsyopvwmtuxn
819 mappings[i] = m2 # the input's range still holds the old mapping 1qcghaikdbrsyopvwmtuxn
820 with nogil: 1qcghaikdbrsyopvwmtuxn
821 h_alloc = create_mem_allocation_handle(add, prop, descs.data(), descs.size()) 1qcghaikdbrsyopvwmtuxn
822 if not h_alloc: 1qcghaikdbrsyopvwmtuxn
823 _raise_last_error()
824 with nogil: 1qcghaikdbrsyopvwmtuxn
825 m2 = create_va_mapping_handle(base_new + offset, h_alloc, h_res_new) 1qcghaikdbrsyopvwmtuxn
826 if not m2: 1qcghaikdbrsyopvwmtuxn
827 _raise_last_error()
828 mappings.push_back(m2) 1qcghaikdbrsyopvwmtuxn
829 rng_new = create_vmm_range(mappings) 1qcghaikdbrsyopvwmtuxn
830 h_ptr_new = deviceptr_create_vmm(base_new, rng_new) 1qcghaikdbrsyopvwmtuxn
831 if not h_ptr_new: 1qcghaikdbrsyopvwmtuxn
832 _raise_last_error()
833 self._copy_deallocation_stream(h_ptr_new, h_in) 1qcghaikdbrsyopvwmtuxn
834 return Buffer_from_deviceptr_handle(h_ptr_new, req, self, None, VirtualMemoryBuffer) 1qcghaikdbrsyopvwmtuxn
836 def deallocate(self, ptr: DevicePointerType, size: int, *, stream: Stream | GraphBuilder | None = None) -> None:
837 """
838 Unmap and free one address range that was reserved and mapped outside this resource.
840 Buffers returned by :meth:`allocate` and :meth:`modify_allocation` free
841 themselves when they close and never call this method. It exists for
842 raw pointers wrapped with :meth:`Buffer.from_handle` with ``mr`` set to
843 this resource: the range must be exactly one reservation, and the caller
844 must already have released its own ``cuMemCreate`` handle, so the
845 physical memory is freed by the unmap.
847 Parameters
848 ----------
849 ptr : DevicePointerType
850 The start of the reservation.
851 size : int
852 The size of the reservation in bytes.
853 stream : :obj:`~_stream.Stream` | :obj:`~graph.GraphBuilder`, optional
854 Keyword-only. If given, ``stream.sync()`` is called before the
855 range is unmapped, except for a default-stream token on a
856 host-located resource, which has no context to synchronize in.
857 """
858 cdef cydriver.CUdeviceptr devptr = 0 if ptr is None else <cydriver.CUdeviceptr>int(ptr) 1:
859 cdef size_t nbytes = size 1:
860 cdef Stream s
861 if stream is not None: 1:
862 s = Stream_accept(stream) 1:
863 # A host-located resource records no stream, so Buffer teardown
864 # passes an unbound default-stream token, which has no context to
865 # synchronize in. There is nothing queued on it to wait for.
866 if self.device is not None or not Stream_is_default_token(s): 1:
867 s.sync() 1:
868 if devptr == 0 or nbytes == 0: 1:
869 return
870 with nogil: 1:
871 HANDLE_RETURN(cydriver.cuMemUnmap(devptr, nbytes)) 1:
872 HANDLE_RETURN(cydriver.cuMemAddressFree(devptr, nbytes)) 1:
874 @property
875 def is_device_accessible(self) -> bool:
876 """
877 Indicates whether the allocated memory is accessible from the device.
878 """
879 return self.config.location_type == "device" 1:x
881 @property
882 def is_host_accessible(self) -> bool:
883 """
884 Indicates whether the allocated memory is accessible from the host.
885 """
886 return self.config.location_type in _HOST_LOCATION_TYPES 2- x ? xbyb
888 @property
889 def is_ipc_enabled(self) -> bool:
890 """Whether buffers of this resource can be shared with other processes.
892 True when ``config.handle_type`` is ``"posix_fd"`` on Linux and the
893 memory lives on a device; ``handle_type`` is the only switch, there is
894 no separate option. With ``handle_type=None`` the allocations cannot
895 be exported, Win32 KMT handles are not transported by cuda.core, and
896 fabric handles are not supported yet. Host-located virtual memory
897 (``location_type`` ``"host"``, ``"host_numa"``, or
898 ``"host_numa_current"``) is not shared yet.
899 """
900 return ( 2@ [ W ] Y ^ K _ ` { 1 | Z } 0 ~ abbbT cb2 dbebfbU gb3 hbibjbV kb4 lbmbO nbS obP pbQ qbrbz A L sbM tbR ubD e c / g h E a i j k d b f B C l zbvb; = AbwbBbF G I J # $
901 self.config.handle_type in _IPC_HANDLE_TYPES 2@ [ W ] Y ^ K _ ` { 1 | Z } 0 ~ abbbT cb2 dbebfbU gb3 hbibjbV kb4 lbmbO nbS obP pbQ qbrbz A L sbM tbR ubD e c / g h E a i j k d b f B C l zbvb; = AbwbBbF G I J # $
902 and self.config.location_type not in _HOST_LOCATION_TYPES 2@ [ W ] Y ^ K _ ` { 1 | Z } 0 ~ abbbT cb2 dbebfbU gb3 hbibjbV kb4 lbmbO nbS obP pbQ qbrbz A L sbM tbR ubD e c / g h E a i j k d b f B C l ; = wbF G I J # $
903 )
905 def _import_ipc_buffer(self, VirtualMemoryIPCBufferDescriptor desc not None, stream) -> VirtualMemoryBuffer:
906 """Import a buffer that another process exported; see :meth:`Buffer.from_ipc_descriptor`.
908 Every exported allocation is imported with this resource's handle
909 type and checked to live on this resource's device. The allocations
910 are then mapped in order into
911 one new address reservation with the access descriptors this
912 resource's options produce for its device and peers, and the
913 deallocation stream is recorded as :meth:`allocate` does. The
914 returned buffer owns the imported handles like a buffer from
915 :meth:`allocate` owns created ones, reports ``is_mapped``, and cannot
916 be exported again.
917 """
918 cdef Stream s = None 1ec/ajdbfCl
919 cdef object cfg = self.config 1ec/ajdbfCl
920 cdef cydriver.CUmemAllocationProp prop
921 cdef cydriver.CUmemAllocationProp imported_prop
922 cdef vector[cydriver.CUmemAccessDesc] descs
923 cdef vector[MemAllocationHandle] allocs
924 cdef vector[VaMappingHandle] mappings
925 cdef MemAllocationHandle h_alloc
926 cdef VaReservationHandle h_res
927 cdef VaMappingHandle h_map
928 cdef VmmRangeHandle rng
929 cdef DevicePtrHandle h_ptr
930 cdef Buffer buf
931 cdef size_t gran, gran_min = 0, addr_align, chunk, total = 0, offset = 0 1ec/ajdbfCl
932 cdef Py_ssize_t n, i
933 cdef cydriver.CUdeviceptr hint
934 cdef int handle_type, fd
935 cdef void* os_handle
937 if not self.is_ipc_enabled: 1ec/ajdbfCl
938 raise RuntimeError("Memory resource is not IPC-enabled") 1C
939 handle_type = int(VirtualMemoryResourceOptions._handle_type_to_driver(cfg.handle_type)) 1ec/ajdbfl
940 if desc._handle_type != handle_type: 1ec/ajdbfl
941 raise ValueError( 1e
942 "the descriptor's handle type does not match this resource's " 1e
943 f"handle_type={str(cfg.handle_type)!r}; import it with a resource configured like the exporter's" 1e
944 )
945 n = len(desc._chunk_sizes) 1ec/ajdbfl
946 if n != len(desc._handles): 1ec/ajdbfl
947 raise ValueError("malformed VirtualMemoryResource buffer descriptor") 1e
948 if stream is not None: 1ec/ajdbfl
949 s = Stream_accept(stream) 1ec/ajdbfl
950 if n == 0: 1ec/ajdbfl
951 if desc._size != 0: 1e/
952 raise ValueError( 1e
953 f"malformed VirtualMemoryResource buffer descriptor: a size of {desc._size} bytes with no " 1e
954 "exported allocations"
955 )
956 # An exported empty buffer: an empty range, as allocate(0) returns.
957 h_ptr = deviceptr_create_vmm(0, create_vmm_range(mappings)) 1/
958 if s is not None and not Stream_is_default_token(s): 1/
959 HANDLE_RETURN(set_deallocation_stream(h_ptr, s._h_stream))
960 buf = Buffer_from_deviceptr_handle(h_ptr, 0, self, None, VirtualMemoryBuffer) 1/
961 buf._ipc_data = IPCDataForBuffer(None, True) 1/
962 return buf 1/
964 self._fill_prop(cfg, &prop) 1ecajdbfl
965 self._fill_access(cfg, &prop, descs) 1ecajdbfl
966 gran = self._granularity(cfg, &prop) 1ecajdbfl
967 # cuMemMap requires the minimum granularity, whatever this resource's
968 # granularity option says; the exporter may have used either.
969 with nogil: 1ecajdbfl
970 HANDLE_RETURN(cydriver.cuMemGetAllocationGranularity( 1ecajdbfl
971 &gran_min, &prop, cydriver.CU_MEM_ALLOC_GRANULARITY_MINIMUM))
972 for chunk_obj in desc._chunk_sizes: 1ecajdbfl
973 chunk = chunk_obj 1ecajdbfl
974 if chunk == 0 or chunk % gran_min != 0: 1ecajdbfl
975 raise ValueError( 1e
976 f"descriptor chunk size {chunk} is not a positive multiple of the {gran_min}-byte minimum " 1e
977 "granularity"
978 )
979 if chunk > <size_t>-1 - total: 1ecajdbfl
980 raise OverflowError("the descriptor's chunk sizes do not fit in size_t")
981 total += chunk 1ecajdbfl
982 if desc._size > total: 1ecajdbfl
983 raise ValueError(f"descriptor size ({desc._size}) exceeds the exported allocations ({total} bytes)") 1e
984 addr_align = cfg.addr_align or gran 1ecajdbfl
985 hint = cfg.addr_hint or 0 1ecajdbfl
987 # Every handle below is a local: if a later step fails, the locals die
988 # in reverse order and release everything imported so far.
989 #
990 # The driver copies each shared allocation into a handle of its own
991 # during cuMemImportFromShareableHandle and keeps no reference to the
992 # file descriptor afterwards: the shareable handle and the allocation
993 # handle are independent references to the memory, and the CUDA
994 # samples and PyTorch close the descriptor right after the import. The
995 # buffer therefore does not keep the descriptor; its file descriptors
996 # live only as long as the caller holds the descriptor object.
997 for i in range(n): 1ecajdbfl
998 fd = int(desc._handles[i]) # raises if the handle was closed 1ecajdbfl
999 chunk = desc._chunk_sizes[i] 1ecajdbfl
1000 os_handle = <void*><uintptr_t>fd 1ecajdbfl
1001 with nogil: 1ecajdbfl
1002 h_alloc = import_mem_allocation_handle( 1ecajdbfl
1003 os_handle, <cydriver.CUmemAllocationHandleType>handle_type, chunk, descs.data(), descs.size())
1004 if not h_alloc: 1ecajdbfl
1005 _raise_last_error_noting( 1e
1006 f"while importing chunk {i + 1} of {n} of a VirtualMemoryResource buffer descriptor; either " 1e
1007 "the shared handle does not refer to a CUDA allocation exported with "
1008 f"handle_type={str(cfg.handle_type)!r}, or the device that owns the memory is not usable in " 1e
1009 "this process (for example, CUDA_VISIBLE_DEVICES hides it)"
1010 )
1011 # The exporter chose where the memory lives. This resource must map
1012 # memory of that kind on that device, or its access descriptors
1013 # would describe the wrong location.
1014 with nogil: 1ecajdbfl
1015 HANDLE_RETURN(cydriver.cuMemGetAllocationPropertiesFromHandle(&imported_prop, as_cu(h_alloc))) 1ecajdbfl
1016 if imported_prop.location.type != prop.location.type or ( 1ecajdbfl
1017 prop.location.type == cydriver.CU_MEM_LOCATION_TYPE_DEVICE 1ecajdbfl
1018 and imported_prop.location.id != prop.location.id 1ecajdbfl
1019 ):
1020 raise ValueError(
1021 f"chunk {i + 1} of the descriptor is memory on {_describe_location(imported_prop.location)}, "
1022 f"but this resource maps memory on {_describe_location(prop.location)}; import the descriptor "
1023 "with a VirtualMemoryResource for the device that owns the memory, and name other devices "
1024 "in its peers option"
1025 )
1026 allocs.push_back(h_alloc) 1ecajdbfl
1027 with nogil: 1ecajdbfl
1028 h_res = create_va_reservation_handle(total, addr_align, hint) 1ecajdbfl
1029 if not h_res: 1ecajdbfl
1030 _raise_last_error()
1031 for i in range(n): 1ecajdbfl
1032 chunk = mem_allocation_size(allocs[<size_t>i]) 1ecajdbfl
1033 with nogil: 1ecajdbfl
1034 h_map = create_va_mapping_handle(as_cu(h_res) + offset, allocs[<size_t>i], h_res) 1ecajdbfl
1035 if not h_map: 1ecajdbfl
1036 _raise_last_error_noting( 1c
1037 f"while mapping chunk {i + 1} of {n} ({chunk} bytes) of a VirtualMemoryResource buffer " 1c
1038 "descriptor; a chunk size that does not match the exported allocation fails here, and the "
1039 "sizes come from the exporting peer"
1040 )
1041 mappings.push_back(h_map) 1ecajdbfl
1042 offset += chunk 1ecajdbfl
1043 rng = create_vmm_range(mappings) 1ecajdbfl
1044 h_ptr = deviceptr_create_vmm(as_cu(h_res), rng) 1ecajdbfl
1045 if not h_ptr: 1ecajdbfl
1046 _raise_last_error()
1047 self._record_deallocation_stream(h_ptr, s) 1ecajdbfl
1048 buf = Buffer_from_deviceptr_handle(h_ptr, desc._size, self, None, VirtualMemoryBuffer) 1ecajdbfl
1049 buf._ipc_data = IPCDataForBuffer(None, True) 1ecajdbfl
1050 return buf 1ecajdbfl
1052 @property
1053 def device_id(self) -> int:
1054 """
1055 Get the device ID associated with this memory resource.
1057 Returns:
1058 int: CUDA device ID. -1 if the memory resource allocates host memory
1059 """
1060 return self.device.device_id if self.device is not None else -1 2Cb# $ p x ? xbyb
1062 def __repr__(self) -> str:
1063 """
1064 Return a string representation of the VirtualMemoryResource.
1066 Returns:
1067 str: A string describing the object
1068 """
1069 return f"<VirtualMemoryResource device={self.device}>"
1072cdef VirtualMemoryIPCBufferDescriptor _export_ipc_descriptor(Buffer buf):
1073 """Export the allocations that back ``buf`` as shareable handles, in address order.
1075 Only the allocations that cover ``buf.size`` are exported; a buffer's size
1076 is a prefix of its range. Each file descriptor is owned by an
1077 :class:`IPCAllocationHandle` in the new descriptor and closed with it;
1078 nothing is cached on the buffer. A buffer that contains memory imported
1079 from another process is refused before any driver call: the driver
1080 exports only allocations this process created with the handle type, and
1081 every allocation carries its provenance (``mem_allocation_is_imported``)
1082 into every range that maps it.
1083 """
1084 cdef VirtualMemoryResource mr = <VirtualMemoryResource>buf._memory_resource 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ
1085 cdef vector[VaMappingHandle] mappings
1086 cdef vector[MemAllocationHandle] allocs
1087 cdef MemAllocationHandle h_alloc
1088 cdef cydriver.CUresult status
1089 cdef size_t i, n, chunk, offset = 0 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ
1090 cdef int handle_type
1091 cdef int fd = -1 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ
1092 cdef list sizes = [] 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ
1093 cdef list handles = [] 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ
1095 if not mr.is_ipc_enabled: 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ
1096 raise RuntimeError("Memory resource is not IPC-enabled") 1C
1097 handle_type = int(VirtualMemoryResourceOptions._handle_type_to_driver(mr.config.handle_type)) 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ
1098 mappings = vmm_range_mappings(vmm_range(buf._h_ptr)) 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ
1099 for i in range(mappings.size()): 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ
1100 if offset >= buf._size: 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ
1101 break
1102 h_alloc = va_mapping_allocation(mappings[i]) 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ
1103 allocs.push_back(h_alloc) 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ
1104 offset += mem_allocation_size(h_alloc) 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ
1105 n = allocs.size() 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ
1106 for i in range(n): 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ
1107 if mem_allocation_is_imported(allocs[i]): 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ
1108 raise RuntimeError( 1bf
1109 f"this buffer contains memory imported from another process (allocation {i + 1} of {n}); imported " 1bf
1110 "memory cannot be exported again. Forward the descriptor it was imported from, or copy the data "
1111 "into a buffer this process allocated."
1112 )
1113 for i in range(n): 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ
1114 h_alloc = allocs[i] 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ
1115 chunk = mem_allocation_size(h_alloc) 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ
1116 with nogil: 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ
1117 status = cydriver.cuMemExportToShareableHandle( 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ
1118 <void*>&fd, as_cu(h_alloc), <cydriver.CUmemAllocationHandleType>handle_type, 0)
1119 _check_noting( 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ
1120 status,
1121 f"while exporting allocation {i + 1} of {n} of a VirtualMemoryResource buffer; each exported " 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ
1122 "allocation takes one file descriptor, so a process near its file descriptor limit fails here",
1123 )
1124 try: 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ
1125 handle = IPCAllocationHandle._init(fd, None) 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ
1126 except: # noqa: E722 rollback-then-raise: the fd is not owned yet
1127 os.close(fd)
1128 raise
1129 # The handle owns the fd now; if append fails, the handle closes it once.
1130 handles.append(handle) 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ
1131 sizes.append(chunk) 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ
1132 return VirtualMemoryIPCBufferDescriptor._from_exports(handle_type, tuple(sizes), tuple(handles), buf._size) 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ