usearch 2.9.0 → 2.9.2

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 (46) hide show
  1. package/CITATION.cff +1 -1
  2. package/CONTRIBUTING.md +36 -2
  3. package/Cargo.toml +1 -1
  4. package/README.md +12 -15
  5. package/VERSION +1 -1
  6. package/c/lib.cpp +19 -17
  7. package/c/test.c +80 -38
  8. package/c/usearch.h +15 -15
  9. package/conanfile.py +1 -1
  10. package/include/usearch/index.hpp +5 -5
  11. package/include/usearch/index_dense.hpp +8 -4
  12. package/include/usearch/index_plugins.hpp +52 -32
  13. package/javascript/types/usearch.d.ts.map +1 -1
  14. package/javascript/usearch.js +4 -1
  15. package/package.json +3 -2
  16. package/simsimd/CONTRIBUTING.md +88 -0
  17. package/simsimd/Cargo.lock +4 -3
  18. package/simsimd/Cargo.toml +10 -2
  19. package/simsimd/README.md +102 -60
  20. package/simsimd/include/simsimd/binary.h +44 -0
  21. package/simsimd/include/simsimd/complex.h +174 -0
  22. package/simsimd/include/simsimd/simsimd.h +18 -9
  23. package/simsimd/include/simsimd/spatial.h +128 -36
  24. package/simsimd/include/simsimd/types.h +3 -2
  25. package/simsimd/package-lock.json +5 -5
  26. package/simsimd/package.json +1 -1
  27. package/simsimd/rust/benches/native.rs +12 -9
  28. package/simsimd/rust/lib.c +32 -55
  29. package/simsimd/rust/lib.rs +374 -56
  30. package/stringzilla/CONTRIBUTING.md +17 -0
  31. package/stringzilla/Cargo.lock +1 -1
  32. package/stringzilla/Cargo.toml +1 -1
  33. package/stringzilla/README.md +175 -34
  34. package/stringzilla/VERSION +1 -1
  35. package/stringzilla/build.rs +77 -4
  36. package/stringzilla/c/lib.c +33 -0
  37. package/stringzilla/cli/README.md +30 -13
  38. package/stringzilla/cli/__init__.py +0 -0
  39. package/stringzilla/cli/split.py +66 -33
  40. package/stringzilla/cli/wc.py +101 -21
  41. package/stringzilla/include/stringzilla/experimental.h +122 -1
  42. package/stringzilla/include/stringzilla/stringzilla.h +262 -132
  43. package/stringzilla/include/stringzilla/stringzilla.hpp +13 -5
  44. package/stringzilla/package.json +1 -1
  45. package/stringzilla/rust/lib.rs +673 -162
  46. package/wasmer.toml +1 -1
