From 346eeddada2f7360acb5c183f030e20ab315684c Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Thu, 17 Apr 2025 13:51:47 -0700 Subject: [PATCH 1/6] Resolve `dpt.asarray` failed to copy input `numpy.ndarray` #2055 adds _from_numpy_empty_like_orderK utility function --- dpctl/tensor/_copy_utils.py | 67 +++++++++++++++++++++++++------------ dpctl/tensor/_ctors.py | 24 +++---------- 2 files changed, 51 insertions(+), 40 deletions(-) diff --git a/dpctl/tensor/_copy_utils.py b/dpctl/tensor/_copy_utils.py index a2189defe7..d9d1c615f7 100644 --- a/dpctl/tensor/_copy_utils.py +++ b/dpctl/tensor/_copy_utils.py @@ -349,6 +349,31 @@ def _copy_from_usm_ndarray_to_usm_ndarray(dst, src): _copy_same_shape(dst, src_same_shape) +def _make_empty_like_orderK(X, dt, usm_type, dev): + st = list(X.strides) + perm = sorted( + range(X.ndim), + key=lambda d: builtins.abs(st[d]) if X.shape[d] > 1 else 0, + reverse=True, + ) + inv_perm = sorted(range(X.ndim), key=lambda i: perm[i]) + sh = X.shape + sh_sorted = tuple(sh[i] for i in perm) + R = dpt.empty(sh_sorted, dtype=dt, usm_type=usm_type, device=dev, order="C") + if min(st) < 0: + st_sorted = [st[i] for i in perm] + sl = tuple( + ( + slice(None, None, -1) + if st_sorted[i] < 0 + else slice(None, None, None) + ) + for i in range(X.ndim) + ) + R = R[sl] + return dpt.permute_dims(R, inv_perm) + + def _empty_like_orderK(X, dt, usm_type=None, dev=None): """Returns empty array like `x`, using order='K' @@ -371,28 +396,28 @@ def _empty_like_orderK(X, dt, usm_type=None, dev=None): return dpt.empty_like( X, dtype=dt, usm_type=usm_type, device=dev, order="F" ) - st = list(X.strides) - perm = sorted( - range(X.ndim), - key=lambda d: builtins.abs(st[d]) if X.shape[d] > 1 else 0, - reverse=True, - ) - inv_perm = sorted(range(X.ndim), key=lambda i: perm[i]) - sh = X.shape - sh_sorted = tuple(sh[i] for i in perm) - R = dpt.empty(sh_sorted, dtype=dt, usm_type=usm_type, device=dev, order="C") - if min(st) < 0: - st_sorted = [st[i] for i in perm] - sl = tuple( - ( - slice(None, None, -1) - if st_sorted[i] < 0 - else slice(None, None, None) - ) - for i in range(X.ndim) + return _make_empty_like_orderK(X, dt, usm_type, dev) + + +def _from_numpy_empty_like_orderK(X, dt, usm_type, dev): + """Returns empty usm_ndarray like NumPy array `x`, using order='K' + + For an array `x` that was obtained by permutation of a contiguous + array the returned array will have the same shape and the same + strides as `x`. + """ + if not isinstance(X, np.ndarray): + raise TypeError(f"Expected np.ndarray, got {type(X)}") + fl = X.flags + if fl["C"] or X.size <= 1: + return dpt.empty( + X.shape, dtype=dt, usm_type=usm_type, device=dev, order="C" ) - R = R[sl] - return dpt.permute_dims(R, inv_perm) + elif fl["F"]: + return dpt.empty( + X.shape, dtype=dt, usm_type=usm_type, device=dev, order="F" + ) + return _make_empty_like_orderK(X, dt, usm_type, dev) def _empty_like_pair_orderK(X1, X2, dt, res_shape, usm_type, dev): diff --git a/dpctl/tensor/_ctors.py b/dpctl/tensor/_ctors.py index ecdba971e2..a3bc560dd3 100644 --- a/dpctl/tensor/_ctors.py +++ b/dpctl/tensor/_ctors.py @@ -24,7 +24,10 @@ import dpctl.tensor as dpt import dpctl.tensor._tensor_impl as ti import dpctl.utils -from dpctl.tensor._copy_utils import _empty_like_orderK +from dpctl.tensor._copy_utils import ( + _empty_like_orderK, + _from_numpy_empty_like_orderK, +) from dpctl.tensor._data_types import _get_dtype from dpctl.tensor._device import normalize_queue_device from dpctl.tensor._usmarray import _is_object_with_buffer_protocol @@ -243,24 +246,7 @@ def _asarray_from_numpy_ndarray( if order == "K": # new USM allocation _ensure_native_dtype_device_support(dtype, copy_q.sycl_device) - res = dpt.usm_ndarray( - ary.shape, - dtype=dtype, - buffer=usm_type, - order="C", - buffer_ctor_kwargs={"queue": copy_q}, - ) - original_strides = ary.strides - ind = sorted( - range(ary.ndim), - key=lambda i: abs(original_strides[i]), - reverse=True, - ) - new_strides = tuple(res.strides[ind[i]] for i in ind) - # reuse previously made USM allocation - res = dpt.usm_ndarray( - res.shape, dtype=res.dtype, buffer=res.usm_data, strides=new_strides - ) + res = _from_numpy_empty_like_orderK(ary, dtype, usm_type, copy_q) else: _ensure_native_dtype_device_support(dtype, copy_q.sycl_device) res = dpt.usm_ndarray( From 5442d79d275c22e5c19450fd7f7ff1514c58dcbf Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Mon, 21 Apr 2025 14:21:25 -0700 Subject: [PATCH 2/6] Adjust docstrings in `_copy_utils` --- dpctl/tensor/_copy_utils.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/dpctl/tensor/_copy_utils.py b/dpctl/tensor/_copy_utils.py index d9d1c615f7..3726179744 100644 --- a/dpctl/tensor/_copy_utils.py +++ b/dpctl/tensor/_copy_utils.py @@ -350,6 +350,10 @@ def _copy_from_usm_ndarray_to_usm_ndarray(dst, src): def _make_empty_like_orderK(X, dt, usm_type, dev): + """ + Returns empty array with shape and strides like `X`, with dtype `dt`, + USM type `usm_type`, on device `dev`. + """ st = list(X.strides) perm = sorted( range(X.ndim), @@ -375,7 +379,8 @@ def _make_empty_like_orderK(X, dt, usm_type, dev): def _empty_like_orderK(X, dt, usm_type=None, dev=None): - """Returns empty array like `x`, using order='K' + """ + Returns empty array like `x`, using order='K' For an array `x` that was obtained by permutation of a contiguous array the returned array will have the same shape and the same @@ -400,7 +405,8 @@ def _empty_like_orderK(X, dt, usm_type=None, dev=None): def _from_numpy_empty_like_orderK(X, dt, usm_type, dev): - """Returns empty usm_ndarray like NumPy array `x`, using order='K' + """ + Returns empty usm_ndarray like NumPy array `x`, using order='K' For an array `x` that was obtained by permutation of a contiguous array the returned array will have the same shape and the same From d73cdddb26d7a1bd3e2d31e23cc217f4e9cfb206 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Mon, 21 Apr 2025 14:39:56 -0700 Subject: [PATCH 3/6] Add test for gh-2055 resolution and refactor tests for _copy_utils internal functions --- dpctl/tests/elementwise/test_type_utils.py | 45 ---------- dpctl/tests/test_tensor_copy_utils.py | 100 +++++++++++++++++++++ 2 files changed, 100 insertions(+), 45 deletions(-) create mode 100644 dpctl/tests/test_tensor_copy_utils.py diff --git a/dpctl/tests/elementwise/test_type_utils.py b/dpctl/tests/elementwise/test_type_utils.py index 274e86e4d6..04b8629db4 100644 --- a/dpctl/tests/elementwise/test_type_utils.py +++ b/dpctl/tests/elementwise/test_type_utils.py @@ -19,7 +19,6 @@ import dpctl import dpctl.tensor as dpt -import dpctl.tensor._copy_utils as cu import dpctl.tensor._type_utils as tu from .utils import _all_dtypes, _map_to_device_dtype @@ -70,50 +69,6 @@ def test_type_util_can_cast(): assert isinstance(r, bool) -def test_type_utils_empty_like_orderK(): - try: - a = dpt.empty((10, 10), dtype=dpt.int32, order="F") - except dpctl.SyclDeviceCreationError: - pytest.skip("No SYCL devices available") - X = cu._empty_like_orderK(a, dpt.int32, a.usm_type, a.device) - assert X.flags["F"] - - -def test_type_utils_empty_like_orderK_invalid_args(): - with pytest.raises(TypeError): - cu._empty_like_orderK([1, 2, 3], dpt.int32, "device", None) - with pytest.raises(TypeError): - cu._empty_like_pair_orderK( - [1, 2, 3], - ( - 1, - 2, - 3, - ), - dpt.int32, - (3,), - "device", - None, - ) - try: - a = dpt.empty(10, dtype=dpt.int32) - except dpctl.SyclDeviceCreationError: - pytest.skip("No SYCL devices available") - with pytest.raises(TypeError): - cu._empty_like_pair_orderK( - a, - ( - 1, - 2, - 3, - ), - dpt.int32, - (10,), - "device", - None, - ) - - def test_type_utils_find_buf_dtype(): def _denier_fn(dt): return False diff --git a/dpctl/tests/test_tensor_copy_utils.py b/dpctl/tests/test_tensor_copy_utils.py new file mode 100644 index 0000000000..61ca3ec87c --- /dev/null +++ b/dpctl/tests/test_tensor_copy_utils.py @@ -0,0 +1,100 @@ +# Data Parallel Control (dpctl) +# +# Copyright 2020-2025 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +import pytest + +import dpctl.tensor as dpt +import dpctl.tensor._copy_utils as cu +from dpctl.tests.helper import get_queue_or_skip + + +def test_copy_utils_empty_like_orderK(): + get_queue_or_skip() + a = dpt.empty((10, 10), dtype=dpt.int32, order="F") + X = cu._empty_like_orderK(a, dpt.int32, a.usm_type, a.device) + assert X.flags["F"] + + +def test_copy_utils_empty_like_orderK_invalid_args(): + get_queue_or_skip() + with pytest.raises(TypeError): + cu._empty_like_orderK([1, 2, 3], dpt.int32, "device", None) + with pytest.raises(TypeError): + cu._empty_like_pair_orderK( + [1, 2, 3], + ( + 1, + 2, + 3, + ), + dpt.int32, + (3,), + "device", + None, + ) + + a = dpt.empty(10, dtype=dpt.int32) + with pytest.raises(TypeError): + cu._empty_like_pair_orderK( + a, + ( + 1, + 2, + 3, + ), + dpt.int32, + (10,), + "device", + None, + ) + + +def test_copy_utils_from_numpy_empty_like_orderK(): + q = get_queue_or_skip() + + a = np.empty((10, 10), dtype=np.int32, order="C") + r0 = cu._from_numpy_empty_like_orderK(a, dpt.int32, "device", q) + assert r0.flags["C"] + + b = np.empty((10, 10), dtype=np.int32, order="F") + r1 = cu._from_numpy_empty_like_orderK(b, dpt.int32, "device", q) + assert r1.flags["F"] + + c = np.empty((2, 3, 4), dtype=np.int32, order="C") + c = np.transpose(c, (1, 0, 2)) + r2 = cu._from_numpy_empty_like_orderK(c, dpt.int32, "device", q) + assert not r2.flags["C"] and not r2.flags["F"] + + +def test_copy_utils_from_numpy_empty_like_orderK_invalid_args(): + with pytest.raises(TypeError): + cu._from_numpy_empty_like_orderK([1, 2, 3], dpt.int32, "device", None) + + +def test_gh_2055(): + """ + Test that `dpt.asarray` works on contiguous NumPy arrays with `order="K"` + when dimensions are permuted. + + See: https://github.com/IntelPython/dpctl/issues/2055 + """ + get_queue_or_skip() + + a = np.ones((2, 3, 4), dtype=dpt.int32) + a_t = np.transpose(a, (2, 0, 1)) + r = dpt.asarray(a_t) + assert not r.flags["C"] and not r.flags["F"] From eafb644f1301fc6d28b714ae85078385fe3fdf9a Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Wed, 23 Apr 2025 14:33:38 -0700 Subject: [PATCH 4/6] factor common call out of logic in _asarray_from_numpy_ndarray --- dpctl/tensor/_ctors.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dpctl/tensor/_ctors.py b/dpctl/tensor/_ctors.py index a3bc560dd3..afd1ca8a25 100644 --- a/dpctl/tensor/_ctors.py +++ b/dpctl/tensor/_ctors.py @@ -236,6 +236,7 @@ def _asarray_from_numpy_ndarray( if dtype is None: # deduce device-representable output data type dtype = _map_to_device_dtype(ary.dtype, copy_q) + _ensure_native_dtype_device_support(dtype, copy_q.sycl_device) f_contig = ary.flags["F"] c_contig = ary.flags["C"] fc_contig = f_contig or c_contig @@ -245,10 +246,8 @@ def _asarray_from_numpy_ndarray( order = "C" if c_contig else "F" if order == "K": # new USM allocation - _ensure_native_dtype_device_support(dtype, copy_q.sycl_device) res = _from_numpy_empty_like_orderK(ary, dtype, usm_type, copy_q) else: - _ensure_native_dtype_device_support(dtype, copy_q.sycl_device) res = dpt.usm_ndarray( ary.shape, dtype=dtype, From 5b4ae4ad61250c7f7fef90923b41eefdaad7173e Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Wed, 23 Apr 2025 14:36:15 -0700 Subject: [PATCH 5/6] np.ndarray->numpy.ndarray in error text --- dpctl/tensor/_copy_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dpctl/tensor/_copy_utils.py b/dpctl/tensor/_copy_utils.py index 3726179744..3674f888a1 100644 --- a/dpctl/tensor/_copy_utils.py +++ b/dpctl/tensor/_copy_utils.py @@ -413,7 +413,7 @@ def _from_numpy_empty_like_orderK(X, dt, usm_type, dev): strides as `x`. """ if not isinstance(X, np.ndarray): - raise TypeError(f"Expected np.ndarray, got {type(X)}") + raise TypeError(f"Expected numpy.ndarray, got {type(X)}") fl = X.flags if fl["C"] or X.size <= 1: return dpt.empty( From 152a3eda820136362bf390ff848c2f42a667cc37 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Wed, 23 Apr 2025 14:38:16 -0700 Subject: [PATCH 6/6] change variable names throughout _copy_utils more consistent with style --- dpctl/tensor/_copy_utils.py | 54 ++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/dpctl/tensor/_copy_utils.py b/dpctl/tensor/_copy_utils.py index 3674f888a1..c80733f77f 100644 --- a/dpctl/tensor/_copy_utils.py +++ b/dpctl/tensor/_copy_utils.py @@ -349,19 +349,19 @@ def _copy_from_usm_ndarray_to_usm_ndarray(dst, src): _copy_same_shape(dst, src_same_shape) -def _make_empty_like_orderK(X, dt, usm_type, dev): +def _make_empty_like_orderK(x, dt, usm_type, dev): """ - Returns empty array with shape and strides like `X`, with dtype `dt`, + Returns empty array with shape and strides like `x`, with dtype `dt`, USM type `usm_type`, on device `dev`. """ - st = list(X.strides) + st = list(x.strides) perm = sorted( - range(X.ndim), - key=lambda d: builtins.abs(st[d]) if X.shape[d] > 1 else 0, + range(x.ndim), + key=lambda d: builtins.abs(st[d]) if x.shape[d] > 1 else 0, reverse=True, ) - inv_perm = sorted(range(X.ndim), key=lambda i: perm[i]) - sh = X.shape + inv_perm = sorted(range(x.ndim), key=lambda i: perm[i]) + sh = x.shape sh_sorted = tuple(sh[i] for i in perm) R = dpt.empty(sh_sorted, dtype=dt, usm_type=usm_type, device=dev, order="C") if min(st) < 0: @@ -372,13 +372,13 @@ def _make_empty_like_orderK(X, dt, usm_type, dev): if st_sorted[i] < 0 else slice(None, None, None) ) - for i in range(X.ndim) + for i in range(x.ndim) ) R = R[sl] return dpt.permute_dims(R, inv_perm) -def _empty_like_orderK(X, dt, usm_type=None, dev=None): +def _empty_like_orderK(x, dt, usm_type=None, dev=None): """ Returns empty array like `x`, using order='K' @@ -386,25 +386,25 @@ def _empty_like_orderK(X, dt, usm_type=None, dev=None): array the returned array will have the same shape and the same strides as `x`. """ - if not isinstance(X, dpt.usm_ndarray): - raise TypeError(f"Expected usm_ndarray, got {type(X)}") + if not isinstance(x, dpt.usm_ndarray): + raise TypeError(f"Expected usm_ndarray, got {type(x)}") if usm_type is None: - usm_type = X.usm_type + usm_type = x.usm_type if dev is None: - dev = X.device - fl = X.flags - if fl["C"] or X.size <= 1: + dev = x.device + fl = x.flags + if fl["C"] or x.size <= 1: return dpt.empty_like( - X, dtype=dt, usm_type=usm_type, device=dev, order="C" + x, dtype=dt, usm_type=usm_type, device=dev, order="C" ) elif fl["F"]: return dpt.empty_like( - X, dtype=dt, usm_type=usm_type, device=dev, order="F" + x, dtype=dt, usm_type=usm_type, device=dev, order="F" ) - return _make_empty_like_orderK(X, dt, usm_type, dev) + return _make_empty_like_orderK(x, dt, usm_type, dev) -def _from_numpy_empty_like_orderK(X, dt, usm_type, dev): +def _from_numpy_empty_like_orderK(x, dt, usm_type, dev): """ Returns empty usm_ndarray like NumPy array `x`, using order='K' @@ -412,18 +412,18 @@ def _from_numpy_empty_like_orderK(X, dt, usm_type, dev): array the returned array will have the same shape and the same strides as `x`. """ - if not isinstance(X, np.ndarray): - raise TypeError(f"Expected numpy.ndarray, got {type(X)}") - fl = X.flags - if fl["C"] or X.size <= 1: + if not isinstance(x, np.ndarray): + raise TypeError(f"Expected numpy.ndarray, got {type(x)}") + fl = x.flags + if fl["C"] or x.size <= 1: return dpt.empty( - X.shape, dtype=dt, usm_type=usm_type, device=dev, order="C" + x.shape, dtype=dt, usm_type=usm_type, device=dev, order="C" ) elif fl["F"]: return dpt.empty( - X.shape, dtype=dt, usm_type=usm_type, device=dev, order="F" + x.shape, dtype=dt, usm_type=usm_type, device=dev, order="F" ) - return _make_empty_like_orderK(X, dt, usm_type, dev) + return _make_empty_like_orderK(x, dt, usm_type, dev) def _empty_like_pair_orderK(X1, X2, dt, res_shape, usm_type, dev): @@ -763,7 +763,7 @@ def _extract_impl(ary, ary_mask, axis=0): if exec_q is None: raise dpctl.utils.ExecutionPlacementError( "arrays have different associated queues. " - "Use `Y.to_device(X.device)` to migrate." + "Use `y.to_device(x.device)` to migrate." ) ary_nd = ary.ndim pp = normalize_axis_index(operator.index(axis), ary_nd)