use std::ops::{Add, AddAssign, Mul};
use oxicuda_blas::GpuFloat;
use crate::error::{SparseError, SparseResult};
use crate::format::CsrMatrix;
type SpGEMMResultU32<T> = (Vec<i32>, Vec<i32>, Vec<T>, u32, u32);
type SpGEMMResultUsize<T> = (Vec<i32>, Vec<i32>, Vec<T>, usize, usize);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Strategy {
Sequential,
Concurrent(usize),
Fused,
}
#[derive(Debug, Clone)]
pub struct BatchScheduler {
_private: (),
}
impl BatchScheduler {
#[inline]
pub fn new() -> Self {
Self { _private: () }
}
pub fn select_strategy(&self, batch_size: usize, avg_nnz: usize) -> Strategy {
Self::select_strategy_static(batch_size, avg_nnz)
}
pub fn select_strategy_static(batch_size: usize, avg_nnz: usize) -> Strategy {
if batch_size <= 4 && avg_nnz >= 10_000 {
return Strategy::Sequential;
}
if batch_size >= 64 && avg_nnz < 256 {
return Strategy::Fused;
}
let streams = batch_size.clamp(1, 8);
Strategy::Concurrent(streams)
}
}
impl Default for BatchScheduler {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct BatchedSpMVPlan<T> {
pub concat_row_ptr: Vec<i32>,
pub concat_col_idx: Vec<i32>,
pub concat_values: Vec<T>,
pub batch_offsets_row_ptr: Vec<usize>,
pub batch_offsets_nnz: Vec<usize>,
pub row_counts: Vec<usize>,
pub col_counts: Vec<usize>,
pub nnz_counts: Vec<usize>,
pub batch_size: usize,
pub strategy: Strategy,
}
impl<T: GpuFloat> BatchedSpMVPlan<T> {
pub fn from_matrices(matrices: &[CsrMatrix<T>]) -> SparseResult<Self> {
if matrices.is_empty() {
return Err(SparseError::InvalidArgument(
"batch must contain at least one matrix".to_string(),
));
}
let batch_size = matrices.len();
let mut concat_row_ptr = Vec::new();
let mut concat_col_idx = Vec::new();
let mut concat_values: Vec<T> = Vec::new();
let mut batch_offsets_row_ptr = Vec::with_capacity(batch_size);
let mut batch_offsets_nnz = Vec::with_capacity(batch_size);
let mut row_counts = Vec::with_capacity(batch_size);
let mut col_counts = Vec::with_capacity(batch_size);
let mut nnz_counts = Vec::with_capacity(batch_size);
for mat in matrices {
let (h_rp, h_ci, h_vals) = mat.to_host()?;
batch_offsets_row_ptr.push(concat_row_ptr.len());
batch_offsets_nnz.push(concat_col_idx.len());
row_counts.push(mat.rows() as usize);
col_counts.push(mat.cols() as usize);
nnz_counts.push(mat.nnz() as usize);
concat_row_ptr.extend_from_slice(&h_rp);
concat_col_idx.extend_from_slice(&h_ci);
concat_values.extend_from_slice(&h_vals);
}
let total_nnz = nnz_counts.iter().copied().sum::<usize>();
let avg_nnz = total_nnz.checked_div(batch_size).unwrap_or(0);
let strategy = BatchScheduler::select_strategy_static(batch_size, avg_nnz);
Ok(Self {
concat_row_ptr,
concat_col_idx,
concat_values,
batch_offsets_row_ptr,
batch_offsets_nnz,
row_counts,
col_counts,
nnz_counts,
batch_size,
strategy,
})
}
pub fn from_host_arrays(
row_ptrs: &[Vec<i32>],
col_indices: &[Vec<i32>],
values: &[Vec<T>],
rows: &[usize],
cols: &[usize],
) -> SparseResult<Self> {
let batch_size = row_ptrs.len();
if batch_size == 0 {
return Err(SparseError::InvalidArgument(
"batch must contain at least one matrix".to_string(),
));
}
if col_indices.len() != batch_size
|| values.len() != batch_size
|| rows.len() != batch_size
|| cols.len() != batch_size
{
return Err(SparseError::InvalidArgument(
"all input slices must have the same length".to_string(),
));
}
let mut concat_row_ptr = Vec::new();
let mut concat_col_idx = Vec::new();
let mut concat_values: Vec<T> = Vec::new();
let mut batch_offsets_row_ptr = Vec::with_capacity(batch_size);
let mut batch_offsets_nnz = Vec::with_capacity(batch_size);
let mut row_counts = Vec::with_capacity(batch_size);
let mut col_counts = Vec::with_capacity(batch_size);
let mut nnz_counts = Vec::with_capacity(batch_size);
for i in 0..batch_size {
batch_offsets_row_ptr.push(concat_row_ptr.len());
batch_offsets_nnz.push(concat_col_idx.len());
row_counts.push(rows[i]);
col_counts.push(cols[i]);
nnz_counts.push(values[i].len());
concat_row_ptr.extend_from_slice(&row_ptrs[i]);
concat_col_idx.extend_from_slice(&col_indices[i]);
concat_values.extend_from_slice(&values[i]);
}
let total_nnz = nnz_counts.iter().copied().sum::<usize>();
let avg_nnz = total_nnz.checked_div(batch_size).unwrap_or(0);
let strategy = BatchScheduler::select_strategy_static(batch_size, avg_nnz);
Ok(Self {
concat_row_ptr,
concat_col_idx,
concat_values,
batch_offsets_row_ptr,
batch_offsets_nnz,
row_counts,
col_counts,
nnz_counts,
batch_size,
strategy,
})
}
#[inline]
pub fn total_nnz(&self) -> usize {
self.nnz_counts.iter().copied().sum()
}
#[inline]
pub fn total_rows(&self) -> usize {
self.row_counts.iter().copied().sum()
}
#[inline]
pub fn avg_nnz(&self) -> usize {
if self.batch_size == 0 {
return 0;
}
self.total_nnz() / self.batch_size
}
}
#[derive(Debug)]
pub struct BatchedSpMV<T: GpuFloat> {
matrices: Vec<HostCsr<T>>,
}
#[derive(Debug, Clone)]
struct HostCsr<T> {
rows: usize,
cols: usize,
row_ptr: Vec<i32>,
col_idx: Vec<i32>,
values: Vec<T>,
}
impl<T: GpuFloat + Add<Output = T> + Mul<Output = T> + AddAssign> BatchedSpMV<T> {
pub fn from_device(matrices: &[CsrMatrix<T>]) -> SparseResult<Self> {
if matrices.is_empty() {
return Err(SparseError::InvalidArgument(
"batch must contain at least one matrix".to_string(),
));
}
let mut host_mats = Vec::with_capacity(matrices.len());
for mat in matrices {
let (rp, ci, vals) = mat.to_host()?;
host_mats.push(HostCsr {
rows: mat.rows() as usize,
cols: mat.cols() as usize,
row_ptr: rp,
col_idx: ci,
values: vals,
});
}
Ok(Self {
matrices: host_mats,
})
}
pub fn from_host(
row_ptrs: Vec<Vec<i32>>,
col_indices: Vec<Vec<i32>>,
values: Vec<Vec<T>>,
rows: Vec<usize>,
cols: Vec<usize>,
) -> SparseResult<Self> {
let n = row_ptrs.len();
if n == 0 {
return Err(SparseError::InvalidArgument(
"batch must contain at least one matrix".to_string(),
));
}
if col_indices.len() != n || values.len() != n || rows.len() != n || cols.len() != n {
return Err(SparseError::InvalidArgument(
"all input vectors must have the same length".to_string(),
));
}
let mut host_mats = Vec::with_capacity(n);
for i in 0..n {
host_mats.push(HostCsr {
rows: rows[i],
cols: cols[i],
row_ptr: row_ptrs[i].clone(),
col_idx: col_indices[i].clone(),
values: values[i].clone(),
});
}
Ok(Self {
matrices: host_mats,
})
}
#[inline]
pub fn batch_size(&self) -> usize {
self.matrices.len()
}
pub fn execute(&self, xs: &[Vec<T>], ys: &mut [Vec<T>], alpha: T, beta: T) -> SparseResult<()> {
let n = self.matrices.len();
if xs.len() != n || ys.len() != n {
return Err(SparseError::DimensionMismatch(format!(
"expected {} vectors, got xs={}, ys={}",
n,
xs.len(),
ys.len()
)));
}
for (i, mat) in self.matrices.iter().enumerate() {
if xs[i].len() < mat.cols {
return Err(SparseError::DimensionMismatch(format!(
"matrix {} has {} cols but x has {} elements",
i,
mat.cols,
xs[i].len()
)));
}
if ys[i].len() < mat.rows {
return Err(SparseError::DimensionMismatch(format!(
"matrix {} has {} rows but y has {} elements",
i,
mat.rows,
ys[i].len()
)));
}
host_csr_spmv(mat, &xs[i], &mut ys[i], alpha, beta);
}
Ok(())
}
}
fn host_csr_spmv<T: GpuFloat + Add<Output = T> + Mul<Output = T> + AddAssign>(
mat: &HostCsr<T>,
x: &[T],
y: &mut [T],
alpha: T,
beta: T,
) {
for (row, y_row) in y.iter_mut().enumerate().take(mat.rows) {
let start = mat.row_ptr[row] as usize;
let end = mat.row_ptr[row + 1] as usize;
let mut acc = T::gpu_zero();
for j in start..end {
let col = mat.col_idx[j] as usize;
acc += mat.values[j] * x[col];
}
*y_row = alpha * acc + beta * *y_row;
}
}
#[derive(Debug, Clone)]
pub struct UniformBatchedSpMV<T> {
rows: usize,
cols: usize,
row_ptr: Vec<i32>,
col_idx: Vec<i32>,
batch_values: Vec<Vec<T>>,
}
impl<T: GpuFloat + Add<Output = T> + Mul<Output = T> + AddAssign> UniformBatchedSpMV<T> {
pub fn from_pattern(pattern: &CsrMatrix<T>, batch_values: Vec<Vec<T>>) -> SparseResult<Self> {
if batch_values.is_empty() {
return Err(SparseError::InvalidArgument(
"batch_values must not be empty".to_string(),
));
}
let nnz = pattern.nnz() as usize;
for (i, vals) in batch_values.iter().enumerate() {
if vals.len() != nnz {
return Err(SparseError::InvalidArgument(format!(
"batch_values[{}] has length {} but pattern nnz is {}",
i,
vals.len(),
nnz
)));
}
}
let (rp, ci, _) = pattern.to_host()?;
Ok(Self {
rows: pattern.rows() as usize,
cols: pattern.cols() as usize,
row_ptr: rp,
col_idx: ci,
batch_values,
})
}
pub fn from_host_arrays(
rows: usize,
cols: usize,
row_ptr: Vec<i32>,
col_idx: Vec<i32>,
batch_values: Vec<Vec<T>>,
) -> SparseResult<Self> {
if batch_values.is_empty() {
return Err(SparseError::InvalidArgument(
"batch_values must not be empty".to_string(),
));
}
if row_ptr.len() != rows + 1 {
return Err(SparseError::InvalidArgument(format!(
"row_ptr length {} != rows + 1 ({})",
row_ptr.len(),
rows + 1
)));
}
let nnz = col_idx.len();
for (i, vals) in batch_values.iter().enumerate() {
if vals.len() != nnz {
return Err(SparseError::InvalidArgument(format!(
"batch_values[{}] length {} != nnz {}",
i,
vals.len(),
nnz
)));
}
}
Ok(Self {
rows,
cols,
row_ptr,
col_idx,
batch_values,
})
}
#[inline]
pub fn batch_size(&self) -> usize {
self.batch_values.len()
}
pub fn execute(&self, xs: &[Vec<T>], ys: &mut [Vec<T>], alpha: T, beta: T) -> SparseResult<()> {
let n = self.batch_values.len();
if xs.len() != n || ys.len() != n {
return Err(SparseError::DimensionMismatch(format!(
"expected {} vectors, got xs={}, ys={}",
n,
xs.len(),
ys.len()
)));
}
for i in 0..n {
if xs[i].len() < self.cols {
return Err(SparseError::DimensionMismatch(format!(
"x[{}] length {} < cols {}",
i,
xs[i].len(),
self.cols
)));
}
if ys[i].len() < self.rows {
return Err(SparseError::DimensionMismatch(format!(
"y[{}] length {} < rows {}",
i,
ys[i].len(),
self.rows
)));
}
let mat = HostCsr {
rows: self.rows,
cols: self.cols,
row_ptr: self.row_ptr.clone(),
col_idx: self.col_idx.clone(),
values: self.batch_values[i].clone(),
};
host_csr_spmv(&mat, &xs[i], &mut ys[i], alpha, beta);
}
Ok(())
}
}
#[derive(Debug)]
pub struct BatchedSpGEMM {
_private: (),
}
impl BatchedSpGEMM {
#[inline]
pub fn new() -> Self {
Self { _private: () }
}
pub fn execute<T: GpuFloat + Add<Output = T> + Mul<Output = T> + AddAssign>(
a_batch: &[CsrMatrix<T>],
b_batch: &[CsrMatrix<T>],
) -> SparseResult<Vec<SpGEMMResultU32<T>>> {
if a_batch.is_empty() {
return Err(SparseError::InvalidArgument(
"batch must not be empty".to_string(),
));
}
if a_batch.len() != b_batch.len() {
return Err(SparseError::InvalidArgument(format!(
"a_batch length {} != b_batch length {}",
a_batch.len(),
b_batch.len()
)));
}
let mut results = Vec::with_capacity(a_batch.len());
for (i, (a, b)) in a_batch.iter().zip(b_batch.iter()).enumerate() {
if a.cols() != b.rows() {
return Err(SparseError::DimensionMismatch(format!(
"pair {}: A.cols ({}) != B.rows ({})",
i,
a.cols(),
b.rows()
)));
}
let (a_rp, a_ci, a_vals) = a.to_host()?;
let (b_rp, b_ci, b_vals) = b.to_host()?;
let (c_rp, c_ci, c_vals) = host_spgemm(
&a_rp,
&a_ci,
&a_vals,
a.rows() as usize,
&b_rp,
&b_ci,
&b_vals,
b.cols() as usize,
);
results.push((c_rp, c_ci, c_vals, a.rows(), b.cols()));
}
Ok(results)
}
#[allow(clippy::too_many_arguments)]
pub fn execute_host<T: GpuFloat + Add<Output = T> + Mul<Output = T> + AddAssign>(
a_row_ptrs: &[Vec<i32>],
a_col_indices: &[Vec<i32>],
a_values: &[Vec<T>],
a_rows: &[usize],
a_cols: &[usize],
b_row_ptrs: &[Vec<i32>],
b_col_indices: &[Vec<i32>],
b_values: &[Vec<T>],
b_cols: &[usize],
) -> SparseResult<Vec<SpGEMMResultUsize<T>>> {
let n = a_row_ptrs.len();
if n == 0 {
return Err(SparseError::InvalidArgument(
"batch must not be empty".to_string(),
));
}
if b_row_ptrs.len() != n
|| a_col_indices.len() != n
|| a_values.len() != n
|| a_rows.len() != n
|| a_cols.len() != n
|| b_col_indices.len() != n
|| b_values.len() != n
|| b_cols.len() != n
{
return Err(SparseError::InvalidArgument(
"all input slices must have the same length".to_string(),
));
}
let mut results = Vec::with_capacity(n);
for i in 0..n {
let b_rows_i = a_cols[i]; let (c_rp, c_ci, c_vals) = host_spgemm(
&a_row_ptrs[i],
&a_col_indices[i],
&a_values[i],
a_rows[i],
&b_row_ptrs[i],
&b_col_indices[i],
&b_values[i],
b_cols[i],
);
let _ = b_rows_i; results.push((c_rp, c_ci, c_vals, a_rows[i], b_cols[i]));
}
Ok(results)
}
}
impl Default for BatchedSpGEMM {
fn default() -> Self {
Self::new()
}
}
#[allow(clippy::too_many_arguments)]
fn host_spgemm<T: GpuFloat + Add<Output = T> + Mul<Output = T> + AddAssign>(
a_rp: &[i32],
a_ci: &[i32],
a_vals: &[T],
a_rows: usize,
b_rp: &[i32],
b_ci: &[i32],
b_vals: &[T],
b_cols: usize,
) -> (Vec<i32>, Vec<i32>, Vec<T>) {
use std::collections::BTreeMap;
let mut c_row_ptr = vec![0i32; a_rows + 1];
let mut c_col_idx = Vec::new();
let mut c_values: Vec<T> = Vec::new();
for row in 0..a_rows {
let a_start = a_rp[row] as usize;
let a_end = a_rp[row + 1] as usize;
let mut acc: BTreeMap<usize, T> = BTreeMap::new();
for ja in a_start..a_end {
let a_col = a_ci[ja] as usize;
let a_val = a_vals[ja];
let b_start = b_rp[a_col] as usize;
let b_end = b_rp[a_col + 1] as usize;
for jb in b_start..b_end {
let b_col = b_ci[jb] as usize;
if b_col < b_cols {
let product = a_val * b_vals[jb];
acc.entry(b_col)
.and_modify(|v| *v += product)
.or_insert(product);
}
}
}
for (&col, &val) in &acc {
c_col_idx.push(col as i32);
c_values.push(val);
}
c_row_ptr[row + 1] = c_col_idx.len() as i32;
}
(c_row_ptr, c_col_idx, c_values)
}
#[derive(Debug)]
pub struct BatchedTriSolve {
_private: (),
}
impl BatchedTriSolve {
#[inline]
pub fn new() -> Self {
Self { _private: () }
}
pub fn execute<T: GpuFloat + Add<Output = T> + Mul<Output = T> + AddAssign>(
l_batch: &[CsrMatrix<T>],
b_batch: &[Vec<T>],
) -> SparseResult<Vec<Vec<T>>> {
if l_batch.is_empty() {
return Err(SparseError::InvalidArgument(
"batch must not be empty".to_string(),
));
}
if l_batch.len() != b_batch.len() {
return Err(SparseError::InvalidArgument(format!(
"l_batch length {} != b_batch length {}",
l_batch.len(),
b_batch.len()
)));
}
let mut results = Vec::with_capacity(l_batch.len());
for (i, (l, b)) in l_batch.iter().zip(b_batch.iter()).enumerate() {
if l.rows() != l.cols() {
return Err(SparseError::DimensionMismatch(format!(
"matrix {} is not square: {}x{}",
i,
l.rows(),
l.cols()
)));
}
if b.len() < l.rows() as usize {
return Err(SparseError::DimensionMismatch(format!(
"matrix {} has {} rows but rhs has {} elements",
i,
l.rows(),
b.len()
)));
}
let (rp, ci, vals) = l.to_host()?;
let x = host_forward_solve(&rp, &ci, &vals, l.rows() as usize, b)?;
results.push(x);
}
Ok(results)
}
pub fn execute_host<T: GpuFloat + Add<Output = T> + Mul<Output = T> + AddAssign>(
row_ptrs: &[Vec<i32>],
col_indices: &[Vec<i32>],
values: &[Vec<T>],
sizes: &[usize],
rhs: &[Vec<T>],
) -> SparseResult<Vec<Vec<T>>> {
let n = row_ptrs.len();
if n == 0 {
return Err(SparseError::InvalidArgument(
"batch must not be empty".to_string(),
));
}
if col_indices.len() != n || values.len() != n || sizes.len() != n || rhs.len() != n {
return Err(SparseError::InvalidArgument(
"all input slices must have the same length".to_string(),
));
}
let mut results = Vec::with_capacity(n);
for i in 0..n {
let x =
host_forward_solve(&row_ptrs[i], &col_indices[i], &values[i], sizes[i], &rhs[i])?;
results.push(x);
}
Ok(results)
}
}
impl Default for BatchedTriSolve {
fn default() -> Self {
Self::new()
}
}
fn host_forward_solve<T: GpuFloat + Add<Output = T> + Mul<Output = T> + AddAssign>(
row_ptr: &[i32],
col_idx: &[i32],
values: &[T],
n: usize,
b: &[T],
) -> SparseResult<Vec<T>> {
let mut x = vec![T::gpu_zero(); n];
for row in 0..n {
let start = row_ptr[row] as usize;
let end = row_ptr[row + 1] as usize;
let mut off_diag_sum = T::gpu_zero();
let mut diag = T::gpu_zero();
for j in start..end {
let col = col_idx[j] as usize;
if col < row {
off_diag_sum += values[j] * x[col];
} else if col == row {
diag = values[j];
}
}
if diag == T::gpu_zero() {
return Err(SparseError::SingularMatrix);
}
let neg_sum = negate_float(off_diag_sum);
let numerator = b[row] + neg_sum;
x[row] = divide_float(numerator, diag);
}
Ok(x)
}
#[inline]
fn negate_float<T: GpuFloat>(val: T) -> T {
let bits = val.to_bits_u64();
if T::SIZE == 4 {
T::from_bits_u64(bits ^ (1u64 << 31))
} else {
T::from_bits_u64(bits ^ (1u64 << 63))
}
}
#[inline]
fn divide_float<T: GpuFloat>(num: T, den: T) -> T {
let n_bits = num.to_bits_u64();
let d_bits = den.to_bits_u64();
if T::SIZE == 4 {
let n = f32::from_bits(n_bits as u32) as f64;
let d = f32::from_bits(d_bits as u32) as f64;
let result = (n / d) as f32;
T::from_bits_u64(u64::from(result.to_bits()))
} else {
let n = f64::from_bits(n_bits);
let d = f64::from_bits(d_bits);
let result = n / d;
T::from_bits_u64(result.to_bits())
}
}
pub fn generate_batched_spmv_ptx<T: GpuFloat>() -> String {
let type_name = T::NAME;
let ptx_type = match T::SIZE {
4 => ".f32",
8 => ".f64",
_ => ".f32",
};
let elem_size = match T::SIZE {
8 => 8,
_ => 4,
};
let zero_literal = match T::SIZE {
8 => "0d0000000000000000",
_ => "0f00000000",
};
format!(
r#"//
// Batched SpMV kernel for {type_name}
// Generated by oxicuda-sparse batched module
//
.version 7.0
.target sm_70
.address_size 64
.visible .entry batched_spmv_{type_name}(
.param .u64 concat_row_ptr,
.param .u64 concat_col_idx,
.param .u64 concat_values,
.param .u64 batch_offsets_rp,
.param .u64 batch_offsets_nnz,
.param .u64 row_counts,
.param .u64 x_ptrs,
.param .u64 y_ptrs,
.param {ptx_type} alpha,
.param {ptx_type} beta,
.param .u32 batch_size
)
{{
.reg .u32 %r<16>;
.reg .u64 %rd<32>;
.reg {ptx_type} %f<8>;
.reg .pred %p<4>;
// blockIdx.x = matrix index in batch
mov.u32 %r0, %ctaid.x;
// Early exit if blockIdx >= batch_size
ld.param.u32 %r1, [batch_size];
setp.ge.u32 %p0, %r0, %r1;
@%p0 ret;
// threadIdx.x = local row within this matrix
mov.u32 %r2, %tid.x;
// Load row_count for this matrix
ld.param.u64 %rd0, [row_counts];
cvt.u64.u32 %rd1, %r0;
mad.wide.u32 %rd2, %r0, 4, %rd0;
ld.global.u32 %r3, [%rd2];
// Early exit if tid >= row_count
setp.ge.u32 %p1, %r2, %r3;
@%p1 ret;
// Load row_ptr offset for this matrix
ld.param.u64 %rd3, [batch_offsets_rp];
mad.wide.u32 %rd4, %r0, 4, %rd3;
ld.global.u32 %r4, [%rd4];
// row_start = concat_row_ptr[rp_offset + tid], row_end = next entry
ld.param.u64 %rd5, [concat_row_ptr];
add.u32 %r11, %r4, %r2;
mul.wide.u32 %rd6, %r11, 4;
add.u64 %rd5, %rd5, %rd6;
ld.global.u32 %r5, [%rd5];
add.u64 %rd6, %rd5, 4;
ld.global.u32 %r6, [%rd6];
// Load nnz offset for this matrix
ld.param.u64 %rd7, [batch_offsets_nnz];
mad.wide.u32 %rd8, %r0, 4, %rd7;
ld.global.u32 %r7, [%rd8];
// Load x and y pointers for this matrix
ld.param.u64 %rd9, [x_ptrs];
mad.wide.u32 %rd10, %r0, 8, %rd9;
ld.global.u64 %rd10, [%rd10];
ld.param.u64 %rd11, [y_ptrs];
mad.wide.u32 %rd12, %r0, 8, %rd11;
ld.global.u64 %rd11, [%rd12];
// Load concatenated col_idx / values bases and alpha / beta scalars
ld.param.u64 %rd12, [concat_col_idx];
ld.param.u64 %rd13, [concat_values];
ld.param{ptx_type} %f6, [alpha];
ld.param{ptx_type} %f7, [beta];
// acc = 0; iterate row_start .. row_end
mov{ptx_type} %f0, {zero_literal};
mov.u32 %r8, %r5;
$ROW_LOOP:
setp.lt.u32 %p2, %r8, %r6;
@!%p2 bra $ROW_DONE;
// k = nnz_offset + absolute row nnz index
add.u32 %r10, %r7, %r8;
// col = concat_col_idx[k]
mul.wide.u32 %rd14, %r10, 4;
add.u64 %rd15, %rd12, %rd14;
ld.global.u32 %r9, [%rd15];
// val = concat_values[k]
mul.wide.u32 %rd16, %r10, {elem_size};
add.u64 %rd17, %rd13, %rd16;
ld.global{ptx_type} %f1, [%rd17];
// x_val = x[col]
mul.wide.u32 %rd18, %r9, {elem_size};
add.u64 %rd19, %rd10, %rd18;
ld.global{ptx_type} %f2, [%rd19];
// acc += val * x_val
fma.rn{ptx_type} %f0, %f1, %f2, %f0;
add.u32 %r8, %r8, 1;
bra $ROW_LOOP;
$ROW_DONE:
// y[tid] = alpha * acc + beta * y[tid]
mul.wide.u32 %rd20, %r2, {elem_size};
add.u64 %rd21, %rd11, %rd20;
ld.global{ptx_type} %f3, [%rd21];
mul.rn{ptx_type} %f4, %f6, %f0;
mul.rn{ptx_type} %f5, %f7, %f3;
add.rn{ptx_type} %f4, %f4, %f5;
st.global{ptx_type} [%rd21], %f4;
ret;
}}
"#
)
}
pub fn batched_spmv_cpu(
n_rows: usize,
_n_cols: usize,
row_ptr: &[u32],
col_idx: &[u32],
values: &[f32],
x_batch: &[f32],
batch_size: usize,
) -> Vec<f32> {
let mut y = vec![0.0f32; n_rows * batch_size];
for row in 0..n_rows {
let start = row_ptr[row] as usize;
let end = row_ptr[row + 1] as usize;
for idx in start..end {
let col = col_idx[idx] as usize;
let val = values[idx];
for b in 0..batch_size {
y[row * batch_size + b] += val * x_batch[col * batch_size + b];
}
}
}
y
}
pub fn mixed_precision_spmv_cpu(
n_rows: usize,
row_ptr: &[u32],
col_idx: &[u32],
values_fp16: &[f32],
x: &[f32],
) -> Vec<f32> {
let mut y = vec![0.0f32; n_rows];
for row in 0..n_rows {
let start = row_ptr[row] as usize;
let end = row_ptr[row + 1] as usize;
let mut acc = 0.0f64;
for idx in start..end {
let col = col_idx[idx] as usize;
let val = values_fp16[idx] as f64;
acc += val * (x[col] as f64);
}
y[row] = acc as f32;
}
y
}
#[cfg(test)]
mod tests;