#[cfg(target_arch = "x86_64")]
use super::horizontal::horizontal_sum_256;
#[cfg(target_arch = "x86_64")]
use super::is_avx2_fma_available;
#[inline(always)]
pub fn simd_sparse_dot_f32(
weight: &[f32],
row_off: usize,
active_indices: &[usize],
active_values: &[f32],
alive: usize,
) -> f32 {
if alive <= 4 {
let mut sum = 0.0f32;
for i in 0..alive {
unsafe {
let c = *active_indices.get_unchecked(i);
sum += *weight.get_unchecked(row_off + c) * *active_values.get_unchecked(i);
}
}
return sum;
}
#[cfg(target_arch = "aarch64")]
{
unsafe { neon_sparse_dot_f32(weight, row_off, active_indices, active_values, alive) }
}
#[cfg(target_arch = "x86_64")]
{
if is_avx2_fma_available() {
unsafe { avx2_sparse_dot_f32(weight, row_off, active_indices, active_values, alive) }
} else {
scalar_sparse_dot_f32(weight, row_off, active_indices, active_values, alive)
}
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
{
unsafe {
wasm32_simd128_sparse_dot_f32(weight, row_off, active_indices, active_values, alive)
}
}
#[cfg(not(any(
target_arch = "aarch64",
target_arch = "x86_64",
all(target_arch = "wasm32", target_feature = "simd128")
)))]
{
scalar_sparse_dot_f32(weight, row_off, active_indices, active_values, alive)
}
}
#[inline(always)]
#[allow(dead_code)]
pub(super) fn scalar_sparse_dot_f32(
weight: &[f32],
row_off: usize,
active_indices: &[usize],
active_values: &[f32],
alive: usize,
) -> f32 {
let mut sum = 0.0f32;
for i in 0..alive {
unsafe {
let c = *active_indices.get_unchecked(i);
sum =
(*weight.get_unchecked(row_off + c)).mul_add(*active_values.get_unchecked(i), sum);
}
}
sum
}
#[cfg(target_arch = "aarch64")]
#[inline]
unsafe fn neon_sparse_dot_f32(
weight: &[f32],
row_off: usize,
active_indices: &[usize],
active_values: &[f32],
alive: usize,
) -> f32 {
use core::arch::aarch64::{vaddvq_f32, vdupq_n_f32, vfmaq_f32, vld1q_f32, vsetq_lane_f32};
unsafe {
let mut acc = vdupq_n_f32(0.0);
let mut i = 0;
let chunks = alive / 4;
for _ in 0..chunks {
let mut ww = vdupq_n_f32(0.0);
ww = vsetq_lane_f32(
*weight.get_unchecked(row_off + *active_indices.get_unchecked(i)),
ww,
0,
);
ww = vsetq_lane_f32(
*weight.get_unchecked(row_off + *active_indices.get_unchecked(i + 1)),
ww,
1,
);
ww = vsetq_lane_f32(
*weight.get_unchecked(row_off + *active_indices.get_unchecked(i + 2)),
ww,
2,
);
ww = vsetq_lane_f32(
*weight.get_unchecked(row_off + *active_indices.get_unchecked(i + 3)),
ww,
3,
);
let vv = vld1q_f32(active_values.as_ptr().add(i));
acc = vfmaq_f32(acc, ww, vv);
i += 4;
}
let mut sum = vaddvq_f32(acc);
while i < alive {
let c = *active_indices.get_unchecked(i);
sum += *weight.get_unchecked(row_off + c) * *active_values.get_unchecked(i);
i += 1;
}
sum
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2,fma")]
#[inline]
unsafe fn avx2_sparse_dot_f32(
weight: &[f32],
row_off: usize,
active_indices: &[usize],
active_values: &[f32],
alive: usize,
) -> f32 {
use core::arch::x86_64::{
_mm256_fmadd_ps, _mm256_i32gather_ps, _mm256_loadu_ps, _mm256_set_epi32, _mm256_setzero_ps,
};
unsafe {
let mut acc = _mm256_setzero_ps();
let mut i = 0;
let chunks = alive / 8;
for _ in 0..chunks {
let idx = _mm256_set_epi32(
*active_indices.get_unchecked(i + 7) as i32,
*active_indices.get_unchecked(i + 6) as i32,
*active_indices.get_unchecked(i + 5) as i32,
*active_indices.get_unchecked(i + 4) as i32,
*active_indices.get_unchecked(i + 3) as i32,
*active_indices.get_unchecked(i + 2) as i32,
*active_indices.get_unchecked(i + 1) as i32,
*active_indices.get_unchecked(i) as i32,
);
let ww = _mm256_i32gather_ps(weight.as_ptr().add(row_off), idx, 4);
let vv = _mm256_loadu_ps(active_values.as_ptr().add(i));
acc = _mm256_fmadd_ps(ww, vv, acc);
i += 8;
}
let mut sum = horizontal_sum_256(acc);
while i < alive {
let c = *active_indices.get_unchecked(i);
sum += *weight.get_unchecked(row_off + c) * *active_values.get_unchecked(i);
i += 1;
}
sum
}
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
#[inline]
unsafe fn wasm32_simd128_sparse_dot_f32(
weight: &[f32],
row_off: usize,
active_indices: &[usize],
active_values: &[f32],
alive: usize,
) -> f32 {
use core::arch::wasm32::{
f32x4_add, f32x4_extract_lane, f32x4_mul, f32x4_replace_lane, f32x4_splat, v128_load,
};
unsafe {
let mut acc = f32x4_splat(0.0);
let mut i = 0;
let chunks = alive / 4;
for _ in 0..chunks {
let mut ww = f32x4_splat(0.0);
ww = f32x4_replace_lane::<0>(
ww,
*weight.get_unchecked(row_off + *active_indices.get_unchecked(i)),
);
ww = f32x4_replace_lane::<1>(
ww,
*weight.get_unchecked(row_off + *active_indices.get_unchecked(i + 1)),
);
ww = f32x4_replace_lane::<2>(
ww,
*weight.get_unchecked(row_off + *active_indices.get_unchecked(i + 2)),
);
ww = f32x4_replace_lane::<3>(
ww,
*weight.get_unchecked(row_off + *active_indices.get_unchecked(i + 3)),
);
let vv = v128_load(active_values.as_ptr().add(i).cast());
acc = f32x4_add(f32x4_mul(ww, vv), acc);
i += 4;
}
let mut sum = f32x4_extract_lane::<0>(acc)
+ f32x4_extract_lane::<1>(acc)
+ f32x4_extract_lane::<2>(acc)
+ f32x4_extract_lane::<3>(acc);
while i < alive {
let c = *active_indices.get_unchecked(i);
sum += *weight.get_unchecked(row_off + c) * *active_values.get_unchecked(i);
i += 1;
}
sum
}
}
#[inline(always)]
pub fn simd_sparse_matmul_rows(
output: &mut [f32],
weight: &[f32],
active_indices: &[usize],
active_values: &[f32],
rows: usize,
cols: usize,
alive: usize,
) {
for r in 0..rows {
let row_off = r * cols;
unsafe {
*output.get_unchecked_mut(r) =
simd_sparse_dot_f32(weight, row_off, active_indices, active_values, alive);
}
}
}