Coverage for cuda/core/_memory/_virtual_memory_resource.pyx: 73.78%

473 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-16 01:23 +0000

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 VirtualMemoryIPCBufferDescriptor, 

22) 

23from cuda.core._rt cimport ( 

24 ContextHandle, 

25 DevicePtrHandle, 

26 MemAllocationHandle, 

27 StreamHandle, 

28 VaMappingHandle, 

29 VaReservationHandle, 

30 VmmRangeHandle, 

31 as_cu, 

32 create_context_bound_legacy_stream, 

33 create_mem_allocation_handle, 

34 create_va_mapping_handle, 

35 create_va_reservation_handle, 

36 create_vmm_range, 

37 deallocation_stream, 

38 deviceptr_create_vmm, 

39 get_last_error, 

40 get_primary_context, 

41 import_mem_allocation_handle, 

42 mem_allocation_is_imported, 

43 mem_allocation_size, 

44 set_deallocation_stream, 

45 va_mapping_allocation, 

46 vmm_range, 

47 vmm_range_mappings, 

48 vmm_range_total, 

49) 

50from cuda.core._stream cimport Stream, Stream_accept, Stream_handle_is_default_token, Stream_is_default_token 

51from cuda.core._utils.cuda_utils cimport HANDLE_RETURN, check_or_create_options 

52  

53import os 

54import platform 

55from dataclasses import dataclass, field 

56from multiprocessing import context as _mp_context 

57from typing import TYPE_CHECKING, Iterable 

58  

59from cuda.core._device import Device 

60from cuda.core._utils.cuda_utils import driver 

61from cuda.core._utils.version import BUILD_CUDA_MAJOR 

62from cuda.core.typing import ( 

63 DevicePointerType, 

64 VirtualMemoryAccessType, 

65 VirtualMemoryAllocationType, 

66 VirtualMemoryGranularityType, 

67 VirtualMemoryHandleType, 

68 VirtualMemoryLocationType, 

69) 

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. 

103 handle_type: :obj:`~_memory.VirtualMemoryHandleType` | str 

104 Export handle type for the physical allocation, and the switch for 

105 sharing buffers with other processes (see 

106 :attr:`VirtualMemoryResource.is_ipc_enabled`). Use ``"posix_fd"`` on 

107 Linux if you plan to import/export the allocation. Use `None` if you 

108 don't need an exportable handle. ``location_type="host"`` requires 

109 `None`. 

110 gpu_direct_rdma: bool 

111 Hint that the allocation should be GDR-capable (if supported). 

112 granularity: :obj:`~_memory.VirtualMemoryGranularityType` | str 

113 Controls granularity query and size rounding. 

114 addr_hint: int 

115 A (optional) virtual address hint to try to reserve at. Setting it to 0 lets the CUDA driver decide. 

116 addr_align: int 

117 Alignment for the VA reservation. If `None`, use the queried granularity. 

118 peers: Iterable[int] 

119 Extra device IDs that should be granted access in addition to ``device``. 

120 self_access: :obj:`~_memory.VirtualMemoryAccessType` | None | str 

121 Access flags for the owning device. 

122 peer_access: :obj:`~_memory.VirtualMemoryAccessType` | None | str 

123 Access flags for peers. 

124 """ 

125  

126 allocation_type: VirtualMemoryAllocationType = VirtualMemoryAllocationType.PINNED 

127 location_type: VirtualMemoryLocationType = VirtualMemoryLocationType.DEVICE 

128 handle_type: VirtualMemoryHandleType = VirtualMemoryHandleType.POSIX_FD 

129 granularity: VirtualMemoryGranularityType = VirtualMemoryGranularityType.RECOMMENDED 

130 gpu_direct_rdma: bool = False 

131 addr_hint: int | None = 0 

132 addr_align: int | None = None 

133 peers: Iterable[int] = field(default_factory=tuple) 

134 self_access: VirtualMemoryAccessType = VirtualMemoryAccessType.READ_WRITE 

135 peer_access: VirtualMemoryAccessType = VirtualMemoryAccessType.READ_WRITE 

136  

137 _a = driver.CUmemAccess_flags 

138 _access_flags = { # noqa: RUF012 

139 VirtualMemoryAccessType.READ_WRITE: _a.CU_MEM_ACCESS_FLAGS_PROT_READWRITE, 

140 VirtualMemoryAccessType.READ: _a.CU_MEM_ACCESS_FLAGS_PROT_READ, 

141 None: 0, 

142 } 

143 _h = driver.CUmemAllocationHandleType 

144 _handle_types = { # noqa: RUF012 

145 None: _h.CU_MEM_HANDLE_TYPE_NONE, 

146 VirtualMemoryHandleType.POSIX_FD: _h.CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR, 

147 VirtualMemoryHandleType.WIN32_KMT: _h.CU_MEM_HANDLE_TYPE_WIN32_KMT, 

148 VirtualMemoryHandleType.FABRIC: _h.CU_MEM_HANDLE_TYPE_FABRIC, 

149 } 

150 _g = driver.CUmemAllocationGranularity_flags 

151 _granularity = { # noqa: RUF012 

152 VirtualMemoryGranularityType.RECOMMENDED: _g.CU_MEM_ALLOC_GRANULARITY_RECOMMENDED, 

153 VirtualMemoryGranularityType.MINIMUM: _g.CU_MEM_ALLOC_GRANULARITY_MINIMUM, 

154 } 

155 _l = driver.CUmemLocationType 

156 _location_type = { # noqa: RUF012 

157 VirtualMemoryLocationType.DEVICE: _l.CU_MEM_LOCATION_TYPE_DEVICE, 

158 VirtualMemoryLocationType.HOST: _l.CU_MEM_LOCATION_TYPE_HOST, 

159 VirtualMemoryLocationType.HOST_NUMA: _l.CU_MEM_LOCATION_TYPE_HOST_NUMA, 

160 VirtualMemoryLocationType.HOST_NUMA_CURRENT: _l.CU_MEM_LOCATION_TYPE_HOST_NUMA_CURRENT, 

161 } 

162 _t = driver.CUmemAllocationType 

163 # CUDA 13 added MANAGED to CUmemAllocationType. The CUDA 12 build has no such member. 

164 _allocation_type = {VirtualMemoryAllocationType.PINNED: _t.CU_MEM_ALLOCATION_TYPE_PINNED} # noqa: RUF012 

165 if BUILD_CUDA_MAJOR >= 13: 

166 _allocation_type[VirtualMemoryAllocationType.MANAGED] = _t.CU_MEM_ALLOCATION_TYPE_MANAGED 

167  

168 @staticmethod 

169 def _access_to_flags(spec: VirtualMemoryAccessType | None) -> int: 

170 flags = VirtualMemoryResourceOptions._access_flags.get(spec) 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m N H t u - x 9 8 7 Ib. * 5 n

171 if flags is None: 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m N H t u - x 9 8 7 Ib. * 5 n

172 raise ValueError(f"Unknown access spec: {spec!r}") 2Ib

173 return flags # type: ignore[no-any-return] 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

174  

175 @staticmethod 

176 def _allocation_type_to_driver(spec: VirtualMemoryAllocationType) -> int: 

177 alloc_type = VirtualMemoryResourceOptions._allocation_type.get(spec) 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m N H t u - x 9 8 7 Jb. * 5 n

178 if alloc_type is None: 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m N H t u - x 9 8 7 Jb. * 5 n

179 raise ValueError(f"Unsupported allocation_type: {spec!r}") 2Jb

180 return alloc_type # type: ignore[no-any-return] 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

181  

182 @staticmethod 

183 def _location_type_to_driver(spec: VirtualMemoryLocationType) -> int: 

184 loc_type = VirtualMemoryResourceOptions._location_type.get(spec) 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m N H t u - x 9 8 7 Kb. * 5 n

185 if loc_type is None: 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m N H t u - x 9 8 7 Kb. * 5 n

186 raise ValueError(f"Unsupported location_type: {spec!r}") 2Kb

187 return loc_type # type: ignore[no-any-return] 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

188  

189 @staticmethod 

190 def _handle_type_to_driver(spec: VirtualMemoryHandleType | None) -> int: 

191 if spec == "win32": 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c / g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m : N H t u - x 9 8 7 MbHb. * 5 n

192 raise NotImplementedError("win32 is currently not supported, please reach out to the CUDA Python team") 2Mb

193 handle_type = VirtualMemoryResourceOptions._handle_types.get(spec) 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c / g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m : N H t u - x 9 8 7 Hb. * 5 n

194 if handle_type is None: 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c / g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m : N H t u - x 9 8 7 Hb. * 5 n

195 raise ValueError(f"Unsupported handle_type: {spec!r}") 2Hb

196 return handle_type # type: ignore[no-any-return] 1'q(WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%m:NHtu-x987.*5n

197  

198 @staticmethod 

199 def _granularity_to_driver(spec: VirtualMemoryGranularityType) -> int: 

200 granularity = VirtualMemoryResourceOptions._granularity.get(spec) 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m N H t u - x 9 8 7 Lb. * 5 n

201 if granularity is None: 2' q ( W Y K 1 Z 0 T 2 U 3 V 4 O S P Q z A L M R D e c g h E a i j k d b 6 f B C l F G I J ) r s y X # $ o p + , ! v w % m N H t u - x 9 8 7 Lb. * 5 n

202 raise ValueError(f"Unsupported granularity: {spec!r}") 2Lb

203 return granularity # type: ignore[no-any-return] 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

204  

205  

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

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

208  

209 Raises ``OverflowError`` instead of wrapping when the rounded size does not 

210 fit in ``size_t``. 

211 """ 

