Add __array__ and __dlpack__ protocol support to OrtValue - #27826
Add __array__ and __dlpack__ protocol support to OrtValue#27826Rishi-Dave wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR exposes NumPy __array__ and DLPack (__dlpack__, __dlpack_device__, from_dlpack) protocols on the public Python onnxruntime.OrtValue wrapper to enable idiomatic np.array(ortvalue)/np.asarray(ortvalue) and direct DLPack interop without using private _ortvalue access.
Changes:
- Add
OrtValue.__array__to support NumPy array conversion (including dtype + NumPy 2.0copybehavior). - Add public DLPack protocol methods and
OrtValue.from_dlpackthat delegate to the underlying pybindC.OrtValue. - Add Python unit tests covering NumPy array protocol and DLPack round-trips across multiple dtypes.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
onnxruntime/python/onnxruntime_inference_collection.py |
Adds NumPy and DLPack protocol methods to the public OrtValue wrapper. |
onnxruntime/test/python/onnxruntime_test_python.py |
Adds new tests validating NumPy __array__ and wrapper-level DLPack behaviors. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| data = data.__dlpack__() | ||
| return cls(C.OrtValue.from_dlpack(data, is_bool_tensor)) | ||
|
|
||
| def __dlpack__(self, *, stream: int | None = None) -> object: |
There was a problem hiding this comment.
OrtValue.__dlpack__ is defined with *, stream=..., which makes stream keyword-only. The underlying pybind C.OrtValue.__dlpack__ accepts a positional stream argument, and some consumers of the DLPack protocol (e.g., framework from_dlpack implementations) may call __dlpack__(stream) positionally. This wrapper would raise TypeError in that case and break interop. Make stream a normal (positional-or-keyword) argument to match the protocol and the underlying binding.
| def __dlpack__(self, *, stream: int | None = None) -> object: | |
| def __dlpack__(self, stream: int | None = None) -> object: |
There was a problem hiding this comment.
The current keyword-only signature matches the Python array API standard, which defines __dlpack__(self, *, stream=None, ...). NumPy, CuPy, and JAX all use keyword-only as well. Every major consumer (numpy.from_dlpack, torch.from_dlpack, cupy.from_dlpack, jax.dlpack.from_dlpack) passes stream as a keyword argument — I wasn't able to find any framework that calls __dlpack__(stream) positionally.
Keeping stream keyword-only here stays consistent with the spec and with how the protocol is consumed in practice.
| Valid only for OrtValues holding Tensors on CPU. | ||
| For device tensors the data is first copied to host memory. |
There was a problem hiding this comment.
The __array__ docstring is internally inconsistent: it says this is "Valid only" for CPU tensors but then states device tensors are copied to host first. Since the implementation unconditionally calls self._ortvalue.numpy() (which can copy device tensors to CPU), please clarify the docstring to accurately describe supported devices/behavior (and any restrictions for non-tensor OrtValues).
| Valid only for OrtValues holding Tensors on CPU. | |
| For device tensors the data is first copied to host memory. | |
| Valid only for OrtValues holding Tensors. | |
| For tensors on non-CPU devices, the data is first copied to host memory. | |
| OrtValues holding non-Tensor types (for example, SparseTensor or Tensor | |
| Sequence) are not supported and will cause an exception to be raised by | |
| the underlying ``numpy()`` call. |
There was a problem hiding this comment.
Fixed in a959fb5 — rewrote the docstring to accurately describe behavior across devices and non-Tensor types.
a959fb5 to
0603480
Compare
Expose the DLPack and numpy array interchange protocols on the Python-level OrtValue wrapper class, enabling idiomatic interop: - __array__(dtype, copy): allows np.array(ort_value) without explicit .numpy() calls, with optional dtype casting - __dlpack__(stream): delegates to C layer for zero-copy tensor sharing - __dlpack_device__(): returns (device_type, device_id) tuple - from_dlpack(source): classmethod that auto-calls __dlpack__() on the source and handles bool tensor detection via is_dlpack_uint8_tensor Fixes microsoft#24071
0603480 to
ae2e63c
Compare
Summary
__array__,__dlpack__,__dlpack_device__, andfrom_dlpackto the PythonOrtValuewrapper classnp.array(ort_value)andOrtValue.from_dlpack(tensor)now work directlyMotivation
The C pybind layer (
C.OrtValue) already exposes__dlpack__,__dlpack_device__, andfrom_dlpack(added in #23110), but the Python-levelOrtValuewrapper class doesn't surface them. This means users must reach intoort_value._ortvalue.__dlpack__()for DLPack interop, and there's no__array__protocol support at all.This PR bridges that gap so that standard Python array interchange protocols work on
OrtValuedirectly:Fixes #24071
Changes
onnxruntime/python/onnxruntime_inference_collection.py: Added four methods toOrtValue:__array__(dtype, copy)— numpy array protocol; delegates to.numpy()with optional dtype casting. Acceptscopyparameter for numpy 2.0 compatibility.__dlpack__(*, stream)— delegates to C layer's__dlpack____dlpack_device__()— delegates to C layer's__dlpack_device__from_dlpack(source)— classmethod that callssource.__dlpack__(), auto-detects bool tensors viaC.is_dlpack_uint8_tensor, and constructs anOrtValueonnxruntime/test/python/onnxruntime_test_python.py: Added 7 new test cases:from_dlpackwith numpy arrays as source__array__protocol with float32, float64, int64, and bool tensorsnp.array(ort_value, dtype=...)Test Plan
ruff format— both files passruff check— no new warnings introducedENABLE_DLPACKis not available in the build