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.
- package/README.md +100 -30
- package/binding.gyp +11 -12
- package/c/README.md +3 -0
- package/c/lib.cpp +50 -13
- package/c/test.c +60 -55
- package/c/usearch.h +16 -0
- package/cpp/CMakeLists.txt +6 -0
- package/cpp/README.md +34 -13
- package/cpp/bench.cpp +99 -47
- package/cpp/test.cpp +947 -132
- package/include/usearch/index.hpp +895 -273
- package/include/usearch/index_dense.hpp +418 -156
- package/include/usearch/index_plugins.hpp +99 -49
- package/javascript/dist/cjs/usearch.d.ts +39 -1
- package/javascript/dist/cjs/usearch.js +11 -1
- package/javascript/dist/esm/usearch.d.ts +39 -1
- package/javascript/dist/esm/usearch.js +11 -1
- package/javascript/tsconfig-esm.json +3 -2
- package/javascript/usearch.ts +18 -7
- package/package.json +2 -2
- package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
- package/prebuilds/linux-arm64/usearch.node +0 -0
- package/prebuilds/linux-x64/usearch.node +0 -0
- package/simsimd/.cmake-format.py +19 -0
- package/simsimd/CONTRIBUTING.md +49 -8
- package/simsimd/Cargo.lock +1 -1
- package/simsimd/Cargo.toml +10 -5
- package/simsimd/Package.swift +16 -0
- package/simsimd/README.md +69 -15
- package/simsimd/binding.gyp +14 -19
- package/simsimd/build.rs +2 -0
- package/simsimd/c/lib.c +18 -0
- package/simsimd/include/module.modulemap +4 -0
- package/simsimd/include/simsimd/binary.h +2 -0
- package/simsimd/include/simsimd/dot.h +466 -32
- package/simsimd/include/simsimd/probability.h +20 -10
- package/simsimd/include/simsimd/simsimd.h +444 -98
- package/simsimd/include/simsimd/spatial.h +480 -44
- package/simsimd/include/simsimd/types.h +161 -39
- package/simsimd/javascript/simsimd.ts +26 -2
- package/simsimd/package.json +1 -1
- package/simsimd/rust/benches/cosine.rs +8 -2
- package/simsimd/rust/benches/native.rs +240 -2
- package/simsimd/rust/benches/sqeuclidean.rs +8 -2
- package/simsimd/rust/lib.rs +50 -13
- package/simsimd/swift/source/SimSIMD.swift +123 -0
- package/simsimd/swift/tests/SimSIMDTests.swift +92 -0
- package/prebuilds/win32-ia32/usearch.node +0 -0
- package/prebuilds/win32-x64/usearch.node +0 -0
- 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 +
|
|
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
|
-
//
|
|
325
|
-
//
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
//
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
|
|
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 //
|
|
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
|
|
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
|
|
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 <
|
|
977
|
-
|
|
978
|
-
a_vec =
|
|
979
|
-
b_vec =
|
|
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 =
|
|
983
|
-
b_vec =
|
|
984
|
-
a +=
|
|
1405
|
+
a_vec = _mm512_loadu_epi8(a);
|
|
1406
|
+
b_vec = _mm512_loadu_epi8(b);
|
|
1407
|
+
a += 64, b += 64, n -= 64;
|
|
985
1408
|
}
|
|
986
|
-
|
|
987
|
-
//
|
|
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
|
-
//
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
a2_i32s_vec =
|
|
993
|
-
b2_i32s_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
|
-
|
|
998
|
-
|
|
999
|
-
|
|
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
|