Skip to content
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ This release is compatible with NumPy 2.5.
* Fixed `__array_namespace_info__().devices()` and `.default_device()` to return Python array API compatible device objects [#2979](https://github.com/IntelPython/dpnp/pull/2979)
* Fixed `dpnp.interp` with an empty input array `x` to return an empty array with the correct dtype [#2985](https://github.com/IntelPython/dpnp/pull/2985)
* Fixed `dpnp.interp` returning `nan` when querying at an exact knot point whose adjacent `fp` value is `inf` [#2986](https://github.com/IntelPython/dpnp/pull/2986)
* Fixed missing strides validation in `dpnp.tensor.usm_ndarray` constructor when allocating new memory [#2927](https://github.com/IntelPython/dpnp/pull/2927)

### Security

Expand Down
49 changes: 31 additions & 18 deletions dpnp/tensor/_usmarray.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -378,20 +378,19 @@ cdef class usm_ndarray:
if (typenum < 0):
if typenum == -2:
raise ValueError(
"Data type '" + str(dtype) +
"' can only have native byteorder."
f"Data type '{dtype}' can only have native byteorder."
)
elif typenum == -1:
raise ValueError(
"Data type '" + str(dtype) + "' is not understood."
f"Data type '{dtype}' is not understood."
)
raise TypeError(
f"Expected string or a dtype object, got {type(dtype)}"
)
itemsize = type_bytesize(typenum)
if (itemsize < 1):
raise TypeError(
"dtype=" + np.dtype(dtype).name + " is not supported."
f"dtype={np.dtype(dtype).name} is not supported."
)
# allocate host C-arrays for shape, strides
err = _from_input_shape_strides(
Expand All @@ -406,11 +405,11 @@ cdef class usm_ndarray:
"array failed.")
elif err == ERROR_INCORRECT_ORDER:
raise ValueError(
"Unsupported order='{}' given. "
"Supported values are 'C' or 'F'.".format(order))
f"Unsupported order='{order}' given. "
"Supported values are 'C' or 'F'.")
elif err == ERROR_UNEXPECTED_STRIDES:
raise ValueError(
"strides={} is not understood".format(strides))
f"strides={strides} is not understood")
else:
raise InternalUSMArrayError(
" .. while processing shape and strides.")
Expand All @@ -421,6 +420,21 @@ cdef class usm_ndarray:
elif isinstance(buffer, (str, bytes)):
if isinstance(buffer, bytes):
buffer = buffer.decode("UTF-8")
if strides is not None and ary_min_displacement < 0:
self._cleanup()
raise ValueError(
f"strides={strides} result in a negative memory "
"displacement and are not allowed when allocating "
"new memory")
if strides is not None and (
(ary_max_displacement - ary_min_displacement + 1) > ary_nelems
):
self._cleanup()
raise ValueError(
f"strides={strides} is incompatible with "
f"shape={shape} when allocating new memory because "
"the memory footprint exceeds the number of elements"
)
_offset = -ary_min_displacement
if (buffer == "shared"):
_buffer = dpmem.MemoryUSMShared(ary_nbytes,
Expand All @@ -434,24 +448,24 @@ cdef class usm_ndarray:
else:
self._cleanup()
raise ValueError(
"buffer='{}' is not understood. "
f"buffer='{buffer}' is not understood. "
"Recognized values are 'device', 'shared', 'host', "
"an instance of `MemoryUSM*` object, or a usm_ndarray"
"".format(buffer)
)
elif isinstance(buffer, usm_ndarray):
if not buffer.flags.writable:
writable_flag = 0
_buffer = buffer.usm_data
else:
self._cleanup()
raise ValueError("buffer='{}' was not understood.".format(buffer))
raise ValueError(f"buffer='{buffer}' was not understood.")
if (shape_to_elem_count(nd, shape_ptr) > 0 and
(_offset + ary_min_displacement < 0 or
(_offset + ary_max_displacement + 1) * itemsize > _buffer.nbytes)):
self._cleanup()
raise ValueError(("buffer='{}' can not accommodate "
"the requested array.").format(buffer))
raise ValueError(
f"buffer='{buffer}' can not accommodate the requested array."
)
is_fp64 = (typenum == UAR_DOUBLE or typenum == UAR_CDOUBLE)
is_fp16 = (typenum == UAR_HALF)
if (is_fp64 or is_fp16):
Expand Down Expand Up @@ -640,9 +654,8 @@ cdef class usm_ndarray:
if (not isinstance(self.base_, dpmem._memory._Memory)):
raise InternalUSMArrayError(
"Invalid instance of usm_ndarray encountered. "
"Private field base_ has an unexpected type {}.".format(
type(self.base_)
)
"Private field base_ has an unexpected type "
f"{type(self.base_)}."
)
ary_iface = self.base_.__sycl_usm_array_interface__
mem_ptr = <char *>(<size_t> ary_iface["data"][0])
Expand Down Expand Up @@ -803,7 +816,7 @@ cdef class usm_ndarray:
self.strides_ = strides_ptr
else:
raise InternalUSMArrayError(
"Encountered in shape setter, error code {err}".format(err)
f"Encountered in shape setter, error code {err}"
)

@property
Expand Down Expand Up @@ -1905,7 +1918,7 @@ cdef api object UsmNDArray_MakeSimpleFromPtr(
cdef int itemsize = type_bytesize(typenum)
if (itemsize < 1):
raise ValueError(
"dtype with typenum=" + str(typenum) + " is not supported."
f"dtype with typenum={typenum} is not supported."
)
cdef size_t nbytes = (<size_t> itemsize) * nelems
cdef c_dpmem._Memory mobj
Expand Down Expand Up @@ -1961,7 +1974,7 @@ cdef api object UsmNDArray_MakeFromPtr(

if (itemsize < 1):
raise ValueError(
"dtype with typenum=" + str(typenum) + " is not supported."
f"dtype with typenum={typenum} is not supported."
)
if (nd < 0):
raise ValueError("Dimensionality must be non-negative")
Expand Down
19 changes: 18 additions & 1 deletion dpnp/tests/tensor/test_usm_ndarray_ctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def test_usm_ndarray_flags():
assert f.fc
assert f.forc
assert not dpt.usm_ndarray(
(5, 1, 1), dtype="i4", strides=(2, 0, 1)
(2, 3, 4), dtype="i4", strides=(4, 8, 1)
).flags.forc

x = dpt.empty(5, dtype="u2")
Expand Down Expand Up @@ -1085,6 +1085,23 @@ def test_ctor_invalid():
dpt.usm_ndarray((4,), dtype="u1", buffer=m, strides={"not": "valid"})


def test_ctor_invalid_strides():
try:
dpt.usm_ndarray((1,), dtype="i4")
except dpctl.SyclDeviceCreationError:
pytest.skip("No SYCL devices available")
# negative displacement
with pytest.raises(
ValueError, match="result in a negative memory displacement"
):
dpt.usm_ndarray((2, 3, 4), dtype="i4", strides=(-1, 1, 1))
# oversized memory footprint
with pytest.raises(
ValueError, match="memory footprint exceeds the number of elements"
):
dpt.usm_ndarray((2, 3, 4), dtype="i4", strides=(1, 16, 128))


def test_reshape():
try:
X = dpt.usm_ndarray((5, 5), "i4")
Expand Down
19 changes: 8 additions & 11 deletions dpnp/tests/third_party/cupy/creation_tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,29 +272,26 @@ def test_ones_like_subok(self):
@pytest.mark.parametrize(
"shape, strides",
[
((2, 3, 4), (8 * 3 * 4, 8 * 4, 8)), # contiguous
((2, 3, 4), (8, 0, 8)), # smaller than contiguous needed
((2, 0, 4), (8, 128, 1024)), # empty can be OK
((2, 3, 4), (4 * 3 * 4, 4 * 4, 4)), # contiguous
((2, 3, 4), (4, 0, 4)), # smaller than contiguous needed
((2, 0, 4), (4, 64, 512)), # empty can be OK
],
)
def test_ndarray_strides(self, shape, strides):
Comment thread
vlad-perevezentsev marked this conversation as resolved.
a = cupy.ndarray(
shape, strides=strides, dtype=cupy.default_float_type()
)
a = cupy.ndarray(shape, strides=strides, dtype=cupy.float32)
assert cupy.byte_bounds(a)[0] == a.data.ptr
assert cupy.byte_bounds(a)[1] - a.data.ptr <= a.data.size

@pytest.mark.skip("due to dpctl-2239")
@pytest.mark.parametrize(
"shape, strides",
[
((2, 3, 4), (8, 128, 1024)), # too large
((2, 3, 4), (-8, 8, 8)), # negative (needs offset)
((2, 3, 4), (4, 512, 4096)), # too large
((2, 3, 4), (-4, 4, 4)), # negative (needs offset)
],
)
def test_ndarray_strides_raises(self, shape, strides):
with pytest.raises(ValueError, match=r"ndarray\(\) with strides.*"):
cupy.ndarray(shape, strides=strides)
with pytest.raises(ValueError):
cupy.ndarray(shape, strides=strides, dtype=cupy.float32)

@testing.for_CF_orders()
@testing.for_all_dtypes()
Expand Down
Loading