use super::{BlockedCoo, Csr, DenseWithMask, SellP, SellPData, SparseView, Validated};
use crate::arch::SimdArch;
use crate::kernel::SimdKernel;
use crate::scalar::Scalar;
pub trait SparseSpMv<T> {
fn spmv(&self, x: &[T], y: &mut [T]);
}
#[inline(always)]
pub(crate) unsafe fn build_index_vector<T: Scalar, Arch: SimdKernel<T>>(
cols: &[i32],
) -> Arch::IndexVector {
const {
assert!(
core::mem::size_of::<Arch::IndexVector>()
== Arch::LANE_COUNT * core::mem::size_of::<i32>(),
"IndexVector size must equal LANE_COUNT * size_of::<i32>()"
)
};
assert!(
cols.len() >= Arch::LANE_COUNT,
"cols slice length {} is less than LANE_COUNT {}",
cols.len(),
Arch::LANE_COUNT
);
let ptr = cols.as_ptr() as *const Arch::IndexVector;
core::ptr::read_unaligned(ptr)
}
#[inline(never)]
fn validate_spmv_sizes(x_len: usize, y_len: usize, ncols: usize, nrows: usize, format_name: &str) {
assert!(
x_len >= ncols,
"x too short for {} ncols (got {}, expected >= {})",
format_name,
x_len,
ncols
);
assert!(
y_len >= nrows,
"y too short for {} nrows (got {}, expected >= {})",
format_name,
y_len,
nrows
);
}
impl<'a, T, Arch> SparseSpMv<T> for SparseView<'a, T, Validated<Csr>, Arch>
where
T: Scalar,
Arch: SimdArch + SimdKernel<T>,
{
#[inline]
fn spmv(&self, x: &[T], y: &mut [T]) {
let data = self.data.storage();
validate_spmv_sizes(x.len(), y.len(), data.ncols, data.nrows, "CSR");
let lane_count = Arch::LANE_COUNT;
for r in 0..data.nrows {
let start = data.row_ptr[r] as usize;
let end = data.row_ptr[r + 1] as usize;
let row_nnz = end - start;
if row_nnz == 0 {
continue;
}
let vals = &data.values[start..end];
let cols = &data.col_indices[start..end];
let simd_len = (row_nnz / lane_count) * lane_count;
let acc_vec = unsafe {
let mut acc_vec0 = Arch::zero();
let mut acc_vec1 = Arch::zero();
let mut acc_vec2 = Arch::zero();
let mut acc_vec3 = Arch::zero();
let unroll_len = (row_nnz / (lane_count * 4)) * (lane_count * 4);
let mut j = 0usize;
while j < unroll_len {
let idx0 = build_index_vector::<T, Arch>(&cols[j..j + lane_count]);
acc_vec0 = Arch::fmadd(
Arch::gather(x.as_ptr(), idx0),
Arch::load_unaligned(vals[j..].as_ptr()),
acc_vec0,
);
let idx1 =
build_index_vector::<T, Arch>(&cols[j + lane_count..j + lane_count * 2]);
acc_vec1 = Arch::fmadd(
Arch::gather(x.as_ptr(), idx1),
Arch::load_unaligned(vals[j + lane_count..].as_ptr()),
acc_vec1,
);
let idx2 = build_index_vector::<T, Arch>(
&cols[j + lane_count * 2..j + lane_count * 3],
);
acc_vec2 = Arch::fmadd(
Arch::gather(x.as_ptr(), idx2),
Arch::load_unaligned(vals[j + lane_count * 2..].as_ptr()),
acc_vec2,
);
let idx3 = build_index_vector::<T, Arch>(
&cols[j + lane_count * 3..j + lane_count * 4],
);
acc_vec3 = Arch::fmadd(
Arch::gather(x.as_ptr(), idx3),
Arch::load_unaligned(vals[j + lane_count * 3..].as_ptr()),
acc_vec3,
);
j += lane_count * 4;
}
let mut acc_vec =
Arch::add(Arch::add(acc_vec0, acc_vec1), Arch::add(acc_vec2, acc_vec3));
while j < simd_len {
let idx = build_index_vector::<T, Arch>(&cols[j..j + lane_count]);
acc_vec = Arch::fmadd(
Arch::gather(x.as_ptr(), idx),
Arch::load_unaligned(vals[j..].as_ptr()),
acc_vec,
);
j += lane_count;
}
acc_vec
};
let mut acc = unsafe { Arch::sum_reduce(acc_vec) };
let mut j = simd_len;
while j < row_nnz {
acc += vals[j] * unsafe { *x.get_unchecked(cols[j] as usize) };
j += 1;
}
y[r] += acc;
}
}
}
impl<'a, T, Arch> SparseSpMv<T> for SparseView<'a, T, DenseWithMask, Arch>
where
T: Scalar,
Arch: SimdArch + SimdKernel<T>,
{
#[inline]
fn spmv(&self, x: &[T], y: &mut [T]) {
let data = &self.data;
validate_spmv_sizes(x.len(), y.len(), data.ncols, data.nrows, "DenseWithMask");
let lane_count = Arch::LANE_COUNT;
for r in 0..data.nrows {
let row_offset = r * data.ncols;
let vals = &data.values[row_offset..row_offset + data.ncols];
let mask_bits = &data.mask[row_offset..row_offset + data.ncols];
let simd_len = (data.ncols / lane_count) * lane_count;
let acc_vec = unsafe {
let zero_vec = Arch::zero();
let mut acc_vec0 = zero_vec;
let mut acc_vec1 = zero_vec;
let mut acc_vec2 = zero_vec;
let mut acc_vec3 = zero_vec;
let unroll_len = (data.ncols / (lane_count * 4)) * (lane_count * 4);
let mut j = 0usize;
while j < unroll_len {
let msk0 = Arch::mask_from_bools(&mask_bits[j..j + lane_count]);
acc_vec0 = Arch::masked_fmadd(
Arch::masked_load_unaligned(vals[j..].as_ptr(), msk0, zero_vec),
Arch::load_unaligned(x[j..].as_ptr()),
acc_vec0,
msk0,
);
let msk1 =
Arch::mask_from_bools(&mask_bits[j + lane_count..j + lane_count * 2]);
acc_vec1 = Arch::masked_fmadd(
Arch::masked_load_unaligned(
vals[j + lane_count..].as_ptr(),
msk1,
zero_vec,
),
Arch::load_unaligned(x[j + lane_count..].as_ptr()),
acc_vec1,
msk1,
);
let msk2 =
Arch::mask_from_bools(&mask_bits[j + lane_count * 2..j + lane_count * 3]);
acc_vec2 = Arch::masked_fmadd(
Arch::masked_load_unaligned(
vals[j + lane_count * 2..].as_ptr(),
msk2,
zero_vec,
),
Arch::load_unaligned(x[j + lane_count * 2..].as_ptr()),
acc_vec2,
msk2,
);
let msk3 =
Arch::mask_from_bools(&mask_bits[j + lane_count * 3..j + lane_count * 4]);
acc_vec3 = Arch::masked_fmadd(
Arch::masked_load_unaligned(
vals[j + lane_count * 3..].as_ptr(),
msk3,
zero_vec,
),
Arch::load_unaligned(x[j + lane_count * 3..].as_ptr()),
acc_vec3,
msk3,
);
j += lane_count * 4;
}
let mut acc_vec =
Arch::add(Arch::add(acc_vec0, acc_vec1), Arch::add(acc_vec2, acc_vec3));
while j < simd_len {
let msk = Arch::mask_from_bools(&mask_bits[j..j + lane_count]);
acc_vec = Arch::masked_fmadd(
Arch::masked_load_unaligned(vals[j..].as_ptr(), msk, zero_vec),
Arch::load_unaligned(x[j..].as_ptr()),
acc_vec,
msk,
);
j += lane_count;
}
acc_vec
};
let mut acc = unsafe { Arch::sum_reduce(acc_vec) };
let mut j = simd_len;
while j < data.ncols {
if mask_bits[j] {
acc += vals[j] * x[j];
}
j += 1;
}
y[r] += acc;
}
}
}
impl<'a, T, const BM: usize, const BN: usize, Arch> SparseSpMv<T>
for SparseView<'a, T, Validated<BlockedCoo<BM, BN>>, Arch>
where
T: Scalar,
Arch: SimdArch + SimdKernel<T>,
{
#[inline]
fn spmv(&self, x: &[T], y: &mut [T]) {
let data = self.data.storage();
validate_spmv_sizes(x.len(), y.len(), data.ncols, data.nrows, "BlockedCoo");
let block_size = BM * BN;
let lane_count = Arch::LANE_COUNT;
if BN == lane_count {
for b in 0..data.nblocks {
let br = data.block_row[b] as usize;
let bc = data.block_col[b] as usize;
let block = &data.blocks[b * block_size..(b + 1) * block_size];
unsafe {
let x_vec = Arch::load_unaligned(x.as_ptr().add(bc));
for i in 0..BM {
let b_vec = Arch::load_unaligned(block.as_ptr().add(i * BN));
y[br + i] += Arch::sum_reduce(Arch::mul(b_vec, x_vec));
}
}
}
} else if BN == lane_count * 2 {
for b in 0..data.nblocks {
let br = data.block_row[b] as usize;
let bc = data.block_col[b] as usize;
let block = &data.blocks[b * block_size..(b + 1) * block_size];
unsafe {
let x_vec0 = Arch::load_unaligned(x.as_ptr().add(bc));
let x_vec1 = Arch::load_unaligned(x.as_ptr().add(bc + lane_count));
for i in 0..BM {
let offset = i * BN;
let prod0 =
Arch::mul(Arch::load_unaligned(block.as_ptr().add(offset)), x_vec0);
let prod1 = Arch::mul(
Arch::load_unaligned(block.as_ptr().add(offset + lane_count)),
x_vec1,
);
y[br + i] += Arch::sum_reduce(Arch::add(prod0, prod1));
}
}
}
} else {
unsafe {
let x_ptr = x.as_ptr();
let y_ptr = y.as_mut_ptr();
for b in 0..data.nblocks {
let br = data.block_row[b] as usize;
let bc = data.block_col[b] as usize;
let block_ptr = data.blocks.as_ptr().add(b * block_size);
for i in 0..BM {
let row_ptr = block_ptr.add(i * BN);
let mut s = T::ZERO;
for k in 0..BN {
s = s + *row_ptr.add(k) * *x_ptr.add(bc + k);
}
*y_ptr.add(br + i) += s;
}
}
}
}
}
}
fn sellp_spmv_scalar<T, const C: usize>(data: &SellPData<'_, T, C>, x: &[T], y: &mut [T])
where
T: Scalar,
{
let nslices = data.nslices();
for s in 0..nslices {
let col_count = data.slice_col_count[s] as usize;
let start_offset = data.slice_ptr[s] as usize;
let mut row_acc = [T::ZERO; C];
for col in 0..col_count {
for row in 0..C {
let idx = start_offset + col * C + row;
let val = data.values[idx];
let c_idx = data.col_indices[idx] as usize;
row_acc[row] += val * unsafe { *x.get_unchecked(c_idx) };
}
}
for row in 0..C {
let r_idx = s * C + row;
if r_idx < y.len() {
y[r_idx] += row_acc[row];
}
}
}
}
unsafe fn sellp_spmv_vectorized<T, const C: usize, Arch>(
data: &SellPData<'_, T, C>,
x: &[T],
y: &mut [T],
) where
T: Scalar,
Arch: SimdArch + SimdKernel<T>,
{
assert_eq!(
Arch::LANE_COUNT,
C,
"sellp_spmv_vectorized requires Arch::LANE_COUNT == C"
);
let nslices = data.nslices();
for s in 0..nslices {
let col_count = data.slice_col_count[s] as usize;
let start_offset = data.slice_ptr[s] as usize;
let mut acc0 = Arch::zero();
let mut acc1 = Arch::zero();
let mut acc2 = Arch::zero();
let mut acc3 = Arch::zero();
let unroll = (col_count / 4) * 4;
let mut col = 0;
while col < unroll {
let offset = start_offset + col * C;
let val_vec = Arch::load_unaligned(data.values[offset..].as_ptr());
let idx_vec = build_index_vector::<T, Arch>(&data.col_indices[offset..offset + C]);
let x_vec = Arch::gather(x.as_ptr(), idx_vec);
acc0 = Arch::fmadd(val_vec, x_vec, acc0);
let offset = start_offset + (col + 1) * C;
let val_vec = Arch::load_unaligned(data.values[offset..].as_ptr());
let idx_vec = build_index_vector::<T, Arch>(&data.col_indices[offset..offset + C]);
let x_vec = Arch::gather(x.as_ptr(), idx_vec);
acc1 = Arch::fmadd(val_vec, x_vec, acc1);
let offset = start_offset + (col + 2) * C;
let val_vec = Arch::load_unaligned(data.values[offset..].as_ptr());
let idx_vec = build_index_vector::<T, Arch>(&data.col_indices[offset..offset + C]);
let x_vec = Arch::gather(x.as_ptr(), idx_vec);
acc2 = Arch::fmadd(val_vec, x_vec, acc2);
let offset = start_offset + (col + 3) * C;
let val_vec = Arch::load_unaligned(data.values[offset..].as_ptr());
let idx_vec = build_index_vector::<T, Arch>(&data.col_indices[offset..offset + C]);
let x_vec = Arch::gather(x.as_ptr(), idx_vec);
acc3 = Arch::fmadd(val_vec, x_vec, acc3);
col += 4;
}
let mut acc = Arch::add(Arch::add(acc0, acc1), Arch::add(acc2, acc3));
while col < col_count {
let offset = start_offset + col * C;
let val_vec = Arch::load_unaligned(data.values[offset..].as_ptr());
let idx_vec = build_index_vector::<T, Arch>(&data.col_indices[offset..offset + C]);
let x_vec = Arch::gather(x.as_ptr(), idx_vec);
acc = Arch::fmadd(val_vec, x_vec, acc);
col += 1;
}
let r_idx = s * C;
if r_idx + C <= y.len() {
let y_ptr = y.as_mut_ptr().add(r_idx);
let y_vec = Arch::load_unaligned(y_ptr);
let res_vec = Arch::add(y_vec, acc);
Arch::store_unaligned(y_ptr, res_vec);
} else {
let mut temp = [T::ZERO; C];
Arch::store_unaligned(temp.as_mut_ptr(), acc);
for row in 0..y.len() - r_idx {
y[r_idx + row] += temp[row];
}
}
}
}
impl<'a, T, const C: usize, Arch> SparseSpMv<T> for SparseView<'a, T, Validated<SellP<C>>, Arch>
where
T: Scalar,
Arch: SimdArch + SimdKernel<T>,
{
#[inline]
fn spmv(&self, x: &[T], y: &mut [T]) {
let data = self.data.storage();
validate_spmv_sizes(x.len(), y.len(), data.ncols, data.nrows, "SellP");
if Arch::LANE_COUNT == C {
unsafe { sellp_spmv_vectorized::<T, C, Arch>(data, x, y) };
} else {
sellp_spmv_scalar::<T, C>(data, x, y);
}
}
}