212 if size > <size_t>-1 - (gran - 1): 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

213 raise OverflowError(f"size {size} rounded up to the {gran}-byte granularity does not fit in size_t") 15

214 return (size + gran - 1) // gran * gran 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

215  

216  

217cdef bint _stream_is_capturing(cydriver.CUstream s) except -1: 

218 cdef cydriver.CUstreamCaptureStatus cap_status 

219 IF CUDA_CORE_BUILD_MAJOR >= 13: 

220 HANDLE_RETURN(cydriver.cuStreamGetCaptureInfo(s, &cap_status, NULL, NULL, NULL, NULL, NULL)) 1X+,!vw%

221 ELSE: 

222 HANDLE_RETURN(cydriver.cuStreamGetCaptureInfo(s, &cap_status, NULL, NULL, NULL, NULL)) 

223 return cap_status == cydriver.CU_STREAM_CAPTURE_STATUS_ACTIVE 1X+,!vw%

224  

225  

226cdef int _raise_last_error() except -1: 

227 """Raise the status a handle factory recorded when it returned empty.""" 

228 HANDLE_RETURN(get_last_error()) 1ecN

229 raise RuntimeError( 

230 "internal cuda.core error, please report: a virtual memory handle factory " 

231 "returned an empty handle without recording a CUDA error" 

232 ) 

233  

234  

235cdef inline int _attach_note(exc, str detail) except -1: 

236 # PEP 678 notes (Python 3.11+) keep the detail separable from the message; 

237 # older interpreters get it appended to the message instead. 

238 add_note = getattr(exc, "add_note", None) 1eca

239 if add_note is not None: 1eca

240 add_note(detail) 1eca

241 else: 

242 exc.args = (f"{exc.args[0]} ({detail})", *exc.args[1:]) 

243 return 0 1eca

244  

245  

246cdef int _raise_last_error_noting(str detail) except -1: 

247 """Raise the status a handle factory recorded, with ``detail`` attached as a note.""" 

248 try: 1ec

249 _raise_last_error() 1ec

250 except Exception as exc: 1ec

251 _attach_note(exc, detail) 1ec

252 raise 1ec

253 return 0 

254  

255  

256cdef int _check_noting(cydriver.CUresult status, str detail) except -1: 

257 """``HANDLE_RETURN(status)``, with ``detail`` attached to the raised error as a note.""" 

258 if status == cydriver.CUDA_SUCCESS: 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ

259 return 0 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ

260 try: 1a

261 HANDLE_RETURN(status) 1a

262 except Exception as exc: 1a

263 _attach_note(exc, detail) 1a

264 raise 1a

265 return 0 

266  

267  

268cdef str _describe_location(cydriver.CUmemLocation loc): 

269 """Name a driver memory location for an error message.""" 

270 if loc.type == cydriver.CU_MEM_LOCATION_TYPE_DEVICE: 

271 return f"device {loc.id}" 

272 if loc.type == cydriver.CU_MEM_LOCATION_TYPE_HOST: 

273 return "host memory" 

274 if loc.type == cydriver.CU_MEM_LOCATION_TYPE_HOST_NUMA: 

275 return f"host NUMA node {loc.id}" 

276 if loc.type == cydriver.CU_MEM_LOCATION_TYPE_HOST_NUMA_CURRENT: 

277 return "the current host NUMA node" 

278 return f"location type {<int>loc.type}" 

279  

280  

281cdef class VirtualMemoryBuffer(Buffer): 

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

283  

284 The buffer owns its address reservations, physical allocations and 

285 mappings through its device pointer handle; closing it is the only way 

286 to release them. A buffer returned by 

287 :meth:`VirtualMemoryResource.modify_allocation` aliases the buffer it 

288 was grown from: the two share their physical memory, and that memory is 

289 freed when the last buffer that maps it closes. 

290 """ 

291  

292 @property 

293 def ipc_descriptor(self) -> VirtualMemoryIPCBufferDescriptor: 

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

295  

296 Every access exports the physical allocations that back the buffer 

297 again and returns a new descriptor that owns one file descriptor per 

298 allocation; the file descriptors close when the descriptor is 

299 released. Keep the descriptor in a variable while you use its 

300 ``fds``: the numbers from ``buf.ipc_descriptor.fds`` alone may 

301 already be closed. A descriptor pins the physical memory while it 

302 exists, in every process that holds one, and nothing else does: the 

303 buffer itself keeps no descriptor, so it pins nothing beyond its own 

304 mapping. 

305 Pickling the buffer through ``multiprocessing`` builds a transient 

306 descriptor, so the sender holds no file descriptors between sends; 

307 a buffer passed as a ``Process`` argument is the exception, because 

308 the child receives the file descriptors when it is spawned, after 

309 pickling, so that descriptor lives until the ``Process`` object is 

310 released. Requires a resource whose ``handle_type`` can be shared; see 

311 :attr:`VirtualMemoryResource.is_ipc_enabled`. Import with 

312 :meth:`Buffer.from_ipc_descriptor` and a 

313 :class:`VirtualMemoryResource` of the receiving process, or send the 

314 buffer itself. 

315  

316 Raises 

317 ------ 

318 RuntimeError 

319 If the resource is not IPC-enabled, or the buffer contains memory 

320 imported from another process (an import, or a buffer 

321 :meth:`VirtualMemoryResource.modify_allocation` derived from 

322 one): such memory cannot be exported again. Forward the descriptor 

323 it was imported from, or copy the data into a buffer this process 

324 allocated. The check happens before any driver call. 

325 """ 

326 Buffer_check_open(self) 1YKZ0234SLMec/ghaijkdbfBClIJ

327 return _export_ipc_descriptor(self) 1YKZ0234SLMec/ghaijkdbfBClIJ

328  

329 def __reduce__(self) -> tuple[object, ...]: 

330 # Unpickling performs a live import from the descriptor; only deserialize 

331 # Buffers from a trusted principal. Must not serialize the parent's stream. 

332 Buffer_check_open(self) 1W1TUVOPQzALMRDEbfBFG

