use crate::math::common::half::f16_bits_to_f32;
use crate::math::common::kahan::KahanF32;
#[path = "dot_4x.rs"]
pub mod dot_4x;
#[path = "dot_8x16x.rs"]
pub mod dot_8x16x;
pub use dot_4x::*;
pub use dot_8x16x::*;
#[inline]
pub unsafe fn dot_product_fallback(a: &[f32], b: &[u16]) -> f32 {
let len = core::cmp::min(a.len(), b.len());
let mut sum = 0.0f32;
for i in 0..len {
unsafe {
let fb = f16_bits_to_f32(*b.get_unchecked(i));
sum += *a.get_unchecked(i) * fb;
}
}
sum }
#[inline]
pub unsafe fn dot_product_bf16_fallback(a: &[u16], b: &[u16]) -> f32 {
let len = core::cmp::min(a.len(), b.len());
let mut sum = 0.0f32;
for i in 0..len {
unsafe {
let fa = f32::from_bits((*a.get_unchecked(i) as u32) << 16);
let fb = f32::from_bits((*b.get_unchecked(i) as u32) << 16);
sum += fa * fb;
}
}
sum
}
#[inline(always)]
pub fn dot_product_f32_native(a: &[f32], b: &[f32]) -> f32 {
let len = core::cmp::min(a.len(), b.len());
let a_sub = &a[..len];
let b_sub = &b[..len];
let mut sum = 0.0f32;
for i in 0..len {
sum += a_sub[i] * b_sub[i];
}
sum
}
#[inline(always)]
pub fn dot_product_f32_native_kahan(a: &[f32], b: &[f32]) -> f32 {
let len = core::cmp::min(a.len(), b.len());
let a_sub = &a[..len];
let b_sub = &b[..len];
let mut acc = KahanF32::new(0.0);
for i in 0..len {
acc.add(a_sub[i] * b_sub[i]);
}
acc.value()
}
#[inline]
pub unsafe fn dot_product_bf16_4x_fallback(
w0: &[u16],
w1: &[u16],
w2: &[u16],
w3: &[u16],
in_frame: &[u16],
) -> [f32; 4] {
unsafe {
[
dot_product_bf16_fallback(in_frame, w0),
dot_product_bf16_fallback(in_frame, w1),
dot_product_bf16_fallback(in_frame, w2),
dot_product_bf16_fallback(in_frame, w3),
]
}
}
#[cfg(test)]
mod kahan_dot_tests {
use super::*;
#[test]
fn test_kahan_naive_agree_small() {
let a = [1.0f32, 2.0, 3.0, 4.0];
let b = [5.0f32, 6.0, 7.0, 8.0];
let naive = dot_product_f32_native(&a, &b);
let kahan = dot_product_f32_native_kahan(&a, &b);
assert!((naive - kahan).abs() < 1e-6, "naive={naive}, kahan={kahan}");
}
#[test]
fn test_kahan_advantage_high_dynamic_range() {
let n = 10_000;
let big = 1.0f32;
let tiny = 1e-7f32;
let a: Vec<f32> = std::iter::repeat_n(1.0, n).collect();
let mut b: Vec<f32> = vec![big];
b.extend(std::iter::repeat_n(tiny, n - 1));
let naive = dot_product_f32_native(&a, &b);
let kahan = dot_product_f32_native_kahan(&a, &b);
let ref_result = big as f64 + (n - 1) as f64 * tiny as f64;
let naive_err = (naive as f64 - ref_result).abs() / ref_result.abs();
let kahan_err = (kahan as f64 - ref_result).abs() / ref_result.abs();
assert!(
kahan_err < naive_err,
"Kahan error ({kahan_err:.3e}) should be smaller than naive ({naive_err:.3e})"
);
}
#[test]
fn test_kahan_empty_slices() {
let a: [f32; 0] = [];
let b: [f32; 0] = [];
assert_eq!(dot_product_f32_native_kahan(&a, &b), 0.0);
}
#[test]
fn test_kahan_single_element() {
assert_eq!(dot_product_f32_native_kahan(&[3.0], &[4.0]), 12.0);
}
#[test]
fn test_kahan_different_lengths() {
let a = [1.0, 2.0, 3.0];
let b = [4.0, 5.0];
let result = dot_product_f32_native_kahan(&a, &b);
assert_eq!(result, 1.0 * 4.0 + 2.0 * 5.0);
}
#[test]
fn test_kahan_deep_accumulation_drift() {
let total_terms: usize = 15 * 3 * 256;
let mut rng_state: u32 = 0xCAFE_BABE;
let mut next_f32 = move || -> f32 {
rng_state = rng_state.wrapping_mul(1103515245).wrapping_add(12345);
let bits = (rng_state >> 9) | 0x3F80_0000;
f32::from_bits(bits) - 1.0
};
let mut a = Vec::with_capacity(total_terms);
let mut b = Vec::with_capacity(total_terms);
let mut f64_ref = 0.0f64;
for i in 0..total_terms {
let large = (next_f32() * 1e4).abs() + 1e3;
let tiny = next_f32() * 1e-7;
let a_val = if i % 128 == 0 {
large
} else if i % 64 == 0 {
large * 0.1
} else {
tiny
};
let b_val = 1.0f32;
a.push(a_val);
b.push(b_val);
f64_ref += a_val as f64 * b_val as f64;
}
let naive = dot_product_f32_native(&a, &b);
let kahan = dot_product_f32_native_kahan(&a, &b);
let naive_err = (naive as f64 - f64_ref).abs() / f64_ref.abs().max(1e-30);
let kahan_err = (kahan as f64 - f64_ref).abs() / f64_ref.abs().max(1e-30);
let naive_drift_db = 20.0 * naive_err.max(1e-30).log10();
let kahan_drift_db = 20.0 * kahan_err.max(1e-30).log10();
let improvement_db = naive_drift_db - kahan_drift_db;
assert!(
improvement_db >= 2.0,
"Kahan improvement ({improvement_db:.2} dB) < 2 dB"
);
}
}