#![allow(clippy::excessive_precision, clippy::approx_constant)]
#[cfg(all(not(feature = "std"), not(target_arch = "aarch64")))]
use crate::compat::Math;
use super::MAX_INPUTS;
#[inline(always)]
pub fn fma(a: f32, b: f32, c: f32) -> f32 {
#[cfg(target_arch = "aarch64")]
{
use core::arch::aarch64::{vdup_n_f32, vfma_f32, vget_lane_f32};
unsafe { vget_lane_f32::<0>(vfma_f32(vdup_n_f32(c), vdup_n_f32(a), vdup_n_f32(b))) }
}
#[cfg(not(target_arch = "aarch64"))]
{
a.mul_add(b, c)
}
}
pub fn frecpe_bits(x: u32) -> u32 {
let sign = x & 0x8000_0000;
let exp = ((x >> 23) & 0xff) as i32;
let frac = x & 0x007f_ffff;
if exp == 0xff {
return if frac != 0 { x | 0x0040_0000 } else { sign };
}
if exp == 0 && frac < (1 << 21) {
return sign | 0x7f80_0000;
}
let mut f = (frac as u64) << 29;
let mut e = exp;
if e == 0 {
if (f >> 51) & 1 == 0 {
e = -1;
f = (f & ((1 << 50) - 1)) << 2;
} else {
f = (f & ((1 << 51) - 1)) << 1;
}
}
let a = 0x100 | ((f >> 44) & 0xff) as u32;
let b = (1u32 << 19) / (2 * a + 1);
let est = b.div_ceil(2);
let mut rexp = 253 - e;
let mut rf = ((est & 0xff) as u64) << 44;
if rexp == 0 {
rf = (1 << 51) | (rf >> 1);
} else if rexp == -1 {
rf = (1 << 50) | (rf >> 2);
rexp = 0;
}
sign | (((rexp & 0xff) as u32) << 23) | (rf >> 29) as u32
}
#[inline]
pub fn frecpe(x: f32) -> f32 {
f32::from_bits(frecpe_bits(x.to_bits()))
}
#[inline]
fn arm_nan(a: f32, b: f32) -> f32 {
const QUIET: u32 = 0x0040_0000;
let (ab, bb) = (a.to_bits(), b.to_bits());
let a_snan = a.is_nan() && ab & QUIET == 0;
let b_snan = b.is_nan() && bb & QUIET == 0;
let pick = if a_snan {
ab
} else if b_snan {
bb
} else if a.is_nan() {
ab
} else {
bb
};
f32::from_bits(pick | QUIET)
}
#[inline]
pub fn fmin(a: f32, b: f32) -> f32 {
if a.is_nan() || b.is_nan() {
arm_nan(a, b)
} else if a == b {
f32::from_bits(a.to_bits() | b.to_bits())
} else if a < b {
a
} else {
b
}
}
#[inline]
pub fn fmax(a: f32, b: f32) -> f32 {
if a.is_nan() || b.is_nan() {
arm_nan(a, b)
} else if a == b {
f32::from_bits(a.to_bits() & b.to_bits())
} else if a > b {
a
} else {
b
}
}
#[inline]
fn round_to_i32(p: f32) -> i32 {
#[cfg(target_arch = "aarch64")]
{
unsafe { core::arch::aarch64::vcvtns_s32_f32(p) }
}
#[cfg(not(target_arch = "aarch64"))]
{
p.round_ties_even() as i32
}
}
#[inline]
pub fn quantize(x: f32) -> i8 {
round_to_i32(127.0 * x) as i16 as i8
}
#[inline]
pub fn lpcnet_exp(x: f32) -> f32 {
let x = fmax(fmin(x, 88.0), -88.0);
let x = fma(x, 1.44269504, 127.0);
let i = x as i32;
let x = x - i as f32;
let y = fma(x, fma(x, fma(0.078024523, x, 0.22606716), 0.69583354), 0.99992522);
y * f32::from_bits((i as u32) << 23)
}
#[inline]
pub fn tanh_approx(x: f32) -> f32 {
let x2 = x * x;
let num = fma(x2, fma(0.60863042, x2, 96.39235687), 952.52801514);
let den = fma(x2, fma(11.88600922, x2, 413.36801147), 952.72399902);
let num = num * x;
let num = num * frecpe(den);
fmax(-1.0, fmin(1.0, num))
}
#[inline]
pub fn sigmoid_approx(x: f32) -> f32 {
let x2 = x * x;
let num = fma(x2, fma(0.00950985, x2, 6.02452230), 238.13200378);
let den = fma(x2, fma(0.74287558, x2, 103.34200287), 952.72399902);
let num = num * x;
let num = fma(num, frecpe(den), 0.5);
fmax(0.0, fmin(1.0, num))
}
#[inline]
pub fn tanh_tail(x: f32) -> f32 {
let ex2 = lpcnet_exp(2.0 * x);
(ex2 - 1.0) / (ex2 + 1.0)
}
#[inline]
pub fn sigmoid_tail(x: f32) -> f32 {
let ex = lpcnet_exp(x);
ex / (ex + 1.0)
}
pub fn vec_exp(buf: &mut [f32]) {
for v in buf.iter_mut() {
*v = lpcnet_exp(*v);
}
}
pub fn vec_tanh(buf: &mut [f32]) {
let groups = buf.len() & !3;
let (head, tail) = buf.split_at_mut(groups);
for v in head.iter_mut() {
*v = tanh_approx(*v);
}
for v in tail.iter_mut() {
*v = tanh_tail(*v);
}
}
pub fn vec_sigmoid(buf: &mut [f32]) {
let groups = buf.len() & !3;
let (head, tail) = buf.split_at_mut(groups);
for v in head.iter_mut() {
*v = sigmoid_approx(*v);
}
for v in tail.iter_mut() {
*v = sigmoid_tail(*v);
}
}
pub fn sgemv(
out: &mut [f32],
weights: &[f32],
rows: usize,
cols: usize,
col_stride: usize,
x: &[f32],
) {
let out = &mut out[..rows];
out.fill(0.0);
for (j, &xj) in x[..cols].iter().enumerate() {
let w = &weights[j * col_stride..j * col_stride + rows];
for (o, &wij) in out.iter_mut().zip(w) {
*o = fma(wij, xj, *o);
}
}
}
pub fn sparse_sgemv8x4(out: &mut [f32], weights: &[f32], idx: &[i32], rows: usize, x: &[f32]) {
let mut p = 0;
let mut w = 0;
for block in out[..rows].as_chunks_mut::<8>().0 {
let mut y = [0.0f32; 8];
let count = idx[p] as usize;
p += 1;
for _ in 0..count {
let pos = idx[p] as usize;
p += 1;
let wb = &weights[w..w + 32];
let xb = &x[pos..pos + 4];
for c in 0..4 {
for k in 0..8 {
y[k] = fma(wb[8 * c + k], xb[c], y[k]);
}
}
w += 32;
}
block.copy_from_slice(&y);
}
}
#[inline]
pub fn quantize_slice(xq: &mut [i8], x: &[f32]) {
for (q, &v) in xq.iter_mut().zip(x) {
*q = quantize(v);
}
}
#[inline(always)]
fn block_dot(acc: &mut [i32; 8], w: &[i8], x: &[i8]) {
for k in 0..8 {
let mut s = 0;
for c in 0..4 {
s += w[4 * k + c] as i32 * x[c] as i32;
}
acc[k] += s;
}
}
pub fn cgemv8x4(
out: &mut [f32],
weights: &[i8],
scale: &[f32],
rows: usize,
cols: usize,
x: &[f32],
) {
let mut xq = [0i8; MAX_INPUTS];
let xq = &mut xq[..cols];
quantize_slice(xq, &x[..cols]);
let groups = cols / 4;
for (b, block) in out[..rows].as_chunks_mut::<8>().0.iter_mut().enumerate() {
let mut acc = [0i32; 8];
let wrow = &weights[b * groups * 32..(b + 1) * groups * 32];
for (g, wb) in wrow.as_chunks::<32>().0.iter().enumerate() {
block_dot(&mut acc, wb, &xq[4 * g..4 * g + 4]);
}
for k in 0..8 {
block[k] = scale[8 * b + k] * acc[k] as f32;
}
}
}
pub fn sparse_cgemv8x4(
out: &mut [f32],
weights: &[i8],
idx: &[i32],
scale: &[f32],
rows: usize,
cols: usize,
x: &[f32],
) {
let mut xq = [0i8; MAX_INPUTS];
let xq = &mut xq[..cols];
quantize_slice(xq, &x[..cols]);
let mut p = 0;
let mut w = 0;
for (b, block) in out[..rows].as_chunks_mut::<8>().0.iter_mut().enumerate() {
let mut acc = [0i32; 8];
let count = idx[p] as usize;
p += 1;
for _ in 0..count {
let pos = idx[p] as usize;
p += 1;
block_dot(&mut acc, &weights[w..w + 32], &xq[pos..pos + 4]);
w += 32;
}
for k in 0..8 {
block[k] = scale[8 * b + k] * acc[k] as f32;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn quantize_rounds_ties_to_even_and_wraps() {
assert_eq!(quantize(0.5 / 127.0), 0);
assert_eq!(quantize(1.5 / 127.0), 2);
assert_eq!(quantize(2.5 / 127.0), 2);
assert_eq!(quantize(-2.5 / 127.0), -2);
assert_eq!(quantize(1.0), 127);
assert_eq!(quantize(-1.0), -127);
assert_eq!(quantize(1.5), -66);
assert_eq!(quantize(f32::NAN), 0);
assert_eq!(quantize(f32::INFINITY), -1);
assert_eq!(quantize(f32::NEG_INFINITY), 0);
}
#[test]
fn fmin_fmax_follow_arm() {
assert!(fmin(f32::NAN, 1.0).is_nan());
assert!(fmin(1.0, f32::NAN).is_nan());
assert!(fmax(f32::NAN, 1.0).is_nan());
assert_eq!(fmin(-0.0, 0.0).to_bits(), (-0.0f32).to_bits());
assert_eq!(fmin(0.0, -0.0).to_bits(), (-0.0f32).to_bits());
assert_eq!(fmax(-0.0, 0.0).to_bits(), 0);
assert_eq!(fmax(0.0, -0.0).to_bits(), 0);
assert_eq!(fmin(2.0, 3.0), 2.0);
assert_eq!(fmax(2.0, 3.0), 3.0);
}
#[test]
fn frecpe_special_values() {
assert_eq!(frecpe(1.0), 0.998_046_9);
assert_eq!(frecpe(f32::INFINITY).to_bits(), 0);
assert_eq!(frecpe(f32::NEG_INFINITY).to_bits(), 0x8000_0000);
assert_eq!(frecpe(0.0), f32::INFINITY);
assert_eq!(frecpe(-0.0), f32::NEG_INFINITY);
assert!(frecpe(f32::NAN).is_nan());
}
#[test]
fn activations_saturate_and_pass_nan() {
assert_eq!(tanh_approx(100.0), 1.0);
assert_eq!(tanh_approx(-100.0), -1.0);
assert_eq!(sigmoid_approx(100.0), 1.0);
assert_eq!(sigmoid_approx(-100.0), 0.0);
assert!(tanh_approx(7.4e9).is_nan());
assert_eq!(tanh_approx(2.3e9), 1.0);
assert!(sigmoid_approx(4.7e9).is_nan());
assert_eq!(sigmoid_approx(4.6e9), 1.0);
assert!(lpcnet_exp(f32::NAN).is_nan());
assert_eq!(lpcnet_exp(-1000.0), lpcnet_exp(-88.0));
}
}