1use super::{BlockedCoo, Csr, DenseWithMask, SellP, SellPData, SparseView, Validated};
15use crate::arch::SimdArch;
16use crate::kernel::SimdKernel;
17use crate::scalar::Scalar;
18
19pub trait SparseSpMv<T> {
21 fn spmv(&self, x: &[T], y: &mut [T]);
26}
27
28#[inline(always)]
38pub(crate) unsafe fn build_index_vector<T: Scalar, Arch: SimdKernel<T>>(
39 cols: &[i32],
40) -> Arch::IndexVector {
41 const {
45 assert!(
46 core::mem::size_of::<Arch::IndexVector>()
47 == Arch::LANE_COUNT * core::mem::size_of::<i32>(),
48 "IndexVector size must equal LANE_COUNT * size_of::<i32>()"
49 )
50 };
51 assert!(
52 cols.len() >= Arch::LANE_COUNT,
53 "cols slice length {} is less than LANE_COUNT {}",
54 cols.len(),
55 Arch::LANE_COUNT
56 );
57 let ptr = cols.as_ptr() as *const Arch::IndexVector;
58 core::ptr::read_unaligned(ptr)
59}
60
61#[inline(never)]
62fn validate_spmv_sizes(x_len: usize, y_len: usize, ncols: usize, nrows: usize, format_name: &str) {
63 assert!(
64 x_len >= ncols,
65 "x too short for {} ncols (got {}, expected >= {})",
66 format_name,
67 x_len,
68 ncols
69 );
70 assert!(
71 y_len >= nrows,
72 "y too short for {} nrows (got {}, expected >= {})",
73 format_name,
74 y_len,
75 nrows
76 );
77}
78
79impl<'a, T, Arch> SparseSpMv<T> for SparseView<'a, T, Validated<Csr>, Arch>
80where
81 T: Scalar,
82 Arch: SimdArch + SimdKernel<T>,
83{
84 #[inline]
85 fn spmv(&self, x: &[T], y: &mut [T]) {
86 let data = self.data.storage();
87 validate_spmv_sizes(x.len(), y.len(), data.ncols, data.nrows, "CSR");
88
89 let lane_count = Arch::LANE_COUNT;
90
91 for r in 0..data.nrows {
92 let start = data.row_ptr[r] as usize;
93 let end = data.row_ptr[r + 1] as usize;
94 let row_nnz = end - start;
95
96 if row_nnz == 0 {
97 continue;
98 }
99
100 let vals = &data.values[start..end];
101 let cols = &data.col_indices[start..end];
102
103 let simd_len = (row_nnz / lane_count) * lane_count;
104
105 let acc_vec = unsafe {
115 let mut acc_vec0 = Arch::zero();
116 let mut acc_vec1 = Arch::zero();
117 let mut acc_vec2 = Arch::zero();
118 let mut acc_vec3 = Arch::zero();
119
120 let unroll_len = (row_nnz / (lane_count * 4)) * (lane_count * 4);
121 let mut j = 0usize;
122 while j < unroll_len {
123 let idx0 = build_index_vector::<T, Arch>(&cols[j..j + lane_count]);
124 acc_vec0 = Arch::fmadd(
125 Arch::gather(x.as_ptr(), idx0),
126 Arch::load_unaligned(vals[j..].as_ptr()),
127 acc_vec0,
128 );
129
130 let idx1 =
131 build_index_vector::<T, Arch>(&cols[j + lane_count..j + lane_count * 2]);
132 acc_vec1 = Arch::fmadd(
133 Arch::gather(x.as_ptr(), idx1),
134 Arch::load_unaligned(vals[j + lane_count..].as_ptr()),
135 acc_vec1,
136 );
137
138 let idx2 = build_index_vector::<T, Arch>(
139 &cols[j + lane_count * 2..j + lane_count * 3],
140 );
141 acc_vec2 = Arch::fmadd(
142 Arch::gather(x.as_ptr(), idx2),
143 Arch::load_unaligned(vals[j + lane_count * 2..].as_ptr()),
144 acc_vec2,
145 );
146
147 let idx3 = build_index_vector::<T, Arch>(
148 &cols[j + lane_count * 3..j + lane_count * 4],
149 );
150 acc_vec3 = Arch::fmadd(
151 Arch::gather(x.as_ptr(), idx3),
152 Arch::load_unaligned(vals[j + lane_count * 3..].as_ptr()),
153 acc_vec3,
154 );
155
156 j += lane_count * 4;
157 }
158
159 let mut acc_vec =
160 Arch::add(Arch::add(acc_vec0, acc_vec1), Arch::add(acc_vec2, acc_vec3));
161
162 while j < simd_len {
163 let idx = build_index_vector::<T, Arch>(&cols[j..j + lane_count]);
164 acc_vec = Arch::fmadd(
165 Arch::gather(x.as_ptr(), idx),
166 Arch::load_unaligned(vals[j..].as_ptr()),
167 acc_vec,
168 );
169 j += lane_count;
170 }
171 acc_vec
172 };
173
174 let mut acc = unsafe { Arch::sum_reduce(acc_vec) };
176
177 let mut j = simd_len;
178 while j < row_nnz {
179 acc += vals[j] * unsafe { *x.get_unchecked(cols[j] as usize) };
187 j += 1;
188 }
189
190 y[r] += acc;
191 }
192 }
193}
194
195impl<'a, T, Arch> SparseSpMv<T> for SparseView<'a, T, DenseWithMask, Arch>
196where
197 T: Scalar,
198 Arch: SimdArch + SimdKernel<T>,
199{
200 #[inline]
201 fn spmv(&self, x: &[T], y: &mut [T]) {
202 let data = &self.data;
203 validate_spmv_sizes(x.len(), y.len(), data.ncols, data.nrows, "DenseWithMask");
204
205 let lane_count = Arch::LANE_COUNT;
206
207 for r in 0..data.nrows {
208 let row_offset = r * data.ncols;
209 let vals = &data.values[row_offset..row_offset + data.ncols];
210 let mask_bits = &data.mask[row_offset..row_offset + data.ncols];
211
212 let simd_len = (data.ncols / lane_count) * lane_count;
213
214 let acc_vec = unsafe {
222 let zero_vec = Arch::zero();
223 let mut acc_vec0 = zero_vec;
224 let mut acc_vec1 = zero_vec;
225 let mut acc_vec2 = zero_vec;
226 let mut acc_vec3 = zero_vec;
227
228 let unroll_len = (data.ncols / (lane_count * 4)) * (lane_count * 4);
229 let mut j = 0usize;
230 while j < unroll_len {
231 let msk0 = Arch::mask_from_bools(&mask_bits[j..j + lane_count]);
232 acc_vec0 = Arch::masked_fmadd(
233 Arch::masked_load_unaligned(vals[j..].as_ptr(), msk0, zero_vec),
234 Arch::load_unaligned(x[j..].as_ptr()),
235 acc_vec0,
236 msk0,
237 );
238
239 let msk1 =
240 Arch::mask_from_bools(&mask_bits[j + lane_count..j + lane_count * 2]);
241 acc_vec1 = Arch::masked_fmadd(
242 Arch::masked_load_unaligned(
243 vals[j + lane_count..].as_ptr(),
244 msk1,
245 zero_vec,
246 ),
247 Arch::load_unaligned(x[j + lane_count..].as_ptr()),
248 acc_vec1,
249 msk1,
250 );
251
252 let msk2 =
253 Arch::mask_from_bools(&mask_bits[j + lane_count * 2..j + lane_count * 3]);
254 acc_vec2 = Arch::masked_fmadd(
255 Arch::masked_load_unaligned(
256 vals[j + lane_count * 2..].as_ptr(),
257 msk2,
258 zero_vec,
259 ),
260 Arch::load_unaligned(x[j + lane_count * 2..].as_ptr()),
261 acc_vec2,
262 msk2,
263 );
264
265 let msk3 =
266 Arch::mask_from_bools(&mask_bits[j + lane_count * 3..j + lane_count * 4]);
267 acc_vec3 = Arch::masked_fmadd(
268 Arch::masked_load_unaligned(
269 vals[j + lane_count * 3..].as_ptr(),
270 msk3,
271 zero_vec,
272 ),
273 Arch::load_unaligned(x[j + lane_count * 3..].as_ptr()),
274 acc_vec3,
275 msk3,
276 );
277
278 j += lane_count * 4;
279 }
280
281 let mut acc_vec =
282 Arch::add(Arch::add(acc_vec0, acc_vec1), Arch::add(acc_vec2, acc_vec3));
283
284 while j < simd_len {
285 let msk = Arch::mask_from_bools(&mask_bits[j..j + lane_count]);
286 acc_vec = Arch::masked_fmadd(
287 Arch::masked_load_unaligned(vals[j..].as_ptr(), msk, zero_vec),
288 Arch::load_unaligned(x[j..].as_ptr()),
289 acc_vec,
290 msk,
291 );
292 j += lane_count;
293 }
294 acc_vec
295 };
296
297 let mut acc = unsafe { Arch::sum_reduce(acc_vec) };
299
300 let mut j = simd_len;
301 while j < data.ncols {
302 if mask_bits[j] {
303 acc += vals[j] * x[j];
304 }
305 j += 1;
306 }
307
308 y[r] += acc;
309 }
310 }
311}
312
313impl<'a, T, const BM: usize, const BN: usize, Arch> SparseSpMv<T>
314 for SparseView<'a, T, Validated<BlockedCoo<BM, BN>>, Arch>
315where
316 T: Scalar,
317 Arch: SimdArch + SimdKernel<T>,
318{
319 #[inline]
320 fn spmv(&self, x: &[T], y: &mut [T]) {
321 let data = self.data.storage();
322 validate_spmv_sizes(x.len(), y.len(), data.ncols, data.nrows, "BlockedCoo");
323
324 let block_size = BM * BN;
325 let lane_count = Arch::LANE_COUNT;
326
327 if BN == lane_count {
328 for b in 0..data.nblocks {
329 let br = data.block_row[b] as usize;
330 let bc = data.block_col[b] as usize;
331 let block = &data.blocks[b * block_size..(b + 1) * block_size];
332
333 unsafe {
339 let x_vec = Arch::load_unaligned(x.as_ptr().add(bc));
340 for i in 0..BM {
341 let b_vec = Arch::load_unaligned(block.as_ptr().add(i * BN));
342 y[br + i] += Arch::sum_reduce(Arch::mul(b_vec, x_vec));
343 }
344 }
345 }
346 } else if BN == lane_count * 2 {
347 for b in 0..data.nblocks {
348 let br = data.block_row[b] as usize;
349 let bc = data.block_col[b] as usize;
350 let block = &data.blocks[b * block_size..(b + 1) * block_size];
351
352 unsafe {
357 let x_vec0 = Arch::load_unaligned(x.as_ptr().add(bc));
358 let x_vec1 = Arch::load_unaligned(x.as_ptr().add(bc + lane_count));
359 for i in 0..BM {
360 let offset = i * BN;
361 let prod0 =
362 Arch::mul(Arch::load_unaligned(block.as_ptr().add(offset)), x_vec0);
363 let prod1 = Arch::mul(
364 Arch::load_unaligned(block.as_ptr().add(offset + lane_count)),
365 x_vec1,
366 );
367 y[br + i] += Arch::sum_reduce(Arch::add(prod0, prod1));
368 }
369 }
370 }
371 } else {
372 unsafe {
377 let x_ptr = x.as_ptr();
378 let y_ptr = y.as_mut_ptr();
379 for b in 0..data.nblocks {
380 let br = data.block_row[b] as usize;
381 let bc = data.block_col[b] as usize;
382 let block_ptr = data.blocks.as_ptr().add(b * block_size);
383
384 for i in 0..BM {
385 let row_ptr = block_ptr.add(i * BN);
386 let mut s = T::ZERO;
387 for k in 0..BN {
388 s = s + *row_ptr.add(k) * *x_ptr.add(bc + k);
389 }
390 *y_ptr.add(br + i) += s;
391 }
392 }
393 }
394 }
395 }
396}
397
398fn sellp_spmv_scalar<T, const C: usize>(data: &SellPData<'_, T, C>, x: &[T], y: &mut [T])
399where
400 T: Scalar,
401{
402 let nslices = data.nslices();
403 for s in 0..nslices {
404 let col_count = data.slice_col_count[s] as usize;
405 let start_offset = data.slice_ptr[s] as usize;
406
407 let mut row_acc = [T::ZERO; C];
408
409 for col in 0..col_count {
410 for row in 0..C {
411 let idx = start_offset + col * C + row;
412 let val = data.values[idx];
413 let c_idx = data.col_indices[idx] as usize;
414 row_acc[row] += val * unsafe { *x.get_unchecked(c_idx) };
422 }
423 }
424
425 for row in 0..C {
426 let r_idx = s * C + row;
427 if r_idx < y.len() {
428 y[r_idx] += row_acc[row];
429 }
430 }
431 }
432}
433
434unsafe fn sellp_spmv_vectorized<T, const C: usize, Arch>(
446 data: &SellPData<'_, T, C>,
447 x: &[T],
448 y: &mut [T],
449) where
450 T: Scalar,
451 Arch: SimdArch + SimdKernel<T>,
452{
453 assert_eq!(
454 Arch::LANE_COUNT,
455 C,
456 "sellp_spmv_vectorized requires Arch::LANE_COUNT == C"
457 );
458 let nslices = data.nslices();
459 for s in 0..nslices {
460 let col_count = data.slice_col_count[s] as usize;
461 let start_offset = data.slice_ptr[s] as usize;
462
463 let mut acc0 = Arch::zero();
464 let mut acc1 = Arch::zero();
465 let mut acc2 = Arch::zero();
466 let mut acc3 = Arch::zero();
467
468 let unroll = (col_count / 4) * 4;
469 let mut col = 0;
470 while col < unroll {
471 let offset = start_offset + col * C;
473 let val_vec = Arch::load_unaligned(data.values[offset..].as_ptr());
474 let idx_vec = build_index_vector::<T, Arch>(&data.col_indices[offset..offset + C]);
475 let x_vec = Arch::gather(x.as_ptr(), idx_vec);
476 acc0 = Arch::fmadd(val_vec, x_vec, acc0);
477
478 let offset = start_offset + (col + 1) * C;
480 let val_vec = Arch::load_unaligned(data.values[offset..].as_ptr());
481 let idx_vec = build_index_vector::<T, Arch>(&data.col_indices[offset..offset + C]);
482 let x_vec = Arch::gather(x.as_ptr(), idx_vec);
483 acc1 = Arch::fmadd(val_vec, x_vec, acc1);
484
485 let offset = start_offset + (col + 2) * C;
487 let val_vec = Arch::load_unaligned(data.values[offset..].as_ptr());
488 let idx_vec = build_index_vector::<T, Arch>(&data.col_indices[offset..offset + C]);
489 let x_vec = Arch::gather(x.as_ptr(), idx_vec);
490 acc2 = Arch::fmadd(val_vec, x_vec, acc2);
491
492 let offset = start_offset + (col + 3) * C;
494 let val_vec = Arch::load_unaligned(data.values[offset..].as_ptr());
495 let idx_vec = build_index_vector::<T, Arch>(&data.col_indices[offset..offset + C]);
496 let x_vec = Arch::gather(x.as_ptr(), idx_vec);
497 acc3 = Arch::fmadd(val_vec, x_vec, acc3);
498
499 col += 4;
500 }
501
502 let mut acc = Arch::add(Arch::add(acc0, acc1), Arch::add(acc2, acc3));
503
504 while col < col_count {
505 let offset = start_offset + col * C;
506 let val_vec = Arch::load_unaligned(data.values[offset..].as_ptr());
507 let idx_vec = build_index_vector::<T, Arch>(&data.col_indices[offset..offset + C]);
508 let x_vec = Arch::gather(x.as_ptr(), idx_vec);
509 acc = Arch::fmadd(val_vec, x_vec, acc);
510 col += 1;
511 }
512
513 let r_idx = s * C;
514 if r_idx + C <= y.len() {
515 let y_ptr = y.as_mut_ptr().add(r_idx);
516 let y_vec = Arch::load_unaligned(y_ptr);
517 let res_vec = Arch::add(y_vec, acc);
518 Arch::store_unaligned(y_ptr, res_vec);
519 } else {
520 let mut temp = [T::ZERO; C];
521 Arch::store_unaligned(temp.as_mut_ptr(), acc);
522 for row in 0..y.len() - r_idx {
523 y[r_idx + row] += temp[row];
524 }
525 }
526 }
527}
528
529impl<'a, T, const C: usize, Arch> SparseSpMv<T> for SparseView<'a, T, Validated<SellP<C>>, Arch>
530where
531 T: Scalar,
532 Arch: SimdArch + SimdKernel<T>,
533{
534 #[inline]
535 fn spmv(&self, x: &[T], y: &mut [T]) {
536 let data = self.data.storage();
537 validate_spmv_sizes(x.len(), y.len(), data.ncols, data.nrows, "SellP");
538
539 if Arch::LANE_COUNT == C {
540 unsafe { sellp_spmv_vectorized::<T, C, Arch>(data, x, y) };
545 } else {
546 sellp_spmv_scalar::<T, C>(data, x, y);
547 }
548 }
549}