333 desc = _export_ipc_descriptor(self) 1W1TUVOPQzALMRDEbfBFG

334 popen = _mp_context.get_spawning_popen() 1W1TUVOPQzALMRDEBFG

335 if popen is not None: 1W1TUVOPQzALMRDEBFG

336 # A Queue, Pipe, or Pool duplicates the file descriptors while this 

337 # transient descriptor is alive. A spawned Process does not: it 

338 # records their numbers now and hands them to the child when it is 

339 # created, after pickling has finished. Keep the descriptor on the 

340 # Popen until then; the Popen dies with the Process object. 

341 vars(popen).setdefault("_cuda_core_descriptors_until_spawned", []).append(desc) 1TUVzALDE

342 return Buffer._reduce_helper, (self.memory_resource, desc) 1W1TUVOPQzALMRDEBFG

343  

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

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

346  

347 Each mapping, reservation and physical allocation goes away when the 

348 last buffer that holds it closes. Virtual memory is unmapped 

349 synchronously, so this call waits for the work queued on the 

350 deallocation stream before it releases this buffer's share; a buffer 

351 released by the garbage collector waits at that point instead. The 

352 unmap cannot be captured. When the stream this close uses, given or recorded, is not 

353 a default stream and is capturing, the call raises and leaves the 

354 buffer open. A default stream is checked when the range is released 

355 instead: if synchronizing it would disturb a capture in its context, 

356 the release reports a :class:`CUDAWarning` and unmaps without 

357 synchronizing that stream. 

358  

359 Parameters 

360 ---------- 

361 stream : :obj:`~_stream.Stream` | :obj:`~graph.GraphBuilder`, optional 

362 If given, replaces the recorded deallocation stream, as for 

363 :meth:`Buffer.close`. 

364 """ 

365 cdef Stream s 

366 cdef StreamHandle h 

367 if not self._h_ptr: 1'q(OSPQzALMRDec/ghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987*5n

368 return 1zA

369 if stream is not None: 1'q(OSPQzALMRDec/ghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987*5n

370 s = Stream_accept(stream) 1!

371 h = s._h_stream 1!

372 else: 

373 # Empty when no stream was recorded (host-located memory). 

374 h = deallocation_stream(self._h_ptr) 1'q(OSPQzALMRDec/ghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987*5n

375 # Default-stream tokens are checked by the range deleter under their 

376 # bound context; a real stream can be checked here and refused. 

377 if h and not Stream_handle_is_default_token(as_cu(h)) and _stream_is_capturing(as_cu(h)): 1'q(OSPQzALMRDec/ghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987*5n

378 raise RuntimeError( 1!%

379 "cannot close a VirtualMemoryResource buffer on a capturing stream: " 

380 "virtual memory deallocation is synchronous and cannot be captured" 

381 ) 

382 Buffer.close(self, stream) 1'q(OSPQzALMRDec/ghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987*5n

383  

384  

385cdef class VirtualMemoryResource(MemoryResource): 

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

387  

388 Parameters 

389 ---------- 

390 device_id : Device | int 

391 Device for which a memory resource is constructed. 

392  

393 config : VirtualMemoryResourceOptions, optional 

394 A configuration object for the VirtualMemoryResource 

395  

396  

397 Warning 

398 ------- 

399 This is a low-level API that is provided only for convenience. Make sure you fully understand 

400 how CUDA Virtual Memory Management works before using this. Other MemoryResource subclasses 

401 in cuda.core should already meet the common needs. 

402  

403 A shared buffer's descriptor comes from the exporting process and is not trusted: 

404 :meth:`Buffer.from_ipc_descriptor` checks what it can, and the driver rejects a size that 

405 does not match the exported allocation. Unpickling a shared buffer performs a live import, 

406 so unpickle buffers only from a trusted principal. A descriptor pins the physical memory 

407 while it exists; release it when the receiver has imported it. 

408  

409 Notes 

410 ----- 

411 Every buffer this resource returns is a :class:`VirtualMemoryBuffer` that 

412 owns its address reservations, physical allocations and mappings; closing 

413 the buffer releases them. :meth:`deallocate` is not involved in that path. 

414  

415 Virtual memory is unmapped synchronously, so closing a buffer waits for 

416 the work queued on its deallocation stream. A buffer released by the 

417 garbage collector waits at that point instead. To control when the wait 

418 happens, close the buffer explicitly or record an idle stream with 

419 :meth:`Buffer.set_deallocation_stream`. 

420  

421 Buffers can be shared with other processes when ``config.handle_type`` 

422 is ``"posix_fd"`` (Linux), the only handle type cuda.core shares today; 

423 ``handle_type`` is the switch and :attr:`is_ipc_enabled` the query. The 

424 rules that follow describe POSIX file descriptors. 

425 :attr:`Buffer.ipc_descriptor` exports the physical allocations that back 

426 a buffer, one file descriptor each, into a new descriptor on every 

427 access; :meth:`Buffer.from_ipc_descriptor` imports a descriptor with a 

428 ``VirtualMemoryResource`` of the receiving process for the device that 

429 owns the memory; and a buffer sent through ``multiprocessing`` does both. 

430 Plain ``pickle`` cannot carry the file descriptors; a process with its 

431 own file descriptor passing sends the descriptor's ``fds``, ``sizes``, 

432 and ``size`` and rebuilds it with 

433 ``VirtualMemoryIPCBufferDescriptor.from_fds()``. The importing resource 

434 maps the memory for its device and the devices in its ``peers`` option, 

435 with its own access options. A descriptor pins the physical memory while 

436 it exists, in every process that holds one, and nothing else does. A 

437 buffer that contains imported memory (an import, or anything 

438 :meth:`modify_allocation` derived from one) cannot be exported again; 

439 forward the descriptor it came from, or copy into a buffer you own. 

440 """ 

441  

442 cdef: 

443 readonly object device 

444 readonly object config 

445 object __weakref__ 

446  

447 def __init__(self, device_id: Device | int, config: VirtualMemoryResourceOptions | None = None) -> None: 

448 self.device = Device(device_id) 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n

449 self.config = check_or_create_options( 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n

450 VirtualMemoryResourceOptions, config, "VirtualMemoryResource options", keep_none=False 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n

451 ) 

452 if self.config.location_type in _HOST_LOCATION_TYPES: 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n

453 self.device = None 2vb; = - x ? xbyb9

454 if self.device is not None and not self.device.properties.virtual_memory_management_supported: 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n

455 raise RuntimeError("VirtualMemoryResource requires CUDA VMM API support") 

456 self._check_config(self.config) 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n

457  

458 def __reduce__(self) -> tuple[object, ...]: 

459 # The resource holds no driver object that other processes must share; 

460 # the receiving process rebuilds an equivalent one from the device and 

461 # the options. This is what lets a Buffer pickle as (resource, descriptor). 

462 return type(self), (self.device.device_id if self.device is not None else 0, self.config) 2NbW Y K 1 Z 0 T ObU PbV QbO S P Q z A L M R D E B CbF G I J

463  

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

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

466  

467 Shared by ``__init__`` and ``modify_allocation``, so a per-call 

468 configuration is held to the same rules as the resource's own. 

