Skip to content

Commit 3684644

Browse files
committed
cythonize Buffer & DMR (WIP - not working!)
1 parent d764f5b commit 3684644

4 files changed

Lines changed: 145 additions & 249 deletions

File tree

cuda_core/cuda/core/experimental/_memory.pyx

Lines changed: 140 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@
44

55
from __future__ import annotations
66

7-
from libc.stdint cimport uintptr_t
7+
cimport cpython
8+
from libc.stdint cimport uintptr_t, intptr_t
9+
from libc.string cimport memset
10+
11+
from cuda.bindings cimport cydriver
12+
813
from cuda.core.experimental._utils.cuda_utils cimport (
914
_check_driver_error as raise_if_driver_error,
1015
check_or_create_options,
16+
HANDLE_RETURN,
1117
)
1218

1319
from dataclasses import dataclass
@@ -53,7 +59,7 @@ cdef class Buffer:
5359
"""
5460

5561
cdef:
56-
uintptr_t _ptr
62+
intptr_t _ptr
5763
size_t _size
5864
object _mr
5965
object _ptr_obj
@@ -140,20 +146,23 @@ cdef class Buffer:
140146
"""Export a buffer allocated for sharing between processes."""
141147
if not self._mr.is_ipc_enabled:
142148
raise RuntimeError("Memory resource is not IPC-enabled")
143-
err, ptr = driver.cuMemPoolExportPointer(self.handle)
144-
raise_if_driver_error(err)
145-
return IPCBufferDescriptor._init(ptr.reserved, self.size)
149+
cdef cydriver.CUmemPoolPtrExportData data
150+
with nogil:
151+
HANDLE_RETURN(cydriver.cuMemPoolExportPointer(&data, <cydriver.CUdeviceptr>(self._ptr)))
152+
cdef bytes data_b = cpython.PyBytes_FromStringAndSize(<char*>(data.reserved), sizeof(data.reserved))
153+
return IPCBufferDescriptor._init(data_b, self.size)
146154

147155
@classmethod
148-
def from_ipc_descriptor(cls, mr: MemoryResource, ipc_buffer: IPCBufferDescriptor) -> Buffer:
156+
def from_ipc_descriptor(cls, mr: DeviceMemoryResource, ipc_buffer: IPCBufferDescriptor) -> Buffer:
149157
"""Import a buffer that was exported from another process."""
150158
if not mr.is_ipc_enabled:
151159
raise RuntimeError("Memory resource is not IPC-enabled")
152-
share_data = driver.CUmemPoolPtrExportData()
160+
cdef cydriver.CUmemPoolPtrExportData share_data
153161
share_data.reserved = ipc_buffer._reserved
154-
err, ptr = driver.cuMemPoolImportPointer(mr._mempool_handle, share_data)
155-
raise_if_driver_error(err)
156-
return Buffer.from_handle(ptr, ipc_buffer.size, mr)
162+
cdef cydriver.CUdeviceptr ptr
163+
with nogil:
164+
HANDLE_RETURN(cydriver.cuMemPoolImportPointer(&ptr, mr._mempool_handle, &share_data))
165+
return Buffer.from_handle(<intptr_t>ptr, ipc_buffer.size, mr)
157166

158167
def copy_to(self, dst: Buffer = None, *, stream: Stream) -> Buffer:
159168
"""Copy from this buffer to the dst buffer asynchronously on the given stream.
@@ -360,10 +369,9 @@ class MemoryResource(abc.ABC):
360369

361370
# IPC is currently only supported on Linux. On other platforms, the IPC handle
362371
# type is set equal to the no-IPC handle type.
372+
cdef cydriver.CUmemAllocationHandleType _IPC_HANDLE_TYPE = cydriver.CUmemAllocationHandleType.CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR \
373+
if platform.system() == "Linux" else cydriver.CUmemAllocationHandleType.CU_MEM_HANDLE_TYPE_NONE
363374

364-
_NOIPC_HANDLE_TYPE = driver.CUmemAllocationHandleType.CU_MEM_HANDLE_TYPE_NONE
365-
_IPC_HANDLE_TYPE = driver.CUmemAllocationHandleType.CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR \
366-
if platform.system() == "Linux" else _NOIPC_HANDLE_TYPE
367375

