Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions cuda_core/cuda/core/_dlpack.pxd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0

Expand Down Expand Up @@ -90,26 +90,28 @@ cdef extern from "_include/dlpack.h":
void (*SetError)(void* error_ctx, const char* kind, const char* message) noexcept
)

# dlpack.h: these entry points return -1 on failure with a Python
# exception set. `except -1` tells Cython so; it does not change the C type.
ctypedef int (*DLPackManagedTensorFromPyObjectNoSync)(
void* py_object,
DLManagedTensorVersioned** out
)
) except -1

ctypedef int (*DLPackManagedTensorToPyObjectNoSync)(
DLManagedTensorVersioned* tensor,
void** out_py_object
)
) except -1

ctypedef int (*DLPackDLTensorFromPyObjectNoSync)(
void* py_object,
DLTensor* out
)
) except -1

ctypedef int (*DLPackCurrentWorkStream)(
_DLDeviceType device_type,
int32_t device_id,
void** out_current_stream
)
) except -1

ctypedef struct DLPackExchangeAPIHeader:
DLPackVersion version
Expand Down
60 changes: 23 additions & 37 deletions cuda_core/cuda/core/_memoryview.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -899,79 +899,65 @@ cdef int _smv_managed_tensor_allocator(
cdef int _smv_managed_tensor_from_py_object_no_sync(
void* py_object,
DLManagedTensorVersioned** out,
) noexcept with gil:
) except -1 with gil:
cdef DLManagedTensorVersioned* dlm_tensor_ver = NULL
if out == NULL:
cpython.PyErr_SetString(RuntimeError, b"out cannot be NULL")
return -1
raise RuntimeError("out cannot be NULL")
out[0] = NULL
cdef object obj = <object>py_object
if not isinstance(obj, StridedMemoryView):
cpython.PyErr_SetString(TypeError, b"py_object must be a StridedMemoryView")
return -1
raise TypeError("py_object must be a StridedMemoryView")
try:
dlm_tensor_ver = _smv_allocate_dlm_tensor_versioned()
_smv_fill_managed_tensor_versioned(dlm_tensor_ver, <StridedMemoryView>obj)
except Exception:
except BaseException:
# Rollback must also run for KeyboardInterrupt.
_smv_versioned_deleter(dlm_tensor_ver)
return -1
raise
out[0] = dlm_tensor_ver
return 0


cdef int _smv_managed_tensor_to_py_object_no_sync(
DLManagedTensorVersioned* tensor,
void** out_py_object,
) noexcept with gil:
cdef object capsule
cdef object py_view
) except -1 with gil:
if out_py_object == NULL:
cpython.PyErr_SetString(RuntimeError, b"out_py_object cannot be NULL")
return -1
raise RuntimeError("out_py_object cannot be NULL")
out_py_object[0] = NULL
if tensor == NULL:
cpython.PyErr_SetString(RuntimeError, b"tensor cannot be NULL")
return -1
try:
capsule = cpython.PyCapsule_New(
<void*>tensor,
DLPACK_VERSIONED_TENSOR_UNUSED_NAME,
_smv_pycapsule_deleter,
)
py_view = _smv_from_dlpack_capsule(capsule, capsule)
cpython.Py_INCREF(py_view)
out_py_object[0] = <void*>py_view
except Exception:
return -1
raise RuntimeError("tensor cannot be NULL")
cdef object capsule = cpython.PyCapsule_New(
<void*>tensor,
DLPACK_VERSIONED_TENSOR_UNUSED_NAME,
_smv_pycapsule_deleter,
)
cdef object py_view = _smv_from_dlpack_capsule(capsule, capsule)
cpython.Py_INCREF(py_view)
out_py_object[0] = <void*>py_view
return 0


cdef int _smv_dltensor_from_py_object_no_sync(
void* py_object,
DLTensor* out,
) noexcept with gil:
) except -1 with gil:
if out == NULL:
cpython.PyErr_SetString(RuntimeError, b"out cannot be NULL")
return -1
raise RuntimeError("out cannot be NULL")
cdef object obj = <object>py_object
if not isinstance(obj, StridedMemoryView):
cpython.PyErr_SetString(TypeError, b"py_object must be a StridedMemoryView")
return -1
try:
_smv_setup_dltensor_borrowed(out, <StridedMemoryView>obj)
except Exception:
return -1
raise TypeError("py_object must be a StridedMemoryView")
_smv_setup_dltensor_borrowed(out, <StridedMemoryView>obj)
return 0


