usearch 2.11.1 → 2.13.1
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 +894 -272
- 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
|
@@ -11,12 +11,21 @@
|
|
|
11
11
|
#ifndef SIMSIMD_TYPES_H
|
|
12
12
|
#define SIMSIMD_TYPES_H
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
// Inferring target OS: Windows, MacOS, or Linux
|
|
15
|
+
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
|
|
16
|
+
#define SIMSIMD_DEFINED_WINDOWS
|
|
17
|
+
#elif defined(__APPLE__) && defined(__MACH__)
|
|
18
|
+
#define SIMSIMD_DEFINED_APPLE
|
|
19
|
+
#elif defined(__linux__)
|
|
20
|
+
#define SIMSIMD_DEFINED_LINUX
|
|
21
|
+
#endif
|
|
22
|
+
|
|
23
|
+
// Annotation for the public API symbols:
|
|
24
|
+
//
|
|
25
|
+
// - `SIMSIMD_PUBLIC` is used for functions that are part of the public API.
|
|
26
|
+
// - `SIMSIMD_INTERNAL` is used for internal helper functions with unstable APIs.
|
|
27
|
+
// - `SIMSIMD_DYNAMIC` is used for functions that are part of the public API, but are dispatched at runtime.
|
|
28
|
+
//
|
|
20
29
|
#if defined(_WIN32) || defined(__CYGWIN__)
|
|
21
30
|
#define SIMSIMD_DYNAMIC __declspec(dllexport)
|
|
22
31
|
#define SIMSIMD_PUBLIC inline static
|
|
@@ -59,6 +68,16 @@
|
|
|
59
68
|
#endif // defined(__ARM_NEON)
|
|
60
69
|
#endif // !defined(SIMSIMD_TARGET_NEON)
|
|
61
70
|
|
|
71
|
+
#if !defined(SIMSIMD_TARGET_NEON_I8)
|
|
72
|
+
#define SIMSIMD_TARGET_NEON_I8 SIMSIMD_TARGET_NEON
|
|
73
|
+
#endif // !defined(SIMSIMD_TARGET_NEON_I8)
|
|
74
|
+
#if !defined(SIMSIMD_TARGET_NEON_F16)
|
|
75
|
+
#define SIMSIMD_TARGET_NEON_F16 SIMSIMD_TARGET_NEON
|
|
76
|
+
#endif // !defined(SIMSIMD_TARGET_NEON_F16)
|
|
77
|
+
#if !defined(SIMSIMD_TARGET_NEON_BF16)
|
|
78
|
+
#define SIMSIMD_TARGET_NEON_BF16 SIMSIMD_TARGET_NEON
|
|
79
|
+
#endif // !defined(SIMSIMD_TARGET_NEON_BF16)
|
|
80
|
+
|
|
62
81
|
// Compiling for Arm: SIMSIMD_TARGET_SVE
|
|
63
82
|
#if !defined(SIMSIMD_TARGET_SVE) || (SIMSIMD_TARGET_SVE && !SIMSIMD_TARGET_ARM)
|
|
64
83
|
#if defined(__ARM_FEATURE_SVE)
|
|
@@ -69,6 +88,16 @@
|
|
|
69
88
|
#endif // defined(__ARM_FEATURE_SVE)
|
|
70
89
|
#endif // !defined(SIMSIMD_TARGET_SVE)
|
|
71
90
|
|
|
91
|
+
#if !defined(SIMSIMD_TARGET_SVE_I8)
|
|
92
|
+
#define SIMSIMD_TARGET_SVE_I8 SIMSIMD_TARGET_SVE
|
|
93
|
+
#endif // !defined(SIMSIMD_TARGET_SVE_I8)
|
|
94
|
+
#if !defined(SIMSIMD_TARGET_SVE_F16)
|
|
95
|
+
#define SIMSIMD_TARGET_SVE_F16 SIMSIMD_TARGET_SVE
|
|
96
|
+
#endif // !defined(SIMSIMD_TARGET_SVE_F16)
|
|
97
|
+
#if !defined(SIMSIMD_TARGET_SVE_BF16)
|
|
98
|
+
#define SIMSIMD_TARGET_SVE_BF16 SIMSIMD_TARGET_SVE
|
|
99
|
+
#endif // !defined(SIMSIMD_TARGET_SVE_BF16)
|
|
100
|
+
|
|
72
101
|
// Compiling for x86: SIMSIMD_TARGET_HASWELL
|
|
73
102
|
//
|
|
74
103
|
// Starting with Ivy Bridge, Intel supports the `F16C` extensions for fast half-precision
|
|
@@ -87,28 +116,6 @@
|
|
|
87
116
|
|
|
88
117
|
// Compiling for x86: SIMSIMD_TARGET_SKYLAKE, SIMSIMD_TARGET_ICE, SIMSIMD_TARGET_SAPPHIRE
|
|
89
118
|
//
|
|
90
|
-
// It's important to provide fine-grained controls over AVX512 families, as they are very fragmented:
|
|
91
|
-
// - Intel Skylake servers: F, CD, VL, DQ, BW
|
|
92
|
-
// - Intel Cascade Lake workstations: F, CD, VL, DQ, BW, VNNI
|
|
93
|
-
// > In other words, it extends Skylake with VNNI support
|
|
94
|
-
// - Intel Sunny Cove (Ice Lake) servers:
|
|
95
|
-
// F, CD, VL, DQ, BW, VNNI, VPOPCNTDQ, IFMA, VBMI, VAES, GFNI, VBMI2, BITALG, VPCLMULQDQ
|
|
96
|
-
// - AMD Zen4 (Genoa):
|
|
97
|
-
// F, CD, VL, DQ, BW, VNNI, VPOPCNTDQ, IFMA, VBMI, VAES, GFNI, VBMI2, BITALG, VPCLMULQDQ, BF16
|
|
98
|
-
// > In other words, it extends Sunny Cove with BF16 support
|
|
99
|
-
// - Golden Cove (Sapphire Rapids): extends Zen4 and Sunny Cove with FP16 support
|
|
100
|
-
//
|
|
101
|
-
// Intel Palm Cove was an irrelevant intermediate release extending Skylake with IFMA and VBMI.
|
|
102
|
-
// Intel Willow Cove was an irrelevant intermediate release extending Sunny Cove with VP2INTERSECT,
|
|
103
|
-
// that aren't supported by any other CPU built to date... and those are only available in Tiger Lake laptops.
|
|
104
|
-
// Intel Cooper Lake was the only intermediary platform, that supported BF16, but not FP16.
|
|
105
|
-
// It's mostly used in 4-socket and 8-socket high-memory configurations.
|
|
106
|
-
//
|
|
107
|
-
// In practical terms, it makes sense to differentiate only 3 AVX512 generations:
|
|
108
|
-
// 1. Skylake (pre 2019): supports single-precision dot-products.
|
|
109
|
-
// 2. Ice Lake (2019-2021): advanced integer algorithms.
|
|
110
|
-
// 3. Sapphire Rapids (2023+): advanced mixed-precision float processing.
|
|
111
|
-
//
|
|
112
119
|
// To list all available macros for x86, take a recent compiler, like GCC 12 and run:
|
|
113
120
|
// gcc-12 -march=sapphirerapids -dM -E - < /dev/null | egrep "SSE|AVX" | sort
|
|
114
121
|
// On Arm machines you may want to check for other flags:
|
|
@@ -131,6 +138,14 @@
|
|
|
131
138
|
#define SIMSIMD_TARGET_ICE 0
|
|
132
139
|
#endif
|
|
133
140
|
#endif // !defined(SIMSIMD_TARGET_ICE)
|
|
141
|
+
#if !defined(SIMSIMD_TARGET_GENOA) || (SIMSIMD_TARGET_GENOA && !SIMSIMD_TARGET_X86)
|
|
142
|
+
#if defined(__AVX512BF16__)
|
|
143
|
+
#define SIMSIMD_TARGET_GENOA 1
|
|
144
|
+
#else
|
|
145
|
+
#undef SIMSIMD_TARGET_GENOA
|
|
146
|
+
#define SIMSIMD_TARGET_GENOA 0
|
|
147
|
+
#endif
|
|
148
|
+
#endif // !defined(SIMSIMD_TARGET_GENOA)
|
|
134
149
|
#if !defined(SIMSIMD_TARGET_SAPPHIRE) || (SIMSIMD_TARGET_SAPPHIRE && !SIMSIMD_TARGET_X86)
|
|
135
150
|
#if defined(__AVX512FP16__)
|
|
136
151
|
#define SIMSIMD_TARGET_SAPPHIRE 1
|
|
@@ -185,19 +200,19 @@ typedef float simsimd_f32_t;
|
|
|
185
200
|
typedef double simsimd_f64_t;
|
|
186
201
|
typedef signed char simsimd_i8_t;
|
|
187
202
|
typedef unsigned char simsimd_b8_t;
|
|
203
|
+
typedef long long simsimd_i64_t;
|
|
188
204
|
typedef unsigned long long simsimd_u64_t;
|
|
189
205
|
|
|
190
206
|
typedef simsimd_u64_t simsimd_size_t;
|
|
191
207
|
typedef simsimd_f64_t simsimd_distance_t;
|
|
192
208
|
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* @brief Half-precision floating-point type.
|
|
209
|
+
/* @brief Half-precision floating-point type.
|
|
196
210
|
*
|
|
197
|
-
* - GCC or Clang on 64-bit
|
|
211
|
+
* - GCC or Clang on 64-bit Arm: `__fp16`, may require `-mfp16-format` option.
|
|
198
212
|
* - GCC or Clang on 64-bit x86: `_Float16`.
|
|
199
213
|
* - Default: `unsigned short`.
|
|
200
214
|
*/
|
|
215
|
+
#if !defined(SIMSIMD_NATIVE_F16) || SIMSIMD_NATIVE_F16
|
|
201
216
|
#if (defined(__GNUC__) || defined(__clang__)) && (defined(__ARM_ARCH) || defined(__aarch64__)) && \
|
|
202
217
|
(defined(__ARM_FP16_FORMAT_IEEE))
|
|
203
218
|
#if !defined(SIMSIMD_NATIVE_F16)
|
|
@@ -219,17 +234,55 @@ typedef _Float16 simsimd_f16_t;
|
|
|
219
234
|
typedef unsigned short simsimd_f16_t;
|
|
220
235
|
#endif
|
|
221
236
|
|
|
237
|
+
#if !defined(SIMSIMD_NATIVE_BF16) || SIMSIMD_NATIVE_BF16
|
|
238
|
+
/**
|
|
239
|
+
* @brief Half-precision brain-float type.
|
|
240
|
+
*
|
|
241
|
+
* - GCC or Clang on 64-bit Arm: `__bf16`
|
|
242
|
+
* - GCC or Clang on 64-bit x86: `_BFloat16`.
|
|
243
|
+
* - Default: `unsigned short`.
|
|
244
|
+
*
|
|
245
|
+
* @warning Apple Clang has hard time with bf16.
|
|
246
|
+
* https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms
|
|
247
|
+
* https://forums.developer.apple.com/forums/thread/726201
|
|
248
|
+
*/
|
|
249
|
+
#if (defined(__GNUC__) || defined(__clang__)) && (defined(__ARM_ARCH) || defined(__aarch64__)) && \
|
|
250
|
+
(defined(__ARM_BF16_FORMAT_ALTERNATIVE))
|
|
251
|
+
#if !defined(SIMSIMD_NATIVE_BF16)
|
|
252
|
+
#define SIMSIMD_NATIVE_BF16 1
|
|
253
|
+
#endif
|
|
254
|
+
typedef __fp16 simsimd_bf16_t;
|
|
255
|
+
#elif ((defined(__GNUC__) || defined(__clang__)) && (defined(__x86_64__) || defined(__i386__)) && \
|
|
256
|
+
(defined(__SSE2__) || defined(__AVX512F__)))
|
|
257
|
+
typedef _Float16 simsimd_bf16_t;
|
|
258
|
+
#if !defined(SIMSIMD_NATIVE_BF16)
|
|
259
|
+
#define SIMSIMD_NATIVE_BF16 1
|
|
260
|
+
#endif
|
|
261
|
+
#else // Unknown compiler or architecture
|
|
262
|
+
#define SIMSIMD_NATIVE_BF16 0
|
|
263
|
+
#endif // Unknown compiler or architecture
|
|
264
|
+
#endif // !SIMSIMD_NATIVE_BF16
|
|
265
|
+
|
|
266
|
+
#if !SIMSIMD_NATIVE_BF16
|
|
267
|
+
typedef unsigned short simsimd_bf16_t;
|
|
268
|
+
#endif
|
|
269
|
+
|
|
222
270
|
/**
|
|
223
271
|
* @brief Alias for the half-precision floating-point type on Arm.
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
272
|
+
*
|
|
273
|
+
* Clang and GCC bring the `float16_t` symbol when you compile for Aarch64.
|
|
274
|
+
* MSVC lacks it, and it's `vld1_f16`-like intrinsics are in reality macros,
|
|
275
|
+
* that cast to 16-bit integers internally, instead of using floats.
|
|
276
|
+
* Some of those are defined as aliases, so we use `#define` preprocessor
|
|
277
|
+
* directives instead of `typedef` to avoid errors.
|
|
227
278
|
*/
|
|
228
279
|
#if SIMSIMD_TARGET_ARM
|
|
229
280
|
#if defined(_MSC_VER)
|
|
230
|
-
|
|
281
|
+
#define simsimd_f16_for_arm_simd_t simsimd_f16_t
|
|
282
|
+
#define simsimd_bf16_for_arm_simd_t simsimd_bf16_t
|
|
231
283
|
#else
|
|
232
|
-
|
|
284
|
+
#define simsimd_f16_for_arm_simd_t float16_t
|
|
285
|
+
#define simsimd_bf16_for_arm_simd_t bfloat16_t
|
|
233
286
|
#endif
|
|
234
287
|
#endif
|
|
235
288
|
|
|
@@ -241,9 +294,21 @@ typedef float16_t simsimd_f16_for_arm_simd_t;
|
|
|
241
294
|
*/
|
|
242
295
|
#ifndef SIMSIMD_UNCOMPRESS_F16
|
|
243
296
|
#if SIMSIMD_NATIVE_F16
|
|
244
|
-
#define SIMSIMD_UNCOMPRESS_F16(x) SIMSIMD_IDENTIFY(x)
|
|
297
|
+
#define SIMSIMD_UNCOMPRESS_F16(x) (SIMSIMD_IDENTIFY(x))
|
|
245
298
|
#else
|
|
246
|
-
#define SIMSIMD_UNCOMPRESS_F16(x) simsimd_uncompress_f16(x)
|
|
299
|
+
#define SIMSIMD_UNCOMPRESS_F16(x) (simsimd_uncompress_f16(x))
|
|
300
|
+
#endif
|
|
301
|
+
#endif
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* @brief Returns the value of the half-precision brain floating-point number,
|
|
305
|
+
* potentially decompressed into single-precision.
|
|
306
|
+
*/
|
|
307
|
+
#ifndef SIMSIMD_UNCOMPRESS_BF16
|
|
308
|
+
#if SIMSIMD_NATIVE_BF16
|
|
309
|
+
#define SIMSIMD_UNCOMPRESS_BF16(x) (SIMSIMD_IDENTIFY(x))
|
|
310
|
+
#else
|
|
311
|
+
#define SIMSIMD_UNCOMPRESS_BF16(x) (simsimd_uncompress_bf16(x))
|
|
247
312
|
#endif
|
|
248
313
|
#endif
|
|
249
314
|
|
|
@@ -302,6 +367,63 @@ SIMSIMD_PUBLIC simsimd_f32_t simsimd_uncompress_f16(unsigned short x) {
|
|
|
302
367
|
return result_union.f;
|
|
303
368
|
}
|
|
304
369
|
|
|
370
|
+
/**
|
|
371
|
+
* @brief Compresses a `float` to an `f16` representation (IEEE-754 16-bit floating-point format).
|
|
372
|
+
*
|
|
373
|
+
* @warning This function won't handle boundary conditions well.
|
|
374
|
+
*
|
|
375
|
+
* https://stackoverflow.com/a/60047308
|
|
376
|
+
* https://gist.github.com/milhidaka/95863906fe828198f47991c813dbe233
|
|
377
|
+
* https://github.com/OpenCyphal/libcanard/blob/636795f4bc395f56af8d2c61d3757b5e762bb9e5/canard.c#L811-L834
|
|
378
|
+
*/
|
|
379
|
+
SIMSIMD_PUBLIC unsigned short simsimd_compress_f16(simsimd_f32_t x) {
|
|
380
|
+
union float_or_unsigned_int_t {
|
|
381
|
+
float f;
|
|
382
|
+
unsigned int i;
|
|
383
|
+
} x_union;
|
|
384
|
+
x_union.f = x;
|
|
385
|
+
|
|
386
|
+
unsigned int b = x_union.i + 0x00001000;
|
|
387
|
+
unsigned int e = (b & 0x7F800000) >> 23;
|
|
388
|
+
unsigned int m = b & 0x007FFFFF;
|
|
389
|
+
unsigned short result = ((b & 0x80000000) >> 16) | (e > 112) * ((((e - 112) << 10) & 0x7C00) | (m >> 13)) |
|
|
390
|
+
((e < 113) & (e > 101)) * ((((0x007FF000 + m) >> (125 - e)) + 1) >> 1) |
|
|
391
|
+
((e > 143) * 0x7FFF);
|
|
392
|
+
return result;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* @brief For compilers that don't natively support the `__bf16` type,
|
|
397
|
+
* upcasts contents into a more conventional `float`.
|
|
398
|
+
*
|
|
399
|
+
* https://stackoverflow.com/questions/55253233/convert-fp32-to-bfloat16-in-c/55254307#55254307
|
|
400
|
+
* https://cloud.google.com/blog/products/ai-machine-learning/bfloat16-the-secret-to-high-performance-on-cloud-tpus
|
|
401
|
+
*/
|
|
402
|
+
SIMSIMD_PUBLIC simsimd_f32_t simsimd_uncompress_bf16(unsigned short x) {
|
|
403
|
+
union float_or_unsigned_int_t {
|
|
404
|
+
float f;
|
|
405
|
+
unsigned int i;
|
|
406
|
+
};
|
|
407
|
+
union float_or_unsigned_int_t result_union;
|
|
408
|
+
result_union.i = x << 16; // Zero extends the mantissa
|
|
409
|
+
return result_union.f;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* @brief Compresses a `float` to a `bf16` representation.
|
|
414
|
+
*/
|
|
415
|
+
SIMSIMD_PUBLIC unsigned short simsimd_compress_bf16(simsimd_f32_t x) {
|
|
416
|
+
union float_or_unsigned_int_t {
|
|
417
|
+
float f;
|
|
418
|
+
unsigned int i;
|
|
419
|
+
};
|
|
420
|
+
union float_or_unsigned_int_t value;
|
|
421
|
+
value.f = x;
|
|
422
|
+
value.i >>= 16;
|
|
423
|
+
value.i &= 0xFFFF;
|
|
424
|
+
return (unsigned short)value.i;
|
|
425
|
+
}
|
|
426
|
+
|
|
305
427
|
#ifdef __cplusplus
|
|
306
428
|
} // extern "C"
|
|
307
429
|
#endif
|
|
@@ -102,6 +102,27 @@ export const jensenshannon = (a: Float64Array | Float32Array, b: Float64Array |
|
|
|
102
102
|
return compiled.jensenshannon(a, b);
|
|
103
103
|
};
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Quantizes a floating-point vector into a binary vector (1 for positive values, 0 for non-positive values) and packs the result into a Uint8Array, where each element represents 8 binary values from the original vector.
|
|
107
|
+
* This function is useful for preparing data for bitwise distance or similarity computations, such as Hamming or Jaccard indices.
|
|
108
|
+
*
|
|
109
|
+
* @param {Float32Array | Float64Array | Int8Array} vector The floating-point vector to be quantized and packed.
|
|
110
|
+
* @returns {Uint8Array} A Uint8Array where each byte represents 8 binary quantized values from the input vector.
|
|
111
|
+
*/
|
|
112
|
+
export const toBinary = (vector: Float32Array | Float64Array | Int8Array): Uint8Array => {
|
|
113
|
+
const byteLength = Math.ceil(vector.length / 8);
|
|
114
|
+
const packedVector = new Uint8Array(byteLength);
|
|
115
|
+
|
|
116
|
+
for (let i = 0; i < vector.length; i++) {
|
|
117
|
+
if (vector[i] > 0) {
|
|
118
|
+
const byteIndex = Math.floor(i / 8);
|
|
119
|
+
const bitPosition = 7 - (i % 8);
|
|
120
|
+
packedVector[byteIndex] |= (1 << bitPosition);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return packedVector;
|
|
125
|
+
};
|
|
105
126
|
export default {
|
|
106
127
|
dot,
|
|
107
128
|
inner,
|
|
@@ -111,10 +132,13 @@ export default {
|
|
|
111
132
|
jaccard,
|
|
112
133
|
kullbackleibler,
|
|
113
134
|
jensenshannon,
|
|
135
|
+
toBinary,
|
|
114
136
|
};
|
|
115
137
|
|
|
116
|
-
|
|
117
|
-
|
|
138
|
+
/**
|
|
139
|
+
* @brief Finds the directory where the native build of the simsimd module is located.
|
|
140
|
+
* @param {string} dir - The directory to start the search from.
|
|
141
|
+
*/
|
|
118
142
|
function getBuildDir(dir: string) {
|
|
119
143
|
if (existsSync(path.join(dir, "build"))) return dir;
|
|
120
144
|
if (existsSync(path.join(dir, "prebuilds"))) return dir;
|
package/simsimd/package.json
CHANGED
|
@@ -17,8 +17,14 @@ pub fn cos_benchmark(c: &mut Criterion) {
|
|
|
17
17
|
group.bench_with_input(BenchmarkId::new("SimSIMD", i), &i, |b, _| {
|
|
18
18
|
b.iter(|| SimSIMD::cosine(&inputs.0, &inputs.1))
|
|
19
19
|
});
|
|
20
|
-
group.bench_with_input(BenchmarkId::new("Rust
|
|
21
|
-
b.iter(|| native::
|
|
20
|
+
group.bench_with_input(BenchmarkId::new("Rust Procedural", i), &i, |b, _| {
|
|
21
|
+
b.iter(|| native::baseline_cos_procedural(&inputs.0, &inputs.1))
|
|
22
|
+
});
|
|
23
|
+
group.bench_with_input(BenchmarkId::new("Rust Functional", i), &i, |b, _| {
|
|
24
|
+
b.iter(|| native::baseline_cos_functional(&inputs.0, &inputs.1))
|
|
25
|
+
});
|
|
26
|
+
group.bench_with_input(BenchmarkId::new("Rust Unrolled", i), &i, |b, _| {
|
|
27
|
+
b.iter(|| native::baseline_cos_unrolled(&inputs.0, &inputs.1))
|
|
22
28
|
});
|
|
23
29
|
}
|
|
24
30
|
}
|
|
@@ -5,7 +5,7 @@ pub(crate) fn generate_random_vector(dim: usize) -> Vec<f32> {
|
|
|
5
5
|
(0..dim).map(|_| rand::thread_rng().gen()).collect()
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
pub(crate) fn
|
|
8
|
+
pub(crate) fn baseline_cos_functional(a: &[f32], b: &[f32]) -> Option<f32> {
|
|
9
9
|
if a.len() != b.len() {
|
|
10
10
|
return None;
|
|
11
11
|
}
|
|
@@ -21,10 +21,248 @@ pub(crate) fn cos_similarity_cpu(a: &[f32], b: &[f32]) -> Option<f32> {
|
|
|
21
21
|
Some(1.0 - (dot_product / (norm_a.sqrt() * norm_b.sqrt())))
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
pub(crate) fn
|
|
24
|
+
pub(crate) fn baseline_l2sq_functional(a: &[f32], b: &[f32]) -> Option<f32> {
|
|
25
25
|
if a.len() != b.len() {
|
|
26
26
|
return None;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
Some(a.iter().zip(b).map(|(x, y)| (x - y).powi(2)).sum())
|
|
30
30
|
}
|
|
31
|
+
|
|
32
|
+
pub(crate) fn baseline_cos_procedural(a: &[f32], b: &[f32]) -> Option<f32> {
|
|
33
|
+
if a.len() != b.len() {
|
|
34
|
+
return None;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let mut dot_product = 0.0;
|
|
38
|
+
let mut norm_a = 0.0;
|
|
39
|
+
let mut norm_b = 0.0;
|
|
40
|
+
|
|
41
|
+
for i in 0..a.len() {
|
|
42
|
+
dot_product += a[i] * b[i];
|
|
43
|
+
norm_a += a[i] * a[i];
|
|
44
|
+
norm_b += b[i] * b[i];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
Some(1.0 - (dot_product / (norm_a.sqrt() * norm_b.sqrt())))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pub(crate) fn baseline_l2sq_procedural(a: &[f32], b: &[f32]) -> Option<f32> {
|
|
51
|
+
if a.len() != b.len() {
|
|
52
|
+
return None;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let mut sum = 0.0;
|
|
56
|
+
for i in 0..a.len() {
|
|
57
|
+
let diff = a[i] - b[i];
|
|
58
|
+
sum += diff * diff;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
Some(sum)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
pub(crate) fn baseline_cos_unrolled(a: &[f32], b: &[f32]) -> Option<f32> {
|
|
65
|
+
if a.len() != b.len() {
|
|
66
|
+
return None;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let mut i = 0;
|
|
70
|
+
let mut remainder = a.len() % 8;
|
|
71
|
+
|
|
72
|
+
// We do this manual unrolling to allow the compiler to vectorize
|
|
73
|
+
// the loop and avoid some branching even if we're not doing it explicitly.
|
|
74
|
+
// This made a significant difference in benchmarking ~4x
|
|
75
|
+
let mut acc1 = 0.0;
|
|
76
|
+
let mut acc2 = 0.0;
|
|
77
|
+
let mut acc3 = 0.0;
|
|
78
|
+
let mut acc4 = 0.0;
|
|
79
|
+
let mut acc5 = 0.0;
|
|
80
|
+
let mut acc6 = 0.0;
|
|
81
|
+
let mut acc7 = 0.0;
|
|
82
|
+
let mut acc8 = 0.0;
|
|
83
|
+
|
|
84
|
+
let mut norm_a_acc1 = 0.0;
|
|
85
|
+
let mut norm_a_acc2 = 0.0;
|
|
86
|
+
let mut norm_a_acc3 = 0.0;
|
|
87
|
+
let mut norm_a_acc4 = 0.0;
|
|
88
|
+
let mut norm_a_acc5 = 0.0;
|
|
89
|
+
let mut norm_a_acc6 = 0.0;
|
|
90
|
+
let mut norm_a_acc7 = 0.0;
|
|
91
|
+
let mut norm_a_acc8 = 0.0;
|
|
92
|
+
|
|
93
|
+
let mut norm_b_acc1 = 0.0;
|
|
94
|
+
let mut norm_b_acc2 = 0.0;
|
|
95
|
+
let mut norm_b_acc3 = 0.0;
|
|
96
|
+
let mut norm_b_acc4 = 0.0;
|
|
97
|
+
let mut norm_b_acc5 = 0.0;
|
|
98
|
+
let mut norm_b_acc6 = 0.0;
|
|
99
|
+
let mut norm_b_acc7 = 0.0;
|
|
100
|
+
let mut norm_b_acc8 = 0.0;
|
|
101
|
+
|
|
102
|
+
while i < (a.len() - remainder) {
|
|
103
|
+
let a1 = unsafe { *a.get_unchecked(i) };
|
|
104
|
+
let a2 = unsafe { *a.get_unchecked(i + 1) };
|
|
105
|
+
let a3 = unsafe { *a.get_unchecked(i + 2) };
|
|
106
|
+
let a4 = unsafe { *a.get_unchecked(i + 3) };
|
|
107
|
+
let a5 = unsafe { *a.get_unchecked(i + 4) };
|
|
108
|
+
let a6 = unsafe { *a.get_unchecked(i + 5) };
|
|
109
|
+
let a7 = unsafe { *a.get_unchecked(i + 6) };
|
|
110
|
+
let a8 = unsafe { *a.get_unchecked(i + 7) };
|
|
111
|
+
|
|
112
|
+
let b1 = unsafe { *b.get_unchecked(i) };
|
|
113
|
+
let b2 = unsafe { *b.get_unchecked(i + 1) };
|
|
114
|
+
let b3 = unsafe { *b.get_unchecked(i + 2) };
|
|
115
|
+
let b4 = unsafe { *b.get_unchecked(i + 3) };
|
|
116
|
+
let b5 = unsafe { *b.get_unchecked(i + 4) };
|
|
117
|
+
let b6 = unsafe { *b.get_unchecked(i + 5) };
|
|
118
|
+
let b7 = unsafe { *b.get_unchecked(i + 6) };
|
|
119
|
+
let b8 = unsafe { *b.get_unchecked(i + 7) };
|
|
120
|
+
|
|
121
|
+
acc1 += (a1 * b1);
|
|
122
|
+
acc2 += (a2 * b2);
|
|
123
|
+
acc3 += (a3 * b3);
|
|
124
|
+
acc4 += (a4 * b4);
|
|
125
|
+
acc5 += (a5 * b5);
|
|
126
|
+
acc6 += (a6 * b6);
|
|
127
|
+
acc7 += (a7 * b7);
|
|
128
|
+
acc8 += (a8 * b8);
|
|
129
|
+
|
|
130
|
+
norm_a_acc1 += (a1 * a1);
|
|
131
|
+
norm_a_acc2 += (a2 * a2);
|
|
132
|
+
norm_a_acc3 += (a3 * a3);
|
|
133
|
+
norm_a_acc4 += (a4 * a4);
|
|
134
|
+
norm_a_acc5 += (a5 * a5);
|
|
135
|
+
norm_a_acc6 += (a6 * a6);
|
|
136
|
+
norm_a_acc7 += (a7 * a7);
|
|
137
|
+
norm_a_acc8 += (a8 * a8);
|
|
138
|
+
|
|
139
|
+
norm_b_acc1 += (b1 * b1);
|
|
140
|
+
norm_b_acc2 += (b2 * b2);
|
|
141
|
+
norm_b_acc3 += (b3 * b3);
|
|
142
|
+
norm_b_acc4 += (b4 * b4);
|
|
143
|
+
norm_b_acc5 += (b5 * b5);
|
|
144
|
+
norm_b_acc6 += (b6 * b6);
|
|
145
|
+
norm_b_acc7 += (b7 * b7);
|
|
146
|
+
norm_b_acc8 += (b8 * b8);
|
|
147
|
+
|
|
148
|
+
i += 8;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
acc1 += acc2;
|
|
152
|
+
acc3 += acc4;
|
|
153
|
+
acc5 += acc6;
|
|
154
|
+
acc7 += acc8;
|
|
155
|
+
|
|
156
|
+
norm_a_acc1 += norm_a_acc2;
|
|
157
|
+
norm_a_acc3 += norm_a_acc4;
|
|
158
|
+
norm_a_acc5 += norm_a_acc6;
|
|
159
|
+
norm_a_acc7 += norm_a_acc8;
|
|
160
|
+
|
|
161
|
+
norm_b_acc1 += norm_b_acc2;
|
|
162
|
+
norm_b_acc3 += norm_b_acc4;
|
|
163
|
+
norm_b_acc5 += norm_b_acc6;
|
|
164
|
+
norm_b_acc7 += norm_b_acc8;
|
|
165
|
+
|
|
166
|
+
acc1 += acc3;
|
|
167
|
+
acc5 += acc7;
|
|
168
|
+
|
|
169
|
+
norm_a_acc1 += norm_a_acc3;
|
|
170
|
+
norm_a_acc5 += norm_a_acc7;
|
|
171
|
+
|
|
172
|
+
norm_b_acc1 += norm_b_acc3;
|
|
173
|
+
norm_b_acc5 += norm_b_acc7;
|
|
174
|
+
|
|
175
|
+
acc1 += acc5;
|
|
176
|
+
norm_a_acc1 += norm_a_acc5;
|
|
177
|
+
norm_b_acc1 += norm_b_acc5;
|
|
178
|
+
|
|
179
|
+
while i < a.len() {
|
|
180
|
+
let a = unsafe { *a.get_unchecked(i) };
|
|
181
|
+
let b = unsafe { *b.get_unchecked(i) };
|
|
182
|
+
|
|
183
|
+
acc1 += (a * b);
|
|
184
|
+
norm_a_acc1 += (a * a);
|
|
185
|
+
norm_b_acc1 += (b * b);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
Some(1.0 - (acc1 / (norm_a_acc1 * norm_b_acc1).sqrt()))
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
pub(crate) fn baseline_l2sq_unrolled(a: &[f32], b: &[f32]) -> Option<f32> {
|
|
192
|
+
if a.len() != b.len() {
|
|
193
|
+
return None;
|
|
194
|
+
}
|
|
195
|
+
let mut i = 0;
|
|
196
|
+
let mut remainder = a.len() % 8;
|
|
197
|
+
|
|
198
|
+
// We do this manual unrolling to allow the compiler to vectorize
|
|
199
|
+
// the loop and avoid some branching even if we're not doing it explicitly.
|
|
200
|
+
// This made a significant difference in benchmarking ~4x
|
|
201
|
+
let mut acc1 = 0.0;
|
|
202
|
+
let mut acc2 = 0.0;
|
|
203
|
+
let mut acc3 = 0.0;
|
|
204
|
+
let mut acc4 = 0.0;
|
|
205
|
+
let mut acc5 = 0.0;
|
|
206
|
+
let mut acc6 = 0.0;
|
|
207
|
+
let mut acc7 = 0.0;
|
|
208
|
+
let mut acc8 = 0.0;
|
|
209
|
+
|
|
210
|
+
while i < (a.len() - remainder) {
|
|
211
|
+
let a1 = unsafe { *a.get_unchecked(i) };
|
|
212
|
+
let a2 = unsafe { *a.get_unchecked(i + 1) };
|
|
213
|
+
let a3 = unsafe { *a.get_unchecked(i + 2) };
|
|
214
|
+
let a4 = unsafe { *a.get_unchecked(i + 3) };
|
|
215
|
+
let a5 = unsafe { *a.get_unchecked(i + 4) };
|
|
216
|
+
let a6 = unsafe { *a.get_unchecked(i + 5) };
|
|
217
|
+
let a7 = unsafe { *a.get_unchecked(i + 6) };
|
|
218
|
+
let a8 = unsafe { *a.get_unchecked(i + 7) };
|
|
219
|
+
|
|
220
|
+
let b1 = unsafe { *b.get_unchecked(i) };
|
|
221
|
+
let b2 = unsafe { *b.get_unchecked(i + 1) };
|
|
222
|
+
let b3 = unsafe { *b.get_unchecked(i + 2) };
|
|
223
|
+
let b4 = unsafe { *b.get_unchecked(i + 3) };
|
|
224
|
+
let b5 = unsafe { *b.get_unchecked(i + 4) };
|
|
225
|
+
let b6 = unsafe { *b.get_unchecked(i + 5) };
|
|
226
|
+
let b7 = unsafe { *b.get_unchecked(i + 6) };
|
|
227
|
+
let b8 = unsafe { *b.get_unchecked(i + 7) };
|
|
228
|
+
|
|
229
|
+
let diff1 = a1 - b1;
|
|
230
|
+
let diff2 = a2 - b2;
|
|
231
|
+
let diff3 = a3 - b3;
|
|
232
|
+
let diff4 = a4 - b4;
|
|
233
|
+
let diff5 = a5 - b5;
|
|
234
|
+
let diff6 = a6 - b6;
|
|
235
|
+
let diff7 = a7 - b7;
|
|
236
|
+
let diff8 = a8 - b8;
|
|
237
|
+
|
|
238
|
+
acc1 += (diff1 * diff1);
|
|
239
|
+
acc2 += (diff2 * diff2);
|
|
240
|
+
acc3 += (diff3 * diff3);
|
|
241
|
+
acc4 += (diff4 * diff4);
|
|
242
|
+
acc5 += (diff5 * diff5);
|
|
243
|
+
acc6 += (diff6 * diff6);
|
|
244
|
+
acc7 += (diff7 * diff7);
|
|
245
|
+
acc8 += (diff8 * diff8);
|
|
246
|
+
|
|
247
|
+
i += 8;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
acc1 += acc2;
|
|
251
|
+
acc3 += acc4;
|
|
252
|
+
acc5 += acc6;
|
|
253
|
+
acc7 += acc8;
|
|
254
|
+
|
|
255
|
+
acc1 += acc3;
|
|
256
|
+
acc5 += acc7;
|
|
257
|
+
|
|
258
|
+
acc1 += acc5;
|
|
259
|
+
|
|
260
|
+
while i < a.len() {
|
|
261
|
+
let a = unsafe { *a.get_unchecked(i) };
|
|
262
|
+
let b = unsafe { *b.get_unchecked(i) };
|
|
263
|
+
let diff = a - b;
|
|
264
|
+
acc1 += (diff * diff);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
Some(acc1)
|
|
268
|
+
}
|
|
@@ -17,8 +17,14 @@ pub fn l2sq_benchmark(c: &mut Criterion) {
|
|
|
17
17
|
group.bench_with_input(BenchmarkId::new("SimSIMD", i), &i, |b, _| {
|
|
18
18
|
b.iter(|| SimSIMD::sqeuclidean(&inputs.0, &inputs.1))
|
|
19
19
|
});
|
|
20
|
-
group.bench_with_input(BenchmarkId::new("Rust
|
|
21
|
-
b.iter(|| native::
|
|
20
|
+
group.bench_with_input(BenchmarkId::new("Rust Procedural", i), &i, |b, _| {
|
|
21
|
+
b.iter(|| native::baseline_l2sq_procedural(&inputs.0, &inputs.1))
|
|
22
|
+
});
|
|
23
|
+
group.bench_with_input(BenchmarkId::new("Rust Functional", i), &i, |b, _| {
|
|
24
|
+
b.iter(|| native::baseline_l2sq_functional(&inputs.0, &inputs.1))
|
|
25
|
+
});
|
|
26
|
+
group.bench_with_input(BenchmarkId::new("Rust Unrolled", i), &i, |b, _| {
|
|
27
|
+
b.iter(|| native::baseline_l2sq_unrolled(&inputs.0, &inputs.1))
|
|
22
28
|
});
|
|
23
29
|
}
|
|
24
30
|
}
|