368376
cdef class IPCBufferDescriptor:
369377
"""Serializable object describing a buffer that can be shared between processes."""
@@ -466,6 +474,7 @@ cdef class DeviceMemoryResourceOptions:
466474
max_size : cython.int = 0
467475

468476

477+
# TODO: cythonize this?
469478
class DeviceMemoryResourceAttributes:
470479
def __init__(self, *args, **kwargs):
471480
raise RuntimeError("DeviceMemoryResourceAttributes cannot be instantiated directly. Please use MemoryResource APIs.")
@@ -483,8 +492,9 @@ class DeviceMemoryResourceAttributes:
483492
def fget(self) -> property_type:
484493
mr = self._mr()
485494
if mr is None:
486-
raise RuntimeError("DeviceMemoryResource is expired")
487-
err, value = driver.cuMemPoolGetAttribute(mr._mempool_handle, attr_enum)
495+
raise RuntimeError("DeviceMemoryResource is expired")
496+
# TODO: this implementation does not allow lowering to Cython + nogil
497+
err, value = driver.cuMemPoolGetAttribute(mr.handle, attr_enum)
488498
raise_if_driver_error(err)
489499
return property_type(value)
490500
return property(fget=fget, doc=stub.__doc__)
@@ -531,7 +541,34 @@ class DeviceMemoryResourceAttributes:
531541
# and the serialized buffer descriptor.
532542
_ipc_registry = {}
533543

534-
class DeviceMemoryResource(MemoryResource):
544+
545+
cdef class _DeviceMemoryResourceBase:
546+
"""Internal only. Responsible for offering C layout & attribute access."""
547+
cdef:
548+
int _dev_id
549+
cydriver.CUmemoryPool _mempool_handle
550+
object _attributes
551+
cydriver.CUmemAllocationHandleType _ipc_handle_type
552+
bint _mempool_owned
553+
bint _is_mapped
554+
object _uuid
555+
IPCAllocationHandle _alloc_handle
556+
557+
def __cinit__(self):
558+
self._dev_id = cydriver.CU_DEVICE_INVALID
559+
self._mempool_handle = NULL
560+
self._attributes = None
561+
self._ipc_handle_type = cydriver.CUmemAllocationHandleType.CU_MEM_HANDLE_TYPE_MAX
562+
self._mempool_owned = False
563+
self._is_mapped = False
564+
self._uuid = None
565+
self._alloc_handle = None
566+
567+
def __dealloc__(self):
568+
pass
569+
570+
571+
cdef class DeviceMemoryResource(_DeviceMemoryResourceBase, MemoryResource):
535572
"""
536573
Create a device memory resource managing a stream-ordered memory pool.
537574
@@ -609,97 +646,91 @@ class DeviceMemoryResource(MemoryResource):
609646
methods. The reconstruction procedure uses the registry to find the
610647
associated MMR.
611648
"""
612-
__slots__ = ("_dev_id", "_mempool_handle", "_attributes", "_ipc_handle_type",
613-
"_mempool_owned", "_is_mapped", "_uuid", "_alloc_handle")
649+
cdef:
650+
dict __dict__
651+
object __weakref__
614652

615653
def __init__(self, device_id: int | Device, options=None):
616-
device_id = getattr(device_id, 'device_id', device_id)
654+
cdef int dev_id = getattr(device_id, 'device_id', device_id)
617655
opts = check_or_create_options(
618656
DeviceMemoryResourceOptions, options, "DeviceMemoryResource options", keep_none=True
619657
)
658+
cdef cydriver.cuuint64_t current_threshold
659+
cdef cydriver.cuuint64_t max_threshold = 0xFFFFFFFFFFFFFFFF
660+
cdef cydriver.CUmemPoolProps properties
620661