469 """ 

470 if cfg.location_type != self.config.location_type: 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n

471 raise ValueError( 17

472 f"config.location_type {str(cfg.location_type)!r} does not match the resource's " 17

473 f"{str(self.config.location_type)!r}; the location of a buffer cannot change" 17

474 ) 

475 # The driver rejects an exportable handle type for HOST memory only; 

476 # the NUMA location types may carry one. 

477 if cfg.location_type == VirtualMemoryLocationType.HOST and cfg.handle_type is not None: 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n

478 raise ValueError( 1?9

479 'virtual memory with location_type="host" cannot have an exportable handle type; ' 

480 "pass handle_type=None" 

481 ) 

482 # The handle type is part of the resource's sharing contract, like the 

483 # location: every chunk of a buffer must be exportable the same way. 

484 if cfg.handle_type != self.config.handle_type: 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n

485 raise ValueError( 16

486 f"config.handle_type {str(cfg.handle_type)!r} does not match the resource's " 16

487 f"{str(self.config.handle_type)!r}; the handle type of a buffer's chunks cannot change" 16

488 ) 

489 if cfg.gpu_direct_rdma and self.device is not None and not self.device.properties.gpu_direct_rdma_supported: 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n

490 raise RuntimeError("GPU Direct RDMA is not supported on this device") 

491 return 0 2' q ( @ [ ] ^ K _ ` { | } ~ abbbcbdbebfbgbhbibjbkblbmbnbobpbqbrbz A sbtbubD e c / g h E a i j k d b 6 f B C l zbvb; = AbwbBbCbF G I J EbFb) r s y X # $ o p + , ! v w % m : N H t u - x ? xbyb9 8 GbDb7 . * 5 n

492  

493 cdef int _fill_prop(self, object cfg, cydriver.CUmemAllocationProp* prop) except -1: 

494 # The location comes from the resource; the rest may come from a 

495 # per-call configuration. 

496 cdef int alloc_type = int(VirtualMemoryResourceOptions._allocation_type_to_driver(cfg.allocation_type)) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

497 cdef int loc_type = int(VirtualMemoryResourceOptions._location_type_to_driver(self.config.location_type)) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

498 cdef int handle_type = int(VirtualMemoryResourceOptions._handle_type_to_driver(cfg.handle_type)) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

499 memset(prop, 0, sizeof(cydriver.CUmemAllocationProp)) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

500 prop.type = <cydriver.CUmemAllocationType>alloc_type 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

501 prop.location.type = <cydriver.CUmemLocationType>loc_type 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

502 prop.location.id = self.device.device_id if self.device is not None else -1 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

503 prop.allocFlags.gpuDirectRDMACapable = 1 if cfg.gpu_direct_rdma else 0 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

504 prop.requestedHandleTypes = <cydriver.CUmemAllocationHandleType>handle_type 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

505 prop.win32HandleMetaData = NULL 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

506 return 0 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

507  

508 cdef int _fill_access( 

509 self, object cfg, const cydriver.CUmemAllocationProp* prop, 

510 vector[cydriver.CUmemAccessDesc]& descs, 

511 ) except -1: 

512 cdef cydriver.CUmemAccessDesc d 

513 cdef int owner_flags = int(VirtualMemoryResourceOptions._access_to_flags(cfg.self_access)) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

514 cdef int peer_flags = int(VirtualMemoryResourceOptions._access_to_flags(cfg.peer_access)) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

515 if owner_flags: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

516 memset(&d, 0, sizeof(d)) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsX#$op+,!vw%mNHtu-x987.*5n

517 d.location.type = prop.location.type 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsX#$op+,!vw%mNHtu-x987.*5n

518 d.location.id = prop.location.id 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsX#$op+,!vw%mNHtu-x987.*5n

519 d.flags = <cydriver.CUmemAccess_flags>owner_flags 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsX#$op+,!vw%mNHtu-x987.*5n

520 descs.push_back(d) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsX#$op+,!vw%mNHtu-x987.*5n

521 if peer_flags: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

522 for peer_dev in cfg.peers: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

523 memset(&d, 0, sizeof(d)) 

524 d.location.type = cydriver.CU_MEM_LOCATION_TYPE_DEVICE 

525 d.location.id = int(peer_dev) 

526 d.flags = <cydriver.CUmemAccess_flags>peer_flags 

527 descs.push_back(d) 

528 return 0 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

529  

530 cdef size_t _granularity(self, object cfg, const cydriver.CUmemAllocationProp* prop) except? 0: 

531 cdef size_t gran = 0 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

532 cdef int flag = int(VirtualMemoryResourceOptions._granularity_to_driver(cfg.granularity)) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

533 with nogil: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

534 HANDLE_RETURN(cydriver.cuMemGetAllocationGranularity( 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

535 &gran, prop, <cydriver.CUmemAllocationGranularity_flags>flag)) 

536 return gran 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

537  

538 cdef int _record_deallocation_stream(self, const DevicePtrHandle& h_ptr, Stream s) except -1: 

539 cdef StreamHandle h 

540 cdef ContextHandle h_ctx 

541 if s is not None and not Stream_is_default_token(s): 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

542 h = s._h_stream 11+,vw

543 elif self.device is None: 1'q(WYKZ0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op!%mNHtu-x987.*5n

544 # Host-located memory needs no context to free, and a default-stream 

545 # token has no context to bind to: record nothing. 

546 return 0 1-x9

547 else: 

548 # Bind the legacy default-stream token to the device's primary 

549 # context so the free is ordered correctly no matter what is current 

550 # then, and so allocate() never depends on a current context. 

551 # get_primary_context keeps its own cache; nothing is cached here. 

552 h_ctx = get_primary_context(self.device.device_id) 1'q(WYKZ0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op!%mNHtu87.*5n

553 if not h_ctx: 1'q(WYKZ0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op!%mNHtu87.*5n

554 _raise_last_error() 

555 h = create_context_bound_legacy_stream(h_ctx) 1'q(WYKZ0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op!%mNHtu87.*5n

556 if not h: 1'q(WYKZ0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op!%mNHtu87.*5n

557 _raise_last_error() 

558 HANDLE_RETURN(set_deallocation_stream(h_ptr, h)) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu87.*5n

559 return 0 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu87.*5n

560  

561 cdef int _copy_deallocation_stream(self, const DevicePtrHandle& dst, const DevicePtrHandle& src) except -1: 

562 cdef StreamHandle h = deallocation_stream(src) 1qcghaikdbrsyXopvwmHtuxn

563 if h: 1qcghaikdbrsyXopvwmHtuxn

564 HANDLE_RETURN(set_deallocation_stream(dst, h)) 1qcghaikdbrsyXopvwmHtun

565 return 0 1qcghaikdbrsyXopvwmHtuxn

566  

567 def allocate(self, size_t size, *, stream: Stream | GraphBuilder | None = None) -> VirtualMemoryBuffer: 

568 """ 

569 Allocate a buffer of the given size using CUDA virtual memory. 

570  

571 Parameters 

572 ---------- 

573 size : int 

574 The size in bytes of the buffer to allocate. It is rounded up to the 

575 allocation granularity; the returned buffer reports the rounded size. 

576 stream : :obj:`~_stream.Stream` | :obj:`~graph.GraphBuilder`, optional 

577 Keyword-only. The allocation itself is synchronous. A real stream is 

578 recorded as the buffer's deallocation stream and synchronized when 

579 the buffer closes; with `None` or a default-stream token the legacy 

580 default stream of the resource's device is recorded instead. A 

581 host-located resource records no default stream: its buffers close 

582 without a synchronization unless a real stream was given. 

583  

584 Returns 

585 ------- 

586 VirtualMemoryBuffer 

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

588  

589 Raises 

590 ------ 

591 CUDAError 

592 If any CUDA driver API call fails during allocation. Nothing is 

593 left allocated when this method raises. 

594 OverflowError 

595 If ``size`` rounded up to the granularity does not fit in ``size_t``. 

