Skip to content

Commit 1d8b02e

Browse files
authored
Allow buffers imported via IPC to be re-exported (NVIDIA#1299)
* Allow buffers imported via IPC to be re-exported. Add property Buffer.is_mapped.
1 parent a49af74 commit 1d8b02e

10 files changed

Lines changed: 165 additions & 78 deletions

File tree

cuda_core/cuda/core/experimental/_memory/_buffer.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ cdef class Buffer:
1212
uintptr_t _ptr
1313
size_t _size
1414
MemoryResource _memory_resource
15+
object _ipc_data
1516
object _ptr_obj
1617
Stream _alloc_stream
1718

cuda_core/cuda/core/experimental/_memory/_buffer.pyx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ from __future__ import annotations
77
from libc.stdint cimport uintptr_t
88

99
from cuda.core.experimental._memory._device_memory_resource cimport DeviceMemoryResource
10-
from cuda.core.experimental._memory._ipc cimport IPCBufferDescriptor
10+
from cuda.core.experimental._memory._ipc cimport IPCBufferDescriptor, IPCDataForBuffer
1111
from cuda.core.experimental._memory cimport _ipc
1212
from cuda.core.experimental._stream cimport Stream_accept, Stream
1313
from cuda.core.experimental._utils.cuda_utils cimport (
@@ -45,6 +45,7 @@ cdef class Buffer:
4545
self._ptr = 0
4646
self._size = 0
4747
self._memory_resource = None
48+
self._ipc_data = None
4849
self._ptr_obj = None
4950
self._alloc_stream = None
5051

@@ -55,13 +56,14 @@ cdef class Buffer:
5556
@classmethod
5657
def _init(
5758
cls, ptr: DevicePointerT, size_t size, mr: MemoryResource | None = None,
58-
stream: Stream | None = None
59+
stream: Stream | None = None, ipc_descriptor: IPCBufferDescriptor | None = None
5960
):
6061
cdef Buffer self = Buffer.__new__(cls)
6162
self._ptr = <uintptr_t>(int(ptr))
6263
self._ptr_obj = ptr
6364
self._size = size
6465
self._memory_resource = mr
66+
self._ipc_data = IPCDataForBuffer(ipc_descriptor, True) if ipc_descriptor is not None else None
6567
self._alloc_stream = <Stream>(stream) if stream is not None else None
6668
return self
6769

@@ -92,15 +94,17 @@ cdef class Buffer:
9294

9395
@classmethod
9496
def from_ipc_descriptor(
95-
cls, mr: DeviceMemoryResource, ipc_buffer: IPCBufferDescriptor,
97+
cls, mr: DeviceMemoryResource, ipc_descriptor: IPCBufferDescriptor,
9698
stream: Stream = None
9799
) -> Buffer:
98100
"""Import a buffer that was exported from another process."""
99-
return _ipc.Buffer_from_ipc_descriptor(cls, mr, ipc_buffer, stream)
101+
return _ipc.Buffer_from_ipc_descriptor(cls, mr, ipc_descriptor, stream)
100102

101103
def get_ipc_descriptor(self) -> IPCBufferDescriptor:
102104
"""Export a buffer allocated for sharing between processes."""
103-
return _ipc.Buffer_get_ipc_descriptor(self)
105+
if self._ipc_data is None:
106+
self._ipc_data = IPCDataForBuffer(_ipc.Buffer_get_ipc_descriptor(self), False)
107+
return self._ipc_data.ipc_descriptor
104108

105109
def close(self, stream: Stream | GraphBuilder | None = None):
106110
"""Deallocate this buffer asynchronously on the given stream.
@@ -257,6 +261,12 @@ cdef class Buffer:
257261
return self._memory_resource.is_host_accessible
258262
raise NotImplementedError("WIP: Currently this property only supports buffers with associated MemoryResource")
259263

264+
@property
265+
def is_mapped(self) -> bool:
266+
"""Return True if this buffer is mapped into the process via IPC."""
267+
return getattr(self._ipc_data, "is_mapped", False)
268+
269+
260270
@property
261271
def memory_resource(self) -> MemoryResource:
262272
"""Return the memory resource associated with this buffer."""

cuda_core/cuda/core/experimental/_memory/_device_memory_resource.pxd

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44

55
from cuda.bindings cimport cydriver
66
from cuda.core.experimental._memory._buffer cimport MemoryResource
7-
from cuda.core.experimental._memory._ipc cimport IPCData
7+
from cuda.core.experimental._memory._ipc cimport IPCDataForMR
88

99

1010
cdef class DeviceMemoryResource(MemoryResource):
1111
cdef:
1212
int _dev_id
1313
cydriver.CUmemoryPool _handle
1414
bint _mempool_owned
15-
IPCData _ipc_data
15+
IPCDataForMR _ipc_data
1616
object _attributes
1717
object _peer_accessible_by
1818
object __weakref__

cuda_core/cuda/core/experimental/_memory/_device_memory_resource.pyx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ from libc.string cimport memset
1212
from cuda.bindings cimport cydriver
1313
from cuda.core.experimental._memory._buffer cimport Buffer, MemoryResource
1414
from cuda.core.experimental._memory cimport _ipc
15-
from cuda.core.experimental._memory._ipc cimport IPCAllocationHandle, IPCData
15+
from cuda.core.experimental._memory._ipc cimport IPCAllocationHandle, IPCDataForMR
1616
from cuda.core.experimental._stream cimport default_stream, Stream_accept, Stream
1717
from cuda.core.experimental._utils.cuda_utils cimport (
1818
check_or_create_options,
@@ -313,8 +313,6 @@ cdef class DeviceMemoryResource(MemoryResource):
313313
"""
314314
if not self.is_ipc_enabled:
315315
raise RuntimeError("Memory resource is not IPC-enabled")
316-
if self.is_mapped:
317-
raise RuntimeError("Imported memory resource cannot be exported")
318316
return self._ipc_data._alloc_handle
319317

320318
def allocate(self, size_t size, stream: Stream | GraphBuilder | None = None) -> Buffer:
@@ -540,7 +538,7 @@ cdef void DMR_init_create(
540538

541539
if opts.ipc_enabled:
542540
alloc_handle = _ipc.DMR_export_mempool(self)
543-
self._ipc_data = IPCData(alloc_handle, mapped=False)
541+
self._ipc_data = IPCDataForMR(alloc_handle, False)
544542

545543

546544
# Raise an exception if the given stream is capturing.

cuda_core/cuda/core/experimental/_memory/_ipc.pxd

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,13 @@ cdef cydriver.CUmemAllocationHandleType IPC_HANDLE_TYPE
2424
cdef is_supported()
2525

2626

27-
cdef class IPCData:
27+
cdef class IPCDataForBuffer:
28+
cdef:
29+
IPCBufferDescriptor _ipc_descriptor
30+
bint _is_mapped
31+
32+
33+
cdef class IPCDataForMR:
2834
cdef:
2935
IPCAllocationHandle _alloc_handle
3036
bint _is_mapped

cuda_core/cuda/core/experimental/_memory/_ipc.pyx

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@ from libc.stdint cimport uintptr_t
77
from libc.string cimport memcpy
88

99
from cuda.bindings cimport cydriver
10+
from cuda.core.experimental._memory._buffer cimport Buffer
1011
from cuda.core.experimental._stream cimport default_stream
11-
from cuda.core.experimental._utils.cuda_utils cimport (
12-
HANDLE_RETURN,
13-
)
12+
from cuda.core.experimental._utils.cuda_utils cimport HANDLE_RETURN
1413

1514
import multiprocessing
1615
import os
@@ -32,15 +31,27 @@ cdef cydriver.CUmemAllocationHandleType IPC_HANDLE_TYPE =
3231
cdef is_supported():
3332
return IPC_HANDLE_TYPE != cydriver.CUmemAllocationHandleType.CU_MEM_HANDLE_TYPE_NONE
3433

35-
cdef class IPCData:
36-
"""Data members related to sharing memory pools via IPC."""
37-
def __cinit__(self):
38-
self._alloc_handle = None
39-
self._is_mapped = False
4034

41-
def __init__(self, IPCAllocationHandle alloc_handle, bint mapped):
35+
cdef class IPCDataForBuffer:
36+
"""Data members related to sharing memory buffers via IPC."""
37+
def __cinit__(self, IPCBufferDescriptor ipc_descriptor, bint is_mapped):
38+
self._ipc_descriptor = ipc_descriptor
39+
self._is_mapped = is_mapped
40+
41+
@property
42+
def ipc_descriptor(self):
43+
return self._ipc_descriptor
44+
45+
@property
46+
def is_mapped(self):
47+
return self._is_mapped
48+
49+
50+
cdef class IPCDataForMR:
51+
"""Data members related to sharing memory resources via IPC."""
52+
def __cinit__(self, IPCAllocationHandle alloc_handle, bint is_mapped):
4253
self._alloc_handle = alloc_handle
43-
self._is_mapped = mapped
54+
self._is_mapped = is_mapped
4455

4556
@property
4657
def alloc_handle(self):
@@ -155,7 +166,7 @@ cdef IPCBufferDescriptor Buffer_get_ipc_descriptor(Buffer self):
155166
return IPCBufferDescriptor._init(data_b, self.size)
156167

157168
cdef Buffer Buffer_from_ipc_descriptor(
158-
cls, DeviceMemoryResource mr, IPCBufferDescriptor ipc_buffer, stream
169+
cls, DeviceMemoryResource mr, IPCBufferDescriptor ipc_descriptor, stream
159170
):
160171
"""Import a buffer that was exported from another process."""
161172
if not mr.is_ipc_enabled:
@@ -166,13 +177,13 @@ cdef Buffer Buffer_from_ipc_descriptor(
166177
cdef cydriver.CUmemPoolPtrExportData data
167178
memcpy(
168179
data.reserved,
169-
<const void*><const char*>(ipc_buffer._payload),
180+
<const void*><const char*>(ipc_descriptor._payload),
170181
sizeof(data.reserved)
171182
)
172183
cdef cydriver.CUdeviceptr ptr
173184
with nogil:
174185
HANDLE_RETURN(cydriver.cuMemPoolImportPointer(&ptr, mr._handle, &data))
175-
return Buffer._init(<uintptr_t>ptr, ipc_buffer.size, mr, stream)
186+
return Buffer._init(<uintptr_t>ptr, ipc_descriptor.size, mr, stream, ipc_descriptor)
176187

177188

178189
# DeviceMemoryResource IPC Implementation
@@ -200,7 +211,7 @@ cdef DeviceMemoryResource DMR_from_allocation_handle(cls, device_id, alloc_handl
200211
from .._device import Device
201212
self._dev_id = Device(device_id).device_id
202213
self._mempool_owned = True
203-
self._ipc_data = IPCData(alloc_handle, mapped=True)
214+
self._ipc_data = IPCDataForMR(alloc_handle, True)
204215

205216
# Map the mempool into this process.
206217
cdef int handle = int(alloc_handle)
@@ -214,9 +225,6 @@ cdef DeviceMemoryResource DMR_from_allocation_handle(cls, device_id, alloc_handl
214225
registered = self.register(uuid)
215226
assert registered is self
216227

217-
# Always close the file handle.
218-
alloc_handle.close()
219-
220228
return self
221229

222230

cuda_core/tests/memory_ipc/test_errors.py

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -87,21 +87,6 @@ def ASSERT(self, exc_type, exc_msg):
8787
assert "CUDA_ERROR_INVALID_VALUE" in exc_msg
8888

8989

90-
class TestExportImportedMR(ChildErrorHarness):
91-
"""Error when exporting a memory resource that was imported."""
92-
93-
def PARENT_ACTION(self, queue):
94-
queue.put(self.mr)
95-
96-
def CHILD_ACTION(self, queue):
97-
mr = queue.get(timeout=CHILD_TIMEOUT_SEC)
98-
mr.get_allocation_handle()
99-
100-
def ASSERT(self, exc_type, exc_msg):
101-
assert exc_type is RuntimeError
102-
assert exc_msg == "Imported memory resource cannot be exported"
103-
104-
10590
class TestImportBuffer(ChildErrorHarness):
10691
"""Error when using a buffer as a buffer descriptor."""
10792

@@ -117,7 +102,7 @@ def CHILD_ACTION(self, queue):
117102

118103
def ASSERT(self, exc_type, exc_msg):
119104
assert exc_type is TypeError
120-
assert exc_msg.startswith("Argument 'ipc_buffer' has incorrect type")
105+
assert exc_msg.startswith("Argument 'ipc_descriptor' has incorrect type")
121106

122107

123108
class TestDanglingBuffer(ChildErrorHarness):

cuda_core/tests/memory_ipc/test_memory_ipc.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def test_main(self, ipc_device, ipc_memory_resource):
1818
# Set up the IPC-enabled memory pool and share it.
1919
device = ipc_device
2020
mr = ipc_memory_resource
21+
assert not mr.is_mapped
2122
pgen = PatternGen(device, NBYTES)
2223

2324
# Start the child process.
@@ -27,6 +28,7 @@ def test_main(self, ipc_device, ipc_memory_resource):
2728

2829
# Allocate and fill memory.
2930
buffer = mr.allocate(NBYTES)
31+
assert not buffer.is_mapped
3032
pgen.fill_buffer(buffer, seed=False)
3133

3234
# Export the buffer via IPC.
@@ -42,7 +44,9 @@ def test_main(self, ipc_device, ipc_memory_resource):
4244

4345
def child_main(self, device, mr, queue):
4446
device.set_current()
47+
assert mr.is_mapped
4548
buffer = queue.get(timeout=CHILD_TIMEOUT_SEC)
49+
assert buffer.is_mapped
4650
pgen = PatternGen(device, NBYTES)
4751
pgen.verify_buffer(buffer, seed=False)
4852
pgen.fill_buffer(buffer, seed=True)

0 commit comments

Comments
 (0)