Coverage for cuda/core/_memory/_managed_memory_resource.pyx: 71.19%
118 statements
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-05 01:11 +0000
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-05 01:11 +0000
1# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2#
3# SPDX-License-Identifier: Apache-2.0
5from __future__ import annotations
7from cuda.bindings cimport cydriver
9from cuda.core._memory._memory_pool cimport _MemPool, _MP_allocate, MP_check_open
10from cuda.core._memory._memory_pool cimport MP_init_create_pool, MP_init_current_pool # no-cython-lint
11from cuda.core._stream cimport Stream, Stream_accept
12from cuda.core._utils.cuda_utils cimport HANDLE_RETURN
13from cuda.core._utils.cuda_utils cimport check_or_create_options # no-cython-lint
14from cuda.core._utils.cuda_utils import CUDAError # no-cython-lint
16from dataclasses import dataclass
17import threading
18from typing import TYPE_CHECKING
19import warnings
21from cuda.core._memory._managed_buffer import ManagedBuffer
22from cuda.core.typing import ManagedMemoryLocationType
24IF CUDA_CORE_BUILD_MAJOR >= 13:
25 from cuda.core._utils.validators import check_str_enum
27if TYPE_CHECKING:
28 from cuda.core.graph import GraphBuilder
30__all__ = ['ManagedMemoryResource', 'ManagedMemoryResourceOptions']
33@dataclass
34cdef class ManagedMemoryResourceOptions:
35 """Customizable :obj:`~_memory.ManagedMemoryResource` options.
37 Attributes
38 ----------
39 preferred_location : int | None, optional
40 A location identifier (device ordinal or NUMA node ID) whose
41 meaning depends on ``preferred_location_type``.
42 (Default to ``None``)
44 preferred_location_type : ManagedMemoryLocationType | str | None, optional
45 Controls how ``preferred_location`` is interpreted.
47 When set to ``None`` (the default), legacy behavior is used:
48 ``preferred_location`` is interpreted as a device ordinal,
49 ``-1`` for host, or ``None`` for no preference.
51 When set explicitly, the type determines both the kind of
52 preferred location and the valid values for
53 ``preferred_location``:
55 - ``"device"``: prefer a specific GPU. ``preferred_location``
56 must be a device ordinal (``>= 0``).
57 - ``"host"``: prefer host memory (OS-managed NUMA placement).
58 ``preferred_location`` must be ``None``.
59 - ``"host_numa"``: prefer a specific host NUMA node.
60 ``preferred_location`` must be a NUMA node ID (``>= 0``),
61 or ``None`` to derive the NUMA node from the current CUDA
62 device's ``host_numa_id`` attribute (requires an active
63 CUDA context).
65 (Default to ``None``)
66 """
67 preferred_location: int | None = None
68 preferred_location_type: ManagedMemoryLocationType | str | None = None
71cdef class ManagedMemoryResource(_MemPool):
72 """
73 A managed memory resource managing a stream-ordered memory pool.
75 Managed memory is accessible from both the host and device, with automatic
76 migration between them as needed.
78 Parameters
79 ----------
80 options : ManagedMemoryResourceOptions
81 Memory resource creation options.
83 If set to `None`, the memory resource uses the driver's current
84 stream-ordered memory pool. If no memory pool is set as current,
85 the driver's default memory pool is used.
87 If not set to `None`, a new memory pool is created, which is owned by
88 the memory resource.
90 When using an existing (current or default) memory pool, the returned
91 managed memory resource does not own the pool (`is_handle_owned` is
92 `False`), and closing the resource has no effect.
94 Notes
95 -----
96 IPC (Inter-Process Communication) is not currently supported for managed
97 memory pools.
98 """
100 def __init__(self, options: ManagedMemoryResourceOptions | None = None) -> None:
101 _MMR_init(self, options) 1erstuvwRSTUxyVzWAXBYZ012345hcjkCdbgaifD6l7E8F9G!H#m$I%JnKLMNOPQopq
103 def allocate(self, size_t size, *, stream: Stream | GraphBuilder) -> ManagedBuffer:
104 """Allocate a managed-memory buffer of the requested size.
106 Parameters
107 ----------
108 size : int
109 The size of the buffer to allocate, in bytes.
110 stream : :obj:`~_stream.Stream`
111 Keyword-only. The stream on which to perform the allocation
112 asynchronously. Must be passed explicitly; pass
113 ``device.default_stream`` to use the default stream.
115 Returns
116 -------
117 ManagedBuffer
118 A :class:`ManagedBuffer` (a :class:`Buffer` subclass) that
119 exposes the property-style advice API
120 (``read_mostly``, ``preferred_location``,
121 ``last_prefetch_location``, ``accessed_by``) and instance methods
122 (``prefetch``, ``discard``, ``discard_prefetch``).
123 """
124 MP_check_open(self) 1erstuvw'()xyzAB*+,-./:hckiflmnopq
125 if not isinstance(stream, Stream): 1erstuvw'()xyzAB*+,-./:hckiflmnopq
126 raise TypeError(f"Only Stream is supported for managed memory allocations, got {type(stream).__name__}") 1h
127 if self.is_mapped: 1erstuvw'()xyzAB*+,-./:ckiflmnopq
128 raise TypeError("Cannot allocate from a mapped IPC-enabled memory resource")
129 cdef Stream s = Stream_accept(stream) 1erstuvw'()xyzAB*+,-./:ckiflmnopq
130 return _MP_allocate(self, size, s, ManagedBuffer) 1erstuvw'()xyzAB*+,-./:ckiflmnopq
132 @property
133 def device_id(self) -> int:
134 """The preferred device ordinal, or -1 if the preferred location is not a device."""
135 if self._pref_loc_type == "device": 1f
136 return self._pref_loc_id 1f
137 return -1
139 @property
140 def preferred_location(self) -> tuple[ManagedMemoryLocationType, int | None] | None:
141 """The preferred location for managed memory allocations.
143 Returns ``None`` if no preferred location is set (driver decides),
144 or a tuple ``(type, id)`` where *type* is one of ``"device"``,
145 ``"host"``, or ``"host_numa"``, and *id* is the device ordinal,
146 ``None`` (for ``"host"``), or the NUMA node ID, respectively.
147 """
148 if self._pref_loc_type is None: 1Cdbg
149 return None 1C
150 if self._pref_loc_type == "host": 1dbg
151 return (ManagedMemoryLocationType.HOST, None) 1g
152 return (ManagedMemoryLocationType(self._pref_loc_type), self._pref_loc_id) 1db
154 @property
155 def is_device_accessible(self) -> bool:
156 """Return True. This memory resource provides device-accessible buffers."""
157 return True 1cki
159 @property
160 def is_host_accessible(self) -> bool:
161 """Return True. This memory resource provides host-accessible buffers."""
162 return True 1cki
164 @property
165 def is_managed(self) -> bool:
166 """Return True. This memory resource provides managed (unified) memory buffers."""
167 return True 1c
170IF CUDA_CORE_BUILD_MAJOR >= 13:
171 cdef _resolve_preferred_location(ManagedMemoryResourceOptions opts):
172 """Resolve preferred location options into driver and stored values.
174 Returns a 4-tuple:
175 (CUmemLocationType, loc_id, pref_loc_type_str, pref_loc_id)
176 """
177 cdef object pref_loc = opts.preferred_location if opts is not None else None 1erstuvwRSTUxyVzWAXBYZ012345hcjkCdbgaifD6l7E8F9G!H#m$I%JnKLMNOPQopq
178 cdef object pref_type = opts.preferred_location_type if opts is not None else None 1erstuvwRSTUxyVzWAXBYZ012345hcjkCdbgaifD6l7E8F9G!H#m$I%JnKLMNOPQopq
180 check_str_enum(pref_type, ManagedMemoryLocationType, allow_none=True) 1erstuvwRSTUxyVzWAXBYZ012345hcjkCdbgaifD6l7E8F9G!H#m$I%JnKLMNOPQopq
182 if pref_type is None: 1erstuvwRSTUxyVzWAXBYZ012345hcjkCdbgaifD6l7E8F9G!H#m$I%JnKLMNOPQopq
183 # Legacy behavior
184 if pref_loc is None: 1erstuvwRSTUxyVzWAXBYZ012345hcjkCdbgaifD6l7E8F9G!H#m$I%JnKLMNOPQopq
185 return ( 1erstuvwRSTUxyVzWAXBYZ012345hcjkCdbgaifD6l7E8F9G!H#m$I%JnKLMNOPQopq
186 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_NONE, 1erstuvwRSTUxyVzWAXBYZ012345hcjkCdbgaifD6l7E8F9G!H#m$I%JnKLMNOPQopq
187 -1, None, -1, 1erstuvwRSTUxyVzWAXBYZ012345hcjkCdbgaifD6l7E8F9G!H#m$I%JnKLMNOPQopq
188 )
189 if pref_loc == -1: 1hcdgaf
190 return ( 1g
191 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_HOST, 1g
192 -1, "host", -1,
193 )
194 if pref_loc < 0: 1hcdaf
195 raise ValueError( 1a
196 f"preferred_location must be a device ordinal (>= 0), -1 for " 1a
197 f"host, or None for no preference, got {pref_loc}" 1a
198 )
199 return ( 1hcdf
200 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_DEVICE, 1hcdf
201 pref_loc, "device", pref_loc, 1hcdf
202 )
204 if pref_type == "device": 1jdbga
205 if pref_loc is None or pref_loc < 0: 1da
206 raise ValueError( 1a
207 f"preferred_location must be a device ordinal (>= 0) when " 1a
208 f"preferred_location_type is 'device', got {pref_loc!r}" 1a
209 )
210 return ( 1d
211 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_DEVICE, 1d
212 pref_loc, "device", pref_loc, 1d
213 )
215 if pref_type == "host": 1jbga
216 if pref_loc is not None: 1ga
217 raise ValueError( 1a
218 f"preferred_location must be None when " 1a
219 f"preferred_location_type is 'host', got {pref_loc!r}" 1a
220 )
221 return ( 1g
222 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_HOST, 1g
223 -1, "host", -1,
224 )
226 # pref_type == "host_numa"
227 if pref_loc is None: 1jba
228 from .._device import Device 1jb
229 dev = Device() 1jb
230 numa_id = dev.properties.host_numa_id 1jb
231 if numa_id < 0: 1jb
232 raise RuntimeError( 1j
233 "Cannot determine host NUMA ID for the current CUDA device. "
234 "The system may not support NUMA, or no CUDA context is "
235 "active. Set preferred_location to an explicit NUMA node ID "
236 "or call Device.set_current() first."
237 )
238 return ( 1b
239 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_HOST_NUMA, 1b
240 numa_id, "host_numa", numa_id, 1b
241 )
242 if pref_loc < 0: 1ba
243 raise ValueError( 1a
244 f"preferred_location must be a NUMA node ID (>= 0) or None " 1a
245 f"when preferred_location_type is 'host_numa', got {pref_loc}" 1a
246 )
247 return ( 1b
248 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_HOST_NUMA, 1b
249 pref_loc, "host_numa", pref_loc, 1b
250 )
253cdef inline _MMR_init(ManagedMemoryResource self, options):
254 IF CUDA_CORE_BUILD_MAJOR >= 13:
255 cdef ManagedMemoryResourceOptions opts = check_or_create_options( 1erstuvwRSTUxyVzWAXBYZ012345hcjkCdbgaifD6l7E8F9G!H#m$I%JnKLMNOPQopq
256 ManagedMemoryResourceOptions, options, "ManagedMemoryResource options",
257 keep_none=True
258 )
259 cdef cydriver.CUmemLocationType loc_type
260 cdef int loc_id
262 loc_type, loc_id, self._pref_loc_type, self._pref_loc_id = ( 1erstuvwRSTUxyVzWAXBYZ012345hcjkCdbgaifD6l7E8F9G!H#m$I%JnKLMNOPQopq
263 _resolve_preferred_location(opts) 1erstuvwRSTUxyVzWAXBYZ012345hcjkCdbgaifD6l7E8F9G!H#m$I%JnKLMNOPQopq
264 )
266 if opts is None: 1erstuvwRSTUxyVzWAXBYZ012345hcjkCdbgaifD6l7E8F9G!H#m$I%JnKLMNOPQopq
267 try: 1erstuvwRSTUxyVzWAXBYZ012345hcjkCdbgaifD6l7E8F9G!H#m$I%JnKLMNOPQopq
268 MP_init_current_pool( 1erstuvwRSTUxyVzWAXBYZ012345hcjkCdbgaifD6l7E8F9G!H#m$I%JnKLMNOPQopq
269 self,
270 loc_type,
271 loc_id,
272 cydriver.CUmemAllocationType.CU_MEM_ALLOCATION_TYPE_MANAGED,
273 )
274 except CUDAError as e:
275 if "CUDA_ERROR_NOT_SUPPORTED" in str(e):
276 from .._device import Device
277 if not Device().properties.concurrent_managed_access:
278 raise RuntimeError(
279 "The default memory pool on this device does not support "
280 "managed allocations (concurrent managed access is not "
281 "available). Use "
282 "ManagedMemoryResource(options=ManagedMemoryResourceOptions(...)) "
283 "to create a dedicated managed pool."
284 ) from e
285 raise
286 else:
287 MP_init_create_pool( 1ehcdbgifDlEFGHmIJnKLMNOPQopq
288 self,
289 loc_type,
290 loc_id,
291 cydriver.CUmemAllocationType.CU_MEM_ALLOCATION_TYPE_MANAGED,
292 False, 1ehcdbgifDlEFGHmIJnKLMNOPQopq
293 0,
294 )
296 _check_concurrent_managed_access() 1erstuvwRSTUxyVzWAXBYZ012345hcjkCdbgaifD6l7E8F9G!H#m$I%JnKLMNOPQopq
297 ELSE:
298 raise RuntimeError("ManagedMemoryResource requires CUDA 13.0 or later")
301cdef bint _concurrent_access_warned = False
302cdef object _concurrent_access_lock = threading.Lock()
305cdef inline _check_concurrent_managed_access():
306 """Warn once if the platform lacks concurrent managed memory access."""
307 global _concurrent_access_warned
308 if _concurrent_access_warned: 1erstuvwRSTUxyVzWAXBYZ012345hcjkCdbgaifD6l7E8F9G!H#m$I%JnKLMNOPQopq
309 return 1erstuvwRSTUxyVzWAXBYZ012345hcjkCdbgaifD6l7E8F9G!H#m$I%JnKLMNOPQopq
311 cdef int c_concurrent = 0 1e
312 with _concurrent_access_lock: 1e
313 if _concurrent_access_warned: 1e
314 return
316 # concurrent_managed_access is a system-level attribute for sm_60 and
317 # later, so any device will do.
318 with nogil: 1e
319 HANDLE_RETURN(cydriver.cuDeviceGetAttribute( 1e
320 &c_concurrent,
321 cydriver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_CONCURRENT_MANAGED_ACCESS,
322 0))
323 if not c_concurrent: 1e
324 warnings.warn(
325 "This platform does not support concurrent managed memory access "
326 "(Device.properties.concurrent_managed_access is False). Host access to any managed "
327 "allocation is forbidden while any GPU kernel is in flight, even "
328 "if the kernel does not touch that allocation. Failing to "
329 "synchronize before host access will cause a segfault. "
330 "See: https://docs.nvidia.com/cuda/cuda-c-programming-guide/"
331 "index.html#gpu-exclusive-access-to-managed-memory",
332 UserWarning,
333 stacklevel=3
334 )
336 _concurrent_access_warned = True 1e
339def reset_concurrent_access_warning() -> None:
340 """Reset the concurrent access warning flag for testing purposes."""
341 global _concurrent_access_warned
342 _concurrent_access_warned = False