cdef int _smv_current_work_stream(
_DLDeviceType device_type,
int32_t device_id,
void** out_current_stream,
) noexcept with gil:
) except -1 with gil:
if out_current_stream == NULL:
cpython.PyErr_SetString(RuntimeError, b"out_current_stream cannot be NULL")
return -1
raise RuntimeError("out_current_stream cannot be NULL")
# cuda.core has no global/current stream state today.
out_current_stream[0] = NULL
return 0
Expand Down
4 changes: 4 additions & 0 deletions cuda_core/docs/source/release/1.3.0-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,7 @@ Fixes and enhancements
:attr:`graph.GraphNode.succ` views with an object that is not a
:class:`~graph.GraphNode` is now a no-op instead of raising ``TypeError``,
consistent with :class:`collections.abc.MutableSet` semantics.

- The ``StridedMemoryView`` DLPack C exchange API now preserves the underlying
Python exception when an export or import fails, as required by the DLPack
contract, instead of returning ``-1`` with no exception set.
42 changes: 42 additions & 0 deletions cuda_core/tests/test_utils_dlpack.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,33 @@ def test_dlpack_c_exchange_api_dltensor_from_py_object_scalar():
assert not out.strides


@pytest.mark.agent_authored(model="gpt-5.6-sol")
def test_dlpack_c_exchange_api_managed_tensor_export_failure_sets_exception():
"""A view that DLPack cannot describe reports its BufferError through the
managed-tensor export entry point and leaves the output tensor NULL."""
api = _get_exchange_api()
swapped = np.dtype(np.int32).newbyteorder("S")
view = StridedMemoryView.from_array_interface(np.zeros(3, dtype=swapped))
out = ctypes.c_void_p(123)

with pytest.raises(BufferError, match="Non-native-endian"):
api.managed_tensor_from_py_object_no_sync(id(view), ctypes.byref(out))
assert not out.value


@pytest.mark.agent_authored(model="gpt-5.6-sol")
def test_dlpack_c_exchange_api_dltensor_export_failure_sets_exception():
"""A view that DLPack cannot describe reports its BufferError through the
borrowed DLTensor export entry point."""
api = _get_exchange_api()
swapped = np.dtype(np.int32).newbyteorder("S")
view = StridedMemoryView.from_array_interface(np.zeros(3, dtype=swapped))
out = _DLTensor()

with pytest.raises(BufferError, match="Non-native-endian"):
api.dltensor_from_py_object_no_sync(id(view), ctypes.byref(out))


def test_dlpack_c_exchange_api_managed_tensor_roundtrip():
"""``managed_tensor_from_py_object_no_sync`` produces a managed tensor that
``managed_tensor_to_py_object_no_sync`` turns back into a StridedMemoryView.
Expand Down Expand Up @@ -562,6 +589,21 @@ def test_dlpack_c_exchange_api_to_py_object_null_tensor():
assert not out_obj.value # set to NULL before the error


@pytest.mark.agent_authored(model="gpt-5.6-sol")
def test_dlpack_c_exchange_api_import_failure_sets_exception():
"""Rejecting an unsupported DLPack device reports the underlying BufferError."""
api = _get_exchange_api()
tensor = _DLManagedTensorVersioned()
tensor.version = _DLPackVersion(1, 0)
tensor.dl_tensor.device = _DLDevice(7, 0) # kDLVulkan is unsupported by cuda.core
tensor.dl_tensor.dtype = _DLDataType(0, 32, 1)
out_obj = ctypes.c_void_p(123)

with pytest.raises(BufferError, match="device not supported"):
api.managed_tensor_to_py_object_no_sync(ctypes.byref(tensor), ctypes.byref(out_obj))
assert not out_obj.value


@pytest.mark.parametrize(
"device_type",
[
Expand Down
Loading