596 """ 

597 cdef Stream s = None 1'q(WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

598 if stream is not None: 1'q(WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

599 s = Stream_accept(stream) 1'qWYK1Z0T2U3V4OSPQzALMRFGIJX+,vw

600 return self._allocate(self.config, size, s) 1'q(WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

601  

602 cdef Buffer _allocate(self, object cfg, size_t size, Stream s): 

603 """Allocate ``size`` bytes with ``cfg``; ``s`` is the accepted stream or None.""" 

604 cdef cydriver.CUmemAllocationProp prop 

605 cdef vector[cydriver.CUmemAccessDesc] descs 

606 cdef size_t gran, aligned, addr_align 

607 cdef cydriver.CUdeviceptr hint 

608 cdef MemAllocationHandle h_alloc 

609 cdef VaReservationHandle h_res 

610 cdef VaMappingHandle h_map 

611 cdef vector[VaMappingHandle] mappings 

612 cdef VmmRangeHandle rng 

613 cdef DevicePtrHandle h_ptr 

614  

615 if size == 0: 1'q(WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

616 # Nothing to reserve or map: a VMM box over an empty range, so the 

617 # buffer is a VirtualMemoryBuffer like every other. A real stream is 

618 # still recorded so that a later grow inherits it; a default-stream 

619 # token is not, which keeps this path free of driver calls. 

620 h_ptr = deviceptr_create_vmm(0, create_vmm_range(mappings)) 1/X

621 if s is not None and not Stream_is_default_token(s): 1/X

622 HANDLE_RETURN(set_deallocation_stream(h_ptr, s._h_stream)) 1X

623 return Buffer_from_deviceptr_handle(h_ptr, 0, self, None, VirtualMemoryBuffer) 1/X

624  

625 self._fill_prop(cfg, &prop) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

626 self._fill_access(cfg, &prop, descs) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

627 gran = self._granularity(cfg, &prop) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

628 aligned = _align_up(size, gran) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

629 addr_align = cfg.addr_align or gran 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

630 hint = cfg.addr_hint or 0 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

631  

632 # Every handle below is a local: if a later step fails, the locals die 

633 # in reverse order and undo everything created so far. 

634 with nogil: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

635 h_alloc = create_mem_allocation_handle(aligned, prop, descs.data(), descs.size()) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

636 if not h_alloc: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

637 _raise_last_error() 

638 with nogil: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

639 h_res = create_va_reservation_handle(aligned, addr_align, hint) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

640 if not h_res: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

641 _raise_last_error() 

642 with nogil: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

643 h_map = create_va_mapping_handle(as_cu(h_res), h_alloc, h_res) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

644 if not h_map: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

645 _raise_last_error() 

646  

647 mappings.push_back(h_map) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

648 rng = create_vmm_range(mappings) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

649 h_ptr = deviceptr_create_vmm(as_cu(h_res), rng) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

650 if not h_ptr: 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

651 _raise_last_error() 

652 self._record_deallocation_stream(h_ptr, s) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

653 return Buffer_from_deviceptr_handle(h_ptr, aligned, self, None, VirtualMemoryBuffer) 1'q(WYK1Z0T2U3V4OSPQzALMRDecghEaijkdb6fBClFGIJ)rsyX#$op+,!vw%mNHtu-x987.*5n

654  

655 def modify_allocation( 

656 self, Buffer buf not None: Buffer, size_t new_size, config: VirtualMemoryResourceOptions | None = None 

657 ) -> VirtualMemoryBuffer: 

658 """ 

659 Grow a buffer of this resource to at least ``new_size`` bytes. 

660  

661 The buffer passed in stays open and usable, and is never returned. The 

662 returned buffer aliases it: both map the same physical memory, which is 

663 freed when the last of the two closes. When the driver can extend the 

664 address range in place, the returned buffer has the same pointer; 

665 otherwise it has a new one and the existing contents are reachable 

666 through both. Closing the returned buffer never closes ``buf``. A 

667 buffer grown from an imported buffer maps imported memory and cannot 

668 be exported again. 

669  

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

671  

672 Parameters 

673 ---------- 

674 buf : VirtualMemoryBuffer 

675 A buffer returned by :meth:`allocate` or by this method. 

676 new_size : int 

677 The requested total size in bytes; rounded up to the granularity. 

678 config : VirtualMemoryResourceOptions, optional 

679 Configuration for the new physical memory chunk only. Existing 

680 chunks keep the access they were created with, and the resource's 

681 own configuration is unchanged. It must name the resource's 

682 ``location_type`` and ``handle_type`` and passes the same checks 

683 as the constructor. 

684 When ``buf`` already covers ``new_size`` there is no new chunk, so 

685 ``config`` has no effect. This method never changes the access of 

686 memory that is already mapped. 

687  

688 Returns 

689 ------- 

690 VirtualMemoryBuffer 

691 A new buffer of at least ``new_size`` and at least ``buf.size`` 

692 bytes. When ``buf`` already covers the request, the result is a 

693 full alias of it and no driver call is made. 

694  

695 Raises 

696 ------ 

697 TypeError 

698 If ``buf`` did not come from this resource. 

699 ValueError 

700 If ``config`` names a different location or handle type than the 

701 resource, or the constructor would reject it. 

702 RuntimeError 

703 If ``buf`` is closed, or ``config`` requests GPUDirect RDMA on a 

704 device without support. 

705 OverflowError 

706 If ``new_size`` rounded up to the granularity does not fit in 

707 ``size_t``. 

708 CUDAError 

709 If a driver call fails. ``buf`` is untouched when this method raises. 

