From 006cc5472898fbb763017e49f3aa9779c5ed5d6c Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Tue, 3 Dec 2024 14:11:01 +0100 Subject: [PATCH 1/6] Move dpnp.from_dlpack to array creation routine --- dpnp/dpnp_iface.py | 55 ---------------------------- dpnp/dpnp_iface_arraycreation.py | 62 ++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 55 deletions(-) diff --git a/dpnp/dpnp_iface.py b/dpnp/dpnp_iface.py index 46dc8f2353cb..fccb3df59344 100644 --- a/dpnp/dpnp_iface.py +++ b/dpnp/dpnp_iface.py @@ -62,7 +62,6 @@ "check_limitations", "check_supported_arrays_type", "default_float_type", - "from_dlpack", "get_dpnp_descriptor", "get_include", "get_normalized_queue_device", @@ -443,60 +442,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 85ea220f5a75..360431c47b6a 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", @@ -2047,6 +2048,67 @@ def fromstring( ) +def from_dlpack(x, /, *, device=None, copy=None): + """ + Create a dpnp array from a Python object implementing the ``__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, tuple, SyclDevice, SyclQueue, Device}, 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.ndarray + 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 + + 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) + + """ + + usm_res = dpt.from_dlpack(x, device=device, copy=copy) + return dpnp_array._create_from_usm_ndarray(usm_res) + + def full( shape, fill_value, From e067321dcb4a7cc8303fce405d891cbdd064dea0 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Wed, 4 Dec 2024 13:50:38 +0100 Subject: [PATCH 2/6] Handle different result types --- dpnp/dpnp_iface_arraycreation.py | 67 ++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/dpnp/dpnp_iface_arraycreation.py b/dpnp/dpnp_iface_arraycreation.py index 360431c47b6a..c812fbc86f80 100644 --- a/dpnp/dpnp_iface_arraycreation.py +++ b/dpnp/dpnp_iface_arraycreation.py @@ -2050,8 +2050,8 @@ def fromstring( def from_dlpack(x, /, *, device=None, copy=None): """ - Create a dpnp array from a Python object implementing the ``__dlpack__`` - protocol. + 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`. @@ -2060,16 +2060,27 @@ def from_dlpack(x, /, *, device=None, copy=None): x : object A Python object representing an array that implements the ``__dlpack__`` and ``__dlpack_device__`` methods. - device : {None, tuple, SyclDevice, SyclQueue, Device}, 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. + 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:`dpctl.tensor.DLDeviceType` device types are ``"kDLCPU"`` + and ``"kDLOneAPI"``. + Default: ``None``. copy : {bool, None}, optional Boolean indicating whether or not to copy the input. @@ -2084,16 +2095,30 @@ def from_dlpack(x, /, *, device=None, copy=None): Returns ------- - out : dpnp.ndarray - Returns a new dpnp array containing the data from another array `obj` - with the ``__dlpack__`` method on the same device as object. + 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: + TypeError if `obj` does not implement ``__dlpack__`` method - ValueError: - if the input array resides on an unsupported device + 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 -------- @@ -2105,8 +2130,10 @@ def from_dlpack(x, /, *, device=None, copy=None): """ - usm_res = dpt.from_dlpack(x, device=device, copy=copy) - return dpnp_array._create_from_usm_ndarray(usm_res) + 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( From 1e29cefff4c4ba9c0517dd8e56087cfb2c760452 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Wed, 4 Dec 2024 14:45:59 +0100 Subject: [PATCH 3/6] Add mapping to dpctl docs in sphinx configuration --- doc/conf.py | 1 + dpnp/dpnp_iface_arraycreation.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 413f3d0ac399..cfa4c13dfbbc 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -220,6 +220,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/dpnp_iface_arraycreation.py b/dpnp/dpnp_iface_arraycreation.py index c812fbc86f80..ed5ede2cb632 100644 --- a/dpnp/dpnp_iface_arraycreation.py +++ b/dpnp/dpnp_iface_arraycreation.py @@ -2066,20 +2066,20 @@ def from_dlpack(x, /, *, device=None, copy=None): * ``None`` : The data remains on the same device. * oneAPI filter selector string : SYCL device selected by filter - selector string. + selector string. * :class:`dpctl.SyclDevice` : Explicit SYCL device that must correspond - to a non-partitioned SYCL device. + to a non-partitioned SYCL device. * :class:`dpctl.SyclQueue` : Implies SYCL device targeted by the SYCL - queue. + queue. * :class:`dpctl.tensor.Device` : Implies SYCL device - ``device.sycl_queue``. The `device` object is obtained via - :attr:`dpctl.tensor.usm_ndarray.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:`dpctl.tensor.DLDeviceType` device types are ``"kDLCPU"`` - and ``"kDLOneAPI"``. + 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:`dpctl.tensor.DLDeviceType` device types are ``"kDLCPU"`` + and ``"kDLOneAPI"``. Default: ``None``. copy : {bool, None}, optional From a0ac30b18d002672f575e1d514dbfcc319a15b66 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Wed, 4 Dec 2024 17:53:39 +0100 Subject: [PATCH 4/6] Borrowed DLDeviceType from DPCTL --- dpnp/__init__.py | 3 +++ dpnp/dpnp_iface_arraycreation.py | 5 ++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dpnp/__init__.py b/dpnp/__init__.py index 4339bd56c8b3..bb285d5029bd 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_arraycreation.py b/dpnp/dpnp_iface_arraycreation.py index ed5ede2cb632..2b7f80c4b5e3 100644 --- a/dpnp/dpnp_iface_arraycreation.py +++ b/dpnp/dpnp_iface_arraycreation.py @@ -2077,9 +2077,8 @@ def from_dlpack(x, /, *, device=None, copy=None): * ``(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:`dpctl.tensor.DLDeviceType` device types are ``"kDLCPU"`` - and ``"kDLOneAPI"``. + the index of the device. The only supported :class:`dpnp.DLDeviceType` + device types are ``"kDLCPU"`` and ``"kDLOneAPI"``. Default: ``None``. copy : {bool, None}, optional From 3293b8fd5a7980142585012aa4051c2ecf5a53a7 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Wed, 4 Dec 2024 17:58:58 +0100 Subject: [PATCH 5/6] Add a test with numpy.ndarray as an input --- dpnp/tests/test_dlpack.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/dpnp/tests/test_dlpack.py b/dpnp/tests/test_dlpack.py index 25090d397cb4..57d3191e5081 100644 --- a/dpnp/tests/test_dlpack.py +++ b/dpnp/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 From 8fa32f2eca3fb365785bd33d4ab4751c80285691 Mon Sep 17 00:00:00 2001 From: Anton <100830759+antonwolfy@users.noreply.github.com> Date: Thu, 5 Dec 2024 11:54:55 +0100 Subject: [PATCH 6/6] Update dpnp/dpnp_iface_arraycreation.py Co-authored-by: Vahid Tavanashad <120411540+vtavana@users.noreply.github.com> --- dpnp/dpnp_iface_arraycreation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dpnp/dpnp_iface_arraycreation.py b/dpnp/dpnp_iface_arraycreation.py index 2b7f80c4b5e3..81a6f05a623c 100644 --- a/dpnp/dpnp_iface_arraycreation.py +++ b/dpnp/dpnp_iface_arraycreation.py @@ -2084,7 +2084,7 @@ def from_dlpack(x, /, *, device=None, copy=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 `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,