Coverage for cuda/core/_memory/_virtual_memory_resource.py: 91.50%
247 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-03 02:41 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-03 02:41 +0000
1# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2#
3# SPDX-License-Identifier: Apache-2.0
5from __future__ import annotations
7from dataclasses import dataclass, field
8from typing import TYPE_CHECKING, Iterable
10if TYPE_CHECKING:
11 from cuda.core._stream import Stream
12 from cuda.core.graph import GraphBuilder
14from cuda.core._device import Device
15from cuda.core._memory._buffer import Buffer, MemoryResource
16from cuda.core._utils.cuda_utils import (
17 Transaction,
18 check_or_create_options,
19 driver,
20)
21from cuda.core._utils.cuda_utils import (
22 _check_driver_error as raise_if_driver_error,
23)
24from cuda.core._utils.version import binding_version
25from cuda.core.typing import (
26 DevicePointerType,
27 VirtualMemoryAccessType,
28 VirtualMemoryAllocationType,
29 VirtualMemoryGranularityType,
30 VirtualMemoryHandleType,
31 VirtualMemoryLocationType,
32)
34__all__ = ["VirtualMemoryResource", "VirtualMemoryResourceOptions"]
36# Location types whose physical backing lives in host memory. Shared by
37# VirtualMemoryResource.__init__ and is_host_accessible so the two cannot drift.
38_HOST_LOCATION_TYPES = frozenset(
39 {
40 VirtualMemoryLocationType.HOST,
41 VirtualMemoryLocationType.HOST_NUMA,
42 VirtualMemoryLocationType.HOST_NUMA_CURRENT,
43 }
44)
47@dataclass
48class VirtualMemoryResourceOptions:
49 """A configuration object for the VirtualMemoryResource
50 Stores configuration information which tells the resource how to use the CUDA VMM APIs
52 Attributes
53 ----------
54 allocation_type: :obj:`~_memory.VirtualMemoryAllocationType` | str
55 Controls the type of allocation.
56 location_type: :obj:`~_memory.VirtualMemoryLocationType` | str
57 Controls the location of the allocation.
58 handle_type: :obj:`~_memory.VirtualMemoryHandleType` | str
59 Export handle type for the physical allocation. Use ``"posix_fd"`` on
60 Linux if you plan to import/export the allocation. Use `None` if you
61 don't need an exportable handle.
62 gpu_direct_rdma: bool
63 Hint that the allocation should be GDR-capable (if supported).
64 granularity: :obj:`~_memory.VirtualMemoryGranularityType` | str
65 Controls granularity query and size rounding.
66 addr_hint: int
67 A (optional) virtual address hint to try to reserve at. Setting it to 0 lets the CUDA driver decide.
68 addr_align: int
69 Alignment for the VA reservation. If `None`, use the queried granularity.
70 peers: Iterable[int]
71 Extra device IDs that should be granted access in addition to ``device``.
72 self_access: :obj:`~_memory.VirtualMemoryAccessType` | None | str
73 Access flags for the owning device.
74 peer_access: :obj:`~_memory.VirtualMemoryAccessType` | None | str
75 Access flags for peers.
76 """
78 allocation_type: VirtualMemoryAllocationType = VirtualMemoryAllocationType.PINNED
79 location_type: VirtualMemoryLocationType = VirtualMemoryLocationType.DEVICE
80 handle_type: VirtualMemoryHandleType = VirtualMemoryHandleType.POSIX_FD
81 granularity: VirtualMemoryGranularityType = VirtualMemoryGranularityType.RECOMMENDED
82 gpu_direct_rdma: bool = False
83 addr_hint: int | None = 0
84 addr_align: int | None = None
85 peers: Iterable[int] = field(default_factory=tuple)
86 self_access: VirtualMemoryAccessType = VirtualMemoryAccessType.READ_WRITE
87 peer_access: VirtualMemoryAccessType = VirtualMemoryAccessType.READ_WRITE
89 _a = driver.CUmemAccess_flags
90 _access_flags = { # noqa: RUF012
91 VirtualMemoryAccessType.READ_WRITE: _a.CU_MEM_ACCESS_FLAGS_PROT_READWRITE,
92 VirtualMemoryAccessType.READ: _a.CU_MEM_ACCESS_FLAGS_PROT_READ,
93 None: 0,
94 }
95 _h = driver.CUmemAllocationHandleType
96 _handle_types = { # noqa: RUF012
97 None: _h.CU_MEM_HANDLE_TYPE_NONE,
98 VirtualMemoryHandleType.POSIX_FD: _h.CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR,
99 VirtualMemoryHandleType.WIN32_KMT: _h.CU_MEM_HANDLE_TYPE_WIN32_KMT,
100 VirtualMemoryHandleType.FABRIC: _h.CU_MEM_HANDLE_TYPE_FABRIC,
101 }
102 _g = driver.CUmemAllocationGranularity_flags
103 _granularity = { # noqa: RUF012
104 VirtualMemoryGranularityType.RECOMMENDED: _g.CU_MEM_ALLOC_GRANULARITY_RECOMMENDED,
105 VirtualMemoryGranularityType.MINIMUM: _g.CU_MEM_ALLOC_GRANULARITY_MINIMUM,
106 }
107 _l = driver.CUmemLocationType
108 _location_type = { # noqa: RUF012
109 VirtualMemoryLocationType.DEVICE: _l.CU_MEM_LOCATION_TYPE_DEVICE,
110 VirtualMemoryLocationType.HOST: _l.CU_MEM_LOCATION_TYPE_HOST,
111 VirtualMemoryLocationType.HOST_NUMA: _l.CU_MEM_LOCATION_TYPE_HOST_NUMA,
112 VirtualMemoryLocationType.HOST_NUMA_CURRENT: _l.CU_MEM_LOCATION_TYPE_HOST_NUMA_CURRENT,
113 }
114 _t = driver.CUmemAllocationType
115 # CUDA 13+ exposes MANAGED in CUmemAllocationType; older 12.x does not
116 _allocation_type = {VirtualMemoryAllocationType.PINNED: _t.CU_MEM_ALLOCATION_TYPE_PINNED} # noqa: RUF012
117 if binding_version() >= (13, 0, 0):
118 _allocation_type[VirtualMemoryAllocationType.MANAGED] = _t.CU_MEM_ALLOCATION_TYPE_MANAGED
120 @staticmethod
121 def _access_to_flags(spec: VirtualMemoryAccessType | None) -> int:
122 flags = VirtualMemoryResourceOptions._access_flags.get(spec) 1cdafbk
123 if flags is None: 1cdafbk
124 raise ValueError(f"Unknown access spec: {spec!r}") 1k
125 return flags # type: ignore[no-any-return] 1cdafb
127 @staticmethod
128 def _allocation_type_to_driver(spec: VirtualMemoryAllocationType) -> int:
129 alloc_type = VirtualMemoryResourceOptions._allocation_type.get(spec) 1cdabl
130 if alloc_type is None: 1cdabl
131 raise ValueError(f"Unsupported allocation_type: {spec!r}") 1l
132 return alloc_type # type: ignore[no-any-return] 1cdab
134 @staticmethod
135 def _location_type_to_driver(spec: VirtualMemoryLocationType) -> int:
136 loc_type = VirtualMemoryResourceOptions._location_type.get(spec) 1cdabm
137 if loc_type is None: 1cdabm
138 raise ValueError(f"Unsupported location_type: {spec!r}") 1m
139 return loc_type # type: ignore[no-any-return] 1cdab
141 @staticmethod
142 def _handle_type_to_driver(spec: VirtualMemoryHandleType | None) -> int:
143 if spec == "win32": 1cdaboj
144 raise NotImplementedError("win32 is currently not supported, please reach out to the CUDA Python team") 1o
145 handle_type = VirtualMemoryResourceOptions._handle_types.get(spec) 1cdabj
146 if handle_type is None: 1cdabj
147 raise ValueError(f"Unsupported handle_type: {spec!r}") 1j
148 return handle_type # type: ignore[no-any-return] 1cdab
150 @staticmethod
151 def _granularity_to_driver(spec: VirtualMemoryGranularityType) -> int:
152 granularity = VirtualMemoryResourceOptions._granularity.get(spec) 1cdabn
153 if granularity is None: 1cdabn
154 raise ValueError(f"Unsupported granularity: {spec!r}") 1n
155 return granularity # type: ignore[no-any-return] 1cdab
158class VirtualMemoryResource(MemoryResource):
159 """Create a device memory resource that uses the CUDA VMM APIs to allocate memory.
161 Parameters
162 ----------
163 device_id : Device | int
164 Device for which a memory resource is constructed.
166 config : VirtualMemoryResourceOptions, optional
167 A configuration object for the VirtualMemoryResource
170 Warning
171 -------
172 This is a low-level API that is provided only for convenience. Make sure you fully understand
173 how CUDA Virtual Memory Management works before using this. Other MemoryResource subclasses
174 in cuda.core should already meet the common needs.
175 """
177 def __init__(self, device_id: Device | int, config: VirtualMemoryResourceOptions | None = None) -> None:
178 self.device: Device | None = Device(device_id) 1cdafbghi
179 self.config: VirtualMemoryResourceOptions = check_or_create_options( # type: ignore[assignment] 1cdafbghi
180 VirtualMemoryResourceOptions, config, "VirtualMemoryResource options", keep_none=False
181 )
182 if self.config.location_type in _HOST_LOCATION_TYPES: 1cdafbghi
183 self.device = None 1ghi
185 if not self.device and self.config.location_type == "device": 1cdafbghi
186 raise RuntimeError("VirtualMemoryResource requires a device for device memory allocations")
188 if self.device and not self.device.properties.virtual_memory_management_supported: 1cdafbghi
189 raise RuntimeError("VirtualMemoryResource requires CUDA VMM API support")
191 # Validate RDMA support if requested
192 if (
193 self.config.gpu_direct_rdma
194 and self.device is not None
195 and not self.device.properties.gpu_direct_rdma_supported
196 ):
197 raise RuntimeError("GPU Direct RDMA is not supported on this device")
199 @staticmethod
200 def _align_up(size: int, gran: int) -> int:
201 """
202 Align a size up to the nearest multiple of a granularity.
203 """
204 return (size + gran - 1) & ~(gran - 1) 1cdab
206 def modify_allocation(
207 self, buf: Buffer, new_size: int, config: VirtualMemoryResourceOptions | None = None
208 ) -> Buffer:
209 """
210 Grow an existing allocation using CUDA VMM, with a configurable policy.
212 This implements true growing allocations that preserve the base pointer
213 by extending the virtual address range and mapping additional physical memory.
215 This function uses transactional allocation: if any step fails, the original buffer is not modified and
216 all steps the function took are rolled back so a new allocation is not created.
218 Parameters
219 ----------
220 buf : Buffer
221 The existing buffer to grow
222 new_size : int
223 The new total size for the allocation
224 config : VirtualMemoryResourceOptions, optional
225 Configuration for the new physical memory chunks. If None, uses current config.
227 Returns
228 -------
229 Buffer
230 The same buffer with updated size and properties, preserving the original pointer
231 """
232 if not isinstance(buf, Buffer): 1ab
233 raise TypeError(f"buf must be a Buffer, got {type(buf).__name__}")
234 if buf.is_closed: 1ab
235 raise RuntimeError("Buffer has been closed")
236 if config is not None: 1ab
237 self.config = config 1b
239 # Build allocation properties for new chunks
240 prop = driver.CUmemAllocationProp() 1ab
241 prop.type = VirtualMemoryResourceOptions._allocation_type_to_driver(self.config.allocation_type) 1ab
242 prop.location.type = VirtualMemoryResourceOptions._location_type_to_driver(self.config.location_type) 1ab
243 # Caller must not invoke modify_allocation on a host-located resource;
244 # we rely on the dataclass invariant that self.device is non-None for
245 # device-located resources (it's only None when location is host).
246 assert self.device is not None, "modify_allocation requires a device-located resource" 1ab
247 prop.location.id = self.device.device_id 1ab
248 prop.allocFlags.gpuDirectRDMACapable = 1 if self.config.gpu_direct_rdma else 0 1ab
249 prop.requestedHandleTypes = VirtualMemoryResourceOptions._handle_type_to_driver(self.config.handle_type) 1ab
250 prop.win32HandleMetaData = 0 1ab
252 # Query granularity
253 gran_flag = VirtualMemoryResourceOptions._granularity_to_driver(self.config.granularity) 1ab
254 res, gran = driver.cuMemGetAllocationGranularity(prop, gran_flag) 1ab
255 raise_if_driver_error(res) 1ab
257 # Calculate sizes
258 additional_size = new_size - buf.size 1ab
259 if additional_size <= 0: 1ab
260 # Same size: only update access policy if needed; avoid zero-sized driver calls
261 descs = self._build_access_descriptors(prop) 1ab
262 if descs: 1ab
263 (res,) = driver.cuMemSetAccess(int(buf.handle), buf.size, descs, len(descs)) 1ab
264 raise_if_driver_error(res) 1ab
265 return buf 1ab
267 aligned_additional_size = VirtualMemoryResource._align_up(additional_size, gran) 1a
268 total_aligned_size = VirtualMemoryResource._align_up(new_size, gran) 1a
269 aligned_prev_size = total_aligned_size - aligned_additional_size 1a
270 addr_align = self.config.addr_align or gran 1a
272 # Try to extend the existing VA range first
273 res, new_ptr = driver.cuMemAddressReserve( 1a
274 aligned_additional_size,
275 addr_align,
276 int(buf.handle) + aligned_prev_size, # fixedAddr hint - aligned end of current range
277 0,
278 )
280 if res != driver.CUresult.CUDA_SUCCESS or new_ptr != (int(buf.handle) + aligned_prev_size): 1a
281 # Check for specific errors that are not recoverable with the slow path
282 if res in ( 1a
283 driver.CUresult.CUDA_ERROR_INVALID_VALUE,
284 driver.CUresult.CUDA_ERROR_NOT_PERMITTED,
285 driver.CUresult.CUDA_ERROR_NOT_INITIALIZED,
286 driver.CUresult.CUDA_ERROR_NOT_SUPPORTED,
287 ):
288 raise_if_driver_error(res)
289 (res2,) = driver.cuMemAddressFree(new_ptr, aligned_additional_size) 1a
290 raise_if_driver_error(res2) 1a
291 # Fallback: couldn't extend contiguously, need full remapping
292 return self._grow_allocation_slow_path( 1a
293 buf, new_size, prop, aligned_additional_size, total_aligned_size, addr_align
294 )
295 else:
296 # Success! We can extend the VA range contiguously
297 return self._grow_allocation_fast_path(buf, new_size, prop, aligned_additional_size, new_ptr)
299 def _grow_allocation_fast_path(
300 self, buf: Buffer, new_size: int, prop: driver.CUmemAllocationProp, aligned_additional_size: int, new_ptr: int
301 ) -> Buffer:
302 """
303 Fast path for growing a virtual memory allocation when the new region can be
304 reserved contiguously after the existing buffer.
306 This function creates and maps new physical memory for the additional size,
307 sets access permissions, and updates the buffer size in place (the pointer
308 remains unchanged).
310 Args:
311 buf (Buffer):
312 The buffer to grow.
314 new_size (int):
315 The new total size in bytes.
317 prop (driver.CUmemAllocationProp):
318 Allocation properties for the new memory.
320 aligned_additional_size (int):
321 The size of the new region to allocate, aligned to granularity.
323 new_ptr (int):
324 The address of the newly reserved contiguous VA region (should
325 be at the end of the current buffer).
327 Returns:
328 Buffer: The same buffer object with its size updated to `new_size`.
329 """
330 with Transaction() as trans: 1f
331 # Create new physical memory for the additional size
332 trans.append( 1f
333 lambda np=new_ptr, s=aligned_additional_size: raise_if_driver_error(driver.cuMemAddressFree(np, s)[0])
334 )
335 res, new_handle = driver.cuMemCreate(aligned_additional_size, prop, 0) 1f
336 raise_if_driver_error(res) 1f
337 # Register undo for creation
338 trans.append(lambda h=new_handle: raise_if_driver_error(driver.cuMemRelease(h)[0])) 1f
340 # Map the new physical memory to the extended VA range
341 (res,) = driver.cuMemMap(new_ptr, aligned_additional_size, 0, new_handle, 0) 1f
342 raise_if_driver_error(res) 1f
343 # Register undo for mapping
344 trans.append( 1f
345 lambda np=new_ptr, s=aligned_additional_size: raise_if_driver_error(driver.cuMemUnmap(np, s)[0])
346 )
348 # Set access permissions for the new portion
349 descs = self._build_access_descriptors(prop) 1f
350 if descs: 1f
351 (res,) = driver.cuMemSetAccess(new_ptr, aligned_additional_size, descs, len(descs)) 1f
352 raise_if_driver_error(res) 1f
354 # All succeeded, cancel undo actions
355 trans.commit() 1f
357 # Update the buffer size (pointer stays the same). `Buffer.size` has
358 # no public setter, so this reaches into the private attribute.
359 buf._size = new_size 1f
360 return buf 1f
362 def _grow_allocation_slow_path(
363 self,
364 buf: Buffer,
365 new_size: int,
366 prop: driver.CUmemAllocationProp,
367 aligned_additional_size: int,
368 total_aligned_size: int,
369 addr_align: int,
370 ) -> Buffer:
371 """
372 Slow path for growing a virtual memory allocation when the new region cannot be
373 reserved contiguously after the existing buffer.
375 This function reserves a new, larger virtual address (VA) range, remaps the old
376 physical memory to the beginning of the new VA range, creates and maps new physical
377 memory for the additional size, sets access permissions, and updates the buffer's
378 pointer and size.
380 Args:
381 buf (Buffer): The buffer to grow.
382 new_size (int): The new total size in bytes.
383 prop (driver.CUmemAllocationProp): Allocation properties for the new memory.
384 aligned_additional_size (int): The size of the new region to allocate, aligned to granularity.
385 total_aligned_size (int): The total new size to reserve, aligned to granularity.
386 addr_align (int): The required address alignment for the new VA range.
388 Returns:
389 Buffer: The buffer object updated with the new pointer and size.
390 """
391 with Transaction() as trans: 1a
392 # Reserve a completely new, larger VA range
393 res, new_ptr = driver.cuMemAddressReserve(total_aligned_size, addr_align, 0, 0) 1a
394 raise_if_driver_error(res) 1a
395 # Register undo for VA reservation
396 trans.append( 1a
397 lambda np=new_ptr, s=total_aligned_size: raise_if_driver_error(driver.cuMemAddressFree(np, s)[0])
398 )
400 # Get the old allocation handle for remapping
401 result, old_handle = driver.cuMemRetainAllocationHandle(buf.handle) 1a
402 raise_if_driver_error(result) 1a
403 # Register undo for old_handle
404 trans.append(lambda h=old_handle: raise_if_driver_error(driver.cuMemRelease(h)[0])) 1a
406 # Unmap the old VA range (aligned previous size)
407 aligned_prev_size = total_aligned_size - aligned_additional_size 1a
408 (result,) = driver.cuMemUnmap(int(buf.handle), aligned_prev_size) 1a
409 raise_if_driver_error(result) 1a
411 def _remap_old() -> None: 1a
412 # Try to remap the old physical memory back to the original VA range
413 try:
414 (res,) = driver.cuMemMap(int(buf.handle), aligned_prev_size, 0, old_handle, 0)
415 raise_if_driver_error(res)
416 except Exception: # noqa: S110
417 # TODO: consider logging this exception
418 pass
420 trans.append(_remap_old) 1a
422 # Remap the old physical memory to the new VA range (aligned previous size)
423 (res,) = driver.cuMemMap(int(new_ptr), aligned_prev_size, 0, old_handle, 0) 1a
424 raise_if_driver_error(res) 1a
426 # Register undo for mapping
427 trans.append(lambda np=new_ptr, s=aligned_prev_size: raise_if_driver_error(driver.cuMemUnmap(np, s)[0])) 1a
429 # Create new physical memory for the additional size
430 res, new_handle = driver.cuMemCreate(aligned_additional_size, prop, 0) 1a
431 raise_if_driver_error(res) 1a
433 # Register undo for new physical memory
434 trans.append(lambda h=new_handle: raise_if_driver_error(driver.cuMemRelease(h)[0])) 1a
436 # Map the new physical memory to the extended portion (aligned offset)
437 (res,) = driver.cuMemMap(int(new_ptr) + aligned_prev_size, aligned_additional_size, 0, new_handle, 0) 1a
438 raise_if_driver_error(res) 1a
440 # Register undo for mapping
441 trans.append( 1a
442 lambda base=int(new_ptr), offs=aligned_prev_size, s=aligned_additional_size: raise_if_driver_error(
443 driver.cuMemUnmap(base + offs, s)[0]
444 )
445 )
447 # Set access permissions for the entire new range
448 descs = self._build_access_descriptors(prop) 1a
449 if descs: 1a
450 (res,) = driver.cuMemSetAccess(new_ptr, total_aligned_size, descs, len(descs)) 1a
451 raise_if_driver_error(res) 1a
453 # All succeeded, cancel undo actions
454 trans.commit() 1a
456 # Free the old VA range (aligned previous size)
457 (res2,) = driver.cuMemAddressFree(int(buf.handle), aligned_prev_size) 1a
458 raise_if_driver_error(res2) 1a
460 # Invalidate the old buffer so its destructor won't try to free again
461 buf._clear() 1a
463 # Return a new Buffer for the new mapping
464 return Buffer.from_handle(ptr=new_ptr, size=new_size, mr=self) 1a
466 def _build_access_descriptors(self, prop: driver.CUmemAllocationProp) -> list[driver.CUmemAccessDesc]:
467 """
468 Build access descriptors for memory access permissions.
470 Returns
471 -------
472 list
473 List of CUmemAccessDesc objects for setting memory access
474 """
475 descs = [] 1cdafb
477 # Owner access
478 owner_flags = VirtualMemoryResourceOptions._access_to_flags(self.config.self_access) 1cdafb
479 if owner_flags: 1cdafb
480 d = driver.CUmemAccessDesc() 1cdafb
481 d.location.type = prop.location.type 1cdafb
482 d.location.id = prop.location.id 1cdafb
483 d.flags = owner_flags 1cdafb
484 descs.append(d) 1cdafb
486 # Peer device access
487 peer_flags = VirtualMemoryResourceOptions._access_to_flags(self.config.peer_access) 1cdafb
488 if peer_flags: 1cdafb
489 for peer_dev in self.config.peers: 1cdafb
490 d = driver.CUmemAccessDesc()
491 d.location.type = driver.CUmemLocationType.CU_MEM_LOCATION_TYPE_DEVICE
492 d.location.id = int(peer_dev)
493 d.flags = peer_flags
494 descs.append(d)
496 return descs 1cdafb
498 def allocate(self, size: int, *, stream: Stream | GraphBuilder | None = None) -> Buffer:
499 """
500 Allocate a buffer of the given size using CUDA virtual memory.
502 Parameters
503 ----------
504 size : int
505 The size in bytes of the buffer to allocate.
506 stream : Stream, optional
507 Keyword-only. Unused because virtual memory operations are
508 synchronous.
510 Returns
511 -------
512 Buffer
513 A Buffer object representing the allocated virtual memory.
515 Raises
516 ------
517 CUDAError
518 If any CUDA driver API call fails during allocation.
520 Notes
521 -----
522 This method uses transactional allocation: if any step fails, all resources
523 allocated so far are automatically cleaned up. The allocation is performed
524 with the configured granularity, access permissions, and peer access as
525 specified in the resource's configuration.
526 """
527 if stream is not None: 1cdab
528 from cuda.core._stream import Stream_accept
530 Stream_accept(stream)
532 config = self.config 1cdab
533 # ---- Build allocation properties ----
534 prop = driver.CUmemAllocationProp() 1cdab
535 prop.type = VirtualMemoryResourceOptions._allocation_type_to_driver(config.allocation_type) 1cdab
536 prop.location.type = VirtualMemoryResourceOptions._location_type_to_driver(config.location_type) 1cdab
537 prop.location.id = self.device.device_id if self.device is not None else -1 1cdab
538 prop.allocFlags.gpuDirectRDMACapable = 1 if config.gpu_direct_rdma else 0 1cdab
539 prop.requestedHandleTypes = VirtualMemoryResourceOptions._handle_type_to_driver(config.handle_type) 1cdab
540 prop.win32HandleMetaData = 0 1cdab
542 # ---- Query and apply granularity ----
543 # Choose min vs recommended granularity per config
544 gran_flag = VirtualMemoryResourceOptions._granularity_to_driver(config.granularity) 1cdab
545 res, gran = driver.cuMemGetAllocationGranularity(prop, gran_flag) 1cdab
546 raise_if_driver_error(res) 1cdab
548 aligned_size = VirtualMemoryResource._align_up(size, gran) 1cdab
549 addr_align = config.addr_align or gran 1cdab
551 # ---- Transactional allocation ----
552 with Transaction() as trans: 1cdab
553 # ---- Create physical memory ----
554 res, handle = driver.cuMemCreate(aligned_size, prop, 0) 1cdab
555 raise_if_driver_error(res) 1cdab
556 # Register undo for physical memory
557 trans.append(lambda h=handle: raise_if_driver_error(driver.cuMemRelease(h)[0])) 1cdab
559 # ---- Reserve VA space ----
560 # Potentially, use a separate size for the VA reservation from the physical allocation size
561 res, ptr = driver.cuMemAddressReserve(aligned_size, addr_align, config.addr_hint, 0) 1cdab
562 raise_if_driver_error(res) 1cdab
563 # Register undo for VA reservation
564 trans.append(lambda p=ptr, s=aligned_size: raise_if_driver_error(driver.cuMemAddressFree(p, s)[0])) 1cdab
566 # ---- Map physical memory into VA ----
567 (res,) = driver.cuMemMap(ptr, aligned_size, 0, handle, 0) 1cdab
568 trans.append(lambda p=ptr, s=aligned_size: raise_if_driver_error(driver.cuMemUnmap(p, s)[0])) 1cdab
569 raise_if_driver_error(res) 1cdab
571 # ---- Set access for owner + peers ----
572 descs = self._build_access_descriptors(prop) 1cdab
573 if descs: 1cdab
574 (res,) = driver.cuMemSetAccess(ptr, aligned_size, descs, len(descs)) 1cdab
575 raise_if_driver_error(res) 1cdab
577 trans.commit() 1cdab
579 # Done — return a Buffer that tracks this VA range
580 buf = Buffer.from_handle(ptr=ptr, size=aligned_size, mr=self) 1cdab
581 return buf 1cdab
583 def deallocate(self, ptr: DevicePointerType, size: int, *, stream: Stream | GraphBuilder | None = None) -> None:
584 """
585 Deallocate memory on the device using CUDA VMM APIs.
587 Parameters
588 ----------
589 ptr : DevicePointerType
590 The pointer to the memory to deallocate.
591 size : int
592 The size in bytes of the memory to deallocate.
593 stream : Stream, optional
594 Keyword-only. Unused because virtual memory operations are
595 synchronous.
596 """
597 ptr = 0 if ptr is None else int(ptr) 1cdab
599 if stream is not None: 1cdab
600 from cuda.core._stream import Stream_accept 1cdab
602 Stream_accept(stream) 1cdab
603 result, handle = driver.cuMemRetainAllocationHandle(ptr) 1cdab
604 raise_if_driver_error(result) 1cdab
605 (result,) = driver.cuMemUnmap(ptr, size) 1cdab
606 raise_if_driver_error(result) 1cdab
607 (result,) = driver.cuMemAddressFree(ptr, size) 1cdab
608 raise_if_driver_error(result) 1cdab
609 (result,) = driver.cuMemRelease(handle) 1cdab
610 raise_if_driver_error(result) 1cdab
612 @property
613 def is_device_accessible(self) -> bool:
614 """
615 Indicates whether the allocated memory is accessible from the device.
616 """
617 return self.config.location_type == "device"
619 @property
620 def is_host_accessible(self) -> bool:
621 """
622 Indicates whether the allocated memory is accessible from the host.
623 """
624 return self.config.location_type in _HOST_LOCATION_TYPES 1ghi
626 @property
627 def device_id(self) -> int:
628 """
629 Get the device ID associated with this memory resource.
631 Returns:
632 int: CUDA device ID. -1 if the memory resource allocates host memory
633 """
634 return self.device.device_id if self.device is not None else -1 1cdb
636 def __repr__(self) -> str:
637 """
638 Return a string representation of the VirtualMemoryResource.
640 Returns:
641 str: A string describing the object
642 """
643 return f"<VirtualMemoryResource device={self.device}>"