@@ -0,0 +1,174 @@
1
+ /**
2
+ * @brief SIMD-accelerated Similarity Measures for Complex Vectors.
3
+ * @author Ash Vardanian
4
+ * @date February 24, 2024
5
+ *
6
+ * Contains:
7
+ * - Inner Product
8
+ * - Cosine Distance
9
+ *
10
+ * For datatypes:
11
+ * - 32-bit floating point numbers
12
+ * - 16-bit floating point numbers
13
+ *
14
+ * For hardware architectures:
15
+ * - Arm (NEON, SVE?)
16
+ * - x86 (AVX2?, AVX512?)
17
+ *
18
+ * x86 intrinsics: https://www.intel.com/content/www/us/en/docs/intrinsics-guide/
19
+ * Arm intrinsics: https://developer.arm.com/architectures/instruction-sets/intrinsics/
20
+ */
21
+ #ifndef SIMSIMD_COMPLEX_H
22
+ #define SIMSIMD_COMPLEX_H
23
+
24
+ #include "types.h"
25
+
26
+ #define SIMSIMD_MAKE_COMPLEX_IP(name, input_type, accumulator_type, converter, epsilon) \
27
+ inline static simsimd_f32_t simsimd_##name##_##input_type##_complex_ip( \
28
+ simsimd_##input_type##_t const* a, simsimd_##input_type##_t const* b, simsimd_size_t n, \
29
+ simsimd_f32_t* real_out, simsimd_f32_t* imag_out) { \
30
+ simsimd_##accumulator_type##_t ab_real, ab_imag; \
31
+ for (simsimd_size_t i = 0; i + 2 <= n; i += 2) { \
32
+ simsimd_##accumulator_type##_t ar = converter(a[i]); \
33
+ simsimd_##accumulator_type##_t br = converter(b[i]); \
34
+ simsimd_##accumulator_type##_t ai = converter(a[i + 1]); \
35
+ simsimd_##accumulator_type##_t bi = converter(b[i + 1]); \
36
+ ab_real += ar * br + ai * bi; \
37
+ ab_imag += ai * br - ar * bi; \
38
+ } \
39
+ *real_out = ab_real; \
40
+ *imag_out = ab_imag; \
41
+ return 1 - ab_real; \
42
+ }
43
+
44
+ #define SIMSIMD_MAKE_COMPLEX_COS(name, input_type, accumulator_type, converter, epsilon) \
45
+ inline static simsimd_f32_t simsimd_##name##_##input_type##_complex_cos( \
46
+ simsimd_##input_type##_t const* a, simsimd_##input_type##_t const* b, simsimd_size_t n, \
47
+ simsimd_f32_t* real_out, simsimd_f32_t* imag_out) { \
48
+ simsimd_##accumulator_type##_t ab_real, ab_imag, a2_sum, b2_sum; \
49
+ for (simsimd_size_t i = 0; i + 2 <= n; i += 2) { \
50
+ simsimd_##accumulator_type##_t ar = converter(a[i]); \
51
+ simsimd_##accumulator_type##_t br = converter(b[i]); \
52
+ simsimd_##accumulator_type##_t ai = converter(a[i + 1]); \
53
+ simsimd_##accumulator_type##_t bi = converter(b[i + 1]); \
54
+ ab_real += ar * br + ai * bi; \
55
+ ab_imag += ai * br - ar * bi; \
56
+ a2_sum += ar * ar + ai * ai; \
57
+ b2_sum += br * br + bi * bi; \
58
+ } \
59
+ *real_out = ab_real; \
60
+ *imag_out = ab_imag; \
61
+ simsimd_##accumulator_type##_t ab_angle = ab_real * SIMSIMD_RSQRT(a2_sum) * SIMSIMD_RSQRT(b2_sum); \
62
+ return 1 - ab_angle; \
63
+ }
64
+
65
+ #ifdef __cplusplus
66
+ extern "C" {
67
+ #endif
68
+
69
+ SIMSIMD_MAKE_COMPLEX_IP(serial, f64, f64, SIMSIMD_IDENTIFY,
70
+ SIMSIMD_F32_DIVISION_EPSILON) // simsimd_serial_f64_complex_ip
71
+ SIMSIMD_MAKE_COMPLEX_COS(serial, f64, f64, SIMSIMD_IDENTIFY,
72
+ SIMSIMD_F32_DIVISION_EPSILON) // simsimd_serial_f64_complex_cos
73
+
74
+ SIMSIMD_MAKE_COMPLEX_IP(serial, f32, f32, SIMSIMD_IDENTIFY,
75
+ SIMSIMD_F32_DIVISION_EPSILON) // simsimd_serial_f32_complex_ip
76
+ SIMSIMD_MAKE_COMPLEX_COS(serial, f32, f32, SIMSIMD_IDENTIFY,
77
+ SIMSIMD_F32_DIVISION_EPSILON) // simsimd_serial_f32_complex_cos
78
+
79
+ SIMSIMD_MAKE_COMPLEX_IP(serial, f16, f32, SIMSIMD_UNCOMPRESS_F16,
80
+ SIMSIMD_F32_DIVISION_EPSILON) // simsimd_serial_f16_complex_ip
81
+ SIMSIMD_MAKE_COMPLEX_COS(serial, f16, f32, SIMSIMD_UNCOMPRESS_F16,
82
+ SIMSIMD_F32_DIVISION_EPSILON) // simsimd_serial_f16_complex_cos
83
+
84
+ SIMSIMD_MAKE_COMPLEX_IP(accurate, f32, f64, SIMSIMD_IDENTIFY,
85
+ SIMSIMD_F32_DIVISION_EPSILON) // simsimd_accurate_f32_complex_ip
86
+ SIMSIMD_MAKE_COMPLEX_COS(accurate, f32, f64, SIMSIMD_IDENTIFY,
87
+ SIMSIMD_F32_DIVISION_EPSILON) // simsimd_accurate_f32_complex_cos
88
+
89
+ SIMSIMD_MAKE_COMPLEX_IP(accurate, f16, f64, SIMSIMD_UNCOMPRESS_F16,
90
+ SIMSIMD_F32_DIVISION_EPSILON) // simsimd_accurate_f16_complex_ip
91
+ SIMSIMD_MAKE_COMPLEX_COS(accurate, f16, f64, SIMSIMD_UNCOMPRESS_F16,
92
+ SIMSIMD_F32_DIVISION_EPSILON) // simsimd_accurate_f16_complex_cos
93
+
94
+ #if SIMSIMD_TARGET_ARM
95
+ #if SIMSIMD_TARGET_ARM_NEON
96
+
97
+ /*
98
+ * @file arm_neon_f32.h
99
+ * @brief Arm NEON implementation of the most common similarity metrics for 32-bit floating point numbers.
100
+ * @author Ash Vardanian
101
+ *
102
+ * - Implements: Kullback-Leibler and Jensen–Shannon divergence.
103
+ * - Uses `f32` for storage and `f32` for accumulation.
104
+ * - Requires compiler capabilities: +simd.
105
+ */
106
+
107
+ __attribute__((target("+simd"))) //
108
+ inline static simsimd_f32_t
109
+ simsimd_neon_f32_complex_cos(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n, //
110
+ simsimd_f32_t* real_out, simsimd_f32_t* imag_out) {
111
+ float32x4_t ab_real_vec = vdupq_n_f32(0);
112
+ float32x4_t ab_imag_vec = vdupq_n_f32(0);
113
+ float32x4_t a2_vec = vdupq_n_f32(0);
114
+ float32x4_t b2_vec = vdupq_n_f32(0);
115
+ simsimd_size_t i = 0;
116
+ for (; i + 8 <= n; i += 8) {
117
+ // Unpack the input arrays into real and imaginary parts:
118
+ // a_real = a[::2]
119
+ // a_imag = a[1::2]
120
+ // b_real = b[::2]
121
+ // b_imag = b[1::2]
122
+ float32x4x2_t a_vec = vld2q_f32(a + i);
123
+ float32x4x2_t b_vec = vld2q_f32(b + i);
124
+ float32x4_t a_real_vec = a_vec.val[0];
125
+ float32x4_t a_imag_vec = a_vec.val[1];
126
+ float32x4_t b_real_vec = b_vec.val[0];
127
+ float32x4_t b_imag_vec = b_vec.val[1];
128
+
129
+ // Compute the dot product:
130
+ // ab_real = sum(ar * br + ai * bi for ar, ai, br, bi in zip(a_real, a_imag, b_real, b_imag))
131
+ // ab_imag = sum(ai * br - ar * bi for ar, ai, br, bi in zip(a_real, a_imag, b_real, b_imag))
132
+ ab_real_vec = vfmaq_f32(ab_real_vec, a_real_vec, b_real_vec);
133
+ ab_real_vec = vfmaq_f32(ab_real_vec, a_imag_vec, b_imag_vec);
134
+ ab_imag_vec = vfmaq_f32(ab_imag_vec, a_imag_vec, b_real_vec);
135
+ ab_imag_vec = vfmsq_f32(ab_imag_vec, a_real_vec, b_imag_vec);
136
+
137
+ // Compute the vector norms:
138
+ // a2 = sum(ar * ar + ai * ai for ar, ai in zip(a_real, a_imag))
139
+ // b2 = sum(br * br + bi * bi for br, bi in zip(b_real, b_imag))
140
+ a2_vec = vfmaq_f32(a2_vec, a_real_vec, a_real_vec);
141
+ a2_vec = vfmaq_f32(a2_vec, a_imag_vec, a_imag_vec);
142
+ b2_vec = vfmaq_f32(b2_vec, b_real_vec, b_real_vec);
143
+ b2_vec = vfmaq_f32(b2_vec, b_imag_vec, b_imag_vec);
144
+ }
145
+
146
+ // Reduce horizontal sums:
147
+ simsimd_f32_t ab_real = vaddvq_f32(ab_real_vec);
148
+ simsimd_f32_t ab_imag = vaddvq_f32(ab_imag_vec);
149
+ simsimd_f32_t a2 = vaddvq_f32(a2_vec);
150
+ simsimd_f32_t b2 = vaddvq_f32(b2_vec);
151
+
152
+ // Handle the tail:
153
+ for (; i < n; i += 2) {
154
+ simsimd_f32_t ar = a[i], ai = a[i + 1], br = b[i], bi = b[i + 1];
155
+ ab_real += ar * br + ai * bi, ab_imag += ai * br - ar * bi;
156
+ a2 += ar * ar + ai * ai, b2 += br * br + bi * bi;
157
+ }
158
+ *real_out = ab_real;
159
+ *imag_out = ab_imag;
160
+
161
+ // Avoid `simsimd_approximate_inverse_square_root` on Arm NEON
162
+ simsimd_f32_t a2_b2_arr[2] = {a2, b2};
163
+ vst1_f32(a2_b2_arr, vrsqrte_f32(vld1_f32(a2_b2_arr)));
164
+ return ab_real != 0 ? 1 - ab_real * a2_b2_arr[0] * a2_b2_arr[1] : 1;
165
+ }
166
+
167
+ #endif // SIMSIMD_TARGET_ARM_NEON
168
+ #endif // SIMSIMD_TARGET_ARM
169
+
170
+ #ifdef __cplusplus
171
+ }
172
+ #endif
173
+
174
+ #endif
@@ -15,10 +15,11 @@
15
15
  #define SIMSIMD_H