710 """ 

711 cdef Buffer b 

712 cdef DevicePtrHandle h_in 

713 cdef size_t in_size 

714 cdef VmmRangeHandle rng, rng_new 

715 cdef object cfg 

716 cdef cydriver.CUmemAllocationProp prop 

717 cdef vector[cydriver.CUmemAccessDesc] descs 

718 cdef vector[VaMappingHandle] mappings 

719 cdef size_t gran, req, total, add, addr_align, offset, i 

720 cdef cydriver.CUdeviceptr base, base_new 

721 cdef MemAllocationHandle h_alloc, a 

722 cdef VaReservationHandle h_res, h_res_new 

723 cdef VaMappingHandle h_map, m2 

724 cdef DevicePtrHandle h_ptr_new 

725 cdef object new_buf 

726  

727 # Every VirtualMemoryBuffer handle comes from deviceptr_create_vmm, so 

728 # the class check is what makes vmm_range() below valid. 

729 if not isinstance(buf, VirtualMemoryBuffer): 2q c g h a i k d b 6 r s y X o p v w m N H t u x 9 8 Db7 5 n

730 raise TypeError(f"buf must be a VirtualMemoryBuffer, got {type(buf).__name__}") 28 Db

731 b = <Buffer>buf 1qcghaikdb6rsyXopvwmNHtux9875n

732 Buffer_check_open(b) 1qcghaikdb6rsyXopvwmNHtux9875n

733 # Read the input's handle and size once. The handle copy keeps the 

734 # input's box alive for the rest of the call, so a close from another 

735 # thread (unsupported; see concurrency.rst) defers the release instead 

736 # of emptying what this call reads. 

737 h_in = b._h_ptr 1qcghaikdb6rsyXopvwmNHtux9875n

738 in_size = b._size 1qcghaikdb6rsyXopvwmNHtux9875n

739 if b.memory_resource is not self: 1qcghaikdb6rsyXopvwmNHtux9875n

740 raise TypeError("buf was not allocated by this VirtualMemoryResource") 18

741 cfg = self.config if config is None else check_or_create_options( 1qcghaikdb6rsyXopvwmNHtux975n

742 VirtualMemoryResourceOptions, config, "VirtualMemoryResource options", keep_none=False 16p97

743 ) 

744 self._check_config(cfg) 1qcghaikdb6rsyXopvwmNHtux975n

745 if in_size == 0: 1qcghaikdbrsyXopvwmNHtux5n

746 # An empty buffer maps nothing; the request is a fresh allocation 

747 # that inherits the stream the empty buffer recorded, if any. 

748 new_buf = self._allocate(cfg, new_size, None) 1X

749 self._copy_deallocation_stream((<Buffer>new_buf)._h_ptr, h_in) 1X

750 return new_buf 1X

751 rng = vmm_range(h_in) 1qcghaikdbrsyopvwmNHtux5n

752  

753 self._fill_prop(cfg, &prop) 1qcghaikdbrsyopvwmNHtux5n

754 self._fill_access(cfg, &prop, descs) 1qcghaikdbrsyopvwmNHtux5n

755 gran = self._granularity(cfg, &prop) 1qcghaikdbrsyopvwmNHtux5n

756 req = _align_up(new_size, gran) 1qcghaikdbrsyopvwmNHtux5n

757 total = vmm_range_total(rng) 1qcghaikdbrsyopvwmNHtuxn

758 base = as_cu(h_in) 1qcghaikdbrsyopvwmNHtuxn

759  

760 if req <= total: 1qcghaikdbrsyopvwmNHtuxn

761 # The range already maps the request: a new buffer over the same 

762 # range, at least as large as the input, with no driver call. The 

763 # result is a distinct object, so closing it never closes ``buf``. 

764 h_ptr_new = deviceptr_create_vmm(base, rng) 1bom

765 if not h_ptr_new: 1bom

766 _raise_last_error() 

767 self._copy_deallocation_stream(h_ptr_new, h_in) 1bom

768 return Buffer_from_deviceptr_handle( 1bom

769 h_ptr_new, req if req > in_size else in_size, self, None, VirtualMemoryBuffer) 1bom

770  

771 # The new chunk is a whole number of granules; the result covers it. 

772 add = _align_up(req - total, gran) 1qcghaikdbrsyopvwmNHtuxn

773 req = total + add 1qcghaikdbrsyopvwmNHtuxn

774 # The result's range is built from a copy of the input's mappings; the 

775 # input's range is immutable and never changes. 

776 mappings = vmm_range_mappings(rng) 1qcghaikdbrsyopvwmNHtuxn

777  

778 # Grow in place: reserve the range right after the current one. The 

779 # driver raises alignment 0 to its default, so the hint is well formed. 

780 with nogil: 1qcghaikdbrsyopvwmNHtuxn

781 h_res = create_va_reservation_handle(add, 0, base + total) 1qcghaikdbrsyopvwmNHtuxn

782 if not h_res: 1qcghaikdbrsyopvwmNHtuxn

783 get_last_error() # the probe may fail; that is not an error here 1N

784 elif as_cu(h_res) != base + total: 1qcghaikdbrsyopvwmHtuxn

785 h_res.reset() # granted elsewhere: free it and move instead 1qcghaikdbrsyopvwmtuxn

786 else: 

787 with nogil: 1abmHn

788 h_alloc = create_mem_allocation_handle(add, prop, descs.data(), descs.size()) 1abmHn

789 if not h_alloc: 1abmHn

790 _raise_last_error() 

791 with nogil: 1abmHn

792 h_map = create_va_mapping_handle(base + total, h_alloc, h_res) 1abmHn

793 if not h_map: 1abmHn

794 _raise_last_error() 

795 mappings.push_back(h_map) 1abmHn

796 rng_new = create_vmm_range(mappings) 1abmHn

797 h_ptr_new = deviceptr_create_vmm(base, rng_new) 1abmHn

798 if not h_ptr_new: 1abmHn

799 _raise_last_error() 

800 self._copy_deallocation_stream(h_ptr_new, h_in) 1abmHn

801 return Buffer_from_deviceptr_handle(h_ptr_new, req, self, None, VirtualMemoryBuffer) 1abmHn

802  

803 # Move: a new range that maps every existing allocation, then the new one. 

804 # The allocations are shared with the input buffer's range. 

805 addr_align = cfg.addr_align or gran 1qcghaikdbrsyopvwmNtuxn

806 with nogil: 1qcghaikdbrsyopvwmNtuxn

807 h_res_new = create_va_reservation_handle(req, addr_align, 0) 1qcghaikdbrsyopvwmNtuxn

808 if not h_res_new: 1qcghaikdbrsyopvwmNtuxn

809 _raise_last_error() 1N

810 base_new = as_cu(h_res_new) 1qcghaikdbrsyopvwmtuxn

811 offset = 0 1qcghaikdbrsyopvwmtuxn

812 for i in range(mappings.size()): 1qcghaikdbrsyopvwmtuxn

813 a = va_mapping_allocation(mappings[i]) 1qcghaikdbrsyopvwmtuxn

814 with nogil: 1qcghaikdbrsyopvwmtuxn

815 m2 = create_va_mapping_handle(base_new + offset, a, h_res_new) 1qcghaikdbrsyopvwmtuxn

816 if not m2: 1qcghaikdbrsyopvwmtuxn

817 _raise_last_error() 

818 offset += mem_allocation_size(a) 1qcghaikdbrsyopvwmtuxn

819 mappings[i] = m2 # the input's range still holds the old mapping 1qcghaikdbrsyopvwmtuxn

820 with nogil: 1qcghaikdbrsyopvwmtuxn

821 h_alloc = create_mem_allocation_handle(add, prop, descs.data(), descs.size()) 1qcghaikdbrsyopvwmtuxn

822 if not h_alloc: 1qcghaikdbrsyopvwmtuxn

823 _raise_last_error() 

824 with nogil: 1qcghaikdbrsyopvwmtuxn

825 m2 = create_va_mapping_handle(base_new + offset, h_alloc, h_res_new) 1qcghaikdbrsyopvwmtuxn

826 if not m2: 1qcghaikdbrsyopvwmtuxn

827 _raise_last_error() 

828 mappings.push_back(m2) 1qcghaikdbrsyopvwmtuxn

829 rng_new = create_vmm_range(mappings) 1qcghaikdbrsyopvwmtuxn

830 h_ptr_new = deviceptr_create_vmm(base_new, rng_new) 1qcghaikdbrsyopvwmtuxn

831 if not h_ptr_new: 1qcghaikdbrsyopvwmtuxn

832 _raise_last_error() 

833 self._copy_deallocation_stream(h_ptr_new, h_in) 1qcghaikdbrsyopvwmtuxn

834 return Buffer_from_deviceptr_handle(h_ptr_new, req, self, None, VirtualMemoryBuffer) 1qcghaikdbrsyopvwmtuxn

835  

836 def deallocate(self, ptr: DevicePointerType, size: int, *, stream: Stream | GraphBuilder | None = None) -> None: 

837 """ 

838 Unmap and free one address range that was reserved and mapped outside this resource. 

839  

840 Buffers returned by :meth:`allocate` and :meth:`modify_allocation` free 

841 themselves when they close and never call this method. It exists for 

842 raw pointers wrapped with :meth:`Buffer.from_handle` with ``mr`` set to 

843 this resource: the range must be exactly one reservation, and the caller 

844 must already have released its own ``cuMemCreate`` handle, so the 

845 physical memory is freed by the unmap. 

846  

847 Parameters 

848 ---------- 

849 ptr : DevicePointerType 

850 The start of the reservation. 

851 size : int 

852 The size of the reservation in bytes. 

853 stream : :obj:`~_stream.Stream` | :obj:`~graph.GraphBuilder`, optional 

854 Keyword-only. If given, ``stream.sync()`` is called before the 

855 range is unmapped, except for a default-stream token on a 

856 host-located resource, which has no context to synchronize in. 

857 """ 

858 cdef cydriver.CUdeviceptr devptr = 0 if ptr is None else <cydriver.CUdeviceptr>int(ptr) 1:

859 cdef size_t nbytes = size 1:

860 cdef Stream s 

861 if stream is not None: 1:

862 s = Stream_accept(stream) 1:

863 # A host-located resource records no stream, so Buffer teardown 

864 # passes an unbound default-stream token, which has no context to 

865 # synchronize in. There is nothing queued on it to wait for. 

866 if self.device is not None or not Stream_is_default_token(s): 1:

867 s.sync() 1:

868 if devptr == 0 or nbytes == 0: 1:

869 return 

870 with nogil: 1:

871 HANDLE_RETURN(cydriver.cuMemUnmap(devptr, nbytes)) 1:

872 HANDLE_RETURN(cydriver.cuMemAddressFree(devptr, nbytes)) 1:

873  

874 @property 

875 def is_device_accessible(self) -> bool: 

876 """ 

