Skip to content
26 changes: 20 additions & 6 deletions dpnp/tests/third_party/cupy/core_tests/test_cub_reduction.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,22 @@ def _test_can_use(self, i_shape, o_shape, r_axis, o_axis, order, expected):
assert result is expected


@pytest.mark.parametrize("shape", [(2,), (2, 3), (2, 3, 4), (2, 3, 4, 5)])
@pytest.mark.parametrize("order", ["C", "F"])
_MIN_SIZE = cupy._core._cub_reduction._CUB_REDUCE_SIZE_THRESHOLD


@pytest.mark.parametrize(
"shape",
[
(_MIN_SIZE,),
(_MIN_SIZE, _MIN_SIZE + 1),
(_MIN_SIZE, 3, _MIN_SIZE + 1),
(_MIN_SIZE, 3, 4, _MIN_SIZE + 1),
],
)
@pytest.mark.parametrize(
"order",
["C", "F"],
)
class TestSimpleCubReductionKernelContiguity(CubReductionTestBase):

@testing.for_contiguous_axes()
Expand Down Expand Up @@ -139,15 +153,15 @@ def test_can_use_cub_oversize_input4(self):
b = cupy.empty((), dtype=cupy.int8)
assert self.can_use([a], [b], (1,), (0,)) is None

# thread_unsafe marker requires pytest-run-parallel, not used by dpnp
# @pytest.mark.thread_unsafe(
# reason="AssertFunctionIsCalled and accelerate mutation.")
@pytest.mark.thread_unsafe(
reason="AssertFunctionIsCalled and accelerate mutation."
)
def test_can_use_accelerator_set_unset(self):
# ensure we use CUB block reduction and not CUB device reduction
old_routine_accelerators = _accelerator.get_routine_accelerators()
_accelerator.set_routine_accelerators([])

