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

1# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 

2# 

3# SPDX-License-Identifier: Apache-2.0 

4  

5from __future__ import annotations 

6  

7from libc.stdint cimport uintptr_t 

8from libc.string cimport memset 

9from libcpp.vector cimport vector 

10  

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 

52  

53import os 

54import platform 

55from dataclasses import dataclass, field, replace 

56from multiprocessing import context as _mp_context 

57from typing import TYPE_CHECKING, Iterable 

58  

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) 

70  

71if TYPE_CHECKING: 

72 from cuda.core.graph import GraphBuilder 

73  

74__all__ = ["VirtualMemoryBuffer", "VirtualMemoryResource", "VirtualMemoryResourceOptions"] 

75  

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) 

85  

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() 

90  

91  

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 

96  

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 """ 

134  

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 

146  

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 

177  

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

182  

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

189  

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

196  

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

203  

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

212  

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

219  

220  

221cdef inline size_t _align_up(size_t size, size_t gran) except? 0: 

222 """Round ``size`` up to a multiple of ``gran``. 

223  

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

230  

231  

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*

239  

240  

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 ) 

248  

249  

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

259  

260  

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 

269  

270  

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 

281  

282  

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}" 

294  

295  

296cdef class VirtualMemoryBuffer(Buffer): 

297 """A :class:`Buffer` returned by :class:`VirtualMemoryResource`. 

298  

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 """ 

306  

307 @property 

308 def ipc_descriptor(self) -> VirtualMemoryFdBufferDescriptor: 

309 """A new descriptor for sharing this buffer with other processes. 

310  

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. 

330  

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

343  

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

358  

359 def close(self, stream: Stream | GraphBuilder | None = None) -> None: 

360 """Release this buffer's share of its address range. 

361  

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. 

373  

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

398  

399  

400cdef class VirtualMemoryResource(MemoryResource): 

401 """Create a device memory resource that uses the CUDA VMM APIs to allocate memory. 

402  

403 Parameters 

404 ---------- 

405 device_id : Device | int 

406 Device for which a memory resource is constructed. 

407  

408 config : VirtualMemoryResourceOptions, optional 

409 A configuration object for the VirtualMemoryResource 

410  

411  

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. 

417  

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. 

423  

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. 

429  

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`. 

435  

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 """ 

459  

460 cdef: 

461 readonly object device 

462 readonly object config 

463 object __weakref__ 

464  

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

475  

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

486  

487 cdef int _check_config(self, object cfg) except -1: 

488 """Reject options the driver would reject, before any driver call. 

489  

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

538  

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

556  

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

578  

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

586  

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

609  

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

615  

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. 

619  

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. 

632  

633 Returns 

634 ------- 

635 VirtualMemoryBuffer 

636 A buffer that owns its reservation, physical allocation and mapping. 

637  

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

650  

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 

663  

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

673  

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

680  

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$

695  

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

703  

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. 

709  

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. 

718  

719 Concurrent calls on buffers that alias one another are safe. 

720  

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. 

736  

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. 

743  

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 

775  

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

801  

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

808  

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

819  

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

826  

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

851  

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

884  

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. 

888  

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. 

895  

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]

922  

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

929  

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`{!

936  

937 @property 

938 def is_ipc_enabled(self) -> bool: 

939 """Whether buffers of this resource can be shared with other processes. 

940  

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 ) 

953  

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`. 

956  

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 

985  

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[

1012  

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

1035  

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

1100  

1101 @property 

1102 def device_id(self) -> int: 

1103 """ 

1104 Get the device ID associated with this memory resource. 

1105  

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`{

1110  

1111 def __repr__(self) -> str: 

1112 """ 

1113 Return a string representation of the VirtualMemoryResource. 

1114  

1115 Returns: 

1116 str: A string describing the object 

1117 """ 

1118 return f"<VirtualMemoryResource device={self.device}>" 

1119  

1120  

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^

1128  

1129  

1130cdef VirtualMemoryFdBufferDescriptor _export_ipc_descriptor(Buffer buf): 

1131 """Export the allocations that back ``buf`` as shareable handles, in address order. 

1132  

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

1152  

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