621662
if opts is None:
622663
# Get the current memory pool.
623-
self._dev_id = device_id
624-
self._mempool_handle = None
625-
self._attributes = None
626-
self._ipc_handle_type = _NOIPC_HANDLE_TYPE
664+
self._dev_id = dev_id
665+
self._ipc_handle_type = cydriver.CUmemAllocationHandleType.CU_MEM_HANDLE_TYPE_NONE
627666
self._mempool_owned = False
628-
self._is_mapped = False
629-
self._uuid = None
630-
self._alloc_handle = None
631667

632-
err, self._mempool_handle = driver.cuDeviceGetMemPool(self.device_id)
633-
raise_if_driver_error(err)
668+
with nogil:
669+
HANDLE_RETURN(cydriver.cuDeviceGetMemPool(&(self._mempool_handle), dev_id))
634670

635-
# Set a higher release threshold to improve performance when there are no active allocations.
636-
# By default, the release threshold is 0, which means memory is immediately released back
637-
# to the OS when there are no active suballocations, causing performance issues.
638-
# Check current release threshold
639-
err, current_threshold = driver.cuMemPoolGetAttribute(
640-
self._mempool_handle, driver.CUmemPool_attribute.CU_MEMPOOL_ATTR_RELEASE_THRESHOLD
641-
)
642-
raise_if_driver_error(err)
643-
# If threshold is 0 (default), set it to maximum to retain memory in the pool
644-
if int(current_threshold) == 0:
645-
err, = driver.cuMemPoolSetAttribute(
646-
self._mempool_handle,
647-
driver.CUmemPool_attribute.CU_MEMPOOL_ATTR_RELEASE_THRESHOLD,
648-
driver.cuuint64_t(0xFFFFFFFFFFFFFFFF),
671+
# Set a higher release threshold to improve performance when there are no active allocations.
672+
# By default, the release threshold is 0, which means memory is immediately released back
673+
# to the OS when there are no active suballocations, causing performance issues.
674+
# Check current release threshold
675+
HANDLE_RETURN(cydriver.cuMemPoolGetAttribute(
676+
self._mempool_handle, cydriver.CUmemPool_attribute.CU_MEMPOOL_ATTR_RELEASE_THRESHOLD, &current_threshold)
649677
)
650-
raise_if_driver_error(err)
678+
679+
# If threshold is 0 (default), set it to maximum to retain memory in the pool
680+
if current_threshold == 0:
681+
HANDLE_RETURN(cydriver.cuMemPoolSetAttribute(
682+
self._mempool_handle,
683+
cydriver.CUmemPool_attribute.CU_MEMPOOL_ATTR_RELEASE_THRESHOLD,
684+
&max_threshold
685+
))
651686
else:
652687
# Create a new memory pool.
653-
if opts.ipc_enabled and _IPC_HANDLE_TYPE == _NOIPC_HANDLE_TYPE:
688+
if opts.ipc_enabled and _IPC_HANDLE_TYPE == cydriver.CUmemAllocationHandleType.CU_MEM_HANDLE_TYPE_NONE:
654689
raise RuntimeError("IPC is not available on {platform.system()}")
655690

656-
properties = driver.CUmemPoolProps()
657-
properties.allocType = driver.CUmemAllocationType.CU_MEM_ALLOCATION_TYPE_PINNED
658-
properties.handleTypes = _IPC_HANDLE_TYPE if opts.ipc_enabled else _NOIPC_HANDLE_TYPE
659-
properties.location = driver.CUmemLocation()
660-
properties.location.id = device_id
661-
properties.location.type = driver.CUmemLocationType.CU_MEM_LOCATION_TYPE_DEVICE
691+
memset(&properties, 0, sizeof(cydriver.CUmemPoolProps))
692+
properties.allocType = cydriver.CUmemAllocationType.CU_MEM_ALLOCATION_TYPE_PINNED
693+
properties.handleTypes = _IPC_HANDLE_TYPE if opts.ipc_enabled else cydriver.CUmemAllocationHandleType.CU_MEM_HANDLE_TYPE_NONE
694+
properties.location.id = dev_id
695+
properties.location.type = cydriver.CUmemLocationType.CU_MEM_LOCATION_TYPE_DEVICE
662696
properties.maxSize = opts.max_size
663-
properties.win32SecurityAttributes = 0
697+
properties.win32SecurityAttributes = NULL
664698
properties.usage = 0
665699

666-
self._dev_id = device_id
667-
self._mempool_handle = None
668-
self._attributes = None
700+
self._dev_id = dev_id
669701
self._ipc_handle_type = properties.handleTypes
670702
self._mempool_owned = True
671-
self._is_mapped = False
672-
self._uuid = None
673-
self._alloc_handle = None
674703

675-
err, self._mempool_handle = driver.cuMemPoolCreate(properties)
676-
raise_if_driver_error(err)
704+
with nogil:
705+
HANDLE_RETURN(cydriver.cuMemPoolCreate(&(self._mempool_handle), &properties))
706+
# TODO: should we also set the threshold here?
677707

678708
if opts.ipc_enabled:
679-
self.get_allocation_handle() # enables Buffer.get_ipc_descriptor, sets uuid
709+
self.get_allocation_handle() # enables Buffer.get_ipc_descriptor, sets uuid
680710

681-
def __del__(self):
711+
def __dealloc__(self):
682712
self.close()
683713

684714
def close(self):
685715
"""Close the device memory resource and destroy the associated memory pool if owned."""
686-
if self._mempool_handle is not None:
687-
try:
688-
if self._mempool_owned:
689-
err, = driver.cuMemPoolDestroy(self._mempool_handle)
690-
raise_if_driver_error(err)
691-
finally:
692-
if self.is_mapped:
693-
self.unregister()
694-
self._dev_id = None
695-
self._mempool_handle = None
696-
self._attributes = None
697-
self._ipc_handle_type = _NOIPC_HANDLE_TYPE
698-
self._mempool_owned = False
699-
self._is_mapped = False
700-
self._uuid = None
701-
self._alloc_handle = None
716+
if self._mempool_handle == NULL:
717+
return
702718

719+
try:
720+
if self._mempool_owned:
721+
with nogil:
722+
HANDLE_RETURN(cydriver.cuMemPoolDestroy(self._mempool_handle))
723+
finally:
724+
if self.is_mapped:
725+
self.unregister()
726+
self._dev_id = cydriver.CU_DEVICE_INVALID
727+
self._mempool_handle = NULL
728+
self._attributes = None
729+
self._ipc_handle_type = cydriver.CUmemAllocationHandleType.CU_MEM_HANDLE_TYPE_MAX
730+
self._mempool_owned = False
731+
self._is_mapped = False
732+
self._uuid = None
733+
self._alloc_handle = None
703734

704735
def __reduce__(self):
705736
return DeviceMemoryResource.from_registry, (self.uuid,)
@@ -775,30 +806,33 @@ class DeviceMemoryResource(MemoryResource):
775806
"""
776807
# Quick exit for registry hits.
777808
uuid = getattr(alloc_handle, 'uuid', None)
778-
self = _ipc_registry.get(uuid)
779-
if self is not None:
780-
return self
809+
mr = _ipc_registry.get(uuid)
810+
if mr is not None:
811+
return mr
781812

782813
device_id = getattr(device_id, 'device_id', device_id)
783814

784-
self = cls.__new__(cls)
815+
cdef DeviceMemoryResource self = DeviceMemoryResource.__new__(cls)
785816
self._dev_id = device_id
786-
self._mempool_handle = None
817+
self._mempool_handle = NULL
787818
self._attributes = None
788819
self._ipc_handle_type = _IPC_HANDLE_TYPE
789820
self._mempool_owned = True
790821
self._is_mapped = True
791822
self._uuid = None
792-
self._alloc_handle = None # only used for non-imported
823+
self._alloc_handle = None # only used for non-imported
793824

794-
err, self._mempool_handle = driver.cuMemPoolImportFromShareableHandle(int(alloc_handle), _IPC_HANDLE_TYPE, 0)
795-
raise_if_driver_error(err)
825+
cdef int handle = int(alloc_handle)
826+
with nogil:
827+
HANDLE_RETURN(cydriver.cuMemPoolImportFromShareableHandle(
828+
&(self._mempool_handle), &handle, _IPC_HANDLE_TYPE, 0)
829+
)
796830
if uuid is not None:
797831
registered = self.register(uuid)
798832
assert registered is self
799833
return self
800834

801-
def get_allocation_handle(self) -> IPCAllocationHandle:
835+
cpdef IPCAllocationHandle get_allocation_handle(self):
802836
"""Export the memory pool handle to be shared (requires IPC).
803837
804838
The handle can be used to share the memory pool with other processes.
@@ -808,13 +842,19 @@ class DeviceMemoryResource(MemoryResource):
808842
-------
809843
The shareable handle for the memory pool.
810844
"""
845+
# Note: This is Linux only (int for file descriptor)
846+
cdef int alloc_handle
847+
811848
if self._alloc_handle is None:
812849
if not self.is_ipc_enabled:
813850
raise RuntimeError("Memory resource is not IPC-enabled")
814851
if self._is_mapped:
815852
raise RuntimeError("Imported memory resource cannot be exported")
816-
err, alloc_handle = driver.cuMemPoolExportToShareableHandle(self._mempool_handle, _IPC_HANDLE_TYPE, 0)
817-
raise_if_driver_error(err)
853+
854+
with nogil:
855+
HANDLE_RETURN(cydriver.cuMemPoolExportToShareableHandle(
856+
&alloc_handle, self._mempool_handle, _IPC_HANDLE_TYPE, 0)
857+
)
818858
try:
819859
assert self._uuid is None
820860
import uuid
@@ -846,9 +886,11 @@ class DeviceMemoryResource(MemoryResource):
846886
raise TypeError("Cannot allocate from a mapped IPC-enabled memory resource")
847887
if stream is None:
848888
stream = default_stream()
849-
err, ptr = driver.cuMemAllocFromPoolAsync(size, self._mempool_handle, stream.handle)
850-
raise_if_driver_error(err)
851-
return Buffer._init(ptr, size, self)
889+
cdef cydriver.CUstream s = <cydriver.CUstream><uintptr_t>(stream.handle)
890+
cdef cydriver.CUdeviceptr devptr
891+
with nogil:
892+
HANDLE_RETURN(cydriver.cuMemAllocFromPoolAsync(&devptr, size, self._mempool_handle, s))
893+
return Buffer._init(<intptr_t>devptr, size, self)
852894

853895
def deallocate(self, ptr: DevicePointerT, size_t size, stream: Stream = None):
854896
"""Deallocate a buffer previously allocated by this resource.
@@ -865,8 +907,10 @@ class DeviceMemoryResource(MemoryResource):
865907
"""
866908
if stream is None:
867909
stream = default_stream()
868-
err, = driver.cuMemFreeAsync(ptr, stream.handle)
869-
raise_if_driver_error(err)
910+
cdef cydriver.CUstream s = <cydriver.CUstream><uintptr_t>(stream.handle)
911+
cdef cydriver.CUdeviceptr devptr = <cydriver.CUdeviceptr><intptr_t>ptr
912+
with nogil:
913+
HANDLE_RETURN(cydriver.cuMemFreeAsync(devptr, s))
870914

871915
@property
872916
def attributes(self) -> DeviceMemoryResourceAttributes:
@@ -881,9 +925,9 @@ class DeviceMemoryResource(MemoryResource):
881925
return self._dev_id
882926

883927
@property
884-
def handle(self) -> cuda.bindings.driver.CUmemoryPool:
928+
def handle(self) -> driver.CUmemoryPool:
885929
"""Handle to the underlying memory pool."""
886-
return self._mempool_handle
930+
return driver.CUmemoryPool(<uintptr_t>(self._mempool_handle))
887931

888932
@property
889933
def is_handle_owned(self) -> bool:
@@ -911,7 +955,7 @@ class DeviceMemoryResource(MemoryResource):
911955
@property
912956
def is_ipc_enabled(self) -> bool:
913957
"""Whether this memory resource has IPC enabled."""
914-
return self._ipc_handle_type != _NOIPC_HANDLE_TYPE
958+
return self._ipc_handle_type != cydriver.CUmemAllocationHandleType.CU_MEM_HANDLE_TYPE_NONE
915959

916960

917961
def _deep_reduce_device_memory_resource(mr):

0 commit comments

Comments
 (0)