#[cfg(rlx_cpu_blas)]
unsafe extern "C" {
#[link_name = "cblas_sgemm"]
fn cblas_sgemm_raw(
order: i32,
transa: i32,
transb: i32,
m: i32,
n: i32,
k: i32,
alpha: f32,
a: *const f32,
lda: i32,
b: *const f32,
ldb: i32,
beta: f32,
c: *mut f32,
ldc: i32,
);
fn cblas_sgemv(
order: i32,
trans: i32,
m: i32,
n: i32,
alpha: f32,
a: *const f32,
lda: i32,
x: *const f32,
incx: i32,
beta: f32,
y: *mut f32,
incy: i32,
);
fn cblas_sger(
order: i32,
m: i32,
n: i32,
alpha: f32,
x: *const f32,
incx: i32,
y: *const f32,
incy: i32,
a: *mut f32,
lda: i32,
);
fn cblas_sscal(n: i32, alpha: f32, x: *mut f32, incx: i32);
}
#[cfg(rlx_cpu_blas)]
#[allow(non_snake_case, clippy::too_many_arguments)]
#[inline]
unsafe fn cblas_sgemm(
order: i32,
transa: i32,
transb: i32,
m: i32,
n: i32,
k: i32,
alpha: f32,
a: *const f32,
lda: i32,
b: *const f32,
ldb: i32,
beta: f32,
c: *mut f32,
ldc: i32,
) {
if m <= 0 || n <= 0 {
return;
}
if k <= 0 {
let (mm, nn, ldc) = (m as usize, n as usize, ldc as usize);
for i in 0..mm {
for j in 0..nn {
let cp = unsafe { c.add(i * ldc + j) };
unsafe { *cp *= beta };
}
}
return;
}
unsafe {
cblas_sgemm_raw(
order, transa, transb, m, n, k, alpha, a, lda, b, ldb, beta, c, ldc,
);
}
}
pub fn limit_inner_threads() {
use std::sync::Once;
static ONCE: Once = Once::new();
ONCE.call_once(|| {
let user_set = [
"OPENBLAS_NUM_THREADS",
"OMP_NUM_THREADS",
"MKL_NUM_THREADS",
"VECLIB_MAXIMUM_THREADS",
]
.iter()
.any(|k| std::env::var_os(k).is_some());
if user_set {
return;
}
set_blas_num_threads(1);
});
}
pub fn set_blas_num_threads(n: i32) {
let n = n.max(1);
use std::sync::atomic::{AtomicI32, Ordering};
static LAST: AtomicI32 = AtomicI32::new(0);
if LAST.swap(n, Ordering::SeqCst) == n {
return;
}
#[cfg(rlx_cpu_blas_accelerate)]
{
unsafe {
std::env::set_var("VECLIB_MAXIMUM_THREADS", n.to_string());
}
}
#[cfg(rlx_cpu_blas_openblas)]
{
unsafe extern "C" {
fn openblas_set_num_threads(n: i32);
}
unsafe {
openblas_set_num_threads(n);
}
}
#[cfg(rlx_cpu_blas_mkl)]
{
unsafe extern "C" {
fn MKL_Set_Num_Threads(n: i32);
}
unsafe {
MKL_Set_Num_Threads(n);
}
}
let _ = n; }
#[inline]
pub fn ensure_blas_threads(n: i32) {
set_blas_num_threads(n);
}
#[inline]
pub fn with_blas_threads<R>(n: i32, f: impl FnOnce() -> R) -> R {
if n <= 1 || rayon::current_thread_index().is_some() {
return f();
}
set_blas_num_threads(n);
let out = f();
set_blas_num_threads(1);
out
}
#[cfg(not(rlx_cpu_blas))]
#[allow(non_snake_case, clippy::too_many_arguments)]
#[inline]
unsafe fn cblas_sgemm(
_order: i32,
transa: i32,
transb: i32,
m: i32,
n: i32,
k: i32,
alpha: f32,
a: *const f32,
lda: i32,
b: *const f32,
ldb: i32,
beta: f32,
c: *mut f32,
ldc: i32,
) {
let m = m as usize;
let n = n as usize;
let k = k as usize;
let lda = lda as usize;
let ldb = ldb as usize;
let ldc = ldc as usize;
let trans_a = transa != NO_TRANS;
let trans_b = transb != NO_TRANS;
if !trans_a && !trans_b {
for i in 0..m {
let crow = unsafe { c.add(i * ldc) };
if beta == 0.0 {
for j in 0..n {
unsafe { *crow.add(j) = 0.0 };
}
} else if beta != 1.0 {
for j in 0..n {
unsafe { *crow.add(j) *= beta };
}
}
for p in 0..k {
let aip = alpha * unsafe { *a.add(i * lda + p) };
if aip == 0.0 {
continue;
}
let brow = unsafe { b.add(p * ldb) };
for j in 0..n {
unsafe { *crow.add(j) += aip * *brow.add(j) };
}
}
}
return;
}
for i in 0..m {
for j in 0..n {
let mut acc: f32 = 0.0;
for p in 0..k {
let av = if trans_a {
unsafe { *a.add(p * lda + i) }
} else {
unsafe { *a.add(i * lda + p) }
};
let bv = if trans_b {
unsafe { *b.add(j * ldb + p) }
} else {
unsafe { *b.add(p * ldb + j) }
};
acc += av * bv;
}
let cp = unsafe { c.add(i * ldc + j) };
unsafe {
*cp = alpha * acc + beta * *cp;
}
}
}
}
#[cfg(not(rlx_cpu_blas))]
#[allow(non_snake_case, clippy::too_many_arguments)]
#[inline]
unsafe fn cblas_sgemv(
_order: i32,
trans: i32,
m: i32,
n: i32,
alpha: f32,
a: *const f32,
lda: i32,
x: *const f32,
_incx: i32,
beta: f32,
y: *mut f32,
_incy: i32,
) {
let m = m as usize;
let n = n as usize;
let lda = lda as usize;
let trans_a = trans != NO_TRANS;
for i in 0..m {
let mut acc = 0f32;
for j in 0..n {
let av = if trans_a {
unsafe { *a.add(j * lda + i) }
} else {
unsafe { *a.add(i * lda + j) }
};
acc += av * unsafe { *x.add(j) };
}
let yp = unsafe { y.add(i) };
unsafe {
*yp = alpha * acc + beta * *yp;
}
}
}
#[cfg(not(rlx_cpu_blas))]
#[allow(non_snake_case, clippy::too_many_arguments)]
#[inline]
unsafe fn cblas_sger(
_order: i32,
m: i32,
n: i32,
alpha: f32,
x: *const f32,
_incx: i32,
y: *const f32,
_incy: i32,
a: *mut f32,
lda: i32,
) {
let m = m as usize;
let n = n as usize;
let lda = lda as usize;
for i in 0..m {
let xi = unsafe { *x.add(i) };
for j in 0..n {
let yj = unsafe { *y.add(j) };
let ap = unsafe { a.add(i * lda + j) };
unsafe {
*ap += alpha * xi * yj;
}
}
}
}
#[cfg(not(rlx_cpu_blas))]
#[inline]
unsafe fn cblas_sscal(n: i32, alpha: f32, x: *mut f32, _incx: i32) {
for i in 0..n as usize {
let xp = unsafe { x.add(i) };
unsafe {
*xp *= alpha;
}
}
}
#[cfg(rlx_cpu_blas)]
unsafe extern "C" {
fn cblas_dgemm(
order: i32,
transa: i32,
transb: i32,
m: i32,
n: i32,
k: i32,
alpha: f64,
a: *const f64,
lda: i32,
b: *const f64,
ldb: i32,
beta: f64,
c: *mut f64,
ldc: i32,
);
#[link_name = "dgesv_"]
fn lapack_dgesv(
n: *const i32,
nrhs: *const i32,
a: *mut f64,
lda: *const i32,
ipiv: *mut i32,
b: *mut f64,
ldb: *const i32,
info_out: *mut i32,
);
#[link_name = "sgesv_"]
fn lapack_sgesv(
n: *const i32,
nrhs: *const i32,
a: *mut f32,
lda: *const i32,
ipiv: *mut i32,
b: *mut f32,
ldb: *const i32,
info_out: *mut i32,
);
#[link_name = "dpotrf_"]
fn lapack_dpotrf(
uplo: *const i8,
n: *const i32,
a: *mut f64,
lda: *const i32,
info_out: *mut i32,
);
#[link_name = "dgetrf_"]
fn lapack_dgetrf(
m: *const i32,
n: *const i32,
a: *mut f64,
lda: *const i32,
ipiv: *mut i32,
info_out: *mut i32,
);
#[link_name = "dsyevd_"]
fn lapack_dsyevd(
jobz: *const i8,
uplo: *const i8,
n: *const i32,
a: *mut f64,
lda: *const i32,
w: *mut f64,
work: *mut f64,
lwork: *const i32,
iwork: *mut i32,
liwork: *const i32,
info_out: *mut i32,
);
#[link_name = "dgeqrf_"]
fn lapack_dgeqrf(
m: *const i32,
n: *const i32,
a: *mut f64,
lda: *const i32,
tau: *mut f64,
work: *mut f64,
lwork: *const i32,
info_out: *mut i32,
);
#[link_name = "dorgqr_"]
fn lapack_dorgqr(
m: *const i32,
n: *const i32,
k: *const i32,
a: *mut f64,
lda: *const i32,
tau: *const f64,
work: *mut f64,
lwork: *const i32,
info_out: *mut i32,
);
#[link_name = "dgesvd_"]
fn lapack_dgesvd(
jobu: *const i8,
jobvt: *const i8,
m: *const i32,
n: *const i32,
a: *mut f64,
lda: *const i32,
s: *mut f64,
u: *mut f64,
ldu: *const i32,
vt: *mut f64,
ldvt: *const i32,
work: *mut f64,
lwork: *const i32,
info_out: *mut i32,
);
#[link_name = "dgesdd_"]
fn lapack_dgesdd(
jobz: *const i8,
m: *const i32,
n: *const i32,
a: *mut f64,
lda: *const i32,
s: *mut f64,
u: *mut f64,
ldu: *const i32,
vt: *mut f64,
ldvt: *const i32,
work: *mut f64,
lwork: *const i32,
iwork: *mut i32,
info_out: *mut i32,
);
#[link_name = "dgelsd_"]
fn lapack_dgelsd(
m: *const i32,
n: *const i32,
nrhs: *const i32,
a: *mut f64,
lda: *const i32,
b: *mut f64,
ldb: *const i32,
s: *mut f64,
rcond: *const f64,
rank: *mut i32,
work: *mut f64,
lwork: *const i32,
iwork: *mut i32,
info_out: *mut i32,
);
fn cblas_dtrsm(
order: i32,
side: i32,
uplo: i32,
transa: i32,
diag: i32,
m: i32,
n: i32,
alpha: f64,
a: *const f64,
lda: i32,
b: *mut f64,
ldb: i32,
);
}
#[cfg(not(rlx_cpu_blas))]
#[allow(non_snake_case, clippy::too_many_arguments)]
#[inline]
unsafe fn cblas_dgemm(
_order: i32,
transa: i32,
transb: i32,
m: i32,
n: i32,
k: i32,
alpha: f64,
a: *const f64,
lda: i32,
b: *const f64,
ldb: i32,
beta: f64,
c: *mut f64,
ldc: i32,
) {
let m = m as usize;
let n = n as usize;
let k = k as usize;
let lda = lda as usize;
let ldb = ldb as usize;
let ldc = ldc as usize;
let trans_a = transa != NO_TRANS;
let trans_b = transb != NO_TRANS;
for i in 0..m {
for j in 0..n {
let mut acc: f64 = 0.0;
for p in 0..k {
let av = if trans_a {
unsafe { *a.add(p * lda + i) }
} else {
unsafe { *a.add(i * lda + p) }
};
let bv = if trans_b {
unsafe { *b.add(j * ldb + p) }
} else {
unsafe { *b.add(p * ldb + j) }
};
acc += av * bv;
}
let cp = unsafe { c.add(i * ldc + j) };
unsafe {
*cp = alpha * acc + beta * *cp;
}
}
}
}
#[cfg(not(rlx_cpu_blas))]
#[allow(non_snake_case, clippy::too_many_arguments)]
unsafe fn lapack_dgesv(
n: *const i32,
nrhs: *const i32,
a: *mut f64,
lda: *const i32,
ipiv: *mut i32,
b: *mut f64,
ldb: *const i32,
info_out: *mut i32,
) {
let nn = unsafe { *n } as usize;
let nrhs = unsafe { *nrhs } as usize;
let lda = unsafe { *lda } as usize;
let ldb = unsafe { *ldb } as usize;
let aij = |a: *mut f64, i: usize, j: usize| unsafe { a.add(j * lda + i) };
for k in 0..nn {
let mut piv = k;
let mut max_abs = unsafe { *aij(a, k, k) }.abs();
for i in (k + 1)..nn {
let v = unsafe { *aij(a, i, k) }.abs();
if v > max_abs {
max_abs = v;
piv = i;
}
}
unsafe {
*ipiv.add(k) = (piv + 1) as i32;
}
if max_abs == 0.0 {
unsafe {
*info_out = (k + 1) as i32;
}
return;
}
if piv != k {
for j in 0..nn {
let p1 = aij(a, k, j);
let p2 = aij(a, piv, j);
unsafe {
std::ptr::swap(p1, p2);
}
}
for j in 0..nrhs {
let p1 = unsafe { b.add(j * ldb + k) };
let p2 = unsafe { b.add(j * ldb + piv) };
unsafe {
std::ptr::swap(p1, p2);
}
}
}
let akk = unsafe { *aij(a, k, k) };
for i in (k + 1)..nn {
let factor = unsafe { *aij(a, i, k) } / akk;
unsafe {
*aij(a, i, k) = factor;
}
for j in (k + 1)..nn {
let v = unsafe { *aij(a, i, j) } - factor * unsafe { *aij(a, k, j) };
unsafe {
*aij(a, i, j) = v;
}
}
for j in 0..nrhs {
let v = unsafe { *b.add(j * ldb + i) } - factor * unsafe { *b.add(j * ldb + k) };
unsafe {
*b.add(j * ldb + i) = v;
}
}
}
}
for j in 0..nrhs {
for i in (0..nn).rev() {
let mut sum = unsafe { *b.add(j * ldb + i) };
for k in (i + 1)..nn {
sum -= unsafe { *aij(a, i, k) } * unsafe { *b.add(j * ldb + k) };
}
unsafe {
*b.add(j * ldb + i) = sum / *aij(a, i, i);
}
}
}
unsafe {
*info_out = 0;
}
}
#[cfg(not(rlx_cpu_blas))]
#[allow(non_snake_case, clippy::too_many_arguments)]
unsafe fn lapack_sgesv(
n: *const i32,
nrhs: *const i32,
a: *mut f32,
lda: *const i32,
ipiv: *mut i32,
b: *mut f32,
ldb: *const i32,
info_out: *mut i32,
) {
let nn = unsafe { *n } as usize;
let nrhs = unsafe { *nrhs } as usize;
let lda = unsafe { *lda } as usize;
let ldb = unsafe { *ldb } as usize;
let aij = |a: *mut f32, i: usize, j: usize| unsafe { a.add(j * lda + i) };
for k in 0..nn {
let mut piv = k;
let mut max_abs = unsafe { *aij(a, k, k) }.abs();
for i in (k + 1)..nn {
let v = unsafe { *aij(a, i, k) }.abs();
if v > max_abs {
max_abs = v;
piv = i;
}
}
unsafe {
*ipiv.add(k) = (piv + 1) as i32;
}
if max_abs == 0.0 {
unsafe {
*info_out = (k + 1) as i32;
}
return;
}
if piv != k {
for j in 0..nn {
let p1 = aij(a, k, j);
let p2 = aij(a, piv, j);
unsafe {
std::ptr::swap(p1, p2);
}
}
for j in 0..nrhs {
let p1 = unsafe { b.add(j * ldb + k) };
let p2 = unsafe { b.add(j * ldb + piv) };
unsafe {
std::ptr::swap(p1, p2);
}
}
}
let akk = unsafe { *aij(a, k, k) };
for i in (k + 1)..nn {
let factor = unsafe { *aij(a, i, k) } / akk;
unsafe {
*aij(a, i, k) = factor;
}
for j in (k + 1)..nn {
let v = unsafe { *aij(a, i, j) } - factor * unsafe { *aij(a, k, j) };
unsafe {
*aij(a, i, j) = v;
}
}
for j in 0..nrhs {
let v = unsafe { *b.add(j * ldb + i) } - factor * unsafe { *b.add(j * ldb + k) };
unsafe {
*b.add(j * ldb + i) = v;
}
}
}
}
for j in 0..nrhs {
for i in (0..nn).rev() {
let mut sum = unsafe { *b.add(j * ldb + i) };
for k in (i + 1)..nn {
sum -= unsafe { *aij(a, i, k) } * unsafe { *b.add(j * ldb + k) };
}
unsafe {
*b.add(j * ldb + i) = sum / *aij(a, i, i);
}
}
}
unsafe {
*info_out = 0;
}
}
#[inline]
pub fn dgemm(a: &[f64], b: &[f64], c: &mut [f64], m: usize, k: usize, n: usize) {
unsafe {
cblas_dgemm(
ROW_MAJOR,
NO_TRANS,
NO_TRANS,
m as i32,
n as i32,
k as i32,
1.0,
a.as_ptr(),
k as i32,
b.as_ptr(),
n as i32,
0.0,
c.as_mut_ptr(),
n as i32,
);
}
}
pub fn dgesv(a: &mut [f64], b: &mut [f64], n: usize, nrhs: usize) -> i32 {
assert_eq!(a.len(), n * n, "dgesv: A must be n×n");
assert_eq!(b.len(), n * nrhs, "dgesv: B must be n×nrhs");
for i in 0..n {
for j in (i + 1)..n {
a.swap(i * n + j, j * n + i);
}
}
if nrhs > 1 {
let mut tmp = vec![0f64; n * nrhs];
for i in 0..n {
for j in 0..nrhs {
tmp[j * n + i] = b[i * nrhs + j];
}
}
b.copy_from_slice(&tmp);
}
let mut ipiv = vec![0i32; n];
let mut info: i32 = 0;
let nn = n as i32;
let nrhs_i = nrhs as i32;
unsafe {
lapack_dgesv(
&nn,
&nrhs_i,
a.as_mut_ptr(),
&nn,
ipiv.as_mut_ptr(),
b.as_mut_ptr(),
&nn,
&mut info,
);
}
if nrhs > 1 && info == 0 {
let mut tmp = vec![0f64; n * nrhs];
for j in 0..nrhs {
for i in 0..n {
tmp[i * nrhs + j] = b[j * n + i];
}
}
b.copy_from_slice(&tmp);
}
info
}
pub fn sgesv(a: &mut [f32], b: &mut [f32], n: usize, nrhs: usize) -> i32 {
assert_eq!(a.len(), n * n, "sgesv: A must be n×n");
assert_eq!(b.len(), n * nrhs, "sgesv: B must be n×nrhs");
for i in 0..n {
for j in (i + 1)..n {
a.swap(i * n + j, j * n + i);
}
}
if nrhs > 1 {
let mut tmp = vec![0f32; n * nrhs];
for i in 0..n {
for j in 0..nrhs {
tmp[j * n + i] = b[i * nrhs + j];
}
}
b.copy_from_slice(&tmp);
}
let mut ipiv = vec![0i32; n];
let mut info: i32 = 0;
let nn = n as i32;
let nrhs_i = nrhs as i32;
unsafe {
lapack_sgesv(
&nn,
&nrhs_i,
a.as_mut_ptr(),
&nn,
ipiv.as_mut_ptr(),
b.as_mut_ptr(),
&nn,
&mut info,
);
}
if nrhs > 1 && info == 0 {
let mut tmp = vec![0f32; n * nrhs];
for j in 0..nrhs {
for i in 0..n {
tmp[i * nrhs + j] = b[j * n + i];
}
}
b.copy_from_slice(&tmp);
}
info
}
const ROW_MAJOR: i32 = 101;
const NO_TRANS: i32 = 111;
const TRANS: i32 = 112;
const CBLAS_LEFT: i32 = 141;
#[allow(dead_code)]
const CBLAS_RIGHT: i32 = 142;
const CBLAS_UPPER: i32 = 121;
const CBLAS_LOWER: i32 = 122;
const CBLAS_NON_UNIT: i32 = 131;
#[allow(dead_code)]
const CBLAS_UNIT: i32 = 132;
#[cfg(not(rlx_cpu_blas))]
fn cholesky_colmajor(a: &mut [f64], n: usize, upper: bool) -> i32 {
let at = |i: usize, j: usize| i + j * n; if upper {
for j in 0..n {
for i in 0..=j {
let mut sum = a[at(i, j)];
for l in 0..i {
sum -= a[at(l, i)] * a[at(l, j)];
}
if i < j {
let d = a[at(i, i)];
a[at(i, j)] = sum / d;
} else {
if sum <= 0.0 {
return (j + 1) as i32;
}
a[at(j, j)] = sum.sqrt();
}
}
}
} else {
for j in 0..n {
for i in j..n {
let mut sum = a[at(i, j)];
for l in 0..j {
sum -= a[at(i, l)] * a[at(j, l)];
}
if i == j {
if sum <= 0.0 {
return (j + 1) as i32;
}
a[at(j, j)] = sum.sqrt();
} else {
let d = a[at(j, j)];
a[at(i, j)] = sum / d;
}
}
}
}
0
}
#[cfg(not(rlx_cpu_blas))]
fn lu_colmajor(a: &mut [f64], m: usize, n: usize, ipiv: &mut [i32]) -> i32 {
let at = |i: usize, j: usize| i + j * m;
let mut info = 0;
for j in 0..m.min(n) {
let mut p = j;
let mut maxv = a[at(j, j)].abs();
for i in (j + 1)..m {
let v = a[at(i, j)].abs();
if v > maxv {
maxv = v;
p = i;
}
}
ipiv[j] = (p + 1) as i32;
if a[at(p, j)] != 0.0 {
if p != j {
for c in 0..n {
a.swap(at(j, c), at(p, c));
}
}
let d = a[at(j, j)];
for i in (j + 1)..m {
a[at(i, j)] /= d;
}
} else if info == 0 {
info = (j + 1) as i32;
}
for c in (j + 1)..n {
let ajc = a[at(j, c)];
if ajc != 0.0 {
for i in (j + 1)..m {
a[at(i, c)] -= a[at(i, j)] * ajc;
}
}
}
}
info
}
#[cfg(not(rlx_cpu_blas))]
fn sym_eig_jacobi(mut s: Vec<f64>, n: usize) -> (Vec<f64>, Vec<f64>) {
let mut v = vec![0f64; n * n];
for i in 0..n {
v[i * n + i] = 1.0;
}
if n <= 1 {
let w = if n == 1 { vec![s[0]] } else { vec![] };
return (w, v);
}
for _sweep in 0..100 {
let mut off = 0.0;
for p in 0..n {
for q in (p + 1)..n {
off += s[p * n + q] * s[p * n + q];
}
}
if off <= 1e-300 {
break;
}
let mut rotated = false;
for p in 0..n {
for q in (p + 1)..n {
let apq = s[p * n + q];
let app = s[p * n + p];
let aqq = s[q * n + q];
if apq.abs() <= f64::EPSILON * (app.abs() + aqq.abs()).max(f64::MIN_POSITIVE) {
continue;
}
rotated = true;
let theta = (aqq - app) / (2.0 * apq);
let t = if theta == 0.0 {
1.0
} else {
theta.signum() / (theta.abs() + (theta * theta + 1.0).sqrt())
};
let c = 1.0 / (t * t + 1.0).sqrt();
let sn = t * c;
for i in 0..n {
let sip = s[i * n + p];
let siq = s[i * n + q];
s[i * n + p] = c * sip - sn * siq;
s[i * n + q] = sn * sip + c * siq;
}
for i in 0..n {
let spi = s[p * n + i];
let sqi = s[q * n + i];
s[p * n + i] = c * spi - sn * sqi;
s[q * n + i] = sn * spi + c * sqi;
}
for i in 0..n {
let vip = v[i * n + p];
let viq = v[i * n + q];
v[i * n + p] = c * vip - sn * viq;
v[i * n + q] = sn * vip + c * viq;
}
}
}
if !rotated {
break;
}
}
let evals: Vec<f64> = (0..n).map(|i| s[i * n + i]).collect();
let mut idx: Vec<usize> = (0..n).collect();
idx.sort_by(|&a, &b| {
evals[a]
.partial_cmp(&evals[b])
.unwrap_or(std::cmp::Ordering::Equal)
});
let w: Vec<f64> = idx.iter().map(|&i| evals[i]).collect();
let mut vout = vec![0f64; n * n];
for (newj, &oldj) in idx.iter().enumerate() {
for i in 0..n {
vout[i * n + newj] = v[i * n + oldj];
}
}
(w, vout)
}
#[cfg(not(rlx_cpu_blas))]
fn svd_thin_rowmajor(am: &[f64], m: usize, n: usize) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
let k = m.min(n);
let (rows, cols, transposed) = if m >= n { (m, n, false) } else { (n, m, true) };
let mut w = vec![0f64; rows * cols];
if transposed {
for i in 0..rows {
for j in 0..cols {
w[i * cols + j] = am[j * n + i];
}
}
} else {
w.copy_from_slice(am);
}
let mut vv = vec![0f64; cols * cols];
for i in 0..cols {
vv[i * cols + i] = 1.0;
}
for _ in 0..60 {
let mut changed = false;
for p in 0..cols {
for q in (p + 1)..cols {
let (mut alpha, mut beta, mut gamma) = (0.0, 0.0, 0.0);
for r in 0..rows {
let wp = w[r * cols + p];
let wq = w[r * cols + q];
alpha += wp * wp;
beta += wq * wq;
gamma += wp * wq;
}
if gamma.abs() <= 1e-15 * (alpha * beta).sqrt() {
continue;
}
changed = true;
let zeta = (beta - alpha) / (2.0 * gamma);
let t = if zeta == 0.0 {
1.0
} else {
zeta.signum() / (zeta.abs() + (1.0 + zeta * zeta).sqrt())
};
let c = 1.0 / (1.0 + t * t).sqrt();
let sn = c * t;
for r in 0..rows {
let wp = w[r * cols + p];
let wq = w[r * cols + q];
w[r * cols + p] = c * wp - sn * wq;
w[r * cols + q] = sn * wp + c * wq;
}
for r in 0..cols {
let vp = vv[r * cols + p];
let vq = vv[r * cols + q];
vv[r * cols + p] = c * vp - sn * vq;
vv[r * cols + q] = sn * vp + c * vq;
}
}
}
if !changed {
break;
}
}
let mut sigma = vec![0f64; cols];
for c in 0..cols {
let mut nrm = 0.0;
for r in 0..rows {
nrm += w[r * cols + c] * w[r * cols + c];
}
sigma[c] = nrm.sqrt();
}
let mut idx: Vec<usize> = (0..cols).collect();
idx.sort_by(|&a, &b| {
sigma[b]
.partial_cmp(&sigma[a])
.unwrap_or(std::cmp::Ordering::Equal)
});
let mut uw = vec![0f64; rows * cols]; let mut vp = vec![0f64; cols * cols];
let mut ss = vec![0f64; cols];
for (newj, &oldj) in idx.iter().enumerate() {
let sv = sigma[oldj];
ss[newj] = sv;
for r in 0..rows {
uw[r * cols + newj] = if sv > 1e-300 {
w[r * cols + oldj] / sv
} else {
0.0
};
}
for r in 0..cols {
vp[r * cols + newj] = vv[r * cols + oldj];
}
}
let mut u_out = vec![0f64; m * k];
let mut vt_out = vec![0f64; k * n];
let s_out = ss[0..k].to_vec();
if transposed {
for r in 0..m {
for cc in 0..k {
u_out[r * k + cc] = vp[r * cols + cc];
}
}
for i in 0..k {
for j in 0..n {
vt_out[i * n + j] = uw[j * cols + i];
}
}
} else {
for r in 0..m {
for cc in 0..k {
u_out[r * k + cc] = uw[r * cols + cc];
}
}
for i in 0..k {
for j in 0..n {
vt_out[i * n + j] = vp[j * cols + i];
}
}
}
(u_out, s_out, vt_out)
}
#[cfg(not(rlx_cpu_blas))]
fn qr_householder_colmajor(a: &mut [f64], m: usize, n: usize, tau: &mut [f64]) {
let at = |i: usize, j: usize| i + j * m;
for j in 0..m.min(n) {
let alpha = a[at(j, j)];
let mut xn2 = 0.0;
for i in (j + 1)..m {
xn2 += a[at(i, j)] * a[at(i, j)];
}
if xn2 == 0.0 {
tau[j] = 0.0;
continue;
}
let norm = (alpha * alpha + xn2).sqrt();
let beta = if alpha >= 0.0 { -norm } else { norm };
let tj = (beta - alpha) / beta;
let inv = 1.0 / (alpha - beta);
for i in (j + 1)..m {
a[at(i, j)] *= inv;
}
tau[j] = tj;
a[at(j, j)] = beta; for c in (j + 1)..n {
let mut wsum = a[at(j, c)];
for i in (j + 1)..m {
wsum += a[at(i, j)] * a[at(i, c)];
}
let f = tj * wsum;
a[at(j, c)] -= f;
for i in (j + 1)..m {
a[at(i, c)] -= f * a[at(i, j)];
}
}
}
}
#[cfg(not(rlx_cpu_blas))]
fn form_q_colmajor(a: &mut [f64], m: usize, n: usize, k: usize, tau: &[f64]) {
let at = |i: usize, j: usize| i + j * m;
for j in k..n {
for i in 0..m {
a[at(i, j)] = 0.0;
}
if j < m {
a[at(j, j)] = 1.0;
}
}
for j in (0..k).rev() {
let tj = tau[j];
if j < n {
a[at(j, j)] = 1.0;
for c in (j + 1)..n {
let mut wsum = a[at(j, c)];
for i in (j + 1)..m {
wsum += a[at(i, j)] * a[at(i, c)];
}
let f = tj * wsum;
a[at(j, c)] -= f;
for i in (j + 1)..m {
a[at(i, c)] -= f * a[at(i, j)];
}
}
}
for i in (j + 1)..m {
a[at(i, j)] *= -tj;
}
a[at(j, j)] = 1.0 - tj;
for i in 0..j {
a[at(i, j)] = 0.0;
}
}
}
#[cfg(not(rlx_cpu_blas))]
#[allow(non_snake_case, clippy::too_many_arguments)]
unsafe fn lapack_dpotrf(
uplo: *const i8,
n: *const i32,
a: *mut f64,
lda: *const i32,
info: *mut i32,
) {
let (uplo, n, lda) = unsafe { (*uplo as u8, *n as usize, *lda as usize) };
let mut buf = vec![0f64; n * n];
unsafe {
for j in 0..n {
for i in 0..n {
buf[i + j * n] = *a.add(i + j * lda);
}
}
}
let rc = cholesky_colmajor(&mut buf, n, uplo == b'U');
unsafe {
*info = rc;
if rc == 0 {
for j in 0..n {
for i in 0..n {
*a.add(i + j * lda) = buf[i + j * n];
}
}
}
}
}
#[cfg(not(rlx_cpu_blas))]
#[allow(non_snake_case, clippy::too_many_arguments)]
unsafe fn lapack_dgetrf(
m: *const i32,
n: *const i32,
a: *mut f64,
lda: *const i32,
ipiv: *mut i32,
info: *mut i32,
) {
let (m, n, lda) = unsafe { (*m as usize, *n as usize, *lda as usize) };
let mut buf = vec![0f64; m * n];
unsafe {
for j in 0..n {
for i in 0..m {
buf[i + j * m] = *a.add(i + j * lda);
}
}
}
let mut ip = vec![0i32; m.min(n)];
let rc = lu_colmajor(&mut buf, m, n, &mut ip);
unsafe {
for j in 0..n {
for i in 0..m {
*a.add(i + j * lda) = buf[i + j * m];
}
}
for (i, &p) in ip.iter().enumerate() {
*ipiv.add(i) = p;
}
*info = rc;
}
}
#[cfg(not(rlx_cpu_blas))]
#[allow(non_snake_case, clippy::too_many_arguments)]
unsafe fn lapack_dsyevd(
jobz: *const i8,
uplo: *const i8,
n: *const i32,
a: *mut f64,
lda: *const i32,
w: *mut f64,
work: *mut f64,
lwork: *const i32,
iwork: *mut i32,
_liwork: *const i32,
info: *mut i32,
) {
let (jobz, uplo, n, lda, lwork) =
unsafe { (*jobz as u8, *uplo as u8, *n as usize, *lda as usize, *lwork) };
if lwork == -1 {
unsafe {
*work = 1.0;
*iwork = 1;
*info = 0;
}
return;
}
let mut full = vec![0f64; n * n];
unsafe {
for j in 0..n {
for i in 0..n {
let v = if uplo == b'U' {
if i <= j {
*a.add(i + j * lda)
} else {
*a.add(j + i * lda)
}
} else if i >= j {
*a.add(i + j * lda)
} else {
*a.add(j + i * lda)
};
full[i * n + j] = v;
}
}
}
let (evals, evecs) = sym_eig_jacobi(full, n);
unsafe {
for (i, &e) in evals.iter().enumerate() {
*w.add(i) = e;
}
if jobz == b'V' {
for j in 0..n {
for i in 0..n {
*a.add(i + j * lda) = evecs[i * n + j];
}
}
}
*info = 0;
}
}
#[cfg(not(rlx_cpu_blas))]
#[allow(non_snake_case, clippy::too_many_arguments)]
unsafe fn lapack_dgeqrf(
m: *const i32,
n: *const i32,
a: *mut f64,
lda: *const i32,
tau: *mut f64,
work: *mut f64,
lwork: *const i32,
info: *mut i32,
) {
let (m, n, lda, lwork) = unsafe { (*m as usize, *n as usize, *lda as usize, *lwork) };
if lwork == -1 {
unsafe {
*work = n.max(1) as f64;
*info = 0;
}
return;
}
let mut buf = vec![0f64; m * n];
unsafe {
for j in 0..n {
for i in 0..m {
buf[i + j * m] = *a.add(i + j * lda);
}
}
}
let k = m.min(n);
let mut tv = vec![0f64; k];
qr_householder_colmajor(&mut buf, m, n, &mut tv);
unsafe {
for j in 0..n {
for i in 0..m {
*a.add(i + j * lda) = buf[i + j * m];
}
}
for (i, &t) in tv.iter().enumerate() {
*tau.add(i) = t;
}
*info = 0;
}
}
#[cfg(not(rlx_cpu_blas))]
#[allow(non_snake_case, clippy::too_many_arguments)]
unsafe fn lapack_dorgqr(
m: *const i32,
n: *const i32,
k: *const i32,
a: *mut f64,
lda: *const i32,
tau: *const f64,
work: *mut f64,
lwork: *const i32,
info: *mut i32,
) {
let (m, n, k, lda, lwork) =
unsafe { (*m as usize, *n as usize, *k as usize, *lda as usize, *lwork) };
if lwork == -1 {
unsafe {
*work = n.max(1) as f64;
*info = 0;
}
return;
}
let mut buf = vec![0f64; m * n];
let mut tv = vec![0f64; k];
unsafe {
for j in 0..n {
for i in 0..m {
buf[i + j * m] = *a.add(i + j * lda);
}
}
for (i, t) in tv.iter_mut().enumerate() {
*t = *tau.add(i);
}
}
form_q_colmajor(&mut buf, m, n, k, &tv);
unsafe {
for j in 0..n {
for i in 0..m {
*a.add(i + j * lda) = buf[i + j * m];
}
}
*info = 0;
}
}
#[cfg(not(rlx_cpu_blas))]
#[allow(non_snake_case, clippy::too_many_arguments)]
unsafe fn lapack_dgesvd(
_jobu: *const i8,
_jobvt: *const i8,
m: *const i32,
n: *const i32,
a: *mut f64,
lda: *const i32,
s: *mut f64,
u: *mut f64,
ldu: *const i32,
vt: *mut f64,
ldvt: *const i32,
work: *mut f64,
lwork: *const i32,
info: *mut i32,
) {
let (m, n, lda, ldu, ldvt, lwork) = unsafe {
(
*m as usize,
*n as usize,
*lda as usize,
*ldu as usize,
*ldvt as usize,
*lwork,
)
};
if lwork == -1 {
unsafe {
*work = 1.0;
*info = 0;
}
return;
}
let k = m.min(n);
let mut am = vec![0f64; m * n];
unsafe {
for i in 0..m {
for j in 0..n {
am[i * n + j] = *a.add(i + j * lda);
}
}
}
let (u_rm, s_v, vt_rm) = svd_thin_rowmajor(&am, m, n);
unsafe {
for (i, &sv) in s_v.iter().enumerate() {
*s.add(i) = sv;
}
for j in 0..k {
for i in 0..m {
*u.add(i + j * ldu) = u_rm[i * k + j];
}
}
for j in 0..n {
for i in 0..k {
*vt.add(i + j * ldvt) = vt_rm[i * n + j];
}
}
*info = 0;
}
}
#[cfg(not(rlx_cpu_blas))]
#[allow(non_snake_case, clippy::too_many_arguments)]
unsafe fn lapack_dgesdd(
_jobz: *const i8,
m: *const i32,
n: *const i32,
a: *mut f64,
lda: *const i32,
s: *mut f64,
u: *mut f64,
ldu: *const i32,
vt: *mut f64,
ldvt: *const i32,
work: *mut f64,
lwork: *const i32,
_iwork: *mut i32,
info: *mut i32,
) {
let (m, n, lda, ldu, ldvt, lwork) = unsafe {
(
*m as usize,
*n as usize,
*lda as usize,
*ldu as usize,
*ldvt as usize,
*lwork,
)
};
if lwork == -1 {
unsafe {
*work = 1.0;
*info = 0;
}
return;
}
let k = m.min(n);
let mut am = vec![0f64; m * n];
unsafe {
for i in 0..m {
for j in 0..n {
am[i * n + j] = *a.add(i + j * lda);
}
}
}
let (u_rm, s_v, vt_rm) = svd_thin_rowmajor(&am, m, n);
unsafe {
for (i, &sv) in s_v.iter().enumerate() {
*s.add(i) = sv;
}
for j in 0..k {
for i in 0..m {
*u.add(i + j * ldu) = u_rm[i * k + j];
}
}
for j in 0..n {
for i in 0..k {
*vt.add(i + j * ldvt) = vt_rm[i * n + j];
}
}
*info = 0;
}
}
#[cfg(not(rlx_cpu_blas))]
#[allow(non_snake_case, clippy::too_many_arguments)]
unsafe fn lapack_dgelsd(
m: *const i32,
n: *const i32,
nrhs: *const i32,
a: *mut f64,
lda: *const i32,
b: *mut f64,
ldb: *const i32,
s: *mut f64,
rcond: *const f64,
rank: *mut i32,
work: *mut f64,
lwork: *const i32,
_iwork: *mut i32,
info: *mut i32,
) {
let (m, n, nrhs, lda, ldb, lwork, rcond) = unsafe {
(
*m as usize,
*n as usize,
*nrhs as usize,
*lda as usize,
*ldb as usize,
*lwork,
*rcond,
)
};
if lwork == -1 {
unsafe {
*work = 1.0;
*info = 0;
}
return;
}
let k = m.min(n);
let mut am = vec![0f64; m * n];
unsafe {
for i in 0..m {
for j in 0..n {
am[i * n + j] = *a.add(i + j * lda);
}
}
}
let (u_rm, s_v, vt_rm) = svd_thin_rowmajor(&am, m, n);
let smax = if k > 0 { s_v[0] } else { 0.0 };
let eff = if rcond < 0.0 { f64::EPSILON } else { rcond };
let thresh = eff * smax;
let mut rk = 0i32;
for &sv in &s_v {
if sv > thresh {
rk += 1;
}
}
let mut xout = vec![0f64; n * nrhs];
unsafe {
for c in 0..nrhs {
let mut z = vec![0f64; k];
for i in 0..k {
if s_v[i] > thresh {
let mut y = 0.0;
for l in 0..m {
y += u_rm[l * k + i] * *b.add(l + c * ldb);
}
z[i] = y / s_v[i];
}
}
for r in 0..n {
let mut xr = 0.0;
for i in 0..k {
xr += vt_rm[i * n + r] * z[i];
}
xout[r * nrhs + c] = xr;
}
}
for c in 0..nrhs {
for r in 0..n {
*b.add(r + c * ldb) = xout[r * nrhs + c];
}
}
for (i, &sv) in s_v.iter().enumerate() {
*s.add(i) = sv;
}
*rank = rk;
*info = 0;
}
}
#[cfg(not(rlx_cpu_blas))]
#[allow(non_snake_case, clippy::too_many_arguments)]
unsafe fn cblas_dtrsm(
order: i32,
side: i32,
uplo: i32,
transa: i32,
diag: i32,
m: i32,
n: i32,
alpha: f64,
a: *const f64,
lda: i32,
b: *mut f64,
ldb: i32,
) {
debug_assert!(
order == ROW_MAJOR && side == CBLAS_LEFT,
"rlx-cpu dtrsm fallback: only row-major left-side is exercised"
);
let _ = (order, side);
let m = m as usize;
let n = n as usize;
let lda = lda as usize;
let ldb = ldb as usize;
let unit = diag == CBLAS_UNIT;
let upper = uplo == CBLAS_UPPER;
let trans = transa != NO_TRANS;
let op_lower = upper == trans; let aat = |i: usize, l: usize| unsafe {
if trans {
*a.add(l * lda + i)
} else {
*a.add(i * lda + l)
}
};
unsafe {
if alpha != 1.0 {
for i in 0..m {
for j in 0..n {
*b.add(i * ldb + j) *= alpha;
}
}
}
if op_lower {
for i in 0..m {
for j in 0..n {
let mut val = *b.add(i * ldb + j);
for l in 0..i {
val -= aat(i, l) * *b.add(l * ldb + j);
}
if !unit {
val /= aat(i, i);
}
*b.add(i * ldb + j) = val;
}
}
} else {
for i in (0..m).rev() {
for j in 0..n {
let mut val = *b.add(i * ldb + j);
for l in (i + 1)..m {
val -= aat(i, l) * *b.add(l * ldb + j);
}
if !unit {
val /= aat(i, i);
}
*b.add(i * ldb + j) = val;
}
}
}
}
}
pub fn dpotrf(a: &mut [f64], n: usize, lower: bool) -> i32 {
assert_eq!(a.len(), n * n, "dpotrf: A must be n×n");
let uplo: i8 = if lower { b'U' as i8 } else { b'L' as i8 };
let nn = n as i32;
let mut info: i32 = 0;
unsafe {
lapack_dpotrf(&uplo, &nn, a.as_mut_ptr(), &nn, &mut info);
}
if info != 0 {
return info;
}
if lower {
for i in 0..n {
for j in (i + 1)..n {
a[i * n + j] = 0.0;
}
}
} else {
for i in 1..n {
for j in 0..i {
a[i * n + j] = 0.0;
}
}
}
info
}
pub fn lu_slogdet(a: &mut [f64], n: usize) -> (f64, f64, f64) {
assert_eq!(a.len(), n * n, "lu_slogdet: A must be n×n");
let mut ipiv = vec![0i32; n];
let mut info: i32 = 0;
let nn = n as i32;
unsafe {
lapack_dgetrf(&nn, &nn, a.as_mut_ptr(), &nn, ipiv.as_mut_ptr(), &mut info);
}
if info > 0 {
return (f64::NEG_INFINITY, 0.0, 0.0); }
let (mut logabs, mut sign, mut det) = (0.0f64, 1.0f64, 1.0f64);
for i in 0..n {
let d = a[i * n + i]; det *= d;
logabs += d.abs().ln();
if d < 0.0 {
sign = -sign;
}
if ipiv[i] != (i as i32 + 1) {
sign = -sign;
det = -det;
}
}
(logabs, sign, det)
}
pub fn dsyevd(a: &mut [f64], w: &mut [f64], n: usize) -> i32 {
assert_eq!(a.len(), n * n);
assert_eq!(w.len(), n);
let jobz: i8 = b'V' as i8;
let uplo: i8 = b'U' as i8;
let nn = n as i32;
let mut info: i32 = 0;
let lwork = (1 + 6 * n + 2 * n * n) as i32;
let liwork = (3 + 5 * n) as i32;
let mut work = vec![0f64; lwork.max(1) as usize];
let mut iwork = vec![0i32; liwork.max(1) as usize];
unsafe {
lapack_dsyevd(
&jobz,
&uplo,
&nn,
a.as_mut_ptr(),
&nn,
w.as_mut_ptr(),
work.as_mut_ptr(),
&lwork,
iwork.as_mut_ptr(),
&liwork,
&mut info,
);
}
info
}
pub fn dgeqrf_full(a: &mut [f64], m: usize, n: usize, q_out: &mut [f64], r_out: &mut [f64]) -> i32 {
assert_eq!(a.len(), m * n, "dgeqrf: A must be m×n");
let k = m.min(n);
assert_eq!(q_out.len(), m * k, "Q must be m×min(m,n)");
assert_eq!(r_out.len(), k * n, "R must be min(m,n)×n");
let mut a_col = transpose_to_col(a, m, n);
let mut tau = vec![0f64; k];
let mm = m as i32;
let nn = n as i32;
let kk = k as i32;
let lwork = (n.max(1)) as i32;
let mut work = vec![0f64; lwork.max(1) as usize];
let mut info: i32 = 0;
unsafe {
lapack_dgeqrf(
&mm,
&nn,
a_col.as_mut_ptr(),
&mm,
tau.as_mut_ptr(),
work.as_mut_ptr(),
&lwork,
&mut info,
);
}
if info != 0 {
return info;
}
for i in 0..k {
for j in 0..n {
let v = if i <= j { a_col[j * m + i] } else { 0.0 };
r_out[i * n + j] = v;
}
}
let mut work2 = vec![0f64; lwork.max(1) as usize];
let mut info2: i32 = 0;
unsafe {
lapack_dorgqr(
&mm,
&kk,
&kk,
a_col.as_mut_ptr(),
&mm,
tau.as_ptr(),
work2.as_mut_ptr(),
&lwork,
&mut info2,
);
}
if info2 != 0 {
return info2;
}
for i in 0..m {
for j in 0..k {
q_out[i * k + j] = a_col[j * m + i];
}
}
0
}
pub fn dgesvd_thin(
a: &mut [f64],
m: usize,
n: usize,
s: &mut [f64],
u: &mut [f64],
vt: &mut [f64],
) -> i32 {
assert_eq!(a.len(), m * n);
let k = m.min(n);
assert_eq!(s.len(), k);
assert_eq!(u.len(), m * k);
assert_eq!(vt.len(), k * n);
let mut a_col = transpose_to_col(a, m, n);
let mut u_col = vec![0f64; m * k];
let mut vt_col = vec![0f64; k * n];
let jobu = b'S' as i8;
let jobvt = b'S' as i8;
let mm = m as i32;
let nn = n as i32;
let ldu = m as i32;
let ldvt = k as i32;
let lwork = (((3 * k + m.max(n)).max(5 * k)) as i32).max(1);
let mut work = vec![0f64; lwork as usize];
let mut info: i32 = 0;
unsafe {
lapack_dgesvd(
&jobu,
&jobvt,
&mm,
&nn,
a_col.as_mut_ptr(),
&mm,
s.as_mut_ptr(),
u_col.as_mut_ptr(),
&ldu,
vt_col.as_mut_ptr(),
&ldvt,
work.as_mut_ptr(),
&lwork,
&mut info,
);
}
if info != 0 {
return info;
}
for i in 0..m {
for j in 0..k {
u[i * k + j] = u_col[j * m + i];
}
}
for i in 0..k {
for j in 0..n {
vt[i * n + j] = vt_col[j * k + i];
}
}
0
}
pub fn qr_thin(a: &[f64], m: usize, n: usize, q: &mut [f64], r: &mut [f64]) -> i32 {
let k = m.min(n);
assert_eq!(a.len(), m * n);
assert_eq!(q.len(), m * k);
assert_eq!(r.len(), k * n);
let mut a_col = transpose_to_col(a, m, n); let (mm, nn, kk) = (m as i32, n as i32, k as i32);
let mut tau = vec![0f64; k];
let mut info: i32 = 0;
let m1: i32 = -1;
let mut wq = [0f64; 1];
unsafe {
lapack_dgeqrf(
&mm,
&nn,
a_col.as_mut_ptr(),
&mm,
tau.as_mut_ptr(),
wq.as_mut_ptr(),
&m1,
&mut info,
);
}
let lwork = (wq[0] as i32).max(1);
let mut work = vec![0f64; lwork as usize];
unsafe {
lapack_dgeqrf(
&mm,
&nn,
a_col.as_mut_ptr(),
&mm,
tau.as_mut_ptr(),
work.as_mut_ptr(),
&lwork,
&mut info,
);
}
if info != 0 {
return info;
}
for i in 0..k {
for j in 0..n {
r[i * n + j] = if i <= j { a_col[i + j * m] } else { 0.0 };
}
}
let mut wq2 = [0f64; 1];
unsafe {
lapack_dorgqr(
&mm,
&kk,
&kk,
a_col.as_mut_ptr(),
&mm,
tau.as_ptr(),
wq2.as_mut_ptr(),
&m1,
&mut info,
);
}
let lwork2 = (wq2[0] as i32).max(1);
let mut work2 = vec![0f64; lwork2 as usize];
unsafe {
lapack_dorgqr(
&mm,
&kk,
&kk,
a_col.as_mut_ptr(),
&mm,
tau.as_ptr(),
work2.as_mut_ptr(),
&lwork2,
&mut info,
);
}
if info != 0 {
return info;
}
for i in 0..m {
for j in 0..k {
q[i * k + j] = a_col[i + j * m];
}
}
info
}
pub fn dgesdd_thin(
a: &mut [f64],
m: usize,
n: usize,
s: &mut [f64],
u: &mut [f64],
vt: &mut [f64],
) -> i32 {
assert_eq!(a.len(), m * n);
let k = m.min(n);
assert_eq!(s.len(), k);
assert_eq!(u.len(), m * k);
assert_eq!(vt.len(), k * n);
let mut a_col = transpose_to_col(a, m, n);
let mut u_col = vec![0f64; m * k];
let mut vt_col = vec![0f64; k * n];
let jobz = b'S' as i8;
let mm = m as i32;
let nn = n as i32;
let ldu = m as i32;
let ldvt = k as i32;
let mut iwork = vec![0i32; 8 * k];
let mut info: i32 = 0;
let mut wq = [0f64; 1];
let m1: i32 = -1;
unsafe {
lapack_dgesdd(
&jobz,
&mm,
&nn,
a_col.as_mut_ptr(),
&mm,
s.as_mut_ptr(),
u_col.as_mut_ptr(),
&ldu,
vt_col.as_mut_ptr(),
&ldvt,
wq.as_mut_ptr(),
&m1,
iwork.as_mut_ptr(),
&mut info,
);
}
if info != 0 {
return info;
}
let lwork = wq[0] as i32;
let mut work = vec![0f64; lwork.max(1) as usize];
unsafe {
lapack_dgesdd(
&jobz,
&mm,
&nn,
a_col.as_mut_ptr(),
&mm,
s.as_mut_ptr(),
u_col.as_mut_ptr(),
&ldu,
vt_col.as_mut_ptr(),
&ldvt,
work.as_mut_ptr(),
&lwork,
iwork.as_mut_ptr(),
&mut info,
);
}
if info != 0 {
return info;
}
for i in 0..m {
for j in 0..k {
u[i * k + j] = u_col[j * m + i];
}
}
for i in 0..k {
for j in 0..n {
vt[i * n + j] = vt_col[j * k + i];
}
}
0
}
pub fn dgelsd_solve(
a: &[f64],
b: &[f64],
m: usize,
n: usize,
nrhs: usize,
rcond: f64,
x: &mut [f64],
) -> i32 {
assert_eq!(a.len(), m * n);
assert_eq!(b.len(), m * nrhs);
assert_eq!(x.len(), n * nrhs);
let k = m.min(n);
let ldb = m.max(n);
let mut a_col = transpose_to_col(a, m, n);
let mut b_col = vec![0f64; ldb * nrhs];
for i in 0..m {
for j in 0..nrhs {
b_col[i + j * ldb] = b[i * nrhs + j];
}
}
let mm = m as i32;
let nn = n as i32;
let nrhs_i = nrhs as i32;
let ldb_i = ldb as i32;
let mut s = vec![0f64; k];
let rc = rcond;
let mut rank: i32 = 0;
let mut info: i32 = 0;
let nlvl = (k as f64).log2().floor() as usize + 1;
let mut iwork = vec![0i32; (3 * k * nlvl + 11 * k).max(1)];
let mut wq = [0f64; 1];
let m1: i32 = -1;
unsafe {
lapack_dgelsd(
&mm,
&nn,
&nrhs_i,
a_col.as_mut_ptr(),
&mm,
b_col.as_mut_ptr(),
&ldb_i,
s.as_mut_ptr(),
&rc,
&mut rank,
wq.as_mut_ptr(),
&m1,
iwork.as_mut_ptr(),
&mut info,
);
}
if info != 0 {
return info;
}
let lwork = wq[0] as i32;
let mut work = vec![0f64; lwork.max(1) as usize];
unsafe {
lapack_dgelsd(
&mm,
&nn,
&nrhs_i,
a_col.as_mut_ptr(),
&mm,
b_col.as_mut_ptr(),
&ldb_i,
s.as_mut_ptr(),
&rc,
&mut rank,
work.as_mut_ptr(),
&lwork,
iwork.as_mut_ptr(),
&mut info,
);
}
if info != 0 {
return info;
}
for i in 0..n {
for j in 0..nrhs {
x[i * nrhs + j] = b_col[i + j * ldb];
}
}
0
}
pub fn dtrsm_lower_or_upper(
a: &[f64],
b: &mut [f64],
n: usize,
nrhs: usize,
lower: bool,
transpose_a: bool,
) {
assert_eq!(a.len(), n * n);
assert_eq!(b.len(), n * nrhs);
unsafe {
cblas_dtrsm(
ROW_MAJOR,
CBLAS_LEFT,
if lower { CBLAS_LOWER } else { CBLAS_UPPER },
if transpose_a { TRANS } else { NO_TRANS },
CBLAS_NON_UNIT,
n as i32,
nrhs as i32,
1.0,
a.as_ptr(),
n as i32,
b.as_mut_ptr(),
nrhs as i32,
);
}
}
fn transpose_to_col(a_row: &[f64], m: usize, n: usize) -> Vec<f64> {
let mut out = vec![0f64; m * n];
for i in 0..m {
for j in 0..n {
out[j * m + i] = a_row[i * n + j];
}
}
out
}
#[inline]
pub(crate) fn f64_accum_enabled() -> bool {
use std::sync::OnceLock;
static ON: OnceLock<bool> = OnceLock::new();
*ON.get_or_init(|| rlx_ir::env_registry::flag("RLX_CPU_MATMUL_F64_ACCUM"))
}
fn dgemm_f32_precise(
a: &[f32],
b: &[f32],
c: &mut [f32],
m: usize,
k: usize,
n: usize,
accumulate: bool,
) {
if m == 0 || n == 0 {
return;
}
let a64: Vec<f64> = a.iter().map(|&x| x as f64).collect();
let b64: Vec<f64> = b.iter().map(|&x| x as f64).collect();
let mut c64 = vec![0f64; m * n];
dgemm(&a64, &b64, &mut c64, m, k, n);
if accumulate {
for (dst, &v) in c.iter_mut().zip(c64.iter()) {
*dst = (*dst as f64 + v) as f32;
}
} else {
for (dst, &v) in c.iter_mut().zip(c64.iter()) {
*dst = v as f32;
}
}
}
#[inline]
pub fn sgemm(a: &[f32], b: &[f32], c: &mut [f32], m: usize, k: usize, n: usize) {
if f64_accum_enabled() {
dgemm_f32_precise(a, b, c, m, k, n, false);
return;
}
unsafe {
cblas_sgemm(
ROW_MAJOR,
NO_TRANS,
NO_TRANS,
m as i32,
n as i32,
k as i32,
1.0,
a.as_ptr(),
k as i32,
b.as_ptr(),
n as i32,
0.0,
c.as_mut_ptr(),
n as i32,
);
}
}
#[inline]
pub fn sgemm_accumulate(a: &[f32], b: &[f32], c: &mut [f32], m: usize, k: usize, n: usize) {
if f64_accum_enabled() {
dgemm_f32_precise(a, b, c, m, k, n, true);
return;
}
unsafe {
cblas_sgemm(
ROW_MAJOR,
NO_TRANS,
NO_TRANS,
m as i32,
n as i32,
k as i32,
1.0,
a.as_ptr(),
k as i32,
b.as_ptr(),
n as i32,
1.0,
c.as_mut_ptr(),
n as i32,
);
}
}
#[inline]
pub fn sgemm_accumulate_auto(a: &[f32], b: &[f32], c: &mut [f32], m: usize, k: usize, n: usize) {
if f64_accum_enabled() {
sgemm_accumulate(a, b, c, m, k, n);
return;
}
if prefer_par_sgemm(m, k, n) {
crate::blas::par_sgemm_accumulate(a, b, c, m, k, n);
} else {
sgemm_accumulate(a, b, c, m, k, n);
}
}
#[inline]
pub fn sgemm_bt(a: &[f32], b: &[f32], c: &mut [f32], m: usize, k: usize, n: usize, alpha: f32) {
unsafe {
cblas_sgemm(
ROW_MAJOR,
NO_TRANS,
TRANS,
m as i32,
n as i32,
k as i32,
alpha,
a.as_ptr(),
k as i32,
b.as_ptr(),
k as i32, 0.0,
c.as_mut_ptr(),
n as i32,
);
}
}
#[inline]
pub fn sgemv_nn(a: &[f32], x: &[f32], y: &mut [f32], n: usize, k: usize, alpha: f32, beta: f32) {
unsafe {
cblas_sgemv(
ROW_MAJOR,
NO_TRANS,
n as i32,
k as i32,
alpha,
a.as_ptr(),
k as i32,
x.as_ptr(),
1,
beta,
y.as_mut_ptr(),
1,
);
}
}
#[inline]
pub fn sgemv_at(a: &[f32], x: &[f32], y: &mut [f32], n: usize, alpha: f32, beta: f32) {
unsafe {
cblas_sgemv(
ROW_MAJOR,
TRANS,
n as i32,
n as i32,
alpha,
a.as_ptr(),
n as i32,
x.as_ptr(),
1,
beta,
y.as_mut_ptr(),
1,
);
}
}
#[inline]
pub fn sger(a: &mut [f32], x: &[f32], y: &[f32], n: usize, alpha: f32) {
unsafe {
cblas_sger(
ROW_MAJOR,
n as i32,
n as i32,
alpha,
x.as_ptr(),
1,
y.as_ptr(),
1,
a.as_mut_ptr(),
n as i32,
);
}
}
#[inline]
pub fn sscal(x: &mut [f32], alpha: f32) {
if x.is_empty() {
return;
}
unsafe {
cblas_sscal(x.len() as i32, alpha, x.as_mut_ptr(), 1);
}
}
#[inline]
pub fn sgemm_strided(
a: &[f32],
b: &[f32],
c: &mut [f32],
m: usize,
k: usize,
n: usize,
lda: usize,
ldc: usize,
) {
unsafe {
cblas_sgemm(
ROW_MAJOR,
NO_TRANS,
NO_TRANS,
m as i32,
n as i32,
k as i32,
1.0,
a.as_ptr(),
lda as i32,
b.as_ptr(),
n as i32,
0.0,
c.as_mut_ptr(),
ldc as i32,
);
}
}
#[cfg(target_arch = "aarch64")]
pub fn bias_add(data: &mut [f32], bias: &[f32], m: usize, n: usize) {
use std::arch::aarch64::*;
let chunks = n / 4;
unsafe {
for row in 0..m {
let base = row * n;
for c in 0..chunks {
let off = base + c * 4;
let v = vld1q_f32(data.as_ptr().add(off));
let b = vld1q_f32(bias.as_ptr().add(c * 4));
vst1q_f32(data.as_mut_ptr().add(off), vaddq_f32(v, b));
}
for i in (chunks * 4)..n {
data[base + i] += bias[i];
}
}
}
}
#[cfg(not(target_arch = "aarch64"))]
pub fn bias_add(data: &mut [f32], bias: &[f32], m: usize, n: usize) {
for row in 0..m {
let base = row * n;
for i in 0..n {
data[base + i] += bias[i];
}
}
}
#[inline]
pub unsafe fn sgemm_general(
a: *const f32,
b: *const f32,
c: *mut f32,
m: usize,
n: usize,
k: usize,
alpha: f32,
beta: f32,
lda: usize,
ldb: usize,
ldc: usize,
trans_a: bool,
trans_b: bool,
) {
unsafe {
cblas_sgemm(
ROW_MAJOR,
if trans_a { TRANS } else { NO_TRANS },
if trans_b { TRANS } else { NO_TRANS },
m as i32,
n as i32,
k as i32,
alpha,
a,
lda as i32,
b,
ldb as i32,
beta,
c,
ldc as i32,
);
}
}
#[inline]
pub fn sgemm_bias(a: &[f32], b: &[f32], bias: &[f32], c: &mut [f32], m: usize, k: usize, n: usize) {
if m <= 8 && crate::cost::hw_model().prefer_neon_sgemm(m, k, n) {
crate::kernels::neon_sgemm_bias_small(a, b, bias, c, m, k, n);
} else if prefer_par_sgemm(m, k, n) {
par_sgemm_bias(a, b, bias, c, m, k, n);
} else {
sgemm(a, b, c, m, k, n);
bias_add(c, bias, m, n);
}
}
#[inline]
pub fn sgemm_epilogue<E: Fn(f32) -> f32>(
a: &[f32],
b: &[f32],
c: &mut [f32],
m: usize,
k: usize,
n: usize,
epilogue: E,
) {
sgemm(a, b, c, m, k, n);
for v in c.iter_mut() {
*v = epilogue(*v);
}
}
#[inline]
pub fn sgemm_bias_epilogue<E: Fn(f32) -> f32>(
a: &[f32],
b: &[f32],
bias: &[f32],
c: &mut [f32],
m: usize,
k: usize,
n: usize,
activation: E,
) {
sgemm(a, b, c, m, k, n);
for i in 0..m {
let row = &mut c[i * n..(i + 1) * n];
for (j, v) in row.iter_mut().enumerate() {
*v = activation(*v + bias[j]);
}
}
}
#[inline]
pub fn sgemm_auto(a: &[f32], b: &[f32], c: &mut [f32], m: usize, k: usize, n: usize) {
#[cfg(feature = "parity-gemm")]
#[allow(clippy::needless_return)]
{
sgemm_via_gemm_crate(a, b, c, m, k, n);
return;
}
#[cfg(all(rlx_cpu_amx_sme, not(feature = "parity-gemm")))]
{
if crate::intrinsics::apple_amx::sme::dispatch_enabled()
&& crate::intrinsics::apple_amx::sme::worth_sme(m, k, n)
{
crate::intrinsics::apple_amx::sme::sme_sgemm(a, b, c, m, k, n);
return;
}
}
#[cfg(all(rlx_cpu_amx_sme, not(feature = "parity-gemm")))]
{
if crate::intrinsics::apple_amx::sme::bf16_dispatch_enabled()
&& crate::intrinsics::apple_amx::sme::worth_sme(m, k, n)
{
crate::intrinsics::apple_amx::sme::sme_sgemm_bf16(a, b, c, m, k, n);
return;
}
}
#[cfg(all(rlx_cpu_amx_bnns, not(feature = "parity-gemm")))]
{
if crate::intrinsics::apple_amx::bnns::dispatch_enabled_f16()
&& crate::intrinsics::apple_amx::bnns::matmul_f32_via_f16(a, b, c, m, k, n)
{
return;
}
if crate::intrinsics::apple_amx::bnns::dispatch_enabled()
&& crate::intrinsics::apple_amx::bnns::matmul_f32_via_bf16(a, b, c, m, k, n)
{
return;
}
}
#[cfg(not(feature = "parity-gemm"))]
if m <= 8 && crate::cost::hw_model().prefer_neon_sgemm(m, k, n) {
crate::kernels::neon_sgemm_small(a, b, c, m, k, n);
return;
}
#[cfg(not(feature = "parity-gemm"))]
{
let flops = (m as u64).saturating_mul(k as u64).saturating_mul(n as u64);
let _ = flops;
if prefer_par_sgemm(m, k, n) {
par_sgemm(a, b, c, m, k, n);
} else {
sgemm(a, b, c, m, k, n);
}
}
}
#[inline]
fn prefer_par_sgemm(m: usize, k: usize, n: usize) -> bool {
let workers = crate::pool::num_threads();
if workers <= 1 {
return false;
}
let flops = (m as u64).saturating_mul(k as u64).saturating_mul(n as u64);
if flops < 1_000_000 {
return false;
}
m.max(n) >= workers.saturating_mul(8)
}
pub fn par_sgemm(a: &[f32], b: &[f32], c: &mut [f32], m: usize, k: usize, n: usize) {
let workers = crate::pool::num_threads();
if !prefer_par_sgemm(m, k, n) {
sgemm(a, b, c, m, k, n);
return;
}
let a_addr = a.as_ptr() as usize;
let b_addr = b.as_ptr() as usize;
let c_addr = c.as_mut_ptr() as usize;
let flops = (m as u64).saturating_mul(k as u64).saturating_mul(n as u64);
if m >= 256 && n >= 256 && flops >= 200_000_000 {
let tiles_m = ((workers as f64).sqrt().round() as usize).clamp(2, 8);
let tiles_n = (workers / tiles_m).max(2);
let n_tiles = tiles_m * tiles_n;
let m_chunk = m.div_ceil(tiles_m);
let n_chunk = n.div_ceil(tiles_n);
crate::pool::par_for(n_tiles, 1, &|off, cnt| {
for t in off..off + cnt {
let tm = t / tiles_n;
let tn = t % tiles_n;
let m0 = tm * m_chunk;
let n0 = tn * n_chunk;
if m0 >= m || n0 >= n {
continue;
}
let local_m = (m0 + m_chunk).min(m) - m0;
let local_n = (n0 + n_chunk).min(n) - n0;
unsafe {
cblas_sgemm(
101,
111,
111,
local_m as i32,
local_n as i32,
k as i32,
1.0,
(a_addr as *const f32).add(m0 * k),
k as i32,
(b_addr as *const f32).add(n0),
n as i32,
0.0,
(c_addr as *mut f32).add(m0 * n + n0),
n as i32,
);
}
}
});
return;
}
if n >= m {
let chunk = n.div_ceil(workers);
crate::pool::par_for(workers, 1, &|off, cnt| {
for w in off..off + cnt {
let n_start = w * chunk;
if n_start >= n {
continue;
}
let n_end = (n_start + chunk).min(n);
let local_n = n_end - n_start;
unsafe {
cblas_sgemm(
101,
111,
111,
m as i32,
local_n as i32,
k as i32,
1.0,
a_addr as *const f32,
k as i32,
(b_addr as *const f32).add(n_start),
n as i32,
0.0,
(c_addr as *mut f32).add(n_start),
n as i32,
);
}
}
});
} else {
let chunk = m.div_ceil(workers);
crate::pool::par_for(workers, 1, &|off, cnt| {
for w in off..off + cnt {
let m_start = w * chunk;
if m_start >= m {
continue;
}
let m_end = (m_start + chunk).min(m);
let local_m = m_end - m_start;
unsafe {
cblas_sgemm(
101,
111,
111,
local_m as i32,
n as i32,
k as i32,
1.0,
(a_addr as *const f32).add(m_start * k),
k as i32,
b_addr as *const f32,
n as i32,
0.0,
(c_addr as *mut f32).add(m_start * n),
n as i32,
);
}
}
});
}
}
pub fn par_sgemm_accumulate(a: &[f32], b: &[f32], c: &mut [f32], m: usize, k: usize, n: usize) {
let workers = crate::pool::num_threads();
if !prefer_par_sgemm(m, k, n) {
sgemm_accumulate(a, b, c, m, k, n);
return;
}
let a_addr = a.as_ptr() as usize;
let b_addr = b.as_ptr() as usize;
let c_addr = c.as_mut_ptr() as usize;
let flops = (m as u64).saturating_mul(k as u64).saturating_mul(n as u64);
if m >= 256 && n >= 256 && flops >= 200_000_000 {
let tiles_m = ((workers as f64).sqrt().round() as usize).clamp(2, 8);
let tiles_n = (workers / tiles_m).max(2);
let n_tiles = tiles_m * tiles_n;
let m_chunk = m.div_ceil(tiles_m);
let n_chunk = n.div_ceil(tiles_n);
crate::pool::par_for(n_tiles, 1, &|off, cnt| {
for t in off..off + cnt {
let tm = t / tiles_n;
let tn = t % tiles_n;
let m0 = tm * m_chunk;
let n0 = tn * n_chunk;
if m0 >= m || n0 >= n {
continue;
}
let local_m = (m0 + m_chunk).min(m) - m0;
let local_n = (n0 + n_chunk).min(n) - n0;
unsafe {
cblas_sgemm(
101,
111,
111,
local_m as i32,
local_n as i32,
k as i32,
1.0,
(a_addr as *const f32).add(m0 * k),
k as i32,
(b_addr as *const f32).add(n0),
n as i32,
1.0,
(c_addr as *mut f32).add(m0 * n + n0),
n as i32,
);
}
}
});
return;
}
if n >= m {
let chunk = n.div_ceil(workers);
crate::pool::par_for(workers, 1, &|off, cnt| {
for w in off..off + cnt {
let n_start = w * chunk;
if n_start >= n {
continue;
}
let n_end = (n_start + chunk).min(n);
let local_n = n_end - n_start;
unsafe {
cblas_sgemm(
101,
111,
111,
m as i32,
local_n as i32,
k as i32,
1.0,
a_addr as *const f32,
k as i32,
(b_addr as *const f32).add(n_start),
n as i32,
1.0,
(c_addr as *mut f32).add(n_start),
n as i32,
);
}
}
});
} else {
let chunk = m.div_ceil(workers);
crate::pool::par_for(workers, 1, &|off, cnt| {
for w in off..off + cnt {
let m_start = w * chunk;
if m_start >= m {
continue;
}
let m_end = (m_start + chunk).min(m);
let local_m = m_end - m_start;
unsafe {
cblas_sgemm(
101,
111,
111,
local_m as i32,
n as i32,
k as i32,
1.0,
(a_addr as *const f32).add(m_start * k),
k as i32,
b_addr as *const f32,
n as i32,
1.0,
(c_addr as *mut f32).add(m_start * n),
n as i32,
);
}
}
});
}
}
#[cfg(feature = "parity-gemm")]
fn sgemm_via_gemm_crate(a: &[f32], b: &[f32], c: &mut [f32], m: usize, k: usize, n: usize) {
use gemm::{Parallelism, gemm};
let cfg = crate::config::RuntimeConfig::global();
let workers = cfg.pool_workers + 1;
let par = if workers > 1 {
Parallelism::Rayon(workers)
} else {
Parallelism::None
};
unsafe {
gemm(
m,
n,
k,
c.as_mut_ptr(),
1, n as isize, false, a.as_ptr(),
1, k as isize, b.as_ptr(),
1, n as isize, 0.0, 1.0, false, false, false, par,
);
}
}
pub fn par_sgemm_bias(
a: &[f32],
b: &[f32],
bias: &[f32],
c: &mut [f32],
m: usize,
k: usize,
n: usize,
) {
let workers = crate::pool::num_threads();
if !prefer_par_sgemm(m, k, n) {
sgemm_bias(a, b, bias, c, m, k, n);
return;
}
let a_addr = a.as_ptr() as usize;
let b_addr = b.as_ptr() as usize;
let bias_addr = bias.as_ptr() as usize;
let c_addr = c.as_mut_ptr() as usize;
if n >= m {
let chunk = n.div_ceil(workers);
crate::pool::par_for(workers, 1, &|off, cnt| {
for w in off..off + cnt {
let n_start = w * chunk;
if n_start >= n {
continue;
}
let n_end = (n_start + chunk).min(n);
let local_n = n_end - n_start;
unsafe {
cblas_sgemm(
101,
111,
111,
m as i32,
local_n as i32,
k as i32,
1.0,
a_addr as *const f32,
k as i32,
(b_addr as *const f32).add(n_start),
n as i32,
0.0,
(c_addr as *mut f32).add(n_start),
n as i32,
);
let local_bias =
std::slice::from_raw_parts((bias_addr as *const f32).add(n_start), local_n);
let local_c =
std::slice::from_raw_parts_mut((c_addr as *mut f32).add(n_start), m * n);
for row in 0..m {
let base = row * n;
for i in 0..local_n {
local_c[base + i] += local_bias[i];
}
}
}
}
});
} else {
let chunk = m.div_ceil(workers);
crate::pool::par_for(workers, 1, &|off, cnt| {
for w in off..off + cnt {
let m_start = w * chunk;
if m_start >= m {
continue;
}
let m_end = (m_start + chunk).min(m);
let local_m = m_end - m_start;
unsafe {
cblas_sgemm(
101,
111,
111,
local_m as i32,
n as i32,
k as i32,
1.0,
(a_addr as *const f32).add(m_start * k),
k as i32,
b_addr as *const f32,
n as i32,
0.0,
(c_addr as *mut f32).add(m_start * n),
n as i32,
);
let bias_sl = std::slice::from_raw_parts(bias_addr as *const f32, n);
let local_c = std::slice::from_raw_parts_mut(
(c_addr as *mut f32).add(m_start * n),
local_m * n,
);
for row in 0..local_m {
let base = row * n;
for i in 0..n {
local_c[base + i] += bias_sl[i];
}
}
}
}
});
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sgemm_identity() {
let a = [1.0, 0.0, 0.0, 1.0f32];
let b = [3.0, 4.0, 5.0, 6.0f32];
let mut c = [0.0f32; 4];
sgemm(&a, &b, &mut c, 2, 2, 2);
assert_eq!(c, [3.0, 4.0, 5.0, 6.0]);
}
#[test]
fn sgemm_rectangular() {
let a = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0f32]; let b = [1.0, 0.0, 0.0, 1.0, 1.0, 0.0f32]; let mut c = [0.0f32; 4];
sgemm(&a, &b, &mut c, 2, 3, 2);
assert_eq!(c, [4.0, 2.0, 10.0, 5.0]);
}
#[test]
fn sgemm_bias_test() {
let a = [1.0, 0.0, 0.0, 1.0f32]; let b = [3.0, 4.0, 5.0, 6.0f32]; let bias = [10.0, 20.0f32]; let mut c = [0.0f32; 4];
sgemm_bias(&a, &b, &bias, &mut c, 2, 2, 2);
assert_eq!(c, [13.0, 24.0, 15.0, 26.0]);
}
#[test]
fn dgemm_identity() {
let a = [1.0, 0.0, 0.0, 1.0f64];
let b = [3.0, 4.0, 5.0, 6.0f64];
let mut c = [0.0f64; 4];
dgemm(&a, &b, &mut c, 2, 2, 2);
assert_eq!(c, [3.0, 4.0, 5.0, 6.0]);
}
#[test]
fn dgemm_f32_precise_matches_f64_reference() {
for &(m, k, n) in &[(2usize, 3usize, 4usize), (3, 512, 2), (1, 4096, 1)] {
let mut s: u32 = 0x1234_5678 ^ (k as u32);
let mut rng = || {
s ^= s << 13;
s ^= s >> 17;
s ^= s << 5;
(s as f32 / u32::MAX as f32) - 0.5
};
let a: Vec<f32> = (0..m * k).map(|_| rng()).collect();
let b: Vec<f32> = (0..k * n).map(|_| rng()).collect();
let mut want = vec![0f32; m * n];
for i in 0..m {
for j in 0..n {
let mut acc = 0f64;
for p in 0..k {
acc += a[i * k + p] as f64 * b[p * n + j] as f64;
}
want[i * n + j] = acc as f32;
}
}
let mut got = vec![0f32; m * n];
dgemm_f32_precise(&a, &b, &mut got, m, k, n, false);
assert_eq!(got, want, "precise GEMM != f64 oracle at ({m},{k},{n})");
}
}
#[test]
fn dgemm_f32_precise_survives_cancellation() {
let big = 16_777_216.0f32; let a = [big, 1.0, -big]; let b = [1.0f32, 1.0, 1.0]; let mut c = [0.0f32];
dgemm_f32_precise(&a, &b, &mut c, 1, 3, 1, false);
assert_eq!(c[0], 1.0, "f64 accumulation should recover the exact 1.0");
let mut c2 = [10.0f32];
dgemm_f32_precise(&a, &b, &mut c2, 1, 3, 1, true);
assert_eq!(c2[0], 11.0);
}
#[test]
fn dgesv_2x2_known_solution() {
let mut a = [2.0, 1.0, 1.0, 3.0_f64];
let mut b = [5.0, 10.0_f64];
let info = dgesv(&mut a, &mut b, 2, 1);
assert_eq!(info, 0, "dgesv signaled singular: info={info}");
let want = [1.0, 3.0_f64];
for (i, (g, w)) in b.iter().zip(want.iter()).enumerate() {
assert!((g - w).abs() < 1e-12, "x[{i}] = {g}, expected {w}");
}
}
#[test]
fn dgesv_3x3_general() {
let a_orig = [4.0, -1.0, 0.0, -1.0, 4.0, -1.0, 0.0, -1.0, 4.0_f64];
let mut a = a_orig;
let mut b = [1.0, 0.0, -1.0_f64];
let info = dgesv(&mut a, &mut b, 3, 1);
assert_eq!(info, 0);
let mut residual = [0.0_f64; 3];
for i in 0..3 {
for j in 0..3 {
residual[i] += a_orig[i * 3 + j] * b[j];
}
}
let want_b = [1.0, 0.0, -1.0_f64];
for i in 0..3 {
assert!(
(residual[i] - want_b[i]).abs() < 1e-12,
"residual[{i}] = {} vs {}",
residual[i],
want_b[i]
);
}
}
#[test]
fn sgemm_bt_test() {
let q = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0f32]; let k = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0f32]; let mut scores = [0.0f32; 4];
sgemm_bt(&q, &k, &mut scores, 2, 3, 2, 1.0);
assert_eq!(scores, [1.0, 0.0, 0.0, 1.0]);
}
#[test]
fn sgemm_epilogue_matches_post_pass() {
let a = [1.0f32, -2.0, 3.0, -4.0]; let b = [1.0f32, 0.0, 0.0, 1.0]; let mut c1 = [0f32; 4];
let mut c2 = [0f32; 4];
sgemm(&a, &b, &mut c1, 2, 2, 2);
sgemm_epilogue(&a, &b, &mut c2, 2, 2, 2, |x| x);
assert_eq!(c1, c2);
let mut c3 = [0f32; 4];
sgemm_epilogue(&a, &b, &mut c3, 2, 2, 2, |x| x.max(0.0));
assert_eq!(c3, [1.0, 0.0, 3.0, 0.0]);
}
#[test]
fn sgemm_bias_epilogue_matches_reference() {
let a = [1.0f32, 2.0, 3.0, 4.0]; let b = [1.0f32, 0.0, 0.0, 1.0]; let bias = [10.0f32, 100.0];
let mut c = [0f32; 4];
sgemm_bias_epilogue(&a, &b, &bias, &mut c, 2, 2, 2, |x| x.max(0.0));
assert_eq!(c, [11.0, 102.0, 13.0, 104.0]);
}
}
#[inline]
#[allow(dead_code)]
fn bf16_bits_to_f32(bits: u16) -> f32 {
f32::from_bits((bits as u32) << 16)
}
#[inline]
fn saxpy_bf16(scale: f32, b: &[u16], acc: &mut [f32]) {
#[cfg(target_arch = "aarch64")]
unsafe {
use std::arch::aarch64::*;
let n = acc.len();
let sv = vdupq_n_f32(scale);
let mut nn = 0;
while nn + 8 <= n {
let bh = vld1q_u16(b.as_ptr().add(nn));
let lo = vreinterpretq_f32_u32(vshlq_n_u32::<16>(vmovl_u16(vget_low_u16(bh))));
let hi = vreinterpretq_f32_u32(vshlq_n_u32::<16>(vmovl_high_u16(bh)));
let a0 = vld1q_f32(acc.as_ptr().add(nn));
let a1 = vld1q_f32(acc.as_ptr().add(nn + 4));
vst1q_f32(acc.as_mut_ptr().add(nn), vfmaq_f32(a0, sv, lo));
vst1q_f32(acc.as_mut_ptr().add(nn + 4), vfmaq_f32(a1, sv, hi));
nn += 8;
}
while nn < n {
acc[nn] += scale * f32::from_bits((b[nn] as u32) << 16);
nn += 1;
}
}
#[cfg(not(target_arch = "aarch64"))]
for (v, &bw) in acc.iter_mut().zip(b) {
*v += scale * bf16_bits_to_f32(bw);
}
}
pub fn sgemm_bf16_rhs(a: &[f32], b: &[u16], c: &mut [f32], m: usize, k: usize, n: usize) {
use rayon::prelude::*;
if m == 1 {
let nthreads = rayon::current_num_threads().max(1);
let kchunk = k.div_ceil(nthreads).max(1);
let partials: Vec<Vec<f32>> = (0..nthreads)
.into_par_iter()
.map(|t| {
let (k0, k1) = ((t * kchunk).min(k), ((t + 1) * kchunk).min(k));
let mut acc = vec![0f32; n];
for kk in k0..k1 {
saxpy_bf16(a[kk], &b[kk * n..kk * n + n], &mut acc);
}
acc
})
.collect();
c.par_iter_mut().enumerate().for_each(|(j, cj)| {
*cj = partials.iter().map(|p| p[j]).sum();
});
} else {
c.par_chunks_mut(n).enumerate().for_each(|(i, crow)| {
crow.iter_mut().for_each(|v| *v = 0.0);
for kk in 0..k {
saxpy_bf16(a[i * k + kk], &b[kk * n..kk * n + n], crow);
}
});
}
}
#[inline]
fn saxpy_f16(scale: f32, b: &[u16], acc: &mut [f32]) {
for (v, &bw) in acc.iter_mut().zip(b) {
*v += scale * half::f16::from_bits(bw).to_f32();
}
}
pub fn sgemm_f16_rhs(a: &[f32], b: &[u16], c: &mut [f32], m: usize, k: usize, n: usize) {
use rayon::prelude::*;
if m == 1 {
let nthreads = rayon::current_num_threads().max(1);
let kchunk = k.div_ceil(nthreads).max(1);
let partials: Vec<Vec<f32>> = (0..nthreads)
.into_par_iter()
.map(|t| {
let (k0, k1) = ((t * kchunk).min(k), ((t + 1) * kchunk).min(k));
let mut acc = vec![0f32; n];
for kk in k0..k1 {
saxpy_f16(a[kk], &b[kk * n..kk * n + n], &mut acc);
}
acc
})
.collect();
c.par_iter_mut().enumerate().for_each(|(j, cj)| {
*cj = partials.iter().map(|p| p[j]).sum();
});
} else {
c.par_chunks_mut(n).enumerate().for_each(|(i, crow)| {
crow.iter_mut().for_each(|v| *v = 0.0);
for kk in 0..k {
saxpy_f16(a[i * k + kk], &b[kk * n..kk * n + n], crow);
}
});
}
}
#[cfg(test)]
mod bf16_gemm_tests {
use super::*;
#[test]
fn sgemm_bf16_rhs_matches_f32_rounded() {
for &(m, k, n) in &[(1usize, 128usize, 64usize), (3, 96, 40)] {
let a: Vec<f32> = (0..m * k)
.map(|i| ((i * 7 % 13) as f32 - 6.0) * 0.1)
.collect();
let bf: Vec<f32> = (0..k * n)
.map(|i| ((i * 5 % 11) as f32 - 5.0) * 0.1)
.collect();
let b16: Vec<u16> = bf.iter().map(|&x| (x.to_bits() >> 16) as u16).collect();
let brnd: Vec<f32> = b16
.iter()
.map(|&w| f32::from_bits((w as u32) << 16))
.collect();
let mut cref = vec![0f32; m * n];
sgemm(&a, &brnd, &mut cref, m, k, n);
let mut cbf = vec![0f32; m * n];
sgemm_bf16_rhs(&a, &b16, &mut cbf, m, k, n);
let worst = cref
.iter()
.zip(&cbf)
.map(|(x, y)| (x - y).abs())
.fold(0.0, f32::max);
assert!(worst < 1e-4, "bf16 gemm ({m}x{k}x{n}) mismatch {worst:.2e}");
}
}
}
#[cfg(test)]
mod bf16_bench {
use super::*;
use std::time::Instant;
#[test]
#[ignore]
fn bench_head_bf16_vs_f32() {
let (m, k, n) = (1usize, 7168usize, 163840usize); let a: Vec<f32> = (0..m * k)
.map(|i| ((i % 97) as f32 - 48.0) * 0.01)
.collect();
let bf: Vec<f32> = (0..k * n)
.map(|i| ((i % 101) as f32 - 50.0) * 0.01)
.collect();
let b16: Vec<u16> = bf.iter().map(|&x| (x.to_bits() >> 16) as u16).collect(); let bf32_kn: Vec<f32> = b16
.iter()
.map(|&w| f32::from_bits((w as u32) << 16))
.collect();
let mut c = vec![0f32; m * n];
for _ in 0..2 {
sgemm(&a, &bf32_kn, &mut c, m, k, n);
} let t = Instant::now();
for _ in 0..5 {
sgemm(&a, &bf32_kn, &mut c, m, k, n);
}
let f32_ms = t.elapsed().as_secs_f64() * 1000.0 / 5.0;
for _ in 0..2 {
sgemm_bf16_rhs(&a, &b16, &mut c, m, k, n);
}
let t = Instant::now();
for _ in 0..5 {
sgemm_bf16_rhs(&a, &b16, &mut c, m, k, n);
}
let bf16_ms = t.elapsed().as_secs_f64() * 1000.0 / 5.0;
eprintln!(
"HEAD GEMV [1,{k}]x[{k},{n}]: cblas-f32 {f32_ms:.1}ms bf16-neon {bf16_ms:.1}ms ({:.2}x)",
f32_ms / bf16_ms
);
}
}
#[cfg(test)]
mod linalg_fallback_tests {
use super::*;
fn matmul_rm(a: &[f64], b: &[f64], m: usize, k: usize, n: usize) -> Vec<f64> {
let mut c = vec![0f64; m * n];
for i in 0..m {
for j in 0..n {
let mut s = 0.0;
for p in 0..k {
s += a[i * k + p] * b[p * n + j];
}
c[i * n + j] = s;
}
}
c
}
fn transpose_rm(a: &[f64], r: usize, c: usize) -> Vec<f64> {
let mut t = vec![0f64; r * c];
for i in 0..r {
for j in 0..c {
t[j * r + i] = a[i * c + j];
}
}
t
}
#[test]
fn dpotrf_lower_reconstructs_spd() {
let m = [2.0, 0.0, 1.0, 0.5, 1.0, 0.0, 0.0, 0.3, 1.5];
let mt = transpose_rm(&m, 3, 3);
let a = matmul_rm(&mt, &m, 3, 3, 3);
let mut lf = a.clone();
let info = dpotrf(&mut lf, 3, true);
assert_eq!(info, 0, "dpotrf flagged non-SPD");
let lt = transpose_rm(&lf, 3, 3);
let recon = matmul_rm(&lf, <, 3, 3, 3);
for i in 0..9 {
assert!(
(recon[i] - a[i]).abs() < 1e-9,
"L·Lᵀ[{i}]={} A={}",
recon[i],
a[i]
);
}
}
#[test]
fn dpotrf_rejects_non_spd() {
let mut a = [1.0, 2.0, 2.0, 1.0];
let info = dpotrf(&mut a, 2, true);
assert!(info > 0, "expected non-SPD signal, got {info}");
}
#[test]
fn dsyevd_eigenpairs_ascending() {
let a = [2.0, 1.0, 0.0, 1.0, 2.0, 1.0, 0.0, 1.0, 2.0];
let a_orig = a;
let mut af = a;
let mut w = [0.0; 3];
let info = dsyevd(&mut af, &mut w, 3);
assert_eq!(info, 0);
let want = [2.0 - 2f64.sqrt(), 2.0, 2.0 + 2f64.sqrt()];
for i in 0..3 {
if i > 0 {
assert!(w[i] >= w[i - 1] - 1e-12, "eigenvalues not ascending");
}
assert!(
(w[i] - want[i]).abs() < 1e-9,
"w[{i}]={} want {}",
w[i],
want[i]
);
}
let mut recon = [0.0f64; 9];
for j in 0..3 {
let v: Vec<f64> = (0..3).map(|i| af[j * 3 + i]).collect();
for i in 0..3 {
let av: f64 = (0..3).map(|l| a_orig[i * 3 + l] * v[l]).sum();
assert!((av - w[j] * v[i]).abs() < 1e-9, "A·v≠w·v j={j} i={i}");
for c in 0..3 {
recon[i * 3 + c] += w[j] * v[i] * v[c];
}
}
}
for idx in 0..9 {
assert!(
(recon[idx] - a_orig[idx]).abs() < 1e-9,
"spectral recon[{idx}]={} A={}",
recon[idx],
a_orig[idx]
);
}
}
#[test]
fn dgeqrf_full_reconstructs_and_orthonormal() {
let (m, n, k) = (4usize, 3usize, 3usize);
let a = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 1.0, 0.0, 1.0];
let mut af = a;
let mut q = vec![0.0; m * k];
let mut r = vec![0.0; k * n];
let info = dgeqrf_full(&mut af, m, n, &mut q, &mut r);
assert_eq!(info, 0);
let recon = matmul_rm(&q, &r, m, k, n);
for i in 0..m * n {
assert!((recon[i] - a[i]).abs() < 1e-9, "Q·R[{i}]");
}
let qt = transpose_rm(&q, m, k);
let qtq = matmul_rm(&qt, &q, k, m, k);
for i in 0..k {
for j in 0..k {
let e = if i == j { 1.0 } else { 0.0 };
assert!((qtq[i * k + j] - e).abs() < 1e-9, "Qᵀ·Q[{i},{j}]");
}
}
}
#[test]
fn dgesvd_thin_reconstructs_both_shapes() {
for &(m, n) in &[(4usize, 2usize), (2usize, 4usize), (3, 3)] {
let k = m.min(n);
let a: Vec<f64> = (0..m * n)
.map(|i| ((i * 7 % 11) as f64) - 5.0 + 0.25 * i as f64)
.collect();
let mut af = a.clone();
let mut s = vec![0.0; k];
let mut u = vec![0.0; m * k];
let mut vt = vec![0.0; k * n];
let info = dgesvd_thin(&mut af, m, n, &mut s, &mut u, &mut vt);
assert_eq!(info, 0, "svd info m={m} n={n}");
for i in 1..k {
assert!(s[i] <= s[i - 1] + 1e-12, "s not descending");
}
let mut us = vec![0.0; m * k];
for i in 0..m {
for j in 0..k {
us[i * k + j] = u[i * k + j] * s[j];
}
}
let recon = matmul_rm(&us, &vt, m, k, n);
for i in 0..m * n {
assert!(
(recon[i] - a[i]).abs() < 1e-8,
"SVD recon m={m} n={n} [{i}] got {} want {}",
recon[i],
a[i]
);
}
}
}
#[test]
fn dgesdd_thin_reconstructs() {
let (m, n, k) = (5usize, 3usize, 3usize);
let a: Vec<f64> = (0..m * n).map(|i| (i as f64).sin() * 3.0 + 1.0).collect();
let mut af = a.clone();
let mut s = vec![0.0; k];
let mut u = vec![0.0; m * k];
let mut vt = vec![0.0; k * n];
let info = dgesdd_thin(&mut af, m, n, &mut s, &mut u, &mut vt);
assert_eq!(info, 0);
let mut us = vec![0.0; m * k];
for i in 0..m {
for j in 0..k {
us[i * k + j] = u[i * k + j] * s[j];
}
}
let recon = matmul_rm(&us, &vt, m, k, n);
for i in 0..m * n {
assert!((recon[i] - a[i]).abs() < 1e-8, "dgesdd recon[{i}]");
}
}
#[test]
fn dgelsd_overdetermined_lstsq() {
let xs = [0.0, 1.0, 2.0, 3.0];
let mut a = vec![0.0; 4 * 2];
let mut b = vec![0.0; 4];
for (i, &x) in xs.iter().enumerate() {
a[i * 2] = x;
a[i * 2 + 1] = 1.0;
b[i] = 2.0 * x + 1.0;
}
let mut x = vec![0.0; 2];
let info = dgelsd_solve(&a, &b, 4, 2, 1, -1.0, &mut x);
assert_eq!(info, 0);
assert!(
(x[0] - 2.0).abs() < 1e-9 && (x[1] - 1.0).abs() < 1e-9,
"lstsq x={x:?}"
);
}
#[test]
fn lu_slogdet_diagonal_known() {
let mut a = [2.0, 0.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 4.0];
let (logabs, sign, det) = lu_slogdet(&mut a, 3);
assert!((det - 24.0).abs() < 1e-9, "det={det}");
assert!((sign - 1.0).abs() < 1e-12);
assert!((logabs - 24f64.ln()).abs() < 1e-9);
}
#[test]
fn dtrsm_lower_solve_roundtrips() {
let l = [2.0, 0.0, 0.0, 1.0, 3.0, 0.0, 0.5, -1.0, 4.0]; let xtrue = [1.0, 2.0, 3.0, -1.0, 0.5, 0.5]; let mut b = matmul_rm(&l, &xtrue, 3, 3, 2); dtrsm_lower_or_upper(&l, &mut b, 3, 2, true, false);
for i in 0..6 {
assert!(
(b[i] - xtrue[i]).abs() < 1e-9,
"dtrsm X[{i}]={} want {}",
b[i],
xtrue[i]
);
}
}
#[test]
fn sgemm_fallback_ikj_matches_and_timing() {
let (m, k, n) = (96usize, 128usize, 112usize);
let mut s: u32 = 0x9e37_79b9;
let mut rng = || {
s ^= s << 13;
s ^= s >> 17;
s ^= s << 5;
(s as f32 / u32::MAX as f32) - 0.5
};
let a: Vec<f32> = (0..m * k).map(|_| rng()).collect();
let b: Vec<f32> = (0..k * n).map(|_| rng()).collect();
let mut want = vec![0f32; m * n];
for i in 0..m {
for j in 0..n {
let mut acc = 0f32;
for p in 0..k {
acc += a[i * k + p] * b[p * n + j];
}
want[i * n + j] = acc;
}
}
let mut got = vec![0f32; m * n];
sgemm(&a, &b, &mut got, m, k, n);
for idx in 0..m * n {
let tol = 1e-4 * (1.0 + want[idx].abs());
assert!(
(got[idx] - want[idx]).abs() <= tol,
"sgemm[{idx}]={} want {}",
got[idx],
want[idx]
);
}
use std::time::Instant;
let (bm, bk, bn) = (256usize, 256usize, 256usize);
let ba: Vec<f32> = (0..bm * bk).map(|_| rng()).collect();
let bb: Vec<f32> = (0..bk * bn).map(|_| rng()).collect();
let mut bc = vec![0f32; bm * bn];
let flop = 2.0 * bm as f64 * bk as f64 * bn as f64;
let naive = |a: &[f32], b: &[f32], c: &mut [f32]| {
for i in 0..bm {
for j in 0..bn {
let mut acc = 0f32;
for p in 0..bk {
acc += a[i * bk + p] * b[p * bn + j];
}
c[i * bn + j] = acc;
}
}
};
for _ in 0..2 {
sgemm(&ba, &bb, &mut bc, bm, bk, bn);
naive(&ba, &bb, &mut bc);
}
let iters = 20;
let tf = Instant::now();
for _ in 0..iters {
sgemm(&ba, &bb, &mut bc, bm, bk, bn);
}
let fast_ms = tf.elapsed().as_secs_f64() * 1e3 / iters as f64;
let tn = Instant::now();
for _ in 0..iters {
naive(&ba, &bb, &mut bc);
}
let naive_ms = tn.elapsed().as_secs_f64() * 1e3 / iters as f64;
eprintln!(
"SGEMM [{bm}x{bk}x{bn}]: fast {fast_ms:.2}ms ({:.2} GFLOP/s) naive-ijk {naive_ms:.2}ms ({:.2} GFLOP/s) speedup {:.1}x",
flop / (fast_ms * 1e-3) / 1e9,
flop / (naive_ms * 1e-3) / 1e9,
naive_ms / fast_ms
);
}
}