usearch 2.13.5 → 2.15.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 +11 -16
- package/cpp/bench.cpp +4 -0
- package/include/usearch/index.hpp +12 -11
- package/include/usearch/index_dense.hpp +2 -1
- package/include/usearch/index_plugins.hpp +158 -39
- package/javascript/dist/cjs/usearch.d.ts +1 -0
- package/javascript/dist/cjs/usearch.js +1 -0
- package/javascript/dist/esm/usearch.d.ts +1 -0
- package/javascript/dist/esm/usearch.js +1 -0
- package/javascript/usearch.ts +7 -6
- package/package.json +1 -1
- 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/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<h1 align="center">USearch</h1>
|
|
2
2
|
<h3 align="center">
|
|
3
3
|
Smaller & <a href="https://www.unum.cloud/blog/2023-11-07-scaling-vector-search-with-intel">Faster</a> Single-File<br/>
|
|
4
|
-
Similarity Search Engine for <a href="https://github.com/ashvardanian/simsimd">Vectors</a> & 🔜 <a href="https://github.com/ashvardanian/stringzilla">Texts</a>
|
|
4
|
+
Similarity Search & Clustering Engine for <a href="https://github.com/ashvardanian/simsimd">Vectors</a> & 🔜 <a href="https://github.com/ashvardanian/stringzilla">Texts</a>
|
|
5
5
|
</h3>
|
|
6
6
|
<br/>
|
|
7
7
|
|
|
@@ -71,13 +71,10 @@ Linux • MacOS • Windows • iOS • WebAssembly •
|
|
|
71
71
|
|
|
72
72
|
__Technical Insights__ and related articles:
|
|
73
73
|
|
|
74
|
-
- [Uses Horner's method for polynomial approximations, beating GCC 12 by 119x](https://ashvardanian.com/posts/gcc-12-vs-avx512fp16/).
|
|
75
74
|
- [Uses Arm SVE and x86 AVX-512's masked loads to eliminate tail `for`-loops](https://ashvardanian.com/posts/simsimd-faster-scipy/#tails-of-the-past-the-significance-of-masked-loads).
|
|
76
|
-
- [Uses
|
|
77
|
-
- [Substitutes LibC's `sqrt` calls with bithacks using Jan Kadlec's constant](https://ashvardanian.com/posts/simsimd-faster-scipy/#bonus-section-bypassing-sqrt-and-libc-dependencies).
|
|
75
|
+
- [Uses Horner's method for polynomial approximations, beating GCC 12 by 119x](https://ashvardanian.com/posts/gcc-12-vs-avx512fp16/).
|
|
78
76
|
- [For every language implements a custom separate binding](https://ashvardanian.com/posts/porting-cpp-library-to-ten-languages/).
|
|
79
|
-
|
|
80
|
-
- [For JavaScript uses typed arrays and NAPI for zero-copy calls](https://ashvardanian.com/posts/javascript-ai-vector-search/).
|
|
77
|
+
|
|
81
78
|
|
|
82
79
|
## Comparison with FAISS
|
|
83
80
|
|
|
@@ -119,17 +116,15 @@ USearch is compact and broadly compatible without sacrificing performance, prima
|
|
|
119
116
|
Base functionality is identical to FAISS, and the interface must be familiar if you have ever investigated Approximate Nearest Neighbors search:
|
|
120
117
|
|
|
121
118
|
```py
|
|
122
|
-
# pip install
|
|
119
|
+
# pip install usearch
|
|
123
120
|
|
|
124
121
|
import numpy as np
|
|
125
122
|
from usearch.index import Index
|
|
126
123
|
|
|
127
|
-
index = Index(ndim=3)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
index.
|
|
131
|
-
|
|
132
|
-
matches = index.search(vector, 10)
|
|
124
|
+
index = Index(ndim=3) # Default settings for 3D vectors
|
|
125
|
+
vector = np.array([0.2, 0.6, 0.4]) # Can be a matrix for batch operations
|
|
126
|
+
index.add(42, vector) # Add one or many vectors in parallel
|
|
127
|
+
matches = index.search(vector, 10) # Find 10 nearest neighbors
|
|
133
128
|
|
|
134
129
|
assert matches[0].key == 42
|
|
135
130
|
assert matches[0].distance <= 0.001
|
|
@@ -137,13 +132,13 @@ assert np.allclose(index[42], vector, atol=0.1) # Ensure high tolerance in mixed
|
|
|
137
132
|
```
|
|
138
133
|
|
|
139
134
|
More settings are always available, and the API is designed to be as flexible as possible.
|
|
140
|
-
The default storage/quantization level is hardware-dependant for efficiency, but `
|
|
135
|
+
The default storage/quantization level is hardware-dependant for efficiency, but `bf16` is recommended for most modern CPUs.
|
|
141
136
|
|
|
142
137
|
```py
|
|
143
138
|
index = Index(
|
|
144
139
|
ndim=3, # Define the number of dimensions in input vectors
|
|
145
140
|
metric='cos', # Choose 'l2sq', 'ip', 'haversine' or other metric, default = 'cos'
|
|
146
|
-
dtype='
|
|
141
|
+
dtype='bf16', # Store as 'f64', 'f32', 'f16', 'i8', 'b1'..., default = None
|
|
147
142
|
connectivity=16, # Optional: Limit number of neighbors per graph node
|
|
148
143
|
expansion_add=128, # Optional: Control the recall of indexing
|
|
149
144
|
expansion_search=64, # Optional: Control the quality of the search
|
|
@@ -549,7 +544,7 @@ doi = {10.5281/zenodo.7949416},
|
|
|
549
544
|
author = {Vardanian, Ash},
|
|
550
545
|
title = {{USearch by Unum Cloud}},
|
|
551
546
|
url = {https://github.com/unum-cloud/usearch},
|
|
552
|
-
version = {2.
|
|
547
|
+
version = {2.15.0},
|
|
553
548
|
year = {2023},
|
|
554
549
|
month = oct,
|
|
555
550
|
}
|
package/cpp/bench.cpp
CHANGED
|
@@ -468,6 +468,7 @@ struct args_t {
|
|
|
468
468
|
|
|
469
469
|
bool big = false;
|
|
470
470
|
|
|
471
|
+
bool quantize_bf16 = false;
|
|
471
472
|
bool quantize_f16 = false;
|
|
472
473
|
bool quantize_i8 = false;
|
|
473
474
|
bool quantize_b1 = false;
|
|
@@ -500,6 +501,8 @@ struct args_t {
|
|
|
500
501
|
}
|
|
501
502
|
|
|
502
503
|
scalar_kind_t quantization() const noexcept {
|
|
504
|
+
if (quantize_bf16)
|
|
505
|
+
return scalar_kind_t::bf16_k;
|
|
503
506
|
if (quantize_f16)
|
|
504
507
|
return scalar_kind_t::f16_k;
|
|
505
508
|
if (quantize_i8)
|
|
@@ -611,6 +614,7 @@ int main(int argc, char** argv) {
|
|
|
611
614
|
(option("--rows-skip") & value("integer", args.vectors_to_skip)).doc("Number of vectors to skip"),
|
|
612
615
|
(option("--rows-take") & value("integer", args.vectors_to_take)).doc("Number of vectors to take"),
|
|
613
616
|
( //
|
|
617
|
+
option("-bf16", "--bf16quant").set(args.quantize_bf16).doc("Enable `bf16_t` quantization") |
|
|
614
618
|
option("-f16", "--f16quant").set(args.quantize_f16).doc("Enable `f16_t` quantization") |
|
|
615
619
|
option("-i8", "--i8quant").set(args.quantize_i8).doc("Enable `i8_t` quantization") |
|
|
616
620
|
option("-b1", "--b1quant").set(args.quantize_b1).doc("Enable `b1x8_t` quantization")),
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
#define UNUM_USEARCH_HPP
|
|
9
9
|
|
|
10
10
|
#define USEARCH_VERSION_MAJOR 2
|
|
11
|
-
#define USEARCH_VERSION_MINOR
|
|
12
|
-
#define USEARCH_VERSION_PATCH
|
|
11
|
+
#define USEARCH_VERSION_MINOR 15
|
|
12
|
+
#define USEARCH_VERSION_PATCH 0
|
|
13
13
|
|
|
14
14
|
// Inferring C++ version
|
|
15
15
|
// https://stackoverflow.com/a/61552074
|
|
@@ -2870,16 +2870,16 @@ class index_gt {
|
|
|
2870
2870
|
result.computed_distances = context.computed_distances;
|
|
2871
2871
|
result.visited_members = context.iteration_cycles;
|
|
2872
2872
|
|
|
2873
|
-
// If we are updating the entry node itself, it won't contain any neighbors,
|
|
2874
|
-
// so we should traverse a level down to find the closest match.
|
|
2875
|
-
if (updated_node_level == max_level_copy)
|
|
2876
|
-
updated_node_level--;
|
|
2877
|
-
|
|
2878
2873
|
// Go down the level, tracking only the closest match;
|
|
2879
2874
|
// It may even be equal to the `updated_slot`
|
|
2880
|
-
compressed_slot_t closest_slot =
|
|
2881
|
-
|
|
2882
|
-
|
|
2875
|
+
compressed_slot_t closest_slot =
|
|
2876
|
+
// If we are updating the entry node itself, it won't contain any neighbors,
|
|
2877
|
+
// so we should traverse a level down to find the closest match.
|
|
2878
|
+
updated_node_level == max_level_copy //
|
|
2879
|
+
? entry_slot_copy
|
|
2880
|
+
: search_for_one_( //
|
|
2881
|
+
value, metric, prefetch, //
|
|
2882
|
+
entry_slot_copy, max_level_copy, updated_node_level, context);
|
|
2883
2883
|
|
|
2884
2884
|
// From `updated_node_level` down - perform proper extensive search
|
|
2885
2885
|
for (level_t level = (std::min)(updated_node_level, max_level_copy); level >= 0; --level) {
|
|
@@ -2943,7 +2943,7 @@ class index_gt {
|
|
|
2943
2943
|
config.expansion = default_expansion_search();
|
|
2944
2944
|
|
|
2945
2945
|
// Using references is cleaner, but would result in UBSan false positives
|
|
2946
|
-
context_t* context_ptr = contexts_.data() + config.thread;
|
|
2946
|
+
context_t* context_ptr = contexts_.data() ? contexts_.data() + config.thread : nullptr;
|
|
2947
2947
|
top_candidates_t* top_ptr = context_ptr ? &context_ptr->top_candidates : nullptr;
|
|
2948
2948
|
search_result_t result{*this, top_ptr};
|
|
2949
2949
|
if (!nodes_count_.load(std::memory_order_relaxed))
|
|
@@ -3697,6 +3697,7 @@ class index_gt {
|
|
|
3697
3697
|
inline neighbors_ref_t neighbors_base_(node_t node) const noexcept { return {node.neighbors_tape()}; }
|
|
3698
3698
|
|
|
3699
3699
|
inline neighbors_ref_t neighbors_non_base_(node_t node, level_t level) const noexcept {
|
|
3700
|
+
usearch_assert_m(level > 0 && level <= node.level(), "Linking to missing level");
|
|
3700
3701
|
return {node.neighbors_tape() + pre_.neighbors_base_bytes + (level - 1) * pre_.neighbors_bytes};
|
|
3701
3702
|
}
|
|
3702
3703
|
|
|
@@ -950,7 +950,7 @@ class index_dense_gt {
|
|
|
950
950
|
|
|
951
951
|
void reserve(index_limits_t limits) {
|
|
952
952
|
if (!try_reserve(limits))
|
|
953
|
-
|
|
953
|
+
__usearch_raise_runtime_error("failed to reserve memory");
|
|
954
954
|
}
|
|
955
955
|
|
|
956
956
|
/**
|
|
@@ -2240,6 +2240,7 @@ class index_dense_gt {
|
|
|
2240
2240
|
case scalar_kind_t::f64_k: return make_casts_<f64_t>();
|
|
2241
2241
|
case scalar_kind_t::f32_k: return make_casts_<f32_t>();
|
|
2242
2242
|
case scalar_kind_t::f16_k: return make_casts_<f16_t>();
|
|
2243
|
+
case scalar_kind_t::bf16_k: return make_casts_<bf16_t>();
|
|
2243
2244
|
case scalar_kind_t::i8_k: return make_casts_<i8_t>();
|
|
2244
2245
|
case scalar_kind_t::b1x8_k: return make_casts_<b1x8_t>();
|
|
2245
2246
|
default: return {};
|
|
@@ -70,20 +70,10 @@ struct uuid_t {
|
|
|
70
70
|
};
|
|
71
71
|
|
|
72
72
|
class f16_bits_t;
|
|
73
|
+
class bf16_bits_t;
|
|
73
74
|
|
|
74
|
-
#if !USEARCH_USE_FP16LIB
|
|
75
|
-
#if USEARCH_USE_SIMSIMD
|
|
76
|
-
using f16_native_t = simsimd_f16_t;
|
|
77
|
-
#elif defined(USEARCH_DEFINED_ARM)
|
|
78
|
-
using f16_native_t = __fp16;
|
|
79
|
-
#else
|
|
80
|
-
using f16_native_t = _Float16;
|
|
81
|
-
#endif
|
|
82
|
-
using f16_t = f16_native_t;
|
|
83
|
-
#else
|
|
84
|
-
using f16_native_t = void;
|
|
85
75
|
using f16_t = f16_bits_t;
|
|
86
|
-
|
|
76
|
+
using bf16_t = bf16_bits_t;
|
|
87
77
|
|
|
88
78
|
using f64_t = double;
|
|
89
79
|
using f32_t = float;
|
|
@@ -132,6 +122,7 @@ enum class scalar_kind_t : std::uint8_t {
|
|
|
132
122
|
b1x8_k = 1,
|
|
133
123
|
u40_k = 2,
|
|
134
124
|
uuid_k = 3,
|
|
125
|
+
bf16_k = 4,
|
|
135
126
|
// Common:
|
|
136
127
|
f64_k = 10,
|
|
137
128
|
f32_k = 11,
|
|
@@ -148,12 +139,6 @@ enum class scalar_kind_t : std::uint8_t {
|
|
|
148
139
|
i8_k = 23,
|
|
149
140
|
};
|
|
150
141
|
|
|
151
|
-
enum class prefetching_kind_t {
|
|
152
|
-
none_k,
|
|
153
|
-
cpu_k,
|
|
154
|
-
io_uring_k,
|
|
155
|
-
};
|
|
156
|
-
|
|
157
142
|
template <typename scalar_at> scalar_kind_t scalar_kind() noexcept {
|
|
158
143
|
if (std::is_same<scalar_at, b1x8_t>())
|
|
159
144
|
return scalar_kind_t::b1x8_k;
|
|
@@ -167,6 +152,8 @@ template <typename scalar_at> scalar_kind_t scalar_kind() noexcept {
|
|
|
167
152
|
return scalar_kind_t::f32_k;
|
|
168
153
|
if (std::is_same<scalar_at, f16_t>())
|
|
169
154
|
return scalar_kind_t::f16_k;
|
|
155
|
+
if (std::is_same<scalar_at, bf16_t>())
|
|
156
|
+
return scalar_kind_t::bf16_k;
|
|
170
157
|
if (std::is_same<scalar_at, i8_t>())
|
|
171
158
|
return scalar_kind_t::i8_k;
|
|
172
159
|
if (std::is_same<scalar_at, u64_t>())
|
|
@@ -208,6 +195,7 @@ inline std::size_t bits_per_scalar(scalar_kind_t scalar_kind) noexcept {
|
|
|
208
195
|
switch (scalar_kind) {
|
|
209
196
|
case scalar_kind_t::uuid_k: return 128;
|
|
210
197
|
case scalar_kind_t::u40_k: return 40;
|
|
198
|
+
case scalar_kind_t::bf16_k: return 16;
|
|
211
199
|
case scalar_kind_t::b1x8_k: return 1;
|
|
212
200
|
case scalar_kind_t::u64_k: return 64;
|
|
213
201
|
case scalar_kind_t::i64_k: return 64;
|
|
@@ -229,6 +217,7 @@ inline std::size_t bits_per_scalar_word(scalar_kind_t scalar_kind) noexcept {
|
|
|
229
217
|
switch (scalar_kind) {
|
|
230
218
|
case scalar_kind_t::uuid_k: return 128;
|
|
231
219
|
case scalar_kind_t::u40_k: return 40;
|
|
220
|
+
case scalar_kind_t::bf16_k: return 16;
|
|
232
221
|
case scalar_kind_t::b1x8_k: return 8;
|
|
233
222
|
case scalar_kind_t::u64_k: return 64;
|
|
234
223
|
case scalar_kind_t::i64_k: return 64;
|
|
@@ -250,6 +239,7 @@ inline char const* scalar_kind_name(scalar_kind_t scalar_kind) noexcept {
|
|
|
250
239
|
switch (scalar_kind) {
|
|
251
240
|
case scalar_kind_t::uuid_k: return "uuid";
|
|
252
241
|
case scalar_kind_t::u40_k: return "u40";
|
|
242
|
+
case scalar_kind_t::bf16_k: return "bf16";
|
|
253
243
|
case scalar_kind_t::b1x8_k: return "b1x8";
|
|
254
244
|
case scalar_kind_t::u64_k: return "u64";
|
|
255
245
|
case scalar_kind_t::i64_k: return "i64";
|
|
@@ -291,10 +281,14 @@ inline expected_gt<scalar_kind_t> scalar_kind_from_name(char const* name, std::s
|
|
|
291
281
|
parsed.result = scalar_kind_t::f64_k;
|
|
292
282
|
else if (str_equals(name, len, "f16"))
|
|
293
283
|
parsed.result = scalar_kind_t::f16_k;
|
|
284
|
+
else if (str_equals(name, len, "bf16"))
|
|
285
|
+
parsed.result = scalar_kind_t::bf16_k;
|
|
294
286
|
else if (str_equals(name, len, "i8"))
|
|
295
287
|
parsed.result = scalar_kind_t::i8_k;
|
|
288
|
+
else if (str_equals(name, len, "b1"))
|
|
289
|
+
parsed.result = scalar_kind_t::b1x8_k;
|
|
296
290
|
else
|
|
297
|
-
parsed.failed("Unknown type, choose: f32, f16,
|
|
291
|
+
parsed.failed("Unknown type, choose: f64, f32, f16, bf16, i8, b1");
|
|
298
292
|
return parsed;
|
|
299
293
|
}
|
|
300
294
|
|
|
@@ -327,29 +321,76 @@ inline expected_gt<metric_kind_t> metric_from_name(char const* name, std::size_t
|
|
|
327
321
|
"tanimoto, sorensen");
|
|
328
322
|
return parsed;
|
|
329
323
|
}
|
|
330
|
-
|
|
331
324
|
inline expected_gt<metric_kind_t> metric_from_name(char const* name) {
|
|
332
325
|
return metric_from_name(name, std::strlen(name));
|
|
333
326
|
}
|
|
334
327
|
|
|
328
|
+
/**
|
|
329
|
+
* @brief Convenience function to upcast a half-precision floating point number to a single-precision one.
|
|
330
|
+
*/
|
|
335
331
|
inline float f16_to_f32(std::uint16_t u16) noexcept {
|
|
336
|
-
#if
|
|
332
|
+
#if USEARCH_USE_FP16LIB
|
|
333
|
+
return fp16_ieee_to_fp32_value(u16);
|
|
334
|
+
#elif USEARCH_USE_SIMSIMD
|
|
335
|
+
return simsimd_uncompress_f16(u16);
|
|
336
|
+
#else
|
|
337
337
|
f16_native_t f16;
|
|
338
338
|
std::memcpy(&f16, &u16, sizeof(std::uint16_t));
|
|
339
339
|
return float(f16);
|
|
340
|
-
#else
|
|
341
|
-
return fp16_ieee_to_fp32_value(u16);
|
|
342
340
|
#endif
|
|
343
341
|
}
|
|
344
342
|
|
|
343
|
+
/**
|
|
344
|
+
* @brief Convenience function to downcast a single-precision floating point number to a half-precision one.
|
|
345
|
+
*/
|
|
345
346
|
inline std::uint16_t f32_to_f16(float f32) noexcept {
|
|
346
|
-
#if
|
|
347
|
+
#if USEARCH_USE_FP16LIB
|
|
348
|
+
return fp16_ieee_from_fp32_value(f32);
|
|
349
|
+
#elif USEARCH_USE_SIMSIMD
|
|
350
|
+
return simsimd_compress_f16(f32);
|
|
351
|
+
#else
|
|
347
352
|
f16_native_t f16 = f16_native_t(f32);
|
|
348
353
|
std::uint16_t u16;
|
|
349
354
|
std::memcpy(&u16, &f16, sizeof(std::uint16_t));
|
|
350
355
|
return u16;
|
|
356
|
+
#endif
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* @brief Convenience function to upcast a brain-floating point number to a single-precision one.
|
|
361
|
+
* https://github.com/ashvardanian/SimSIMD/blob/ff51434d90c66f916e94ff05b24530b127aa4cff/include/simsimd/types.h#L395-L410
|
|
362
|
+
*/
|
|
363
|
+
inline float bf16_to_f32(std::uint16_t u16) noexcept {
|
|
364
|
+
#if USEARCH_USE_SIMSIMD
|
|
365
|
+
return simsimd_uncompress_bf16(u16);
|
|
351
366
|
#else
|
|
352
|
-
|
|
367
|
+
union float_or_unsigned_int_t {
|
|
368
|
+
float f;
|
|
369
|
+
unsigned int i;
|
|
370
|
+
};
|
|
371
|
+
union float_or_unsigned_int_t result_union;
|
|
372
|
+
result_union.i = u16 << 16; // Zero extends the mantissa
|
|
373
|
+
return result_union.f;
|
|
374
|
+
#endif
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* @brief Convenience function to downcast a single-precision floating point number to a brain-floating point one.
|
|
379
|
+
* https://github.com/ashvardanian/SimSIMD/blob/ff51434d90c66f916e94ff05b24530b127aa4cff/include/simsimd/types.h#L412-L425
|
|
380
|
+
*/
|
|
381
|
+
inline std::uint16_t f32_to_bf16(float f32) noexcept {
|
|
382
|
+
#if USEARCH_USE_SIMSIMD
|
|
383
|
+
return simsimd_compress_bf16(f32);
|
|
384
|
+
#else
|
|
385
|
+
union float_or_unsigned_int_t {
|
|
386
|
+
float f;
|
|
387
|
+
unsigned int i;
|
|
388
|
+
};
|
|
389
|
+
union float_or_unsigned_int_t value;
|
|
390
|
+
value.f = f32;
|
|
391
|
+
value.i >>= 16;
|
|
392
|
+
value.i &= 0xFFFF;
|
|
393
|
+
return (unsigned short)value.i;
|
|
353
394
|
#endif
|
|
354
395
|
}
|
|
355
396
|
|
|
@@ -412,6 +453,65 @@ class f16_bits_t {
|
|
|
412
453
|
}
|
|
413
454
|
};
|
|
414
455
|
|
|
456
|
+
/**
|
|
457
|
+
* @brief Numeric type for brain-floating point half-precision floating point.
|
|
458
|
+
* If hardware support isn't available, falls back to a hardware
|
|
459
|
+
* agnostic in-software implementation.
|
|
460
|
+
*/
|
|
461
|
+
class bf16_bits_t {
|
|
462
|
+
std::uint16_t uint16_{};
|
|
463
|
+
|
|
464
|
+
public:
|
|
465
|
+
inline bf16_bits_t() noexcept : uint16_(0) {}
|
|
466
|
+
inline bf16_bits_t(bf16_bits_t&&) = default;
|
|
467
|
+
inline bf16_bits_t& operator=(bf16_bits_t&&) = default;
|
|
468
|
+
inline bf16_bits_t(bf16_bits_t const&) = default;
|
|
469
|
+
inline bf16_bits_t& operator=(bf16_bits_t const&) = default;
|
|
470
|
+
|
|
471
|
+
inline operator float() const noexcept { return bf16_to_f32(uint16_); }
|
|
472
|
+
inline explicit operator bool() const noexcept { return bf16_to_f32(uint16_) > 0.5f; }
|
|
473
|
+
|
|
474
|
+
inline bf16_bits_t(std::int8_t v) noexcept : uint16_(f32_to_bf16(v)) {}
|
|
475
|
+
inline bf16_bits_t(bool v) noexcept : uint16_(f32_to_bf16(v)) {}
|
|
476
|
+
inline bf16_bits_t(float v) noexcept : uint16_(f32_to_bf16(v)) {}
|
|
477
|
+
inline bf16_bits_t(double v) noexcept : uint16_(f32_to_bf16(static_cast<float>(v))) {}
|
|
478
|
+
|
|
479
|
+
inline bool operator<(bf16_bits_t const& other) const noexcept { return float(*this) < float(other); }
|
|
480
|
+
|
|
481
|
+
inline bf16_bits_t operator+(bf16_bits_t other) const noexcept { return {float(*this) + float(other)}; }
|
|
482
|
+
inline bf16_bits_t operator-(bf16_bits_t other) const noexcept { return {float(*this) - float(other)}; }
|
|
483
|
+
inline bf16_bits_t operator*(bf16_bits_t other) const noexcept { return {float(*this) * float(other)}; }
|
|
484
|
+
inline bf16_bits_t operator/(bf16_bits_t other) const noexcept { return {float(*this) / float(other)}; }
|
|
485
|
+
inline float operator+(float other) const noexcept { return float(*this) + other; }
|
|
486
|
+
inline float operator-(float other) const noexcept { return float(*this) - other; }
|
|
487
|
+
inline float operator*(float other) const noexcept { return float(*this) * other; }
|
|
488
|
+
inline float operator/(float other) const noexcept { return float(*this) / other; }
|
|
489
|
+
inline double operator+(double other) const noexcept { return float(*this) + other; }
|
|
490
|
+
inline double operator-(double other) const noexcept { return float(*this) - other; }
|
|
491
|
+
inline double operator*(double other) const noexcept { return float(*this) * other; }
|
|
492
|
+
inline double operator/(double other) const noexcept { return float(*this) / other; }
|
|
493
|
+
|
|
494
|
+
inline bf16_bits_t& operator+=(float v) noexcept {
|
|
495
|
+
uint16_ = f32_to_bf16(v + bf16_to_f32(uint16_));
|
|
496
|
+
return *this;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
inline bf16_bits_t& operator-=(float v) noexcept {
|
|
500
|
+
uint16_ = f32_to_bf16(v - bf16_to_f32(uint16_));
|
|
501
|
+
return *this;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
inline bf16_bits_t& operator*=(float v) noexcept {
|
|
505
|
+
uint16_ = f32_to_bf16(v * bf16_to_f32(uint16_));
|
|
506
|
+
return *this;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
inline bf16_bits_t& operator/=(float v) noexcept {
|
|
510
|
+
uint16_ = f32_to_bf16(v / bf16_to_f32(uint16_));
|
|
511
|
+
return *this;
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
|
|
415
515
|
/**
|
|
416
516
|
* @brief An STL-based executor or a "thread-pool" for parallel execution.
|
|
417
517
|
* Isn't efficient for small batches, as it recreates the threads on every call.
|
|
@@ -936,6 +1036,10 @@ template <> struct cast_gt<f16_bits_t, f16_bits_t> {
|
|
|
936
1036
|
static bool try_(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
|
|
937
1037
|
};
|
|
938
1038
|
|
|
1039
|
+
template <> struct cast_gt<bf16_bits_t, bf16_bits_t> {
|
|
1040
|
+
static bool try_(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
|
|
1041
|
+
};
|
|
1042
|
+
|
|
939
1043
|
template <> struct cast_gt<i8_t, i8_t> {
|
|
940
1044
|
static bool try_(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
|
|
941
1045
|
};
|
|
@@ -1008,19 +1112,23 @@ template <typename to_scalar_at> struct cast_from_i8_gt {
|
|
|
1008
1112
|
}
|
|
1009
1113
|
};
|
|
1010
1114
|
|
|
1011
|
-
template <> struct cast_gt<i8_t,
|
|
1115
|
+
template <> struct cast_gt<i8_t, f16_bits_t> : public cast_from_i8_gt<f16_t> {};
|
|
1116
|
+
template <> struct cast_gt<i8_t, bf16_bits_t> : public cast_from_i8_gt<bf16_t> {};
|
|
1012
1117
|
template <> struct cast_gt<i8_t, f32_t> : public cast_from_i8_gt<f32_t> {};
|
|
1013
1118
|
template <> struct cast_gt<i8_t, f64_t> : public cast_from_i8_gt<f64_t> {};
|
|
1014
1119
|
|
|
1015
|
-
template <> struct cast_gt<
|
|
1120
|
+
template <> struct cast_gt<f16_bits_t, i8_t> : public cast_to_i8_gt<f16_t> {};
|
|
1121
|
+
template <> struct cast_gt<bf16_bits_t, i8_t> : public cast_to_i8_gt<bf16_t> {};
|
|
1016
1122
|
template <> struct cast_gt<f32_t, i8_t> : public cast_to_i8_gt<f32_t> {};
|
|
1017
1123
|
template <> struct cast_gt<f64_t, i8_t> : public cast_to_i8_gt<f64_t> {};
|
|
1018
1124
|
|
|
1019
|
-
template <> struct cast_gt<b1x8_t,
|
|
1125
|
+
template <> struct cast_gt<b1x8_t, f16_bits_t> : public cast_from_b1x8_gt<f16_t> {};
|
|
1126
|
+
template <> struct cast_gt<b1x8_t, bf16_bits_t> : public cast_from_b1x8_gt<bf16_t> {};
|
|
1020
1127
|
template <> struct cast_gt<b1x8_t, f32_t> : public cast_from_b1x8_gt<f32_t> {};
|
|
1021
1128
|
template <> struct cast_gt<b1x8_t, f64_t> : public cast_from_b1x8_gt<f64_t> {};
|
|
1022
1129
|
|
|
1023
|
-
template <> struct cast_gt<
|
|
1130
|
+
template <> struct cast_gt<f16_bits_t, b1x8_t> : public cast_to_b1x8_gt<f16_t> {};
|
|
1131
|
+
template <> struct cast_gt<bf16_bits_t, b1x8_t> : public cast_to_b1x8_gt<bf16_t> {};
|
|
1024
1132
|
template <> struct cast_gt<f32_t, b1x8_t> : public cast_to_b1x8_gt<f32_t> {};
|
|
1025
1133
|
template <> struct cast_gt<f64_t, b1x8_t> : public cast_to_b1x8_gt<f64_t> {};
|
|
1026
1134
|
|
|
@@ -1602,6 +1710,7 @@ class metric_punned_t {
|
|
|
1602
1710
|
case scalar_kind_t::f32_k: datatype = simsimd_datatype_f32_k; break;
|
|
1603
1711
|
case scalar_kind_t::f64_k: datatype = simsimd_datatype_f64_k; break;
|
|
1604
1712
|
case scalar_kind_t::f16_k: datatype = simsimd_datatype_f16_k; break;
|
|
1713
|
+
case scalar_kind_t::bf16_k: datatype = simsimd_datatype_bf16_k; break;
|
|
1605
1714
|
case scalar_kind_t::i8_k: datatype = simsimd_datatype_i8_k; break;
|
|
1606
1715
|
case scalar_kind_t::b1x8_k: datatype = simsimd_datatype_b8_k; break;
|
|
1607
1716
|
default: break;
|
|
@@ -1654,9 +1763,10 @@ class metric_punned_t {
|
|
|
1654
1763
|
switch (metric_kind_) {
|
|
1655
1764
|
case metric_kind_t::ip_k: {
|
|
1656
1765
|
switch (scalar_kind_) {
|
|
1657
|
-
case scalar_kind_t::
|
|
1658
|
-
case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<f16_t, f32_t>>; break;
|
|
1766
|
+
case scalar_kind_t::bf16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<bf16_t, f32_t>>; break;
|
|
1659
1767
|
case scalar_kind_t::i8_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<i8_t, f32_t>>; break;
|
|
1768
|
+
case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<f16_t, f32_t>>; break;
|
|
1769
|
+
case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<f32_t>>; break;
|
|
1660
1770
|
case scalar_kind_t::f64_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<f64_t>>; break;
|
|
1661
1771
|
default: metric_ptr_ = 0; break;
|
|
1662
1772
|
}
|
|
@@ -1664,9 +1774,10 @@ class metric_punned_t {
|
|
|
1664
1774
|
}
|
|
1665
1775
|
case metric_kind_t::cos_k: {
|
|
1666
1776
|
switch (scalar_kind_) {
|
|
1667
|
-
case scalar_kind_t::
|
|
1668
|
-
case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<f16_t, f32_t>>; break;
|
|
1777
|
+
case scalar_kind_t::bf16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<bf16_t, f32_t>>; break;
|
|
1669
1778
|
case scalar_kind_t::i8_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<i8_t, f32_t>>; break;
|
|
1779
|
+
case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<f16_t, f32_t>>; break;
|
|
1780
|
+
case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<f32_t>>; break;
|
|
1670
1781
|
case scalar_kind_t::f64_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<f64_t>>; break;
|
|
1671
1782
|
default: metric_ptr_ = 0; break;
|
|
1672
1783
|
}
|
|
@@ -1674,9 +1785,10 @@ class metric_punned_t {
|
|
|
1674
1785
|
}
|
|
1675
1786
|
case metric_kind_t::l2sq_k: {
|
|
1676
1787
|
switch (scalar_kind_) {
|
|
1677
|
-
case scalar_kind_t::
|
|
1678
|
-
case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<f16_t, f32_t>>; break;
|
|
1788
|
+
case scalar_kind_t::bf16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<bf16_t, f32_t>>; break;
|
|
1679
1789
|
case scalar_kind_t::i8_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<i8_t, f32_t>>; break;
|
|
1790
|
+
case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<f16_t, f32_t>>; break;
|
|
1791
|
+
case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<f32_t>>; break;
|
|
1680
1792
|
case scalar_kind_t::f64_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<f64_t>>; break;
|
|
1681
1793
|
default: metric_ptr_ = 0; break;
|
|
1682
1794
|
}
|
|
@@ -1684,6 +1796,9 @@ class metric_punned_t {
|
|
|
1684
1796
|
}
|
|
1685
1797
|
case metric_kind_t::pearson_k: {
|
|
1686
1798
|
switch (scalar_kind_) {
|
|
1799
|
+
case scalar_kind_t::bf16_k:
|
|
1800
|
+
metric_ptr_ = (uptr_t)&equidimensional_<metric_pearson_gt<bf16_t, f32_t>>;
|
|
1801
|
+
break;
|
|
1687
1802
|
case scalar_kind_t::i8_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_pearson_gt<i8_t, f32_t>>; break;
|
|
1688
1803
|
case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_pearson_gt<f16_t, f32_t>>; break;
|
|
1689
1804
|
case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_pearson_gt<f32_t>>; break;
|
|
@@ -1694,7 +1809,8 @@ class metric_punned_t {
|
|
|
1694
1809
|
}
|
|
1695
1810
|
case metric_kind_t::haversine_k: {
|
|
1696
1811
|
switch (scalar_kind_) {
|
|
1697
|
-
case scalar_kind_t::
|
|
1812
|
+
case scalar_kind_t::bf16_k: metric_ptr_ = 0; break; //< Half-precision 2D vectors are silly.
|
|
1813
|
+
case scalar_kind_t::f16_k: metric_ptr_ = 0; break; //< Half-precision 2D vectors are silly.
|
|
1698
1814
|
case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_haversine_gt<f32_t>>; break;
|
|
1699
1815
|
case scalar_kind_t::f64_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_haversine_gt<f64_t>>; break;
|
|
1700
1816
|
default: metric_ptr_ = 0; break;
|
|
@@ -1703,6 +1819,9 @@ class metric_punned_t {
|
|
|
1703
1819
|
}
|
|
1704
1820
|
case metric_kind_t::divergence_k: {
|
|
1705
1821
|
switch (scalar_kind_) {
|
|
1822
|
+
case scalar_kind_t::bf16_k:
|
|
1823
|
+
metric_ptr_ = (uptr_t)&equidimensional_<metric_divergence_gt<bf16_t, f32_t>>;
|
|
1824
|
+
break;
|
|
1706
1825
|
case scalar_kind_t::f16_k:
|
|
1707
1826
|
metric_ptr_ = (uptr_t)&equidimensional_<metric_divergence_gt<f16_t, f32_t>>;
|
|
1708
1827
|
break;
|
|
@@ -1962,7 +2081,7 @@ class flat_hash_multi_set_gt {
|
|
|
1962
2081
|
// Allocate new memory
|
|
1963
2082
|
data_ = (char*)allocator_t{}.allocate(other.buckets_ * bytes_per_bucket());
|
|
1964
2083
|
if (!data_)
|
|
1965
|
-
|
|
2084
|
+
__usearch_raise_runtime_error("failed memory allocation");
|
|
1966
2085
|
|
|
1967
2086
|
// Copy metadata
|
|
1968
2087
|
buckets_ = other.buckets_;
|
|
@@ -2002,7 +2121,7 @@ class flat_hash_multi_set_gt {
|
|
|
2002
2121
|
// Allocate new memory
|
|
2003
2122
|
data_ = (char*)allocator_t{}.allocate(other.buckets_ * bytes_per_bucket());
|
|
2004
2123
|
if (!data_)
|
|
2005
|
-
|
|
2124
|
+
__usearch_raise_runtime_error("failed memory allocation");
|
|
2006
2125
|
|
|
2007
2126
|
// Copy metadata
|
|
2008
2127
|
buckets_ = other.buckets_;
|
|
@@ -2375,7 +2494,7 @@ class flat_hash_multi_set_gt {
|
|
|
2375
2494
|
|
|
2376
2495
|
void reserve(std::size_t capacity) {
|
|
2377
2496
|
if (!try_reserve(capacity))
|
|
2378
|
-
|
|
2497
|
+
__usearch_raise_runtime_error("failed to reserve memory");
|
|
2379
2498
|
}
|
|
2380
2499
|
|
|
2381
2500
|
bool try_emplace(element_t const& element) noexcept {
|
package/javascript/usearch.ts
CHANGED
|
@@ -70,6 +70,7 @@ export enum ScalarKind {
|
|
|
70
70
|
F32 = "f32",
|
|
71
71
|
F64 = "f64",
|
|
72
72
|
F16 = "f16",
|
|
73
|
+
BF16 = "bf16",
|
|
73
74
|
I8 = "i8",
|
|
74
75
|
B1 = "b1",
|
|
75
76
|
}
|
|
@@ -610,12 +611,12 @@ function exactSearch(
|
|
|
610
611
|
}
|
|
611
612
|
|
|
612
613
|
const usearch = {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
614
|
+
Index,
|
|
615
|
+
MetricKind,
|
|
616
|
+
ScalarKind,
|
|
617
|
+
Matches,
|
|
618
|
+
BatchMatches,
|
|
619
|
+
exactSearch,
|
|
619
620
|
};
|
|
620
621
|
export default usearch;
|
|
621
622
|
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|