usearch 2.13.4 → 2.14.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 +21 -18
- package/c/usearch.h +11 -0
- package/include/usearch/index.hpp +12 -11
- package/include/usearch/index_dense.hpp +12 -6
- package/include/usearch/index_plugins.hpp +236 -71
- 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
|
|
@@ -254,7 +249,7 @@ assert!(
|
|
|
254
249
|
Training a quantization model and dimension-reduction is a common approach to accelerate vector search.
|
|
255
250
|
Those, however, are only sometimes reliable, can significantly affect the statistical properties of your data, and require regular adjustments if your distribution shifts.
|
|
256
251
|
Instead, we have focused on high-precision arithmetic over low-precision downcasted vectors.
|
|
257
|
-
The same index, and `add` and `search` operations will automatically down-cast or up-cast between `f64_t`, `f32_t`, `f16_t`, `i8_t`, and single-bit representations.
|
|
252
|
+
The same index, and `add` and `search` operations will automatically down-cast or up-cast between `f64_t`, `f32_t`, `f16_t`, `i8_t`, and single-bit `b1x8_t` representations.
|
|
258
253
|
You can use the following command to check, if hardware acceleration is enabled:
|
|
259
254
|
|
|
260
255
|
```sh
|
|
@@ -264,11 +259,19 @@ $ python -c 'from usearch.index import Index; print(Index(ndim=166, metric="tani
|
|
|
264
259
|
> ice
|
|
265
260
|
```
|
|
266
261
|
|
|
262
|
+
In most cases, it's recommended to use half-precision floating-point numbers on modern hardware.
|
|
263
|
+
When quantization is enabled, the "get"-like functions won't be able to recover the original data, so you may want to replicate the original vectors elsewhere.
|
|
264
|
+
When quantizing to `i8_t` integers, note that it's only valid for cosine-like metrics.
|
|
265
|
+
As part of the quantization process, the vectors are normalized to unit length and later scaled to [-127, 127] range to occupy the full 8-bit range.
|
|
266
|
+
When quantizing to `b1x8_t` single-bit representations, note that it's only valid for binary metrics like Jaccard, Hamming, etc.
|
|
267
|
+
As part of the quantization process, the scalar components greater than zero are set to `true`, and the rest to `false`.
|
|
268
|
+
|
|
269
|
+

|
|
270
|
+
|
|
267
271
|
Using smaller numeric types will save you RAM needed to store the vectors, but you can also compress the neighbors lists forming our proximity graphs.
|
|
268
272
|
By default, 32-bit `uint32_t` is used to enumerate those, which is not enough if you need to address over 4 Billion entries.
|
|
269
273
|
For such cases we provide a custom `uint40_t` type, that will still be 37.5% more space-efficient than the commonly used 8-byte integers, and will scale up to 1 Trillion entries.
|
|
270
274
|
|
|
271
|
-

|
|
272
275
|
|
|
273
276
|
## `Indexes` for Multi-Index Lookups
|
|
274
277
|
|
|
@@ -541,7 +544,7 @@ doi = {10.5281/zenodo.7949416},
|
|
|
541
544
|
author = {Vardanian, Ash},
|
|
542
545
|
title = {{USearch by Unum Cloud}},
|
|
543
546
|
url = {https://github.com/unum-cloud/usearch},
|
|
544
|
-
version = {2.
|
|
547
|
+
version = {2.14.0},
|
|
545
548
|
year = {2023},
|
|
546
549
|
month = oct,
|
|
547
550
|
}
|
package/c/usearch.h
CHANGED
|
@@ -72,6 +72,17 @@ USEARCH_EXPORT typedef struct usearch_init_options_t {
|
|
|
72
72
|
usearch_metric_t metric;
|
|
73
73
|
/**
|
|
74
74
|
* @brief The scalar kind used for quantization of vector data during indexing.
|
|
75
|
+
* In most cases, on modern hardware, it's recommended to use half-precision floating-point numbers.
|
|
76
|
+
* When quantization is enabled, the "get"-like functions won't be able to recover the original data,
|
|
77
|
+
* so you may want to replicate the original vectors elsewhere.
|
|
78
|
+
*
|
|
79
|
+
* Quantizing to integers is also possible, but it's important to note that it's only valid for cosine-like
|
|
80
|
+
* metrics. As part of the quantization process, the vectors are normalized to unit length and later scaled
|
|
81
|
+
* to @b [-127,127] range to occupy the full 8-bit range.
|
|
82
|
+
*
|
|
83
|
+
* Quantizing to 1-bit booleans is also possible, but it's only valid for binary metrics like Jaccard, Hamming,
|
|
84
|
+
* etc. As part of the quantization process, the scalar components greater than zero are set to `true`, and the
|
|
85
|
+
* rest to `false`.
|
|
75
86
|
*/
|
|
76
87
|
usearch_scalar_kind_t quantization;
|
|
77
88
|
/**
|
|
@@ -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 14
|
|
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
|
|
|
@@ -95,7 +95,7 @@ struct index_dense_head_result_t {
|
|
|
95
95
|
* @brief Configuration settings for the construction of dense
|
|
96
96
|
* equidimensional vector indexes.
|
|
97
97
|
*
|
|
98
|
-
* Unlike the underlying `index_gt` class,
|
|
98
|
+
* Unlike the underlying `index_gt` class, incorporates the
|
|
99
99
|
* `::expansion_add` and `::expansion_search` parameters passed
|
|
100
100
|
* separately for the lower-level engine.
|
|
101
101
|
*/
|
|
@@ -207,8 +207,11 @@ struct index_dense_metadata_result_t {
|
|
|
207
207
|
}
|
|
208
208
|
};
|
|
209
209
|
|
|
210
|
+
/**
|
|
211
|
+
* @brief Fixes serialized scalar-kind codes for pre-v2.10 versions, until we can upgrade to v3.
|
|
212
|
+
* The old enum `scalar_kind_t` is defined without explicit constants from 0.
|
|
213
|
+
*/
|
|
210
214
|
inline scalar_kind_t convert_pre_2_10_scalar_kind(scalar_kind_t scalar_kind) noexcept {
|
|
211
|
-
// The old enum `scalar_kind_t` is defined without explicit constants from 0
|
|
212
215
|
switch (static_cast<std::underlying_type<scalar_kind_t>::type>(scalar_kind)) {
|
|
213
216
|
case 0: return scalar_kind_t::unknown_k;
|
|
214
217
|
case 1: return scalar_kind_t::b1x8_k;
|
|
@@ -229,13 +232,15 @@ inline scalar_kind_t convert_pre_2_10_scalar_kind(scalar_kind_t scalar_kind) noe
|
|
|
229
232
|
}
|
|
230
233
|
}
|
|
231
234
|
|
|
235
|
+
/**
|
|
236
|
+
* @brief Fixes the metadata for pre-v2.10 versions, until we can upgrade to v3.
|
|
237
|
+
* Originates from: https://github.com/unum-cloud/usearch/issues/423
|
|
238
|
+
*/
|
|
232
239
|
inline void fix_pre_2_10_metadata(index_dense_head_t& head) {
|
|
233
|
-
// deal with incompatibility with pre-2.10 versions: https://github.com/unum-cloud/usearch/issues/423
|
|
234
240
|
if (head.version_major == 2 && head.version_minor < 10) {
|
|
235
241
|
head.kind_scalar = convert_pre_2_10_scalar_kind(head.kind_scalar);
|
|
236
242
|
head.kind_key = convert_pre_2_10_scalar_kind(head.kind_key);
|
|
237
243
|
head.kind_compressed_slot = convert_pre_2_10_scalar_kind(head.kind_compressed_slot);
|
|
238
|
-
// update minor versions so that no repeated conversion occurs
|
|
239
244
|
head.version_minor = 10;
|
|
240
245
|
head.version_patch = 0;
|
|
241
246
|
}
|
|
@@ -316,7 +321,7 @@ inline index_dense_metadata_result_t index_dense_metadata_from_path(char const*
|
|
|
316
321
|
/**
|
|
317
322
|
* @brief Extracts metadata from a pre-constructed index serialized into an in-memory buffer.
|
|
318
323
|
*/
|
|
319
|
-
inline index_dense_metadata_result_t index_dense_metadata_from_buffer(memory_mapped_file_t file,
|
|
324
|
+
inline index_dense_metadata_result_t index_dense_metadata_from_buffer(memory_mapped_file_t const& file,
|
|
320
325
|
std::size_t offset = 0) noexcept {
|
|
321
326
|
index_dense_metadata_result_t result;
|
|
322
327
|
|
|
@@ -324,7 +329,7 @@ inline index_dense_metadata_result_t index_dense_metadata_from_buffer(memory_map
|
|
|
324
329
|
if (offset + sizeof(index_dense_head_buffer_t) >= file.size())
|
|
325
330
|
return result.failed("End of file reached!");
|
|
326
331
|
|
|
327
|
-
byte_t
|
|
332
|
+
byte_t const* file_data = file.data() + offset;
|
|
328
333
|
std::size_t const file_size = file.size() - offset;
|
|
329
334
|
std::memcpy(&result.head_buffer, file_data, sizeof(index_dense_head_buffer_t));
|
|
330
335
|
|
|
@@ -2235,6 +2240,7 @@ class index_dense_gt {
|
|
|
2235
2240
|
case scalar_kind_t::f64_k: return make_casts_<f64_t>();
|
|
2236
2241
|
case scalar_kind_t::f32_k: return make_casts_<f32_t>();
|
|
2237
2242
|
case scalar_kind_t::f16_k: return make_casts_<f16_t>();
|
|
2243
|
+
case scalar_kind_t::bf16_k: return make_casts_<bf16_t>();
|
|
2238
2244
|
case scalar_kind_t::i8_k: return make_casts_<i8_t>();
|
|
2239
2245
|
case scalar_kind_t::b1x8_k: return make_casts_<b1x8_t>();
|
|
2240
2246
|
default: return {};
|
|
@@ -70,20 +70,26 @@ struct uuid_t {
|
|
|
70
70
|
};
|
|
71
71
|
|
|
72
72
|
class f16_bits_t;
|
|
73
|
-
class
|
|
73
|
+
class bf16_bits_t;
|
|
74
74
|
|
|
75
75
|
#if !USEARCH_USE_FP16LIB
|
|
76
76
|
#if USEARCH_USE_SIMSIMD
|
|
77
77
|
using f16_native_t = simsimd_f16_t;
|
|
78
|
+
using bf16_native_t = simsimd_bf16_t;
|
|
78
79
|
#elif defined(USEARCH_DEFINED_ARM)
|
|
79
80
|
using f16_native_t = __fp16;
|
|
81
|
+
using bf16_native_t = __fp16; // No better choice on most compilers!
|
|
80
82
|
#else
|
|
81
83
|
using f16_native_t = _Float16;
|
|
84
|
+
using bf16_native_t = _Float16; // No better choice on most compilers!
|
|
82
85
|
#endif
|
|
83
86
|
using f16_t = f16_native_t;
|
|
87
|
+
using bf16_t = bf16_native_t;
|
|
84
88
|
#else
|
|
85
89
|
using f16_native_t = void;
|
|
90
|
+
using bf16_native_t = void;
|
|
86
91
|
using f16_t = f16_bits_t;
|
|
92
|
+
using bf16_t = bf16_bits_t;
|
|
87
93
|
#endif
|
|
88
94
|
|
|
89
95
|
using f64_t = double;
|
|
@@ -99,6 +105,9 @@ using i32_t = std::int32_t;
|
|
|
99
105
|
using i16_t = std::int16_t;
|
|
100
106
|
using i8_t = std::int8_t;
|
|
101
107
|
|
|
108
|
+
/**
|
|
109
|
+
* @brief Enumerates the most commonly used distance metrics, mostly for dense vector representations.
|
|
110
|
+
*/
|
|
102
111
|
enum class metric_kind_t : std::uint8_t {
|
|
103
112
|
unknown_k = 0,
|
|
104
113
|
// Classics:
|
|
@@ -111,19 +120,26 @@ enum class metric_kind_t : std::uint8_t {
|
|
|
111
120
|
haversine_k = 'h',
|
|
112
121
|
divergence_k = 'd',
|
|
113
122
|
|
|
114
|
-
// Sets:
|
|
115
|
-
jaccard_k = 'j',
|
|
123
|
+
// Dense Sets:
|
|
116
124
|
hamming_k = 'b',
|
|
117
125
|
tanimoto_k = 't',
|
|
118
126
|
sorensen_k = 's',
|
|
127
|
+
|
|
128
|
+
// Sparse Sets:
|
|
129
|
+
jaccard_k = 'j',
|
|
119
130
|
};
|
|
120
131
|
|
|
132
|
+
/**
|
|
133
|
+
* @brief Enumerates the most commonly used scalar types, mostly for dense vector representations.
|
|
134
|
+
* Doesn't include logical types, like complex numbers or quaternions.
|
|
135
|
+
*/
|
|
121
136
|
enum class scalar_kind_t : std::uint8_t {
|
|
122
137
|
unknown_k = 0,
|
|
123
138
|
// Custom:
|
|
124
139
|
b1x8_k = 1,
|
|
125
140
|
u40_k = 2,
|
|
126
141
|
uuid_k = 3,
|
|
142
|
+
bf16_k = 4,
|
|
127
143
|
// Common:
|
|
128
144
|
f64_k = 10,
|
|
129
145
|
f32_k = 11,
|
|
@@ -159,6 +175,8 @@ template <typename scalar_at> scalar_kind_t scalar_kind() noexcept {
|
|
|
159
175
|
return scalar_kind_t::f32_k;
|
|
160
176
|
if (std::is_same<scalar_at, f16_t>())
|
|
161
177
|
return scalar_kind_t::f16_k;
|
|
178
|
+
if (std::is_same<scalar_at, bf16_t>())
|
|
179
|
+
return scalar_kind_t::bf16_k;
|
|
162
180
|
if (std::is_same<scalar_at, i8_t>())
|
|
163
181
|
return scalar_kind_t::i8_k;
|
|
164
182
|
if (std::is_same<scalar_at, u64_t>())
|
|
@@ -198,33 +216,66 @@ inline bool str_equals(char const* begin, std::size_t len, char const* other_beg
|
|
|
198
216
|
|
|
199
217
|
inline std::size_t bits_per_scalar(scalar_kind_t scalar_kind) noexcept {
|
|
200
218
|
switch (scalar_kind) {
|
|
219
|
+
case scalar_kind_t::uuid_k: return 128;
|
|
220
|
+
case scalar_kind_t::u40_k: return 40;
|
|
221
|
+
case scalar_kind_t::bf16_k: return 16;
|
|
222
|
+
case scalar_kind_t::b1x8_k: return 1;
|
|
223
|
+
case scalar_kind_t::u64_k: return 64;
|
|
224
|
+
case scalar_kind_t::i64_k: return 64;
|
|
201
225
|
case scalar_kind_t::f64_k: return 64;
|
|
226
|
+
case scalar_kind_t::u32_k: return 32;
|
|
227
|
+
case scalar_kind_t::i32_k: return 32;
|
|
202
228
|
case scalar_kind_t::f32_k: return 32;
|
|
229
|
+
case scalar_kind_t::u16_k: return 16;
|
|
230
|
+
case scalar_kind_t::i16_k: return 16;
|
|
203
231
|
case scalar_kind_t::f16_k: return 16;
|
|
232
|
+
case scalar_kind_t::u8_k: return 8;
|
|
204
233
|
case scalar_kind_t::i8_k: return 8;
|
|
205
|
-
case scalar_kind_t::
|
|
234
|
+
case scalar_kind_t::f8_k: return 8;
|
|
206
235
|
default: return 0;
|
|
207
236
|
}
|
|
208
237
|
}
|
|
209
238
|
|
|
210
239
|
inline std::size_t bits_per_scalar_word(scalar_kind_t scalar_kind) noexcept {
|
|
211
240
|
switch (scalar_kind) {
|
|
241
|
+
case scalar_kind_t::uuid_k: return 128;
|
|
242
|
+
case scalar_kind_t::u40_k: return 40;
|
|
243
|
+
case scalar_kind_t::bf16_k: return 16;
|
|
244
|
+
case scalar_kind_t::b1x8_k: return 8;
|
|
245
|
+
case scalar_kind_t::u64_k: return 64;
|
|
246
|
+
case scalar_kind_t::i64_k: return 64;
|
|
212
247
|
case scalar_kind_t::f64_k: return 64;
|
|
248
|
+
case scalar_kind_t::u32_k: return 32;
|
|
249
|
+
case scalar_kind_t::i32_k: return 32;
|
|
213
250
|
case scalar_kind_t::f32_k: return 32;
|
|
251
|
+
case scalar_kind_t::u16_k: return 16;
|
|
252
|
+
case scalar_kind_t::i16_k: return 16;
|
|
214
253
|
case scalar_kind_t::f16_k: return 16;
|
|
254
|
+
case scalar_kind_t::u8_k: return 8;
|
|
215
255
|
case scalar_kind_t::i8_k: return 8;
|
|
216
|
-
case scalar_kind_t::
|
|
256
|
+
case scalar_kind_t::f8_k: return 8;
|
|
217
257
|
default: return 0;
|
|
218
258
|
}
|
|
219
259
|
}
|
|
220
260
|
|
|
221
261
|
inline char const* scalar_kind_name(scalar_kind_t scalar_kind) noexcept {
|
|
222
262
|
switch (scalar_kind) {
|
|
263
|
+
case scalar_kind_t::uuid_k: return "uuid";
|
|
264
|
+
case scalar_kind_t::u40_k: return "u40";
|
|
265
|
+
case scalar_kind_t::bf16_k: return "bf16";
|
|
266
|
+
case scalar_kind_t::b1x8_k: return "b1x8";
|
|
267
|
+
case scalar_kind_t::u64_k: return "u64";
|
|
268
|
+
case scalar_kind_t::i64_k: return "i64";
|
|
269
|
+
case scalar_kind_t::f64_k: return "f64";
|
|
270
|
+
case scalar_kind_t::u32_k: return "u32";
|
|
271
|
+
case scalar_kind_t::i32_k: return "i32";
|
|
223
272
|
case scalar_kind_t::f32_k: return "f32";
|
|
273
|
+
case scalar_kind_t::u16_k: return "u16";
|
|
274
|
+
case scalar_kind_t::i16_k: return "i16";
|
|
224
275
|
case scalar_kind_t::f16_k: return "f16";
|
|
225
|
-
case scalar_kind_t::
|
|
276
|
+
case scalar_kind_t::u8_k: return "u8";
|
|
226
277
|
case scalar_kind_t::i8_k: return "i8";
|
|
227
|
-
case scalar_kind_t::
|
|
278
|
+
case scalar_kind_t::f8_k: return "f8";
|
|
228
279
|
default: return "";
|
|
229
280
|
}
|
|
230
281
|
}
|
|
@@ -242,8 +293,8 @@ inline char const* metric_kind_name(metric_kind_t metric) noexcept {
|
|
|
242
293
|
case metric_kind_t::hamming_k: return "hamming";
|
|
243
294
|
case metric_kind_t::tanimoto_k: return "tanimoto";
|
|
244
295
|
case metric_kind_t::sorensen_k: return "sorensen";
|
|
296
|
+
default: return "";
|
|
245
297
|
}
|
|
246
|
-
return "";
|
|
247
298
|
}
|
|
248
299
|
inline expected_gt<scalar_kind_t> scalar_kind_from_name(char const* name, std::size_t len) {
|
|
249
300
|
expected_gt<scalar_kind_t> parsed;
|
|
@@ -253,10 +304,14 @@ inline expected_gt<scalar_kind_t> scalar_kind_from_name(char const* name, std::s
|
|
|
253
304
|
parsed.result = scalar_kind_t::f64_k;
|
|
254
305
|
else if (str_equals(name, len, "f16"))
|
|
255
306
|
parsed.result = scalar_kind_t::f16_k;
|
|
307
|
+
else if (str_equals(name, len, "bf16"))
|
|
308
|
+
parsed.result = scalar_kind_t::bf16_k;
|
|
256
309
|
else if (str_equals(name, len, "i8"))
|
|
257
310
|
parsed.result = scalar_kind_t::i8_k;
|
|
311
|
+
else if (str_equals(name, len, "b1"))
|
|
312
|
+
parsed.result = scalar_kind_t::b1x8_k;
|
|
258
313
|
else
|
|
259
|
-
parsed.failed("Unknown type, choose: f32, f16,
|
|
314
|
+
parsed.failed("Unknown type, choose: f64, f32, f16, bf16, i8, b1");
|
|
260
315
|
return parsed;
|
|
261
316
|
}
|
|
262
317
|
|
|
@@ -289,11 +344,13 @@ inline expected_gt<metric_kind_t> metric_from_name(char const* name, std::size_t
|
|
|
289
344
|
"tanimoto, sorensen");
|
|
290
345
|
return parsed;
|
|
291
346
|
}
|
|
292
|
-
|
|
293
347
|
inline expected_gt<metric_kind_t> metric_from_name(char const* name) {
|
|
294
348
|
return metric_from_name(name, std::strlen(name));
|
|
295
349
|
}
|
|
296
350
|
|
|
351
|
+
/**
|
|
352
|
+
* @brief Convenience function to upcast a half-precision floating point number to a single-precision one.
|
|
353
|
+
*/
|
|
297
354
|
inline float f16_to_f32(std::uint16_t u16) noexcept {
|
|
298
355
|
#if !USEARCH_USE_FP16LIB
|
|
299
356
|
f16_native_t f16;
|
|
@@ -304,6 +361,9 @@ inline float f16_to_f32(std::uint16_t u16) noexcept {
|
|
|
304
361
|
#endif
|
|
305
362
|
}
|
|
306
363
|
|
|
364
|
+
/**
|
|
365
|
+
* @brief Convenience function to downcast a single-precision floating point number to a half-precision one.
|
|
366
|
+
*/
|
|
307
367
|
inline std::uint16_t f32_to_f16(float f32) noexcept {
|
|
308
368
|
#if !USEARCH_USE_FP16LIB
|
|
309
369
|
f16_native_t f16 = f16_native_t(f32);
|
|
@@ -315,6 +375,36 @@ inline std::uint16_t f32_to_f16(float f32) noexcept {
|
|
|
315
375
|
#endif
|
|
316
376
|
}
|
|
317
377
|
|
|
378
|
+
/**
|
|
379
|
+
* @brief Convenience function to upcast a brain-floating point number to a single-precision one.
|
|
380
|
+
* https://github.com/ashvardanian/SimSIMD/blob/ff51434d90c66f916e94ff05b24530b127aa4cff/include/simsimd/types.h#L395-L410
|
|
381
|
+
*/
|
|
382
|
+
inline float bf16_to_f32(std::uint16_t u16) noexcept {
|
|
383
|
+
union float_or_unsigned_int_t {
|
|
384
|
+
float f;
|
|
385
|
+
unsigned int i;
|
|
386
|
+
};
|
|
387
|
+
union float_or_unsigned_int_t result_union;
|
|
388
|
+
result_union.i = u16 << 16; // Zero extends the mantissa
|
|
389
|
+
return result_union.f;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* @brief Convenience function to downcast a single-precision floating point number to a brain-floating point one.
|
|
394
|
+
* https://github.com/ashvardanian/SimSIMD/blob/ff51434d90c66f916e94ff05b24530b127aa4cff/include/simsimd/types.h#L412-L425
|
|
395
|
+
*/
|
|
396
|
+
inline std::uint16_t f32_to_bf16(float f32) noexcept {
|
|
397
|
+
union float_or_unsigned_int_t {
|
|
398
|
+
float f;
|
|
399
|
+
unsigned int i;
|
|
400
|
+
};
|
|
401
|
+
union float_or_unsigned_int_t value;
|
|
402
|
+
value.f = f32;
|
|
403
|
+
value.i >>= 16;
|
|
404
|
+
value.i &= 0xFFFF;
|
|
405
|
+
return (unsigned short)value.i;
|
|
406
|
+
}
|
|
407
|
+
|
|
318
408
|
/**
|
|
319
409
|
* @brief Numeric type for the IEEE 754 half-precision floating point.
|
|
320
410
|
* If hardware support isn't available, falls back to a hardware
|
|
@@ -333,8 +423,7 @@ class f16_bits_t {
|
|
|
333
423
|
inline operator float() const noexcept { return f16_to_f32(uint16_); }
|
|
334
424
|
inline explicit operator bool() const noexcept { return f16_to_f32(uint16_) > 0.5f; }
|
|
335
425
|
|
|
336
|
-
inline f16_bits_t(
|
|
337
|
-
inline f16_bits_t(std::int8_t v) noexcept : uint16_(v) {}
|
|
426
|
+
inline f16_bits_t(std::int8_t v) noexcept : uint16_(f32_to_f16(v)) {}
|
|
338
427
|
inline f16_bits_t(bool v) noexcept : uint16_(f32_to_f16(v)) {}
|
|
339
428
|
inline f16_bits_t(float v) noexcept : uint16_(f32_to_f16(v)) {}
|
|
340
429
|
inline f16_bits_t(double v) noexcept : uint16_(f32_to_f16(static_cast<float>(v))) {}
|
|
@@ -345,14 +434,14 @@ class f16_bits_t {
|
|
|
345
434
|
inline f16_bits_t operator-(f16_bits_t other) const noexcept { return {float(*this) - float(other)}; }
|
|
346
435
|
inline f16_bits_t operator*(f16_bits_t other) const noexcept { return {float(*this) * float(other)}; }
|
|
347
436
|
inline f16_bits_t operator/(f16_bits_t other) const noexcept { return {float(*this) / float(other)}; }
|
|
348
|
-
inline
|
|
349
|
-
inline
|
|
350
|
-
inline
|
|
351
|
-
inline
|
|
352
|
-
inline
|
|
353
|
-
inline
|
|
354
|
-
inline
|
|
355
|
-
inline
|
|
437
|
+
inline float operator+(float other) const noexcept { return float(*this) + other; }
|
|
438
|
+
inline float operator-(float other) const noexcept { return float(*this) - other; }
|
|
439
|
+
inline float operator*(float other) const noexcept { return float(*this) * other; }
|
|
440
|
+
inline float operator/(float other) const noexcept { return float(*this) / other; }
|
|
441
|
+
inline double operator+(double other) const noexcept { return float(*this) + other; }
|
|
442
|
+
inline double operator-(double other) const noexcept { return float(*this) - other; }
|
|
443
|
+
inline double operator*(double other) const noexcept { return float(*this) * other; }
|
|
444
|
+
inline double operator/(double other) const noexcept { return float(*this) / other; }
|
|
356
445
|
|
|
357
446
|
inline f16_bits_t& operator+=(float v) noexcept {
|
|
358
447
|
uint16_ = f32_to_f16(v + f16_to_f32(uint16_));
|
|
@@ -375,6 +464,65 @@ class f16_bits_t {
|
|
|
375
464
|
}
|
|
376
465
|
};
|
|
377
466
|
|
|
467
|
+
/**
|
|
468
|
+
* @brief Numeric type for brain-floating point half-precision floating point.
|
|
469
|
+
* If hardware support isn't available, falls back to a hardware
|
|
470
|
+
* agnostic in-software implementation.
|
|
471
|
+
*/
|
|
472
|
+
class bf16_bits_t {
|
|
473
|
+
std::uint16_t uint16_{};
|
|
474
|
+
|
|
475
|
+
public:
|
|
476
|
+
inline bf16_bits_t() noexcept : uint16_(0) {}
|
|
477
|
+
inline bf16_bits_t(bf16_bits_t&&) = default;
|
|
478
|
+
inline bf16_bits_t& operator=(bf16_bits_t&&) = default;
|
|
479
|
+
inline bf16_bits_t(bf16_bits_t const&) = default;
|
|
480
|
+
inline bf16_bits_t& operator=(bf16_bits_t const&) = default;
|
|
481
|
+
|
|
482
|
+
inline operator float() const noexcept { return bf16_to_f32(uint16_); }
|
|
483
|
+
inline explicit operator bool() const noexcept { return bf16_to_f32(uint16_) > 0.5f; }
|
|
484
|
+
|
|
485
|
+
inline bf16_bits_t(std::int8_t v) noexcept : uint16_(f32_to_bf16(v)) {}
|
|
486
|
+
inline bf16_bits_t(bool v) noexcept : uint16_(f32_to_bf16(v)) {}
|
|
487
|
+
inline bf16_bits_t(float v) noexcept : uint16_(f32_to_bf16(v)) {}
|
|
488
|
+
inline bf16_bits_t(double v) noexcept : uint16_(f32_to_bf16(static_cast<float>(v))) {}
|
|
489
|
+
|
|
490
|
+
inline bool operator<(bf16_bits_t const& other) const noexcept { return float(*this) < float(other); }
|
|
491
|
+
|
|
492
|
+
inline bf16_bits_t operator+(bf16_bits_t other) const noexcept { return {float(*this) + float(other)}; }
|
|
493
|
+
inline bf16_bits_t operator-(bf16_bits_t other) const noexcept { return {float(*this) - float(other)}; }
|
|
494
|
+
inline bf16_bits_t operator*(bf16_bits_t other) const noexcept { return {float(*this) * float(other)}; }
|
|
495
|
+
inline bf16_bits_t operator/(bf16_bits_t other) const noexcept { return {float(*this) / float(other)}; }
|
|
496
|
+
inline float operator+(float other) const noexcept { return float(*this) + other; }
|
|
497
|
+
inline float operator-(float other) const noexcept { return float(*this) - other; }
|
|
498
|
+
inline float operator*(float other) const noexcept { return float(*this) * other; }
|
|
499
|
+
inline float operator/(float other) const noexcept { return float(*this) / other; }
|
|
500
|
+
inline double operator+(double other) const noexcept { return float(*this) + other; }
|
|
501
|
+
inline double operator-(double other) const noexcept { return float(*this) - other; }
|
|
502
|
+
inline double operator*(double other) const noexcept { return float(*this) * other; }
|
|
503
|
+
inline double operator/(double other) const noexcept { return float(*this) / other; }
|
|
504
|
+
|
|
505
|
+
inline bf16_bits_t& operator+=(float v) noexcept {
|
|
506
|
+
uint16_ = f32_to_bf16(v + bf16_to_f32(uint16_));
|
|
507
|
+
return *this;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
inline bf16_bits_t& operator-=(float v) noexcept {
|
|
511
|
+
uint16_ = f32_to_bf16(v - bf16_to_f32(uint16_));
|
|
512
|
+
return *this;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
inline bf16_bits_t& operator*=(float v) noexcept {
|
|
516
|
+
uint16_ = f32_to_bf16(v * bf16_to_f32(uint16_));
|
|
517
|
+
return *this;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
inline bf16_bits_t& operator/=(float v) noexcept {
|
|
521
|
+
uint16_ = f32_to_bf16(v / bf16_to_f32(uint16_));
|
|
522
|
+
return *this;
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
|
|
378
526
|
/**
|
|
379
527
|
* @brief An STL-based executor or a "thread-pool" for parallel execution.
|
|
380
528
|
* Isn't efficient for small batches, as it recreates the threads on every call.
|
|
@@ -907,7 +1055,7 @@ template <> struct cast_gt<b1x8_t, b1x8_t> {
|
|
|
907
1055
|
static bool try_(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
|
|
908
1056
|
};
|
|
909
1057
|
|
|
910
|
-
template <typename from_scalar_at> struct
|
|
1058
|
+
template <typename from_scalar_at> struct cast_to_b1x8_gt {
|
|
911
1059
|
inline static bool try_(byte_t const* input, std::size_t dim, byte_t* output) noexcept {
|
|
912
1060
|
from_scalar_at const* typed_input = reinterpret_cast<from_scalar_at const*>(input);
|
|
913
1061
|
unsigned char* typed_output = reinterpret_cast<unsigned char*>(output);
|
|
@@ -928,7 +1076,7 @@ template <typename from_scalar_at> struct cast_gt<from_scalar_at, b1x8_t> {
|
|
|
928
1076
|
}
|
|
929
1077
|
};
|
|
930
1078
|
|
|
931
|
-
template <typename to_scalar_at> struct
|
|
1079
|
+
template <typename to_scalar_at> struct cast_from_b1x8_gt {
|
|
932
1080
|
static bool try_(byte_t const* input, std::size_t dim, byte_t* output) noexcept {
|
|
933
1081
|
unsigned char const* typed_input = reinterpret_cast<unsigned char const*>(input);
|
|
934
1082
|
to_scalar_at* typed_output = reinterpret_cast<to_scalar_at*>(output);
|
|
@@ -940,53 +1088,59 @@ template <typename to_scalar_at> struct cast_gt<b1x8_t, to_scalar_at> {
|
|
|
940
1088
|
}
|
|
941
1089
|
};
|
|
942
1090
|
|
|
943
|
-
|
|
944
|
-
*
|
|
945
|
-
*
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1091
|
+
template <typename from_scalar_at> struct cast_to_i8_gt {
|
|
1092
|
+
inline static bool try_(byte_t const* input, std::size_t dim, byte_t* output) noexcept {
|
|
1093
|
+
from_scalar_at const* typed_input = reinterpret_cast<from_scalar_at const*>(input);
|
|
1094
|
+
std::int8_t* typed_output = reinterpret_cast<std::int8_t*>(output);
|
|
1095
|
+
// Unlike other casting mechanisms, switching to small range integers is a two step procedure.
|
|
1096
|
+
// First we want to estimate the magnitude of the vector to scale into [-1.0, 1.0] interval,
|
|
1097
|
+
// instead of clamping. And then we scale the values into the [-127, 127] range.
|
|
1098
|
+
// ! This makes an assumption, that the distance metric is dot-product-like, which may not
|
|
1099
|
+
// ! be true in many cases, so it's recommended to avoid automatic casting from floats to
|
|
1100
|
+
// ! integers.
|
|
1101
|
+
double magnitude = 0.0;
|
|
1102
|
+
for (std::size_t i = 0; i != dim; ++i)
|
|
1103
|
+
magnitude += (double)typed_input[i] * (double)typed_input[i];
|
|
1104
|
+
magnitude = std::sqrt(magnitude);
|
|
1105
|
+
for (std::size_t i = 0; i != dim; ++i)
|
|
1106
|
+
typed_output[i] =
|
|
1107
|
+
static_cast<std::int8_t>(usearch::clamp<double>(typed_input[i] * 127.0 / magnitude, -127.0, 127.0));
|
|
1108
|
+
return true;
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
949
1111
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
inline i8_converted_t(i8_converted_t&&) = default;
|
|
959
|
-
inline i8_converted_t& operator=(i8_converted_t&&) = default;
|
|
960
|
-
inline i8_converted_t(i8_converted_t const&) = default;
|
|
961
|
-
inline i8_converted_t& operator=(i8_converted_t const&) = default;
|
|
962
|
-
|
|
963
|
-
inline operator f16_t() const noexcept { return static_cast<f16_t>(f32_t(int8_) / divisor_k); }
|
|
964
|
-
inline operator f32_t() const noexcept { return f32_t(int8_) / divisor_k; }
|
|
965
|
-
inline operator f64_t() const noexcept { return f64_t(int8_) / divisor_k; }
|
|
966
|
-
inline explicit operator bool() const noexcept { return int8_ > (max_k / 2); }
|
|
967
|
-
inline explicit operator std::int8_t() const noexcept { return int8_; }
|
|
968
|
-
inline explicit operator std::int16_t() const noexcept { return int8_; }
|
|
969
|
-
inline explicit operator std::int32_t() const noexcept { return int8_; }
|
|
970
|
-
inline explicit operator std::int64_t() const noexcept { return int8_; }
|
|
971
|
-
|
|
972
|
-
// Even with `f16_t` arguments we may want to use `f32_t` for clamping and comparions.
|
|
973
|
-
inline i8_converted_t(f16_t v)
|
|
974
|
-
: int8_(static_cast<std::int8_t>(usearch::clamp<f32_t>(v * divisor_k, min_k, max_k))) {}
|
|
975
|
-
inline i8_converted_t(f32_t v)
|
|
976
|
-
: int8_(static_cast<std::int8_t>(usearch::clamp<f32_t>(v * divisor_k, min_k, max_k))) {}
|
|
977
|
-
inline i8_converted_t(f64_t v)
|
|
978
|
-
: int8_(static_cast<std::int8_t>(usearch::clamp<f64_t>(v * divisor_k, min_k, max_k))) {}
|
|
1112
|
+
template <typename to_scalar_at> struct cast_from_i8_gt {
|
|
1113
|
+
static bool try_(byte_t const* input, std::size_t dim, byte_t* output) noexcept {
|
|
1114
|
+
std::int8_t const* typed_input = reinterpret_cast<std::int8_t const*>(input);
|
|
1115
|
+
to_scalar_at* typed_output = reinterpret_cast<to_scalar_at*>(output);
|
|
1116
|
+
for (std::size_t i = 0; i != dim; ++i)
|
|
1117
|
+
typed_output[i] = static_cast<to_scalar_at>(typed_input[i]) / 127.f;
|
|
1118
|
+
return true;
|
|
1119
|
+
}
|
|
979
1120
|
};
|
|
980
1121
|
|
|
981
|
-
|
|
1122
|
+
template <> struct cast_gt<i8_t, f16_t> : public cast_from_i8_gt<f16_t> {};
|
|
1123
|
+
template <> struct cast_gt<i8_t, bf16_t> : public cast_from_i8_gt<bf16_t> {};
|
|
1124
|
+
template <> struct cast_gt<i8_t, f32_t> : public cast_from_i8_gt<f32_t> {};
|
|
1125
|
+
template <> struct cast_gt<i8_t, f64_t> : public cast_from_i8_gt<f64_t> {};
|
|
1126
|
+
|
|
1127
|
+
template <> struct cast_gt<f16_t, i8_t> : public cast_to_i8_gt<f16_t> {};
|
|
1128
|
+
template <> struct cast_gt<bf16_t, i8_t> : public cast_to_i8_gt<bf16_t> {};
|
|
1129
|
+
template <> struct cast_gt<f32_t, i8_t> : public cast_to_i8_gt<f32_t> {};
|
|
1130
|
+
template <> struct cast_gt<f64_t, i8_t> : public cast_to_i8_gt<f64_t> {};
|
|
982
1131
|
|
|
983
|
-
template <> struct cast_gt<
|
|
984
|
-
template <> struct cast_gt<
|
|
985
|
-
template <> struct cast_gt<
|
|
1132
|
+
template <> struct cast_gt<b1x8_t, f16_t> : public cast_from_b1x8_gt<f16_t> {};
|
|
1133
|
+
template <> struct cast_gt<b1x8_t, bf16_t> : public cast_from_b1x8_gt<bf16_t> {};
|
|
1134
|
+
template <> struct cast_gt<b1x8_t, f32_t> : public cast_from_b1x8_gt<f32_t> {};
|
|
1135
|
+
template <> struct cast_gt<b1x8_t, f64_t> : public cast_from_b1x8_gt<f64_t> {};
|
|
986
1136
|
|
|
987
|
-
template <> struct cast_gt<f16_t,
|
|
988
|
-
template <> struct cast_gt<
|
|
989
|
-
template <> struct cast_gt<
|
|
1137
|
+
template <> struct cast_gt<f16_t, b1x8_t> : public cast_to_b1x8_gt<f16_t> {};
|
|
1138
|
+
template <> struct cast_gt<bf16_t, b1x8_t> : public cast_to_b1x8_gt<bf16_t> {};
|
|
1139
|
+
template <> struct cast_gt<f32_t, b1x8_t> : public cast_to_b1x8_gt<f32_t> {};
|
|
1140
|
+
template <> struct cast_gt<f64_t, b1x8_t> : public cast_to_b1x8_gt<f64_t> {};
|
|
1141
|
+
|
|
1142
|
+
template <> struct cast_gt<b1x8_t, i8_t> : public cast_from_b1x8_gt<i8_t> {};
|
|
1143
|
+
template <> struct cast_gt<i8_t, b1x8_t> : public cast_to_b1x8_gt<i8_t> {};
|
|
990
1144
|
|
|
991
1145
|
/* Don't complain if the vectorization of the inner loops fails:
|
|
992
1146
|
*
|
|
@@ -1563,6 +1717,7 @@ class metric_punned_t {
|
|
|
1563
1717
|
case scalar_kind_t::f32_k: datatype = simsimd_datatype_f32_k; break;
|
|
1564
1718
|
case scalar_kind_t::f64_k: datatype = simsimd_datatype_f64_k; break;
|
|
1565
1719
|
case scalar_kind_t::f16_k: datatype = simsimd_datatype_f16_k; break;
|
|
1720
|
+
case scalar_kind_t::bf16_k: datatype = simsimd_datatype_bf16_k; break;
|
|
1566
1721
|
case scalar_kind_t::i8_k: datatype = simsimd_datatype_i8_k; break;
|
|
1567
1722
|
case scalar_kind_t::b1x8_k: datatype = simsimd_datatype_b8_k; break;
|
|
1568
1723
|
default: break;
|
|
@@ -1615,9 +1770,10 @@ class metric_punned_t {
|
|
|
1615
1770
|
switch (metric_kind_) {
|
|
1616
1771
|
case metric_kind_t::ip_k: {
|
|
1617
1772
|
switch (scalar_kind_) {
|
|
1618
|
-
case scalar_kind_t::
|
|
1619
|
-
case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<f16_t, f32_t>>; break;
|
|
1773
|
+
case scalar_kind_t::bf16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<bf16_t, f32_t>>; break;
|
|
1620
1774
|
case scalar_kind_t::i8_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<i8_t, f32_t>>; break;
|
|
1775
|
+
case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<f16_t, f32_t>>; break;
|
|
1776
|
+
case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<f32_t>>; break;
|
|
1621
1777
|
case scalar_kind_t::f64_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<f64_t>>; break;
|
|
1622
1778
|
default: metric_ptr_ = 0; break;
|
|
1623
1779
|
}
|
|
@@ -1625,9 +1781,10 @@ class metric_punned_t {
|
|
|
1625
1781
|
}
|
|
1626
1782
|
case metric_kind_t::cos_k: {
|
|
1627
1783
|
switch (scalar_kind_) {
|
|
1628
|
-
case scalar_kind_t::
|
|
1629
|
-
case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<f16_t, f32_t>>; break;
|
|
1784
|
+
case scalar_kind_t::bf16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<bf16_t, f32_t>>; break;
|
|
1630
1785
|
case scalar_kind_t::i8_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<i8_t, f32_t>>; break;
|
|
1786
|
+
case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<f16_t, f32_t>>; break;
|
|
1787
|
+
case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<f32_t>>; break;
|
|
1631
1788
|
case scalar_kind_t::f64_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<f64_t>>; break;
|
|
1632
1789
|
default: metric_ptr_ = 0; break;
|
|
1633
1790
|
}
|
|
@@ -1635,9 +1792,10 @@ class metric_punned_t {
|
|
|
1635
1792
|
}
|
|
1636
1793
|
case metric_kind_t::l2sq_k: {
|
|
1637
1794
|
switch (scalar_kind_) {
|
|
1638
|
-
case scalar_kind_t::
|
|
1639
|
-
case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<f16_t, f32_t>>; break;
|
|
1795
|
+
case scalar_kind_t::bf16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<bf16_t, f32_t>>; break;
|
|
1640
1796
|
case scalar_kind_t::i8_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<i8_t, f32_t>>; break;
|
|
1797
|
+
case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<f16_t, f32_t>>; break;
|
|
1798
|
+
case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<f32_t>>; break;
|
|
1641
1799
|
case scalar_kind_t::f64_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<f64_t>>; break;
|
|
1642
1800
|
default: metric_ptr_ = 0; break;
|
|
1643
1801
|
}
|
|
@@ -1645,6 +1803,9 @@ class metric_punned_t {
|
|
|
1645
1803
|
}
|
|
1646
1804
|
case metric_kind_t::pearson_k: {
|
|
1647
1805
|
switch (scalar_kind_) {
|
|
1806
|
+
case scalar_kind_t::bf16_k:
|
|
1807
|
+
metric_ptr_ = (uptr_t)&equidimensional_<metric_pearson_gt<bf16_t, f32_t>>;
|
|
1808
|
+
break;
|
|
1648
1809
|
case scalar_kind_t::i8_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_pearson_gt<i8_t, f32_t>>; break;
|
|
1649
1810
|
case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_pearson_gt<f16_t, f32_t>>; break;
|
|
1650
1811
|
case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_pearson_gt<f32_t>>; break;
|
|
@@ -1655,7 +1816,8 @@ class metric_punned_t {
|
|
|
1655
1816
|
}
|
|
1656
1817
|
case metric_kind_t::haversine_k: {
|
|
1657
1818
|
switch (scalar_kind_) {
|
|
1658
|
-
case scalar_kind_t::
|
|
1819
|
+
case scalar_kind_t::bf16_k: metric_ptr_ = 0; break; //< Half-precision 2D vectors are silly.
|
|
1820
|
+
case scalar_kind_t::f16_k: metric_ptr_ = 0; break; //< Half-precision 2D vectors are silly.
|
|
1659
1821
|
case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_haversine_gt<f32_t>>; break;
|
|
1660
1822
|
case scalar_kind_t::f64_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_haversine_gt<f64_t>>; break;
|
|
1661
1823
|
default: metric_ptr_ = 0; break;
|
|
@@ -1664,6 +1826,9 @@ class metric_punned_t {
|
|
|
1664
1826
|
}
|
|
1665
1827
|
case metric_kind_t::divergence_k: {
|
|
1666
1828
|
switch (scalar_kind_) {
|
|
1829
|
+
case scalar_kind_t::bf16_k:
|
|
1830
|
+
metric_ptr_ = (uptr_t)&equidimensional_<metric_divergence_gt<bf16_t, f32_t>>;
|
|
1831
|
+
break;
|
|
1667
1832
|
case scalar_kind_t::f16_k:
|
|
1668
1833
|
metric_ptr_ = (uptr_t)&equidimensional_<metric_divergence_gt<f16_t, f32_t>>;
|
|
1669
1834
|
break;
|
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
|