Skip to content

Commit 11cb846

Browse files
committed
docs(cuda.core): add deallocation stream transfer example
Show how to move a Buffer between streams with an event so its eventual deallocation remains correctly ordered.
1 parent 575c18f commit 11cb846

4 files changed

Lines changed: 87 additions & 3 deletions

File tree

cuda_core/cuda/core/_memory/_buffer.pyx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -745,10 +745,9 @@ cdef tuple Buffer_coerce_batch(object buffers, str what, str single_hint):
745745

746746
cdef inline void Buffer_set_deallocation_stream(Buffer self, object stream):
747747
"""Validate and replace a live buffer's deallocation recipe."""
748-
cdef Stream s
749748
if not self._h_ptr:
750749
raise RuntimeError("Cannot set the deallocation stream on a closed Buffer")
751-
s = Stream_accept(stream)
750+
cdef Stream s = Stream_accept(stream)
752751
_apply_deallocation_stream(self._h_ptr, s._h_stream)
753752

754753

cuda_core/docs/source/api.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ A :class:`Buffer` records the stream that will order its eventual deallocation.
8181
Use :meth:`Buffer.set_deallocation_stream` to replace that stream without
8282
closing the buffer. Changing the recorded stream does not synchronize streams;
8383
the caller must order allocation and every access before the deallocation,
84-
using events or other CUDA synchronization mechanisms as needed.
84+
using events or other CUDA synchronization mechanisms as needed. See
85+
:cuda-core-example:`buffer_deallocation_stream.py <buffer_deallocation_stream.py>`
86+
for a complete example.
8587

8688

8789
CUDA compilation toolchain

cuda_core/docs/source/examples.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ Linking and graphs
3939
- :cuda-core-example:`cuda_graphs.py <cuda_graphs.py>`
4040
captures and replays a multi-kernel CUDA graph to reduce launch overhead.
4141

42+
Memory management
43+
-----------------
44+
45+
- :cuda-core-example:`buffer_deallocation_stream.py <buffer_deallocation_stream.py>`
46+
transfers a buffer between streams and safely changes the stream that orders
47+
its deallocation.
48+
4249
Interoperability and memory access
4350
----------------------------------
4451

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
# ################################################################################
6+
#
7+
# This example transfers a buffer from a producer stream to a consumer stream.
8+
# An event orders the consumer after the producer. The buffer then records the
9+
# consumer stream for its eventual deallocation.
10+
#
11+
# ################################################################################
12+
13+
# /// script
14+
# dependencies = ["cuda_bindings", "cuda_core"]
15+
# ///
16+
17+
import ctypes
18+
19+
from cuda.core import Device, LegacyPinnedMemoryResource
20+
21+
22+
def produce_data(device, stream, size, value):
23+
"""Allocate and fill a buffer on the producer stream."""
24+
buffer = device.allocate(size, stream=stream)
25+
buffer.fill(value, stream=stream)
26+
ready = stream.record()
27+
return buffer, ready
28+
29+
30+
def consume_data(buffer, ready, output, stream):
31+
"""Submit consumer work and transfer the deallocation stream."""
32+
stream.wait(ready)
33+
buffer.set_deallocation_stream(stream)
34+
buffer.copy_to(output, stream=stream)
35+
36+
37+
def main():
38+
device = Device()
39+
device.set_current()
40+
producer_stream = device.create_stream()
41+
consumer_stream = device.create_stream()
42+
pinned_mr = LegacyPinnedMemoryResource()
43+
44+
size = 4096
45+
value = 42
46+
buffer = None
47+
ready = None
48+
output = None
49+
50+
try:
51+
output = pinned_mr.allocate(size)
52+
buffer, ready = produce_data(device, producer_stream, size, value)
53+
consume_data(buffer, ready, output, consumer_stream)
54+
55+
# No stream argument is needed. The buffer now records consumer_stream.
56+
# The free operation runs after the copy on that stream.
57+
buffer.close()
58+
buffer = None
59+
consumer_stream.sync()
60+
61+
result = ctypes.string_at(int(output.handle), output.size)
62+
assert result == bytes([value]) * size
63+
print("Buffer deallocation stream transfer completed.")
64+
finally:
65+
if buffer is not None:
66+
buffer.close()
67+
if output is not None:
68+
output.close()
69+
if ready is not None:
70+
ready.close()
71+
consumer_stream.close()
72+
producer_stream.close()
73+
74+
75+
if __name__ == "__main__":
76+
main()

0 commit comments

Comments
 (0)