From da30e19e2d4ce076296bfeb17472c43db6504580 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:11:44 -0500 Subject: [PATCH 1/3] test: add test for structured dtype properties --- cuda_core/tests/test_utils.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/cuda_core/tests/test_utils.py b/cuda_core/tests/test_utils.py index d9d9bc398c..9fede20e4a 100644 --- a/cuda_core/tests/test_utils.py +++ b/cuda_core/tests/test_utils.py @@ -443,3 +443,20 @@ def test_from_buffer_with_non_power_of_two_itemsize(): buffer = dev.memory_resource.allocate(required_size) view = StridedMemoryView.from_buffer(buffer, shape=shape, strides=layout.strides, dtype=dtype, is_readonly=True) assert view.dtype == dtype + + +def test_struct_array(): + cp = pytest.importorskip("cupy") + + x = np.array([(1.0, 2), (2.0, 3)], dtype=[("array1", np.float64), ("array2", np.int64)]) + + y = cp.empty(2, dtype=x.dtype) + y.set(x) + + smv = StridedMemoryView.from_cuda_array_interface(y, stream_ptr=0) + assert smv.size * smv.dtype.itemsize == x.nbytes + assert smv.size == x.size + assert smv.shape == x.shape + # full dtype information doesn't seem to be preserved due to use of type strings, + # which are lossy, e.g., dtype([("a", "int")]).str == "V8" + assert smv.dtype == np.dtype(f"V{x.itemsize}") From ec12a0edf0c34b17f119f5aeeab4a7585e5ca9b8 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:12:12 -0500 Subject: [PATCH 2/3] feat: improve support for structured dtype arrays held by `StridedMemoryView` --- cuda_core/cuda/core/_memoryview.pyx | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/cuda_core/cuda/core/_memoryview.pyx b/cuda_core/cuda/core/_memoryview.pyx index c12cbbaa8a..2d013feeab 100644 --- a/cuda_core/cuda/core/_memoryview.pyx +++ b/cuda_core/cuda/core/_memoryview.pyx @@ -365,8 +365,7 @@ cdef class StridedMemoryView: if self.dl_tensor != NULL: self._dtype = dtype_dlpack_to_numpy(&self.dl_tensor.dtype) elif self.metadata is not None: - # TODO: this only works for built-in numeric types - self._dtype = _typestr2dtype[self.metadata["typestr"]] + self._dtype = _typestr2dtype(self.metadata["typestr"]) return self._dtype @@ -503,8 +502,24 @@ _builtin_numeric_dtypes = [ numpy.dtype("bool"), ] # Doing it once to avoid repeated overhead -_typestr2dtype = {dtype.str: dtype for dtype in _builtin_numeric_dtypes} -_typestr2itemsize = {dtype.str: dtype.itemsize for dtype in _builtin_numeric_dtypes} +_TYPESTR_TO_DTYPE = {dtype.str: dtype for dtype in _builtin_numeric_dtypes} +_TYPESTR_TO_ITEMSIZE = {dtype.str: dtype.itemsize for dtype in _builtin_numeric_dtypes} + +cpdef object _typestr2dtype(str typestr): + if (dtype := _TYPESTR_TO_DTYPE.get(typestr)) is not None: + return dtype + + _TYPESTR_TO_DTYPE[typestr] = dtype = numpy.dtype(typestr) + return dtype + + +cdef int _typestr2itemsize(str typestr) except -1: + if (itemsize := _TYPESTR_TO_ITEMSIZE.get(typestr)) is not None: + return itemsize + + dtype = _typestr2dtype(typestr) + _TYPESTR_TO_ITEMSIZE[typestr] = itemsize = dtype.itemsize + return itemsize cdef object dtype_dlpack_to_numpy(DLDataType* dtype): @@ -664,7 +679,7 @@ cdef _StridedLayout layout_from_cai(object metadata): cdef _StridedLayout layout = _StridedLayout.__new__(_StridedLayout) cdef object shape = metadata["shape"] cdef object strides = metadata.get("strides") - cdef int itemsize = _typestr2itemsize[metadata["typestr"]] + cdef int itemsize = _typestr2itemsize(metadata["typestr"]) layout.init_from_tuple(shape, strides, itemsize, True) return layout From 9d37149165829b456f538b974bb13e952549868b Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:19:53 -0500 Subject: [PATCH 3/3] chore: simplify using an lru cache instead of manual caching --- cuda_core/cuda/core/_memoryview.pyx | 43 ++++++----------------------- 1 file changed, 8 insertions(+), 35 deletions(-) diff --git a/cuda_core/cuda/core/_memoryview.pyx b/cuda_core/cuda/core/_memoryview.pyx index 2d013feeab..1dbebe7789 100644 --- a/cuda_core/cuda/core/_memoryview.pyx +++ b/cuda_core/cuda/core/_memoryview.pyx @@ -485,41 +485,14 @@ cdef StridedMemoryView view_as_dlpack(obj, stream_ptr, view=None): return buf -_builtin_numeric_dtypes = [ - numpy.dtype("uint8"), - numpy.dtype("uint16"), - numpy.dtype("uint32"), - numpy.dtype("uint64"), - numpy.dtype("int8"), - numpy.dtype("int16"), - numpy.dtype("int32"), - numpy.dtype("int64"), - numpy.dtype("float16"), - numpy.dtype("float32"), - numpy.dtype("float64"), - numpy.dtype("complex64"), - numpy.dtype("complex128"), - numpy.dtype("bool"), -] -# Doing it once to avoid repeated overhead -_TYPESTR_TO_DTYPE = {dtype.str: dtype for dtype in _builtin_numeric_dtypes} -_TYPESTR_TO_ITEMSIZE = {dtype.str: dtype.itemsize for dtype in _builtin_numeric_dtypes} - -cpdef object _typestr2dtype(str typestr): - if (dtype := _TYPESTR_TO_DTYPE.get(typestr)) is not None: - return dtype - - _TYPESTR_TO_DTYPE[typestr] = dtype = numpy.dtype(typestr) - return dtype - - -cdef int _typestr2itemsize(str typestr) except -1: - if (itemsize := _TYPESTR_TO_ITEMSIZE.get(typestr)) is not None: - return itemsize - - dtype = _typestr2dtype(typestr) - _TYPESTR_TO_ITEMSIZE[typestr] = itemsize = dtype.itemsize - return itemsize +@functools.lru_cache +def _typestr2dtype(str typestr): + return numpy.dtype(typestr) + + +@functools.lru_cache +def _typestr2itemsize(str typestr): + return _typestr2dtype(typestr).itemsize cdef object dtype_dlpack_to_numpy(DLDataType* dtype):