Coverage for cuda / core / _memory / _managed_memory_resource.pyx: 88.99%
109 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-29 01:27 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-29 01:27 +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
10from cuda.core._memory._memory_pool cimport MP_init_create_pool, MP_init_current_pool # no-cython-lint
11from cuda.core._utils.cuda_utils cimport HANDLE_RETURN
12from cuda.core._utils.cuda_utils cimport check_or_create_options # no-cython-lint
13from cuda.core._utils.cuda_utils import CUDAError # no-cython-lint
15from dataclasses import dataclass
16import threading
17import warnings
19__all__ = ['ManagedMemoryResource', 'ManagedMemoryResourceOptions']
22@dataclass
23cdef class ManagedMemoryResourceOptions:
24 """Customizable :obj:`~_memory.ManagedMemoryResource` options.
26 Attributes
27 ----------
28 preferred_location : int | None, optional
29 A location identifier (device ordinal or NUMA node ID) whose
30 meaning depends on ``preferred_location_type``.
31 (Default to ``None``)
33 preferred_location_type : ``"device"`` | ``"host"`` | ``"host_numa"`` | None, optional
34 Controls how ``preferred_location`` is interpreted.
36 When set to ``None`` (the default), legacy behavior is used:
37 ``preferred_location`` is interpreted as a device ordinal,
38 ``-1`` for host, or ``None`` for no preference.
40 When set explicitly, the type determines both the kind of
41 preferred location and the valid values for
42 ``preferred_location``:
44 - ``"device"``: prefer a specific GPU. ``preferred_location``
45 must be a device ordinal (``>= 0``).
46 - ``"host"``: prefer host memory (OS-managed NUMA placement).
47 ``preferred_location`` must be ``None``.
48 - ``"host_numa"``: prefer a specific host NUMA node.
49 ``preferred_location`` must be a NUMA node ID (``>= 0``),
50 or ``None`` to derive the NUMA node from the current CUDA
51 device's ``host_numa_id`` attribute (requires an active
52 CUDA context).
54 (Default to ``None``)
55 """
56 preferred_location: int | None = None
57 preferred_location_type: str | None = None
60cdef class ManagedMemoryResource(_MemPool):
61 """
62 A managed memory resource managing a stream-ordered memory pool.
64 Managed memory is accessible from both the host and device, with automatic
65 migration between them as needed.
67 Parameters
68 ----------
69 options : ManagedMemoryResourceOptions
70 Memory resource creation options.
72 If set to `None`, the memory resource uses the driver's current
73 stream-ordered memory pool. If no memory pool is set as current,
74 the driver's default memory pool is used.
76 If not set to `None`, a new memory pool is created, which is owned by
77 the memory resource.
79 When using an existing (current or default) memory pool, the returned
80 managed memory resource does not own the pool (`is_handle_owned` is
81 `False`), and closing the resource has no effect.
83 Notes
84 -----
85 IPC (Inter-Process Communication) is not currently supported for managed
86 memory pools.
87 """
89 def __init__(self, options=None):
90 _MMR_init(self, options) 1uegijcbdahfkvlwmxnyozpAqBrCst
92 @property
93 def device_id(self) -> int:
94 """The preferred device ordinal, or -1 if the preferred location is not a device."""
95 if self._pref_loc_type == "device": 1f
96 return self._pref_loc_id 1f
97 return -1
99 @property
100 def preferred_location(self) -> tuple | None:
101 """The preferred location for managed memory allocations.
103 Returns ``None`` if no preferred location is set (driver decides),
104 or a tuple ``(type, id)`` where *type* is one of ``"device"``,
105 ``"host"``, or ``"host_numa"``, and *id* is the device ordinal,
106 ``None`` (for ``"host"``), or the NUMA node ID, respectively.
107 """
108 if self._pref_loc_type is None: 1jcbd
109 return None 1j
110 if self._pref_loc_type == "host": 1cbd
111 return ("host", None) 1d
112 return (self._pref_loc_type, self._pref_loc_id) 1cb
114 @property
115 def is_device_accessible(self) -> bool:
116 """Return True. This memory resource provides device-accessible buffers."""
117 return True 1eih
119 @property
120 def is_host_accessible(self) -> bool:
121 """Return True. This memory resource provides host-accessible buffers."""
122 return True 1eih
124 @property
125 def is_managed(self) -> bool:
126 """Return True. This memory resource provides managed (unified) memory buffers."""
127 return True 1e
130IF CUDA_CORE_BUILD_MAJOR >= 13:
131 cdef tuple _VALID_LOCATION_TYPES = ("device", "host", "host_numa")
134 cdef _resolve_preferred_location(ManagedMemoryResourceOptions opts):
135 """Resolve preferred location options into driver and stored values.
137 Returns a 4-tuple:
138 (CUmemLocationType, loc_id, pref_loc_type_str, pref_loc_id)
139 """
140 cdef object pref_loc = opts.preferred_location if opts is not None else None 1uegijcbdahfkvlwmxnyozpAqBrCst
141 cdef object pref_type = opts.preferred_location_type if opts is not None else None 1uegijcbdahfkvlwmxnyozpAqBrCst
143 if pref_type is not None and pref_type not in _VALID_LOCATION_TYPES: 1uegijcbdahfkvlwmxnyozpAqBrCst
144 raise ValueError( 1a
145 f"preferred_location_type must be one of {_VALID_LOCATION_TYPES!r} " 1a
146 f"or None, got {pref_type!r}" 1a
147 )
149 if pref_type is None: 1uegijcbdahfkvlwmxnyozpAqBrCst
150 # Legacy behavior
151 if pref_loc is None: 1uegijcbdahfkvlwmxnyozpAqBrCst
152 return ( 1uegijcbdahfkvlwmxnyozpAqBrCst
153 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_NONE, 1uegijcbdahfkvlwmxnyozpAqBrCst
154 -1, None, -1, 1uegijcbdahfkvlwmxnyozpAqBrCst
155 )
156 if pref_loc == -1: 1ecdaf
157 return ( 1d
158 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_HOST, 1d
159 -1, "host", -1,
160 )
161 if pref_loc < 0: 1ecaf
162 raise ValueError( 1a
163 f"preferred_location must be a device ordinal (>= 0), -1 for " 1a
164 f"host, or None for no preference, got {pref_loc}" 1a
165 )
166 return ( 1ecf
167 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_DEVICE, 1ecf
168 pref_loc, "device", pref_loc, 1ecf
169 )
171 if pref_type == "device": 1gcbda
172 if pref_loc is None or pref_loc < 0: 1ca
173 raise ValueError( 1a
174 f"preferred_location must be a device ordinal (>= 0) when " 1a
175 f"preferred_location_type is 'device', got {pref_loc!r}" 1a
176 )
177 return ( 1c
178 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_DEVICE, 1c
179 pref_loc, "device", pref_loc, 1c
180 )
182 if pref_type == "host": 1gbda
183 if pref_loc is not None: 1da
184 raise ValueError( 1a
185 f"preferred_location must be None when " 1a
186 f"preferred_location_type is 'host', got {pref_loc!r}" 1a
187 )
188 return ( 1d
189 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_HOST, 1d
190 -1, "host", -1,
191 )
193 # pref_type == "host_numa"
194 if pref_loc is None: 1gba
195 from .._device import Device 1gb
196 dev = Device() 1gb
197 numa_id = dev.properties.host_numa_id 1gb
198 if numa_id < 0: 1gb
199 raise RuntimeError( 1g
200 "Cannot determine host NUMA ID for the current CUDA device. "
201 "The system may not support NUMA, or no CUDA context is "
202 "active. Set preferred_location to an explicit NUMA node ID "
203 "or call Device.set_current() first."
204 )
205 return ( 1b
206 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_HOST_NUMA, 1b
207 numa_id, "host_numa", numa_id, 1b
208 )
209 if pref_loc < 0: 1ba
210 raise ValueError( 1a
211 f"preferred_location must be a NUMA node ID (>= 0) or None " 1a
212 f"when preferred_location_type is 'host_numa', got {pref_loc}" 1a
213 )
214 return ( 1b
215 cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_HOST_NUMA, 1b
216 pref_loc, "host_numa", pref_loc, 1b
217 )
220cdef inline _MMR_init(ManagedMemoryResource self, options):
221 IF CUDA_CORE_BUILD_MAJOR >= 13:
222 cdef ManagedMemoryResourceOptions opts = check_or_create_options( 1uegijcbdahfkvlwmxnyozpAqBrCst
223 ManagedMemoryResourceOptions, options, "ManagedMemoryResource options",
224 keep_none=True
225 )
226 cdef cydriver.CUmemLocationType loc_type
227 cdef int loc_id
229 loc_type, loc_id, self._pref_loc_type, self._pref_loc_id = ( 1uegijcbdahfkvlwmxnyozpAqBrCst
230 _resolve_preferred_location(opts) 1uegijcbdahfkvlwmxnyozpAqBrCst
231 )
233 if opts is None: 1uegijcbdahfkvlwmxnyozpAqBrCst
234 try: 1uegijcbdahfkvlwmxnyozpAqBrCst
235 MP_init_current_pool( 1uegijcbdahfkvlwmxnyozpAqBrCst
236 self,
237 loc_type,
238 loc_id,
239 cydriver.CUmemAllocationType.CU_MEM_ALLOCATION_TYPE_MANAGED,
240 )
241 except CUDAError as e:
242 if "CUDA_ERROR_NOT_SUPPORTED" in str(e):
243 from .._device import Device
244 if not Device().properties.concurrent_managed_access:
245 raise RuntimeError(
246 "The default memory pool on this device does not support "
247 "managed allocations (concurrent managed access is not "
248 "available). Use "
249 "ManagedMemoryResource(options=ManagedMemoryResourceOptions(...)) "
250 "to create a dedicated managed pool."
251 ) from e
252 raise
253 else:
254 MP_init_create_pool( 1ecbdhfklmnopqrst
255 self,
256 loc_type,
257 loc_id,
258 cydriver.CUmemAllocationType.CU_MEM_ALLOCATION_TYPE_MANAGED,
259 False, 1ecbdhfklmnopqrst
260 0,
261 )
263 _check_concurrent_managed_access() 1uegijcbdahfkvlwmxnyozpAqBrCst
264 ELSE:
265 raise RuntimeError("ManagedMemoryResource requires CUDA 13.0 or later")
268cdef bint _concurrent_access_warned = False
269cdef object _concurrent_access_lock = threading.Lock()
272cdef inline _check_concurrent_managed_access():
273 """Warn once if the platform lacks concurrent managed memory access."""
274 global _concurrent_access_warned
275 if _concurrent_access_warned: 1uegijcbdahfkvlwmxnyozpAqBrCst
276 return 1uegijcbdhfkvlwmxnyozpAqBrCst
278 cdef int c_concurrent = 0 1a
279 with _concurrent_access_lock: 1a
280 if _concurrent_access_warned: 1a
281 return
283 # concurrent_managed_access is a system-level attribute for sm_60 and
284 # later, so any device will do.
285 with nogil: 1a
286 HANDLE_RETURN(cydriver.cuDeviceGetAttribute( 1a
287 &c_concurrent,
288 cydriver.CUdevice_attribute.CU_DEVICE_ATTRIBUTE_CONCURRENT_MANAGED_ACCESS,
289 0))
290 if not c_concurrent: 1a
291 warnings.warn(
292 "This platform does not support concurrent managed memory access "
293 "(Device.properties.concurrent_managed_access is False). Host access to any managed "
294 "allocation is forbidden while any GPU kernel is in flight, even "
295 "if the kernel does not touch that allocation. Failing to "
296 "synchronize before host access will cause a segfault. "
297 "See: https://docs.nvidia.com/cuda/cuda-c-programming-guide/"
298 "index.html#gpu-exclusive-access-to-managed-memory",
299 UserWarning,
300 stacklevel=3
301 )
303 _concurrent_access_warned = True 1a
306def reset_concurrent_access_warning():
307 """Reset the concurrent access warning flag for testing purposes."""
308 global _concurrent_access_warned
309 _concurrent_access_warned = False