Coverage for cuda/core/_memory/_virtual_memory_resource.pyx: 75.85%
501 statements
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-05 01:11 +0000
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-05 01:11 +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 VirtualMemoryFdBufferDescriptor,
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, replace
56from multiprocessing import context as _mp_context
57from typing import TYPE_CHECKING, Iterable
59from cuda.core._device import Device, _device_from_uuid
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. ``"host_numa"`` requires
103 ``numa_id``. ``"host_numa_current"`` is rejected because the driver
104 does not accept it for physical allocations.
105 handle_type: :obj:`~_memory.VirtualMemoryHandleType` | str
106 Export handle type for the physical allocation, and the switch for
107 sharing buffers with other processes (see
108 :attr:`VirtualMemoryResource.is_ipc_enabled`). Use ``"posix_fd"`` on
109 Linux if you plan to import/export the allocation. Use `None` if you
110 don't need an exportable handle. ``location_type="host"`` requires
111 `None`.
112 gpu_direct_rdma: bool
113 Hint that the allocation should be GDR-capable (if supported).
114 granularity: :obj:`~_memory.VirtualMemoryGranularityType` | str
115 Controls granularity query and size rounding.
116 addr_hint: int
117 A (optional) virtual address hint to try to reserve at. Setting it to 0 lets the CUDA driver decide.
118 addr_align: int
119 Alignment for the VA reservation. If `None`, use the queried granularity.
120 peers: Iterable[int]
121 Extra device IDs that should be granted access in addition to ``device``.
122 The options store them as a tuple.
123 self_access: :obj:`~_memory.VirtualMemoryAccessType` | None | str
124 Access flags for the owning device.
125 peer_access: :obj:`~_memory.VirtualMemoryAccessType` | None | str
126 Access flags for peers.
127 numa_id: int | None
128 Host NUMA node of the allocation. Required for
129 ``location_type="host_numa"`` and rejected for every other location
130 type. Must be >= 0; ``Device.properties.host_numa_id`` is the node
131 closest to a device. On a system without NUMA, ``host_numa_id`` is -1
132 and the driver expects ``numa_id=0``.
133 """
135 allocation_type: VirtualMemoryAllocationType = VirtualMemoryAllocationType.PINNED
136 location_type: VirtualMemoryLocationType = VirtualMemoryLocationType.DEVICE
137 handle_type: VirtualMemoryHandleType = VirtualMemoryHandleType.POSIX_FD
138 granularity: VirtualMemoryGranularityType = VirtualMemoryGranularityType.RECOMMENDED
139 gpu_direct_rdma: bool = False
140 addr_hint: int | None = 0
141 addr_align: int | None = None
142 peers: Iterable[int] = field(default_factory=tuple)
143 self_access: VirtualMemoryAccessType = VirtualMemoryAccessType.READ_WRITE
144 peer_access: VirtualMemoryAccessType = VirtualMemoryAccessType.READ_WRITE
145 numa_id: int | None = None
147 _a = driver.CUmemAccess_flags
148 _access_flags = { # noqa: RUF012
149 VirtualMemoryAccessType.READ_WRITE: _a.CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
150 VirtualMemoryAccessType.READ: _a.CU_MEM_ACCESS_FLAGS_PROT_READ,
151 None: 0,
152 }
153 _h = driver.CUmemAllocationHandleType
154 _handle_types = { # noqa: RUF012
155 None: _h.CU_MEM_HANDLE_TYPE_NONE,
156 VirtualMemoryHandleType.POSIX_FD: _h.CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR,
157 VirtualMemoryHandleType.WIN32_KMT: _h.CU_MEM_HANDLE_TYPE_WIN32_KMT,
158 VirtualMemoryHandleType.FABRIC: _h.CU_MEM_HANDLE_TYPE_FABRIC,
159 }
160 _g = driver.CUmemAllocationGranularity_flags
161 _granularity = { # noqa: RUF012
162 VirtualMemoryGranularityType.RECOMMENDED: _g.CU_MEM_ALLOC_GRANULARITY_RECOMMENDED,
163 VirtualMemoryGranularityType.MINIMUM: _g.CU_MEM_ALLOC_GRANULARITY_MINIMUM,
164 }
165 _l = driver.CUmemLocationType
166 _location_type = { # noqa: RUF012
167 VirtualMemoryLocationType.DEVICE: _l.CU_MEM_LOCATION_TYPE_DEVICE,
168 VirtualMemoryLocationType.HOST: _l.CU_MEM_LOCATION_TYPE_HOST,
169 VirtualMemoryLocationType.HOST_NUMA: _l.CU_MEM_LOCATION_TYPE_HOST_NUMA,
170 VirtualMemoryLocationType.HOST_NUMA_CURRENT: _l.CU_MEM_LOCATION_TYPE_HOST_NUMA_CURRENT,
171 }
172 _t = driver.CUmemAllocationType
173 # CUDA 13 added MANAGED to CUmemAllocationType. The CUDA 12 build has no such member.
174 _allocation_type = {VirtualMemoryAllocationType.PINNED: _t.CU_MEM_ALLOCATION_TYPE_PINNED} # noqa: RUF012
175 if BUILD_CUDA_MAJOR >= 13:
176 _allocation_type[VirtualMemoryAllocationType.MANAGED] = _t.CU_MEM_ALLOCATION_TYPE_MANAGED
178 def __post_init__(self) -> None:
179 # Allocation and pickling each read the peers, so a one-shot iterable
180 # such as a generator must not reach them.
181 self.peers = tuple(int(peer) for peer in self.peers) 2, F - Rb| } Y ~ 0 abL bbcbdbeb3 fb1 gb2 hbibjbV kblbSbmbnbW obpbTbqbrbX sbtbUbubQ vbU wbR xbS ybzbu v N AbO BbT Cby e c [ j k z a A D f h l d b 8 g x G i FbDb_ GbEbHb^ B C H I JbKb. + $ / p q M Z ( ) w E ; = ' s t * o ] P J r K ? n ` { % ! Qb# LbIb9 NbMbPbOb1b@ : 4 m
183 @staticmethod
184 def _access_to_flags(spec: VirtualMemoryAccessType | None) -> int:
185 flags = VirtualMemoryResourceOptions._access_flags.get(spec) 2, F - Y 0 L 3 1 2 V 5 W 6 X 7 Q U R S u v N O T y e c j k z a A D f h l d b 8 g x G i B C H I . + $ / p q M Z ( ) w E ; = ' s t * o P J r K ? n % ! # 9 Wb@ : 4 m
186 if flags is None: 2, F - Y 0 L 3 1 2 V 5 W 6 X 7 Q U R S u v N O T y e c j k z a A D f h l d b 8 g x G i B C H I . + $ / p q M Z ( ) w E ; = ' s t * o P J r K ? n % ! # 9 Wb@ : 4 m
187 raise ValueError(f"Unknown access spec: {spec!r}") 2Wb
188 return flags # type: ignore[no-any-return] 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
190 @staticmethod
191 def _allocation_type_to_driver(spec: VirtualMemoryAllocationType) -> int:
192 alloc_type = VirtualMemoryResourceOptions._allocation_type.get(spec) 2, F - Y 0 L 3 1 2 V 5 W 6 X 7 Q U R S u v N O T y e c j k z a A D f h l d b 8 g x G i B C H I . + $ / p q M Z ( ) w E ; = ' s t * o P J r K ? n % ! # 9 Xb@ : 4 m
193 if alloc_type is None: 2, F - Y 0 L 3 1 2 V 5 W 6 X 7 Q U R S u v N O T y e c j k z a A D f h l d b 8 g x G i B C H I . + $ / p q M Z ( ) w E ; = ' s t * o P J r K ? n % ! # 9 Xb@ : 4 m
194 raise ValueError(f"Unsupported allocation_type: {spec!r}") 2Xb
195 return alloc_type # type: ignore[no-any-return] 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
197 @staticmethod
198 def _location_type_to_driver(spec: VirtualMemoryLocationType) -> int:
199 loc_type = VirtualMemoryResourceOptions._location_type.get(spec) 2, F - Y 0 L 3 1 2 V 5 W 6 X 7 Q U R S u v N O T y e c j k z a A D f h l d b 8 g x G i B C H I . + $ / p q M Z ( ) w E ; = ' s t * o P J r K ? n % ! # 9 Yb@ : 4 m
200 if loc_type is None: 2, F - Y 0 L 3 1 2 V 5 W 6 X 7 Q U R S u v N O T y e c j k z a A D f h l d b 8 g x G i B C H I . + $ / p q M Z ( ) w E ; = ' s t * o P J r K ? n % ! # 9 Yb@ : 4 m
201 raise ValueError(f"Unsupported location_type: {spec!r}") 2Yb
202 return loc_type # type: ignore[no-any-return] 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
204 @staticmethod
205 def _handle_type_to_driver(spec: VirtualMemoryHandleType | None) -> int:
206 if spec == "win32": 2, F - Y 0 L 3 1 2 V 5 W 6 X 7 Q U R S u v N O T y e c [ j k z a A D f h l d b 8 g x G i B C H I . + $ / p q M Z ( ) w E ; = ' s t * o ] P J r K ? n % ! # 9 0bVb@ : 4 m
207 raise NotImplementedError("win32 is currently not supported, please reach out to the CUDA Python team") 20b
208 handle_type = VirtualMemoryResourceOptions._handle_types.get(spec) 2, F - Y 0 L 3 1 2 V 5 W 6 X 7 Q U R S u v N O T y e c [ j k z a A D f h l d b 8 g x G i B C H I . + $ / p q M Z ( ) w E ; = ' s t * o ] P J r K ? n % ! # 9 Vb@ : 4 m
209 if handle_type is None: 2, F - Y 0 L 3 1 2 V 5 W 6 X 7 Q U R S u v N O T y e c [ j k z a A D f h l d b 8 g x G i B C H I . + $ / p q M Z ( ) w E ; = ' s t * o ] P J r K ? n % ! # 9 Vb@ : 4 m
210 raise ValueError(f"Unsupported handle_type: {spec!r}") 2Vb
211 return handle_type # type: ignore[no-any-return] 1,F-Y0L312V5W6X7QURSuvNOTyec[jkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*o]PJrK?n%!#9@:4m
213 @staticmethod
214 def _granularity_to_driver(spec: VirtualMemoryGranularityType) -> int:
215 granularity = VirtualMemoryResourceOptions._granularity.get(spec) 2, F - Y 0 L 3 1 2 V 5 W 6 X 7 Q U R S u v N O T y e c j k z a A D f h l d b 8 g x G i B C H I . + $ / p q M Z ( ) w E ; = ' s t * o P J r K ? n % ! # 9 Zb@ : 4 m
216 if granularity is None: 2, F - Y 0 L 3 1 2 V 5 W 6 X 7 Q U R S u v N O T y e c j k z a A D f h l d b 8 g x G i B C H I . + $ / p q M Z ( ) w E ; = ' s t * o P J r K ? n % ! # 9 Zb@ : 4 m
217 raise ValueError(f"Unsupported granularity: {spec!r}") 2Zb
218 return granularity # type: ignore[no-any-return] 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
221cdef inline size_t _align_up(size_t size, size_t gran) except? 0:
222 """Round ``size`` up to a multiple of ``gran``.
224 Raises ``OverflowError`` instead of wrapping when the rounded size does not
225 fit in ``size_t``.
226 """
227 if size > <size_t>-1 - (gran - 1): 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
228 raise OverflowError(f"size {size} rounded up to the {gran}-byte granularity does not fit in size_t") 14
229 return (size + gran - 1) // gran * gran 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
232cdef bint _stream_is_capturing(cydriver.CUstream s) except -1:
233 cdef cydriver.CUstreamCaptureStatus cap_status
234 IF CUDA_CORE_BUILD_MAJOR >= 13:
235 HANDLE_RETURN(cydriver.cuStreamGetCaptureInfo(s, &cap_status, NULL, NULL, NULL, NULL, NULL)) 1.Z;='st*
236 ELSE:
237 HANDLE_RETURN(cydriver.cuStreamGetCaptureInfo(s, &cap_status, NULL, NULL, NULL, NULL))
238 return cap_status == cydriver.CU_STREAM_CAPTURE_STATUS_ACTIVE 1.Z;='st*
241cdef int _raise_last_error() except -1:
242 """Raise the status a handle factory recorded when it returned empty."""
243 HANDLE_RETURN(get_last_error()) 1ec+$P
244 raise RuntimeError(
245 "internal cuda.core error, please report: a virtual memory handle factory "
246 "returned an empty handle without recording a CUDA error"
247 )
250cdef inline int _attach_note(exc, str detail) except -1:
251 # PEP 678 notes (Python 3.11+) keep the detail separable from the message;
252 # older interpreters get it appended to the message instead.
253 add_note = getattr(exc, "add_note", None) 1eca
254 if add_note is not None: 1eca
255 add_note(detail) 1eca
256 else:
257 exc.args = (f"{exc.args[0]} ({detail})", *exc.args[1:])
258 return 0 1eca
261cdef int _raise_last_error_noting(str detail) except -1:
262 """Raise the status a handle factory recorded, with ``detail`` attached as a note."""
263 try: 1ec
264 _raise_last_error() 1ec
265 except Exception as exc: 1ec
266 _attach_note(exc, detail) 1ec
267 raise 1ec
268 return 0
271cdef int _check_noting(cydriver.CUresult status, str detail) except -1:
272 """``HANDLE_RETURN(status)``, with ``detail`` attached to the raised error as a note."""
273 if status == cydriver.CUDA_SUCCESS: 1Y0L312V5W6X7QURSuvNOTyecjkzaADfhldbgxGiBCHI
274 return 0 1Y0L312V5W6X7QURSuvNOTyecjkzaADfhldbgxGiBCHI
275 try: 1a
276 HANDLE_RETURN(status) 1a
277 except Exception as exc: 1a
278 _attach_note(exc, detail) 1a
279 raise 1a
280 return 0
283cdef str _describe_location(cydriver.CUmemLocation loc):
284 """Name a driver memory location for an error message."""
285 if loc.type == cydriver.CU_MEM_LOCATION_TYPE_DEVICE:
286 return f"device {loc.id}"
287 if loc.type == cydriver.CU_MEM_LOCATION_TYPE_HOST:
288 return "host memory"
289 if loc.type == cydriver.CU_MEM_LOCATION_TYPE_HOST_NUMA:
290 return f"host NUMA node {loc.id}"
291 if loc.type == cydriver.CU_MEM_LOCATION_TYPE_HOST_NUMA_CURRENT:
292 return "the current host NUMA node"
293 return f"location type {<int>loc.type}"
296cdef class VirtualMemoryBuffer(Buffer):
297 """A :class:`Buffer` returned by :class:`VirtualMemoryResource`.
299 The buffer owns its address reservations, physical allocations and
300 mappings through its device pointer handle; closing it is the only way
301 to release them. A buffer returned by
302 :meth:`VirtualMemoryResource.modify_allocation` aliases the buffer it
303 was grown from: the two share their physical memory, and that memory is
304 freed when the last buffer that maps it closes.
305 """
307 @property
308 def ipc_descriptor(self) -> VirtualMemoryFdBufferDescriptor:
309 """A new descriptor for sharing this buffer with other processes.
311 Every access exports the physical allocations that back the buffer
312 again and returns a new descriptor that owns one file descriptor per
313 allocation; the file descriptors close when the descriptor is
314 released. Keep the descriptor in a variable while you use its
315 ``fds``: the numbers from ``buf.ipc_descriptor.fds`` alone may
316 already be closed. A descriptor pins the physical memory while it
317 exists, in every process that holds one, and nothing else does: the
318 buffer itself keeps no descriptor, so it pins nothing beyond its own
319 mapping.
320 Pickling the buffer through ``multiprocessing`` builds a transient
321 descriptor, so the sender holds no file descriptors between sends;
322 a buffer passed as a ``Process`` argument is the exception, because
323 the child receives the file descriptors when it is spawned, after
324 pickling, so that descriptor lives until the ``Process`` object is
325 released. Requires a resource whose ``handle_type`` can be shared; see
326 :attr:`VirtualMemoryResource.is_ipc_enabled`. Import with
327 :meth:`Buffer.from_ipc_descriptor` and a
328 :class:`VirtualMemoryResource` of the receiving process, or send the
329 buffer itself.
331 Raises
332 ------
333 RuntimeError
334 If the resource is not IPC-enabled, or the buffer contains memory
335 imported from another process (an import, or a buffer
336 :meth:`VirtualMemoryResource.modify_allocation` derived from
337 one): such memory cannot be exported again. Forward the descriptor
338 it was imported from, or copy the data into a buffer this process
339 allocated. The check happens before any driver call.
340 """
341 Buffer_check_open(self) 10L12567UNOec[jkafhldbgxGiHI
342 return _export_ipc_descriptor(self) 10L12567UNOec[jkafhldbgxGiHI
344 def __reduce__(self) -> tuple[object, ...]:
345 # Unpickling performs a live import from the descriptor; only deserialize
346 # Buffers from a trusted principal. Must not serialize the parent's stream.
347 Buffer_check_open(self) 1Y3VWXQRSuvNOTyzADbgxBC
348 desc = _export_ipc_descriptor(self) 1Y3VWXQRSuvNOTyzADbgxBC
349 popen = _mp_context.get_spawning_popen() 1Y3VWXQRSuvNOTyzADxBC
350 if popen is not None: 1Y3VWXQRSuvNOTyzADxBC
351 # A Queue, Pipe, or Pool duplicates the file descriptors while this
352 # transient descriptor is alive. A spawned Process does not: it
353 # records their numbers now and hands them to the child when it is
354 # created, after pickling has finished. Keep the descriptor on the
355 # Popen until then; the Popen dies with the Process object.
356 vars(popen).setdefault("_cuda_core_descriptors_until_spawned", []).append(desc) 1VWXuvNyzA
357 return Buffer._reduce_helper, (self.memory_resource, desc) 1Y3VWXQRSuvNOTyzADxBC
359 def close(self, stream: Stream | GraphBuilder | None = None) -> None:
360 """Release this buffer's share of its address range.
362 Each mapping, reservation and physical allocation goes away when the
363 last buffer that holds it closes. Virtual memory is unmapped
364 synchronously, so this call waits for the work queued on the
365 deallocation stream before it releases this buffer's share; a buffer
366 released by the garbage collector waits at that point instead. The
367 unmap cannot be captured. When the stream this close uses, given or recorded, is not
368 a default stream and is capturing, the call raises and leaves the
369 buffer open. A default stream is checked when the range is released
370 instead: if synchronizing it would disturb a capture in its context,
371 the release reports a :class:`CUDAWarning` and unmaps without
372 synchronizing that stream.
374 Parameters
375 ----------
376 stream : :obj:`~_stream.Stream` | :obj:`~graph.GraphBuilder`, optional
377 If given, replaces the recorded deallocation stream, as for
378 :meth:`Buffer.close`.
379 """
380 cdef Stream s
381 cdef StreamHandle h
382 if not self._h_ptr: 1,F-QURSuvNOTyec[jkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9:4m
383 return 1uv.
384 if stream is not None: 1,F-QURSuvNOTyec[jkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9:4m
385 s = Stream_accept(stream) 1'
386 h = s._h_stream 1'
387 else:
388 # Empty when no stream was recorded (host-located memory).
389 h = deallocation_stream(self._h_ptr) 1,F-QURSuvNOTyec[jkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9:4m
390 # Default-stream tokens are checked by the range deleter under their
391 # bound context; a real stream can be checked here and refused.
392 if h and not Stream_handle_is_default_token(as_cu(h)) and _stream_is_capturing(as_cu(h)): 1,F-QURSuvNOTyec[jkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9:4m
393 raise RuntimeError( 1'*
394 "cannot close a VirtualMemoryResource buffer on a capturing stream: "
395 "virtual memory deallocation is synchronous and cannot be captured"
396 )
397 Buffer.close(self, stream) 1,F-QURSuvNOTyec[jkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9:4m
400cdef class VirtualMemoryResource(MemoryResource):
401 """Create a device memory resource that uses the CUDA VMM APIs to allocate memory.
403 Parameters
404 ----------
405 device_id : Device | int
406 Device for which a memory resource is constructed.
408 config : VirtualMemoryResourceOptions, optional
409 A configuration object for the VirtualMemoryResource
412 Warning
413 -------
414 This is a low-level API that is provided only for convenience. Make sure you fully understand
415 how CUDA Virtual Memory Management works before using this. Other MemoryResource subclasses
416 in cuda.core should already meet the common needs.
418 A shared buffer's descriptor comes from the exporting process and is not trusted:
419 :meth:`Buffer.from_ipc_descriptor` checks what it can, and the driver rejects a size that
420 does not match the exported allocation. Unpickling a shared buffer performs a live import,
421 so unpickle buffers only from a trusted principal. A descriptor pins the physical memory
422 while it exists; release it when the receiver has imported it.
424 Notes
425 -----
426 Every buffer this resource returns is a :class:`VirtualMemoryBuffer` that
427 owns its address reservations, physical allocations and mappings; closing
428 the buffer releases them. :meth:`deallocate` is not involved in that path.
430 Virtual memory is unmapped synchronously, so closing a buffer waits for
431 the work queued on its deallocation stream. A buffer released by the
432 garbage collector waits at that point instead. To control when the wait
433 happens, close the buffer explicitly or record an idle stream with
434 :meth:`Buffer.set_deallocation_stream`.
436 Buffers can be shared with other processes when ``config.handle_type``
437 is ``"posix_fd"`` (Linux), the only handle type cuda.core shares today;
438 ``handle_type`` is the switch and :attr:`is_ipc_enabled` the query. The
439 rules that follow describe POSIX file descriptors.
440 :attr:`Buffer.ipc_descriptor` exports the physical allocations that back
441 a buffer, one file descriptor each, into a new descriptor on every
442 access; :meth:`Buffer.from_ipc_descriptor` imports a descriptor with a
443 ``VirtualMemoryResource`` of the receiving process for the device that
444 owns the memory; and a buffer sent through ``multiprocessing`` does both.
445 A pickled resource names its device and its ``peers`` by UUID, and the
446 receiving process rebuilds it for the devices with those UUIDs, whatever
447 their ordinals are there; it raises ``RuntimeError`` if one of the devices
448 is not visible. Plain ``pickle`` cannot carry the file descriptors; a
449 process with its own file descriptor passing sends the descriptor's
450 ``fds``, ``sizes``, and ``size`` and rebuilds it with
451 ``VirtualMemoryFdBufferDescriptor.from_fds()``. The importing resource
452 maps the memory for its device and the devices in its ``peers`` option,
453 with its own access options. A descriptor pins the physical memory while
454 it exists, in every process that holds one, and nothing else does. A
455 buffer that contains imported memory (an import, or anything
456 :meth:`modify_allocation` derived from one) cannot be exported again;
457 forward the descriptor it came from, or copy into a buffer you own.
458 """
460 cdef:
461 readonly object device
462 readonly object config
463 object __weakref__
465 def __init__(self, device_id: Device | int, config: VirtualMemoryResourceOptions | None = None) -> None:
466 self.device = Device(device_id) 2, F - | } ~ abL bbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbu v AbBbCby e c [ j k z a A D f h l d b 8 g x G i FbDb_ GbEbHb^ B C H I JbKb. + $ / p q M Z ( ) w E ; = ' s t * o ] P J r K ? n ` { % ! Qb# LbIb9 NbMbPbOb@ : 4 m
467 self.config = check_or_create_options( 2, F - | } ~ abL bbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbu v AbBbCby e c [ j k z a A D f h l d b 8 g x G i FbDb_ GbEbHb^ B C H I JbKb. + $ / p q M Z ( ) w E ; = ' s t * o ] P J r K ? n ` { % ! Qb# LbIb9 NbMbPbOb@ : 4 m
468 VirtualMemoryResourceOptions, config, "VirtualMemoryResource options", keep_none=False 2, F - | } ~ abL bbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbu v AbBbCby e c [ j k z a A D f h l d b 8 g x G i FbDb_ GbEbHb^ B C H I JbKb. + $ / p q M Z ( ) w E ; = ' s t * o ] P J r K ? n ` { % ! Qb# LbIb9 NbMbPbOb@ : 4 m
469 )
470 if self.config.location_type in _HOST_LOCATION_TYPES: 2, F - | } ~ abL bbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbu v AbBbCby e c [ j k z a A D f h l d b 8 g x G i FbDb_ GbEbHb^ B C H I JbKb. + $ / p q M Z ( ) w E ; = ' s t * o ] P J r K ? n ` { % ! Qb# LbIb9 NbMbPbOb@ : 4 m
471 self.device = None 2Db_ ? n ` { % ! QbMbPbOb
472 if self.device is not None and not self.device.properties.virtual_memory_management_supported: 2, F - | } ~ abL bbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbu v AbBbCby e c [ j k z a A D f h l d b 8 g x G i FbDb_ GbEbHb^ B C H I JbKb. + $ / p q M Z ( ) w E ; = ' s t * o ] P J r K ? n ` { % ! Qb# LbIb9 NbMbPbOb@ : 4 m
473 raise RuntimeError("VirtualMemoryResource requires CUDA VMM API support")
474 self._check_config(self.config) 2, F - | } ~ abL bbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbu v AbBbCby e c [ j k z a A D f h l d b 8 g x G i FbDb_ GbEbHb^ B C H I JbKb. + $ / p q M Z ( ) w E ; = ' s t * o ] P J r K ? n ` { % ! Qb# LbIb9 NbMbPbOb@ : 4 m
476 def __reduce__(self) -> tuple[object, ...]:
477 # The resource holds no driver object that other processes must share;
478 # the receiving process rebuilds an equivalent one from the device and
479 # the options. This is what lets a Buffer pickle as (resource, descriptor).
480 # Device ordinals are per process, so the owning device and the peers
481 # travel by UUID and the receiver maps them to its own devices.
482 owner_uuid = self.device.uuid if self.device is not None else None 2RbY 0 L 3 1 2 V SbW TbX UbQ U R S u v N O T y z A D x ^ B C H I
483 peer_uuids = tuple(Device(peer).uuid for peer in self.config.peers) 2RbY 0 L 3 1 2 V SbW TbX UbQ U R S u v N O T y z A D x ^ B C H I
484 return _reconstruct_virtual_memory_resource, ( 2RbY 0 L 3 1 2 V SbW TbX UbQ U R S u v N O T y z A D x ^ B C H I
485 type(self), owner_uuid, peer_uuids, replace(self.config, peers=())) 2RbY 0 L 3 1 2 V SbW TbX UbQ U R S u v N O T y z A D x ^ B C H I
487 cdef int _check_config(self, object cfg) except -1:
488 """Reject options the driver would reject, before any driver call.
490 Shared by ``__init__`` and ``modify_allocation``, so a per-call
491 configuration is held to the same rules as the resource's own.
492 """
493 if cfg.location_type != self.config.location_type: 2, F - | } ~ abL bbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbu v AbBbCby e c [ j k z a A D f h l d b 8 g x G i FbDb_ GbEbHb^ B C H I JbKb. + $ / p q M Z ( ) w E ; = ' s t * o ] P J r K ? n ` { % ! Qb# LbIb9 NbMbPbOb@ : 4 m
494 raise ValueError( 19
495 f"config.location_type {str(cfg.location_type)!r} does not match the resource's " 19
496 f"{str(self.config.location_type)!r}; the location of a buffer cannot change" 19
497 )
498 if cfg.numa_id != self.config.numa_id: 2, F - | } ~ abL bbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbu v AbBbCby e c [ j k z a A D f h l d b 8 g x G i FbDb_ GbEbHb^ B C H I JbKb. + $ / p q M Z ( ) w E ; = ' s t * o ] P J r K ? n ` { % ! Qb# LbIb9 NbMbPbOb@ : 4 m
499 raise ValueError( 1!
500 f"config.numa_id {cfg.numa_id!r} does not match the resource's " 1!
501 f"{self.config.numa_id!r}; the location of a buffer cannot change" 1!
502 )
503 if cfg.location_type == VirtualMemoryLocationType.HOST_NUMA_CURRENT: 2, F - | } ~ abL bbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbu v AbBbCby e c [ j k z a A D f h l d b 8 g x G i FbDb_ GbEbHb^ B C H I JbKb. + $ / p q M Z ( ) w E ; = ' s t * o ] P J r K ? n ` { % ! Qb# LbIb9 NbMbPbOb@ : 4 m
504 raise ValueError( 2Qb
505 'location_type="host_numa_current" is not supported: the driver does not accept it '
506 'for physical allocations; use location_type="host_numa" with numa_id'
507 )
508 if cfg.location_type == VirtualMemoryLocationType.HOST_NUMA: 2, F - | } ~ abL bbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbu v AbBbCby e c [ j k z a A D f h l d b 8 g x G i FbDb_ GbEbHb^ B C H I JbKb. + $ / p q M Z ( ) w E ; = ' s t * o ] P J r K ? n ` { % ! # LbIb9 NbMbPbOb@ : 4 m
509 if cfg.numa_id is None: 2_ { ! PbOb
510 raise ValueError( 2Pb
511 'location_type="host_numa" requires numa_id; pass numa_id=<host NUMA node id>, '
512 "e.g. device.properties.host_numa_id"
513 )
514 if cfg.numa_id < 0: 2_ { ! Ob
515 raise ValueError(f"numa_id must be >= 0, got {cfg.numa_id}") 2Ob
516 elif cfg.numa_id is not None: 2, F - | } ~ abL bbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbu v AbBbCby e c [ j k z a A D f h l d b 8 g x G i FbDbGbEbHb^ B C H I JbKb. + $ / p q M Z ( ) w E ; = ' s t * o ] P J r K ? n ` % # LbIb9 NbMb@ : 4 m
517 raise ValueError( 2NbMb
518 'numa_id is only valid with location_type="host_numa", ' 2NbMb
519 f"got location_type={str(cfg.location_type)!r}" 2NbMb
520 )
521 # The driver rejects an exportable handle type for HOST memory only;
522 # the NUMA location types may carry one.
523 if cfg.location_type == VirtualMemoryLocationType.HOST and cfg.handle_type is not None: 2, F - | } ~ abL bbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbu v AbBbCby e c [ j k z a A D f h l d b 8 g x G i FbDb_ GbEbHb^ B C H I JbKb. + $ / p q M Z ( ) w E ; = ' s t * o ] P J r K ? n ` { % ! # LbIb9 @ : 4 m
524 raise ValueError( 1`%
525 'virtual memory with location_type="host" cannot have an exportable handle type; '
526 "pass handle_type=None"
527 )
528 # The handle type is part of the resource's sharing contract, like the
529 # location: every chunk of a buffer must be exportable the same way.
530 if cfg.handle_type != self.config.handle_type: 2, F - | } ~ abL bbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbu v AbBbCby e c [ j k z a A D f h l d b 8 g x G i FbDb_ GbEbHb^ B C H I JbKb. + $ / p q M Z ( ) w E ; = ' s t * o ] P J r K ? n ` { % ! # LbIb9 @ : 4 m
531 raise ValueError( 18
532 f"config.handle_type {str(cfg.handle_type)!r} does not match the resource's " 18
533 f"{str(self.config.handle_type)!r}; the handle type of a buffer's chunks cannot change" 18
534 )
535 if cfg.gpu_direct_rdma and self.device is not None and not self.device.properties.gpu_direct_rdma_supported: 2, F - | } ~ abL bbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbu v AbBbCby e c [ j k z a A D f h l d b 8 g x G i FbDb_ GbEbHb^ B C H I JbKb. + $ / p q M Z ( ) w E ; = ' s t * o ] P J r K ? n ` { % ! # LbIb9 @ : 4 m
536 raise RuntimeError("GPU Direct RDMA is not supported on this device")
537 return 0 2, F - | } ~ abL bbcbdbebfbgbhbibjbkblbmbnbobpbqbrbsbtbubvbwbxbybzbu v AbBbCby e c [ j k z a A D f h l d b 8 g x G i FbDb_ GbEbHb^ B C H I JbKb. + $ / p q M Z ( ) w E ; = ' s t * o ] P J r K ? n ` { % ! # LbIb9 @ : 4 m
539 cdef int _fill_prop(self, object cfg, cydriver.CUmemAllocationProp* prop) except -1:
540 # The location comes from the resource; the rest may come from a
541 # per-call configuration.
542 cdef int alloc_type = int(VirtualMemoryResourceOptions._allocation_type_to_driver(cfg.allocation_type)) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
543 cdef int loc_type = int(VirtualMemoryResourceOptions._location_type_to_driver(self.config.location_type)) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
544 cdef int handle_type = int(VirtualMemoryResourceOptions._handle_type_to_driver(cfg.handle_type)) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
545 memset(prop, 0, sizeof(cydriver.CUmemAllocationProp)) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
546 prop.type = <cydriver.CUmemAllocationType>alloc_type 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
547 prop.location.type = <cydriver.CUmemLocationType>loc_type 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
548 if self.config.location_type == VirtualMemoryLocationType.HOST_NUMA: 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
549 prop.location.id = self.config.numa_id 1!
550 else:
551 prop.location.id = self.device.device_id if self.device is not None else -1 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%#9@:4m
552 prop.allocFlags.gpuDirectRDMACapable = 1 if cfg.gpu_direct_rdma else 0 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
553 prop.requestedHandleTypes = <cydriver.CUmemAllocationHandleType>handle_type 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
554 prop.win32HandleMetaData = NULL 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
555 return 0 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
557 cdef int _fill_access(
558 self, object cfg, const cydriver.CUmemAllocationProp* prop,
559 vector[cydriver.CUmemAccessDesc]& descs,
560 ) except -1:
561 cdef cydriver.CUmemAccessDesc d
562 cdef int owner_flags = int(VirtualMemoryResourceOptions._access_to_flags(cfg.self_access)) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
563 cdef int peer_flags = int(VirtualMemoryResourceOptions._access_to_flags(cfg.peer_access)) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
564 if owner_flags: 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
565 memset(&d, 0, sizeof(d)) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqZ()wE;='st*oPJrK?n%!#9@:4m
566 d.location.type = prop.location.type 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqZ()wE;='st*oPJrK?n%!#9@:4m
567 d.location.id = prop.location.id 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqZ()wE;='st*oPJrK?n%!#9@:4m
568 d.flags = <cydriver.CUmemAccess_flags>owner_flags 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqZ()wE;='st*oPJrK?n%!#9@:4m
569 descs.push_back(d) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqZ()wE;='st*oPJrK?n%!#9@:4m
570 if peer_flags: 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
571 for peer_dev in cfg.peers: 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
572 memset(&d, 0, sizeof(d)) 1$
573 d.location.type = cydriver.CU_MEM_LOCATION_TYPE_DEVICE 1$
574 d.location.id = int(peer_dev) 1$
575 d.flags = <cydriver.CUmemAccess_flags>peer_flags 1$
576 descs.push_back(d) 1$
577 return 0 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
579 cdef size_t _granularity(self, object cfg, const cydriver.CUmemAllocationProp* prop) except? 0:
580 cdef size_t gran = 0 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
581 cdef int flag = int(VirtualMemoryResourceOptions._granularity_to_driver(cfg.granularity)) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
582 with nogil: 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
583 HANDLE_RETURN(cydriver.cuMemGetAllocationGranularity( 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
584 &gran, prop, <cydriver.CUmemAllocationGranularity_flags>flag))
585 return gran 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
587 cdef int _record_deallocation_stream(self, const DevicePtrHandle& h_ptr, Stream s) except -1:
588 cdef StreamHandle h
589 cdef ContextHandle h_ctx
590 if s is not None and not Stream_is_default_token(s): 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
591 h = s._h_stream 13.;=st
592 elif self.device is None: 1,F-Y0L12V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI+$/pqMZ()wE'*oPJrK?n%!#9@:4m
593 # Host-located memory needs no context to free, and a default-stream
594 # token has no context to bind to: record nothing.
595 return 0 1?n%!
596 else:
597 # Bind the legacy default-stream token to the device's primary
598 # context so the free is ordered correctly no matter what is current
599 # then, and so allocate() never depends on a current context.
600 # get_primary_context keeps its own cache; nothing is cached here.
601 h_ctx = get_primary_context(self.device.device_id) 1,F-Y0L12V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI+$/pqMZ()wE'*oPJrK#9@:4m
602 if not h_ctx: 1,F-Y0L12V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI+$/pqMZ()wE'*oPJrK#9@:4m
603 _raise_last_error()
604 h = create_context_bound_legacy_stream(h_ctx) 1,F-Y0L12V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI+$/pqMZ()wE'*oPJrK#9@:4m
605 if not h: 1,F-Y0L12V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI+$/pqMZ()wE'*oPJrK#9@:4m
606 _raise_last_error()
607 HANDLE_RETURN(set_deallocation_stream(h_ptr, h)) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK#9@:4m
608 return 0 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK#9@:4m
610 cdef int _copy_deallocation_stream(self, const DevicePtrHandle& dst, const DevicePtrHandle& src) except -1:
611 cdef StreamHandle h = deallocation_stream(src) 1FcjkafldbpqMZwEstoJrKnm
612 if h: 1FcjkafldbpqMZwEstoJrKnm
613 HANDLE_RETURN(set_deallocation_stream(dst, h)) 1FcjkafldbpqMZwEstoJrKm
614 return 0 1FcjkafldbpqMZwEstoJrKnm
616 def allocate(self, size_t size, *, stream: Stream | GraphBuilder | None = None) -> VirtualMemoryBuffer:
617 """
618 Allocate a buffer of the given size using CUDA virtual memory.
620 Parameters
621 ----------
622 size : int
623 The size in bytes of the buffer to allocate. It is rounded up to the
624 allocation granularity; the returned buffer reports the rounded size.
625 stream : :obj:`~_stream.Stream` | :obj:`~graph.GraphBuilder`, optional
626 Keyword-only. The allocation itself is synchronous. A real stream is
627 recorded as the buffer's deallocation stream and synchronized when
628 the buffer closes; with `None` or a default-stream token the legacy
629 default stream of the resource's device is recorded instead. A
630 host-located resource records no default stream: its buffers close
631 without a synchronization unless a real stream was given.
633 Returns
634 -------
635 VirtualMemoryBuffer
636 A buffer that owns its reservation, physical allocation and mapping.
638 Raises
639 ------
640 CUDAError
641 If any CUDA driver API call fails during allocation. Nothing is
642 left allocated when this method raises.
643 OverflowError
644 If ``size`` rounded up to the granularity does not fit in ``size_t``.
645 """
646 cdef Stream s = None 1,F-Y0L312V5W6X7QURSuvNOTyec[jkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
647 if stream is not None: 1,F-Y0L312V5W6X7QURSuvNOTyec[jkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
648 s = Stream_accept(stream) 1,FY0L312V5W6X7QURSuvNOTBCHI.Z;=st
649 return self._allocate(self.config, size, s) 1,F-Y0L312V5W6X7QURSuvNOTyec[jkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
651 cdef Buffer _allocate(self, object cfg, size_t size, Stream s):
652 """Allocate ``size`` bytes with ``cfg``; ``s`` is the accepted stream or None."""
653 cdef cydriver.CUmemAllocationProp prop
654 cdef vector[cydriver.CUmemAccessDesc] descs
655 cdef size_t gran, aligned, addr_align
656 cdef cydriver.CUdeviceptr hint
657 cdef MemAllocationHandle h_alloc
658 cdef VaReservationHandle h_res
659 cdef VaMappingHandle h_map
660 cdef vector[VaMappingHandle] mappings
661 cdef VmmRangeHandle rng
662 cdef DevicePtrHandle h_ptr
664 if size == 0: 1,F-Y0L312V5W6X7QURSuvNOTyec[jkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
665 # Nothing to reserve or map: a VMM box over an empty range, so the
666 # buffer is a VirtualMemoryBuffer like every other. A real stream is
667 # still recorded so that a later grow inherits it; a default-stream
668 # token is not, which keeps this path free of driver calls.
669 h_ptr = deviceptr_create_vmm(0, create_vmm_range(mappings)) 1[Z
670 if s is not None and not Stream_is_default_token(s): 1[Z
671 HANDLE_RETURN(set_deallocation_stream(h_ptr, s._h_stream)) 1Z
672 return Buffer_from_deviceptr_handle(h_ptr, 0, self, None, VirtualMemoryBuffer) 1[Z
674 self._fill_prop(cfg, &prop) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
675 self._fill_access(cfg, &prop, descs) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
676 gran = self._granularity(cfg, &prop) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
677 aligned = _align_up(size, gran) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
678 addr_align = cfg.addr_align or gran 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
679 hint = cfg.addr_hint or 0 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
681 # Every handle below is a local: if a later step fails, the locals die
682 # in reverse order and undo everything created so far.
683 with nogil: 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
684 h_alloc = create_mem_allocation_handle(aligned, prop, descs.data(), descs.size()) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
685 if not h_alloc: 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
686 _raise_last_error()
687 with nogil: 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
688 h_res = create_va_reservation_handle(aligned, addr_align, hint) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
689 if not h_res: 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
690 _raise_last_error() 1+
691 with nogil: 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
692 h_map = create_va_mapping_handle(as_cu(h_res), h_alloc, h_res) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
693 if not h_map: 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
694 _raise_last_error() 1$
696 mappings.push_back(h_map) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
697 rng = create_vmm_range(mappings) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
698 h_ptr = deviceptr_create_vmm(as_cu(h_res), rng) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
699 if not h_ptr: 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
700 _raise_last_error()
701 self._record_deallocation_stream(h_ptr, s) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
702 return Buffer_from_deviceptr_handle(h_ptr, aligned, self, None, VirtualMemoryBuffer) 1,F-Y0L312V5W6X7QURSuvNOTyecjkzaADfhldb8gxGiBCHI.+$/pqMZ()wE;='st*oPJrK?n%!#9@:4m
704 def modify_allocation(
705 self, Buffer buf not None: Buffer, size_t new_size, config: VirtualMemoryResourceOptions | None = None
706 ) -> VirtualMemoryBuffer:
707 """
708 Grow a buffer of this resource to at least ``new_size`` bytes.
710 The buffer passed in stays open and usable, and is never returned. The
711 returned buffer aliases it: both map the same physical memory, which is
712 freed when the last of the two closes. When the driver can extend the
713 address range in place, the returned buffer has the same pointer;
714 otherwise it has a new one and the existing contents are reachable
715 through both. Closing the returned buffer never closes ``buf``. A
716 buffer grown from an imported buffer maps imported memory and cannot
717 be exported again.
719 Concurrent calls on buffers that alias one another are safe.
721 Parameters
722 ----------
723 buf : VirtualMemoryBuffer
724 A buffer returned by :meth:`allocate` or by this method.
725 new_size : int
726 The requested total size in bytes; rounded up to the granularity.
727 config : VirtualMemoryResourceOptions, optional
728 Configuration for the new physical memory chunk only. Existing
729 chunks keep the access they were created with, and the resource's
730 own configuration is unchanged. It must name the resource's
731 ``location_type``, ``handle_type``, and ``numa_id`` and pass the
732 same checks as the constructor.
733 When ``buf`` already covers ``new_size`` there is no new chunk, so
734 ``config`` has no effect. This method never changes the access of
735 memory that is already mapped.
737 Returns
738 -------
739 VirtualMemoryBuffer
740 A new buffer of at least ``new_size`` and at least ``buf.size``
741 bytes. When ``buf`` already covers the request, the result is a
742 full alias of it and no driver call is made.
744 Raises
745 ------
746 TypeError
747 If ``buf`` did not come from this resource.
748 ValueError
749 If ``config`` names a different location type, handle type, or
750 ``numa_id`` than the resource, or the constructor would reject it.
751 RuntimeError
752 If ``buf`` is closed, or ``config`` requests GPUDirect RDMA on a
753 device without support.
754 OverflowError
755 If ``new_size`` rounded up to the granularity does not fit in
756 ``size_t``.
757 CUDAError
758 If a driver call fails. ``buf`` is untouched when this method raises.
759 """
760 cdef Buffer b
761 cdef DevicePtrHandle h_in
762 cdef size_t in_size
763 cdef VmmRangeHandle rng, rng_new
764 cdef object cfg
765 cdef cydriver.CUmemAllocationProp prop
766 cdef vector[cydriver.CUmemAccessDesc] descs
767 cdef vector[VaMappingHandle] mappings
768 cdef size_t gran, req, total, add, addr_align, offset, i
769 cdef cydriver.CUdeviceptr base, base_new
770 cdef MemAllocationHandle h_alloc, a
771 cdef VaReservationHandle h_res, h_res_new
772 cdef VaMappingHandle h_map, m2
773 cdef DevicePtrHandle h_ptr_new
774 cdef object new_buf
776 # Every VirtualMemoryBuffer handle comes from deviceptr_create_vmm, so
777 # the class check is what makes vmm_range() below valid.
778 if not isinstance(buf, VirtualMemoryBuffer): 2F c j k a f l d b 8 p q M Z w E s t o P J r K n % ! # Ib9 4 m
779 raise TypeError(f"buf must be a VirtualMemoryBuffer, got {type(buf).__name__}") 2# Ib
780 b = <Buffer>buf 1Fcjkafldb8pqMZwEstoPJrKn%!#94m
781 Buffer_check_open(b) 1Fcjkafldb8pqMZwEstoPJrKn%!#94m
782 # Read the input's handle and size once. The handle copy keeps the
783 # input's box alive for the rest of the call, so a close from another
784 # thread (unsupported; see concurrency.rst) defers the release instead
785 # of emptying what this call reads.
786 h_in = b._h_ptr 1Fcjkafldb8pqMZwEstoPJrKn%!#94m
787 in_size = b._size 1Fcjkafldb8pqMZwEstoPJrKn%!#94m
788 if b.memory_resource is not self: 1Fcjkafldb8pqMZwEstoPJrKn%!#94m
789 raise TypeError("buf was not allocated by this VirtualMemoryResource") 1#
790 cfg = self.config if config is None else check_or_create_options( 1Fcjkafldb8pqMZwEstoPJrKn%!94m
791 VirtualMemoryResourceOptions, config, "VirtualMemoryResource options", keep_none=False 18E%!9
792 )
793 self._check_config(cfg) 1Fcjkafldb8pqMZwEstoPJrKn%!94m
794 if in_size == 0: 1FcjkafldbpqMZwEstoPJrKn4m
795 # An empty buffer maps nothing; the request is a fresh allocation
796 # that inherits the stream the empty buffer recorded, if any.
797 new_buf = self._allocate(cfg, new_size, None) 1Z
798 self._copy_deallocation_stream((<Buffer>new_buf)._h_ptr, h_in) 1Z
799 return new_buf 1Z
800 rng = vmm_range(h_in) 1FcjkafldbpqMwEstoPJrKn4m
802 self._fill_prop(cfg, &prop) 1FcjkafldbpqMwEstoPJrKn4m
803 self._fill_access(cfg, &prop, descs) 1FcjkafldbpqMwEstoPJrKn4m
804 gran = self._granularity(cfg, &prop) 1FcjkafldbpqMwEstoPJrKn4m
805 req = _align_up(new_size, gran) 1FcjkafldbpqMwEstoPJrKn4m
806 total = vmm_range_total(rng) 1FcjkafldbpqMwEstoPJrKnm
807 base = as_cu(h_in) 1FcjkafldbpqMwEstoPJrKnm
809 if req <= total: 1FcjkafldbpqMwEstoPJrKnm
810 # The range already maps the request: a new buffer over the same
811 # range, at least as large as the input, with no driver call. The
812 # result is a distinct object, so closing it never closes ``buf``.
813 h_ptr_new = deviceptr_create_vmm(base, rng) 1bwo
814 if not h_ptr_new: 1bwo
815 _raise_last_error()
816 self._copy_deallocation_stream(h_ptr_new, h_in) 1bwo
817 return Buffer_from_deviceptr_handle( 1bwo
818 h_ptr_new, req if req > in_size else in_size, self, None, VirtualMemoryBuffer) 1bwo
820 # The new chunk is a whole number of granules; the result covers it.
821 add = _align_up(req - total, gran) 1FcjkafldbpqMwEstoPJrKnm
822 req = total + add 1FcjkafldbpqMwEstoPJrKnm
823 # The result's range is built from a copy of the input's mappings; the
824 # input's range is immutable and never changes.
825 mappings = vmm_range_mappings(rng) 1FcjkafldbpqMwEstoPJrKnm
827 # Grow in place: reserve the range right after the current one. The
828 # driver raises alignment 0 to its default, so the hint is well formed.
829 with nogil: 1FcjkafldbpqMwEstoPJrKnm
830 h_res = create_va_reservation_handle(add, 0, base + total) 1FcjkafldbpqMwEstoPJrKnm
831 if not h_res: 1FcjkafldbpqMwEstoPJrKnm
832 get_last_error() # the probe may fail; that is not an error here 1P
833 elif as_cu(h_res) != base + total: 1FcjkafldbpqMwEstoJrKnm
834 h_res.reset() # granted elsewhere: free it and move instead 1afbpqstornm
835 else:
836 with nogil: 1FcjkafldMwEJKnm
837 h_alloc = create_mem_allocation_handle(add, prop, descs.data(), descs.size()) 1FcjkafldMwEJKnm
838 if not h_alloc: 1FcjkafldMwEJKnm
839 _raise_last_error()
840 with nogil: 1FcjkafldMwEJKnm
841 h_map = create_va_mapping_handle(base + total, h_alloc, h_res) 1FcjkafldMwEJKnm
842 if not h_map: 1FcjkafldMwEJKnm
843 _raise_last_error()
844 mappings.push_back(h_map) 1FcjkafldMwEJKnm
845 rng_new = create_vmm_range(mappings) 1FcjkafldMwEJKnm
846 h_ptr_new = deviceptr_create_vmm(base, rng_new) 1FcjkafldMwEJKnm
847 if not h_ptr_new: 1FcjkafldMwEJKnm
848 _raise_last_error()
849 self._copy_deallocation_stream(h_ptr_new, h_in) 1FcjkafldMwEJKnm
850 return Buffer_from_deviceptr_handle(h_ptr_new, req, self, None, VirtualMemoryBuffer) 1FcjkafldMwEJKnm
852 # Move: a new range that maps every existing allocation, then the new one.
853 # The allocations are shared with the input buffer's range.
854 addr_align = cfg.addr_align or gran 1afbpqstoPrnm
855 with nogil: 1afbpqstoPrnm
856 h_res_new = create_va_reservation_handle(req, addr_align, 0) 1afbpqstoPrnm
857 if not h_res_new: 1afbpqstoPrnm
858 _raise_last_error() 1P
859 base_new = as_cu(h_res_new) 1afbpqstornm
860 offset = 0 1afbpqstornm
861 for i in range(mappings.size()): 1afbpqstornm
862 a = va_mapping_allocation(mappings[i]) 1afbpqstornm
863 with nogil: 1afbpqstornm
864 m2 = create_va_mapping_handle(base_new + offset, a, h_res_new) 1afbpqstornm
865 if not m2: 1afbpqstornm
866 _raise_last_error()
867 offset += mem_allocation_size(a) 1afbpqstornm
868 mappings[i] = m2 # the input's range still holds the old mapping 1afbpqstornm
869 with nogil: 1afbpqstornm
870 h_alloc = create_mem_allocation_handle(add, prop, descs.data(), descs.size()) 1afbpqstornm
871 if not h_alloc: 1afbpqstornm
872 _raise_last_error()
873 with nogil: 1afbpqstornm
874 m2 = create_va_mapping_handle(base_new + offset, h_alloc, h_res_new) 1afbpqstornm
875 if not m2: 1afbpqstornm
876 _raise_last_error()
877 mappings.push_back(m2) 1afbpqstornm
878 rng_new = create_vmm_range(mappings) 1afbpqstornm
879 h_ptr_new = deviceptr_create_vmm(base_new, rng_new) 1afbpqstornm
880 if not h_ptr_new: 1afbpqstornm
881 _raise_last_error()
882 self._copy_deallocation_stream(h_ptr_new, h_in) 1afbpqstornm
883 return Buffer_from_deviceptr_handle(h_ptr_new, req, self, None, VirtualMemoryBuffer) 1afbpqstornm
885 def deallocate(self, ptr: DevicePointerType, size: int, *, stream: Stream | GraphBuilder | None = None) -> None:
886 """
887 Unmap and free one address range that was reserved and mapped outside this resource.
889 Buffers returned by :meth:`allocate` and :meth:`modify_allocation` free
890 themselves when they close and never call this method. It exists for
891 raw pointers wrapped with :meth:`Buffer.from_handle` with ``mr`` set to
892 this resource: the range must be exactly one reservation, and the caller
893 must already have released its own ``cuMemCreate`` handle, so the
894 physical memory is freed by the unmap.
896 Parameters
897 ----------
898 ptr : DevicePointerType
899 The start of the reservation.
900 size : int
901 The size of the reservation in bytes.
902 stream : :obj:`~_stream.Stream` | :obj:`~graph.GraphBuilder`, optional
903 Keyword-only. If given, ``stream.sync()`` is called before the
904 range is unmapped, except for a default-stream token on a
905 host-located resource, which has no context to synchronize in.
906 """
907 cdef cydriver.CUdeviceptr devptr = 0 if ptr is None else <cydriver.CUdeviceptr>int(ptr) 1]
908 cdef size_t nbytes = size 1]
909 cdef Stream s
910 if stream is not None: 1]
911 s = Stream_accept(stream) 1]
912 # A host-located resource records no stream, so Buffer teardown
913 # passes an unbound default-stream token, which has no context to
914 # synchronize in. There is nothing queued on it to wait for.
915 if self.device is not None or not Stream_is_default_token(s): 1]
916 s.sync() 1]
917 if devptr == 0 or nbytes == 0: 1]
918 return
919 with nogil: 1]
920 HANDLE_RETURN(cydriver.cuMemUnmap(devptr, nbytes)) 1]
921 HANDLE_RETURN(cydriver.cuMemAddressFree(devptr, nbytes)) 1]
923 @property
924 def is_device_accessible(self) -> bool:
925 """
926 Indicates whether the allocated memory is accessible from the device.
927 """
928 return self.config.location_type == "device" 1]n
930 @property
931 def is_host_accessible(self) -> bool:
932 """
933 Indicates whether the allocated memory is accessible from the host.
934 """
935 return self.config.location_type in _HOST_LOCATION_TYPES 1?n`{!
937 @property
938 def is_ipc_enabled(self) -> bool:
939 """Whether buffers of this resource can be shared with other processes.
941 True when ``config.handle_type`` is ``"posix_fd"`` on Linux and the
942 memory lives on a device; ``handle_type`` is the only switch, there is
943 no separate option. With ``handle_type=None`` the allocations cannot
944 be exported, Win32 KMT handles are not transported by cuda.core, and
945 fabric handles are not supported yet. Host-located virtual memory
946 (``location_type`` ``"host"``, ``"host_numa"``, or
947 ``"host_numa_current"``) is not shared yet.
948 """
949 return ( 2| } Y ~ 0 abL bbcbdbeb3 fb1 gb2 hbibjbV kb5 lbmbnbW ob6 pbqbrbX sb7 tbubQ vbU wbR xbS ybzbu v N AbO BbT Cby e c [ j k z a A D f h l d b g x G i FbDb_ GbEbHbB C H I ( )
950 self.config.handle_type in _IPC_HANDLE_TYPES 2| } Y ~ 0 abL bbcbdbeb3 fb1 gb2 hbibjbV kb5 lbmbnbW ob6 pbqbrbX sb7 tbubQ vbU wbR xbS ybzbu v N AbO BbT Cby e c [ j k z a A D f h l d b g x G i FbDb_ GbEbHbB C H I ( )
951 and self.config.location_type not in _HOST_LOCATION_TYPES 2| } Y ~ 0 abL bbcbdbeb3 fb1 gb2 hbibjbV kb5 lbmbnbW ob6 pbqbrbX sb7 tbubQ vbU wbR xbS ybzbu v N AbO BbT Cby e c [ j k z a A D f h l d b g x G i _ EbB C H I ( )
952 )
954 def _import_ipc_buffer(self, VirtualMemoryFdBufferDescriptor desc not None, stream) -> VirtualMemoryBuffer:
955 """Import a buffer that another process exported; see :meth:`Buffer.from_ipc_descriptor`.
957 Every exported allocation is imported with this resource's handle
958 type and checked to live on this resource's device. The allocations
959 are then mapped in order into
960 one new address reservation with the access descriptors this
961 resource's options produce for its device and peers, and the
962 deallocation stream is recorded as :meth:`allocate` does. The
963 returned buffer owns the imported handles like a buffer from
964 :meth:`allocate` owns created ones, reports ``is_mapped``, and cannot
965 be exported again.
966 """
967 cdef Stream s = None 1ec[ahdbgGi
968 cdef object cfg = self.config 1ec[ahdbgGi
969 cdef cydriver.CUmemAllocationProp prop
970 cdef cydriver.CUmemAllocationProp imported_prop
971 cdef vector[cydriver.CUmemAccessDesc] descs
972 cdef vector[MemAllocationHandle] allocs
973 cdef vector[VaMappingHandle] mappings
974 cdef MemAllocationHandle h_alloc
975 cdef VaReservationHandle h_res
976 cdef VaMappingHandle h_map
977 cdef VmmRangeHandle rng
978 cdef DevicePtrHandle h_ptr
979 cdef Buffer buf
980 cdef size_t gran, gran_min = 0, addr_align, chunk, total = 0, offset = 0 1ec[ahdbgGi
981 cdef Py_ssize_t n, i
982 cdef cydriver.CUdeviceptr hint
983 cdef int handle_type, fd
984 cdef void* os_handle
986 if not self.is_ipc_enabled: 1ec[ahdbgGi
987 raise RuntimeError("Memory resource is not IPC-enabled") 1G
988 handle_type = int(VirtualMemoryResourceOptions._handle_type_to_driver(cfg.handle_type)) 1ec[ahdbgi
989 if desc._handle_type != handle_type: 1ec[ahdbgi
990 raise ValueError( 1e
991 "the descriptor's handle type does not match this resource's " 1e
992 f"handle_type={str(cfg.handle_type)!r}; import it with a resource configured like the exporter's" 1e
993 )
994 n = len(desc._chunk_sizes) 1ec[ahdbgi
995 if n != len(desc._handles): 1ec[ahdbgi
996 raise ValueError("malformed VirtualMemoryResource buffer descriptor") 1e
997 if stream is not None: 1ec[ahdbgi
998 s = Stream_accept(stream) 1ec[ahdbgi
999 if n == 0: 1ec[ahdbgi
1000 if desc._size != 0: 1e[
1001 raise ValueError( 1e
1002 f"malformed VirtualMemoryResource buffer descriptor: a size of {desc._size} bytes with no " 1e
1003 "exported allocations"
1004 )
1005 # An exported empty buffer: an empty range, as allocate(0) returns.
1006 h_ptr = deviceptr_create_vmm(0, create_vmm_range(mappings)) 1[
1007 if s is not None and not Stream_is_default_token(s): 1[
1008 HANDLE_RETURN(set_deallocation_stream(h_ptr, s._h_stream))
1009 buf = Buffer_from_deviceptr_handle(h_ptr, 0, self, None, VirtualMemoryBuffer) 1[
1010 buf._ipc_data = IPCDataForBuffer(None, True) 1[
1011 return buf 1[
1013 self._fill_prop(cfg, &prop) 1ecahdbgi
1014 self._fill_access(cfg, &prop, descs) 1ecahdbgi
1015 gran = self._granularity(cfg, &prop) 1ecahdbgi
1016 # cuMemMap requires the minimum granularity, whatever this resource's
1017 # granularity option says; the exporter may have used either.
1018 with nogil: 1ecahdbgi
1019 HANDLE_RETURN(cydriver.cuMemGetAllocationGranularity( 1ecahdbgi
1020 &gran_min, &prop, cydriver.CU_MEM_ALLOC_GRANULARITY_MINIMUM))
1021 for chunk_obj in desc._chunk_sizes: 1ecahdbgi
1022 chunk = chunk_obj 1ecahdbgi
1023 if chunk == 0 or chunk % gran_min != 0: 1ecahdbgi
1024 raise ValueError( 1e
1025 f"descriptor chunk size {chunk} is not a positive multiple of the {gran_min}-byte minimum " 1e
1026 "granularity"
1027 )
1028 if chunk > <size_t>-1 - total: 1ecahdbgi
1029 raise OverflowError("the descriptor's chunk sizes do not fit in size_t")
1030 total += chunk 1ecahdbgi
1031 if desc._size > total: 1ecahdbgi
1032 raise ValueError(f"descriptor size ({desc._size}) exceeds the exported allocations ({total} bytes)") 1e
1033 addr_align = cfg.addr_align or gran 1ecahdbgi
1034 hint = cfg.addr_hint or 0 1ecahdbgi
1036 # Every handle below is a local: if a later step fails, the locals die
1037 # in reverse order and release everything imported so far.
1038 #
1039 # The driver copies each shared allocation into a handle of its own
1040 # during cuMemImportFromShareableHandle and keeps no reference to the
1041 # file descriptor afterwards: the shareable handle and the allocation
1042 # handle are independent references to the memory, and the CUDA
1043 # samples and PyTorch close the descriptor right after the import. The
1044 # buffer therefore does not keep the descriptor; its file descriptors
1045 # live only as long as the caller holds the descriptor object.
1046 for i in range(n): 1ecahdbgi
1047 fd = int(desc._handles[i]) # raises if the handle was closed 1ecahdbgi
1048 chunk = desc._chunk_sizes[i] 1ecahdbgi
1049 os_handle = <void*><uintptr_t>fd 1ecahdbgi
1050 with nogil: 1ecahdbgi
1051 h_alloc = import_mem_allocation_handle( 1ecahdbgi
1052 os_handle, <cydriver.CUmemAllocationHandleType>handle_type, chunk, descs.data(), descs.size())
1053 if not h_alloc: 1ecahdbgi
1054 _raise_last_error_noting( 1e
1055 f"while importing chunk {i + 1} of {n} of a VirtualMemoryResource buffer descriptor; either " 1e
1056 "the shared handle does not refer to a CUDA allocation exported with "
1057 f"handle_type={str(cfg.handle_type)!r}, or the device that owns the memory is not usable in " 1e
1058 "this process (for example, CUDA_VISIBLE_DEVICES hides it)"
1059 )
1060 # The exporter chose where the memory lives. This resource must map
1061 # memory of that kind on that device, or its access descriptors
1062 # would describe the wrong location.
1063 with nogil: 1ecahdbgi
1064 HANDLE_RETURN(cydriver.cuMemGetAllocationPropertiesFromHandle(&imported_prop, as_cu(h_alloc))) 1ecahdbgi
1065 if imported_prop.location.type != prop.location.type or ( 1ecahdbgi
1066 prop.location.type == cydriver.CU_MEM_LOCATION_TYPE_DEVICE 1ecahdbgi
1067 and imported_prop.location.id != prop.location.id 1ecahdbgi
1068 ):
1069 raise ValueError(
1070 f"chunk {i + 1} of the descriptor is memory on {_describe_location(imported_prop.location)}, "
1071 f"but this resource maps memory on {_describe_location(prop.location)}; import the descriptor "
1072 "with a VirtualMemoryResource for the device that owns the memory, and name other devices "
1073 "in its peers option"
1074 )
1075 allocs.push_back(h_alloc) 1ecahdbgi
1076 with nogil: 1ecahdbgi
1077 h_res = create_va_reservation_handle(total, addr_align, hint) 1ecahdbgi
1078 if not h_res: 1ecahdbgi
1079 _raise_last_error()
1080 for i in range(n): 1ecahdbgi
1081 chunk = mem_allocation_size(allocs[<size_t>i]) 1ecahdbgi
1082 with nogil: 1ecahdbgi
1083 h_map = create_va_mapping_handle(as_cu(h_res) + offset, allocs[<size_t>i], h_res) 1ecahdbgi
1084 if not h_map: 1ecahdbgi
1085 _raise_last_error_noting( 1c
1086 f"while mapping chunk {i + 1} of {n} ({chunk} bytes) of a VirtualMemoryResource buffer " 1c
1087 "descriptor; a chunk size that does not match the exported allocation fails here, and the "
1088 "sizes come from the exporting peer"
1089 )
1090 mappings.push_back(h_map) 1ecahdbgi
1091 offset += chunk 1ecahdbgi
1092 rng = create_vmm_range(mappings) 1ecahdbgi
1093 h_ptr = deviceptr_create_vmm(as_cu(h_res), rng) 1ecahdbgi
1094 if not h_ptr: 1ecahdbgi
1095 _raise_last_error()
1096 self._record_deallocation_stream(h_ptr, s) 1ecahdbgi
1097 buf = Buffer_from_deviceptr_handle(h_ptr, desc._size, self, None, VirtualMemoryBuffer) 1ecahdbgi
1098 buf._ipc_data = IPCDataForBuffer(None, True) 1ecahdbgi
1099 return buf 1ecahdbgi
1101 @property
1102 def device_id(self) -> int:
1103 """
1104 Get the device ID associated with this memory resource.
1106 Returns:
1107 int: CUDA device ID. -1 if the memory resource allocates host memory
1108 """
1109 return self.device.device_id if self.device is not None else -1 1^()En`{
1111 def __repr__(self) -> str:
1112 """
1113 Return a string representation of the VirtualMemoryResource.
1115 Returns:
1116 str: A string describing the object
1117 """
1118 return f"<VirtualMemoryResource device={self.device}>"
1121def _reconstruct_virtual_memory_resource(
1122 cls: type, owner_uuid: str | None, peer_uuids: tuple[str, ...], config: VirtualMemoryResourceOptions
1123) -> VirtualMemoryResource:
1124 """Rebuild a pickled resource with the receiving process's device ordinals."""
1125 device = _device_from_uuid(owner_uuid) if owner_uuid is not None else 0 1^
1126 peers = tuple(_device_from_uuid(peer_uuid).device_id for peer_uuid in peer_uuids) 1^
1127 return cls(device, replace(config, peers=peers)) 1^
1130cdef VirtualMemoryFdBufferDescriptor _export_ipc_descriptor(Buffer buf):
1131 """Export the allocations that back ``buf`` as shareable handles, in address order.
1133 Only the allocations that cover ``buf.size`` are exported; a buffer's size
1134 is a prefix of its range. Each file descriptor is owned by an
1135 :class:`IPCAllocationHandle` in the new descriptor and closed with it;
1136 nothing is cached on the buffer. A buffer that contains memory imported
1137 from another process is refused before any driver call: the driver
1138 exports only allocations this process created with the handle type, and
1139 every allocation carries its provenance (``mem_allocation_is_imported``)
1140 into every range that maps it.
1141 """
1142 cdef VirtualMemoryResource mr = <VirtualMemoryResource>buf._memory_resource 1Y0L312V5W6X7QURSuvNOTyec[jkzaADfhldbgxGiBCHI
1143 cdef vector[VaMappingHandle] mappings
1144 cdef vector[MemAllocationHandle] allocs
1145 cdef MemAllocationHandle h_alloc
1146 cdef cydriver.CUresult status
1147 cdef size_t i, n, chunk, offset = 0 1Y0L312V5W6X7QURSuvNOTyec[jkzaADfhldbgxGiBCHI
1148 cdef int handle_type
1149 cdef int fd = -1 1Y0L312V5W6X7QURSuvNOTyec[jkzaADfhldbgxGiBCHI
1150 cdef list sizes = [] 1Y0L312V5W6X7QURSuvNOTyec[jkzaADfhldbgxGiBCHI
1151 cdef list handles = [] 1Y0L312V5W6X7QURSuvNOTyec[jkzaADfhldbgxGiBCHI
1153 if not mr.is_ipc_enabled: 1Y0L312V5W6X7QURSuvNOTyec[jkzaADfhldbgxGiBCHI
1154 raise RuntimeError("Memory resource is not IPC-enabled") 1G
1155 handle_type = int(VirtualMemoryResourceOptions._handle_type_to_driver(mr.config.handle_type)) 1Y0L312V5W6X7QURSuvNOTyec[jkzaADfhldbgxGiBCHI
1156 mappings = vmm_range_mappings(vmm_range(buf._h_ptr)) 1Y0L312V5W6X7QURSuvNOTyec[jkzaADfhldbgxGiBCHI
1157 for i in range(mappings.size()): 1Y0L312V5W6X7QURSuvNOTyec[jkzaADfhldbgxGiBCHI
1158 if offset >= buf._size: 1Y0L312V5W6X7QURSuvNOTyecjkzaADfhldbgxGiBCHI
1159 break
1160 h_alloc = va_mapping_allocation(mappings[i]) 1Y0L312V5W6X7QURSuvNOTyecjkzaADfhldbgxGiBCHI
1161 allocs.push_back(h_alloc) 1Y0L312V5W6X7QURSuvNOTyecjkzaADfhldbgxGiBCHI
1162 offset += mem_allocation_size(h_alloc) 1Y0L312V5W6X7QURSuvNOTyecjkzaADfhldbgxGiBCHI
1163 n = allocs.size() 1Y0L312V5W6X7QURSuvNOTyec[jkzaADfhldbgxGiBCHI
1164 for i in range(n): 1Y0L312V5W6X7QURSuvNOTyec[jkzaADfhldbgxGiBCHI
1165 if mem_allocation_is_imported(allocs[i]): 1Y0L312V5W6X7QURSuvNOTyecjkzaADfhldbgxGiBCHI
1166 raise RuntimeError( 1bg
1167 f"this buffer contains memory imported from another process (allocation {i + 1} of {n}); imported " 1bg
1168 "memory cannot be exported again. Forward the descriptor it was imported from, or copy the data "
1169 "into a buffer this process allocated."
1170 )
1171 for i in range(n): 1Y0L312V5W6X7QURSuvNOTyec[jkzaADfhldbgxGiBCHI
1172 h_alloc = allocs[i] 1Y0L312V5W6X7QURSuvNOTyecjkzaADfhldbgxGiBCHI
1173 chunk = mem_allocation_size(h_alloc) 1Y0L312V5W6X7QURSuvNOTyecjkzaADfhldbgxGiBCHI
1174 with nogil: 1Y0L312V5W6X7QURSuvNOTyecjkzaADfhldbgxGiBCHI
1175 status = cydriver.cuMemExportToShareableHandle( 1Y0L312V5W6X7QURSuvNOTyecjkzaADfhldbgxGiBCHI
1176 <void*>&fd, as_cu(h_alloc), <cydriver.CUmemAllocationHandleType>handle_type, 0)
1177 _check_noting( 1Y0L312V5W6X7QURSuvNOTyecjkzaADfhldbgxGiBCHI
1178 status,
1179 f"while exporting allocation {i + 1} of {n} of a VirtualMemoryResource buffer; each exported " 1Y0L312V5W6X7QURSuvNOTyecjkzaADfhldbgxGiBCHI
1180 "allocation takes one file descriptor, so a process near its file descriptor limit fails here",
1181 )
1182 try: 1Y0L312V5W6X7QURSuvNOTyecjkzaADfhldbgxGiBCHI
1183 handle = IPCAllocationHandle._init(fd, None) 1Y0L312V5W6X7QURSuvNOTyecjkzaADfhldbgxGiBCHI
1184 except: # noqa: E722 rollback-then-raise: the fd is not owned yet
1185 # nvbug 6977893: an Orin driver can return a negative fd despite success.
1186 if fd >= 0:
1187 os.close(fd)
1188 raise
1189 # The handle owns the fd now; if append fails, the handle closes it once.
1190 handles.append(handle) 1Y0L312V5W6X7QURSuvNOTyecjkzaADfhldbgxGiBCHI
1191 sizes.append(chunk) 1Y0L312V5W6X7QURSuvNOTyecjkzaADfhldbgxGiBCHI
1192 return VirtualMemoryFdBufferDescriptor._from_exports(handle_type, tuple(sizes), tuple(handles), buf._size) 1Y0L312V5W6X7QURSuvNOTyec[jkzaADfhldbgxGiBCHI