1use std::sync::OnceLock;
25
26use wide::{f32x4, f32x8, f64x2, f64x4};
27
28#[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
29use wide::{f32x16, f64x8};
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum SimdLevel {
38 Scalar,
40 Sse,
42 Avx2,
44 Avx512,
46}
47
48static SIMD_LEVEL: OnceLock<SimdLevel> = OnceLock::new();
50
51pub fn detect_simd_level() -> SimdLevel {
60 *SIMD_LEVEL.get_or_init(|| {
61 #[cfg(target_arch = "x86_64")]
62 {
63 if is_x86_feature_detected!("avx512f") {
64 return SimdLevel::Avx512;
65 }
66 if is_x86_feature_detected!("avx2") {
67 return SimdLevel::Avx2;
68 }
69 if is_x86_feature_detected!("sse4.1") {
70 return SimdLevel::Sse;
71 }
72 SimdLevel::Scalar
73 }
74
75 #[cfg(target_arch = "aarch64")]
76 {
77 SimdLevel::Sse
78 }
79
80 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
81 {
82 SimdLevel::Scalar
83 }
84 })
85}
86
87pub trait EdgeSimd: Sized + Copy {
92 fn dot_simd(a: &[Self], b: &[Self]) -> Self;
104
105 fn exp_in_place_simd(x: &mut [Self]);
126}
127
128#[inline]
147fn dot_f64_scalar(a: &[f64], b: &[f64]) -> f64 {
148 a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
149}
150
151#[inline]
162fn dot_f64_sse(a: &[f64], b: &[f64]) -> f64 {
163 const LANES: usize = 2;
164
165 let n = a.len().min(b.len());
166 let mut total = f64x2::ZERO;
167 let mut i = 0;
168
169 while i + LANES <= n {
170 total += f64x2::from(&a[i..i + LANES]) * f64x2::from(&b[i..i + LANES]);
171 i += LANES;
172 }
173
174 total.reduce_add() + dot_f64_scalar(&a[i..n], &b[i..n])
175}
176
177#[inline]
188fn dot_f64_avx2(a: &[f64], b: &[f64]) -> f64 {
189 const LANES: usize = 4;
190
191 let n = a.len().min(b.len());
192 let mut total = f64x4::ZERO;
193 let mut i = 0;
194
195 while i + LANES <= n {
196 total += f64x4::from(&a[i..i + LANES]) * f64x4::from(&b[i..i + LANES]);
197 i += LANES;
198 }
199
200 total.reduce_add() + dot_f64_sse(&a[i..n], &b[i..n])
201}
202
203#[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
214#[inline]
215fn dot_f64_avx512(a: &[f64], b: &[f64]) -> f64 {
216 const LANES: usize = 8;
217
218 let n = a.len().min(b.len());
219 let mut total = f64x8::ZERO;
220 let mut i = 0;
221
222 while i + LANES <= n {
223 total += f64x8::from(&a[i..i + LANES]) * f64x8::from(&b[i..i + LANES]);
224 i += LANES;
225 }
226
227 total.reduce_add() + dot_f64_avx2(&a[i..n], &b[i..n])
228}
229
230#[cfg(not(all(target_arch = "x86_64", target_feature = "avx512f")))]
242#[inline]
243fn dot_f64_avx512(a: &[f64], b: &[f64]) -> f64 {
244 dot_f64_avx2(a, b)
245}
246
247#[inline]
262fn dot_f32_scalar(a: &[f32], b: &[f32]) -> f32 {
263 a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
264}
265
266#[inline]
277fn dot_f32_sse(a: &[f32], b: &[f32]) -> f32 {
278 const LANES: usize = 4;
279
280 let n = a.len().min(b.len());
281 let mut total = f32x4::ZERO;
282 let mut i = 0;
283
284 while i + LANES <= n {
285 total += f32x4::from(&a[i..i + LANES]) * f32x4::from(&b[i..i + LANES]);
286 i += LANES;
287 }
288
289 total.reduce_add() + dot_f32_scalar(&a[i..n], &b[i..n])
290}
291
292#[inline]
303fn dot_f32_avx2(a: &[f32], b: &[f32]) -> f32 {
304 const LANES: usize = 8;
305
306 let n = a.len().min(b.len());
307 let mut total = f32x8::ZERO;
308 let mut i = 0;
309
310 while i + LANES <= n {
311 total += f32x8::from(&a[i..i + LANES]) * f32x8::from(&b[i..i + LANES]);
312 i += LANES;
313 }
314
315 total.reduce_add() + dot_f32_sse(&a[i..n], &b[i..n])
316}
317
318#[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
329#[inline]
330fn dot_f32_avx512(a: &[f32], b: &[f32]) -> f32 {
331 const LANES: usize = 16;
332
333 let n = a.len().min(b.len());
334 let mut total = f32x16::ZERO;
335 let mut i = 0;
336
337 while i + LANES <= n {
338 total += f32x16::from(&a[i..i + LANES]) * f32x16::from(&b[i..i + LANES]);
339 i += LANES;
340 }
341
342 total.reduce_add() + dot_f32_avx2(&a[i..n], &b[i..n])
343}
344
345#[cfg(not(all(target_arch = "x86_64", target_feature = "avx512f")))]
356#[inline]
357fn dot_f32_avx512(a: &[f32], b: &[f32]) -> f32 {
358 dot_f32_avx2(a, b)
359}
360
361#[inline]
375fn exp_f64_scalar(x: &mut [f64]) {
376 for v in x.iter_mut() {
377 *v = v.exp();
378 }
379}
380
381#[inline]
390fn exp_f64_sse(x: &mut [f64]) {
391 const LANES: usize = 2;
392
393 let n = x.len();
394 let mut i = 0;
395
396 while i + LANES <= n {
397 let v = f64x2::from(&x[i..i + LANES]).exp();
398 x[i..i + LANES].copy_from_slice(v.as_array());
399 i += LANES;
400 }
401
402 exp_f64_scalar(&mut x[i..]);
403}
404
405#[inline]
411fn exp_f64_avx2(x: &mut [f64]) {
412 const LANES: usize = 4;
413
414 let n = x.len();
415 let mut i = 0;
416
417 while i + LANES <= n {
418 let v = f64x4::from(&x[i..i + LANES]).exp();
419 x[i..i + LANES].copy_from_slice(v.as_array());
420 i += LANES;
421 }
422
423 exp_f64_sse(&mut x[i..]);
424}
425
426#[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
432#[inline]
433fn exp_f64_avx512(x: &mut [f64]) {
434 const LANES: usize = 8;
435
436 let n = x.len();
437 let mut i = 0;
438
439 while i + LANES <= n {
440 let v = f64x8::from(&x[i..i + LANES]).exp();
441 x[i..i + LANES].copy_from_slice(v.as_array());
442 i += LANES;
443 }
444
445 exp_f64_avx2(&mut x[i..]);
446}
447
448#[cfg(not(all(target_arch = "x86_64", target_feature = "avx512f")))]
454#[inline]
455fn exp_f64_avx512(x: &mut [f64]) {
456 exp_f64_avx2(x)
457}
458
459#[inline]
469fn exp_f32_scalar(x: &mut [f32]) {
470 for v in x.iter_mut() {
471 *v = v.exp();
472 }
473}
474
475#[inline]
481fn exp_f32_sse(x: &mut [f32]) {
482 const LANES: usize = 4;
483
484 let n = x.len();
485 let mut i = 0;
486
487 while i + LANES <= n {
488 let v = f32x4::from(&x[i..i + LANES]).exp();
489 x[i..i + LANES].copy_from_slice(v.as_array());
490 i += LANES;
491 }
492
493 exp_f32_scalar(&mut x[i..]);
494}
495
496#[inline]
502fn exp_f32_avx2(x: &mut [f32]) {
503 const LANES: usize = 8;
504
505 let n = x.len();
506 let mut i = 0;
507
508 while i + LANES <= n {
509 let v = f32x8::from(&x[i..i + LANES]).exp();
510 x[i..i + LANES].copy_from_slice(v.as_array());
511 i += LANES;
512 }
513
514 exp_f32_sse(&mut x[i..]);
515}
516
517#[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
523#[inline]
524fn exp_f32_avx512(x: &mut [f32]) {
525 const LANES: usize = 16;
526
527 let n = x.len();
528 let mut i = 0;
529
530 while i + LANES <= n {
531 let v = f32x16::from(&x[i..i + LANES]).exp();
532 x[i..i + LANES].copy_from_slice(v.as_array());
533 i += LANES;
534 }
535
536 exp_f32_avx2(&mut x[i..]);
537}
538
539#[cfg(not(all(target_arch = "x86_64", target_feature = "avx512f")))]
545#[inline]
546fn exp_f32_avx512(x: &mut [f32]) {
547 exp_f32_avx2(x)
548}
549
550impl EdgeSimd for f64 {
555 #[inline]
556 fn dot_simd(a: &[f64], b: &[f64]) -> f64 {
557 match detect_simd_level() {
558 SimdLevel::Avx512 => dot_f64_avx512(a, b),
559 SimdLevel::Avx2 => dot_f64_avx2(a, b),
560 SimdLevel::Sse => dot_f64_sse(a, b),
561 SimdLevel::Scalar => dot_f64_scalar(a, b),
562 }
563 }
564
565 #[inline]
566 fn exp_in_place_simd(x: &mut [f64]) {
567 match detect_simd_level() {
568 SimdLevel::Avx512 => exp_f64_avx512(x),
569 SimdLevel::Avx2 => exp_f64_avx2(x),
570 SimdLevel::Sse => exp_f64_sse(x),
571 SimdLevel::Scalar => exp_f64_scalar(x),
572 }
573 }
574}
575
576impl EdgeSimd for f32 {
577 #[inline]
578 fn dot_simd(a: &[f32], b: &[f32]) -> f32 {
579 match detect_simd_level() {
580 SimdLevel::Avx512 => dot_f32_avx512(a, b),
581 SimdLevel::Avx2 => dot_f32_avx2(a, b),
582 SimdLevel::Sse => dot_f32_sse(a, b),
583 SimdLevel::Scalar => dot_f32_scalar(a, b),
584 }
585 }
586
587 #[inline]
588 fn exp_in_place_simd(x: &mut [f32]) {
589 match detect_simd_level() {
590 SimdLevel::Avx512 => exp_f32_avx512(x),
591 SimdLevel::Avx2 => exp_f32_avx2(x),
592 SimdLevel::Sse => exp_f32_sse(x),
593 SimdLevel::Scalar => exp_f32_scalar(x),
594 }
595 }
596}
597
598#[cfg(test)]
603mod tests {
604 use super::*;
605 use approx::assert_relative_eq;
606
607 const SIZES: [usize; 16] = [0, 1, 2, 3, 5, 6, 7, 8, 9, 15, 16, 17, 31, 32, 33, 45];
611
612 fn ramp_f64(n: usize) -> Vec<f64> {
613 (0..n).map(|i| (i as f64) * 0.5 - 3.0).collect()
614 }
615
616 fn ramp_f32(n: usize) -> Vec<f32> {
617 (0..n).map(|i| (i as f32) * 0.5 - 3.0).collect()
618 }
619
620 #[test]
623 fn test_dot_tiers_match_scalar_f64() {
624 for n in SIZES {
625 let a = ramp_f64(n);
626 let b: Vec<f64> = ramp_f64(n).iter().map(|v| v * 1.7 + 0.3).collect();
627 let expected = dot_f64_scalar(&a, &b);
628 assert_relative_eq!(dot_f64_sse(&a, &b), expected, epsilon = 1e-12);
629 assert_relative_eq!(dot_f64_avx2(&a, &b), expected, epsilon = 1e-12);
630 assert_relative_eq!(dot_f64_avx512(&a, &b), expected, epsilon = 1e-12);
631 assert_relative_eq!(f64::dot_simd(&a, &b), expected, epsilon = 1e-12);
632 }
633 }
634
635 #[test]
636 fn test_dot_tiers_match_scalar_f32() {
637 for n in SIZES {
638 let a = ramp_f32(n);
639 let b: Vec<f32> = ramp_f32(n).iter().map(|v| v * 1.7 + 0.3).collect();
640 let expected = dot_f32_scalar(&a, &b);
641 assert_relative_eq!(dot_f32_sse(&a, &b), expected, epsilon = 1e-4);
642 assert_relative_eq!(dot_f32_avx2(&a, &b), expected, epsilon = 1e-4);
643 assert_relative_eq!(dot_f32_avx512(&a, &b), expected, epsilon = 1e-4);
644 assert_relative_eq!(f32::dot_simd(&a, &b), expected, epsilon = 1e-4);
645 }
646 }
647
648 #[test]
649 fn test_exp_tiers_match_scalar() {
650 for n in SIZES {
651 let mut expected = ramp_f64(n);
652 exp_f64_scalar(&mut expected);
653
654 for kernel in [
655 exp_f64_sse as fn(&mut [f64]),
656 exp_f64_avx2,
657 exp_f64_avx512,
658 f64::exp_in_place_simd,
659 ] {
660 let mut x = ramp_f64(n);
661 kernel(&mut x);
662 for (got, want) in x.iter().zip(expected.iter()) {
663 assert_relative_eq!(got, want, max_relative = 1e-12);
664 }
665 }
666
667 let mut expected = ramp_f32(n);
668 exp_f32_scalar(&mut expected);
669
670 for kernel in [
671 exp_f32_sse as fn(&mut [f32]),
672 exp_f32_avx2,
673 exp_f32_avx512,
674 f32::exp_in_place_simd,
675 ] {
676 let mut x = ramp_f32(n);
677 kernel(&mut x);
678 for (got, want) in x.iter().zip(expected.iter()) {
679 assert_relative_eq!(got, want, max_relative = 1e-5);
680 }
681 }
682 }
683 }
684
685 #[test]
689 fn test_dot_truncates_on_length_mismatch() {
690 let a = vec![1.0_f64; 20];
691 let b = vec![2.0_f64; 5];
692 assert_relative_eq!(dot_f64_scalar(&a, &b), 10.0, epsilon = 1e-12);
693 assert_relative_eq!(dot_f64_sse(&a, &b), 10.0, epsilon = 1e-12);
694 assert_relative_eq!(dot_f64_avx2(&a, &b), 10.0, epsilon = 1e-12);
695 assert_relative_eq!(dot_f64_avx512(&a, &b), 10.0, epsilon = 1e-12);
696 assert_relative_eq!(f64::dot_simd(&a, &b), 10.0, epsilon = 1e-12);
697 }
698
699 #[test]
701 fn test_detect_simd_level_is_stable() {
702 assert_eq!(detect_simd_level(), detect_simd_level());
703 }
704}