reference.hh
Go to the documentation of this file.
1// SPDX-License-Identifier: MIT
2// Host LAPACK references (docs/design/verification.md): eigenvalues, singular values and getrf pivots
3// from LAPACKE, never from a BatchLAS path under test. Without LAPACKE (no host backend) every
4// function returns false, empty input included, and the caller skips: never a pass. With LAPACKE
5// (have_reference) false is a failed reference, and the caller fails.
6#pragma once
7
9#include <batchlas/error.hh>
10
11#include <algorithm>
12#include <complex>
13#include <cstdint>
14#include <stdexcept>
15#include <string>
16#include <type_traits>
17#include <span>
18#include <vector>
19
20// Undefined would read as 0 and silently compile the "no reference" branch.
21#ifndef BATCHLAS_VERIFY_HAVE_LAPACKE
22#error "batchlas/verify/reference.hh: link batchlas::verify, which defines BATCHLAS_VERIFY_HAVE_LAPACKE"
23#endif
24
25#if BATCHLAS_VERIFY_HAVE_LAPACKE
26#include <lapacke.h>
27
29/// The host dgemm probe (libbatchlas_verify, cached): empty when correct, else the diagnostic.
30std::string host_dgemm_defect();
31} // namespace batchlas::verify::detail
32#endif
33
35
36/// True when this build has host LAPACKE references; a reference that returns false here failed.
37inline constexpr bool have_reference = BATCHLAS_VERIFY_HAVE_LAPACKE != 0;
38
39namespace detail {
40// Every double reference except ?sterf runs blocked code on the host dgemm, which OpenBLAS 0.3.20
41// gets wrong on Cooperlake without OPENBLAS_CORETYPE: a wrong reference fails a correct result or
42// passes a wrong one, so refuse it. evidence: docs/perf/lu.md#the-host-dgetrf-oracle-is-broken-on-this-box
43template <class D> void require_host_dgemm() {
44#if BATCHLAS_VERIFY_HAVE_LAPACKE
45 if constexpr (std::is_same_v<D, double> || std::is_same_v<D, std::complex<double>>) {
46 static const std::string defect = host_dgemm_defect();
47 if (!defect.empty()) throw batchlas::unsupported("batchlas::verify: host LAPACKE reference refused\n" + defect);
48 }
49#endif
50}
51} // namespace detail
52
53namespace detail {
54// Item @p item of @p A as a packed std::vector, converted to U.
55template <class U, class T> std::vector<U> item_vector(const MatrixView<T, MatrixFormat::Dense>& A, int item) {
56 const auto m = A.template astype<U>(std::span<const int>(&item, 1));
57 return std::vector<U>(m.data().data(), m.data().data() + std::size_t(A.rows()) * std::size_t(A.cols()));
58}
59} // namespace detail
60
61/// Ascending eigenvalues of the n x n Hermitian @p a (column-major, ld = n, both triangles valid; destroyed).
62template <class D>
63 requires(std::is_same_v<D, double> || std::is_same_v<D, std::complex<double>>)
64bool eigenvalues(int n, std::vector<D>& a, std::vector<double>& w) {
65 w.assign(static_cast<std::size_t>(n), 0.0);
66#if BATCHLAS_VERIFY_HAVE_LAPACKE
67 if (n == 0) return true;
69 if constexpr (std::is_same_v<D, double>)
70 return LAPACKE_dsyevd(LAPACK_COL_MAJOR, 'N', 'L', n, a.data(), n, w.data()) == 0;
71 else
72 return LAPACKE_zheevd(LAPACK_COL_MAJOR, 'N', 'L', n, reinterpret_cast<lapack_complex_double*>(a.data()), n,
73 w.data()) == 0;
74#else
75 (void)a;
76 return false;
77#endif
78}
79
80/// Descending singular values of the m x n @p a (column-major, ld = m; destroyed).
81template <class D>
82 requires(std::is_same_v<D, double> || std::is_same_v<D, std::complex<double>>)
83bool singular_values(int m, int n, std::vector<D>& a, std::vector<double>& s) {
84 s.assign(static_cast<std::size_t>(std::min(m, n)), 0.0);
85#if BATCHLAS_VERIFY_HAVE_LAPACKE
86 if (s.empty()) return true;
88 if constexpr (std::is_same_v<D, double>)
89 return LAPACKE_dgesdd(LAPACK_COL_MAJOR, 'N', m, n, a.data(), m, s.data(), nullptr, 1, nullptr, 1) == 0;
90 else
91 return LAPACKE_zgesdd(LAPACK_COL_MAJOR, 'N', m, n, reinterpret_cast<lapack_complex_double*>(a.data()), m,
92 s.data(), nullptr, 1, nullptr, 1) == 0;
93#else
94 (void)a;
95 return false;
96#endif
97}
98
99// A residual bound is satisfied by ANY valid pivot choice, so a kernel that pivots on |z| instead of
100// LAPACK's |re|+|im|, or breaks ties the other way, passes every residual. This is the check that does not.
101/// 1-based pivots of the m x n @p a (column-major, ld = m; overwritten by its LU factor): min(m, n)
102/// of them, from ?getrf in E's own precision (float, double, complex<float>, complex<double>).
103/// @pre @p a holds the data in its native precision (MatrixView::astype<E>(items), never a promoted copy,
104/// which sends float data to dgetrf). The view overload below does this for you.
105// Never promote float data to dgetrf: OpenBLAS 0.3.20's dgetrf on Cooperlake Xeons (its broken dgemm
106// kernel) is wrong from n = 10 while sgetrf is not. evidence: docs/perf/lu.md#the-host-dgetrf-oracle-is-broken-on-this-box
107template <class E> bool getrf_pivots(int m, int n, std::vector<E>& a, std::vector<std::int32_t>& ipiv) {
108 static_assert(std::is_same_v<E, float> || std::is_same_v<E, double> || std::is_same_v<E, std::complex<float>> ||
109 std::is_same_v<E, std::complex<double>>,
110 "getrf_pivots: float, double, complex<float> or complex<double> only");
111 ipiv.assign(static_cast<std::size_t>(std::min(m, n)), 0);
112#if BATCHLAS_VERIFY_HAVE_LAPACKE
113 if (ipiv.empty()) return true;
115 std::vector<lapack_int> p(ipiv.size());
116 lapack_int info;
117 if constexpr (std::is_same_v<E, float>)
118 info = LAPACKE_sgetrf(LAPACK_COL_MAJOR, m, n, a.data(), m, p.data());
119 else if constexpr (std::is_same_v<E, double>)
120 info = LAPACKE_dgetrf(LAPACK_COL_MAJOR, m, n, a.data(), m, p.data());
121 else if constexpr (std::is_same_v<E, std::complex<float>>)
122 info = LAPACKE_cgetrf(LAPACK_COL_MAJOR, m, n, reinterpret_cast<lapack_complex_float*>(a.data()), m, p.data());
123 else
124 info = LAPACKE_zgetrf(LAPACK_COL_MAJOR, m, n, reinterpret_cast<lapack_complex_double*>(a.data()), m, p.data());
125 // info > 0 is an exactly singular U: the pivots are still valid, so only a negative info fails.
126 if (info < 0) return false;
127 for (std::size_t i = 0; i < p.size(); ++i) ipiv[i] = static_cast<std::int32_t>(p[i]);
128 return true;
129#else
130 (void)a;
131 return false;
132#endif
133}
134
135/// getrf_pivots of item @p item of a dense view, copied in the view's own precision.
136template <class T> bool getrf_pivots(const MatrixView<T, MatrixFormat::Dense>& A, int item, std::vector<std::int32_t>& ipiv) {
137 auto a = detail::item_vector<T>(A, item);
138 return getrf_pivots(A.rows(), A.cols(), a, ipiv);
139}
140
141/// LAPACK geqrf of the m x n @p a (column-major, ld = m; overwritten by R and the reflectors) and its
142/// min(m, n) @p tau.
143template <class D> bool geqrf_tau(int m, int n, std::vector<D>& a, std::vector<D>& tau) {
144 static_assert(std::is_same_v<D, double> || std::is_same_v<D, std::complex<double>>,
145 "geqrf_tau: double or complex<double> only");
146 tau.assign(static_cast<std::size_t>(std::min(m, n)), D(0));
147#if BATCHLAS_VERIFY_HAVE_LAPACKE
148 if (tau.empty()) return true;
150 if constexpr (std::is_same_v<D, double>)
151 return LAPACKE_dgeqrf(LAPACK_COL_MAJOR, m, n, a.data(), m, tau.data()) == 0;
152 else
153 return LAPACKE_zgeqrf(LAPACK_COL_MAJOR, m, n, reinterpret_cast<lapack_complex_double*>(a.data()), m,
154 reinterpret_cast<lapack_complex_double*>(tau.data())) == 0;
155#else
156 (void)a;
157 return false;
158#endif
159}
160
161/// Eigenvalues of the symmetric tridiagonal (diagonal @p d, off-diagonal @p e): ascending, written to @p d.
162inline bool tridiagonal_eigenvalues(std::vector<double>& d, std::vector<double>& e) {
163#if BATCHLAS_VERIFY_HAVE_LAPACKE
164 if (d.empty()) return true;
165 return LAPACKE_dsterf(static_cast<lapack_int>(d.size()), d.data(), e.data()) == 0;
166#else
167 (void)d;
168 (void)e;
169 return false;
170#endif
171}
172
173/// eigenvalues() of item @p item of the Hermitian @p A (both triangles valid), promoted.
174template <class T> bool eigenvalues(const MatrixView<T, MatrixFormat::Dense>& A, int item, std::vector<double>& w) {
176 return eigenvalues(A.rows(), a, w);
177}
178
179/// singular_values() of item @p item of @p A, promoted.
180template <class T> bool singular_values(const MatrixView<T, MatrixFormat::Dense>& A, int item, std::vector<double>& s) {
182 return singular_values(A.rows(), A.cols(), a, s);
183}
184
185/// geqrf_tau() of item @p item of @p A, promoted; the factor is discarded.
186template <class T>
192
193} // namespace batchlas::verify
Non-owning view of a batch of matrices: the argument type of every entry point.
Definition matrix.hh:485
int rows() const
Rows per item (the capacity on a heterogeneous batch).
Definition matrix.hh:605
int cols() const
Columns per item (the capacity on a heterogeneous batch).
Definition matrix.hh:611
std::conditional_t< is_std_complex_v< T >, std::complex< double >, double > promoted_type
T's promoted type: double, or complex<double> for complex T.
Definition matrix.hh:649
No kernel, backend or vendor entry point in this build on this device serves the request.
Definition error.hh:122
The BatchLAS exception hierarchy.
The batched containers: Matrix (owning), MatrixView (non-owning), KernelMatrixView (device-side),...
std::vector< U > item_vector(const MatrixView< T, MatrixFormat::Dense > &A, int item)
Definition reference.hh:55
constexpr bool have_reference
True when this build has host LAPACKE references; a reference that returns false here failed.
Definition reference.hh:37
bool getrf_pivots(int m, int n, std::vector< E > &a, std::vector< std::int32_t > &ipiv)
1-based pivots of the m x n a (column-major, ld = m; overwritten by its LU factor): min(m,...
Definition reference.hh:107
bool singular_values(int m, int n, std::vector< D > &a, std::vector< double > &s)
Descending singular values of the m x n a (column-major, ld = m; destroyed).
Definition reference.hh:83
bool tridiagonal_eigenvalues(std::vector< double > &d, std::vector< double > &e)
Eigenvalues of the symmetric tridiagonal (diagonal d, off-diagonal e): ascending, written to d.
Definition reference.hh:162
bool eigenvalues(int n, std::vector< D > &a, std::vector< double > &w)
Ascending eigenvalues of the n x n Hermitian a (column-major, ld = n, both triangles valid; destroyed...
Definition reference.hh:64
bool geqrf_tau(int m, int n, std::vector< D > &a, std::vector< D > &tau)
LAPACK geqrf of the m x n a (column-major, ld = m; overwritten by R and the reflectors) and its min(m...
Definition reference.hh:143