From 5cd9afdecc165ead3ab3d388e42b5327fa29517c Mon Sep 17 00:00:00 2001 From: Omar Atie Date: Thu, 13 Aug 2026 16:41:00 -0700 Subject: [PATCH 1/5] fix(cuda.core): route pool-backed MR buffers through deallocate() Pool-backed and graph memory resources now wrap raw allocations with MR-owned device pointer handles, matching Buffer.from_handle(mr=...). Subclasses can observe or customize teardown via deallocate(). Fixes NVIDIA#2615 Signed-off-by: Omar Atie Co-authored-by: Cursor --- cuda_core/cuda/core/_memory/_buffer.pxd | 13 ++ cuda_core/cuda/core/_memory/_buffer.pyx | 16 +++ .../core/_memory/_graph_memory_resource.pyx | 21 ++-- cuda_core/cuda/core/_memory/_memory_pool.pyx | 20 ++- cuda_core/tests/test_memory.py | 115 ++++++++++++++++-- 5 files changed, 154 insertions(+), 31 deletions(-) diff --git a/cuda_core/cuda/core/_memory/_buffer.pxd b/cuda_core/cuda/core/_memory/_buffer.pxd index a9fa0d7e99c..e7a900da561 100644 --- a/cuda_core/cuda/core/_memory/_buffer.pxd +++ b/cuda_core/cuda/core/_memory/_buffer.pxd @@ -2,10 +2,14 @@ # # SPDX-License-Identifier: Apache-2.0 +from libc.stddef cimport size_t + from libcpp cimport bool as cpp_bool from libcpp.atomic cimport atomic as std_atomic +from cuda.bindings cimport cydriver from cuda.core._resource_handles cimport DevicePtrHandle +from cuda.core._stream cimport Stream cdef struct _MemAttrs: @@ -46,6 +50,15 @@ cdef Buffer Buffer_from_deviceptr_handle( ) +# Wrap a raw device pointer with MR-owned teardown and record the stream. +cdef DevicePtrHandle deviceptr_create_owned_by_mr( + cydriver.CUdeviceptr ptr, + size_t size, + object mr, + Stream stream, +) except * + + # Shared argument coercion for the batched free functions (copy_batch, # prefetch_batch, discard_batch, discard_prefetch_batch). `single_hint` # names the per-buffer API to use instead when a bare Buffer is passed. diff --git a/cuda_core/cuda/core/_memory/_buffer.pyx b/cuda_core/cuda/core/_memory/_buffer.pyx index 2f9d3bce218..557e9288209 100644 --- a/cuda_core/cuda/core/_memory/_buffer.pyx +++ b/cuda_core/cuda/core/_memory/_buffer.pyx @@ -92,6 +92,22 @@ cdef inline void _apply_deallocation_stream( HANDLE_RETURN(status) +cdef DevicePtrHandle deviceptr_create_owned_by_mr( + cydriver.CUdeviceptr ptr, + size_t size, + object mr, + Stream stream, +) except *: + """Create an MR-owned device pointer handle with a recorded deallocation stream.""" + cdef DevicePtrHandle h_ptr = deviceptr_create_with_mr(ptr, size, mr) + try: + _apply_deallocation_stream(h_ptr, stream._h_stream) + except BaseException: + h_ptr.reset() + raise + return h_ptr + + __all__ = ['Buffer', 'MemoryResource'] diff --git a/cuda_core/cuda/core/_memory/_graph_memory_resource.pyx b/cuda_core/cuda/core/_memory/_graph_memory_resource.pyx index 67ecf97f58c..97d6150696c 100644 --- a/cuda_core/cuda/core/_memory/_graph_memory_resource.pyx +++ b/cuda_core/cuda/core/_memory/_graph_memory_resource.pyx @@ -7,11 +7,14 @@ from __future__ import annotations from libc.stdint cimport intptr_t from cuda.bindings cimport cydriver -from cuda.core._memory._buffer cimport Buffer, Buffer_from_deviceptr_handle, MemoryResource +from cuda.core._memory._buffer cimport ( + Buffer, + Buffer_from_deviceptr_handle, + MemoryResource, + deviceptr_create_owned_by_mr, +) from cuda.core._resource_handles cimport ( DevicePtrHandle, - deviceptr_alloc_async, - get_last_error, as_cu, ) @@ -210,18 +213,12 @@ cdef inline int check_capturing(cydriver.CUstream s) except?-1 nogil: cdef inline Buffer GMR_allocate(cyGraphMemoryResource self, size_t size, Stream stream): cdef cydriver.CUstream s = as_cu(stream._h_stream) + cdef cydriver.CUdeviceptr ptr cdef DevicePtrHandle h_ptr with nogil: check_capturing(s) - h_ptr = deviceptr_alloc_async(size, stream._h_stream) - if not h_ptr: - HANDLE_RETURN(get_last_error()) - raise RuntimeError( - f"Failed to allocate {size} bytes from GraphMemoryResource: " - "cuda-core returned an empty allocation handle without recording a CUDA error. " - "This is an internal cuda-core error; please report it with your CUDA driver, " - "CUDA Toolkit, and cuda-python versions." - ) + HANDLE_RETURN(cydriver.cuMemAllocAsync(&ptr, size, s)) + h_ptr = deviceptr_create_owned_by_mr(ptr, size, self, stream) return Buffer_from_deviceptr_handle(h_ptr, size, self, None) diff --git a/cuda_core/cuda/core/_memory/_memory_pool.pyx b/cuda_core/cuda/core/_memory/_memory_pool.pyx index cccc95a01a2..693dd22bb2f 100644 --- a/cuda_core/cuda/core/_memory/_memory_pool.pyx +++ b/cuda_core/cuda/core/_memory/_memory_pool.pyx @@ -10,14 +10,18 @@ from libc.stdint cimport uintptr_t from libc.string cimport memset from cuda.bindings cimport cydriver -from cuda.core._memory._buffer cimport Buffer, Buffer_from_deviceptr_handle, MemoryResource +from cuda.core._memory._buffer cimport ( + Buffer, + Buffer_from_deviceptr_handle, + MemoryResource, + deviceptr_create_owned_by_mr, +) from cuda.core._memory cimport _ipc from cuda.core._stream cimport Stream_accept, Stream from cuda.core._resource_handles cimport ( MemoryPoolHandle, DevicePtrHandle, create_mempool_handle, - deviceptr_alloc_from_pool, get_last_error, as_cu, as_py, @@ -330,18 +334,12 @@ cdef inline int check_not_capturing(cydriver.CUstream s) except?-1 nogil: cdef Buffer _MP_allocate(_MemPool self, size_t size, Stream stream, type cls = Buffer): cdef cydriver.CUstream s = as_cu(stream._h_stream) + cdef cydriver.CUdeviceptr ptr cdef DevicePtrHandle h_ptr with nogil: check_not_capturing(s) - h_ptr = deviceptr_alloc_from_pool(size, self._h_pool, stream._h_stream) - if not h_ptr: - HANDLE_RETURN(get_last_error()) - raise RuntimeError( - f"Failed to allocate {size} bytes from {self.__class__.__name__}: " - "cuda-core returned an empty allocation handle without recording a CUDA error. " - "This is an internal cuda-core error; please report it with your CUDA driver, " - "CUDA Toolkit, and cuda-python versions." - ) + HANDLE_RETURN(cydriver.cuMemAllocFromPoolAsync(&ptr, size, as_cu(self._h_pool), s)) + h_ptr = deviceptr_create_owned_by_mr(ptr, size, self, stream) return Buffer_from_deviceptr_handle(h_ptr, size, self, None, cls) diff --git a/cuda_core/tests/test_memory.py b/cuda_core/tests/test_memory.py index 7d620efca8d..58ed643d71c 100644 --- a/cuda_core/tests/test_memory.py +++ b/cuda_core/tests/test_memory.py @@ -515,6 +515,94 @@ def deallocate(self, ptr, size, *, stream=None): assert received["stream"].handle == stream.handle +class _RecordingPoolMR(DeviceMemoryResource): + """Pool-backed MR that records deallocate() invocations.""" + + def __init__(self, device, options=None): + super().__init__(device, options) + self.dealloc_calls = [] + + def deallocate(self, ptr, size, *, stream): + self.dealloc_calls.append((ptr, size, stream)) + super().deallocate(ptr, size, stream=stream) + + +@pytest.mark.agent_authored(model="cursor-grok-4.6") +@pytest.mark.parametrize("pinned", [False, True], ids=["device", "pinned"]) +def test_pool_backed_mr_deallocate_called_on_close(mempool_device, pinned): + """Pool-backed mr.allocate() honors overridden deallocate() on close (#2615).""" + dev = mempool_device + stream = dev.default_stream + if pinned: + skip_if_pinned_memory_unsupported(dev) + + class RecordingMR(PinnedMemoryResource): + def __init__(self, options=None): + super().__init__(options) + self.dealloc_calls = [] + + def deallocate(self, ptr, size, *, stream): + self.dealloc_calls.append((ptr, size, stream)) + super().deallocate(ptr, size, stream=stream) + + mr = RecordingMR(PinnedMemoryResourceOptions(max_size=POOL_SIZE)) + else: + + class RecordingMR(DeviceMemoryResource): + def __init__(self, device, options=None): + super().__init__(device, options) + self.dealloc_calls = [] + + def deallocate(self, ptr, size, *, stream): + self.dealloc_calls.append((ptr, size, stream)) + super().deallocate(ptr, size, stream=stream) + + mr = RecordingMR(dev, DeviceMemoryResourceOptions(max_size=POOL_SIZE)) + + buf = mr.allocate(1024, stream=stream) + assert buf.memory_resource is mr + assert len(mr.dealloc_calls) == 0 + buf.close(stream=stream) + stream.sync() + assert len(mr.dealloc_calls) == 1 + assert mr.dealloc_calls[0][1] == 1024 + + +@pytest.mark.agent_authored(model="cursor-grok-4.6") +def test_pool_backed_mr_deallocate_called_on_gc(mempool_device): + """Pool-backed mr.allocate() honors overridden deallocate() on GC (#2615).""" + import gc + + dev = mempool_device + stream = dev.default_stream + mr = _RecordingPoolMR(dev, DeviceMemoryResourceOptions(max_size=POOL_SIZE)) + buf = mr.allocate(1024, stream=stream) + assert len(mr.dealloc_calls) == 0 + del buf + gc.collect() + stream.sync() + assert len(mr.dealloc_calls) == 1 + + +@pytest.mark.agent_authored(model="cursor-grok-4.6") +def test_pool_backed_mr_deallocate_receives_stream(mempool_device): + """Pool-backed mr.allocate() forwards close(stream) to deallocate() (#2615).""" + dev = mempool_device + stream = dev.create_stream() + received = {} + + class StreamCapturePoolMR(_RecordingPoolMR): + def deallocate(self, ptr, size, *, stream): + received["stream"] = stream + super().deallocate(ptr, size, stream=stream) + + mr = StreamCapturePoolMR(dev, DeviceMemoryResourceOptions(max_size=POOL_SIZE)) + buf = mr.allocate(1024, stream=stream) + buf.close(stream=stream) + stream.sync() + assert received["stream"].handle == stream.handle + + @pytest.mark.parametrize("buffer_type", [Buffer, ManagedBuffer]) def test_from_handle_mr_records_default_stream(buffer_type): """When a Buffer/ManagedBuffer is minted via :meth:`from_handle` with ``mr`` @@ -2304,25 +2392,36 @@ def test_dmr_handle_and_ownership(mempool_device): @pytest.mark.agent_authored(model="claude-opus-4.8") def test_dmr_deallocate_frees_pool_pointer(mempool_device): - """Closing a Buffer.from_handle(..., mr=mr) view frees the pointer via the Python - _MemPool.deallocate path; the pool's in-use bytes drop back.""" + """Closing a buffer from mr.allocate() frees via deallocate() and returns pool bytes.""" + dev = mempool_device + stream = dev.default_stream + mr = DeviceMemoryResource(dev, DeviceMemoryResourceOptions(max_size=POOL_SIZE)) + size = 256 + buf = mr.allocate(size, stream=stream) + stream.sync() + used_after_alloc = mr.attributes.used_mem_current + assert used_after_alloc >= size + buf.close(stream=stream) + stream.sync() + assert int(buf.handle) == 0 + assert mr.attributes.used_mem_current < used_after_alloc + + +@pytest.mark.agent_authored(model="cursor-grok-4.6") +def test_dmr_from_handle_deallocate_frees_pool_pointer(mempool_device): + """Buffer.from_handle(..., mr=mr) also routes teardown through deallocate().""" dev = mempool_device stream = dev.default_stream mr = DeviceMemoryResource(dev, DeviceMemoryResourceOptions(max_size=POOL_SIZE)) size = 256 - # Raw pool allocation owned by nobody else, so exactly one owner frees it (no - # double free); a Buffer.from_handle view then routes teardown through the - # Python deallocate path that mr.allocate()'s C++-direct free would skip. ptr = handle_return(driver.cuMemAllocFromPoolAsync(size, mr.handle, stream.handle)) stream.sync() used_after_alloc = mr.attributes.used_mem_current assert used_after_alloc >= size buf = Buffer.from_handle(int(ptr), size, mr=mr) - buf.close(stream) + buf.close(stream=stream) stream.sync() assert int(buf.handle) == 0 - # In-use bytes fell back, so the pointer was actually returned (buf.handle == 0 - # alone wouldn't prove it: the deleter callback swallows a failed free). assert mr.attributes.used_mem_current < used_after_alloc From db06ca8fed6326dd050b9de05764b4cdd6e76d32 Mon Sep 17 00:00:00 2001 From: Omar Atie Date: Thu, 13 Aug 2026 16:46:44 -0700 Subject: [PATCH 2/5] fix(cuda.core): keep direct deleter for built-in pool MR types Use MR-owned handles only for Python subclasses so built-in pool/graph MRs retain nogil cuMemFreeAsync teardown during interpreter shutdown. Signed-off-by: Omar Atie Co-authored-by: Cursor --- .../core/_memory/_graph_memory_resource.pyx | 25 ++++++++++++++++- cuda_core/cuda/core/_memory/_memory_pool.pyx | 28 ++++++++++++++++++- pr_body_2615.md | 18 ++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 pr_body_2615.md diff --git a/cuda_core/cuda/core/_memory/_graph_memory_resource.pyx b/cuda_core/cuda/core/_memory/_graph_memory_resource.pyx index 97d6150696c..a930b2f7a24 100644 --- a/cuda_core/cuda/core/_memory/_graph_memory_resource.pyx +++ b/cuda_core/cuda/core/_memory/_graph_memory_resource.pyx @@ -15,6 +15,8 @@ from cuda.core._memory._buffer cimport ( ) from cuda.core._resource_handles cimport ( DevicePtrHandle, + deviceptr_alloc_async, + get_last_error, as_cu, ) @@ -211,13 +213,34 @@ cdef inline int check_capturing(cydriver.CUstream s) except?-1 nogil: "a non-capturing stream.") +cdef inline bint _GMR_is_builtin_type(cyGraphMemoryResource self): + """Return True for the built-in GraphMemoryResource (not Python subclasses).""" + cdef str name = self.__class__.__name__ + cdef str mod = self.__class__.__module__ + return name == "GraphMemoryResource" and mod == "cuda.core._memory._graph_memory_resource" + + cdef inline Buffer GMR_allocate(cyGraphMemoryResource self, size_t size, Stream stream): cdef cydriver.CUstream s = as_cu(stream._h_stream) cdef cydriver.CUdeviceptr ptr cdef DevicePtrHandle h_ptr + cdef bint use_direct_deleter = _GMR_is_builtin_type(self) with nogil: check_capturing(s) - HANDLE_RETURN(cydriver.cuMemAllocAsync(&ptr, size, s)) + if use_direct_deleter: + h_ptr = deviceptr_alloc_async(size, stream._h_stream) + else: + HANDLE_RETURN(cydriver.cuMemAllocAsync(&ptr, size, s)) + if use_direct_deleter: + if not h_ptr: + HANDLE_RETURN(get_last_error()) + raise RuntimeError( + f"Failed to allocate {size} bytes from GraphMemoryResource: " + "cuda-core returned an empty allocation handle without recording a CUDA error. " + "This is an internal cuda-core error; please report it with your CUDA driver, " + "CUDA Toolkit, and cuda-python versions." + ) + return Buffer_from_deviceptr_handle(h_ptr, size, self, None) h_ptr = deviceptr_create_owned_by_mr(ptr, size, self, stream) return Buffer_from_deviceptr_handle(h_ptr, size, self, None) diff --git a/cuda_core/cuda/core/_memory/_memory_pool.pyx b/cuda_core/cuda/core/_memory/_memory_pool.pyx index 693dd22bb2f..fad08b02886 100644 --- a/cuda_core/cuda/core/_memory/_memory_pool.pyx +++ b/cuda_core/cuda/core/_memory/_memory_pool.pyx @@ -22,6 +22,7 @@ from cuda.core._resource_handles cimport ( MemoryPoolHandle, DevicePtrHandle, create_mempool_handle, + deviceptr_alloc_from_pool, get_last_error, as_cu, as_py, @@ -332,13 +333,38 @@ cdef inline int check_not_capturing(cydriver.CUstream s) except?-1 nogil: "a capturing stream (consider using GraphMemoryResource).") +cdef inline bint _MP_is_builtin_type(_MemPool self): + """Return True for built-in pool MR types (not Python subclasses).""" + cdef str name = self.__class__.__name__ + cdef str mod = self.__class__.__module__ + return mod.startswith("cuda.core._memory._") and name in ( + "DeviceMemoryResource", + "PinnedMemoryResource", + "ManagedMemoryResource", + ) + + cdef Buffer _MP_allocate(_MemPool self, size_t size, Stream stream, type cls = Buffer): cdef cydriver.CUstream s = as_cu(stream._h_stream) cdef cydriver.CUdeviceptr ptr cdef DevicePtrHandle h_ptr + cdef bint use_direct_deleter = _MP_is_builtin_type(self) with nogil: check_not_capturing(s) - HANDLE_RETURN(cydriver.cuMemAllocFromPoolAsync(&ptr, size, as_cu(self._h_pool), s)) + if use_direct_deleter: + h_ptr = deviceptr_alloc_from_pool(size, self._h_pool, stream._h_stream) + else: + HANDLE_RETURN(cydriver.cuMemAllocFromPoolAsync(&ptr, size, as_cu(self._h_pool), s)) + if use_direct_deleter: + if not h_ptr: + HANDLE_RETURN(get_last_error()) + raise RuntimeError( + f"Failed to allocate {size} bytes from {self.__class__.__name__}: " + "cuda-core returned an empty allocation handle without recording a CUDA error. " + "This is an internal cuda-core error; please report it with your CUDA driver, " + "CUDA Toolkit, and cuda-python versions." + ) + return Buffer_from_deviceptr_handle(h_ptr, size, self, None, cls) h_ptr = deviceptr_create_owned_by_mr(ptr, size, self, stream) return Buffer_from_deviceptr_handle(h_ptr, size, self, None, cls) diff --git a/pr_body_2615.md b/pr_body_2615.md new file mode 100644 index 00000000000..0f740cbdacd --- /dev/null +++ b/pr_body_2615.md @@ -0,0 +1,18 @@ +## Description + +closes #2615 + +Pool-backed memory resources (`DeviceMemoryResource`, `PinnedMemoryResource`, `ManagedMemoryResource`) and `GraphMemoryResource` previously returned buffers whose C++ deleter called `cuMemFreeAsync` directly, bypassing Python `deallocate()` overrides. + +This change routes teardown for **subclasses** of pool-backed and graph memory resources through MR-owned device pointer handles (the same path as `Buffer.from_handle(mr=...)`), recording the allocation stream at creation and invoking `MemoryResource.deallocate()`. + +Built-in types (`DeviceMemoryResource`, `PinnedMemoryResource`, `ManagedMemoryResource`, `GraphMemoryResource`) keep the existing direct C++ deleter so stream-ordered frees still work without the GIL during interpreter shutdown. + +## Checklist +- [x] New or existing tests cover these changes. +- [ ] The documentation is up to date with these changes. + +## Test plan + +- [ ] `pytest cuda_core/tests/test_memory.py -k "pool_backed_mr or dmr_deallocate_frees_pool_pointer or dmr_from_handle_deallocate"` +- [ ] CI source builds and GPU tests for `cuda.core` From c92f9b746de964fd119f40553b3be7f9b5b67d37 Mon Sep 17 00:00:00 2001 From: Omar Atie Date: Thu, 13 Aug 2026 16:47:19 -0700 Subject: [PATCH 3/5] chore: drop local PR body draft from branch Co-authored-by: Cursor --- pr_body_2615.md | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 pr_body_2615.md diff --git a/pr_body_2615.md b/pr_body_2615.md deleted file mode 100644 index 0f740cbdacd..00000000000 --- a/pr_body_2615.md +++ /dev/null @@ -1,18 +0,0 @@ -## Description - -closes #2615 - -Pool-backed memory resources (`DeviceMemoryResource`, `PinnedMemoryResource`, `ManagedMemoryResource`) and `GraphMemoryResource` previously returned buffers whose C++ deleter called `cuMemFreeAsync` directly, bypassing Python `deallocate()` overrides. - -This change routes teardown for **subclasses** of pool-backed and graph memory resources through MR-owned device pointer handles (the same path as `Buffer.from_handle(mr=...)`), recording the allocation stream at creation and invoking `MemoryResource.deallocate()`. - -Built-in types (`DeviceMemoryResource`, `PinnedMemoryResource`, `ManagedMemoryResource`, `GraphMemoryResource`) keep the existing direct C++ deleter so stream-ordered frees still work without the GIL during interpreter shutdown. - -## Checklist -- [x] New or existing tests cover these changes. -- [ ] The documentation is up to date with these changes. - -## Test plan - -- [ ] `pytest cuda_core/tests/test_memory.py -k "pool_backed_mr or dmr_deallocate_frees_pool_pointer or dmr_from_handle_deallocate"` -- [ ] CI source builds and GPU tests for `cuda.core` From 735d1fb164ef1ae16bab55f91a131c6744d0d9dc Mon Sep 17 00:00:00 2001 From: Omar Atie Date: Thu, 13 Aug 2026 18:11:06 -0700 Subject: [PATCH 4/5] docs(cuda.core): document subclass deallocate routing (#2615) Document MemoryResource subclass teardown behavior and add a 1.2.0 release note for pool-backed MR deallocate consistency. Signed-off-by: Omar Atie Co-authored-by: Cursor --- cuda_core/cuda/core/_memory/_buffer.pyx | 14 ++++++++++++++ cuda_core/docs/source/release/1.2.0-notes.rst | 7 +++++++ 2 files changed, 21 insertions(+) diff --git a/cuda_core/cuda/core/_memory/_buffer.pyx b/cuda_core/cuda/core/_memory/_buffer.pyx index 557e9288209..2d7ac50745e 100644 --- a/cuda_core/cuda/core/_memory/_buffer.pyx +++ b/cuda_core/cuda/core/_memory/_buffer.pyx @@ -607,6 +607,15 @@ cdef class MemoryResource: returned by the :meth:`allocate` method would hold a reference to self, the buffer properties are retrieved simply by looking up the underlying memory resource's respective property.) + + Notes + ----- + Python subclasses of pool-backed memory resources + (:class:`~_memory.DeviceMemoryResource`, :class:`~_memory.PinnedMemoryResource`, + :class:`~_memory.ManagedMemoryResource`) and :class:`~_memory.GraphMemoryResource` + route buffer teardown through :meth:`deallocate`, including for buffers + returned by :meth:`allocate`. Built-in resource types use a direct C++ + deallocation path that bypasses Python during interpreter shutdown. """ def allocate(self, size_t size, *, stream: Stream | GraphBuilder) -> Buffer: @@ -653,6 +662,11 @@ cdef class MemoryResource: Keyword-only. The stream on which to perform the deallocation asynchronously. Must be passed explicitly; pass ``device.default_stream`` to use the default stream. + + Notes + ----- + For memory resources that own buffer pointers, this method is also + invoked when a :class:`Buffer` is closed or garbage-collected. """ raise TypeError("MemoryResource.deallocate must be implemented by subclasses.") diff --git a/cuda_core/docs/source/release/1.2.0-notes.rst b/cuda_core/docs/source/release/1.2.0-notes.rst index 9e96d54b3d2..665d0974f10 100644 --- a/cuda_core/docs/source/release/1.2.0-notes.rst +++ b/cuda_core/docs/source/release/1.2.0-notes.rst @@ -40,6 +40,13 @@ Fixes and enhancements free operation. Previously, these errors could be suppressed. Automatic buffer cleanup remains non-raising and reports failures as warnings. +- Python subclasses of pool-backed memory resources and + :class:`GraphMemoryResource` now route buffer teardown from + :meth:`MemoryResource.allocate` through :meth:`MemoryResource.deallocate`, + matching :meth:`Buffer.from_handle` behavior. Built-in resource types keep + the direct C++ deallocation path for interpreter-shutdown safety. + (`#2615 `__) + - Graph node resources are now retained independently across graph clones, executable graphs, updates, node deletion, and in-flight launches. Previously, modifying a graph definition could release resources still used by an From 04b3cc1fc1d9a19440e05bd2da1482a70604fe30 Mon Sep 17 00:00:00 2001 From: Omar Atie Date: Fri, 14 Aug 2026 17:48:03 -0700 Subject: [PATCH 5/5] chore: regenerate _buffer.pyi stubs (#2615) Update stubgen-pyx output after docstring changes for pre-commit.ci on PR #2620. Signed-off-by: Omar Atie --- cuda_core/cuda/core/_memory/_buffer.pyi | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cuda_core/cuda/core/_memory/_buffer.pyi b/cuda_core/cuda/core/_memory/_buffer.pyi index 4d8bd657968..009fc47e92c 100644 --- a/cuda_core/cuda/core/_memory/_buffer.pyi +++ b/cuda_core/cuda/core/_memory/_buffer.pyi @@ -262,6 +262,15 @@ class MemoryResource: returned by the :meth:`allocate` method would hold a reference to self, the buffer properties are retrieved simply by looking up the underlying memory resource's respective property.) + + Notes + ----- + Python subclasses of pool-backed memory resources + (:class:`~_memory.DeviceMemoryResource`, :class:`~_memory.PinnedMemoryResource`, + :class:`~_memory.ManagedMemoryResource`) and :class:`~_memory.GraphMemoryResource` + route buffer teardown through :meth:`deallocate`, including for buffers + returned by :meth:`allocate`. Built-in resource types use a direct C++ + deallocation path that bypasses Python during interpreter shutdown. """ def allocate(self, size: int, *, stream: Stream | GraphBuilder) -> Buffer: @@ -301,6 +310,11 @@ class MemoryResource: Keyword-only. The stream on which to perform the deallocation asynchronously. Must be passed explicitly; pass ``device.default_stream`` to use the default stream. + + Notes + ----- + For memory resources that own buffer pointers, this method is also + invoked when a :class:`Buffer` is closed or garbage-collected. """ @property