From f00eda8650b9b0901417d39fa6e9136bcb250b0f Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Wed, 25 Mar 2026 13:39:01 +0100 Subject: [PATCH 1/4] Specify rtol in explog tests to consider a case when output arrays has float16 dtype --- dpnp/tests/third_party/cupy/math_tests/test_explog.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/dpnp/tests/third_party/cupy/math_tests/test_explog.py b/dpnp/tests/third_party/cupy/math_tests/test_explog.py index 2d4b539d1fb4..03a90155197a 100644 --- a/dpnp/tests/third_party/cupy/math_tests/test_explog.py +++ b/dpnp/tests/third_party/cupy/math_tests/test_explog.py @@ -6,9 +6,14 @@ class TestExplog: + # rtol=1e-3 is used to pass the test when dtype is int8/unint8 + # for such a case, output dtype is float16 + _rtol_dict = {numpy.float16: 1e-3, "default": 1e-7} @testing.for_all_dtypes() - @testing.numpy_cupy_allclose(atol=1e-5, type_check=has_support_aspect64()) + @testing.numpy_cupy_allclose( + rtol=_rtol_dict, atol=1e-5, type_check=has_support_aspect64() + ) def check_unary(self, name, xp, dtype, no_complex=False): if no_complex: if numpy.dtype(dtype).kind == "c": @@ -16,11 +21,9 @@ def check_unary(self, name, xp, dtype, no_complex=False): a = testing.shaped_arange((2, 3), xp, dtype) return getattr(xp, name)(a) - # rtol=1e-3 is added for dpnp to pass the test when dtype is int8/unint8 - # for such a case, output dtype is float16 @testing.for_all_dtypes() @testing.numpy_cupy_allclose( - rtol=1e-3, atol=1e-5, type_check=has_support_aspect64() + rtol=_rtol_dict, atol=1e-5, type_check=has_support_aspect64() ) def check_binary(self, name, xp, dtype, no_complex=False): if no_complex: From dbb7199a0fc56715ea0f92d0bae05789f243fff9 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Wed, 25 Mar 2026 13:44:39 +0100 Subject: [PATCH 2/4] Specify rtol in hyperbolic tests to consider a case when output arrays has float16 dtype --- .../third_party/cupy/math_tests/test_hyperbolic.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/dpnp/tests/third_party/cupy/math_tests/test_hyperbolic.py b/dpnp/tests/third_party/cupy/math_tests/test_hyperbolic.py index 5613cee41589..3f0f89d66d70 100644 --- a/dpnp/tests/third_party/cupy/math_tests/test_hyperbolic.py +++ b/dpnp/tests/third_party/cupy/math_tests/test_hyperbolic.py @@ -7,10 +7,14 @@ class TestHyperbolic(unittest.TestCase): + # rtol=1e-2 is used to pass the test when dtype is int8/unint8 + # for such a case, output dtype is float16 + _rtol_dict = {numpy.float16: 1e-2, "default": 1e-7} @testing.for_all_dtypes() @testing.numpy_cupy_allclose( - atol={numpy.float16: 1e-3, "default": 1e-5}, + rtol=_rtol_dict, + atol=1e-5, type_check=has_support_aspect64(), ) def check_unary(self, name, xp, dtype): @@ -18,7 +22,7 @@ def check_unary(self, name, xp, dtype): return getattr(xp, name)(a) @testing.for_dtypes(["e", "f", "d"]) - @testing.numpy_cupy_allclose(atol={numpy.float16: 1e-3, "default": 1e-5}) + @testing.numpy_cupy_allclose(rtol=_rtol_dict, atol=1e-5) def check_unary_unit(self, name, xp, dtype): a = xp.array([0.2, 0.4, 0.6, 0.8], dtype=dtype) return getattr(xp, name)(a) @@ -36,7 +40,7 @@ def test_arcsinh(self): self.check_unary("arcsinh") @testing.for_dtypes(["e", "f", "d"]) - @testing.numpy_cupy_allclose(atol={numpy.float16: 1e-3, "default": 1e-5}) + @testing.numpy_cupy_allclose(rtol=_rtol_dict, atol=1e-5) def test_arccosh(self, xp, dtype): a = xp.array([1, 2, 3], dtype=dtype) return xp.arccosh(a) From 9b108ec00acc8570d0d600fab6d64695615bf850 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Wed, 25 Mar 2026 13:47:40 +0100 Subject: [PATCH 3/4] Update interp tests to consider when xp.sin(fx) uses float16 precision --- .../third_party/cupy/math_tests/test_misc.py | 66 +++++++++++++++---- dpnp/tests/third_party/cupy/testing/_loops.py | 47 ++++++++----- 2 files changed, 84 insertions(+), 29 deletions(-) diff --git a/dpnp/tests/third_party/cupy/math_tests/test_misc.py b/dpnp/tests/third_party/cupy/math_tests/test_misc.py index e2f12ae373a6..dcc3c4017c6b 100644 --- a/dpnp/tests/third_party/cupy/math_tests/test_misc.py +++ b/dpnp/tests/third_party/cupy/math_tests/test_misc.py @@ -9,6 +9,27 @@ class TestMisc: + @staticmethod + def _interp_atol(_result_dtype, dtype_x=None, **_kwargs): + """Compute absolute tolerance based on intermediate computation dtype. + + Args: + _result_dtype: Output dtype (unused - we check input dtype instead) + dtype_x: Input dtype for fx coordinates + _kwargs: Additional test parameters (unused) + + When dtype_x is int8/uint8/float16, xp.sin(fx) uses float16 precision, + so we need relaxed tolerance even if the final result is upcasted to float64. + Float16 has ~3 decimal digits of precision, hence atol=1e-3. + """ + if dtype_x is not None: + if numpy.dtype(dtype_x).type in ( + numpy.int8, + numpy.uint8, + numpy.float16, + ): + return 1e-3 + return 1e-5 @testing.for_all_dtypes() @testing.numpy_cupy_allclose(atol=1e-5, type_check=has_support_aspect64()) @@ -401,9 +422,12 @@ def test_real_if_close_with_float_tol_false(self, xp, dtype): @testing.for_all_dtypes(name="dtype_x", no_bool=True, no_complex=True) @testing.for_all_dtypes(name="dtype_y", no_bool=True) - @testing.numpy_cupy_allclose(atol=1e-5, type_check=has_support_aspect64()) + @testing.numpy_cupy_allclose( + atol=_interp_atol, type_check=has_support_aspect64() + ) def test_interp(self, xp, dtype_y, dtype_x): # interpolate at points on and outside the boundaries + # tolerance is automatically adjusted based on dtype_x via resolver x = xp.asarray([0, 1, 2, 4, 6, 8, 9, 10], dtype=dtype_x) fx = xp.asarray([1, 3, 5, 7, 9], dtype=dtype_x) fy = xp.sin(fx).astype(dtype_y) @@ -411,7 +435,9 @@ def test_interp(self, xp, dtype_y, dtype_x): @testing.for_all_dtypes(name="dtype_x", no_bool=True, no_complex=True) @testing.for_all_dtypes(name="dtype_y", no_bool=True) - @testing.numpy_cupy_allclose(atol=1e-5, type_check=has_support_aspect64()) + @testing.numpy_cupy_allclose( + atol=_interp_atol, type_check=has_support_aspect64() + ) def test_interp_period(self, xp, dtype_y, dtype_x): # interpolate at points on and outside the boundaries x = xp.asarray([0, 1, 2, 4, 6, 8, 9, 10], dtype=dtype_x) @@ -421,7 +447,9 @@ def test_interp_period(self, xp, dtype_y, dtype_x): @testing.for_all_dtypes(name="dtype_x", no_bool=True, no_complex=True) @testing.for_all_dtypes(name="dtype_y", no_bool=True) - @testing.numpy_cupy_allclose(atol=1e-5, type_check=has_support_aspect64()) + @testing.numpy_cupy_allclose( + atol=_interp_atol, type_check=has_support_aspect64() + ) def test_interp_left_right(self, xp, dtype_y, dtype_x): # interpolate at points on and outside the boundaries x = xp.asarray([0, 1, 2, 4, 6, 8, 9, 10], dtype=dtype_x) @@ -434,7 +462,9 @@ def test_interp_left_right(self, xp, dtype_y, dtype_x): @testing.with_requires("numpy>=1.17.0") @testing.for_all_dtypes(name="dtype_x", no_bool=True, no_complex=True) @testing.for_dtypes("efdFD", name="dtype_y") - @testing.numpy_cupy_allclose(atol=1e-5, type_check=has_support_aspect64()) + @testing.numpy_cupy_allclose( + atol=_interp_atol, type_check=has_support_aspect64() + ) def test_interp_nan_fy(self, xp, dtype_y, dtype_x): # interpolate at points on and outside the boundaries x = xp.asarray([0, 1, 2, 4, 6, 8, 9, 10], dtype=dtype_x) @@ -446,7 +476,9 @@ def test_interp_nan_fy(self, xp, dtype_y, dtype_x): @testing.with_requires("numpy>=1.17.0") @testing.for_float_dtypes(name="dtype_x") @testing.for_dtypes("efdFD", name="dtype_y") - @testing.numpy_cupy_allclose(atol=1e-5, type_check=has_support_aspect64()) + @testing.numpy_cupy_allclose( + atol=_interp_atol, type_check=has_support_aspect64() + ) def test_interp_nan_fx(self, xp, dtype_y, dtype_x): # interpolate at points on and outside the boundaries x = xp.asarray([0, 1, 2, 4, 6, 8, 9, 10], dtype=dtype_x) @@ -458,7 +490,9 @@ def test_interp_nan_fx(self, xp, dtype_y, dtype_x): @testing.with_requires("numpy>=1.17.0") @testing.for_float_dtypes(name="dtype_x") @testing.for_dtypes("efdFD", name="dtype_y") - @testing.numpy_cupy_allclose(atol=1e-5, type_check=has_support_aspect64()) + @testing.numpy_cupy_allclose( + atol=_interp_atol, type_check=has_support_aspect64() + ) def test_interp_nan_x(self, xp, dtype_y, dtype_x): # interpolate at points on and outside the boundaries x = xp.asarray([0, 1, 2, 4, 6, 8, 9, 10], dtype=dtype_x) @@ -470,7 +504,9 @@ def test_interp_nan_x(self, xp, dtype_y, dtype_x): @testing.with_requires("numpy>=1.17.0") @testing.for_all_dtypes(name="dtype_x", no_bool=True, no_complex=True) @testing.for_dtypes("efdFD", name="dtype_y") - @testing.numpy_cupy_allclose(atol=1e-5, type_check=has_support_aspect64()) + @testing.numpy_cupy_allclose( + atol=_interp_atol, type_check=has_support_aspect64() + ) def test_interp_inf_fy(self, xp, dtype_y, dtype_x): # interpolate at points on and outside the boundaries x = xp.asarray([0, 1, 2, 4, 6, 8, 9, 10], dtype=dtype_x) @@ -482,7 +518,9 @@ def test_interp_inf_fy(self, xp, dtype_y, dtype_x): @testing.with_requires("numpy>=1.17.0") @testing.for_float_dtypes(name="dtype_x") @testing.for_dtypes("efdFD", name="dtype_y") - @testing.numpy_cupy_allclose(atol=1e-5, type_check=has_support_aspect64()) + @testing.numpy_cupy_allclose( + atol=_interp_atol, type_check=has_support_aspect64() + ) def test_interp_inf_fx(self, xp, dtype_y, dtype_x): # interpolate at points on and outside the boundaries x = xp.asarray([0, 1, 2, 4, 6, 8, 9, 10], dtype=dtype_x) @@ -494,7 +532,9 @@ def test_interp_inf_fx(self, xp, dtype_y, dtype_x): @testing.with_requires("numpy>=1.17.0") @testing.for_float_dtypes(name="dtype_x") @testing.for_dtypes("efdFD", name="dtype_y") - @testing.numpy_cupy_allclose(atol=1e-5, type_check=has_support_aspect64()) + @testing.numpy_cupy_allclose( + atol=_interp_atol, type_check=has_support_aspect64() + ) def test_interp_inf_x(self, xp, dtype_y, dtype_x): # interpolate at points on and outside the boundaries x = xp.asarray([0, 1, 2, 4, 6, 8, 9, 10], dtype=dtype_x) @@ -505,7 +545,9 @@ def test_interp_inf_x(self, xp, dtype_y, dtype_x): @testing.for_all_dtypes(name="dtype_x", no_bool=True, no_complex=True) @testing.for_all_dtypes(name="dtype_y", no_bool=True) - @testing.numpy_cupy_allclose(atol=1e-5, type_check=has_support_aspect64()) + @testing.numpy_cupy_allclose( + atol=_interp_atol, type_check=has_support_aspect64() + ) def test_interp_size1(self, xp, dtype_y, dtype_x): # interpolate at points on and outside the boundaries x = xp.asarray([0, 1, 2, 4, 6, 8, 9, 10], dtype=dtype_x) @@ -518,7 +560,9 @@ def test_interp_size1(self, xp, dtype_y, dtype_x): @testing.with_requires("numpy>=1.17.0") @testing.for_float_dtypes(name="dtype_x") @testing.for_dtypes("efdFD", name="dtype_y") - @testing.numpy_cupy_allclose(atol=1e-5, type_check=has_support_aspect64()) + @testing.numpy_cupy_allclose( + atol=_interp_atol, type_check=has_support_aspect64() + ) def test_interp_inf_to_nan(self, xp, dtype_y, dtype_x): # from NumPy's test_non_finite_inf x = xp.asarray([0.5], dtype=dtype_x) diff --git a/dpnp/tests/third_party/cupy/testing/_loops.py b/dpnp/tests/third_party/cupy/testing/_loops.py index 026c451e71e3..ee2072eab120 100644 --- a/dpnp/tests/third_party/cupy/testing/_loops.py +++ b/dpnp/tests/third_party/cupy/testing/_loops.py @@ -410,7 +410,7 @@ def test_func(*args, **kw): numpy_r = numpy_r[mask] if not skip: - check_func(cupy_r, numpy_r) + check_func(cupy_r, numpy_r, **kw) return test_func @@ -469,6 +469,9 @@ def _convert_output_to_ndarray(c_out, n_out, sp_name, check_sparse_format): def _check_tolerance_keys(rtol, atol): def _check(tol): + if callable(tol): + # Callable tolerance is allowed + return if isinstance(tol, dict): for k in tol.keys(): if type(k) is type: @@ -486,9 +489,13 @@ def _check(tol): _check(atol) -def _resolve_tolerance(type_check, result, rtol, atol): +def _resolve_tolerance(type_check, result, rtol, atol, **test_kwargs): def _resolve(dtype, tol): - if isinstance(tol, dict): + if callable(tol): + # Support callable tolerance that can inspect test kwargs + return tol(dtype, **test_kwargs) + elif isinstance(tol, dict): + # Original dict lookup logic tol1 = tol.get(dtype.type) if tol1 is None: tol1 = tol.get("default") @@ -523,13 +530,15 @@ def numpy_cupy_allclose( """Decorator that checks NumPy results and CuPy ones are close. Args: - rtol(float or dict): Relative tolerance. Besides a float value, a - dictionary that maps a dtypes to a float value can be supplied to - adjust tolerance per dtype. If the dictionary has ``'default'`` - string as its key, its value is used as the default tolerance in - case any dtype keys do not match. - atol(float or dict): Absolute tolerance. Besides a float value, a - dictionary can be supplied as ``rtol``. + rtol(float, dict, or callable): Relative tolerance. Can be: + - A float value + - A dictionary that maps dtypes to float values. If the dictionary + has ``'default'`` string as its key, its value is used as the + default tolerance in case any dtype keys do not match. + - A callable with signature ``(dtype, **test_kwargs)`` that returns + a float. This allows dynamic tolerance based on test parameters + like input dtypes. + atol(float, dict, or callable): Absolute tolerance. Same options as ``rtol``. err_msg(str): The error message to be printed in case of failure. verbose(bool): If ``True``, the conflicting values are appended to the error message. @@ -583,8 +592,10 @@ def numpy_cupy_allclose( # "must be supplied as float." # ) - def check_func(c, n): - rtol1, atol1 = _resolve_tolerance(type_check, c, rtol, atol) + def check_func(c, n, **test_kwargs): + rtol1, atol1 = _resolve_tolerance( + type_check, c, rtol, atol, **test_kwargs + ) _array.assert_allclose( c, n, rtol1, atol1, err_msg=err_msg, verbose=verbose ) @@ -641,7 +652,7 @@ def numpy_cupy_array_almost_equal( .. seealso:: :func:`cupy.testing.assert_array_almost_equal` """ - def check_func(x, y): + def check_func(x, y, **test_kwargs): _array.assert_array_almost_equal(x, y, decimal, err_msg, verbose) return _make_decorator( @@ -684,7 +695,7 @@ def numpy_cupy_array_almost_equal_nulp( .. seealso:: :func:`cupy.testing.assert_array_almost_equal_nulp` """ - def check_func(x, y): + def check_func(x, y, **test_kwargs): _array.assert_array_almost_equal_nulp(x, y, nulp) return _make_decorator( @@ -738,7 +749,7 @@ def numpy_cupy_array_max_ulp( """ - def check_func(x, y): + def check_func(x, y, **test_kwargs): _array.assert_array_max_ulp(x, y, maxulp, dtype) return _make_decorator( @@ -787,7 +798,7 @@ def numpy_cupy_array_equal( .. seealso:: :func:`cupy.testing.assert_array_equal` """ - def check_func(x, y): + def check_func(x, y, **test_kwargs): _array.assert_array_equal( x, y, err_msg, verbose, strides_check=strides_check ) @@ -826,7 +837,7 @@ def numpy_cupy_array_list_equal( DeprecationWarning, ) - def check_func(x, y): + def check_func(x, y, **test_kwargs): _array.assert_array_equal(x, y, err_msg, verbose) return _make_decorator( @@ -871,7 +882,7 @@ def numpy_cupy_array_less( .. seealso:: :func:`cupy.testing.assert_array_less` """ - def check_func(x, y): + def check_func(x, y, **test_kwargs): _array.assert_array_less(x, y, err_msg, verbose) return _make_decorator( From 2013d473bd54a3929de34397a2bb69ab8da624a0 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Wed, 25 Mar 2026 14:26:47 +0100 Subject: [PATCH 4/4] Renamed all check_func parameters to avoid conflicts with common test parameter names --- dpnp/tests/third_party/cupy/testing/_loops.py | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/dpnp/tests/third_party/cupy/testing/_loops.py b/dpnp/tests/third_party/cupy/testing/_loops.py index ee2072eab120..03232642b221 100644 --- a/dpnp/tests/third_party/cupy/testing/_loops.py +++ b/dpnp/tests/third_party/cupy/testing/_loops.py @@ -592,12 +592,17 @@ def numpy_cupy_allclose( # "must be supplied as float." # ) - def check_func(c, n, **test_kwargs): + def check_func(cupy_result, numpy_result, **test_kwargs): rtol1, atol1 = _resolve_tolerance( - type_check, c, rtol, atol, **test_kwargs + type_check, cupy_result, rtol, atol, **test_kwargs ) _array.assert_allclose( - c, n, rtol1, atol1, err_msg=err_msg, verbose=verbose + cupy_result, + numpy_result, + rtol1, + atol1, + err_msg=err_msg, + verbose=verbose, ) return _make_decorator( @@ -652,8 +657,10 @@ def numpy_cupy_array_almost_equal( .. seealso:: :func:`cupy.testing.assert_array_almost_equal` """ - def check_func(x, y, **test_kwargs): - _array.assert_array_almost_equal(x, y, decimal, err_msg, verbose) + def check_func(cupy_result, numpy_result, **test_kwargs): + _array.assert_array_almost_equal( + cupy_result, numpy_result, decimal, err_msg, verbose + ) return _make_decorator( check_func, name, type_check, False, accept_error, sp_name, scipy_name @@ -695,8 +702,8 @@ def numpy_cupy_array_almost_equal_nulp( .. seealso:: :func:`cupy.testing.assert_array_almost_equal_nulp` """ - def check_func(x, y, **test_kwargs): - _array.assert_array_almost_equal_nulp(x, y, nulp) + def check_func(cupy_result, numpy_result, **test_kwargs): + _array.assert_array_almost_equal_nulp(cupy_result, numpy_result, nulp) return _make_decorator( check_func, @@ -749,8 +756,8 @@ def numpy_cupy_array_max_ulp( """ - def check_func(x, y, **test_kwargs): - _array.assert_array_max_ulp(x, y, maxulp, dtype) + def check_func(cupy_result, numpy_result, **test_kwargs): + _array.assert_array_max_ulp(cupy_result, numpy_result, maxulp, dtype) return _make_decorator( check_func, name, type_check, False, accept_error, sp_name, scipy_name @@ -798,9 +805,13 @@ def numpy_cupy_array_equal( .. seealso:: :func:`cupy.testing.assert_array_equal` """ - def check_func(x, y, **test_kwargs): + def check_func(cupy_result, numpy_result, **test_kwargs): _array.assert_array_equal( - x, y, err_msg, verbose, strides_check=strides_check + cupy_result, + numpy_result, + err_msg, + verbose, + strides_check=strides_check, ) return _make_decorator( @@ -837,8 +848,8 @@ def numpy_cupy_array_list_equal( DeprecationWarning, ) - def check_func(x, y, **test_kwargs): - _array.assert_array_equal(x, y, err_msg, verbose) + def check_func(cupy_result, numpy_result, **test_kwargs): + _array.assert_array_equal(cupy_result, numpy_result, err_msg, verbose) return _make_decorator( check_func, name, False, False, False, sp_name, scipy_name @@ -882,8 +893,8 @@ def numpy_cupy_array_less( .. seealso:: :func:`cupy.testing.assert_array_less` """ - def check_func(x, y, **test_kwargs): - _array.assert_array_less(x, y, err_msg, verbose) + def check_func(cupy_result, numpy_result, **test_kwargs): + _array.assert_array_less(cupy_result, numpy_result, err_msg, verbose) return _make_decorator( check_func, name, type_check, False, accept_error, sp_name, scipy_name