usearch 2.11.1 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +100 -30
  2. package/binding.gyp +11 -12
  3. package/c/README.md +3 -0
  4. package/c/lib.cpp +50 -13
  5. package/c/test.c +60 -55
  6. package/c/usearch.h +16 -0
  7. package/cpp/CMakeLists.txt +6 -0
  8. package/cpp/README.md +34 -13
  9. package/cpp/bench.cpp +99 -47
  10. package/cpp/test.cpp +947 -132
  11. package/include/usearch/index.hpp +895 -273
  12. package/include/usearch/index_dense.hpp +418 -156
  13. package/include/usearch/index_plugins.hpp +99 -49
  14. package/javascript/dist/cjs/usearch.d.ts +39 -1
  15. package/javascript/dist/cjs/usearch.js +11 -1
  16. package/javascript/dist/esm/usearch.d.ts +39 -1
  17. package/javascript/dist/esm/usearch.js +11 -1
  18. package/javascript/tsconfig-esm.json +3 -2
  19. package/javascript/usearch.ts +18 -7
  20. package/package.json +2 -2
  21. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  22. package/prebuilds/linux-arm64/usearch.node +0 -0
  23. package/prebuilds/linux-x64/usearch.node +0 -0
  24. package/simsimd/.cmake-format.py +19 -0
  25. package/simsimd/CONTRIBUTING.md +49 -8
  26. package/simsimd/Cargo.lock +1 -1
  27. package/simsimd/Cargo.toml +10 -5
  28. package/simsimd/Package.swift +16 -0
  29. package/simsimd/README.md +69 -15
  30. package/simsimd/binding.gyp +14 -19
  31. package/simsimd/build.rs +2 -0
  32. package/simsimd/c/lib.c +18 -0
  33. package/simsimd/include/module.modulemap +4 -0
  34. package/simsimd/include/simsimd/binary.h +2 -0
  35. package/simsimd/include/simsimd/dot.h +466 -32
  36. package/simsimd/include/simsimd/probability.h +20 -10
  37. package/simsimd/include/simsimd/simsimd.h +444 -98
  38. package/simsimd/include/simsimd/spatial.h +480 -44
  39. package/simsimd/include/simsimd/types.h +161 -39
  40. package/simsimd/javascript/simsimd.ts +26 -2
  41. package/simsimd/package.json +1 -1
  42. package/simsimd/rust/benches/cosine.rs +8 -2
  43. package/simsimd/rust/benches/native.rs +240 -2
  44. package/simsimd/rust/benches/sqeuclidean.rs +8 -2
  45. package/simsimd/rust/lib.rs +50 -13
  46. package/simsimd/swift/source/SimSIMD.swift +123 -0
  47. package/simsimd/swift/tests/SimSIMDTests.swift +92 -0
  48. package/prebuilds/win32-ia32/usearch.node +0 -0
  49. package/prebuilds/win32-x64/usearch.node +0 -0
  50. package/simsimd/.releaserc +0 -87
@@ -12,6 +12,7 @@
12
12
  * - 64-bit IEEE floating point numbers
13
13
  * - 32-bit IEEE floating point numbers
14
14
  * - 16-bit IEEE floating point numbers
15
+ * - 16-bit brain floating point numbers
15
16
  * - 8-bit signed integral numbers
16
17
  *
17
18
  * For hardware architectures:
@@ -42,6 +43,8 @@ SIMSIMD_PUBLIC void simsimd_l2sq_f32_serial(simsimd_f32_t const* a, simsimd_f32_
42
43
  SIMSIMD_PUBLIC void simsimd_cos_f32_serial(simsimd_f32_t const* a, simsimd_f32_t const*, simsimd_size_t n, simsimd_distance_t* d);
43
44
  SIMSIMD_PUBLIC void simsimd_l2sq_f16_serial(simsimd_f16_t const* a, simsimd_f16_t const*, simsimd_size_t n, simsimd_distance_t* d);
44
45
  SIMSIMD_PUBLIC void simsimd_cos_f16_serial(simsimd_f16_t const* a, simsimd_f16_t const*, simsimd_size_t n, simsimd_distance_t* d);
46
+ SIMSIMD_PUBLIC void simsimd_l2sq_bf16_serial(simsimd_bf16_t const* a, simsimd_bf16_t const*, simsimd_size_t n, simsimd_distance_t* d);
47
+ SIMSIMD_PUBLIC void simsimd_cos_bf16_serial(simsimd_bf16_t const* a, simsimd_bf16_t const*, simsimd_size_t n, simsimd_distance_t* d);
45
48
  SIMSIMD_PUBLIC void simsimd_l2sq_i8_serial(simsimd_i8_t const* a, simsimd_i8_t const*, simsimd_size_t n, simsimd_distance_t* d);
46
49
  SIMSIMD_PUBLIC void simsimd_cos_i8_serial(simsimd_i8_t const* a, simsimd_i8_t const*, simsimd_size_t n, simsimd_distance_t* d);
47
50
 
@@ -52,6 +55,8 @@ SIMSIMD_PUBLIC void simsimd_l2sq_f32_accurate(simsimd_f32_t const* a, simsimd_f3
52
55
  SIMSIMD_PUBLIC void simsimd_cos_f32_accurate(simsimd_f32_t const* a, simsimd_f32_t const*, simsimd_size_t n, simsimd_distance_t* d);
53
56
  SIMSIMD_PUBLIC void simsimd_l2sq_f16_accurate(simsimd_f16_t const* a, simsimd_f16_t const*, simsimd_size_t n, simsimd_distance_t* d);
54
57
  SIMSIMD_PUBLIC void simsimd_cos_f16_accurate(simsimd_f16_t const* a, simsimd_f16_t const*, simsimd_size_t n, simsimd_distance_t* d);
58
+ SIMSIMD_PUBLIC void simsimd_l2sq_bf16_accurate(simsimd_bf16_t const* a, simsimd_bf16_t const*, simsimd_size_t n, simsimd_distance_t* d);
59
+ SIMSIMD_PUBLIC void simsimd_cos_bf16_accurate(simsimd_bf16_t const* a, simsimd_bf16_t const*, simsimd_size_t n, simsimd_distance_t* d);
55
60
  SIMSIMD_PUBLIC void simsimd_l2sq_i8_accurate(simsimd_i8_t const* a, simsimd_i8_t const*, simsimd_size_t n, simsimd_distance_t* d);
56
61
  SIMSIMD_PUBLIC void simsimd_cos_i8_accurate(simsimd_i8_t const* a, simsimd_i8_t const*, simsimd_size_t n, simsimd_distance_t* d);
57
62
 
@@ -86,6 +91,8 @@ SIMSIMD_PUBLIC void simsimd_l2sq_i8_haswell(simsimd_i8_t const* a, simsimd_i8_t
86
91
  SIMSIMD_PUBLIC void simsimd_cos_i8_haswell(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t n, simsimd_distance_t*);
87
92
  SIMSIMD_PUBLIC void simsimd_l2sq_f16_haswell(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n, simsimd_distance_t*);
88
93
  SIMSIMD_PUBLIC void simsimd_cos_f16_haswell(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n, simsimd_distance_t*);
94
+ SIMSIMD_PUBLIC void simsimd_l2sq_bf16_haswell(simsimd_bf16_t const* a, simsimd_bf16_t const* b, simsimd_size_t n, simsimd_distance_t*);
95
+ SIMSIMD_PUBLIC void simsimd_cos_bf16_haswell(simsimd_bf16_t const* a, simsimd_bf16_t const* b, simsimd_size_t n, simsimd_distance_t*);
89
96
 
90
97
  /* SIMD-powered backends for AVX512 CPUs of Skylake generation and newer, using 32-bit arithmetic over 512-bit words.
91
98
  * Skylake was launched in 2015, and discontinued in 2019. Skylake had support for F, CD, VL, DQ, and BW extensions,
@@ -102,6 +109,8 @@ SIMSIMD_PUBLIC void simsimd_cos_f64_skylake(simsimd_f64_t const* a, simsimd_f64_
102
109
  */
103
110
  SIMSIMD_PUBLIC void simsimd_l2sq_i8_ice(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t n, simsimd_distance_t*);
104
111
  SIMSIMD_PUBLIC void simsimd_cos_i8_ice(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t n, simsimd_distance_t*);
112
+ SIMSIMD_PUBLIC void simsimd_l2sq_bf16_genoa(simsimd_bf16_t const* a, simsimd_bf16_t const* b, simsimd_size_t n, simsimd_distance_t*);
113
+ SIMSIMD_PUBLIC void simsimd_cos_bf16_genoa(simsimd_bf16_t const* a, simsimd_bf16_t const* b, simsimd_size_t n, simsimd_distance_t*);
105
114
  SIMSIMD_PUBLIC void simsimd_l2sq_f16_sapphire(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n, simsimd_distance_t*);
106
115
  SIMSIMD_PUBLIC void simsimd_cos_f16_sapphire(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n, simsimd_distance_t*);
107
116
 
@@ -144,6 +153,9 @@ SIMSIMD_MAKE_COS(serial, f32, f32, SIMSIMD_IDENTIFY) // simsimd_cos_f32_serial
144
153
  SIMSIMD_MAKE_L2SQ(serial, f16, f32, SIMSIMD_UNCOMPRESS_F16) // simsimd_l2sq_f16_serial
145
154
  SIMSIMD_MAKE_COS(serial, f16, f32, SIMSIMD_UNCOMPRESS_F16) // simsimd_cos_f16_serial
146
155
 
156
+ SIMSIMD_MAKE_L2SQ(serial, bf16, f32, SIMSIMD_UNCOMPRESS_BF16) // simsimd_l2sq_bf16_serial
157
+ SIMSIMD_MAKE_COS(serial, bf16, f32, SIMSIMD_UNCOMPRESS_BF16) // simsimd_cos_bf16_serial
158
+
147
159
  SIMSIMD_MAKE_L2SQ(serial, i8, i32, SIMSIMD_IDENTIFY) // simsimd_l2sq_i8_serial
148
160
  SIMSIMD_MAKE_COS(serial, i8, i32, SIMSIMD_IDENTIFY) // simsimd_cos_i8_serial
149
161
 
@@ -153,6 +165,9 @@ SIMSIMD_MAKE_COS(accurate, f32, f64, SIMSIMD_IDENTIFY) // simsimd_cos_f32_accur
153
165
  SIMSIMD_MAKE_L2SQ(accurate, f16, f64, SIMSIMD_UNCOMPRESS_F16) // simsimd_l2sq_f16_accurate
154
166
  SIMSIMD_MAKE_COS(accurate, f16, f64, SIMSIMD_UNCOMPRESS_F16) // simsimd_cos_f16_accurate
155
167
 
168
+ SIMSIMD_MAKE_L2SQ(accurate, bf16, f64, SIMSIMD_UNCOMPRESS_BF16) // simsimd_l2sq_bf16_accurate
169
+ SIMSIMD_MAKE_COS(accurate, bf16, f64, SIMSIMD_UNCOMPRESS_BF16) // simsimd_cos_bf16_accurate
170
+
156
171
  SIMSIMD_MAKE_L2SQ(accurate, i8, i32, SIMSIMD_IDENTIFY) // simsimd_l2sq_i8_accurate
157
172
  SIMSIMD_MAKE_COS(accurate, i8, i32, SIMSIMD_IDENTIFY) // simsimd_cos_i8_accurate
158
173
 
@@ -285,16 +300,146 @@ SIMSIMD_PUBLIC void simsimd_cos_f16_neon(simsimd_f16_t const* a, simsimd_f16_t c
285
300
 
286
301
  #pragma clang attribute pop
287
302
  #pragma GCC pop_options
303
+ #endif // SIMSIMD_TARGET_NEON
304
+
305
+ #if SIMSIMD_TARGET_NEON_BF16
306
+ #pragma GCC push_options
307
+ #pragma GCC target("arch=armv8.6-a+simd+bf16")
308
+ #pragma clang attribute push(__attribute__((target("arch=armv8.6-a+simd+bf16"))), apply_to = function)
309
+
310
+ SIMSIMD_PUBLIC void simsimd_cos_bf16_neon(simsimd_bf16_t const* a, simsimd_bf16_t const* b, simsimd_size_t n,
311
+ simsimd_distance_t* result) {
312
+
313
+ // Similar to `simsimd_cos_i8_neon`, we can use the `BFMMLA` instruction through
314
+ // the `vbfmmlaq_f32` intrinsic to compute matrix products and later drop 1/4 of values.
315
+ // The only difference is that `zip` isn't provided for `bf16` and we need to reinterpret back
316
+ // and forth before zipping. Same as with integers, on modern Arm CPUs, this "smart"
317
+ // approach is actually slower by around 25%.
318
+ //
319
+ // float32x4_t products_low_vec = vdupq_n_f32(0.0f);
320
+ // float32x4_t products_high_vec = vdupq_n_f32(0.0f);
321
+ // for (; i + 8 <= n; i += 8) {
322
+ // bfloat16x8_t a_vec = vld1q_bf16((simsimd_bf16_for_arm_simd_t const*)a + i);
323
+ // bfloat16x8_t b_vec = vld1q_bf16((simsimd_bf16_for_arm_simd_t const*)b + i);
324
+ // int16x8_t a_vec_s16 = vreinterpretq_s16_bf16(a_vec);
325
+ // int16x8_t b_vec_s16 = vreinterpretq_s16_bf16(b_vec);
326
+ // int16x8x2_t y_w_vecs_s16 = vzipq_s16(a_vec_s16, b_vec_s16);
327
+ // bfloat16x8_t y_vec = vreinterpretq_bf16_s16(y_w_vecs_s16.val[0]);
328
+ // bfloat16x8_t w_vec = vreinterpretq_bf16_s16(y_w_vecs_s16.val[1]);
329
+ // bfloat16x4_t a_low = vget_low_bf16(a_vec);
330
+ // bfloat16x4_t b_low = vget_low_bf16(b_vec);
331
+ // bfloat16x4_t a_high = vget_high_bf16(a_vec);
332
+ // bfloat16x4_t b_high = vget_high_bf16(b_vec);
333
+ // bfloat16x8_t x_vec = vcombine_bf16(a_low, b_low);
334
+ // bfloat16x8_t v_vec = vcombine_bf16(a_high, b_high);
335
+ // products_low_vec = vbfmmlaq_f32(products_low_vec, x_vec, y_vec);
336
+ // products_high_vec = vbfmmlaq_f32(products_high_vec, v_vec, w_vec);
337
+ // }
338
+ // float32x4_t products_vec = vaddq_f32(products_high_vec, products_low_vec);
339
+ // simsimd_f32_t a2 = products_vec[0], ab = products_vec[1], b2 = products_vec[3];
340
+
341
+ float32x4_t ab_high_vec = vdupq_n_f32(0), ab_low_vec = vdupq_n_f32(0);
342
+ float32x4_t a2_high_vec = vdupq_n_f32(0), a2_low_vec = vdupq_n_f32(0);
343
+ float32x4_t b2_high_vec = vdupq_n_f32(0), b2_low_vec = vdupq_n_f32(0);
344
+ simsimd_size_t i = 0;
345
+ for (; i + 8 <= n; i += 8) {
346
+ bfloat16x8_t a_vec = vld1q_bf16((simsimd_bf16_for_arm_simd_t const*)a + i);
347
+ bfloat16x8_t b_vec = vld1q_bf16((simsimd_bf16_for_arm_simd_t const*)b + i);
348
+ ab_high_vec = vbfmlaltq_f32(ab_high_vec, a_vec, b_vec);
349
+ ab_low_vec = vbfmlalbq_f32(ab_low_vec, a_vec, b_vec);
350
+ a2_high_vec = vbfmlaltq_f32(a2_high_vec, a_vec, a_vec);
351
+ a2_low_vec = vbfmlalbq_f32(a2_low_vec, a_vec, a_vec);
352
+ b2_high_vec = vbfmlaltq_f32(b2_high_vec, b_vec, b_vec);
353
+ b2_low_vec = vbfmlalbq_f32(b2_low_vec, b_vec, b_vec);
354
+ }
355
+
356
+ // In case the software emulation for `bf16` scalars is enabled, the `simsimd_uncompress_bf16`
357
+ // function will run. It is extremely slow, so even for the tail, let's combine serial
358
+ // loads and stores with vectorized math.
359
+ if (i < n) {
360
+ union {
361
+ bfloat16x8_t bf16_vec;
362
+ simsimd_bf16_t bf16[8];
363
+ } a_padded_tail, b_padded_tail;
364
+ simsimd_size_t j = 0;
365
+ for (; i < n; ++i, ++j)
366
+ a_padded_tail.bf16[j] = a[i], b_padded_tail.bf16[j] = b[i];
367
+ for (; j < 8; ++j)
368
+ a_padded_tail.bf16[j] = 0, b_padded_tail.bf16[j] = 0;
369
+ ab_high_vec = vbfmlaltq_f32(ab_high_vec, a_padded_tail.bf16_vec, b_padded_tail.bf16_vec);
370
+ ab_low_vec = vbfmlalbq_f32(ab_low_vec, a_padded_tail.bf16_vec, b_padded_tail.bf16_vec);
371
+ a2_high_vec = vbfmlaltq_f32(a2_high_vec, a_padded_tail.bf16_vec, a_padded_tail.bf16_vec);
372
+ a2_low_vec = vbfmlalbq_f32(a2_low_vec, a_padded_tail.bf16_vec, a_padded_tail.bf16_vec);
373
+ b2_high_vec = vbfmlaltq_f32(b2_high_vec, b_padded_tail.bf16_vec, b_padded_tail.bf16_vec);
374
+ b2_low_vec = vbfmlalbq_f32(b2_low_vec, b_padded_tail.bf16_vec, b_padded_tail.bf16_vec);
375
+ }
376
+
377
+ // Avoid `simsimd_approximate_inverse_square_root` on Arm NEON
378
+ simsimd_f32_t ab = vaddvq_f32(vaddq_f32(ab_high_vec, ab_low_vec)),
379
+ a2 = vaddvq_f32(vaddq_f32(a2_high_vec, a2_low_vec)),
380
+ b2 = vaddvq_f32(vaddq_f32(b2_high_vec, b2_low_vec));
381
+ simsimd_f32_t a2_b2_arr[2] = {a2, b2};
382
+ float32x2_t a2_b2 = vld1_f32(a2_b2_arr);
383
+ a2_b2 = vrsqrte_f32(a2_b2);
384
+ vst1_f32(a2_b2_arr, a2_b2);
385
+ *result = ab != 0 ? 1 - ab * a2_b2_arr[0] * a2_b2_arr[1] : 1;
386
+ }
387
+
388
+ SIMSIMD_PUBLIC void simsimd_l2sq_bf16_neon(simsimd_bf16_t const* a, simsimd_bf16_t const* b, simsimd_size_t n,
389
+ simsimd_distance_t* result) {
390
+ float32x4_t diff_high_vec = vdupq_n_f32(0), diff_low_vec = vdupq_n_f32(0);
391
+ float32x4_t sum_high_vec = vdupq_n_f32(0), sum_low_vec = vdupq_n_f32(0);
392
+ simsimd_size_t i = 0;
393
+ for (; i + 8 <= n; i += 8) {
394
+ bfloat16x8_t a_vec = vld1q_bf16((simsimd_bf16_for_arm_simd_t const*)a + i);
395
+ bfloat16x8_t b_vec = vld1q_bf16((simsimd_bf16_for_arm_simd_t const*)b + i);
396
+ // We can't perform subtraction in `bf16`. One option would be to upcast to `f32`
397
+ // and then subtract, converting back to `bf16` for computing the squared difference.
398
+ diff_high_vec = vsubq_f32(vcvt_f32_bf16(vget_high_bf16(a_vec)), vcvt_f32_bf16(vget_high_bf16(b_vec)));
399
+ diff_low_vec = vsubq_f32(vcvt_f32_bf16(vget_low_bf16(a_vec)), vcvt_f32_bf16(vget_low_bf16(b_vec)));
400
+ sum_high_vec = vfmaq_f32(sum_high_vec, diff_high_vec, diff_high_vec);
401
+ sum_low_vec = vfmaq_f32(sum_low_vec, diff_low_vec, diff_low_vec);
402
+ }
403
+
404
+ // In case the software emulation for `bf16` scalars is enabled, the `simsimd_uncompress_bf16`
405
+ // function will run. It is extremely slow, so even for the tail, let's combine serial
406
+ // loads and stores with vectorized math.
407
+ if (i < n) {
408
+ union {
409
+ bfloat16x8_t bf16_vec;
410
+ simsimd_bf16_t bf16[8];
411
+ } a_padded_tail, b_padded_tail;
412
+ simsimd_size_t j = 0;
413
+ for (; i < n; ++i, ++j)
414
+ a_padded_tail.bf16[j] = a[i], b_padded_tail.bf16[j] = b[i];
415
+ for (; j < 8; ++j)
416
+ a_padded_tail.bf16[j] = 0, b_padded_tail.bf16[j] = 0;
417
+ diff_high_vec = vsubq_f32(vcvt_f32_bf16(vget_high_bf16(a_padded_tail.bf16_vec)),
418
+ vcvt_f32_bf16(vget_high_bf16(b_padded_tail.bf16_vec)));
419
+ diff_low_vec = vsubq_f32(vcvt_f32_bf16(vget_low_bf16(a_padded_tail.bf16_vec)),
420
+ vcvt_f32_bf16(vget_low_bf16(b_padded_tail.bf16_vec)));
421
+ sum_high_vec = vfmaq_f32(sum_high_vec, diff_high_vec, diff_high_vec);
422
+ sum_low_vec = vfmaq_f32(sum_low_vec, diff_low_vec, diff_low_vec);
423
+ }
424
+
425
+ // Avoid `simsimd_approximate_inverse_square_root` on Arm NEON
426
+ simsimd_f32_t sum = vaddvq_f32(vaddq_f32(sum_high_vec, sum_low_vec));
427
+ *result = sum;
428
+ }
429
+ #pragma clang attribute pop
430
+ #pragma GCC pop_options
431
+ #endif // SIMSIMD_TARGET_NEON_BF16
288
432
 
433
+ #if SIMSIMD_TARGET_NEON_I8
289
434
  #pragma GCC push_options
290
- #pragma GCC target("arch=armv8.2-a+dotprod")
291
- #pragma clang attribute push(__attribute__((target("arch=armv8.2-a+dotprod"))), apply_to = function)
435
+ #pragma GCC target("arch=armv8.2-a+dotprod+i8mm")
436
+ #pragma clang attribute push(__attribute__((target("arch=armv8.2-a+dotprod+i8mm"))), apply_to = function)
292
437
 
293
438
  SIMSIMD_PUBLIC void simsimd_l2sq_i8_neon(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t n,
294
439
  simsimd_distance_t* result) {
295
440
  int32x4_t d2_vec = vdupq_n_s32(0);
296
441
  simsimd_size_t i = 0;
297
- for (; i + 7 < n; i += 8) {
442
+ for (; i + 8 <= n; i += 8) {
298
443
  int8x8_t a_vec = vld1_s8(a + i);
299
444
  int8x8_t b_vec = vld1_s8(b + i);
300
445
  int16x8_t a_vec16 = vmovl_s8(a_vec);
@@ -315,33 +460,109 @@ SIMSIMD_PUBLIC void simsimd_l2sq_i8_neon(simsimd_i8_t const* a, simsimd_i8_t con
315
460
  SIMSIMD_PUBLIC void simsimd_cos_i8_neon(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t n,
316
461
  simsimd_distance_t* result) {
317
462
 
318
- int32x4_t ab_vec = vdupq_n_s32(0);
319
- int32x4_t a2_vec = vdupq_n_s32(0);
320
- int32x4_t b2_vec = vdupq_n_s32(0);
321
463
  simsimd_size_t i = 0;
322
464
 
465
+ // Variant 1.
323
466
  // If the 128-bit `vdot_s32` intrinsic is unavailable, we can use the 64-bit `vdot_s32`.
324
- // for (simsimd_size_t i = 0; i != n; i += 8) {
325
- // int16x8_t a_vec = vmovl_s8(vld1_s8(a + i));
326
- // int16x8_t b_vec = vmovl_s8(vld1_s8(b + i));
327
- // int16x8_t ab_part_vec = vmulq_s16(a_vec, b_vec);
328
- // int16x8_t a2_part_vec = vmulq_s16(a_vec, a_vec);
329
- // int16x8_t b2_part_vec = vmulq_s16(b_vec, b_vec);
330
- // ab_vec = vaddq_s32(ab_vec, vaddq_s32(vmovl_s16(vget_high_s16(ab_part_vec)), //
331
- // vmovl_s16(vget_low_s16(ab_part_vec))));
332
- // a2_vec = vaddq_s32(a2_vec, vaddq_s32(vmovl_s16(vget_high_s16(a2_part_vec)), //
333
- // vmovl_s16(vget_low_s16(a2_part_vec))));
334
- // b2_vec = vaddq_s32(b2_vec, vaddq_s32(vmovl_s16(vget_high_s16(b2_part_vec)), //
335
- // vmovl_s16(vget_low_s16(b2_part_vec))));
336
- // }
337
- for (; i + 15 < n; i += 16) {
467
+ //
468
+ // int32x4_t ab_vec = vdupq_n_s32(0);
469
+ // int32x4_t a2_vec = vdupq_n_s32(0);
470
+ // int32x4_t b2_vec = vdupq_n_s32(0);
471
+ // for (simsimd_size_t i = 0; i != n; i += 8) {
472
+ // int16x8_t a_vec = vmovl_s8(vld1_s8(a + i));
473
+ // int16x8_t b_vec = vmovl_s8(vld1_s8(b + i));
474
+ // int16x8_t ab_part_vec = vmulq_s16(a_vec, b_vec);
475
+ // int16x8_t a2_part_vec = vmulq_s16(a_vec, a_vec);
476
+ // int16x8_t b2_part_vec = vmulq_s16(b_vec, b_vec);
477
+ // ab_vec = vaddq_s32(ab_vec, vaddq_s32(vmovl_s16(vget_high_s16(ab_part_vec)), //
478
+ // vmovl_s16(vget_low_s16(ab_part_vec))));
479
+ // a2_vec = vaddq_s32(a2_vec, vaddq_s32(vmovl_s16(vget_high_s16(a2_part_vec)), //
480
+ // vmovl_s16(vget_low_s16(a2_part_vec))));
481
+ // b2_vec = vaddq_s32(b2_vec, vaddq_s32(vmovl_s16(vget_high_s16(b2_part_vec)), //
482
+ // vmovl_s16(vget_low_s16(b2_part_vec))));
483
+ // }
484
+ //
485
+ // Variant 2.
486
+ // With the 128-bit `vdotq_s32` intrinsic, we can use the following code:
487
+ //
488
+ // for (; i + 16 <= n; i += 16) {
489
+ // int8x16_t a_vec = vld1q_s8(a + i);
490
+ // int8x16_t b_vec = vld1q_s8(b + i);
491
+ // ab_vec = vdotq_s32(ab_vec, a_vec, b_vec);
492
+ // a2_vec = vdotq_s32(a2_vec, a_vec, a_vec);
493
+ // b2_vec = vdotq_s32(b2_vec, b_vec, b_vec);
494
+ // }
495
+ //
496
+ // Variant 3.
497
+ // To use MMLA instructions, we need to reorganize the contents of the vectors.
498
+ // On input we have `a_vec` and `b_vec`:
499
+ //
500
+ // a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]
501
+ // b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]
502
+ //
503
+ // We will be multiplying matrices of size 2x8 and 8x2. So we need to perform a few shuffles:
504
+ //
505
+ // X =
506
+ // a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7],
507
+ // b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]
508
+ // Y =
509
+ // a[0], b[0],
510
+ // a[1], b[1],
511
+ // a[2], b[2],
512
+ // a[3], b[3],
513
+ // a[4], b[4],
514
+ // a[5], b[5],
515
+ // a[6], b[6],
516
+ // a[7], b[7]
517
+ //
518
+ // V =
519
+ // a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15],
520
+ // b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]
521
+ // W =
522
+ // a[8], b[8],
523
+ // a[9], b[9],
524
+ // a[10], b[10],
525
+ // a[11], b[11],
526
+ // a[12], b[12],
527
+ // a[13], b[13],
528
+ // a[14], b[14],
529
+ // a[15], b[15]
530
+ //
531
+ // Performing matrix multiplications we can aggregate into a matrix `products_low_vec` and `products_high_vec`:
532
+ //
533
+ // X * X, X * Y V * W, V * V
534
+ // Y * X, Y * Y W * W, W * V
535
+ //
536
+ // Of those values we need only 3/4, as the (X * Y) and (Y * X) are the same.
537
+ //
538
+ // int32x4_t products_low_vec = vdupq_n_s32(0), products_high_vec = vdupq_n_s32(0);
539
+ // int8x16_t a_low_b_low_vec, a_high_b_high_vec;
540
+ // for (; i + 16 <= n; i += 16) {
541
+ // int8x16_t a_vec = vld1q_s8(a + i);
542
+ // int8x16_t b_vec = vld1q_s8(b + i);
543
+ // int8x16x2_t y_w_vecs = vzipq_s8(a_vec, b_vec);
544
+ // int8x16_t x_vec = vcombine_s8(vget_low_s8(a_vec), vget_low_s8(b_vec));
545
+ // int8x16_t v_vec = vcombine_s8(vget_high_s8(a_vec), vget_high_s8(b_vec));
546
+ // products_low_vec = vmmlaq_s32(products_low_vec, x_vec, y_w_vecs.val[0]);
547
+ // products_high_vec = vmmlaq_s32(products_high_vec, v_vec, y_w_vecs.val[1]);
548
+ // }
549
+ // int32x4_t products_vec = vaddq_s32(products_high_vec, products_low_vec);
550
+ // int32_t a2 = products_vec[0];
551
+ // int32_t ab = products_vec[1];
552
+ // int32_t b2 = products_vec[3];
553
+ //
554
+ // That solution is elegant, but it requires the additional `+i8mm` extension and is currently slower,
555
+ // at least on AWS Graviton 3.
556
+ int32x4_t ab_vec = vdupq_n_s32(0);
557
+ int32x4_t a2_vec = vdupq_n_s32(0);
558
+ int32x4_t b2_vec = vdupq_n_s32(0);
559
+ for (; i + 16 <= n; i += 16) {
338
560
  int8x16_t a_vec = vld1q_s8(a + i);
339
561
  int8x16_t b_vec = vld1q_s8(b + i);
340
562
  ab_vec = vdotq_s32(ab_vec, a_vec, b_vec);
341
563
  a2_vec = vdotq_s32(a2_vec, a_vec, a_vec);
342
564
  b2_vec = vdotq_s32(b2_vec, b_vec, b_vec);
343
565
  }
344
-
345
566
  int32_t ab = vaddvq_s32(ab_vec);
346
567
  int32_t a2 = vaddvq_s32(a2_vec);
347
568
  int32_t b2 = vaddvq_s32(b2_vec);
@@ -362,7 +583,7 @@ SIMSIMD_PUBLIC void simsimd_cos_i8_neon(simsimd_i8_t const* a, simsimd_i8_t cons
362
583
 
363
584
  #pragma clang attribute pop
364
585
  #pragma GCC pop_options
365
- #endif // SIMSIMD_TARGET_NEON
586
+ #endif // SIMSIMD_TARGET_NEON_I8
366
587
 
367
588
  #if SIMSIMD_TARGET_SVE
368
589
  #pragma GCC push_options
@@ -622,6 +843,113 @@ SIMSIMD_PUBLIC void simsimd_cos_f16_haswell(simsimd_f16_t const* a, simsimd_f16_
622
843
  *result = ab != 0 ? _mm_cvtss_f32(result_vec) : 1; // Extract the final result
623
844
  }
624
845
 
846
+ SIMSIMD_PUBLIC void simsimd_l2sq_bf16_haswell(simsimd_bf16_t const* a, simsimd_bf16_t const* b, simsimd_size_t n,
847
+ simsimd_distance_t* result) {
848
+ __m256 d2_vec = _mm256_setzero_ps();
849
+ simsimd_size_t i = 0;
850
+ for (; i + 8 <= n; i += 8) {
851
+ // Upcasting from `bf16` to `f32` is done by shifting the `bf16` values by 16 bits to the left, like:
852
+ // x = _mm256_castsi256_ps(_mm256_slli_epi32(_mm256_cvtepu16_epi32(x), 16))
853
+ __m256 a_vec =
854
+ _mm256_castsi256_ps(_mm256_slli_epi32(_mm256_cvtepu16_epi32(_mm_loadu_si128((__m128i const*)(a + i))), 16));
855
+ __m256 b_vec =
856
+ _mm256_castsi256_ps(_mm256_slli_epi32(_mm256_cvtepu16_epi32(_mm_loadu_si128((__m128i const*)(b + i))), 16));
857
+ __m256 d_vec = _mm256_sub_ps(a_vec, b_vec);
858
+ d2_vec = _mm256_fmadd_ps(d_vec, d_vec, d2_vec);
859
+ }
860
+
861
+ // In case the software emulation for `bf16` scalars is enabled, the `simsimd_uncompress_bf16`
862
+ // function will run. It is extremely slow, so even for the tail, let's combine serial
863
+ // loads and stores with vectorized math.
864
+ if (i < n) {
865
+ union {
866
+ __m128i bf16_vec;
867
+ simsimd_bf16_t bf16[8];
868
+ } a_padded_tail, b_padded_tail;
869
+ simsimd_size_t j = 0;
870
+ for (; i < n; ++i, ++j)
871
+ a_padded_tail.bf16[j] = a[i], b_padded_tail.bf16[j] = b[i];
872
+ for (; j < 8; ++j)
873
+ a_padded_tail.bf16[j] = 0, b_padded_tail.bf16[j] = 0;
874
+ __m256 a_vec = _mm256_castsi256_ps(_mm256_slli_epi32(_mm256_cvtepu16_epi32(a_padded_tail.bf16_vec), 16));
875
+ __m256 b_vec = _mm256_castsi256_ps(_mm256_slli_epi32(_mm256_cvtepu16_epi32(b_padded_tail.bf16_vec), 16));
876
+ __m256 d_vec = _mm256_sub_ps(a_vec, b_vec);
877
+ d2_vec = _mm256_fmadd_ps(d_vec, d_vec, d2_vec);
878
+ }
879
+
880
+ d2_vec = _mm256_add_ps(_mm256_permute2f128_ps(d2_vec, d2_vec, 1), d2_vec);
881
+ d2_vec = _mm256_hadd_ps(d2_vec, d2_vec);
882
+ d2_vec = _mm256_hadd_ps(d2_vec, d2_vec);
883
+
884
+ simsimd_f32_t f32_result;
885
+ _mm_store_ss(&f32_result, _mm256_castps256_ps128(d2_vec));
886
+ *result = f32_result;
887
+ }
888
+
889
+ SIMSIMD_PUBLIC void simsimd_cos_bf16_haswell(simsimd_bf16_t const* a, simsimd_bf16_t const* b, simsimd_size_t n,
890
+ simsimd_distance_t* result) {
891
+
892
+ __m256 ab_vec = _mm256_setzero_ps(), a2_vec = _mm256_setzero_ps(), b2_vec = _mm256_setzero_ps();
893
+ simsimd_size_t i = 0;
894
+ for (; i + 8 <= n; i += 8) {
895
+ // Upcasting from `bf16` to `f32` is done by shifting the `bf16` values by 16 bits to the left, like:
896
+ // x = _mm256_castsi256_ps(_mm256_slli_epi32(_mm256_cvtepu16_epi32(x), 16))
897
+ __m256 a_vec =
898
+ _mm256_castsi256_ps(_mm256_slli_epi32(_mm256_cvtepu16_epi32(_mm_loadu_si128((__m128i const*)(a + i))), 16));
899
+ __m256 b_vec =
900
+ _mm256_castsi256_ps(_mm256_slli_epi32(_mm256_cvtepu16_epi32(_mm_loadu_si128((__m128i const*)(b + i))), 16));
901
+ ab_vec = _mm256_fmadd_ps(a_vec, b_vec, ab_vec);
902
+ a2_vec = _mm256_fmadd_ps(a_vec, a_vec, a2_vec);
903
+ b2_vec = _mm256_fmadd_ps(b_vec, b_vec, b2_vec);
904
+ }
905
+
906
+ // In case the software emulation for `bf16` scalars is enabled, the `simsimd_uncompress_bf16`
907
+ // function will run. It is extremely slow, so even for the tail, let's combine serial
908
+ // loads and stores with vectorized math.
909
+ if (i < n) {
910
+ union {
911
+ __m128i bf16_vec;
912
+ simsimd_bf16_t bf16[8];
913
+ } a_padded_tail, b_padded_tail;
914
+ simsimd_size_t j = 0;
915
+ for (; i < n; ++i, ++j)
916
+ a_padded_tail.bf16[j] = a[i], b_padded_tail.bf16[j] = b[i];
917
+ for (; j < 8; ++j)
918
+ a_padded_tail.bf16[j] = 0, b_padded_tail.bf16[j] = 0;
919
+ __m256 a_vec = _mm256_castsi256_ps(_mm256_slli_epi32(_mm256_cvtepu16_epi32(a_padded_tail.bf16_vec), 16));
920
+ __m256 b_vec = _mm256_castsi256_ps(_mm256_slli_epi32(_mm256_cvtepu16_epi32(b_padded_tail.bf16_vec), 16));
921
+ ab_vec = _mm256_fmadd_ps(a_vec, b_vec, ab_vec);
922
+ a2_vec = _mm256_fmadd_ps(a_vec, a_vec, a2_vec);
923
+ b2_vec = _mm256_fmadd_ps(b_vec, b_vec, b2_vec);
924
+ }
925
+
926
+ // Horizontal reductions:
927
+ ab_vec = _mm256_add_ps(_mm256_permute2f128_ps(ab_vec, ab_vec, 1), ab_vec);
928
+ ab_vec = _mm256_hadd_ps(ab_vec, ab_vec);
929
+ ab_vec = _mm256_hadd_ps(ab_vec, ab_vec);
930
+
931
+ a2_vec = _mm256_add_ps(_mm256_permute2f128_ps(a2_vec, a2_vec, 1), a2_vec);
932
+ a2_vec = _mm256_hadd_ps(a2_vec, a2_vec);
933
+ a2_vec = _mm256_hadd_ps(a2_vec, a2_vec);
934
+
935
+ b2_vec = _mm256_add_ps(_mm256_permute2f128_ps(b2_vec, b2_vec, 1), b2_vec);
936
+ b2_vec = _mm256_hadd_ps(b2_vec, b2_vec);
937
+ b2_vec = _mm256_hadd_ps(b2_vec, b2_vec);
938
+
939
+ simsimd_f32_t ab, a2, b2;
940
+ _mm_store_ss(&ab, _mm256_castps256_ps128(ab_vec));
941
+ _mm_store_ss(&a2, _mm256_castps256_ps128(a2_vec));
942
+ _mm_store_ss(&b2, _mm256_castps256_ps128(b2_vec));
943
+
944
+ // Replace simsimd_approximate_inverse_square_root with `rsqrtss`
945
+ __m128 a2_sqrt_recip = _mm_rsqrt_ss(_mm_set_ss((float)a2));
946
+ __m128 b2_sqrt_recip = _mm_rsqrt_ss(_mm_set_ss((float)b2));
947
+ __m128 result_vec = _mm_mul_ss(a2_sqrt_recip, b2_sqrt_recip); // Multiply the reciprocal square roots
948
+ result_vec = _mm_mul_ss(result_vec, _mm_set_ss((float)ab)); // Multiply by ab
949
+ result_vec = _mm_sub_ss(_mm_set_ss(1.0f), result_vec); // Subtract from 1
950
+ *result = ab != 0 ? _mm_cvtss_f32(result_vec) : 1; // Extract the final result
951
+ }
952
+
625
953
  SIMSIMD_PUBLIC void simsimd_l2sq_i8_haswell(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t n,
626
954
  simsimd_distance_t* result) {
627
955
 
@@ -681,13 +1009,13 @@ SIMSIMD_PUBLIC void simsimd_cos_i8_haswell(simsimd_i8_t const* a, simsimd_i8_t c
681
1009
  __m256i a_vec = _mm256_loadu_si256((__m256i const*)(a + i));
682
1010
  __m256i b_vec = _mm256_loadu_si256((__m256i const*)(b + i));
683
1011
 
684
- // Unpack int8 to int16
1012
+ // Unpack `int8` to `int16`
685
1013
  __m256i a_low_16 = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(a_vec, 0));
686
1014
  __m256i a_high_16 = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(a_vec, 1));
687
1015
  __m256i b_low_16 = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(b_vec, 0));
688
1016
  __m256i b_high_16 = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(b_vec, 1));
689
1017
 
690
- // Multiply and accumulate at int16 level, accumulate at int32 level
1018
+ // Multiply and accumulate as `int16`, accumulate products as `int32`
691
1019
  ab_low_vec = _mm256_add_epi32(ab_low_vec, _mm256_madd_epi16(a_low_16, b_low_16));
692
1020
  ab_high_vec = _mm256_add_epi32(ab_high_vec, _mm256_madd_epi16(a_high_16, b_high_16));
693
1021
  a2_low_vec = _mm256_add_epi32(a2_low_vec, _mm256_madd_epi16(a_low_16, a_low_16));
@@ -868,6 +1196,99 @@ simsimd_cos_f64_skylake_cycle:
868
1196
  #pragma GCC pop_options
869
1197
  #endif // SIMSIMD_TARGET_SKYLAKE
870
1198
 
1199
+ #if SIMSIMD_TARGET_GENOA
1200
+ #pragma GCC push_options
1201
+ #pragma GCC target("avx512f", "avx512vl", "bmi2", "avx512bw", "avx512bf16")
1202
+ #pragma clang attribute push(__attribute__((target("avx512f,avx512vl,bmi2,avx512bw,avx512bf16"))), apply_to = function)
1203
+
1204
+ SIMSIMD_PUBLIC void simsimd_l2sq_bf16_genoa(simsimd_bf16_t const* a, simsimd_bf16_t const* b, simsimd_size_t n,
1205
+ simsimd_distance_t* result) {
1206
+ __m512 d2_top_vec = _mm512_setzero_ps(), d2_bot_vec = _mm512_setzero_ps();
1207
+ __m512 d_top_vec = _mm512_setzero_ps(), d_bot_vec = _mm512_setzero_ps();
1208
+ __m512 a_f32_top_vec, a_f32_bot_vec, b_f32_top_vec, b_f32_bot_vec;
1209
+ __m512i a_i16_vec, b_i16_vec;
1210
+
1211
+ simsimd_l2sq_bf16_genoa_cycle:
1212
+ if (n < 32) {
1213
+ __mmask32 mask = (__mmask32)_bzhi_u32(0xFFFFFFFF, n);
1214
+ a_i16_vec = _mm512_maskz_loadu_epi16(mask, a);
1215
+ b_i16_vec = _mm512_maskz_loadu_epi16(mask, b);
1216
+ n = 0;
1217
+ } else {
1218
+ a_i16_vec = _mm512_loadu_epi16(a);
1219
+ b_i16_vec = _mm512_loadu_epi16(b);
1220
+ a += 32, b += 32, n -= 32;
1221
+ }
1222
+ // Let's perform the subtraction with single-precision, while the dot-product with half-precision.
1223
+ // For that we need to perform a couple of casts - each is a bitshift. To convert `bf16` to `f32`,
1224
+ // expand it to 32-bit integers, then shift the bits by 16 to the left. Then subtract as floats,
1225
+ // and shift back. During expansion, we will double the space, and should use separate registers
1226
+ // for top and bottom halves.
1227
+ a_f32_bot_vec =
1228
+ _mm512_castsi512_ps(_mm512_slli_epi32(_mm512_cvtepu16_epi32(_mm512_castsi512_si256(a_i16_vec)), 16));
1229
+ b_f32_bot_vec =
1230
+ _mm512_castsi512_ps(_mm512_slli_epi32(_mm512_cvtepu16_epi32(_mm512_castsi512_si256(b_i16_vec)), 16));
1231
+
1232
+ // Some compilers don't have `_mm512_extracti32x8_epi32`, so we need to use `_mm512_extracti64x4_epi64`
1233
+ a_f32_top_vec =
1234
+ _mm512_castsi512_ps(_mm512_slli_epi32(_mm512_cvtepu16_epi32(_mm512_extracti64x4_epi64(a_i16_vec, 1)), 16));
1235
+ b_f32_top_vec =
1236
+ _mm512_castsi512_ps(_mm512_slli_epi32(_mm512_cvtepu16_epi32(_mm512_extracti64x4_epi64(b_i16_vec, 1)), 16));
1237
+
1238
+ // Subtract and cast back
1239
+ d_top_vec = _mm512_sub_ps(a_f32_top_vec, b_f32_top_vec);
1240
+ d_bot_vec = _mm512_sub_ps(a_f32_bot_vec, b_f32_bot_vec);
1241
+ d_top_vec = _mm512_castsi512_ps(_mm512_srli_epi32(_mm512_castps_si512(d_top_vec), 16));
1242
+ d_bot_vec = _mm512_castsi512_ps(_mm512_srli_epi32(_mm512_castps_si512(d_bot_vec), 16));
1243
+
1244
+ // Square and accumulate
1245
+ d2_top_vec = _mm512_dpbf16_ps(d2_top_vec, (__m512bh)(d_top_vec), (__m512bh)(d_top_vec));
1246
+ d2_bot_vec = _mm512_dpbf16_ps(d2_bot_vec, (__m512bh)(d_bot_vec), (__m512bh)(d_bot_vec));
1247
+ if (n)
1248
+ goto simsimd_l2sq_bf16_genoa_cycle;
1249
+
1250
+ *result = _mm512_reduce_add_ps(d2_top_vec) + _mm512_reduce_add_ps(d2_bot_vec);
1251
+ }
1252
+
1253
+ SIMSIMD_PUBLIC void simsimd_cos_bf16_genoa(simsimd_bf16_t const* a, simsimd_bf16_t const* b, simsimd_size_t n,
1254
+ simsimd_distance_t* result) {
1255
+ __m512 ab_vec = _mm512_setzero_ps();
1256
+ __m512 a2_vec = _mm512_setzero_ps();
1257
+ __m512 b2_vec = _mm512_setzero_ps();
1258
+ __m512i a_i16_vec, b_i16_vec;
1259
+
1260
+ simsimd_cos_bf16_genoa_cycle:
1261
+ if (n < 32) {
1262
+ __mmask32 mask = (__mmask32)_bzhi_u32(0xFFFFFFFF, n);
1263
+ a_i16_vec = _mm512_maskz_loadu_epi16(mask, a);
1264
+ b_i16_vec = _mm512_maskz_loadu_epi16(mask, b);
1265
+ n = 0;
1266
+ } else {
1267
+ a_i16_vec = _mm512_loadu_epi16(a);
1268
+ b_i16_vec = _mm512_loadu_epi16(b);
1269
+ a += 32, b += 32, n -= 32;
1270
+ }
1271
+ ab_vec = _mm512_dpbf16_ps(ab_vec, (__m512bh)(a_i16_vec), (__m512bh)(b_i16_vec));
1272
+ a2_vec = _mm512_dpbf16_ps(a2_vec, (__m512bh)(a_i16_vec), (__m512bh)(a_i16_vec));
1273
+ b2_vec = _mm512_dpbf16_ps(b2_vec, (__m512bh)(b_i16_vec), (__m512bh)(b_i16_vec));
1274
+ if (n)
1275
+ goto simsimd_cos_bf16_genoa_cycle;
1276
+
1277
+ simsimd_f32_t ab = _mm512_reduce_add_ps(ab_vec);
1278
+ simsimd_f32_t a2 = _mm512_reduce_add_ps(a2_vec);
1279
+ simsimd_f32_t b2 = _mm512_reduce_add_ps(b2_vec);
1280
+
1281
+ // Compute the reciprocal square roots of a2 and b2
1282
+ __m128 rsqrts = _mm_rsqrt14_ps(_mm_set_ps(0.f, 0.f, a2 + 1.e-9f, b2 + 1.e-9f));
1283
+ simsimd_f32_t rsqrt_a2 = _mm_cvtss_f32(rsqrts);
1284
+ simsimd_f32_t rsqrt_b2 = _mm_cvtss_f32(_mm_shuffle_ps(rsqrts, rsqrts, _MM_SHUFFLE(0, 0, 0, 1)));
1285
+ *result = ab != 0 ? 1 - ab * rsqrt_a2 * rsqrt_b2 : 0;
1286
+ }
1287
+
1288
+ #pragma clang attribute pop
1289
+ #pragma GCC pop_options
1290
+ #endif // SIMSIMD_TARGET_GENOA
1291
+
871
1292
  #if SIMSIMD_TARGET_SAPPHIRE
872
1293
  #pragma GCC push_options
873
1294
  #pragma GCC target("avx512f", "avx512vl", "bmi2", "avx512fp16")
@@ -929,7 +1350,7 @@ simsimd_cos_f16_sapphire_cycle:
929
1350
  __m128 rsqrts = _mm_rsqrt14_ps(_mm_set_ps(0.f, 0.f, a2 + 1.e-9f, b2 + 1.e-9f));
930
1351
  simsimd_f32_t rsqrt_a2 = _mm_cvtss_f32(rsqrts);
931
1352
  simsimd_f32_t rsqrt_b2 = _mm_cvtss_f32(_mm_shuffle_ps(rsqrts, rsqrts, _MM_SHUFFLE(0, 0, 0, 1)));
932
- *result = 1 - ab * rsqrt_a2 * rsqrt_b2;
1353
+ *result = ab != 0 ? 1 - ab * rsqrt_a2 * rsqrt_b2 : 0;
933
1354
  }
934
1355
 
935
1356
  #pragma clang attribute pop
@@ -967,36 +1388,51 @@ simsimd_l2sq_i8_ice_cycle:
967
1388
 
968
1389
  SIMSIMD_PUBLIC void simsimd_cos_i8_ice(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t n,
969
1390
  simsimd_distance_t* result) {
970
- __m512i ab_i32s_vec = _mm512_setzero_si512();
1391
+ __m512i ab_low_i32s_vec = _mm512_setzero_si512();
1392
+ __m512i ab_high_i32s_vec = _mm512_setzero_si512();
971
1393
  __m512i a2_i32s_vec = _mm512_setzero_si512();
972
1394
  __m512i b2_i32s_vec = _mm512_setzero_si512();
973
1395
  __m512i a_vec, b_vec;
1396
+ __m512i a_abs_vec, b_abs_vec;
974
1397
 
975
1398
  simsimd_cos_i8_ice_cycle:
976
- if (n < 32) {
977
- __mmask32 mask = (__mmask32)_bzhi_u32(0xFFFFFFFF, n);
978
- a_vec = _mm512_cvtepi8_epi16(_mm256_maskz_loadu_epi8(mask, a));
979
- b_vec = _mm512_cvtepi8_epi16(_mm256_maskz_loadu_epi8(mask, b));
1399
+ if (n < 64) {
1400
+ __mmask64 mask = (__mmask64)_bzhi_u64(0xFFFFFFFFFFFFFFFF, n);
1401
+ a_vec = _mm512_maskz_loadu_epi8(mask, a);
1402
+ b_vec = _mm512_maskz_loadu_epi8(mask, b);
980
1403
  n = 0;
981
1404
  } else {
982
- a_vec = _mm512_cvtepi8_epi16(_mm256_loadu_epi8(a));
983
- b_vec = _mm512_cvtepi8_epi16(_mm256_loadu_epi8(b));
984
- a += 32, b += 32, n -= 32;
1405
+ a_vec = _mm512_loadu_epi8(a);
1406
+ b_vec = _mm512_loadu_epi8(b);
1407
+ a += 64, b += 64, n -= 64;
985
1408
  }
986
- // Unfortunately we can't use the `_mm512_dpbusd_epi32` intrinsics here either,
987
- // as it's assymetric with respect to the sign of the input arguments:
1409
+
1410
+ // We can't directly use the `_mm512_dpbusd_epi32` intrinsic everywhere,
1411
+ // as it's asymmetric with respect to the sign of the input arguments:
988
1412
  // Signed(ZeroExtend16(a.byte[4*j]) * SignExtend16(b.byte[4*j]))
989
- // So we have to use the `_mm512_dpwssd_epi32` intrinsics instead, upcasting
990
- // to 16-bit beforehand.
991
- ab_i32s_vec = _mm512_dpwssd_epi32(ab_i32s_vec, a_vec, b_vec);
992
- a2_i32s_vec = _mm512_dpwssd_epi32(a2_i32s_vec, a_vec, a_vec);
993
- b2_i32s_vec = _mm512_dpwssd_epi32(b2_i32s_vec, b_vec, b_vec);
1413
+ // Luckily to compute the squares, we just drop the sign bit of the second argument.
1414
+ a_abs_vec = _mm512_abs_epi8(a_vec);
1415
+ b_abs_vec = _mm512_abs_epi8(b_vec);
1416
+ a2_i32s_vec = _mm512_dpbusds_epi32(a2_i32s_vec, a_abs_vec, a_abs_vec);
1417
+ b2_i32s_vec = _mm512_dpbusds_epi32(b2_i32s_vec, b_abs_vec, b_abs_vec);
1418
+
1419
+ // The same trick won't work for the primary dot-product, as the signs vector
1420
+ // components may differ significantly. So we have to use two `_mm512_dpwssd_epi32`
1421
+ // intrinsics instead, upcasting four chunks to 16-bit integers beforehand!
1422
+ ab_low_i32s_vec = _mm512_dpwssds_epi32( //
1423
+ ab_low_i32s_vec, //
1424
+ _mm512_cvtepi8_epi16(_mm512_castsi512_si256(a_vec)), //
1425
+ _mm512_cvtepi8_epi16(_mm512_castsi512_si256(b_vec)));
1426
+ ab_high_i32s_vec = _mm512_dpwssds_epi32( //
1427
+ ab_high_i32s_vec, //
1428
+ _mm512_cvtepi8_epi16(_mm512_extracti64x4_epi64(a_vec, 1)), //
1429
+ _mm512_cvtepi8_epi16(_mm512_extracti64x4_epi64(b_vec, 1)));
994
1430
  if (n)
995
1431
  goto simsimd_cos_i8_ice_cycle;
996
1432
 
997
- simsimd_f32_t ab = _mm512_reduce_add_epi32(ab_i32s_vec);
998
- simsimd_f32_t a2 = _mm512_reduce_add_epi32(a2_i32s_vec);
999
- simsimd_f32_t b2 = _mm512_reduce_add_epi32(b2_i32s_vec);
1433
+ int ab = _mm512_reduce_add_epi32(_mm512_add_epi32(ab_low_i32s_vec, ab_high_i32s_vec));
1434
+ int a2 = _mm512_reduce_add_epi32(a2_i32s_vec);
1435
+ int b2 = _mm512_reduce_add_epi32(b2_i32s_vec);
1000
1436
 
1001
1437
  // Compute the reciprocal square roots of a2 and b2
1002
1438
  // Mysteriously, MSVC has no `_mm_rsqrt14_ps` intrinsic, but has it's masked variants,
@@ -1004,7 +1440,7 @@ simsimd_cos_i8_ice_cycle:
1004
1440
  __m128 rsqrts = _mm_maskz_rsqrt14_ps(0xFF, _mm_set_ps(0.f, 0.f, a2 + 1.e-9f, b2 + 1.e-9f));
1005
1441
  simsimd_f32_t rsqrt_a2 = _mm_cvtss_f32(rsqrts);
1006
1442
  simsimd_f32_t rsqrt_b2 = _mm_cvtss_f32(_mm_shuffle_ps(rsqrts, rsqrts, _MM_SHUFFLE(0, 0, 0, 1)));
1007
- *result = 1 - ab * rsqrt_a2 * rsqrt_b2;
1443
+ *result = ab != 0 ? 1 - ab * rsqrt_a2 * rsqrt_b2 : 0;
1008
1444
  }
1009
1445
 
1010
1446
  #pragma clang attribute pop