1#[cfg(target_arch = "x86_64")]
8use std::arch::x86_64::*;
9
10#[cfg(target_arch = "aarch64")]
11use std::arch::aarch64::*;
12
13#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
14use std::arch::wasm32::*;
15
16use std::sync::OnceLock;
17
18use super::simd_config;
19
20pub type DotKernel = fn(&[f32], &[f32]) -> f32;
22
23static DOT_PRODUCT_KERNEL: OnceLock<DotKernel> = OnceLock::new();
24
25#[inline]
29pub fn resolved_dot_product_kernel() -> DotKernel {
30 *DOT_PRODUCT_KERNEL.get_or_init(resolve_dot_product_kernel)
31}
32
33fn resolve_dot_product_kernel() -> DotKernel {
34 let config = simd_config();
35
36 #[cfg(target_arch = "x86_64")]
37 {
38 if config.avx512f_enabled {
39 return dot_product_avx512_kernel;
40 }
41 if config.avx2_enabled && config.fma_enabled {
42 return dot_product_avx2_kernel;
43 }
44 }
45
46 #[cfg(target_arch = "aarch64")]
47 {
48 if config.neon_enabled {
49 return dot_product_neon_kernel;
50 }
51 }
52
53 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
54 {
55 if config.simd128_enabled() {
56 return dot_product_simd128_kernel;
57 }
58 }
59
60 dot_product_scalar
61}
62
63pub type DotBatch4Kernel = fn(&[f32], &[f32], &[f32], &[f32], &[f32]) -> [f32; 4];
72
73static DOT_PRODUCT_BATCH4_KERNEL: OnceLock<DotBatch4Kernel> = OnceLock::new();
74
75#[inline]
79pub fn resolved_dot_product_batch4_kernel() -> DotBatch4Kernel {
80 *DOT_PRODUCT_BATCH4_KERNEL.get_or_init(resolve_dot_product_batch4_kernel)
81}
82
83#[inline]
87pub fn dot_product_batch4(
88 query: &[f32],
89 c0: &[f32],
90 c1: &[f32],
91 c2: &[f32],
92 c3: &[f32],
93) -> [f32; 4] {
94 if query.len() != c0.len()
95 || query.len() != c1.len()
96 || query.len() != c2.len()
97 || query.len() != c3.len()
98 {
99 debug_assert!(
100 false,
101 "dot_product_batch4: dimension mismatch (query={}, c0={}, c1={}, c2={}, c3={})",
102 query.len(),
103 c0.len(),
104 c1.len(),
105 c2.len(),
106 c3.len()
107 );
108 return [0.0; 4];
109 }
110 resolved_dot_product_batch4_kernel()(query, c0, c1, c2, c3)
111}
112
113fn resolve_dot_product_batch4_kernel() -> DotBatch4Kernel {
114 let config = simd_config();
115
116 #[cfg(target_arch = "x86_64")]
117 {
118 if config.avx2_enabled && config.fma_enabled {
119 return dot_product_batch4_avx2_kernel;
120 }
121 }
122
123 #[cfg(target_arch = "aarch64")]
124 {
125 if config.neon_enabled {
126 return dot_product_batch4_neon_kernel;
127 }
128 }
129
130 dot_product_batch4_scalar
131}
132
133fn dot_product_batch4_scalar(
135 q: &[f32],
136 c0: &[f32],
137 c1: &[f32],
138 c2: &[f32],
139 c3: &[f32],
140) -> [f32; 4] {
141 let mut out = [0.0f32; 4];
142 for i in 0..q.len() {
143 let qi = q[i];
144 out[0] += qi * c0[i];
145 out[1] += qi * c1[i];
146 out[2] += qi * c2[i];
147 out[3] += qi * c3[i];
148 }
149 out
150}
151
152#[cfg(target_arch = "x86_64")]
153#[inline]
154fn dot_product_avx512_kernel(a: &[f32], b: &[f32]) -> f32 {
155 unsafe { dot_product_avx512_unrolled(a, b) }
157}
158
159#[cfg(target_arch = "x86_64")]
160#[inline]
161fn dot_product_avx2_kernel(a: &[f32], b: &[f32]) -> f32 {
162 if a.len() == 384 {
164 unsafe { dot_product_384_avx2(a, b) }
165 } else {
166 unsafe { dot_product_avx2_8acc(a, b) }
167 }
168}
169
170#[cfg(target_arch = "aarch64")]
171#[inline]
172fn dot_product_neon_kernel(a: &[f32], b: &[f32]) -> f32 {
173 unsafe { dot_product_neon_unrolled(a, b) }
175}
176
177#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
178#[inline]
179fn dot_product_simd128_kernel(a: &[f32], b: &[f32]) -> f32 {
180 unsafe { dot_product_simd128_unrolled(a, b) }
184}
185
186#[inline]
190pub fn dot_product(a: &[f32], b: &[f32]) -> f32 {
191 if a.len() != b.len() {
193 return 0.0;
194 }
195 debug_assert_eq!(a.len(), b.len());
196 resolved_dot_product_kernel()(a, b)
197}
198
199#[inline]
201pub(crate) fn dot_product_scalar(a: &[f32], b: &[f32]) -> f32 {
202 a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
203}
204
205#[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;
255 let remaining = n - main_processed;
256 let remaining_chunks = remaining / SIMD_WIDTH;
257
258 let mut remainder_sum = _mm512_setzero_ps();
259 for i in 0..remaining_chunks {
260 let offset = main_processed + i * SIMD_WIDTH;
261 let a_vec = _mm512_loadu_ps(a.as_ptr().add(offset));
262 let b_vec = _mm512_loadu_ps(b.as_ptr().add(offset));
263 remainder_sum = _mm512_fmadd_ps(a_vec, b_vec, remainder_sum);
264 }
265
266 let mut total = main_sum + horizontal_sum_avx512(remainder_sum);
267
268 let scalar_start = main_processed + remaining_chunks * SIMD_WIDTH;
269 for i in scalar_start..n {
270 total += a[i] * b[i];
271 }
272
273 total
274}
275
276#[cfg(target_arch = "x86_64")]
282#[target_feature(enable = "avx512f")]
283#[inline]
284pub(crate) unsafe fn horizontal_sum_avx512(v: __m512) -> f32 {
285 _mm512_reduce_add_ps(v)
286}
287
288#[cfg(target_arch = "x86_64")]
294#[target_feature(enable = "avx2", enable = "fma")]
295unsafe fn dot_product_avx2_8acc(a: &[f32], b: &[f32]) -> f32 {
296 const SIMD_WIDTH: usize = 8;
297 const UNROLL: usize = 8;
298 const CHUNK_SIZE: usize = SIMD_WIDTH * UNROLL; let n = a.len();
300 debug_assert_eq!(n, b.len());
301 let chunks = n / CHUNK_SIZE;
302
303 let mut sum0 = _mm256_setzero_ps();
305 let mut sum1 = _mm256_setzero_ps();
306 let mut sum2 = _mm256_setzero_ps();
307 let mut sum3 = _mm256_setzero_ps();
308 let mut sum4 = _mm256_setzero_ps();
309 let mut sum5 = _mm256_setzero_ps();
310 let mut sum6 = _mm256_setzero_ps();
311 let mut sum7 = _mm256_setzero_ps();
312
313 for i in 0..chunks {
314 let base = i * CHUNK_SIZE;
315
316 let a0 = _mm256_loadu_ps(a.as_ptr().add(base));
317 let b0 = _mm256_loadu_ps(b.as_ptr().add(base));
318 sum0 = _mm256_fmadd_ps(a0, b0, sum0);
319
320 let a1 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH));
321 let b1 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH));
322 sum1 = _mm256_fmadd_ps(a1, b1, sum1);
323
324 let a2 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 2));
325 let b2 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 2));
326 sum2 = _mm256_fmadd_ps(a2, b2, sum2);
327
328 let a3 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 3));
329 let b3 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 3));
330 sum3 = _mm256_fmadd_ps(a3, b3, sum3);
331
332 let a4 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 4));
333 let b4 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 4));
334 sum4 = _mm256_fmadd_ps(a4, b4, sum4);
335
336 let a5 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 5));
337 let b5 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 5));
338 sum5 = _mm256_fmadd_ps(a5, b5, sum5);
339
340 let a6 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 6));
341 let b6 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 6));
342 sum6 = _mm256_fmadd_ps(a6, b6, sum6);
343
344 let a7 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 7));
345 let b7 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 7));
346 sum7 = _mm256_fmadd_ps(a7, b7, sum7);
347 }
348
349 let sum01 = _mm256_add_ps(sum0, sum1);
351 let sum23 = _mm256_add_ps(sum2, sum3);
352 let sum45 = _mm256_add_ps(sum4, sum5);
353 let sum67 = _mm256_add_ps(sum6, sum7);
354 let sum0123 = _mm256_add_ps(sum01, sum23);
355 let sum4567 = _mm256_add_ps(sum45, sum67);
356 let sum_vec = _mm256_add_ps(sum0123, sum4567);
357
358 let sum = horizontal_sum_avx2(sum_vec);
359
360 let main_processed = chunks * CHUNK_SIZE;
361 let remaining = n - main_processed;
362 let remaining_chunks = remaining / SIMD_WIDTH;
363
364 let mut remainder_sum = _mm256_setzero_ps();
365 for i in 0..remaining_chunks {
366 let offset = main_processed + i * SIMD_WIDTH;
367 let a_vec = _mm256_loadu_ps(a.as_ptr().add(offset));
368 let b_vec = _mm256_loadu_ps(b.as_ptr().add(offset));
369 remainder_sum = _mm256_fmadd_ps(a_vec, b_vec, remainder_sum);
370 }
371
372 let mut total = sum + horizontal_sum_avx2(remainder_sum);
373
374 let scalar_start = main_processed + remaining_chunks * SIMD_WIDTH;
375 for i in scalar_start..n {
376 total += a[i] * b[i];
377 }
378
379 total
380}
381
382#[cfg(target_arch = "x86_64")]
388#[target_feature(enable = "avx2", enable = "fma")]
389unsafe fn dot_product_384_avx2(a: &[f32], b: &[f32]) -> f32 {
390 const SIMD_WIDTH: usize = 8;
391 const UNROLL: usize = 8;
393 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);
398 debug_assert_eq!(b.len(), 384);
399 debug_assert_eq!(CHUNKS * CHUNK_SIZE + TAIL_ITERS * SIMD_WIDTH, 384);
400
401 let mut sum0 = _mm256_setzero_ps();
403 let mut sum1 = _mm256_setzero_ps();
404 let mut sum2 = _mm256_setzero_ps();
405 let mut sum3 = _mm256_setzero_ps();
406 let mut sum4 = _mm256_setzero_ps();
407 let mut sum5 = _mm256_setzero_ps();
408 let mut sum6 = _mm256_setzero_ps();
409 let mut sum7 = _mm256_setzero_ps();
410
411 for i in 0..CHUNKS {
413 let base = i * CHUNK_SIZE;
414
415 let a0 = _mm256_loadu_ps(a.as_ptr().add(base));
416 let b0 = _mm256_loadu_ps(b.as_ptr().add(base));
417 sum0 = _mm256_fmadd_ps(a0, b0, sum0);
418
419 let a1 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH));
420 let b1 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH));
421 sum1 = _mm256_fmadd_ps(a1, b1, sum1);
422
423 let a2 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 2));
424 let b2 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 2));
425 sum2 = _mm256_fmadd_ps(a2, b2, sum2);
426
427 let a3 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 3));
428 let b3 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 3));
429 sum3 = _mm256_fmadd_ps(a3, b3, sum3);
430
431 let a4 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 4));
432 let b4 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 4));
433 sum4 = _mm256_fmadd_ps(a4, b4, sum4);
434
435 let a5 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 5));
436 let b5 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 5));
437 sum5 = _mm256_fmadd_ps(a5, b5, sum5);
438
439 let a6 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 6));
440 let b6 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 6));
441 sum6 = _mm256_fmadd_ps(a6, b6, sum6);
442
443 let a7 = _mm256_loadu_ps(a.as_ptr().add(base + SIMD_WIDTH * 7));
444 let b7 = _mm256_loadu_ps(b.as_ptr().add(base + SIMD_WIDTH * 7));
445 sum7 = _mm256_fmadd_ps(a7, b7, sum7);
446 }
447
448 let sum01 = _mm256_add_ps(sum0, sum1);
450 let sum23 = _mm256_add_ps(sum2, sum3);
451 let sum45 = _mm256_add_ps(sum4, sum5);
452 let sum67 = _mm256_add_ps(sum6, sum7);
453 let sum0123 = _mm256_add_ps(sum01, sum23);
454 let sum4567 = _mm256_add_ps(sum45, sum67);
455 let sum_vec = _mm256_add_ps(sum0123, sum4567);
456
457 horizontal_sum_avx2(sum_vec)
458}
459
460#[cfg(target_arch = "x86_64")]
466#[target_feature(enable = "avx2")]
467#[inline]
468pub(crate) unsafe fn horizontal_sum_avx2(v: __m256) -> f32 {
469 let high = _mm256_extractf128_ps(v, 1);
471 let low = _mm256_castps256_ps128(v);
472 let sum128 = _mm_add_ps(high, low);
473
474 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)
481}
482
483#[cfg(target_arch = "x86_64")]
489#[inline]
490fn dot_product_batch4_avx2_kernel(
491 q: &[f32],
492 c0: &[f32],
493 c1: &[f32],
494 c2: &[f32],
495 c3: &[f32],
496) -> [f32; 4] {
497 if q.len() == 384 {
498 unsafe { dot_product_384_batch4_avx2(q, c0, c1, c2, c3) }
499 } else {
500 unsafe { dot_product_batch4_avx2(q, c0, c1, c2, c3) }
501 }
502}
503
504#[cfg(target_arch = "x86_64")]
510#[target_feature(enable = "avx2", enable = "fma")]
511unsafe fn dot_product_384_batch4_avx2(
512 q: &[f32],
513 c0: &[f32],
514 c1: &[f32],
515 c2: &[f32],
516 c3: &[f32],
517) -> [f32; 4] {
518 const W: usize = 8; const CHUNK: usize = W * 2; const CHUNKS: usize = 384 / CHUNK; debug_assert_eq!(q.len(), 384);
523
524 let mut acc00 = _mm256_setzero_ps();
525 let mut acc01 = _mm256_setzero_ps();
526 let mut acc10 = _mm256_setzero_ps();
527 let mut acc11 = _mm256_setzero_ps();
528 let mut acc20 = _mm256_setzero_ps();
529 let mut acc21 = _mm256_setzero_ps();
530 let mut acc30 = _mm256_setzero_ps();
531 let mut acc31 = _mm256_setzero_ps();
532
533 for i in 0..CHUNKS {
534 let base = i * CHUNK;
535 let q0 = _mm256_loadu_ps(q.as_ptr().add(base));
536 let q1 = _mm256_loadu_ps(q.as_ptr().add(base + W));
537
538 acc00 = _mm256_fmadd_ps(q0, _mm256_loadu_ps(c0.as_ptr().add(base)), acc00);
539 acc01 = _mm256_fmadd_ps(q1, _mm256_loadu_ps(c0.as_ptr().add(base + W)), acc01);
540 acc10 = _mm256_fmadd_ps(q0, _mm256_loadu_ps(c1.as_ptr().add(base)), acc10);
541 acc11 = _mm256_fmadd_ps(q1, _mm256_loadu_ps(c1.as_ptr().add(base + W)), acc11);
542 acc20 = _mm256_fmadd_ps(q0, _mm256_loadu_ps(c2.as_ptr().add(base)), acc20);
543 acc21 = _mm256_fmadd_ps(q1, _mm256_loadu_ps(c2.as_ptr().add(base + W)), acc21);
544 acc30 = _mm256_fmadd_ps(q0, _mm256_loadu_ps(c3.as_ptr().add(base)), acc30);
545 acc31 = _mm256_fmadd_ps(q1, _mm256_loadu_ps(c3.as_ptr().add(base + W)), acc31);
546 }
547
548 [
549 horizontal_sum_avx2(_mm256_add_ps(acc00, acc01)),
550 horizontal_sum_avx2(_mm256_add_ps(acc10, acc11)),
551 horizontal_sum_avx2(_mm256_add_ps(acc20, acc21)),
552 horizontal_sum_avx2(_mm256_add_ps(acc30, acc31)),
553 ]
554}
555
556#[cfg(target_arch = "x86_64")]
562#[target_feature(enable = "avx2", enable = "fma")]
563unsafe fn dot_product_batch4_avx2(
564 q: &[f32],
565 c0: &[f32],
566 c1: &[f32],
567 c2: &[f32],
568 c3: &[f32],
569) -> [f32; 4] {
570 const W: usize = 8;
571 const CHUNK: usize = W * 2; let n = q.len();
574 let chunks = n / CHUNK;
575
576 let mut acc00 = _mm256_setzero_ps();
577 let mut acc01 = _mm256_setzero_ps();
578 let mut acc10 = _mm256_setzero_ps();
579 let mut acc11 = _mm256_setzero_ps();
580 let mut acc20 = _mm256_setzero_ps();
581 let mut acc21 = _mm256_setzero_ps();
582 let mut acc30 = _mm256_setzero_ps();
583 let mut acc31 = _mm256_setzero_ps();
584
585 for i in 0..chunks {
586 let base = i * CHUNK;
587 let q0 = _mm256_loadu_ps(q.as_ptr().add(base));
588 let q1 = _mm256_loadu_ps(q.as_ptr().add(base + W));
589
590 acc00 = _mm256_fmadd_ps(q0, _mm256_loadu_ps(c0.as_ptr().add(base)), acc00);
591 acc01 = _mm256_fmadd_ps(q1, _mm256_loadu_ps(c0.as_ptr().add(base + W)), acc01);
592 acc10 = _mm256_fmadd_ps(q0, _mm256_loadu_ps(c1.as_ptr().add(base)), acc10);
593 acc11 = _mm256_fmadd_ps(q1, _mm256_loadu_ps(c1.as_ptr().add(base + W)), acc11);
594 acc20 = _mm256_fmadd_ps(q0, _mm256_loadu_ps(c2.as_ptr().add(base)), acc20);
595 acc21 = _mm256_fmadd_ps(q1, _mm256_loadu_ps(c2.as_ptr().add(base + W)), acc21);
596 acc30 = _mm256_fmadd_ps(q0, _mm256_loadu_ps(c3.as_ptr().add(base)), acc30);
597 acc31 = _mm256_fmadd_ps(q1, _mm256_loadu_ps(c3.as_ptr().add(base + W)), acc31);
598 }
599
600 let mut out = [
601 horizontal_sum_avx2(_mm256_add_ps(acc00, acc01)),
602 horizontal_sum_avx2(_mm256_add_ps(acc10, acc11)),
603 horizontal_sum_avx2(_mm256_add_ps(acc20, acc21)),
604 horizontal_sum_avx2(_mm256_add_ps(acc30, acc31)),
605 ];
606
607 let scalar_start = chunks * CHUNK;
608 for i in scalar_start..n {
609 let qi = q[i];
610 out[0] += qi * c0[i];
611 out[1] += qi * c1[i];
612 out[2] += qi * c2[i];
613 out[3] += qi * c3[i];
614 }
615
616 out
617}
618
619#[cfg(target_arch = "aarch64")]
625#[inline]
626unsafe fn dot_product_neon_unrolled(a: &[f32], b: &[f32]) -> f32 {
627 const SIMD_WIDTH: usize = 4;
628 const UNROLL: usize = 4;
629 const CHUNK_SIZE: usize = SIMD_WIDTH * UNROLL; let n = a.len();
631 debug_assert_eq!(n, b.len());
632 let chunks = n / CHUNK_SIZE;
633
634 let mut sum0 = vdupq_n_f32(0.0);
636 let mut sum1 = vdupq_n_f32(0.0);
637 let mut sum2 = vdupq_n_f32(0.0);
638 let mut sum3 = vdupq_n_f32(0.0);
639
640 for i in 0..chunks {
641 let base = i * CHUNK_SIZE;
642
643 let a0 = vld1q_f32(a.as_ptr().add(base));
644 let b0 = vld1q_f32(b.as_ptr().add(base));
645 sum0 = vfmaq_f32(sum0, a0, b0);
646
647 let a1 = vld1q_f32(a.as_ptr().add(base + SIMD_WIDTH));
648 let b1 = vld1q_f32(b.as_ptr().add(base + SIMD_WIDTH));
649 sum1 = vfmaq_f32(sum1, a1, b1);
650
651 let a2 = vld1q_f32(a.as_ptr().add(base + SIMD_WIDTH * 2));
652 let b2 = vld1q_f32(b.as_ptr().add(base + SIMD_WIDTH * 2));
653 sum2 = vfmaq_f32(sum2, a2, b2);
654
655 let a3 = vld1q_f32(a.as_ptr().add(base + SIMD_WIDTH * 3));
656 let b3 = vld1q_f32(b.as_ptr().add(base + SIMD_WIDTH * 3));
657 sum3 = vfmaq_f32(sum3, a3, b3);
658 }
659
660 let sum01 = vaddq_f32(sum0, sum1);
662 let sum23 = vaddq_f32(sum2, sum3);
663 let sum_vec = vaddq_f32(sum01, sum23);
664
665 let mut sum = horizontal_sum_neon(sum_vec);
666
667 let main_processed = chunks * CHUNK_SIZE;
668 let remaining = n - main_processed;
669 let remaining_chunks = remaining / SIMD_WIDTH;
670
671 let mut remainder_sum = vdupq_n_f32(0.0);
672 for i in 0..remaining_chunks {
673 let offset = main_processed + i * SIMD_WIDTH;
674 let a_vec = vld1q_f32(a.as_ptr().add(offset));
675 let b_vec = vld1q_f32(b.as_ptr().add(offset));
676 remainder_sum = vfmaq_f32(remainder_sum, a_vec, b_vec);
677 }
678
679 sum += horizontal_sum_neon(remainder_sum);
680
681 let scalar_start = main_processed + remaining_chunks * SIMD_WIDTH;
682 for i in scalar_start..n {
683 sum += a[i] * b[i];
684 }
685
686 sum
687}
688
689#[cfg(target_arch = "aarch64")]
695#[inline]
696pub(crate) unsafe fn horizontal_sum_neon(v: float32x4_t) -> f32 {
697 vaddvq_f32(v)
698}
699
700#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
706#[inline]
707unsafe fn dot_product_simd128_unrolled(a: &[f32], b: &[f32]) -> f32 {
708 const SIMD_WIDTH: usize = 4;
709 const UNROLL: usize = 4;
710 const CHUNK_SIZE: usize = SIMD_WIDTH * UNROLL; let n = a.len();
713 debug_assert_eq!(n, b.len());
714 let chunks = n / CHUNK_SIZE;
715
716 let mut sum0 = f32x4_splat(0.0);
718 let mut sum1 = f32x4_splat(0.0);
719 let mut sum2 = f32x4_splat(0.0);
720 let mut sum3 = f32x4_splat(0.0);
721
722 for i in 0..chunks {
723 let base = i * CHUNK_SIZE;
724
725 let a0 = v128_load(a.as_ptr().add(base) as *const v128);
726 let b0 = v128_load(b.as_ptr().add(base) as *const v128);
727 sum0 = f32x4_add(sum0, f32x4_mul(a0, b0));
728
729 let a1 = v128_load(a.as_ptr().add(base + SIMD_WIDTH) as *const v128);
730 let b1 = v128_load(b.as_ptr().add(base + SIMD_WIDTH) as *const v128);
731 sum1 = f32x4_add(sum1, f32x4_mul(a1, b1));
732
733 let a2 = v128_load(a.as_ptr().add(base + SIMD_WIDTH * 2) as *const v128);
734 let b2 = v128_load(b.as_ptr().add(base + SIMD_WIDTH * 2) as *const v128);
735 sum2 = f32x4_add(sum2, f32x4_mul(a2, b2));
736
737 let a3 = v128_load(a.as_ptr().add(base + SIMD_WIDTH * 3) as *const v128);
738 let b3 = v128_load(b.as_ptr().add(base + SIMD_WIDTH * 3) as *const v128);
739 sum3 = f32x4_add(sum3, f32x4_mul(a3, b3));
740 }
741
742 let sum01 = f32x4_add(sum0, sum1);
744 let sum23 = f32x4_add(sum2, sum3);
745 let sum_vec = f32x4_add(sum01, sum23);
746
747 let mut total = horizontal_sum_simd128(sum_vec);
748
749 let main_processed = chunks * CHUNK_SIZE;
750 let remaining = n - main_processed;
751 let remaining_chunks = remaining / SIMD_WIDTH;
752
753 let mut remainder_sum = f32x4_splat(0.0);
754 for i in 0..remaining_chunks {
755 let offset = main_processed + i * SIMD_WIDTH;
756 let a_vec = v128_load(a.as_ptr().add(offset) as *const v128);
757 let b_vec = v128_load(b.as_ptr().add(offset) as *const v128);
758 remainder_sum = f32x4_add(remainder_sum, f32x4_mul(a_vec, b_vec));
759 }
760
761 total += horizontal_sum_simd128(remainder_sum);
762
763 let scalar_start = main_processed + remaining_chunks * SIMD_WIDTH;
764 for i in scalar_start..n {
765 total += a[i] * b[i];
766 }
767
768 total
769}
770
771#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
779#[inline]
780pub(crate) unsafe fn horizontal_sum_simd128(v: v128) -> f32 {
781 f32x4_extract_lane::<0>(v)
782 + f32x4_extract_lane::<1>(v)
783 + f32x4_extract_lane::<2>(v)
784 + f32x4_extract_lane::<3>(v)
785}
786
787#[cfg(target_arch = "aarch64")]
793#[inline]
794fn dot_product_batch4_neon_kernel(
795 q: &[f32],
796 c0: &[f32],
797 c1: &[f32],
798 c2: &[f32],
799 c3: &[f32],
800) -> [f32; 4] {
801 unsafe { dot_product_batch4_neon(q, c0, c1, c2, c3) }
803}
804
805#[cfg(target_arch = "aarch64")]
811#[inline]
812unsafe fn dot_product_batch4_neon(
813 q: &[f32],
814 c0: &[f32],
815 c1: &[f32],
816 c2: &[f32],
817 c3: &[f32],
818) -> [f32; 4] {
819 const W: usize = 4; const CHUNK: usize = W * 2; let n = q.len();
823 let chunks = n / CHUNK;
824
825 let mut acc00 = vdupq_n_f32(0.0);
826 let mut acc01 = vdupq_n_f32(0.0);
827 let mut acc10 = vdupq_n_f32(0.0);
828 let mut acc11 = vdupq_n_f32(0.0);
829 let mut acc20 = vdupq_n_f32(0.0);
830 let mut acc21 = vdupq_n_f32(0.0);
831 let mut acc30 = vdupq_n_f32(0.0);
832 let mut acc31 = vdupq_n_f32(0.0);
833
834 for i in 0..chunks {
835 let base = i * CHUNK;
836 let q0 = vld1q_f32(q.as_ptr().add(base));
837 let q1 = vld1q_f32(q.as_ptr().add(base + W));
838
839 acc00 = vfmaq_f32(acc00, q0, vld1q_f32(c0.as_ptr().add(base)));
840 acc01 = vfmaq_f32(acc01, q1, vld1q_f32(c0.as_ptr().add(base + W)));
841 acc10 = vfmaq_f32(acc10, q0, vld1q_f32(c1.as_ptr().add(base)));
842 acc11 = vfmaq_f32(acc11, q1, vld1q_f32(c1.as_ptr().add(base + W)));
843 acc20 = vfmaq_f32(acc20, q0, vld1q_f32(c2.as_ptr().add(base)));
844 acc21 = vfmaq_f32(acc21, q1, vld1q_f32(c2.as_ptr().add(base + W)));
845 acc30 = vfmaq_f32(acc30, q0, vld1q_f32(c3.as_ptr().add(base)));
846 acc31 = vfmaq_f32(acc31, q1, vld1q_f32(c3.as_ptr().add(base + W)));
847 }
848
849 let mut out = [
850 vaddvq_f32(vaddq_f32(acc00, acc01)),
851 vaddvq_f32(vaddq_f32(acc10, acc11)),
852 vaddvq_f32(vaddq_f32(acc20, acc21)),
853 vaddvq_f32(vaddq_f32(acc30, acc31)),
854 ];
855
856 let scalar_start = chunks * CHUNK;
857 for i in scalar_start..n {
858 let qi = q[i];
859 out[0] += qi * c0[i];
860 out[1] += qi * c1[i];
861 out[2] += qi * c2[i];
862 out[3] += qi * c3[i];
863 }
864
865 out
866}
867
868#[inline]
870fn same_query_batch4(chunk: &[(&[f32], &[f32])]) -> bool {
871 debug_assert_eq!(chunk.len(), 4);
872 let q_ptr = chunk[0].0.as_ptr();
873 let q_len = chunk[0].0.len();
874 q_len == chunk[0].1.len()
875 && chunk
876 .iter()
877 .all(|(q, c)| q.as_ptr() == q_ptr && q.len() == q_len && c.len() == q_len)
878}
879
880pub fn batch_dot_product(pairs: &[(&[f32], &[f32])]) -> Vec<f32> {
882 let pair_kernel = resolved_dot_product_kernel();
883 let batch4_kernel = resolved_dot_product_batch4_kernel();
884 let mut out = Vec::with_capacity(pairs.len());
885
886 let mut chunks = pairs.chunks_exact(4);
887 for chunk in &mut chunks {
888 if same_query_batch4(chunk) {
889 let q = chunk[0].0;
890 let dots = batch4_kernel(q, chunk[0].1, chunk[1].1, chunk[2].1, chunk[3].1);
891 out.extend_from_slice(&dots);
892 } else {
893 for &(a, b) in chunk {
894 out.push(if a.len() == b.len() {
895 pair_kernel(a, b)
896 } else {
897 0.0
898 });
899 }
900 }
901 }
902 for &(a, b) in chunks.remainder() {
903 out.push(if a.len() == b.len() {
904 pair_kernel(a, b)
905 } else {
906 0.0
907 });
908 }
909 out
910}