diff --git a/CHANGELOG.md b/CHANGELOG.md index d305a5b593b7..7458ef021ae8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.16.1] - 12/05/2024 +## [0.16.1] - 12/06/2024 This is a bug-fix release. @@ -18,8 +18,9 @@ This is a bug-fix release. ### Fixed * Resolved an issue with Compute Follows Data inconsistency in `dpnp.extract` function [#2172](https://github.com/IntelPython/dpnp/pull/2172) -* Resolves an import error when using `dpnp` in virtual environment on Linux [#2199](https://github.com/IntelPython/dpnp/pull/2199) +* Resolved an import error when using `dpnp` in virtual environment on Linux [#2199](https://github.com/IntelPython/dpnp/pull/2199) * Fixed incorrect result produced by `dpnp.fft.fft` function when input array has negative strides [#2202](https://github.com/IntelPython/dpnp/pull/2202) +* Fixed an issue with `numpy.ndarray` input processing in the `dpnp.from_dlpack` function and updated the documentation [#2209](https://github.com/IntelPython/dpnp/pull/2209) * Resolved a compilation error when building with DPC++ 2025.1 compiler [#2211](https://github.com/IntelPython/dpnp/pull/2211) diff --git a/doc/conf.py b/doc/conf.py index 081070a5e59b..e98298a6bdae 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -217,6 +217,7 @@ def _can_document_member(member, *args, **kwargs): "python": ("https://docs.python.org/3/", None), "numpy": ("https://docs.scipy.org/doc/numpy/", None), "scipy": ("https://docs.scipy.org/doc/scipy/reference/", None), + "dpctl": ("https://intelpython.github.io/dpctl/latest/", None), } # If true, `todo` and `todoList` produce output, else they produce nothing. diff --git a/dpnp/__init__.py b/dpnp/__init__.py index 165e57a1bb44..be3b1bf949d4 100644 --- a/dpnp/__init__.py +++ b/dpnp/__init__.py @@ -49,6 +49,9 @@ [os.getenv("PATH", ""), mypath, dpctlpath] ) +# Borrowed from DPCTL +from dpctl.tensor import DLDeviceType + from dpnp.dpnp_array import dpnp_array as ndarray from dpnp.dpnp_flatiter import flatiter as flatiter from dpnp.dpnp_iface_types import * diff --git a/dpnp/dpnp_iface.py b/dpnp/dpnp_iface.py index 62cd0683fe5a..878dfd4a9f75 100644 --- a/dpnp/dpnp_iface.py +++ b/dpnp/dpnp_iface.py @@ -63,7 +63,6 @@ "check_supported_arrays_type", "convert_single_elem_array_to_scalar", "default_float_type", - "from_dlpack", "get_dpnp_descriptor", "get_include", "get_normalized_queue_device", @@ -464,60 +463,6 @@ def default_float_type(device=None, sycl_queue=None): return map_dtype_to_device(float64, _sycl_queue.sycl_device) -def from_dlpack(obj, /, *, device=None, copy=None): - """ - Create a dpnp array from a Python object implementing the ``__dlpack__`` - protocol. - - See https://dmlc.github.io/dlpack/latest/ for more details. - - Parameters - ---------- - obj : object - A Python object representing an array that implements the ``__dlpack__`` - and ``__dlpack_device__`` methods. - device : {:class:`dpctl.SyclDevice`, :class:`dpctl.SyclQueue`, - :class:`dpctl.tensor.Device`, tuple, None}, optional - Array API concept of a device where the output array is to be placed. - ``device`` can be ``None``, an oneAPI filter selector string, - an instance of :class:`dpctl.SyclDevice` corresponding to - a non-partitioned SYCL device, an instance of :class:`dpctl.SyclQueue`, - a :class:`dpctl.tensor.Device` object returned by - :attr:`dpctl.tensor.usm_ndarray.device`, or a 2-tuple matching - the format of the output of the ``__dlpack_device__`` method, - an integer enumerator representing the device type followed by - an integer representing the index of the device. - Default: ``None``. - copy {bool, None}, optional - Boolean indicating whether or not to copy the input. - - * If `copy``is ``True``, the input will always be copied. - * If ``False``, a ``BufferError`` will be raised if a copy is deemed - necessary. - * If ``None``, a copy will be made only if deemed necessary, otherwise, - the existing memory buffer will be reused. - - Default: ``None``. - - Returns - ------- - out : dpnp_array - Returns a new dpnp array containing the data from another array - (obj) with the ``__dlpack__`` method on the same device as object. - - Raises - ------ - TypeError: - if `obj` does not implement ``__dlpack__`` method - ValueError: - if the input array resides on an unsupported device - - """ - - usm_res = dpt.from_dlpack(obj, device=device, copy=copy) - return dpnp_array._create_from_usm_ndarray(usm_res) - - def get_dpnp_descriptor( ext_obj, copy_when_strides=True, diff --git a/dpnp/dpnp_iface_arraycreation.py b/dpnp/dpnp_iface_arraycreation.py index 2c8f55dd57c9..b92e5e908efd 100644 --- a/dpnp/dpnp_iface_arraycreation.py +++ b/dpnp/dpnp_iface_arraycreation.py @@ -75,6 +75,7 @@ "fromfunction", "fromiter", "fromstring", + "from_dlpack", "full", "full_like", "geomspace", @@ -1956,6 +1957,93 @@ def fromstring( ) +def from_dlpack(x, /, *, device=None, copy=None): + """ + Constructs :class:`dpnp.ndarray` or :class:`numpy.ndarray` instance from + a Python object `x` that implements ``__dlpack__`` protocol. + + For full documentation refer to :obj:`numpy.from_dlpack`. + + Parameters + ---------- + x : object + A Python object representing an array that implements the ``__dlpack__`` + and ``__dlpack_device__`` methods. + device : {None, string, tuple, device}, optional + Device where the output array is to be placed. `device` keyword values + can be: + + * ``None`` : The data remains on the same device. + * oneAPI filter selector string : SYCL device selected by filter + selector string. + * :class:`dpctl.SyclDevice` : Explicit SYCL device that must correspond + to a non-partitioned SYCL device. + * :class:`dpctl.SyclQueue` : Implies SYCL device targeted by the SYCL + queue. + * :class:`dpctl.tensor.Device` : Implies SYCL device + ``device.sycl_queue``. The `device` object is obtained via + :attr:`dpctl.tensor.usm_ndarray.device`. + * ``(device_type, device_id)`` : 2-tuple matching the format of the + output of the ``__dlpack_device__`` method: an integer enumerator + representing the device type followed by an integer representing + the index of the device. The only supported :class:`dpnp.DLDeviceType` + device types are ``"kDLCPU"`` and ``"kDLOneAPI"``. + + Default: ``None``. + copy : {bool, None}, optional + Boolean indicating whether or not to copy the input. + + * If `copy` is ``True``, the input will always be copied. + * If ``False``, a ``BufferError`` will be raised if a copy is deemed + necessary. + * If ``None``, a copy will be made only if deemed necessary, otherwise, + the existing memory buffer will be reused. + + Default: ``None``. + + Returns + ------- + out : {dpnp.ndarray, numpy.ndarray} + An array containing the data in `x`. When `copy` is ``None`` or + ``False``, this may be a view into the original memory. + The type of the returned object depends on where the data backing up + input object `x` resides. If it resides in a USM allocation on a SYCL + device, the type :class:`dpnp.ndarray` is returned, otherwise if it + resides on ``"kDLCPU"`` device the type is :class:`numpy.ndarray`, and + otherwise an exception is raised. + + Raises + ------ + TypeError + if `obj` does not implement ``__dlpack__`` method + ValueError + if data of the input object resides on an unsupported device + + Notes + ----- + If the return type is :class:`dpnp.ndarray`, the associated SYCL queue is + derived from the `device` keyword. When `device` keyword value has type + :class:`dpctl.SyclQueue`, the explicit queue instance is used, when `device` + keyword value has type :class:`dpctl.tensor.Device`, the + ``device.sycl_queue`` is used. In all other cases, the cached SYCL queue + corresponding to the implied SYCL device is used. + + Examples + -------- + >>> import dpnp as np + >>> import numpy + >>> x = numpy.arange(10) + >>> # create a view of the numpy array "x" in dpnp: + >>> y = np.from_dlpack(x) + + """ + + result = dpt.from_dlpack(x, device=device, copy=copy) + if isinstance(result, dpt.usm_ndarray): + return dpnp_array._create_from_usm_ndarray(result) + return result + + def full( shape, fill_value, diff --git a/tests/test_dlpack.py b/tests/test_dlpack.py index 25090d397cb4..57d3191e5081 100644 --- a/tests/test_dlpack.py +++ b/tests/test_dlpack.py @@ -8,8 +8,6 @@ get_all_dtypes, ) -device_oneAPI = 14 # DLDeviceType.kDLOneAPI - class TestDLPack: @pytest.mark.parametrize("stream", [None, 1]) @@ -56,11 +54,11 @@ def test_non_contiguous(self, xp): def test_device(self): x = dpnp.arange(5) - assert x.__dlpack_device__()[0] == device_oneAPI + assert x.__dlpack_device__()[0] == dpnp.DLDeviceType.kDLOneAPI y = dpnp.from_dlpack(x) - assert y.__dlpack_device__()[0] == device_oneAPI + assert y.__dlpack_device__()[0] == dpnp.DLDeviceType.kDLOneAPI z = y[::2] - assert z.__dlpack_device__()[0] == device_oneAPI + assert z.__dlpack_device__()[0] == dpnp.DLDeviceType.kDLOneAPI def test_ndim0(self): x = dpnp.array(1.0) @@ -72,3 +70,15 @@ def test_device(self): y = dpnp.from_dlpack(x, device=x.__dlpack_device__()) assert x.device == y.device assert x.get_array()._pointer == y.get_array()._pointer + + def test_numpy_input(self): + x = numpy.arange(10) + + y = dpnp.from_dlpack(x) + assert isinstance(y, numpy.ndarray) + assert y.ctypes.data == x.ctypes.data + assert y.dtype == x.dtype + + z = dpnp.from_dlpack(x, device=(dpnp.DLDeviceType.kDLCPU, 0)) + assert isinstance(z, numpy.ndarray) + assert z.dtype == y.dtype