16
16
 
17
17
  #define SIMSIMD_VERSION_MAJOR 3
18
- #define SIMSIMD_VERSION_MINOR 8
18
+ #define SIMSIMD_VERSION_MINOR 9
19
19
  #define SIMSIMD_VERSION_PATCH 0
20
20
 
21
21
  #include "binary.h" // Hamming, Jaccard
22
+ #include "complex.h" // Complex numbers
22
23
  #include "probability.h" // Kullback-Leibler, Jensen–Shannon
23
24
  #include "spatial.h" // L2, Inner Product, Cosine
24
25
 
@@ -388,14 +389,6 @@ inline static void simsimd_find_metric_punned( //
388
389
  // Binary vectors
389
390
  case simsimd_datatype_b8_k:
390
391
 
391
- #if SIMSIMD_TARGET_ARM_NEON
392
- if (viable & simsimd_cap_arm_neon_k)
393
- switch (kind) {
394
- case simsimd_metric_hamming_k: *m = (simsimd_metric_punned_t)&simsimd_neon_b8_hamming, *c = simsimd_cap_arm_neon_k; return;
395
- case simsimd_metric_jaccard_k: *m = (simsimd_metric_punned_t)&simsimd_neon_b8_jaccard, *c = simsimd_cap_arm_neon_k; return;
396
- default: break;
397
- }
398
- #endif
399
392
  #if SIMSIMD_TARGET_ARM_SVE
400
393
  if (viable & simsimd_cap_arm_sve_k)
401
394
  switch (kind) {
@@ -404,6 +397,14 @@ inline static void simsimd_find_metric_punned( //
404
397
  default: break;
405
398
  }
406
399
  #endif
400
+ #if SIMSIMD_TARGET_ARM_NEON
401
+ if (viable & simsimd_cap_arm_neon_k)
402
+ switch (kind) {
403
+ case simsimd_metric_hamming_k: *m = (simsimd_metric_punned_t)&simsimd_neon_b8_hamming, *c = simsimd_cap_arm_neon_k; return;
404
+ case simsimd_metric_jaccard_k: *m = (simsimd_metric_punned_t)&simsimd_neon_b8_jaccard, *c = simsimd_cap_arm_neon_k; return;
405
+ default: break;
406
+ }
407
+ #endif
407
408
  #if SIMSIMD_TARGET_X86_AVX512
408
409
  if (viable & simsimd_cap_x86_avx512vpopcntdq_k)
409
410
  switch (kind) {
@@ -412,6 +413,14 @@ inline static void simsimd_find_metric_punned( //
412
413
  default: break;
413
414
  }
414
415
  #endif
416
+ #if SIMSIMD_TARGET_X86_AVX2
417
+ if (viable & simsimd_cap_x86_avx2_k)
418
+ switch (kind) {
419
+ case simsimd_metric_hamming_k: *m = (simsimd_metric_punned_t)&simsimd_avx2_b8_hamming, *c = simsimd_cap_x86_avx2_k; return;
420
+ case simsimd_metric_jaccard_k: *m = (simsimd_metric_punned_t)&simsimd_avx2_b8_jaccard, *c = simsimd_cap_x86_avx2_k; return;
421
+ default: break;
422
+ }
423
+ #endif
415
424
 
416
425
  if (viable & simsimd_cap_serial_k)
417
426
  switch (kind) {
@@ -194,11 +194,25 @@ simsimd_neon_f16_l2sq(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_si
194
194
  float32x4_t diff_vec = vsubq_f32(a_vec, b_vec);
195
195
  sum_vec = vfmaq_f32(sum_vec, diff_vec, diff_vec);
196
196
  }
197
- simsimd_f32_t sum = vaddvq_f32(sum_vec);
198
- for (; i < n; ++i) {
199
- simsimd_f32_t diff = SIMSIMD_UNCOMPRESS_F16(a[i]) - SIMSIMD_UNCOMPRESS_F16(b[i]);
200
- sum += diff * diff;
197
+
198
+ // In case the software emulation for `f16` scalars is enabled, the `simsimd_uncompress_f16`
199
+ // function will run. It is extremely slow, so even for the tail, let's combine serial
200
+ // loads and stores with vectorized math.
201
+ if (i < n) {
202
+ union {
203
+ float16x4_t f16_vec;
204
+ simsimd_f16_t f16[4];
205
+ } a_padded_tail, b_padded_tail;
206
+ simsimd_size_t j = 0;
207
+ for (; i < n; ++i, ++j)
208
+ a_padded_tail.f16[j] = a[i], b_padded_tail.f16[j] = b[i];
209
+ for (; j < 4; ++j)
210
+ a_padded_tail.f16[j] = 0, b_padded_tail.f16[j] = 0;
211
+ float32x4_t diff_vec = vsubq_f32(vcvt_f32_f16(a_padded_tail.f16_vec), vcvt_f32_f16(b_padded_tail.f16_vec));
212
+ sum_vec = vfmaq_f32(sum_vec, diff_vec, diff_vec);
201
213
  }
214
+
215
+ simsimd_f32_t sum = vaddvq_f32(sum_vec);
202
216
  return sum;
203
217
  }
204
218
 
@@ -212,9 +226,23 @@ simsimd_neon_f16_ip(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size
212
226
  float32x4_t b_vec = vcvt_f32_f16(vld1_f16((float16_t const*)b + i));
213
227
  ab_vec = vfmaq_f32(ab_vec, a_vec, b_vec);
214
228
  }
229
+
230
+ // In case the software emulation for `f16` scalars is enabled, the `simsimd_uncompress_f16`
231
+ // function will run. It is extremely slow, so even for the tail, let's combine serial
232
+ // loads and stores with vectorized math.
233
+ if (i < n) {
234
+ union {
235
+ float16x4_t f16_vec;
236
+ simsimd_f16_t f16[4];
237
+ } a_padded_tail, b_padded_tail;
238
+ simsimd_size_t j = 0;
239
+ for (; i < n; ++i, ++j)
240
+ a_padded_tail.f16[j] = a[i], b_padded_tail.f16[j] = b[i];
241
+ for (; j < 4; ++j)
242
+ a_padded_tail.f16[j] = 0, b_padded_tail.f16[j] = 0;
243
+ ab_vec = vfmaq_f32(ab_vec, vcvt_f32_f16(a_padded_tail.f16_vec), vcvt_f32_f16(b_padded_tail.f16_vec));
244
+ }
215
245
  simsimd_f32_t ab = vaddvq_f32(ab_vec);
216
- for (; i < n; ++i)
217
- ab += SIMSIMD_UNCOMPRESS_F16(a[i]) * SIMSIMD_UNCOMPRESS_F16(b[i]);
218
246
  return 1 - ab;
219
247
  }
220
248
 
@@ -230,13 +258,29 @@ simsimd_neon_f16_cos(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_siz
230
258
  a2_vec = vfmaq_f32(a2_vec, a_vec, a_vec);
231
259
  b2_vec = vfmaq_f32(b2_vec, b_vec, b_vec);
232
260
  }
233
- simsimd_f32_t ab = vaddvq_f32(ab_vec), a2 = vaddvq_f32(a2_vec), b2 = vaddvq_f32(b2_vec);
234
- for (; i < n; ++i) {
235
- simsimd_f32_t ai = SIMSIMD_UNCOMPRESS_F16(a[i]), bi = SIMSIMD_UNCOMPRESS_F16(b[i]);
236
- ab += ai * bi, a2 += ai * ai, b2 += bi * bi;
261
+
262
+ // In case the software emulation for `f16` scalars is enabled, the `simsimd_uncompress_f16`
263
+ // function will run. It is extremely slow, so even for the tail, let's combine serial
264
+ // loads and stores with vectorized math.
265
+ if (i < n) {
266
+ union {
267
+ float16x4_t f16_vec;
268
+ simsimd_f16_t f16[4];
269
+ } a_padded_tail, b_padded_tail;
270
+ simsimd_size_t j = 0;
271
+ for (; i < n; ++i, ++j)
272
+ a_padded_tail.f16[j] = a[i], b_padded_tail.f16[j] = b[i];
273
+ for (; j < 4; ++j)
274
+ a_padded_tail.f16[j] = 0, b_padded_tail.f16[j] = 0;
275
+ float32x4_t a_vec = vcvt_f32_f16(a_padded_tail.f16_vec);
276
+ float32x4_t b_vec = vcvt_f32_f16(b_padded_tail.f16_vec);
277
+ ab_vec = vfmaq_f32(ab_vec, a_vec, b_vec);
278
+ a2_vec = vfmaq_f32(a2_vec, a_vec, a_vec);
279
+ b2_vec = vfmaq_f32(b2_vec, b_vec, b_vec);
237
280
  }
238
281
 
239
282
  // Avoid `simsimd_approximate_inverse_square_root` on Arm NEON
283
+ simsimd_f32_t ab = vaddvq_f32(ab_vec), a2 = vaddvq_f32(a2_vec), b2 = vaddvq_f32(b2_vec);
240
284
  simsimd_f32_t a2_b2_arr[2] = {a2, b2};
241
285
  float32x2_t a2_b2 = vld1_f32(a2_b2_arr);
242
286
  a2_b2 = vrsqrte_f32(a2_b2);
@@ -582,18 +626,31 @@ simsimd_avx2_f16_l2sq(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_si
582
626
  d2_vec = _mm256_fmadd_ps(d_vec, d_vec, d2_vec);
583
627
  }
584
628
 
629
+ // In case the software emulation for `f16` scalars is enabled, the `simsimd_uncompress_f16`
630
+ // function will run. It is extremely slow, so even for the tail, let's combine serial
631
+ // loads and stores with vectorized math.
632
+ if (i < n) {
633
+ union {
634
+ __m128i f16_vec;
635
+ simsimd_f16_t f16[8];
636
+ } a_padded_tail, b_padded_tail;
637
+ simsimd_size_t j = 0;
638
+ for (; i < n; ++i, ++j)
639
+ a_padded_tail.f16[j] = a[i], b_padded_tail.f16[j] = b[i];
640
+ for (; j < 8; ++j)
641
+ a_padded_tail.f16[j] = 0, b_padded_tail.f16[j] = 0;
642
+ __m256 a_vec = _mm256_cvtph_ps(a_padded_tail.f16_vec);
643
+ __m256 b_vec = _mm256_cvtph_ps(b_padded_tail.f16_vec);
644
+ __m256 d_vec = _mm256_sub_ps(a_vec, b_vec);
645
+ d2_vec = _mm256_fmadd_ps(d_vec, d_vec, d2_vec);
646
+ }
647
+
585
648
  d2_vec = _mm256_add_ps(_mm256_permute2f128_ps(d2_vec, d2_vec, 1), d2_vec);
586
649
  d2_vec = _mm256_hadd_ps(d2_vec, d2_vec);
587
650
  d2_vec = _mm256_hadd_ps(d2_vec, d2_vec);
588
651
 
589
652
  simsimd_f32_t result;
590
653
  _mm_store_ss(&result, _mm256_castps256_ps128(d2_vec));
591
-
592
- // Accumulate the tail:
593
- for (; i < n; ++i) {
594
- simsimd_f32_t n = SIMSIMD_UNCOMPRESS_F16(a[i]) - SIMSIMD_UNCOMPRESS_F16(b[i]);
595
- result += n * n;
596
- }
597
654
  return result;
598
655
  }
599
656
 
@@ -608,18 +665,33 @@ simsimd_avx2_f16_ip(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size
608
665
  ab_vec = _mm256_fmadd_ps(a_vec, b_vec, ab_vec);
609
666
  }
610
667
 
668
+ // In case the software emulation for `f16` scalars is enabled, the `simsimd_uncompress_f16`
669
+ // function will run. It is extremely slow, so even for the tail, let's combine serial
670
+ // loads and stores with vectorized math.
671
+ if (i < n) {
672
+ union {
673
+ __m128i f16_vec;
674
+ simsimd_f16_t f16[8];
675
+ } a_padded_tail, b_padded_tail;
676
+ simsimd_size_t j = 0;
677
+ for (; i < n; ++i, ++j)
678
+ a_padded_tail.f16[j] = a[i], b_padded_tail.f16[j] = b[i];
679
+ for (; j < 8; ++j)
680
+ a_padded_tail.f16[j] = 0, b_padded_tail.f16[j] = 0;
681
+ __m256 a_vec = _mm256_cvtph_ps(a_padded_tail.f16_vec);
682
+ __m256 b_vec = _mm256_cvtph_ps(b_padded_tail.f16_vec);
683
+ ab_vec = _mm256_fmadd_ps(a_vec, b_vec, ab_vec);
684
+ }
685
+
611
686
  ab_vec = _mm256_add_ps(_mm256_permute2f128_ps(ab_vec, ab_vec, 1), ab_vec);
612
687
  ab_vec = _mm256_hadd_ps(ab_vec, ab_vec);
613
688
  ab_vec = _mm256_hadd_ps(ab_vec, ab_vec);
614
689
 
615
690
  simsimd_f32_t result;
616
691
  _mm_store_ss(&result, _mm256_castps256_ps128(ab_vec));
617
-
618
- // Accumulate the tail:
619
- for (; i < n; ++i)
620
- result += SIMSIMD_UNCOMPRESS_F16(a[i]) * SIMSIMD_UNCOMPRESS_F16(b[i]);
621
692
  return 1 - result;
622
693
  }
694
+
623
695
  __attribute__((target("avx2,f16c,fma"))) //
624
696
  inline static simsimd_f32_t
625
697
  simsimd_avx2_f16_cos(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n) {
@@ -634,6 +706,27 @@ simsimd_avx2_f16_cos(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_siz
634
706
  b2_vec = _mm256_fmadd_ps(b_vec, b_vec, b2_vec);
635
707
  }
636
708
 
709
+ // In case the software emulation for `f16` scalars is enabled, the `simsimd_uncompress_f16`
710
+ // function will run. It is extremely slow, so even for the tail, let's combine serial
711
+ // loads and stores with vectorized math.
712
+ if (i < n) {
713
+ union {
714
+ __m128i f16_vec;
715
+ simsimd_f16_t f16[8];
716
+ } a_padded_tail, b_padded_tail;
717
+ simsimd_size_t j = 0;
718
+ for (; i < n; ++i, ++j)
719
+ a_padded_tail.f16[j] = a[i], b_padded_tail.f16[j] = b[i];
720
+ for (; j < 8; ++j)
721
+ a_padded_tail.f16[j] = 0, b_padded_tail.f16[j] = 0;
722
+ __m256 a_vec = _mm256_cvtph_ps(a_padded_tail.f16_vec);
723
+ __m256 b_vec = _mm256_cvtph_ps(b_padded_tail.f16_vec);
724
+ ab_vec = _mm256_fmadd_ps(a_vec, b_vec, ab_vec);
725
+ a2_vec = _mm256_fmadd_ps(a_vec, a_vec, a2_vec);
726
+ b2_vec = _mm256_fmadd_ps(b_vec, b_vec, b2_vec);
727
+ }
728
+
729
+ // Horizontal reductions:
637
730
  ab_vec = _mm256_add_ps(_mm256_permute2f128_ps(ab_vec, ab_vec, 1), ab_vec);
638
731
  ab_vec = _mm256_hadd_ps(ab_vec, ab_vec);
639
732
  ab_vec = _mm256_hadd_ps(ab_vec, ab_vec);
@@ -651,12 +744,6 @@ simsimd_avx2_f16_cos(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_siz
651
744
  _mm_store_ss(&a2, _mm256_castps256_ps128(a2_vec));
652
745
  _mm_store_ss(&b2, _mm256_castps256_ps128(b2_vec));
653
746
 
654
- // Accumulate the tail:
655
- for (; i < n; ++i) {
656
- simsimd_f32_t ai = SIMSIMD_UNCOMPRESS_F16(a[i]), bi = SIMSIMD_UNCOMPRESS_F16(b[i]);
657
- ab += ai * bi, a2 += ai * ai, b2 += bi * bi;
658
- }
659
-
660
747
  // Replace simsimd_approximate_inverse_square_root with `rsqrtss`
661
748
  __m128 a2_sqrt_recip = _mm_rsqrt_ss(_mm_set_ss((float)a2));
662
749
  __m128 b2_sqrt_recip = _mm_rsqrt_ss(_mm_set_ss((float)b2));
@@ -1037,19 +1124,24 @@ simsimd_avx512_i8_cos(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size
1037
1124
  __m512i a_vec, b_vec;
1038
1125
 
1039
1126
  simsimd_avx512_i8_cos_cycle:
1040
- if (n < 64) {
1041
- __mmask64 mask = _bzhi_u64(0xFFFFFFFFFFFFFFFF, n);
1042
- a_vec = _mm512_maskz_loadu_epi8(mask, a);
1043
- b_vec = _mm512_maskz_loadu_epi8(mask, b);
1127
+ if (n < 32) {
1128
+ __mmask32 mask = _bzhi_u32(0xFFFFFFFF, n);
1129
+ a_vec = _mm512_cvtepi8_epi16(_mm256_maskz_loadu_epi8(mask, a));
1130
+ b_vec = _mm512_cvtepi8_epi16(_mm256_maskz_loadu_epi8(mask, b));
1044
1131
  n = 0;
1045
1132
  } else {
1046
- a_vec = _mm512_loadu_epi8(a);
1047
- b_vec = _mm512_loadu_epi8(b);
1048
- a += 64, b += 64, n -= 64;
1133
+ a_vec = _mm512_cvtepi8_epi16(_mm256_loadu_epi8(a));
1134
+ b_vec = _mm512_cvtepi8_epi16(_mm256_loadu_epi8(b));
1135
+ a += 32, b += 32, n -= 32;
1049
1136
  }
1050
- ab_i32s_vec = _mm512_dpbusd_epi32(ab_i32s_vec, a_vec, b_vec);
1051
- a2_i32s_vec = _mm512_dpbusd_epi32(a2_i32s_vec, a_vec, a_vec);
1052
- b2_i32s_vec = _mm512_dpbusd_epi32(b2_i32s_vec, b_vec, b_vec);
1137
+ // Unfortunately we can't use the `_mm512_dpbusd_epi32` intrinsics here either,
1138
+ // as it's assymetric with respect to the sign of the input arguments:
1139
+ // Signed(ZeroExtend16(a.byte[4*j]) * SignExtend16(b.byte[4*j]))
1140
+ // So we have to use the `_mm512_dpwssd_epi32` intrinsics instead, upcasting
1141
+ // to 16-bit beforehand.
1142
+ ab_i32s_vec = _mm512_dpwssd_epi32(ab_i32s_vec, a_vec, b_vec);
1143
+ a2_i32s_vec = _mm512_dpwssd_epi32(a2_i32s_vec, a_vec, a_vec);
1144
+ b2_i32s_vec = _mm512_dpwssd_epi32(b2_i32s_vec, b_vec, b_vec);
1053
1145
  if (n)
1054
1146
  goto simsimd_avx512_i8_cos_cycle;
1055
1147
 
@@ -84,11 +84,11 @@
84
84
  #endif
85
85
 
86
86
  #ifndef SIMSIMD_F32_DIVISION_EPSILON
87
- #define SIMSIMD_F32_DIVISION_EPSILON 1e-7
87
+ #define SIMSIMD_F32_DIVISION_EPSILON (1e-7)
88
88
  #endif
89
89
 
90
90
  #ifndef SIMSIMD_F16_DIVISION_EPSILON
91
- #define SIMSIMD_F16_DIVISION_EPSILON 1e-3
91
+ #define SIMSIMD_F16_DIVISION_EPSILON (1e-3)
92
92
  #endif
93
93
 
94
94
  #ifdef __cplusplus
@@ -100,6 +100,7 @@ typedef float simsimd_f32_t;
100
100
  typedef double simsimd_f64_t;
101
101
  typedef signed char simsimd_i8_t;
102
102
  typedef unsigned char simsimd_b8_t;
103
+ typedef unsigned long long simsimd_u64_t;
103
104
  typedef unsigned long long simsimd_size_t;
104
105
 
105
106
  #if !defined(SIMSIMD_NATIVE_F16) || SIMSIMD_NATIVE_F16
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "simsimd",
3
- "version": "3.6.4",
3
+ "version": "3.8.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "simsimd",
9
- "version": "3.6.4",
9
+ "version": "3.8.1",
10
10
  "hasInstallScript": true,
11
11
  "license": "Apache 2.0",
12
12
  "dependencies": {
@@ -627,9 +627,9 @@
627
627
  "dev": true
628
628
  },
629
629
  "node_modules/ip": {
630
- "version": "2.0.0",
631
- "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
632
- "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==",
630
+ "version": "2.0.1",
631
+ "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
632
+ "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==",
633
633
  "dev": true
634
634
  },
635
635
  "node_modules/is-fullwidth-code-point": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simsimd",
3
- "version": "3.8.0",
3
+ "version": "3.9.0",
4
4
  "description": "Fastest SIMD-Accelerated Vector Similarity Functions for x86 and Arm",
5
5
  "homepage": "https://github.com/ashvardanian/simsimd",
6
6
  "author": "Ash Vardanian",
@@ -10,18 +10,21 @@ pub(crate) fn cosine_similarity_cpu(a: &[f32], b: &[f32]) -> Option<f32> {
10
10
  return None;
11
11
  }
12
12
 
13
- let dot_product: f32 = a.iter().zip(b.iter()).map(|(a, b)| a * b).sum();
14
- let norm_a: f32 = a.iter().map(|a| a.powf(2.0)).sum();
15
- let norm_b: f32 = b.iter().map(|b| b.powf(2.0)).sum();
13
+ let (dot_product, norm_a, norm_b) = a
14
+ .iter()
15
+ .zip(b)
16
+ .map(|(a, b)| (a * b, a * a, b * b))
17
+ .fold((0.0, 0.0, 0.0), |acc, x| {
18
+ (acc.0 + x.0, acc.1 + x.1, acc.2 + x.2)
19
+ });
16
20
 
17
21
  Some(1.0 - (dot_product / (norm_a.sqrt() * norm_b.sqrt())))
18
22
  }
19
23
 
20
- pub(crate) fn squared_euclidean_cpu(a: &[f32], b: &[f32]) -> f32 {
21
- assert_eq!(a.len(), b.len());
24
+ pub(crate) fn squared_euclidean_cpu(a: &[f32], b: &[f32]) -> Option<f32> {
25
+ if a.len() != b.len() {
26
+ return None;
27
+ }
22
28
 
23
- a.iter()
24
- .zip(b.iter())
25
- .map(|(x, y)| ((*x) - (*y)) * ((*x) - (*y)))
26
- .fold(0.0, ::std::ops::Add::add)
29
+ Some(a.iter().zip(b).map(|(x, y)| (x - y).powi(2)).sum())
27
30
  }
@@ -7,62 +7,39 @@ simsimd_capability_t cached_capabilities(void) {
7
7
  return static_capabilities;
8
8
  }
9
9
 
10
- simsimd_f32_t cosine_i8(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t d) {
11
- static simsimd_metric_punned_t metric = 0;
12
- if (metric == 0) {
13
- simsimd_capability_t used_capability;
14
- simsimd_find_metric_punned(simsimd_metric_cosine_k, simsimd_datatype_i8_k, cached_capabilities(),
15
- simsimd_cap_any_k, &metric, &used_capability);
10
+ #define SIMSIMD_METRIC_DECLARATION(name, type) \
11
+ simsimd_f32_t name##_##type(simsimd_##type##_t const* a, simsimd_##type##_t const* b, simsimd_size_t d) { \
12
+ static simsimd_metric_punned_t metric = 0; \
13
+ if (metric == 0) { \
14
+ simsimd_capability_t used_capability; \
15
+ simsimd_find_metric_punned(simsimd_metric_##name##_k, simsimd_datatype_##type##_k, cached_capabilities(), \
16
+ simsimd_cap_any_k, &metric, &used_capability); \
17
+ } \
18
+ return metric(a, b, d, d); \
16
19
  }
17
- return metric(a, b, d, d);
18
- }
19
20
 
20
- simsimd_f32_t cosine_f32(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t d) {
21
- static simsimd_metric_punned_t metric = 0;
22
- if (metric == 0) {
23
- simsimd_capability_t used_capability;
24
- simsimd_find_metric_punned(simsimd_metric_cosine_k, simsimd_datatype_f32_k, cached_capabilities(),
25
- simsimd_cap_any_k, &metric, &used_capability);
26
- }
27
- return metric(a, b, d, d);
28
- }
21
+ // Spatial distances
22
+ SIMSIMD_METRIC_DECLARATION(cosine, i8)
23
+ SIMSIMD_METRIC_DECLARATION(cosine, f16)
24
+ SIMSIMD_METRIC_DECLARATION(cosine, f32)
25
+ SIMSIMD_METRIC_DECLARATION(cosine, f64)
26
+ SIMSIMD_METRIC_DECLARATION(inner, i8)
27
+ SIMSIMD_METRIC_DECLARATION(inner, f16)
28
+ SIMSIMD_METRIC_DECLARATION(inner, f32)
29
+ SIMSIMD_METRIC_DECLARATION(inner, f64)
30
+ SIMSIMD_METRIC_DECLARATION(sqeuclidean, i8)
31
+ SIMSIMD_METRIC_DECLARATION(sqeuclidean, f16)
32
+ SIMSIMD_METRIC_DECLARATION(sqeuclidean, f32)
33
+ SIMSIMD_METRIC_DECLARATION(sqeuclidean, f64)
29
34
 
30
- simsimd_f32_t inner_i8(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t d) {
31
- static simsimd_metric_punned_t metric = 0;
32
- if (metric == 0) {
33
- simsimd_capability_t used_capability;
34
- simsimd_find_metric_punned(simsimd_metric_inner_k, simsimd_datatype_i8_k, cached_capabilities(),
35
- simsimd_cap_any_k, &metric, &used_capability);
36
- }
37
- return metric(a, b, d, d);
38
- }
35
+ // Binary distances
36
+ SIMSIMD_METRIC_DECLARATION(hamming, b8)
37
+ SIMSIMD_METRIC_DECLARATION(jaccard, b8)
39
38
 
40
- simsimd_f32_t inner_f32(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t d) {
41
- static simsimd_metric_punned_t metric = 0;
42
- if (metric == 0) {
43
- simsimd_capability_t used_capability;
44
- simsimd_find_metric_punned(simsimd_metric_inner_k, simsimd_datatype_f32_k, cached_capabilities(),
45
- simsimd_cap_any_k, &metric, &used_capability);
46
- }
47
- return metric(a, b, d, d);
48
- }
49
-
50
- simsimd_f32_t sqeuclidean_i8(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t d) {
51
- static simsimd_metric_punned_t metric = 0;
52
- if (metric == 0) {
53
- simsimd_capability_t used_capability;
54
- simsimd_find_metric_punned(simsimd_metric_sqeuclidean_k, simsimd_datatype_i8_k, cached_capabilities(),
55
- simsimd_cap_any_k, &metric, &used_capability);
56
- }
57
- return metric(a, b, d, d);
58
- }
59
-
60
- simsimd_f32_t sqeuclidean_f32(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t d) {
61
- static simsimd_metric_punned_t metric = 0;
62
- if (metric == 0) {
63
- simsimd_capability_t used_capability;
64
- simsimd_find_metric_punned(simsimd_metric_sqeuclidean_k, simsimd_datatype_f32_k, cached_capabilities(),
65
- simsimd_cap_any_k, &metric, &used_capability);
66
- }
67
- return metric(a, b, d, d);
68
- }
39
+ // Probability distributions
40
+ SIMSIMD_METRIC_DECLARATION(kl, f16)
41
+ SIMSIMD_METRIC_DECLARATION(kl, f32)
42
+ SIMSIMD_METRIC_DECLARATION(kl, f64)
43
+ SIMSIMD_METRIC_DECLARATION(js, f16)
44
+ SIMSIMD_METRIC_DECLARATION(js, f32)
45
+ SIMSIMD_METRIC_DECLARATION(js, f64)