1#[cfg(target_arch = "x86_64")]
4use std::arch::x86_64::*;
5
6#[cfg(target_arch = "aarch64")]
7use std::arch::aarch64::*;
8
9use std::sync::OnceLock;
10
11use super::simd_config;
12
13pub type DotKernel = fn(&[f32], &[f32]) -> f32;
15
16static DOT_PRODUCT_KERNEL: OnceLock<DotKernel> = OnceLock::new();
17
18#[inline]
22pub fn resolved_dot_product_kernel() -> DotKernel {
23 *DOT_PRODUCT_KERNEL.get_or_init(resolve_dot_product_kernel)
24}
25
26fn resolve_dot_product_kernel() -> DotKernel {
27 let config = simd_config();
28
29 #[cfg(target_arch = "x86_64")]
30 {
31 if config.avx512f_enabled {
32 return dot_product_avx512_kernel;
33 }
34 if config.avx2_enabled && config.fma_enabled {
35 return dot_product_avx2_kernel;
36 }
37 }
38
39 #[cfg(target_arch = "aarch64")]
40 {
41 if config.neon_enabled {
42 return dot_product_neon_kernel;
43 }
44 }
45
46 dot_product_scalar
47}
48
49pub type DotBatch4Kernel = fn(&[f32], &[f32], &[f32], &[f32], &[f32]) -> [f32; 4];
58
59static DOT_PRODUCT_BATCH4_KERNEL: OnceLock<DotBatch4Kernel> = OnceLock::new();
60
61#[inline]
65pub fn resolved_dot_product_batch4_kernel() -> DotBatch4Kernel {
66 *DOT_PRODUCT_BATCH4_KERNEL.get_or_init(resolve_dot_product_batch4_kernel)
67}
68
69#[inline]
73pub fn dot_product_batch4(
74 query: &[f32],
75 c0: &[f32],
76 c1: &[f32],
77 c2: &[f32],
78 c3: &[f32],
79) -> [f32; 4] {
80 if query.len() != c0.len()
81 || query.len() != c1.len()
82 || query.len() != c2.len()
83 || query.len() != c3.len()
84 {
85 debug_assert!(
86 false,
87 "dot_product_batch4: dimension mismatch (query={}, c0={}, c1={}, c2={}, c3={})",
88 query.len(),
89 c0.len(),
90 c1.len(),
91 c2.len(),
92 c3.len()
93 );
94 return [0.0; 4];
95 }
96 resolved_dot_product_batch4_kernel()(query, c0, c1, c2, c3)
97}
98
99fn resolve_dot_product_batch4_kernel() -> DotBatch4Kernel {
100 let config = simd_config();
101
102 #[cfg(target_arch = "x86_64")]
103 {
104 if config.avx2_enabled && config.fma_enabled {
105 return dot_product_batch4_avx2_kernel;
106 }
107 }
108
109 #[cfg(target_arch = "aarch64")]
110 {
111 if config.neon_enabled {
112 return dot_product_batch4_neon_kernel;
113 }
114 }
115
116 dot_product_batch4_scalar
117}
118
119fn dot_product_batch4_scalar(
121 q: &[f32],
122 c0: &[f32],
123 c1: &[f32],
124 c2: &[f32],
125 c3: &[f32],
126) -> [f32; 4] {
127 let mut out = [0.0f32; 4];
128 for i in 0..q.len() {
129 let qi = q[i];
130 out[0] += qi * c0[i];
131 out[1] += qi * c1[i];
132 out[2] += qi * c2[i];
133 out[3] += qi * c3[i];
134 }
135 out
136}
137
138#[cfg(target_arch = "x86_64")]
139#[inline]
140fn dot_product_avx512_kernel(a: &[f32], b: &[f32]) -> f32 {
141 unsafe { dot_product_avx512_unrolled(a, b) }
143}
144
145#[cfg(target_arch = "x86_64")]
146#[inline]
147fn dot_product_avx2_kernel(a: &[f32], b: &[f32]) -> f32 {
148 if a.len() == 384 {
150 unsafe { dot_product_384_avx2(a, b) }
151 } else {
152 unsafe { dot_product_avx2_8acc(a, b) }
153 }
154}
155
156#[cfg(target_arch = "aarch64")]
157#[inline]
158fn dot_product_neon_kernel(a: &[f32], b: &[f32]) -> f32 {
159 unsafe { dot_product_neon_unrolled(a, b) }
161}
162
163#[inline]
177pub fn dot_product(a: &[f32], b: &[f32]) -> f32 {
178 if a.len() != b.len() {
180 return 0.0;
181 }
182 debug_assert_eq!(a.len(), b.len());
183 resolved_dot_product_kernel()(a, b)
184}
185
186#[inline]
188pub(crate) fn dot_product_scalar(a: &[f32], b: &[f32]) -> f32 {
189 a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
190}
191
192#[cfg(target_arch = "x86_64")]
211#[target_feature(enable = "avx512f")]
212unsafe fn dot_product_avx512_unrolled(a: &[f32], b: &[f32]) -> f32 {
213 const SIMD_WIDTH: usize = 16;
214 const UNROLL: usize = 4;
215 const CHUNK_SIZE: usize = SIMD_WIDTH * UNROLL; let n = a.len();
218 debug_assert_eq!(n, b.len());
219 let chunks = n / CHUNK_SIZE;
220
221 let mut sum0 = _mm512_setzero_ps();
223 let mut sum1 = _mm512_setzero_ps();
224 let mut sum2 = _mm512_setzero_ps();
225 let mut sum3 = _mm512_setzero_ps();
226
227 for i in 0..chunks {
228 let base = i * CHUNK_SIZE;
229
230 let a0 = _mm512_loadu_ps(a.as_ptr().add(base));
231 let b0 = _mm512_loadu_ps(b.as_ptr().add(base));
232 sum0 = _mm512_fmadd_ps(a0, b0, sum0);
233
234 let a1 = _mm512_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH));
235 let b1 = _mm512_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH));
236 sum1 = _mm512_fmadd_ps(a1, b1, sum1);
237
238 let a2 = _mm512_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 2));
239 let b2 = _mm512_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 2));
240 sum2 = _mm512_fmadd_ps(a2, b2, sum2);
241
242 let a3 = _mm512_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 3));
243 let b3 = _mm512_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 3));
244 sum3 = _mm512_fmadd_ps(a3, b3, sum3);
245 }
246
247 let sum01 = _mm512_add_ps(sum0, sum1);
249 let sum23 = _mm512_add_ps(sum2, sum3);
250 let sum_vec = _mm512_add_ps(sum01, sum23);
251
252 let main_sum = horizontal_sum_avx512(sum_vec);
253
254 let main_processed = chunks * CHUNK_SIZE;
256 let remaining = n - main_processed;
257 let remaining_chunks = remaining / SIMD_WIDTH;
258
259 let mut remainder_sum = _mm512_setzero_ps();
260 for i in 0..remaining_chunks {
261 let offset = main_processed + i * SIMD_WIDTH;
262 let a_vec = _mm512_loadu_ps(a.as_ptr().add(offset));
263 let b_vec = _mm512_loadu_ps(b.as_ptr().add(offset));
264 remainder_sum = _mm512_fmadd_ps(a_vec, b_vec, remainder_sum);
265 }
266
267 let mut total = main_sum + horizontal_sum_avx512(remainder_sum);
268
269 let scalar_start = main_processed + remaining_chunks * SIMD_WIDTH;
271 for i in scalar_start..n {
272 total += a[i] * b[i];
273 }
274
275 total
276}
277
278#[cfg(target_arch = "x86_64")]
284#[target_feature(enable = "avx512f")]
285#[inline]
286pub(crate) unsafe fn horizontal_sum_avx512(v: __m512) -> f32 {
287 _mm512_reduce_add_ps(v)
288}
289
290#[cfg(target_arch = "x86_64")]
309#[target_feature(enable = "avx2", enable = "fma")]
310unsafe fn dot_product_avx2_8acc(a: &[f32], b: &[f32]) -> f32 {
311 const SIMD_WIDTH: usize = 8;
312 const UNROLL: usize = 8;
313 const CHUNK_SIZE: usize = SIMD_WIDTH * UNROLL; let n = a.len();
315 debug_assert_eq!(n, b.len());
316 let chunks = n / CHUNK_SIZE;
317
318 let mut sum0 = _mm256_setzero_ps();
320 let mut sum1 = _mm256_setzero_ps();
321 let mut sum2 = _mm256_setzero_ps();
322 let mut sum3 = _mm256_setzero_ps();
323 let mut sum4 = _mm256_setzero_ps();
324 let mut sum5 = _mm256_setzero_ps();
325 let mut sum6 = _mm256_setzero_ps();
326 let mut sum7 = _mm256_setzero_ps();
327
328 for i in 0..chunks {
329 let base = i * CHUNK_SIZE;
330
331 let a0 = _mm256_loadu_ps(a.as_ptr().add(base));
332 let b0 = _mm256_loadu_ps(b.as_ptr().add(base));
333 sum0 = _mm256_fmadd_ps(a0, b0, sum0);
334
335 let a1 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH));
336 let b1 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH));
337 sum1 = _mm256_fmadd_ps(a1, b1, sum1);
338
339 let a2 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 2));
340 let b2 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 2));
341 sum2 = _mm256_fmadd_ps(a2, b2, sum2);
342
343 let a3 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 3));
344 let b3 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 3));
345 sum3 = _mm256_fmadd_ps(a3, b3, sum3);
346
347 let a4 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 4));
348 let b4 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 4));
349 sum4 = _mm256_fmadd_ps(a4, b4, sum4);
350
351 let a5 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 5));
352 let b5 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 5));
353 sum5 = _mm256_fmadd_ps(a5, b5, sum5);
354
355 let a6 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 6));
356 let b6 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 6));
357 sum6 = _mm256_fmadd_ps(a6, b6, sum6);
358
359 let a7 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 7));
360 let b7 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 7));
361 sum7 = _mm256_fmadd_ps(a7, b7, sum7);
362 }
363
364 let sum01 = _mm256_add_ps(sum0, sum1);
366 let sum23 = _mm256_add_ps(sum2, sum3);
367 let sum45 = _mm256_add_ps(sum4, sum5);
368 let sum67 = _mm256_add_ps(sum6, sum7);
369 let sum0123 = _mm256_add_ps(sum01, sum23);
370 let sum4567 = _mm256_add_ps(sum45, sum67);
371 let sum_vec = _mm256_add_ps(sum0123, sum4567);
372
373 let sum = horizontal_sum_avx2(sum_vec);
374
375 let main_processed = chunks * CHUNK_SIZE;
377 let remaining = n - main_processed;
378 let remaining_chunks = remaining / SIMD_WIDTH;
379
380 let mut remainder_sum = _mm256_setzero_ps();
381 for i in 0..remaining_chunks {
382 let offset = main_processed + i * SIMD_WIDTH;
383 let a_vec = _mm256_loadu_ps(a.as_ptr().add(offset));
384 let b_vec = _mm256_loadu_ps(b.as_ptr().add(offset));
385 remainder_sum = _mm256_fmadd_ps(a_vec, b_vec, remainder_sum);
386 }
387
388 let mut total = sum + horizontal_sum_avx2(remainder_sum);
389
390 let scalar_start = main_processed + remaining_chunks * SIMD_WIDTH;
392 for i in scalar_start..n {
393 total += a[i] * b[i];
394 }
395
396 total
397}
398
399#[cfg(target_arch = "x86_64")]
415#[target_feature(enable = "avx2", enable = "fma")]
416unsafe fn dot_product_384_avx2(a: &[f32], b: &[f32]) -> f32 {
417 const SIMD_WIDTH: usize = 8;
418 const UNROLL: usize = 8;
420 const CHUNK_SIZE: usize = SIMD_WIDTH * UNROLL; const CHUNKS: usize = 384 / CHUNK_SIZE; const TAIL_ITERS: usize = (384 - CHUNKS * CHUNK_SIZE) / SIMD_WIDTH; debug_assert_eq!(a.len(), 384);
425 debug_assert_eq!(b.len(), 384);
426 debug_assert_eq!(CHUNKS * CHUNK_SIZE + TAIL_ITERS * SIMD_WIDTH, 384);
427
428 let mut sum0 = _mm256_setzero_ps();
430 let mut sum1 = _mm256_setzero_ps();
431 let mut sum2 = _mm256_setzero_ps();
432 let mut sum3 = _mm256_setzero_ps();
433 let mut sum4 = _mm256_setzero_ps();
434 let mut sum5 = _mm256_setzero_ps();
435 let mut sum6 = _mm256_setzero_ps();
436 let mut sum7 = _mm256_setzero_ps();
437
438 for i in 0..CHUNKS {
440 let base = i * CHUNK_SIZE;
441
442 let a0 = _mm256_loadu_ps(a.as_ptr().add(base));
443 let b0 = _mm256_loadu_ps(b.as_ptr().add(base));
444 sum0 = _mm256_fmadd_ps(a0, b0, sum0);
445
446 let a1 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH));
447 let b1 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH));
448 sum1 = _mm256_fmadd_ps(a1, b1, sum1);
449
450 let a2 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 2));
451 let b2 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 2));
452 sum2 = _mm256_fmadd_ps(a2, b2, sum2);
453
454 let a3 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 3));
455 let b3 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 3));
456 sum3 = _mm256_fmadd_ps(a3, b3, sum3);
457
458 let a4 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 4));
459 let b4 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 4));
460 sum4 = _mm256_fmadd_ps(a4, b4, sum4);
461
462 let a5 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 5));
463 let b5 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 5));
464 sum5 = _mm256_fmadd_ps(a5, b5, sum5);
465
466 let a6 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 6));
467 let b6 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 6));
468 sum6 = _mm256_fmadd_ps(a6, b6, sum6);
469
470 let a7 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 7));
471 let b7 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 7));
472 sum7 = _mm256_fmadd_ps(a7, b7, sum7);
473 }
474
475 let sum01 = _mm256_add_ps(sum0, sum1);
477 let sum23 = _mm256_add_ps(sum2, sum3);
478 let sum45 = _mm256_add_ps(sum4, sum5);
479 let sum67 = _mm256_add_ps(sum6, sum7);
480 let sum0123 = _mm256_add_ps(sum01, sum23);
481 let sum4567 = _mm256_add_ps(sum45, sum67);
482 let sum_vec = _mm256_add_ps(sum0123, sum4567);
483
484 horizontal_sum_avx2(sum_vec)
485}
486
487#[cfg(target_arch = "x86_64")]
493#[target_feature(enable = "avx2")]
494#[inline]
495pub(crate) unsafe fn horizontal_sum_avx2(v: __m256) -> f32 {
496 let high = _mm256_extractf128_ps(v, 1);
498 let low = _mm256_castps256_ps128(v);
499 let sum128 = _mm_add_ps(high, low);
500
501 let shuf = _mm_movehdup_ps(sum128); let sums = _mm_add_ps(sum128, shuf); let shuf2 = _mm_movehl_ps(sums, sums); let sums2 = _mm_add_ss(sums, shuf2); _mm_cvtss_f32(sums2)
508}
509
510#[cfg(target_arch = "x86_64")]
516#[inline]
517fn dot_product_batch4_avx2_kernel(
518 q: &[f32],
519 c0: &[f32],
520 c1: &[f32],
521 c2: &[f32],
522 c3: &[f32],
523) -> [f32; 4] {
524 if q.len() == 384 {
525 unsafe { dot_product_384_batch4_avx2(q, c0, c1, c2, c3) }
526 } else {
527 unsafe { dot_product_batch4_avx2(q, c0, c1, c2, c3) }
528 }
529}
530
531#[cfg(target_arch = "x86_64")]
542#[target_feature(enable = "avx2", enable = "fma")]
543unsafe fn dot_product_384_batch4_avx2(
544 q: &[f32],
545 c0: &[f32],
546 c1: &[f32],
547 c2: &[f32],
548 c3: &[f32],
549) -> [f32; 4] {
550 const W: usize = 8; const CHUNK: usize = W * 2; const CHUNKS: usize = 384 / CHUNK; debug_assert_eq!(q.len(), 384);
555
556 let mut acc00 = _mm256_setzero_ps();
557 let mut acc01 = _mm256_setzero_ps();
558 let mut acc10 = _mm256_setzero_ps();
559 let mut acc11 = _mm256_setzero_ps();
560 let mut acc20 = _mm256_setzero_ps();
561 let mut acc21 = _mm256_setzero_ps();
562 let mut acc30 = _mm256_setzero_ps();
563 let mut acc31 = _mm256_setzero_ps();
564
565 for i in 0..CHUNKS {
566 let base = i * CHUNK;
567 let q0 = _mm256_loadu_ps(q.as_ptr().add(base));
568 let q1 = _mm256_loadu_ps(q.as_ptr().add(base + W));
569
570 acc00 = _mm256_fmadd_ps(q0, _mm256_loadu_ps(c0.as_ptr().add(base)), acc00);
571 acc01 = _mm256_fmadd_ps(q1, _mm256_loadu_ps(c0.as_ptr().add(base + W)), acc01);
572 acc10 = _mm256_fmadd_ps(q0, _mm256_loadu_ps(c1.as_ptr().add(base)), acc10);
573 acc11 = _mm256_fmadd_ps(q1, _mm256_loadu_ps(c1.as_ptr().add(base + W)), acc11);
574 acc20 = _mm256_fmadd_ps(q0, _mm256_loadu_ps(c2.as_ptr().add(base)), acc20);
575 acc21 = _mm256_fmadd_ps(q1, _mm256_loadu_ps(c2.as_ptr().add(base + W)), acc21);
576 acc30 = _mm256_fmadd_ps(q0, _mm256_loadu_ps(c3.as_ptr().add(base)), acc30);
577 acc31 = _mm256_fmadd_ps(q1, _mm256_loadu_ps(c3.as_ptr().add(base + W)), acc31);
578 }
579
580 [
581 horizontal_sum_avx2(_mm256_add_ps(acc00, acc01)),
582 horizontal_sum_avx2(_mm256_add_ps(acc10, acc11)),
583 horizontal_sum_avx2(_mm256_add_ps(acc20, acc21)),
584 horizontal_sum_avx2(_mm256_add_ps(acc30, acc31)),
585 ]
586}
587
588#[cfg(target_arch = "x86_64")]
603#[target_feature(enable = "avx2", enable = "fma")]
604unsafe fn dot_product_batch4_avx2(
605 q: &[f32],
606 c0: &[f32],
607 c1: &[f32],
608 c2: &[f32],
609 c3: &[f32],
610) -> [f32; 4] {
611 const W: usize = 8;
612 const CHUNK: usize = W * 2; let n = q.len();
615 let chunks = n / CHUNK;
616
617 let mut acc00 = _mm256_setzero_ps();
618 let mut acc01 = _mm256_setzero_ps();
619 let mut acc10 = _mm256_setzero_ps();
620 let mut acc11 = _mm256_setzero_ps();
621 let mut acc20 = _mm256_setzero_ps();
622 let mut acc21 = _mm256_setzero_ps();
623 let mut acc30 = _mm256_setzero_ps();
624 let mut acc31 = _mm256_setzero_ps();
625
626 for i in 0..chunks {
627 let base = i * CHUNK;
628 let q0 = _mm256_loadu_ps(q.as_ptr().add(base));
629 let q1 = _mm256_loadu_ps(q.as_ptr().add(base + W));
630
631 acc00 = _mm256_fmadd_ps(q0, _mm256_loadu_ps(c0.as_ptr().add(base)), acc00);
632 acc01 = _mm256_fmadd_ps(q1, _mm256_loadu_ps(c0.as_ptr().add(base + W)), acc01);
633 acc10 = _mm256_fmadd_ps(q0, _mm256_loadu_ps(c1.as_ptr().add(base)), acc10);
634 acc11 = _mm256_fmadd_ps(q1, _mm256_loadu_ps(c1.as_ptr().add(base + W)), acc11);
635 acc20 = _mm256_fmadd_ps(q0, _mm256_loadu_ps(c2.as_ptr().add(base)), acc20);
636 acc21 = _mm256_fmadd_ps(q1, _mm256_loadu_ps(c2.as_ptr().add(base + W)), acc21);
637 acc30 = _mm256_fmadd_ps(q0, _mm256_loadu_ps(c3.as_ptr().add(base)), acc30);
638 acc31 = _mm256_fmadd_ps(q1, _mm256_loadu_ps(c3.as_ptr().add(base + W)), acc31);
639 }
640
641 let mut out = [
642 horizontal_sum_avx2(_mm256_add_ps(acc00, acc01)),
643 horizontal_sum_avx2(_mm256_add_ps(acc10, acc11)),
644 horizontal_sum_avx2(_mm256_add_ps(acc20, acc21)),
645 horizontal_sum_avx2(_mm256_add_ps(acc30, acc31)),
646 ];
647
648 let scalar_start = chunks * CHUNK;
649 for i in scalar_start..n {
650 let qi = q[i];
651 out[0] += qi * c0[i];
652 out[1] += qi * c1[i];
653 out[2] += qi * c2[i];
654 out[3] += qi * c3[i];
655 }
656
657 out
658}
659
660#[cfg(target_arch = "aarch64")]
677#[inline]
678unsafe fn dot_product_neon_unrolled(a: &[f32], b: &[f32]) -> f32 {
679 const SIMD_WIDTH: usize = 4;
680 const UNROLL: usize = 4;
681 const CHUNK_SIZE: usize = SIMD_WIDTH * UNROLL; let n = a.len();
683 debug_assert_eq!(n, b.len());
684 let chunks = n / CHUNK_SIZE;
685
686 let mut sum0 = vdupq_n_f32(0.0);
688 let mut sum1 = vdupq_n_f32(0.0);
689 let mut sum2 = vdupq_n_f32(0.0);
690 let mut sum3 = vdupq_n_f32(0.0);
691
692 for i in 0..chunks {
693 let base = i * CHUNK_SIZE;
694
695 let a0 = vld1q_f32(a.as_ptr().add(base));
696 let b0 = vld1q_f32(b.as_ptr().add(base));
697 sum0 = vfmaq_f32(sum0, a0, b0);
698
699 let a1 = vld1q_f32(a.as_ptr().add(base + SIMD_WIDTH));
700 let b1 = vld1q_f32(b.as_ptr().add(base + SIMD_WIDTH));
701 sum1 = vfmaq_f32(sum1, a1, b1);
702
703 let a2 = vld1q_f32(a.as_ptr().add(base + SIMD_WIDTH * 2));
704 let b2 = vld1q_f32(b.as_ptr().add(base + SIMD_WIDTH * 2));
705 sum2 = vfmaq_f32(sum2, a2, b2);
706
707 let a3 = vld1q_f32(a.as_ptr().add(base + SIMD_WIDTH * 3));
708 let b3 = vld1q_f32(b.as_ptr().add(base + SIMD_WIDTH * 3));
709 sum3 = vfmaq_f32(sum3, a3, b3);
710 }
711
712 let sum01 = vaddq_f32(sum0, sum1);
714 let sum23 = vaddq_f32(sum2, sum3);
715 let sum_vec = vaddq_f32(sum01, sum23);
716
717 let mut sum = horizontal_sum_neon(sum_vec);
718
719 let main_processed = chunks * CHUNK_SIZE;
721 let remaining = n - main_processed;
722 let remaining_chunks = remaining / SIMD_WIDTH;
723
724 let mut remainder_sum = vdupq_n_f32(0.0);
725 for i in 0..remaining_chunks {
726 let offset = main_processed + i * SIMD_WIDTH;
727 let a_vec = vld1q_f32(a.as_ptr().add(offset));
728 let b_vec = vld1q_f32(b.as_ptr().add(offset));
729 remainder_sum = vfmaq_f32(remainder_sum, a_vec, b_vec);
730 }
731
732 sum += horizontal_sum_neon(remainder_sum);
733
734 let scalar_start = main_processed + remaining_chunks * SIMD_WIDTH;
736 for i in scalar_start..n {
737 sum += a[i] * b[i];
738 }
739
740 sum
741}
742
743#[cfg(target_arch = "aarch64")]
749#[inline]
750pub(crate) unsafe fn horizontal_sum_neon(v: float32x4_t) -> f32 {
751 vaddvq_f32(v)
752}
753
754#[cfg(target_arch = "aarch64")]
760#[inline]
761fn dot_product_batch4_neon_kernel(
762 q: &[f32],
763 c0: &[f32],
764 c1: &[f32],
765 c2: &[f32],
766 c3: &[f32],
767) -> [f32; 4] {
768 unsafe { dot_product_batch4_neon(q, c0, c1, c2, c3) }
770}
771
772#[cfg(target_arch = "aarch64")]
788#[inline]
789unsafe fn dot_product_batch4_neon(
790 q: &[f32],
791 c0: &[f32],
792 c1: &[f32],
793 c2: &[f32],
794 c3: &[f32],
795) -> [f32; 4] {
796 const W: usize = 4; const CHUNK: usize = W * 2; let n = q.len();
800 let chunks = n / CHUNK;
801
802 let mut acc00 = vdupq_n_f32(0.0);
803 let mut acc01 = vdupq_n_f32(0.0);
804 let mut acc10 = vdupq_n_f32(0.0);
805 let mut acc11 = vdupq_n_f32(0.0);
806 let mut acc20 = vdupq_n_f32(0.0);
807 let mut acc21 = vdupq_n_f32(0.0);
808 let mut acc30 = vdupq_n_f32(0.0);
809 let mut acc31 = vdupq_n_f32(0.0);
810
811 for i in 0..chunks {
812 let base = i * CHUNK;
813 let q0 = vld1q_f32(q.as_ptr().add(base));
814 let q1 = vld1q_f32(q.as_ptr().add(base + W));
815
816 acc00 = vfmaq_f32(acc00, q0, vld1q_f32(c0.as_ptr().add(base)));
817 acc01 = vfmaq_f32(acc01, q1, vld1q_f32(c0.as_ptr().add(base + W)));
818 acc10 = vfmaq_f32(acc10, q0, vld1q_f32(c1.as_ptr().add(base)));
819 acc11 = vfmaq_f32(acc11, q1, vld1q_f32(c1.as_ptr().add(base + W)));
820 acc20 = vfmaq_f32(acc20, q0, vld1q_f32(c2.as_ptr().add(base)));
821 acc21 = vfmaq_f32(acc21, q1, vld1q_f32(c2.as_ptr().add(base + W)));
822 acc30 = vfmaq_f32(acc30, q0, vld1q_f32(c3.as_ptr().add(base)));
823 acc31 = vfmaq_f32(acc31, q1, vld1q_f32(c3.as_ptr().add(base + W)));
824 }
825
826 let mut out = [
827 vaddvq_f32(vaddq_f32(acc00, acc01)),
828 vaddvq_f32(vaddq_f32(acc10, acc11)),
829 vaddvq_f32(vaddq_f32(acc20, acc21)),
830 vaddvq_f32(vaddq_f32(acc30, acc31)),
831 ];
832
833 let scalar_start = chunks * CHUNK;
834 for i in scalar_start..n {
835 let qi = q[i];
836 out[0] += qi * c0[i];
837 out[1] += qi * c1[i];
838 out[2] += qi * c2[i];
839 out[3] += qi * c3[i];
840 }
841
842 out
843}
844
845#[inline]
847fn same_query_batch4(chunk: &[(&[f32], &[f32])]) -> bool {
848 debug_assert_eq!(chunk.len(), 4);
849 let q_ptr = chunk[0].0.as_ptr();
850 let q_len = chunk[0].0.len();
851 q_len == chunk[0].1.len()
852 && chunk
853 .iter()
854 .all(|(q, c)| q.as_ptr() == q_ptr && q.len() == q_len && c.len() == q_len)
855}
856
857pub fn batch_dot_product(pairs: &[(&[f32], &[f32])]) -> Vec<f32> {
863 let pair_kernel = resolved_dot_product_kernel();
864 let batch4_kernel = resolved_dot_product_batch4_kernel();
865 let mut out = Vec::with_capacity(pairs.len());
866
867 let mut chunks = pairs.chunks_exact(4);
868 for chunk in &mut chunks {
869 if same_query_batch4(chunk) {
870 let q = chunk[0].0;
871 let dots = batch4_kernel(q, chunk[0].1, chunk[1].1, chunk[2].1, chunk[3].1);
872 out.extend_from_slice(&dots);
873 } else {
874 for &(a, b) in chunk {
875 out.push(if a.len() == b.len() {
876 pair_kernel(a, b)
877 } else {
878 0.0
879 });
880 }
881 }
882 }
883 for &(a, b) in chunks.remainder() {
884 out.push(if a.len() == b.len() {
885 pair_kernel(a, b)
886 } else {
887 0.0
888 });
889 }
890 out
891}