Skip to content

Commit 56ccf86

Browse files
Update mkl_lapack::batch_error handling in getrf_batch / getri_batch (#2458)
This PR suggests improvements to the handling of `oneapi::mkl::lapack::batch_error` in `oneapi::mkl::lapack::getrf_batch` and `oneapi::mkl::lapack::getri_batch` OneMKL batched functions throw a single `oneapi::mkl::lapack::batch_error` interpreted as computation_error for all failed matrices (ids()). Now set dev_info[...] = 1 for each to determinate singular matrix. This change allows a consistent raising of `LinAlgError` in `dpnp.linalg.inv()` for both non-batched and batched singular matrices and unskipping tests with singular matrices for `dpnp.linalg.inv()` , `dpnp.linalg.det`, `dpnp.linalg.slogdet`
1 parent 55deb00 commit 56ccf86

File tree

7 files changed

+67
-39
lines changed

7 files changed

+67
-39
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ This release achieves 100% compliance with Python Array API specification (revis
4747
* 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)
4848
* 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)
4949
* Resolved build issues on non-Intel targets in `dpnp.i0` and `dpnp.kaiser` [#2439](https://github.com/IntelPython/dpnp/pull/2439)
50+
* Ensure consistency in the `dpnp.linalg.LinAlgError` exception raised on singular input matrices for both non-batched and batched cases in `dpnp.linalg.inv` [#2458] (https://github.com/IntelPython/dpnp/pull/2458)
5051

5152

5253
## [0.17.0] - 02/26/2025

dpnp/backend/extensions/lapack/getrf_batch.cpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,22 +110,24 @@ static sycl::event getrf_batch_impl(sycl::queue &exec_q,
110110
// Get the indices of matrices within the batch that encountered an
111111
// error
112112
auto error_matrices_ids = be.ids();
113-
// Get the indices of the first zero diagonal elements of these matrices
114-
auto error_info = be.exceptions();
115113

116114
auto error_matrices_ids_size = error_matrices_ids.size();
117115
auto dev_info_size = static_cast<std::size_t>(py::len(dev_info));
118-
if (error_matrices_ids_size != dev_info_size) {
119-
throw py::value_error("The size of `dev_info` must be equal to " +
116+
if (error_matrices_ids_size > dev_info_size) {
117+
throw py::value_error("The size of `dev_info` must be greater than"
118+
" or equal to " +
120119
std::to_string(error_matrices_ids_size) +
121120
", but currently it is " +
122121
std::to_string(dev_info_size) + ".");
123122
}
124123

124+
// OneMKL batched functions throw a single `batch_error`
125+
// instead of per-matrix exceptions or an info array.
126+
// This is interpreted as a computation_error (singular matrix),
127+
// consistent with non-batched LAPACK behavior.
128+
// Set dev_info[...] to any positive value for each failed index.
125129
for (size_t i = 0; i < error_matrices_ids.size(); ++i) {
126-
// Assign the index of the first zero diagonal element in each
127-
// error matrix to the corresponding index in 'dev_info'
128-
dev_info[error_matrices_ids[i]] = error_info[i];
130+
dev_info[error_matrices_ids[i]] = 1;
129131
}
130132
} catch (mkl_lapack::exception const &e) {
131133
is_exception_caught = true;

dpnp/backend/extensions/lapack/getri_batch.cpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,22 +108,24 @@ static sycl::event getri_batch_impl(sycl::queue &exec_q,
108108
// Get the indices of matrices within the batch that encountered an
109109
// error
110110
auto error_matrices_ids = be.ids();
111-
// Get the indices of the first zero diagonal elements of these matrices
112-
auto error_info = be.exceptions();
113111

114112
auto error_matrices_ids_size = error_matrices_ids.size();
115113
auto dev_info_size = static_cast<std::size_t>(py::len(dev_info));
116-
if (error_matrices_ids_size != dev_info_size) {
117-
throw py::value_error("The size of `dev_info` must be equal to " +
114+
if (error_matrices_ids_size > dev_info_size) {
115+
throw py::value_error("The size of `dev_info` must be greater than"
116+
" or equal to " +
118117
std::to_string(error_matrices_ids_size) +
119118
", but currently it is " +
120119
std::to_string(dev_info_size) + ".");
121120
}
122121

122+
// OneMKL batched functions throw a single `batch_error`
123+
// instead of per-matrix exceptions or an info array.
124+
// This is interpreted as a computation_error (singular matrix),
125+
// consistent with non-batched LAPACK behavior.
126+
// Set dev_info[...] to any positive value for each failed index.
123127
for (size_t i = 0; i < error_matrices_ids.size(); ++i) {
124-
// Assign the index of the first zero diagonal element in each
125-
// error matrix to the corresponding index in 'dev_info'
126-
dev_info[error_matrices_ids[i]] = error_info[i];
128+
dev_info[error_matrices_ids[i]] = 1;
127129
}
128130
} catch (mkl_lapack::exception const &e) {
129131
is_exception_caught = true;

dpnp/tests/helper.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,3 +443,20 @@ def is_win_platform():
443443

444444
def numpy_version():
445445
return numpy.lib.NumpyVersion(numpy.__version__)
446+
447+
448+
def requires_intel_mkl_version(version):
449+
"""
450+
Check if Intel MKL is used and its version is greater than or
451+
equal to the specified one.
452+
453+
The check is based on MKL backend name stored in Build Dependencies
454+
and only applies if Intel NumPy is detected.
455+
The version is extracted from the BLAS section of NumPy's build
456+
information and compared to the given version string.
457+
"""
458+
if not is_intel_numpy():
459+
return False
460+
461+
build_deps = numpy.show_config(mode="dicts")["Build Dependencies"]
462+
return build_deps["blas"]["version"] >= version

dpnp/tests/test_linalg.py

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,8 @@
2424
has_support_aspect64,
2525
is_cpu_device,
2626
is_cuda_device,
27-
is_gpu_device,
28-
is_win_platform,
2927
numpy_version,
28+
requires_intel_mkl_version,
3029
)
3130
from .third_party.cupy import testing
3231

@@ -334,11 +333,13 @@ def test_nan(self, p):
334333
# while OneMKL returns nans
335334
if is_cuda_device() and p in [-dpnp.inf, -1, 1, dpnp.inf, "fro"]:
336335
pytest.skip("Different behavior on CUDA")
337-
elif (
338-
is_gpu_device()
339-
and is_win_platform()
340-
and p in [-dpnp.inf, -1, 1, dpnp.inf, "fro"]
341-
):
336+
elif requires_intel_mkl_version("2025.2") and p in [
337+
-dpnp.inf,
338+
-1,
339+
1,
340+
dpnp.inf,
341+
"fro",
342+
]:
342343
pytest.skip("SAT-7966")
343344
a = generate_random_numpy_array((2, 2, 2, 2))
344345
a[0, 0] = 0
@@ -460,10 +461,6 @@ def test_det_singular_matrix(self, matrix):
460461

461462
assert_allclose(result, expected)
462463

463-
# TODO: remove skipif when MKLD-13852 is resolved
464-
# _getrf_batch does not raise an error with singular matrices.
465-
# Skip running on cpu because dpnp uses _getrf_batch only on cpu.
466-
@pytest.mark.skipif(is_cpu_device(), reason="MKLD-13852")
467464
def test_det_singular_matrix_3D(self):
468465
a_np = numpy.array(
469466
[[[1, 2], [3, 4]], [[1, 2], [1, 2]], [[1, 3], [3, 1]]]
@@ -1761,9 +1758,10 @@ def test_inv_singular_matrix(self, matrix):
17611758
assert_raises(numpy.linalg.LinAlgError, numpy.linalg.inv, a_np)
17621759
assert_raises(dpnp.linalg.LinAlgError, dpnp.linalg.inv, a_dp)
17631760

1764-
# TODO: remove skip when MKLD-13852 is resolved
1765-
# _getrf_batch does not raise an error with singular matrices.
1766-
@pytest.mark.skip("MKLD-13852")
1761+
# TODO: remove skipif when Intel MKL 2025.2 is released
1762+
@pytest.mark.skipif(
1763+
not requires_intel_mkl_version("2025.2"), reason="mkl<2025.2"
1764+
)
17671765
def test_inv_singular_matrix_3D(self):
17681766
a_np = numpy.array(
17691767
[[[1, 2], [3, 4]], [[1, 2], [1, 2]], [[1, 3], [3, 1]]]
@@ -2785,6 +2783,13 @@ def test_slogdet_strides(self):
27852783
assert_allclose(sign_result, sign_expected)
27862784
assert_allclose(logdet_result, logdet_expected)
27872785

2786+
# TODO: remove skipif when Intel MKL 2025.2 is released
2787+
# Skip running on CPU because dpnp uses _getrf_batch only on CPU
2788+
# for dpnp.linalg.det/slogdet.
2789+
@pytest.mark.skipif(
2790+
is_cpu_device() and not requires_intel_mkl_version("2025.2"),
2791+
reason="mkl<2025.2",
2792+
)
27882793
@pytest.mark.parametrize(
27892794
"matrix",
27902795
[
@@ -2815,10 +2820,13 @@ def test_slogdet_singular_matrix(self, matrix):
28152820
assert_allclose(sign_result, sign_expected)
28162821
assert_allclose(logdet_result, logdet_expected)
28172822

2818-
# TODO: remove skipif when MKLD-13852 is resolved
2819-
# _getrf_batch does not raise an error with singular matrices.
2820-
# Skip running on cpu because dpnp uses _getrf_batch only on cpu.
2821-
@pytest.mark.skipif(is_cpu_device(), reason="MKLD-13852")
2823+
# TODO: remove skipif when Intel MKL 2025.2 is released
2824+
# Skip running on CPU because dpnp uses _getrf_batch only on CPU
2825+
# for dpnp.linalg.det/slogdet.
2826+
@pytest.mark.skipif(
2827+
is_cpu_device() and not requires_intel_mkl_version("2025.2"),
2828+
reason="mkl<2025.2",
2829+
)
28222830
def test_slogdet_singular_matrix_3D(self):
28232831
a_np = numpy.array(
28242832
[[[1, 2], [3, 4]], [[1, 2], [1, 2]], [[1, 3], [3, 1]]]

dpnp/tests/third_party/cupy/linalg_tests/test_norms.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,6 @@ def test_det_zero_dim(self, dtype):
170170
with pytest.raises(xp.linalg.LinAlgError):
171171
xp.linalg.det(a)
172172

173-
# TODO: remove skipif when MKLD-13852 is resolved
174-
# _getrf_batch does not raise an error with singular matrices.
175-
# Skip running on cpu because dpnp uses _getrf_batch only on cpu.
176-
@pytest.mark.skipif(is_cpu_device(), reason="MKLD-13852")
177173
@testing.for_float_dtypes(no_float16=True)
178174
@testing.numpy_cupy_allclose(rtol=1e-3, atol=1e-4)
179175
def test_det_singular(self, xp, dtype):

dpnp/tests/third_party/cupy/linalg_tests/test_solve.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from dpnp.tests.helper import (
88
assert_dtype_allclose,
99
has_support_aspect64,
10+
requires_intel_mkl_version,
1011
)
1112
from dpnp.tests.third_party.cupy import testing
1213
from dpnp.tests.third_party.cupy.testing import _condition
@@ -213,9 +214,10 @@ def test_inv(self, dtype):
213214
):
214215
xp.linalg.inv(a)
215216

216-
# TODO: remove skip when MKLD-13852 is resolved
217-
# _getrf_batch does not raise an error with singular matrices.
218-
@pytest.mark.skip("MKLD-13852")
217+
# TODO: remove skipif when Intel MKL 2025.2 is released
218+
@pytest.mark.skipif(
219+
not requires_intel_mkl_version("2025.2"), reason="mkl<2025.2"
220+
)
219221
@testing.for_dtypes("ifdFD")
220222
def test_batched_inv(self, dtype):
221223
for xp in (numpy, cupy):

0 commit comments

Comments
 (0)