Coverage for cuda/core/_memory/_managed_memory_resource.pyx: 70.94%
117 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-16 01:23 +0000
« 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
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) 1enopqrsGHIJtuKvLwMxNOPQRSTUcijydbgahfzVkWAXBYCZD0l1E2Fm
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) 1enopqrs345tuvwx6789!#$cjhfklm
125 assert isinstance(stream, Stream), "Only Stream is supported for managed memory allocations" 1enopqrs345tuvwx6789!#$cjhfklm
126 if self.is_mapped: 1enopqrs345tuvwx6789!#$cjhfklm
127 raise TypeError("Cannot allocate from a mapped IPC-enabled memory resource")
128 cdef Stream s = Stream_accept(stream) 1enopqrs345tuvwx6789!#$cjhfklm
129 return _MP_allocate(self, size, s, ManagedBuffer) 1enopqrs345tuvwx6789!#$cjhfklm
131 @property
132 def device_id(self) -> int:
133 """The preferred device ordinal, or -1 if the preferred location is not a device."""
134 if self._pref_loc_type == "device": 1f
135 return self._pref_loc_id 1f
136 return -1
138 @property
139 def preferred_location(self) -> tuple[ManagedMemoryLocationType, int | None] | None:
140 """The preferred location for managed memory allocations.
142 Returns ``None`` if no preferred location is set (driver decides),
143 or a tuple ``(type, id)`` where *type* is one of ``"device"``,
144 ``"host"``, or ``"host_numa"``, and *id* is the device ordinal,
145 ``None`` (for ``"host"``), or the NUMA node ID, respectively.
146 """
147 if self._pref_loc_type is None: 1ydbg
148 return None 1y
149 if self._pref_loc_type == "host": 1dbg
150 return (ManagedMemoryLocationType.HOST, None) 1g
151 return (ManagedMemoryLocationType(self._pref_loc_type), self._pref_loc_id) 1db
153 @property
154 def is_device_accessible(self) -> bool:
155 """Return True. This memory resource provides device-accessible buffers."""
156 return True 1cjh
158 @property
159 def is_host_accessible(self) -> bool:
160 """Return True. This memory resource provides host-accessible buffers."""
161 return True 1cjh
163 @property
164 def is_managed(self) -> bool:
165 """Return True. This memory resource provides managed (unified) memory buffers."""
166 return True 1c
169IF CUDA_CORE_BUILD_MAJOR >= 13:
170 cdef _resolve_preferred_location(ManagedMemoryResourceOptions opts):
171 """Resolve preferred location options into driver and stored values.
173 Returns a 4-tuple:
174 (CUmemLocationType, loc_id, pref_loc_type_str, pref_loc_id)
175 """
176 cdef object pref_loc = opts.preferred_location if opts is not None else None 1enopqrsGHIJtuKvLwMxNOPQRSTUcijydbgahfzVkWAXBYCZD0l1E2Fm
177 cdef object pref_type = opts.preferred_location_type if opts is not None else None 1enopqrsGHIJtuKvLwMxNOPQRSTUcijydbgahfzVkWAXBYCZD0l1E2Fm
179 check_str_enum(pref_type, ManagedMemoryLocationType, allow_none=True) 1enopqrsGHIJtuKvLwMxNOPQRSTUcijydbgahfzVkWAXBYCZD0l1E2Fm
181 if pref_type is None: 1enopqrsGHIJtuKvLwMxNOPQRSTUcijydbgahfzVkWAXBYCZD0l1E2Fm
182 # Legacy behavior
183 if pref_loc is None: 1enopqrsGHIJtuKvLwMxNOPQRSTUcijydbgahfzVkWAXBYCZD0l1E2Fm
184 return ( 1enopqrsGHIJtuKvLwMxNOPQRSTUcijydbgahfzVkWAXBYCZD0l1E2Fm
185 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_NONE, 1enopqrsGHIJtuKvLwMxNOPQRSTUcijydbgahfzVkWAXBYCZD0l1E2Fm
186 -1, None, -1, 1enopqrsGHIJtuKvLwMxNOPQRSTUcijydbgahfzVkWAXBYCZD0l1E2Fm
187 )
188 if pref_loc == -1: 1cdgaf
189 return ( 1g
190 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_HOST, 1g
191 -1, "host", -1,
192 )
193 if pref_loc < 0: 1cdaf
194 raise ValueError( 1a
195 f"preferred_location must be a device ordinal (>= 0), -1 for " 1a
196 f"host, or None for no preference, got {pref_loc}" 1a
197 )
198 return ( 1cdf
199 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_DEVICE, 1cdf
200 pref_loc, "device", pref_loc, 1cdf
201 )
203 if pref_type == "device": 1idbga
204 if pref_loc is None or pref_loc < 0: 1da
205 raise ValueError( 1a
206 f"preferred_location must be a device ordinal (>= 0) when " 1a
207 f"preferred_location_type is 'device', got {pref_loc!r}" 1a
208 )
209 return ( 1d
210 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_DEVICE, 1d
211 pref_loc, "device", pref_loc, 1d
212 )
214 if pref_type == "host": 1ibga
215 if pref_loc is not None: 1ga
216 raise ValueError( 1a
217 f"preferred_location must be None when " 1a
218 f"preferred_location_type is 'host', got {pref_loc!r}" 1a
219 )
220 return ( 1g
221 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_HOST, 1g
222 -1, "host", -1,
223 )
225 # pref_type == "host_numa"
226 if pref_loc is None: 1iba
227 from .._device import Device 1ib
228 dev = Device() 1ib
229 numa_id = dev.properties.host_numa_id 1ib
230 if numa_id < 0: 1ib
231 raise RuntimeError( 1i
232 "Cannot determine host NUMA ID for the current CUDA device. "
233 "The system may not support NUMA, or no CUDA context is "
234 "active. Set preferred_location to an explicit NUMA node ID "
235 "or call Device.set_current() first."
236 )
237 return ( 1b
238 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_HOST_NUMA, 1b
239 numa_id, "host_numa", numa_id, 1b
240 )
241 if pref_loc < 0: 1ba
242 raise ValueError( 1a
243 f"preferred_location must be a NUMA node ID (>= 0) or None " 1a
244 f"when preferred_location_type is 'host_numa', got {pref_loc}" 1a
245 )
246 return ( 1b
247 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_HOST_NUMA, 1b
248 pref_loc, "host_numa", pref_loc, 1b
249 )
252cdef inline _MMR_init(ManagedMemoryResource self, options):
253 IF CUDA_CORE_BUILD_MAJOR >= 13:
254 cdef ManagedMemoryResourceOptions opts = check_or_create_options( 1enopqrsGHIJtuKvLwMxNOPQRSTUcijydbgahfzVkWAXBYCZD0l1E2Fm
255 ManagedMemoryResourceOptions, options, "ManagedMemoryResource options",
256 keep_none=True
257 )
258 cdef cydriver.CUmemLocationType loc_type
259 cdef int loc_id
261 loc_type, loc_id, self._pref_loc_type, self._pref_loc_id = ( 1enopqrsGHIJtuKvLwMxNOPQRSTUcijydbgahfzVkWAXBYCZD0l1E2Fm
262 _resolve_preferred_location(opts) 1enopqrsGHIJtuKvLwMxNOPQRSTUcijydbgahfzVkWAXBYCZD0l1E2Fm
263 )
265 if opts is None: 1enopqrsGHIJtuKvLwMxNOPQRSTUcijydbgahfzVkWAXBYCZD0l1E2Fm
266 try: 1enopqrsGHIJtuKvLwMxNOPQRSTUcijydbgahfzVkWAXBYCZD0l1E2Fm
267 MP_init_current_pool( 1enopqrsGHIJtuKvLwMxNOPQRSTUcijydbgahfzVkWAXBYCZD0l1E2Fm
268 self,
269 loc_type,
270 loc_id,
271 cydriver.CUmemAllocationType.CU_MEM_ALLOCATION_TYPE_MANAGED,
272 )
273 except CUDAError as e:
274 if "CUDA_ERROR_NOT_SUPPORTED" in str(e):
275 from .._device import Device
276 if not Device().properties.concurrent_managed_access:
277 raise RuntimeError(
278 "The default memory pool on this device does not support "
279 "managed allocations (concurrent managed access is not "
280 "available). Use "
281 "ManagedMemoryResource(options=ManagedMemoryResourceOptions(...)) "
282 "to create a dedicated managed pool."
283 ) from e
284 raise
285 else:
286 MP_init_create_pool( 1ecdbghfzkABCDlEFm
287 self,
288 loc_type,
289 loc_id,
290 cydriver.CUmemAllocationType.CU_MEM_ALLOCATION_TYPE_MANAGED,
291 False, 1ecdbghfzkABCDlEFm
292 0,
293 )
295 _check_concurrent_managed_access() 1enopqrsGHIJtuKvLwMxNOPQRSTUcijydbgahfzVkWAXBYCZD0l1E2Fm
296 ELSE:
297 raise RuntimeError("ManagedMemoryResource requires CUDA 13.0 or later")
300cdef bint _concurrent_access_warned = False
301cdef object _concurrent_access_lock = threading.Lock()
304cdef inline _check_concurrent_managed_access():
305 """Warn once if the platform lacks concurrent managed memory access."""
306 global _concurrent_access_warned
307 if _concurrent_access_warned: 1enopqrsGHIJtuKvLwMxNOPQRSTUcijydbgahfzVkWAXBYCZD0l1E2Fm
308 return 1enopqrsGHIJtuKvLwMxNOPQRSTUcijydbgahfzVkWAXBYCZD0l1E2Fm
310 cdef int c_concurrent = 0 1e
311 with _concurrent_access_lock: 1e
312 if _concurrent_access_warned: 1e
313 return
315 # concurrent_managed_access is a system-level attribute for sm_60 and
316 # later, so any device will do.
317 with nogil: 1e
318 HANDLE_RETURN(cydriver.cuDeviceGetAttribute( 1e
319 &c_concurrent,
320 cydriver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_CONCURRENT_MANAGED_ACCESS,
321 0))
322 if not c_concurrent: 1e
323 warnings.warn(
324 "This platform does not support concurrent managed memory access "
325 "(Device.properties.concurrent_managed_access is False). Host access to any managed "
326 "allocation is forbidden while any GPU kernel is in flight, even "
327 "if the kernel does not touch that allocation. Failing to "
328 "synchronize before host access will cause a segfault. "
329 "See: https://docs.nvidia.com/cuda/cuda-c-programming-guide/"
330 "index.html#gpu-exclusive-access-to-managed-memory",
331 UserWarning,
332 stacklevel=3
333 )
335 _concurrent_access_warned = True 1e
338def reset_concurrent_access_warning() -> None:
339 """Reset the concurrent access warning flag for testing purposes."""
340 global _concurrent_access_warned
341 _concurrent_access_warned = False