Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ This release achieves 100% compliance with Python Array API specification (revis
* Updated `conda create` commands build and install instructions of `Quick start guide` to avoid a compilation error [#2395](https://github.com/IntelPython/dpnp/pull/2395)
* Added handling of empty string passed to a test env variable defining data type scope as a `False` value [#2415](https://github.com/IntelPython/dpnp/pull/2415)
* Resolved build issues on non-Intel targets in `dpnp.i0` and `dpnp.kaiser` [#2439](https://github.com/IntelPython/dpnp/pull/2439)
* Ensure consistent `dpnp.linalg.LinAlgError` on singular matrices for both non-batched and batched cases in `dpnp.linalg.inv` [#2458] (https://github.com/IntelPython/dpnp/pull/2458)


## [0.17.0] - 02/26/2025
Expand Down
22 changes: 15 additions & 7 deletions dpnp/backend/extensions/lapack/getrf_batch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,22 +110,30 @@ static sycl::event getrf_batch_impl(sycl::queue &exec_q,
// Get the indices of matrices within the batch that encountered an
// error
auto error_matrices_ids = be.ids();
// Get the indices of the first zero diagonal elements of these matrices
auto error_info = be.exceptions();
// List of exception pointers corresponding to
// each failed matrix in the batch.
auto error_exceptions = be.exceptions();

auto error_matrices_ids_size = error_matrices_ids.size();
auto dev_info_size = static_cast<std::size_t>(py::len(dev_info));
if (error_matrices_ids_size != dev_info_size) {
throw py::value_error("The size of `dev_info` must be equal to " +
if (error_matrices_ids_size > dev_info_size) {
throw py::value_error("The size of `dev_info` must be greater than"
" or equal to " +
std::to_string(error_matrices_ids_size) +
", but currently it is " +
std::to_string(dev_info_size) + ".");
}

// MKL returns an empty exception list (MKLD-17226)
// which makes it impossible to extract specific error types.
// Workaround: mark the failed matrices in dev_info with 1
// to indicate a failure (singular matrix).

// TODO: Once be.exceptions() returns a valid list,
// fill dev_info only based on the caught exception type
// mkl_lapack::computation_error -> dev_info = any positive values
for (size_t i = 0; i < error_matrices_ids.size(); ++i) {
// Assign the index of the first zero diagonal element in each
// error matrix to the corresponding index in 'dev_info'
dev_info[error_matrices_ids[i]] = error_info[i];
dev_info[error_matrices_ids[i]] = 1;
}
} catch (mkl_lapack::exception const &e) {
is_exception_caught = true;
Expand Down
22 changes: 15 additions & 7 deletions dpnp/backend/extensions/lapack/getri_batch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,22 +108,30 @@ static sycl::event getri_batch_impl(sycl::queue &exec_q,
// Get the indices of matrices within the batch that encountered an
// error
auto error_matrices_ids = be.ids();
// Get the indices of the first zero diagonal elements of these matrices
auto error_info = be.exceptions();
// List of exception pointers corresponding to
// each failed matrix in the batch.
auto error_exceptions = be.exceptions();

auto error_matrices_ids_size = error_matrices_ids.size();
auto dev_info_size = static_cast<std::size_t>(py::len(dev_info));
if (error_matrices_ids_size != dev_info_size) {
throw py::value_error("The size of `dev_info` must be equal to " +
if (error_matrices_ids_size > dev_info_size) {
throw py::value_error("The size of `dev_info` must be greater than"
" or equal to " +
std::to_string(error_matrices_ids_size) +
", but currently it is " +
std::to_string(dev_info_size) + ".");
}

// MKL returns an empty exception list (MKLD-17226)
// which makes it impossible to extract specific error types.
// Workaround: mark the failed matrices in dev_info with 1
// to indicate a failure (singular matrix).

// TODO: Once be.exceptions() returns a valid list,
// fill dev_info only based on the caught exception type
// mkl_lapack::computation_error -> dev_info = any positive values
for (size_t i = 0; i < error_matrices_ids.size(); ++i) {
// Assign the index of the first zero diagonal element in each
// error matrix to the corresponding index in 'dev_info'
dev_info[error_matrices_ids[i]] = error_info[i];
dev_info[error_matrices_ids[i]] = 1;
}
} catch (mkl_lapack::exception const &e) {
is_exception_caught = true;
Expand Down
18 changes: 18 additions & 0 deletions dpnp/tests/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,24 @@ def not_excluded(dtype):
return dtypes


def get_intel_mkl_version():
"""
Return the version of Intel MKL used by NumPy during testing.

The check is based on MKL backend name stored in Build Dependencies
and only applies if Intel NumPy is detected.
The version is extracted from the BLAS section of NumPy's build
information.

Return None if Intel MKL is not used.
"""
if not is_intel_numpy():
return None

build_deps = numpy.show_config(mode="dicts")["Build Dependencies"]
return build_deps["blas"]["version"]


def has_support_aspect16(device=None):
"""
Return True if the device supports 16-bit precision floating point operations,
Expand Down
28 changes: 10 additions & 18 deletions dpnp/tests/test_linalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@
get_all_dtypes,
get_float_complex_dtypes,
get_integer_float_dtypes,
get_intel_mkl_version,
has_support_aspect64,
is_cpu_device,
is_cuda_device,
is_gpu_device,
is_win_platform,
numpy_version,
)
from .third_party.cupy import testing
Expand Down Expand Up @@ -334,11 +333,7 @@ def test_nan(self, p):
# while OneMKL returns nans
if is_cuda_device() and p in [-dpnp.inf, -1, 1, dpnp.inf, "fro"]:
pytest.skip("Different behavior on CUDA")
elif (
is_gpu_device()
and is_win_platform()
and p in [-dpnp.inf, -1, 1, dpnp.inf, "fro"]
):
elif p in [-dpnp.inf, -1, 1, dpnp.inf, "fro"]:
pytest.skip("SAT-7966")
a = generate_random_numpy_array((2, 2, 2, 2))
a[0, 0] = 0
Expand Down Expand Up @@ -460,10 +455,6 @@ def test_det_singular_matrix(self, matrix):

assert_allclose(result, expected)

# TODO: remove skipif when MKLD-13852 is resolved
# _getrf_batch does not raise an error with singular matrices.
# Skip running on cpu because dpnp uses _getrf_batch only on cpu.
@pytest.mark.skipif(is_cpu_device(), reason="MKLD-13852")
def test_det_singular_matrix_3D(self):
a_np = numpy.array(
[[[1, 2], [3, 4]], [[1, 2], [1, 2]], [[1, 3], [3, 1]]]
Expand Down Expand Up @@ -1761,9 +1752,8 @@ def test_inv_singular_matrix(self, matrix):
assert_raises(numpy.linalg.LinAlgError, numpy.linalg.inv, a_np)
assert_raises(dpnp.linalg.LinAlgError, dpnp.linalg.inv, a_dp)

# TODO: remove skip when MKLD-13852 is resolved
# _getrf_batch does not raise an error with singular matrices.
@pytest.mark.skip("MKLD-13852")
# TODO: remove skipif when Intel MKL 2025.2 is released
@pytest.mark.skipif(get_intel_mkl_version() < "2025.2", reason="mkl<2025.2")
def test_inv_singular_matrix_3D(self):
a_np = numpy.array(
[[[1, 2], [3, 4]], [[1, 2], [1, 2]], [[1, 3], [3, 1]]]
Expand Down Expand Up @@ -2785,6 +2775,12 @@ def test_slogdet_strides(self):
assert_allclose(sign_result, sign_expected)
assert_allclose(logdet_result, logdet_expected)

# TODO: remove skipif when Intel MKL 2025.2 is released
# Skip running on cpu because dpnp uses _getrf_batch only on cpu.
@pytest.mark.skipif(
is_cpu_device() and get_intel_mkl_version() < "2025.2",
reason="mkl<2025.2",
)
@pytest.mark.parametrize(
"matrix",
[
Expand Down Expand Up @@ -2815,10 +2811,6 @@ def test_slogdet_singular_matrix(self, matrix):
assert_allclose(sign_result, sign_expected)
assert_allclose(logdet_result, logdet_expected)

# TODO: remove skipif when MKLD-13852 is resolved
# _getrf_batch does not raise an error with singular matrices.
# Skip running on cpu because dpnp uses _getrf_batch only on cpu.
@pytest.mark.skipif(is_cpu_device(), reason="MKLD-13852")
def test_slogdet_singular_matrix_3D(self):
a_np = numpy.array(
[[[1, 2], [3, 4]], [[1, 2], [1, 2]], [[1, 3], [3, 1]]]
Expand Down
4 changes: 0 additions & 4 deletions dpnp/tests/third_party/cupy/linalg_tests/test_norms.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,6 @@ def test_det_zero_dim(self, dtype):
with pytest.raises(xp.linalg.LinAlgError):
xp.linalg.det(a)

# TODO: remove skipif when MKLD-13852 is resolved
# _getrf_batch does not raise an error with singular matrices.
# Skip running on cpu because dpnp uses _getrf_batch only on cpu.
@pytest.mark.skipif(is_cpu_device(), reason="MKLD-13852")
@testing.for_float_dtypes(no_float16=True)
@testing.numpy_cupy_allclose(rtol=1e-3, atol=1e-4)
def test_det_singular(self, xp, dtype):
Expand Down
6 changes: 3 additions & 3 deletions dpnp/tests/third_party/cupy/linalg_tests/test_solve.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import dpnp as cupy
from dpnp.tests.helper import (
assert_dtype_allclose,
get_intel_mkl_version,
has_support_aspect64,
)
from dpnp.tests.third_party.cupy import testing
Expand Down Expand Up @@ -213,9 +214,8 @@ def test_inv(self, dtype):
):
xp.linalg.inv(a)

# TODO: remove skip when MKLD-13852 is resolved
# _getrf_batch does not raise an error with singular matrices.
@pytest.mark.skip("MKLD-13852")
# TODO: remove skipif when Intel MKL 2025.2 is released
@pytest.mark.skipif(get_intel_mkl_version() < "2025.2", reason="mkl<2025.2")
@testing.for_dtypes("ifdFD")
def test_batched_inv(self, dtype):
for xp in (numpy, cupy):
Expand Down
Loading