Coverage for cuda/core/_memory/_pinned_memory_resource.pyx: 62.03%
79 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
8from cuda.core._memory._buffer cimport Buffer
9from cuda.core._memory._memory_pool cimport (
10 _MemPool,
11 _MP_allocate,
12 MP_check_open,
13 MP_init_create_pool,
14 MP_init_current_pool,
15)
16from cuda.core._memory cimport _ipc
17from cuda.core._memory._ipc cimport IPCAllocationHandle
18from cuda.core._stream cimport Stream, Stream_accept
19from cuda.core._utils.cuda_utils cimport (
20 check_or_create_options,
21 HANDLE_RETURN,
22)
24from dataclasses import dataclass
25import multiprocessing
26import platform # no-cython-lint
27import uuid
29from cuda.core._utils.cuda_utils import check_multiprocessing_start_method
31from typing import TYPE_CHECKING
33if TYPE_CHECKING:
34 from cuda.core.graph import GraphBuilder
36__all__ = ['PinnedMemoryResource', 'PinnedMemoryResourceOptions']
39@dataclass
40cdef class PinnedMemoryResourceOptions:
41 """Customizable :obj:`~_memory.PinnedMemoryResource` options.
43 Attributes
44 ----------
45 ipc_enabled : bool, optional
46 Specifies whether to create an IPC-enabled memory pool. When set to
47 True, the memory pool and its allocations can be shared with other
48 processes. (Default to False)
50 max_size : int, optional
51 Maximum pool size. When set to 0, defaults to a system-dependent value.
52 (Default to 0)
54 numa_id : int or None, optional
55 Host NUMA node ID for pool placement. When set to None (the default),
56 the behavior depends on ``ipc_enabled``:
58 - ``ipc_enabled=False``: OS-managed placement (location type HOST).
59 - ``ipc_enabled=True``: automatically derived from the current CUDA
60 device's ``host_numa_id`` attribute, requiring an active CUDA
61 context.
63 When set to a non-negative integer, that NUMA node is used explicitly
64 regardless of ``ipc_enabled`` (location type HOST_NUMA).
65 """
66 ipc_enabled : bool = False
67 max_size : int = 0
68 numa_id : int | None = None
71cdef class PinnedMemoryResource(_MemPool):
72 """
73 A host-pinned memory resource managing a stream-ordered memory pool.
75 Parameters
76 ----------
77 options : PinnedMemoryResourceOptions
78 Memory resource creation options.
80 If set to `None`, the memory resource uses the driver's current
81 stream-ordered memory pool. If no memory
82 pool is set as current, the driver's default memory pool
83 is used.
85 If not set to `None`, a new memory pool is created, which is owned by
86 the memory resource.
88 When using an existing (current or default) memory pool, the returned
89 host-pinned memory resource does not own the pool (`is_handle_owned` is
90 `False`), and closing the resource has no effect.
92 Notes
93 -----
94 The device associated with ``stream`` must support host memory pools. If
95 ``numa_id`` is set or derived for IPC, it must support host NUMA memory pools.
96 You can query these capabilities through
97 ``Device.properties.host_memory_pools_supported`` and
98 ``Device.properties.host_numa_memory_pools_supported``. If the required pool
99 is unsupported and stream-ordered allocation is not needed, use
100 :class:`LegacyPinnedMemoryResource`.
102 To create an IPC-Enabled memory resource (MR) that is capable of sharing
103 allocations between processes, specify ``ipc_enabled=True`` in the initializer
104 option. When IPC is enabled and ``numa_id`` is not specified, the NUMA node
105 is automatically derived from the current CUDA device's ``host_numa_id``
106 attribute, which requires an active CUDA context. If ``numa_id`` is
107 explicitly set, that value is used regardless of ``ipc_enabled``.
109 See :class:`DeviceMemoryResource` for more details on IPC usage patterns.
110 """
112 def __init__(self, options: PinnedMemoryResourceOptions | None = None) -> None:
113 _PMR_init(self, options) 1fopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU4Vgc5W6X7Y8Zhd90!i#$%'()*jkl1m+eab3n2,
115 def allocate(self, size_t size, *, stream: Stream | GraphBuilder) -> Buffer:
116 """Allocate a host-pinned buffer asynchronously on the supplied stream.
118 Parameters
119 ----------
120 size : int
121 The size of the buffer to allocate, in bytes.
122 stream : :obj:`~_stream.Stream` | :obj:`~graph.GraphBuilder`
123 Keyword-only. The stream on which to perform the allocation
124 asynchronously. Must be passed explicitly; pass
125 ``device.default_stream`` to use the default stream.
127 Returns
128 -------
129 Buffer
130 The allocated buffer object, which is accessible from the host.
132 Raises
133 ------
134 RuntimeError
135 If the stream's device does not support the requested host memory
136 pool. Use :class:`LegacyPinnedMemoryResource` when stream-ordered
137 allocation is not required.
138 """
139 MP_check_open(self) 2f - . / : ; = ~ ? ab@ bb[ } ] ^ _ ` { | U g c h d i j k l 1 m e a b
140 if self.is_mapped: 2f - . / : ; = ~ ? ab@ bb[ } ] ^ _ ` { | U g c h d i j k l 1 m e a b
141 raise TypeError("Cannot allocate from a mapped IPC-enabled memory resource")
142 cdef Stream s = Stream_accept(stream) 2f - . / : ; = ~ ? ab@ bb[ } ] ^ _ ` { | U g c h d i j k l 1 m e a b
143 device = s.device 2f - . / : ; = ~ ? ab@ bb[ } ] ^ _ ` { | U g c h d i j k l 1 m e a b
144 cdef bint supported = (
145 device.properties.host_numa_memory_pools_supported 2- . / : ; = ~ ? ab@ bb[ } ] ^ _ ` { | c d a
146 if self._numa_id >= 0 2f - . / : ; = ~ ? ab@ bb[ } ] ^ _ ` { | U g c h d i j k l 1 m e a b
147 else device.properties.host_memory_pools_supported 1fUghijkl1meb
148 )
150 if not supported: 2f - . / : ; = ~ ? ab@ bb[ } ] ^ _ ` { | U g c h d i j k l 1 m e a b
151 raise RuntimeError(
152 f"CUDA device {device.device_id} does not support the requested "
153 "host memory pool for PinnedMemoryResource. Use "
154 "LegacyPinnedMemoryResource if memory-pool features are not required."
155 )
156 return _MP_allocate(self, size, s) 2f - . / : ; = ~ ? ab@ bb[ } ] ^ _ ` { | U g c h d i j k l 1 m e a b
158 def __reduce__(self) -> tuple[object, ...]:
159 MP_check_open(self)
160 return PinnedMemoryResource.from_registry, (self.uuid,)
162 @staticmethod
163 def from_registry(uuid: uuid.UUID) -> PinnedMemoryResource: # no-cython-lint
164 """
165 Obtain a registered mapped memory resource.
167 Raises
168 ------
169 RuntimeError
170 If no mapped memory resource is found in the registry.
171 """
172 return <PinnedMemoryResource>(_ipc.MP_from_registry(uuid))
174 def register(self, uuid: uuid.UUID) -> PinnedMemoryResource: # no-cython-lint
175 """
176 Register a mapped memory resource.
178 Returns
179 -------
180 The registered mapped memory resource. If one was previously registered
181 with the given key, it is returned.
183 Raises
184 ------
185 RuntimeError
186 If the memory resource is not IPC-enabled.
187 ValueError
188 If the memory resource already has a different UUID.
189 """
190 return <PinnedMemoryResource>(_ipc.MP_register(self, uuid)) 2cbnb
192 @classmethod
193 def from_allocation_handle(
194 cls, alloc_handle: int | IPCAllocationHandle
195 ) -> PinnedMemoryResource:
196 """Create a host-pinned memory resource from an allocation handle.
198 Construct a new `PinnedMemoryResource` instance that imports a memory
199 pool from a shareable handle. The memory pool is marked as owned.
201 Parameters
202 ----------
203 alloc_handle : int | IPCAllocationHandle
204 The shareable handle of the host-pinned memory resource to import. If an
205 integer is supplied, it must represent a valid platform-specific
206 handle. It is the caller's responsibility to close that handle.
208 Returns
209 -------
210 A new host-pinned memory resource instance with the imported handle.
211 """
212 # cuMemPoolImportFromShareableHandle requires CUDA to be initialized, but in
213 # a child process CUDA may not be initialized yet. For DeviceMemoryResource,
214 # this is not a concern because most likely when retrieving the device_id the
215 # user would have already initialized CUDA. But since PinnedMemoryResource is
216 # not device-specific it is unlikelt the case.
217 HANDLE_RETURN(cydriver.cuInit(0)) 2cb
219 cdef PinnedMemoryResource mr = <PinnedMemoryResource>(
220 _ipc.MP_from_allocation_handle(cls, alloc_handle)) 2cb
221 return mr
223 @property
224 def allocation_handle(self) -> IPCAllocationHandle:
225 """Shareable handle for this memory pool (requires IPC).
227 The handle can be used to share the memory pool with other processes.
228 The handle is cached in this `MemoryResource` and owned by it.
229 """
230 MP_check_open(self) 2dbeb- . fbcb/ : ; jbkb= gblb? hbmb@ ib[ } ] ^ _ ` { | a b
231 if not self.is_ipc_enabled: 2dbeb- . fbcb/ : ; jbkb= gblb? hbmb@ ib[ } ] ^ _ ` { | a b
232 raise RuntimeError("Memory resource is not IPC-enabled") 1b
233 return self._ipc_data._alloc_handle 2dbeb- . fbcb/ : ; jbkb= gblb? hbmb@ ib[ } ] ^ _ ` { | a
235 @property
236 def device_id(self) -> int:
237 """Return -1. Pinned memory is host memory and is not associated with a specific device."""
238 return -1 1Umeab
240 @property
241 def numa_id(self) -> int:
242 """The host NUMA node ID used for pool placement, or -1 for OS-managed placement."""
243 return self._numa_id 1ab3n2
245 @property
246 def is_device_accessible(self) -> bool:
247 """Return True. This memory resource provides device-accessible buffers."""
248 return True 1mea
250 @property
251 def is_host_accessible(self) -> bool:
252 """Return True. This memory resource provides host-accessible buffers."""
253 return True 1mea
256cdef inline _PMR_init(PinnedMemoryResource self, options):
257 from .._device import Device 1fopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU4Vgc5W6X7Y8Zhd90!i#$%'()*jkl1m+eab3n2,
259 cdef PinnedMemoryResourceOptions opts = check_or_create_options( 1fopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU4Vgc5W6X7Y8Zhd90!i#$%'()*jkl1m+eab3n2,
260 PinnedMemoryResourceOptions, options, "PinnedMemoryResource options",
261 keep_none=True
262 )
263 cdef bint ipc_enabled = False 1fopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU4Vgc5W6X7Y8Zhd90!i#$%'()*jkl1m+eab3n2,
264 cdef size_t max_size = 0 1fopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU4Vgc5W6X7Y8Zhd90!i#$%'()*jkl1m+eab3n2,
265 cdef cydriver.CUmemLocationType loc_type
266 cdef int numa_id = -1 1fopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU4Vgc5W6X7Y8Zhd90!i#$%'()*jkl1m+eab3n2,
268 if opts is not None: 1fopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU4Vgc5W6X7Y8Zhd90!i#$%'()*jkl1m+eab3n2,
269 ipc_enabled = opts.ipc_enabled 1fopqrstuvwxyzABCDEFGHIJKLMNOPQRST4Vgc5W6X7Y8Zhd90!i#$%'()*jkl+eab3n2,
270 if ipc_enabled and not _ipc.is_supported(): 1fopqrstuvwxyzABCDEFGHIJKLMNOPQRST4Vgc5W6X7Y8Zhd90!i#$%'()*jkl+eab3n2,
271 raise RuntimeError(f"IPC is not available on {platform.system()}")
272 max_size = opts.max_size 1fopqrstuvwxyzABCDEFGHIJKLMNOPQRST4Vgc5W6X7Y8Zhd90!i#$%'()*jkl+eab3n2,
274 if opts.numa_id is not None: 1fopqrstuvwxyzABCDEFGHIJKLMNOPQRST4Vgc5W6X7Y8Zhd90!i#$%'()*jkl+eab3n2,
275 numa_id = opts.numa_id 12,
276 if numa_id < 0: 12,
277 raise ValueError(f"numa_id must be >= 0, got {numa_id}") 1,
278 elif ipc_enabled: 1fopqrstuvwxyzABCDEFGHIJKLMNOPQRST4Vgc5W6X7Y8Zhd90!i#$%'()*jkl+eab3n
279 dev = Device() 1opqrstuvwxyzABCDEFGHIJKLMNOPQRSTVcWXYZd0an
280 numa_id = dev.properties.host_numa_id 1opqrstuvwxyzABCDEFGHIJKLMNOPQRSTVcWXYZd0an
281 if numa_id < 0: 1opqrstuvwxyzABCDEFGHIJKLMNOPQRSTVcWXYZd0an
282 raise RuntimeError(
283 "Cannot determine host NUMA ID for IPC-enabled pinned "
284 "memory pool. The system may not support NUMA, or no "
285 "CUDA context is active. Set numa_id explicitly or "
286 "call Device.set_current() first.")
288 if numa_id >= 0: 1fopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU4Vgc5W6X7Y8Zhd90!i#$%'()*jkl1m+eab3n2
289 loc_type = cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_HOST_NUMA 1opqrstuvwxyzABCDEFGHIJKLMNOPQRSTVcWXYZd0an2
290 else:
291 loc_type = cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_HOST 1fU4g5678h9!i#$%'()*jkl1m+eb3
293 self._numa_id = numa_id 1fopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU4Vgc5W6X7Y8Zhd90!i#$%'()*jkl1m+eab3n2
295 if opts is None: 1fopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU4Vgc5W6X7Y8Zhd90!i#$%'()*jkl1m+eab3n2
296 MP_init_current_pool( 1U1m
297 self,
298 loc_type,
299 numa_id,
300 cydriver.CUmemAllocationType.CU_MEM_ALLOCATION_TYPE_PINNED,
301 )
302 else:
303 MP_init_create_pool( 1fopqrstuvwxyzABCDEFGHIJKLMNOPQRST4Vgc5W6X7Y8Zhd90!i#$%'()*jkl+eab3n2
304 self,
305 loc_type,
306 numa_id,
307 cydriver.CUmemAllocationType.CU_MEM_ALLOCATION_TYPE_PINNED,
308 ipc_enabled,
309 max_size, 1fopqrstuvwxyzABCDEFGHIJKLMNOPQRST4Vgc5W6X7Y8Zhd90!i#$%'()*jkl+eab3n2
310 )
313def _deep_reduce_pinned_memory_resource(mr: object) -> tuple[object, ...]:
314 check_multiprocessing_start_method() 2dbeb- . fb/ : ; = gb? hb@ ib[ ] ^ _ ` { |
315 alloc_handle = mr.allocation_handle 2dbeb- . fb/ : ; = gb? hb@ ib[ ] ^ _ ` { |
316 return mr.from_allocation_handle, (alloc_handle,) 2dbeb- . fb/ : ; = gb? hb@ ib[ ] ^ _ ` { |
319multiprocessing.reduction.register(PinnedMemoryResource, _deep_reduce_pinned_memory_resource)