877 Indicates whether the allocated memory is accessible from the device. 

878 """ 

879 return self.config.location_type == "device" 1:x

880  

881 @property 

882 def is_host_accessible(self) -> bool: 

883 """ 

884 Indicates whether the allocated memory is accessible from the host. 

885 """ 

886 return self.config.location_type in _HOST_LOCATION_TYPES 2- x ? xbyb

887  

888 @property 

889 def is_ipc_enabled(self) -> bool: 

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

891  

892 True when ``config.handle_type`` is ``"posix_fd"`` on Linux and the 

893 memory lives on a device; ``handle_type`` is the only switch, there is 

894 no separate option. With ``handle_type=None`` the allocations cannot 

895 be exported, Win32 KMT handles are not transported by cuda.core, and 

896 fabric handles are not supported yet. Host-located virtual memory 

897 (``location_type`` ``"host"``, ``"host_numa"``, or 

898 ``"host_numa_current"``) is not shared yet. 

899 """ 

900 return ( 2@ [ W ] Y ^ K _ ` { 1 | Z } 0 ~ abbbT cb2 dbebfbU gb3 hbibjbV kb4 lbmbO nbS obP pbQ qbrbz A L sbM tbR ubD e c / g h E a i j k d b f B C l zbvb; = AbwbBbF G I J # $

901 self.config.handle_type in _IPC_HANDLE_TYPES 2@ [ W ] Y ^ K _ ` { 1 | Z } 0 ~ abbbT cb2 dbebfbU gb3 hbibjbV kb4 lbmbO nbS obP pbQ qbrbz A L sbM tbR ubD e c / g h E a i j k d b f B C l zbvb; = AbwbBbF G I J # $

902 and self.config.location_type not in _HOST_LOCATION_TYPES 2@ [ W ] Y ^ K _ ` { 1 | Z } 0 ~ abbbT cb2 dbebfbU gb3 hbibjbV kb4 lbmbO nbS obP pbQ qbrbz A L sbM tbR ubD e c / g h E a i j k d b f B C l ; = wbF G I J # $

903 ) 

904  

905 def _import_ipc_buffer(self, VirtualMemoryIPCBufferDescriptor desc not None, stream) -> VirtualMemoryBuffer: 

906 """Import a buffer that another process exported; see :meth:`Buffer.from_ipc_descriptor`. 

907  

908 Every exported allocation is imported with this resource's handle 

909 type and checked to live on this resource's device. The allocations 

910 are then mapped in order into 

911 one new address reservation with the access descriptors this 

912 resource's options produce for its device and peers, and the 

913 deallocation stream is recorded as :meth:`allocate` does. The 

914 returned buffer owns the imported handles like a buffer from 

915 :meth:`allocate` owns created ones, reports ``is_mapped``, and cannot 

916 be exported again. 

917 """ 

918 cdef Stream s = None 1ec/ajdbfCl

919 cdef object cfg = self.config 1ec/ajdbfCl

920 cdef cydriver.CUmemAllocationProp prop 

921 cdef cydriver.CUmemAllocationProp imported_prop 

922 cdef vector[cydriver.CUmemAccessDesc] descs 

923 cdef vector[MemAllocationHandle] allocs 

924 cdef vector[VaMappingHandle] mappings 

925 cdef MemAllocationHandle h_alloc 

926 cdef VaReservationHandle h_res 

927 cdef VaMappingHandle h_map 

928 cdef VmmRangeHandle rng 

929 cdef DevicePtrHandle h_ptr 

930 cdef Buffer buf 

931 cdef size_t gran, gran_min = 0, addr_align, chunk, total = 0, offset = 0 1ec/ajdbfCl

932 cdef Py_ssize_t n, i 

933 cdef cydriver.CUdeviceptr hint 

934 cdef int handle_type, fd 

935 cdef void* os_handle 

936  

937 if not self.is_ipc_enabled: 1ec/ajdbfCl

938 raise RuntimeError("Memory resource is not IPC-enabled") 1C

939 handle_type = int(VirtualMemoryResourceOptions._handle_type_to_driver(cfg.handle_type)) 1ec/ajdbfl

940 if desc._handle_type != handle_type: 1ec/ajdbfl

941 raise ValueError( 1e

942 "the descriptor's handle type does not match this resource's " 1e

943 f"handle_type={str(cfg.handle_type)!r}; import it with a resource configured like the exporter's" 1e

944 ) 

945 n = len(desc._chunk_sizes) 1ec/ajdbfl

946 if n != len(desc._handles): 1ec/ajdbfl

947 raise ValueError("malformed VirtualMemoryResource buffer descriptor") 1e

948 if stream is not None: 1ec/ajdbfl

949 s = Stream_accept(stream) 1ec/ajdbfl

950 if n == 0: 1ec/ajdbfl

951 if desc._size != 0: 1e/

952 raise ValueError( 1e

953 f"malformed VirtualMemoryResource buffer descriptor: a size of {desc._size} bytes with no " 1e

954 "exported allocations" 

955 ) 

956 # An exported empty buffer: an empty range, as allocate(0) returns. 

957 h_ptr = deviceptr_create_vmm(0, create_vmm_range(mappings)) 1/

958 if s is not None and not Stream_is_default_token(s): 1/

959 HANDLE_RETURN(set_deallocation_stream(h_ptr, s._h_stream)) 

960 buf = Buffer_from_deviceptr_handle(h_ptr, 0, self, None, VirtualMemoryBuffer) 1/

961 buf._ipc_data = IPCDataForBuffer(None, True) 1/

962 return buf 1/

963  

964 self._fill_prop(cfg, &prop) 1ecajdbfl

965 self._fill_access(cfg, &prop, descs) 1ecajdbfl

966 gran = self._granularity(cfg, &prop) 1ecajdbfl

967 # cuMemMap requires the minimum granularity, whatever this resource's 

968 # granularity option says; the exporter may have used either. 

969 with nogil: 1ecajdbfl

970 HANDLE_RETURN(cydriver.cuMemGetAllocationGranularity( 1ecajdbfl

971 &gran_min, &prop, cydriver.CU_MEM_ALLOC_GRANULARITY_MINIMUM)) 

972 for chunk_obj in desc._chunk_sizes: 1ecajdbfl

973 chunk = chunk_obj 1ecajdbfl

974 if chunk == 0 or chunk % gran_min != 0: 1ecajdbfl

975 raise ValueError( 1e

976 f"descriptor chunk size {chunk} is not a positive multiple of the {gran_min}-byte minimum " 1e

977 "granularity" 

978 ) 

979 if chunk > <size_t>-1 - total: 1ecajdbfl

980 raise OverflowError("the descriptor's chunk sizes do not fit in size_t") 

981 total += chunk 1ecajdbfl

982 if desc._size > total: 1ecajdbfl

983 raise ValueError(f"descriptor size ({desc._size}) exceeds the exported allocations ({total} bytes)") 1e

984 addr_align = cfg.addr_align or gran 1ecajdbfl

985 hint = cfg.addr_hint or 0 1ecajdbfl

986  

987 # Every handle below is a local: if a later step fails, the locals die 

988 # in reverse order and release everything imported so far. 

989 # 

990 # The driver copies each shared allocation into a handle of its own 

991 # during cuMemImportFromShareableHandle and keeps no reference to the 

992 # file descriptor afterwards: the shareable handle and the allocation 

993 # handle are independent references to the memory, and the CUDA 

994 # samples and PyTorch close the descriptor right after the import. The 

995 # buffer therefore does not keep the descriptor; its file descriptors 

996 # live only as long as the caller holds the descriptor object. 

997 for i in range(n): 1ecajdbfl

998 fd = int(desc._handles[i]) # raises if the handle was closed 1ecajdbfl

999 chunk = desc._chunk_sizes[i] 1ecajdbfl

1000 os_handle = <void*><uintptr_t>fd 1ecajdbfl

1001 with nogil: 1ecajdbfl

1002 h_alloc = import_mem_allocation_handle( 1ecajdbfl

1003 os_handle, <cydriver.CUmemAllocationHandleType>handle_type, chunk, descs.data(), descs.size()) 

1004 if not h_alloc: 1ecajdbfl

1005 _raise_last_error_noting( 1e

1006 f"while importing chunk {i + 1} of {n} of a VirtualMemoryResource buffer descriptor; either " 1e

1007 "the shared handle does not refer to a CUDA allocation exported with " 

1008 f"handle_type={str(cfg.handle_type)!r}, or the device that owns the memory is not usable in " 1e

1009 "this process (for example, CUDA_VISIBLE_DEVICES hides it)" 

1010 ) 

1011 # The exporter chose where the memory lives. This resource must map 

1012 # memory of that kind on that device, or its access descriptors 

1013 # would describe the wrong location. 

1014 with nogil: 1ecajdbfl

1015 HANDLE_RETURN(cydriver.cuMemGetAllocationPropertiesFromHandle(&imported_prop, as_cu(h_alloc))) 1ecajdbfl

1016 if imported_prop.location.type != prop.location.type or ( 1ecajdbfl

1017 prop.location.type == cydriver.CU_MEM_LOCATION_TYPE_DEVICE 1ecajdbfl

1018 and imported_prop.location.id != prop.location.id 1ecajdbfl

1019 ): 

1020 raise ValueError( 

1021 f"chunk {i + 1} of the descriptor is memory on {_describe_location(imported_prop.location)}, " 

1022 f"but this resource maps memory on {_describe_location(prop.location)}; import the descriptor " 

1023 "with a VirtualMemoryResource for the device that owns the memory, and name other devices " 

1024 "in its peers option" 

1025 ) 

1026 allocs.push_back(h_alloc) 1ecajdbfl

1027 with nogil: 1ecajdbfl

1028 h_res = create_va_reservation_handle(total, addr_align, hint) 1ecajdbfl

1029 if not h_res: 1ecajdbfl

1030 _raise_last_error() 

1031 for i in range(n): 1ecajdbfl

1032 chunk = mem_allocation_size(allocs[<size_t>i]) 1ecajdbfl

1033 with nogil: 1ecajdbfl

1034 h_map = create_va_mapping_handle(as_cu(h_res) + offset, allocs[<size_t>i], h_res) 1ecajdbfl

1035 if not h_map: 1ecajdbfl

1036 _raise_last_error_noting( 1c

1037 f"while mapping chunk {i + 1} of {n} ({chunk} bytes) of a VirtualMemoryResource buffer " 1c

1038 "descriptor; a chunk size that does not match the exported allocation fails here, and the " 

1039 "sizes come from the exporting peer" 

1040 ) 

1041 mappings.push_back(h_map) 1ecajdbfl

1042 offset += chunk 1ecajdbfl

1043 rng = create_vmm_range(mappings) 1ecajdbfl

1044 h_ptr = deviceptr_create_vmm(as_cu(h_res), rng) 1ecajdbfl

1045 if not h_ptr: 1ecajdbfl

1046 _raise_last_error() 

1047 self._record_deallocation_stream(h_ptr, s) 1ecajdbfl

1048 buf = Buffer_from_deviceptr_handle(h_ptr, desc._size, self, None, VirtualMemoryBuffer) 1ecajdbfl

1049 buf._ipc_data = IPCDataForBuffer(None, True) 1ecajdbfl

1050 return buf 1ecajdbfl

1051  

1052 @property 

1053 def device_id(self) -> int: 

1054 """ 

1055 Get the device ID associated with this memory resource. 

1056  

1057 Returns: 

1058 int: CUDA device ID. -1 if the memory resource allocates host memory 

1059 """ 

1060 return self.device.device_id if self.device is not None else -1 2Cb# $ p x ? xbyb

1061  

1062 def __repr__(self) -> str: 

1063 """ 

1064 Return a string representation of the VirtualMemoryResource. 

1065  

1066 Returns: 

1067 str: A string describing the object 

1068 """ 

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

1070  

1071  

1072cdef VirtualMemoryIPCBufferDescriptor _export_ipc_descriptor(Buffer buf): 

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

1074  

1075 Only the allocations that cover ``buf.size`` are exported; a buffer's size 

1076 is a prefix of its range. Each file descriptor is owned by an 

1077 :class:`IPCAllocationHandle` in the new descriptor and closed with it; 

1078 nothing is cached on the buffer. A buffer that contains memory imported 

1079 from another process is refused before any driver call: the driver 

1080 exports only allocations this process created with the handle type, and 

1081 every allocation carries its provenance (``mem_allocation_is_imported``) 

1082 into every range that maps it. 

1083 """ 

1084 cdef VirtualMemoryResource mr = <VirtualMemoryResource>buf._memory_resource 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ

1085 cdef vector[VaMappingHandle] mappings 

1086 cdef vector[MemAllocationHandle] allocs 

1087 cdef MemAllocationHandle h_alloc 

1088 cdef cydriver.CUresult status 

1089 cdef size_t i, n, chunk, offset = 0 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ

1090 cdef int handle_type 

1091 cdef int fd = -1 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ

1092 cdef list sizes = [] 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ

1093 cdef list handles = [] 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ

1094  

1095 if not mr.is_ipc_enabled: 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ

1096 raise RuntimeError("Memory resource is not IPC-enabled") 1C

1097 handle_type = int(VirtualMemoryResourceOptions._handle_type_to_driver(mr.config.handle_type)) 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ

1098 mappings = vmm_range_mappings(vmm_range(buf._h_ptr)) 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ

1099 for i in range(mappings.size()): 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ

1100 if offset >= buf._size: 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ

1101 break 

1102 h_alloc = va_mapping_allocation(mappings[i]) 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ

1103 allocs.push_back(h_alloc) 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ

1104 offset += mem_allocation_size(h_alloc) 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ

1105 n = allocs.size() 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ

1106 for i in range(n): 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ

1107 if mem_allocation_is_imported(allocs[i]): 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ

1108 raise RuntimeError( 1bf

1109 f"this buffer contains memory imported from another process (allocation {i + 1} of {n}); imported " 1bf

1110 "memory cannot be exported again. Forward the descriptor it was imported from, or copy the data " 

1111 "into a buffer this process allocated." 

1112 ) 

1113 for i in range(n): 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ

1114 h_alloc = allocs[i] 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ

1115 chunk = mem_allocation_size(h_alloc) 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ

1116 with nogil: 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ

1117 status = cydriver.cuMemExportToShareableHandle( 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ

1118 <void*>&fd, as_cu(h_alloc), <cydriver.CUmemAllocationHandleType>handle_type, 0) 

1119 _check_noting( 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ

1120 status, 

1121 f"while exporting allocation {i + 1} of {n} of a VirtualMemoryResource buffer; each exported " 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ

1122 "allocation takes one file descriptor, so a process near its file descriptor limit fails here", 

1123 ) 

1124 try: 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ

1125 handle = IPCAllocationHandle._init(fd, None) 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ

1126 except: # noqa: E722 rollback-then-raise: the fd is not owned yet 

1127 os.close(fd) 

1128 raise 

1129 # The handle owns the fd now; if append fails, the handle closes it once. 

1130 handles.append(handle) 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ

1131 sizes.append(chunk) 1WYK1Z0T2U3V4OSPQzALMRDecghEaijkdbfBClFGIJ

1132 return VirtualMemoryIPCBufferDescriptor._from_exports(handle_type, tuple(sizes), tuple(handles), buf._size) 1WYK1Z0T2U3V4OSPQzALMRDec/ghEaijkdbfBClFGIJ