diff --git a/dpctl/tensor/_copy_utils.py b/dpctl/tensor/_copy_utils.py index a2189defe7..c80733f77f 100644 --- a/dpctl/tensor/_copy_utils.py +++ b/dpctl/tensor/_copy_utils.py @@ -349,36 +349,19 @@ def _copy_from_usm_ndarray_to_usm_ndarray(dst, src): _copy_same_shape(dst, src_same_shape) -def _empty_like_orderK(X, dt, usm_type=None, dev=None): - """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 - strides as `x`. +def _make_empty_like_orderK(x, dt, usm_type, dev): """ - 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 - if dev is None: - 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" - ) - elif fl["F"]: - return dpt.empty_like( - X, dtype=dt, usm_type=usm_type, device=dev, order="F" - ) - st = list(X.strides) + 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), - 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: @@ -389,12 +372,60 @@ def _empty_like_orderK(X, dt, usm_type=None, dev=None): 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): + """ + 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 + strides as `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 + if dev is None: + 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" + ) + elif fl["F"]: + return dpt.empty_like( + x, dtype=dt, usm_type=usm_type, device=dev, order="F" + ) + 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 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" + ) + 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): if not isinstance(X1, dpt.usm_ndarray): raise TypeError(f"Expected usm_ndarray, got {type(X1)}") @@ -732,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) diff --git a/dpctl/tensor/_ctors.py b/dpctl/tensor/_ctors.py index ecdba971e2..afd1ca8a25 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 @@ -233,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 @@ -242,27 +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 = 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( ary.shape, dtype=dtype, 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"]