a = cupy.random.random((10, 10))
a = cupy.random.random((10, _cub_reduction._CUB_REDUCE_SIZE_THRESHOLD))
# this is the only function we can mock; the rest is cdef'd
func_name = "".join(
(
Expand Down
85 changes: 85 additions & 0 deletions dpnp/tests/third_party/cupy/core_tests/test_dlpack.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import ctypes

import dpctl
import numpy
import pytest
Expand All @@ -8,6 +10,12 @@
import dpnp.tensor._dlpack as dlp
from dpnp.tests.third_party.cupy import testing

ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [
ctypes.py_object,
ctypes.c_char_p,
]


# TODO: to roll back the changes once the issue with CUDA support is resolved for random
def _gen_array(dtype, alloc_q=None):
Expand All @@ -29,6 +37,32 @@ def _gen_array(dtype, alloc_q=None):
return cupy.asarray(array, sycl_queue=alloc_q).astype(dtype)


def _inspect_dlpack(capsule):
ptr = ctypes.pythonapi.PyCapsule_GetPointer(capsule, b"dltensor")
managed = ctypes.cast(ptr, ctypes.POINTER(DLManagedTensor)).contents
tensor = managed.dl_tensor

shape = [tensor.shape[i] for i in range(tensor.ndim)]
strides = None
if tensor.strides:
strides = [tensor.strides[i] for i in range(tensor.ndim)]

return {
"data": tensor.data,
"device_type": tensor.device.device_type,
"device_id": tensor.device.device_id,
"ndim": tensor.ndim,
"dtype": {
"code": tensor.dtype.code,
"bits": tensor.dtype.bits,
"lanes": tensor.dtype.lanes,
},
"shape": shape,
"strides": strides,
"byte_offset": tensor.byte_offset,
}


class DLDummy:
"""Dummy object to wrap a __dlpack__ capsule, so we can use from_dlpack."""

Expand All @@ -43,6 +77,41 @@ def __dlpack_device__(self):
return self.device


class DLDevice(ctypes.Structure):
_fields_ = [
("device_type", ctypes.c_int32),
("device_id", ctypes.c_int32),
]


class DLDataType(ctypes.Structure):
_fields_ = [
("code", ctypes.c_uint8),
("bits", ctypes.c_uint8),
("lanes", ctypes.c_uint16),
]


class DLTensor(ctypes.Structure):
_fields_ = [
("data", ctypes.c_void_p),
("device", DLDevice),
("ndim", ctypes.c_int32),
("dtype", DLDataType),
("shape", ctypes.POINTER(ctypes.c_int64)),
("strides", ctypes.POINTER(ctypes.c_int64)),
("byte_offset", ctypes.c_uint64),
]


class DLManagedTensor(ctypes.Structure):
_fields_ = [
("dl_tensor", DLTensor),
("manager_ctx", ctypes.c_void_p),
("deleter", ctypes.c_void_p),
]


@pytest.mark.skip("toDlpack() and fromDlpack() are not supported")
class TestDLPackConversion:

Expand Down Expand Up @@ -295,3 +364,19 @@ def test_multiple_consumption_error(self, recwarn):
assert "consumed multiple times" in str(e.value)
for w in recwarn:
assert issubclass(w.category, cupy.VisibleDeprecationWarning)


class TestDLTensorContent:
@pytest.fixture(scope="class")
@classmethod
def configure(cls):
arr = _gen_array("uint32")
arr_flip = cupy.transpose(cupy.flip(arr, axis=1), (1, 0))
info = _inspect_dlpack(arr_flip.__dlpack__())
yield arr_flip, info

def test_strides(self, configure):
arr, dlpack_interface = configure
array_strides = tuple(s // arr.itemsize for s in arr.strides)
dlpack_strides = tuple(dlpack_interface["strides"])
assert array_strides == dlpack_strides
110 changes: 101 additions & 9 deletions dpnp/tests/third_party/cupy/core_tests/test_ndarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,56 @@ class UserNdarray(cupy.ndarray):
b.custom_attr = 100


@testing.parameterize(
*testing.product(
{
"np_order": ["C", "F"],
"np_pinned": [False], # no pinned memory
"pinned_alloc_fails": [False], # no pinned memory
"cp_setup": [
("C", False, (48, 16, 4)),
("C", True, (16, 4)),
("F", False, (4, 8, 24)),
("F", True, (4, 8)),
],
}
)
)
class TestAsarray(unittest.TestCase):
# @pytest.mark.thread_unsafe(reason="mutates global pinned allocator.")
def test_asarray(self):
cp_order, view, strides = self.cp_setup
shape = (2, 3, 4)
if self.np_pinned:
count = numpy.prod(shape)
dtype = numpy.float32()
pinned_ptr = cupy.cuda.alloc_pinned_memory(count * dtype.itemsize)
a_cpu = numpy.frombuffer(
pinned_ptr, dtype=dtype, count=count
).reshape(shape)
else:
a_cpu = numpy.ndarray(
shape, dtype=numpy.float32, order=self.np_order
)
a_cpu[...] = numpy.arange(a_cpu.size).reshape(a_cpu.shape)
if view:
a_cpu = a_cpu[:, 1, :]
try:
if self.pinned_alloc_fails:
cupy.cuda.set_pinned_memory_allocator(lambda _: None)
a = cupy.asarray(a_cpu, order=cp_order)
finally:
# None means "no pool", not "the default pool"
# cupy.cuda.set_pinned_memory_allocator(
# cupy.get_default_pinned_memory_pool().malloc
# )
pass
assert a.flags.c_contiguous == (cp_order == "C")
assert a.flags.f_contiguous == (cp_order == "F")
assert a.strides == strides
testing.assert_array_equal(a_cpu, a)


@testing.parameterize(
*testing.product(
{
Expand Down Expand Up @@ -249,9 +299,11 @@ def test_copy_multi_device_non_contiguous_K(self):
@testing.multi_gpu(2)
# @pytest.mark.xfail(
# runtime.is_hip,
# reason='ROCm may work differently in async D2D copy with streams')
# reason="ROCm may work differently in async D2D copy with streams",
# )
# @pytest.mark.thread_unsafe(
# reason="order is unclear multithread. Also, hard crash in threaded!")
# reason="order is unclear multithread. Also, hard crash in threaded!"
# )
def test_copy_multi_device_with_stream(self):
# Kernel that takes long enough then finally writes values.
src = _test_copy_multi_device_with_stream_src
Expand All @@ -278,14 +330,16 @@ def test_copy_multi_device_with_stream(self):
)


@pytest.mark.filterwarnings(
# Shape setting is deprecated starting NumPy 2.5
"ignore::DeprecationWarning"
)
class TestNdarrayShape(unittest.TestCase):

@testing.with_requires("numpy>=2.5")
@testing.numpy_cupy_array_equal()
def test_shape_set(self, xp):
arr = xp.ndarray((2, 3))
with testing.assert_warns(DeprecationWarning):
arr.shape = (3, 2)
arr.shape = (3, 2)
return xp.array(arr.shape)

@pytest.mark.skip(
Expand All @@ -298,15 +352,12 @@ def test_shape_set_infer(self, xp):
arr.shape = (3, -1)
return xp.array(arr.shape)

@testing.with_requires("numpy>=2.5")
@testing.numpy_cupy_array_equal()
def test_shape_set_int(self, xp):
arr = xp.ndarray((2, 3))
with testing.assert_warns(DeprecationWarning):
arr.shape = 6
arr.shape = 6
return xp.array(arr.shape)

@pytest.mark.filterwarnings("ignore::DeprecationWarning")
def test_shape_need_copy(self):
# from cupy/cupy#5470
for xp in (numpy, cupy):
Expand Down Expand Up @@ -563,6 +614,47 @@ def test_shape_mismatch(self):
wrap_take(a, i, out=o)


@testing.parameterize(
{"shape": (3, 4, 5), "indices": (2, 3), "out_shape": (2, 3)},
{"shape": (), "indices": (), "out_shape": ()},
)
class TestNdarrayTakeTypeMismatch(unittest.TestCase):
# NOTE(seberg): Historically cupy was always fully restrictive
# while NumPy was just wrong: https://github.com/numpy/numpy/pull/30615
# As of CuPy 14.2, CuPy uses 2.5+ (future) behavior.

@testing.with_requires("numpy>=2.5")
@testing.numpy_cupy_array_equal()
# incorrectly given by numpy (presumably until Deprecation is finalized)
@pytest.mark.filterwarnings("ignore::numpy.exceptions.ComplexWarning")
def test_output_dtype_same_kind_ok(self, xp):
# After NumPy 2.5, NumPy gets the cast safety right, the following
# is OK under same-kind casting rules.
a = testing.shaped_arange(self.shape, xp, numpy.int64)
i = testing.shaped_arange(self.indices, xp, numpy.int32) % 3
results = []
for out_dtype in (numpy.complex64, numpy.int32):
o = testing.shaped_arange(self.out_shape, xp, out_dtype)
results.append(wrap_take(a, i, out=o))
return results

@pytest.mark.skip()
@pytest.mark.filterwarnings(
"error:Implicit casting of output dtype:DeprecationWarning"
)
def test_output_dtype_unsafe_rejected(self):
for xp in (numpy, cupy):
a = testing.shaped_arange(self.shape, xp, numpy.float32)
i = testing.shaped_arange(self.indices, xp, numpy.int32) % 3
o = testing.shaped_arange(self.out_shape, xp, numpy.int32)
# As of NumPy 2.5 this is a deprecation warning, but CuPy never
# allowed it (so no deprecation required)
if xp is numpy and not testing.numpy_satisfies(">=2.5"):
continue
with pytest.warns((TypeError, DeprecationWarning)):
wrap_take(a, i, out=o)


@testing.parameterize(
{"shape": (0,), "indices": (0,), "axis": None},
{"shape": (0,), "indices": (0, 1), "axis": None},
Expand Down
27 changes: 12 additions & 15 deletions dpnp/tests/third_party/cupy/core_tests/test_ndarray_reduction.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import pytest

import dpnp as cupy

# import cupy._core._accelerator as _acc
# from cupy._core import _cub_reduction
from dpnp.tests.third_party.cupy import testing


Expand Down Expand Up @@ -34,11 +37,10 @@ def exclude_cutensor(cls):
# pass
# _acc.set_reduction_accelerators(red_acc)

# yield
yield

# _acc.set_routine_accelerators(old_routine_accelerators)
# _acc.set_reduction_accelerators(old_reduction_accelerators)
pass

@testing.for_all_dtypes()
@testing.numpy_cupy_allclose(contiguous_check=False)
Expand Down Expand Up @@ -298,15 +300,7 @@ def _axes_for_shape(shape):
"shape,axis",
[
(shape, axis)
for shape in [
(),
(0,),
(0, 2),
(2, 0),
(0, 2, 3),
(2, 0, 3),
(2, 3, 0),
]
for shape in [(), (0,), (0, 2), (2, 0), (0, 2, 3), (2, 0, 3), (2, 3, 0)]
for axis in _axes_for_shape(shape)
],
)
Expand All @@ -331,13 +325,16 @@ def test_zero_size(self, xp, shape, axis, order, func):

# This class compares CUB results against NumPy's. ("fallback" is CuPy's
# original kernel, also tested here to reduce code duplication.)
# Non-empty shapes keep both the first and last axis >= 128 so the
# contiguous reduction stays on the CUB block-reduction path rather than
# the short-axis fallback.
@pytest.mark.parametrize(
"shape",
[
(10,),
(10, 20),
(10, 20, 30),
(10, 20, 30, 40),
(128,),
(128, 128),
(128, 2, 128),
(128, 2, 2, 128),
# skip (2, 3, 0) because it would not hit the CUB code path
(0,),
(2, 0),
Expand Down
Loading
Loading