usearch 0.14.0 → 0.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/.github/workflows/release.yml +56 -4
- package/.github/workflows/update_version.sh +6 -1
- package/.vscode/launch.json +21 -22
- package/.vscode/settings.json +7 -1
- package/CITATION.cff +4 -4
- package/CMakeLists.txt +1 -1
- package/Cargo.toml +1 -1
- package/Package.swift +0 -1
- package/README.md +100 -27
- package/binding.gyp +0 -1
- package/build.gradle +1 -1
- package/build.rs +3 -3
- package/c/lib.cpp +32 -9
- package/c/usearch.h +67 -36
- package/conanfile.py +1 -1
- package/{src → cpp}/bench.cpp +52 -36
- package/{src → cpp}/test.cpp +17 -7
- package/docs/benchmarks.md +33 -10
- package/docs/c/index.rst +11 -0
- package/docs/c/reference.rst +7 -0
- package/docs/compilation.md +24 -9
- package/docs/conf.dox +6 -6
- package/docs/conf.py +3 -1
- package/docs/cpp/index.rst +11 -0
- package/docs/cpp/reference.rst +7 -0
- package/docs/index.rst +10 -0
- package/docs/java/index.rst +11 -0
- package/docs/java/reference.rst +7 -0
- package/docs/javascript/index.rst +11 -0
- package/docs/javascript/reference.rst +9 -0
- package/docs/python/index.rst +11 -0
- package/docs/python/reference.rst +35 -0
- package/golang/lib.go +1 -1
- package/include/usearch/{usearch.hpp → index.hpp} +152 -148
- package/include/usearch/index_punned_dense.hpp +633 -0
- package/include/usearch/index_punned_helpers.hpp +528 -0
- package/java/cloud/unum/usearch/cloud_unum_usearch_Index.cpp +8 -7
- package/javascript/lib.cpp +26 -50
- package/javascript/usearch.js +117 -2
- package/objc/USearchObjective.mm +145 -49
- package/objc/include/USearchObjective.h +106 -30
- package/package.json +2 -2
- package/python/lib.cpp +134 -331
- package/python/scripts/bench.ipynb +170 -0
- package/python/scripts/bench.py +87 -175
- package/python/scripts/index_faiss.py +100 -0
- package/python/scripts/test.py +41 -37
- package/python/usearch/eval.py +353 -0
- package/python/usearch/index.py +117 -113
- package/python/usearch/io.py +7 -2
- package/python/usearch/join.py +100 -0
- package/python/usearch/numba.py +101 -0
- package/rust/lib.cpp +27 -14
- package/rust/lib.hpp +4 -10
- package/rust/lib.rs +50 -17
- package/setup.py +12 -2
- package/simsimd/include/simsimd/simsimd.h +32 -3
- package/swift/Index+Sugar.swift +55 -29
- package/swift/Test.swift +1 -1
- package/wolfram/lib.cpp +4 -4
- package/python/scripts/bench_params.py +0 -201
- package/python/usearch/synthetic.py +0 -31
- package/src/punned.hpp +0 -906
- /package/{src → cpp}/CMakeLists.txt +0 -0
package/src/punned.hpp
DELETED
|
@@ -1,906 +0,0 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
#include <stdlib.h> // `aligned_alloc`
|
|
3
|
-
|
|
4
|
-
#include <cstring> // `std::strncmp`
|
|
5
|
-
#include <functional> // `std::function`
|
|
6
|
-
#include <numeric> // `std::iota`
|
|
7
|
-
#include <shared_mutex> // `std::shared_mutex`
|
|
8
|
-
#include <stdexcept> // `std::invalid_argument`
|
|
9
|
-
#include <thread> // `std::thread`
|
|
10
|
-
#include <vector> // `std::vector`
|
|
11
|
-
|
|
12
|
-
#include <fp16/fp16.h>
|
|
13
|
-
#include <tsl/robin_map.h>
|
|
14
|
-
|
|
15
|
-
#include <usearch/usearch.hpp>
|
|
16
|
-
|
|
17
|
-
#if defined(USEARCH_USE_OPENMP)
|
|
18
|
-
#include <omp.h> // `omp_get_num_threads()`
|
|
19
|
-
#endif
|
|
20
|
-
|
|
21
|
-
#if defined(USEARCH_IS_LINUX)
|
|
22
|
-
#include <sys/auxv.h>
|
|
23
|
-
#endif
|
|
24
|
-
|
|
25
|
-
#if defined(USEARCH_IS_ARM)
|
|
26
|
-
#include <arm_fp16.h>
|
|
27
|
-
#endif
|
|
28
|
-
|
|
29
|
-
#if defined(USEARCH_USE_SIMSIMD)
|
|
30
|
-
#include <simsimd/simsimd.h>
|
|
31
|
-
#endif
|
|
32
|
-
|
|
33
|
-
namespace unum {
|
|
34
|
-
namespace usearch {
|
|
35
|
-
|
|
36
|
-
enum class common_scalar_kind_t {
|
|
37
|
-
f64_k,
|
|
38
|
-
f32_k,
|
|
39
|
-
f16_k,
|
|
40
|
-
f8_k,
|
|
41
|
-
b1_k,
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
using byte_t = char;
|
|
45
|
-
using punned_distance_t = float;
|
|
46
|
-
using punned_metric_t = punned_distance_t (*)(byte_t const*, byte_t const*, std::size_t, std::size_t);
|
|
47
|
-
|
|
48
|
-
template <typename metric_at>
|
|
49
|
-
inline punned_distance_t punned_metric(byte_t const* a, byte_t const* b, std::size_t a_bytes, std::size_t b_bytes) {
|
|
50
|
-
using scalar_t = typename metric_at::scalar_t;
|
|
51
|
-
return metric_at{}((scalar_t const*)a, (scalar_t const*)b, a_bytes / sizeof(scalar_t), b_bytes / sizeof(scalar_t));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
using punned_stateful_metric_t =
|
|
55
|
-
std::function<punned_distance_t(byte_t const*, byte_t const*, std::size_t, std::size_t)>;
|
|
56
|
-
|
|
57
|
-
template <typename at, typename compare_at> inline at clamp(at v, at lo, at hi, compare_at comp) {
|
|
58
|
-
return comp(v, lo) ? lo : comp(hi, v) ? hi : v;
|
|
59
|
-
}
|
|
60
|
-
template <typename at> inline at clamp(at v, at lo, at hi) { return usearch::clamp(v, lo, hi, std::less<at>{}); }
|
|
61
|
-
|
|
62
|
-
class f8_bits_t;
|
|
63
|
-
class f16_bits_t;
|
|
64
|
-
|
|
65
|
-
inline float f16_to_f32(std::uint16_t u16) {
|
|
66
|
-
#if defined(__AVX512F__)
|
|
67
|
-
_Float16 f16;
|
|
68
|
-
std::memcpy(&f16, &u16, sizeof(std::uint16_t));
|
|
69
|
-
return float(f16);
|
|
70
|
-
#elif defined(USEARCH_IS_ARM)
|
|
71
|
-
__fp16 f16;
|
|
72
|
-
std::memcpy(&f16, &u16, sizeof(std::uint16_t));
|
|
73
|
-
return float(f16);
|
|
74
|
-
#else
|
|
75
|
-
return fp16_ieee_to_fp32_value(u16);
|
|
76
|
-
#endif
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
inline std::uint16_t f32_to_f16(float f32) {
|
|
80
|
-
#if defined(__AVX512F__)
|
|
81
|
-
_Float16 f16 = _Float16(f32);
|
|
82
|
-
std::uint16_t u16;
|
|
83
|
-
std::memcpy(&u16, &f16, sizeof(std::uint16_t));
|
|
84
|
-
return u16;
|
|
85
|
-
#elif defined(USEARCH_IS_ARM)
|
|
86
|
-
__fp16 f16 = __fp16(f32);
|
|
87
|
-
std::uint16_t u16;
|
|
88
|
-
std::memcpy(&u16, &f16, sizeof(std::uint16_t));
|
|
89
|
-
return u16;
|
|
90
|
-
#else
|
|
91
|
-
return fp16_ieee_from_fp32_value(f32);
|
|
92
|
-
#endif
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
class f16_bits_t {
|
|
96
|
-
std::uint16_t uint16_{};
|
|
97
|
-
|
|
98
|
-
public:
|
|
99
|
-
inline f16_bits_t() : uint16_(0) {}
|
|
100
|
-
inline f16_bits_t(f16_bits_t&&) = default;
|
|
101
|
-
inline f16_bits_t& operator=(f16_bits_t&&) = default;
|
|
102
|
-
inline f16_bits_t(f16_bits_t const&) = default;
|
|
103
|
-
inline f16_bits_t& operator=(f16_bits_t const&) = default;
|
|
104
|
-
|
|
105
|
-
inline operator float() const { return f16_to_f32(uint16_); }
|
|
106
|
-
|
|
107
|
-
inline f16_bits_t(f8_bits_t);
|
|
108
|
-
inline f16_bits_t(float v) : uint16_(f32_to_f16(v)) {}
|
|
109
|
-
inline f16_bits_t(double v) : uint16_(f32_to_f16(v)) {}
|
|
110
|
-
|
|
111
|
-
inline f16_bits_t operator+(f16_bits_t other) const { return {float(*this) + float(other)}; }
|
|
112
|
-
inline f16_bits_t operator-(f16_bits_t other) const { return {float(*this) - float(other)}; }
|
|
113
|
-
inline f16_bits_t operator*(f16_bits_t other) const { return {float(*this) * float(other)}; }
|
|
114
|
-
inline f16_bits_t operator/(f16_bits_t other) const { return {float(*this) / float(other)}; }
|
|
115
|
-
inline f16_bits_t operator+(float other) const { return {float(*this) + other}; }
|
|
116
|
-
inline f16_bits_t operator-(float other) const { return {float(*this) - other}; }
|
|
117
|
-
inline f16_bits_t operator*(float other) const { return {float(*this) * other}; }
|
|
118
|
-
inline f16_bits_t operator/(float other) const { return {float(*this) / other}; }
|
|
119
|
-
inline f16_bits_t operator+(double other) const { return {float(*this) + other}; }
|
|
120
|
-
inline f16_bits_t operator-(double other) const { return {float(*this) - other}; }
|
|
121
|
-
inline f16_bits_t operator*(double other) const { return {float(*this) * other}; }
|
|
122
|
-
inline f16_bits_t operator/(double other) const { return {float(*this) / other}; }
|
|
123
|
-
|
|
124
|
-
inline f16_bits_t& operator+=(float v) {
|
|
125
|
-
uint16_ = f32_to_f16(v + f16_to_f32(uint16_));
|
|
126
|
-
return *this;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
inline f16_bits_t& operator-=(float v) {
|
|
130
|
-
uint16_ = f32_to_f16(v - f16_to_f32(uint16_));
|
|
131
|
-
return *this;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
inline f16_bits_t& operator*=(float v) {
|
|
135
|
-
uint16_ = f32_to_f16(v * f16_to_f32(uint16_));
|
|
136
|
-
return *this;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
inline f16_bits_t& operator/=(float v) {
|
|
140
|
-
uint16_ = f32_to_f16(v / f16_to_f32(uint16_));
|
|
141
|
-
return *this;
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* @brief Numeric type for uniformly-distributed floating point
|
|
147
|
-
* values within [-1,1] range, quantized to integers [-100,100].
|
|
148
|
-
*/
|
|
149
|
-
class f8_bits_t {
|
|
150
|
-
std::int8_t int8_{};
|
|
151
|
-
|
|
152
|
-
public:
|
|
153
|
-
constexpr static float divisor_k = 100.f;
|
|
154
|
-
constexpr static std::int8_t min_k = -100;
|
|
155
|
-
constexpr static std::int8_t max_k = 100;
|
|
156
|
-
|
|
157
|
-
inline f8_bits_t() : int8_(0) {}
|
|
158
|
-
inline f8_bits_t(f8_bits_t&&) = default;
|
|
159
|
-
inline f8_bits_t& operator=(f8_bits_t&&) = default;
|
|
160
|
-
inline f8_bits_t(f8_bits_t const&) = default;
|
|
161
|
-
inline f8_bits_t& operator=(f8_bits_t const&) = default;
|
|
162
|
-
|
|
163
|
-
inline operator float() const { return float(int8_) / divisor_k; }
|
|
164
|
-
inline operator f16_bits_t() const { return float(int8_) / divisor_k; }
|
|
165
|
-
inline operator double() const { return double(int8_) / divisor_k; }
|
|
166
|
-
inline explicit operator std::int8_t() const { return int8_; }
|
|
167
|
-
inline explicit operator std::int16_t() const { return int8_; }
|
|
168
|
-
inline explicit operator std::int32_t() const { return int8_; }
|
|
169
|
-
inline explicit operator std::int64_t() const { return int8_; }
|
|
170
|
-
|
|
171
|
-
inline f8_bits_t(float v)
|
|
172
|
-
: int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
|
|
173
|
-
inline f8_bits_t(double v)
|
|
174
|
-
: int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
inline f16_bits_t::f16_bits_t(f8_bits_t v) : f16_bits_t(float(v)) {}
|
|
178
|
-
|
|
179
|
-
struct cos_f8_t {
|
|
180
|
-
inline punned_distance_t operator()(f8_bits_t const* a, f8_bits_t const* b, std::size_t n) const {
|
|
181
|
-
std::int32_t ab{}, a2{}, b2{};
|
|
182
|
-
for (std::size_t i = 0; i != n; i++) {
|
|
183
|
-
std::int16_t ai{a[i]};
|
|
184
|
-
std::int16_t bi{b[i]};
|
|
185
|
-
ab += ai * bi;
|
|
186
|
-
a2 += square(ai);
|
|
187
|
-
b2 += square(bi);
|
|
188
|
-
}
|
|
189
|
-
return 1.f - ab / (std::sqrt(a2) * std::sqrt(b2));
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
struct l2sq_f8_t {
|
|
194
|
-
inline punned_distance_t operator()(f8_bits_t const* a, f8_bits_t const* b, std::size_t n) const {
|
|
195
|
-
std::int32_t ab_deltas_sq{};
|
|
196
|
-
for (std::size_t i = 0; i != n; i++)
|
|
197
|
-
ab_deltas_sq += square(std::int16_t(a[i]) - std::int16_t(b[i]));
|
|
198
|
-
return ab_deltas_sq;
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
struct uuid_t {
|
|
203
|
-
std::uint8_t octets[16];
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
template <typename callback_at> //
|
|
207
|
-
void multithreaded(std::size_t threads, std::size_t tasks, callback_at&& callback) {
|
|
208
|
-
|
|
209
|
-
if (threads == 0)
|
|
210
|
-
threads = std::thread::hardware_concurrency();
|
|
211
|
-
if (threads == 1 || tasks <= 128) {
|
|
212
|
-
for (std::size_t task_idx = 0; task_idx < tasks; ++task_idx)
|
|
213
|
-
callback(0, task_idx);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
#if defined(USEARCH_USE_OPENMP)
|
|
218
|
-
omp_set_num_threads(threads);
|
|
219
|
-
#pragma omp parallel for schedule(dynamic)
|
|
220
|
-
for (std::size_t i = 0; i < tasks; ++i)
|
|
221
|
-
callback(omp_get_thread_num(), i);
|
|
222
|
-
#else
|
|
223
|
-
std::vector<std::thread> threads_pool;
|
|
224
|
-
std::size_t tasks_per_thread = (tasks / threads) + ((tasks % threads) != 0);
|
|
225
|
-
for (std::size_t thread_idx = 0; thread_idx != threads; ++thread_idx) {
|
|
226
|
-
threads_pool.emplace_back([=]() {
|
|
227
|
-
for (std::size_t task_idx = thread_idx * tasks_per_thread;
|
|
228
|
-
task_idx < (std::min)(tasks, thread_idx * tasks_per_thread + tasks_per_thread); ++task_idx)
|
|
229
|
-
callback(thread_idx, task_idx);
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
for (std::size_t thread_idx = 0; thread_idx != threads; ++thread_idx)
|
|
233
|
-
threads_pool[thread_idx].join();
|
|
234
|
-
#endif
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* @brief Relies on `posix_memalign` for aligned memory allocations.
|
|
239
|
-
*/
|
|
240
|
-
template <typename element_at = char, std::size_t alignment_ak = 64> //
|
|
241
|
-
class aligned_allocator_gt {
|
|
242
|
-
public:
|
|
243
|
-
using value_type = element_at;
|
|
244
|
-
using size_type = std::size_t;
|
|
245
|
-
using pointer = element_at*;
|
|
246
|
-
using const_pointer = element_at const*;
|
|
247
|
-
template <typename other_element_at> struct rebind {
|
|
248
|
-
using other = aligned_allocator_gt<other_element_at>;
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
pointer allocate(size_type length) const {
|
|
252
|
-
void* result = nullptr;
|
|
253
|
-
int status = posix_memalign(&result, alignment_ak, ceil2(length * sizeof(value_type)));
|
|
254
|
-
return status == 0 ? (pointer)result : nullptr;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
void deallocate(pointer begin, size_type) const { std::free(begin); }
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
using aligned_allocator_t = aligned_allocator_gt<>;
|
|
261
|
-
|
|
262
|
-
enum class accuracy_t {
|
|
263
|
-
f32_k,
|
|
264
|
-
f16_k,
|
|
265
|
-
f64_k,
|
|
266
|
-
f8_k,
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
inline char const* accuracy_name(accuracy_t accuracy) {
|
|
270
|
-
switch (accuracy) {
|
|
271
|
-
case accuracy_t::f32_k: return "f32";
|
|
272
|
-
case accuracy_t::f16_k: return "f16";
|
|
273
|
-
case accuracy_t::f64_k: return "f64";
|
|
274
|
-
case accuracy_t::f8_k: return "f8";
|
|
275
|
-
default: return "";
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
inline bool str_equals(char const* begin, std::size_t len, char const* other_begin) {
|
|
280
|
-
std::size_t other_len = std::strlen(other_begin);
|
|
281
|
-
return len == other_len && std::strncmp(begin, other_begin, len) == 0;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
inline expected_gt<accuracy_t> accuracy_from_name(char const* name, std::size_t len) {
|
|
285
|
-
expected_gt<accuracy_t> parsed;
|
|
286
|
-
if (str_equals(name, len, "f32"))
|
|
287
|
-
parsed.result = accuracy_t::f32_k;
|
|
288
|
-
else if (str_equals(name, len, "f64"))
|
|
289
|
-
parsed.result = accuracy_t::f64_k;
|
|
290
|
-
else if (str_equals(name, len, "f16"))
|
|
291
|
-
parsed.result = accuracy_t::f16_k;
|
|
292
|
-
else if (str_equals(name, len, "f8"))
|
|
293
|
-
parsed.result = accuracy_t::f8_k;
|
|
294
|
-
else
|
|
295
|
-
parsed.failed("Unknown type, choose: f32, f16, f64, f8");
|
|
296
|
-
return parsed;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
inline expected_gt<common_metric_kind_t> common_metric_from_name(char const* name, std::size_t len) {
|
|
300
|
-
expected_gt<common_metric_kind_t> parsed;
|
|
301
|
-
if (str_equals(name, len, "l2sq") || str_equals(name, len, "euclidean_sq")) {
|
|
302
|
-
parsed.result = common_metric_kind_t::l2sq_k;
|
|
303
|
-
} else if (str_equals(name, len, "ip") || str_equals(name, len, "inner") || str_equals(name, len, "dot")) {
|
|
304
|
-
parsed.result = common_metric_kind_t::ip_k;
|
|
305
|
-
} else if (str_equals(name, len, "cos") || str_equals(name, len, "angular")) {
|
|
306
|
-
parsed.result = common_metric_kind_t::cos_k;
|
|
307
|
-
} else if (str_equals(name, len, "haversine")) {
|
|
308
|
-
parsed.result = common_metric_kind_t::haversine_k;
|
|
309
|
-
} else if (str_equals(name, len, "pearson")) {
|
|
310
|
-
parsed.result = common_metric_kind_t::pearson_k;
|
|
311
|
-
} else if (str_equals(name, len, "hamming")) {
|
|
312
|
-
parsed.result = common_metric_kind_t::bitwise_hamming_k;
|
|
313
|
-
} else if (str_equals(name, len, "tanimoto")) {
|
|
314
|
-
parsed.result = common_metric_kind_t::bitwise_tanimoto_k;
|
|
315
|
-
} else if (str_equals(name, len, "sorensen")) {
|
|
316
|
-
parsed.result = common_metric_kind_t::bitwise_sorensen_k;
|
|
317
|
-
} else
|
|
318
|
-
parsed.failed(
|
|
319
|
-
"Unknown distance, choose: l2sq, ip, cos, haversine, jaccard, pearson, hamming, tanimoto, sorensen");
|
|
320
|
-
return parsed;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
template <typename index_at>
|
|
324
|
-
expected_gt<index_at> make_punned( //
|
|
325
|
-
common_metric_kind_t metric, std::size_t dimensions, accuracy_t accuracy, index_config_t const& config) {
|
|
326
|
-
|
|
327
|
-
expected_gt<index_at> parsed;
|
|
328
|
-
if (metric == common_metric_kind_t::l2sq_k) {
|
|
329
|
-
if (dimensions == 0)
|
|
330
|
-
parsed.failed("The number of dimensions must be positive");
|
|
331
|
-
else
|
|
332
|
-
parsed.result = index_at::l2sq(dimensions, accuracy, config);
|
|
333
|
-
} else if (metric == common_metric_kind_t::ip_k) {
|
|
334
|
-
if (dimensions == 0)
|
|
335
|
-
parsed.failed("The number of dimensions must be positive");
|
|
336
|
-
else
|
|
337
|
-
parsed.result = index_at::ip(dimensions, accuracy, config);
|
|
338
|
-
} else if (metric == common_metric_kind_t::cos_k) {
|
|
339
|
-
if (dimensions == 0)
|
|
340
|
-
parsed.failed("The number of dimensions must be positive");
|
|
341
|
-
else
|
|
342
|
-
parsed.result = index_at::cos(dimensions, accuracy, config);
|
|
343
|
-
} else if (metric == common_metric_kind_t::haversine_k) {
|
|
344
|
-
if (dimensions != 2 && dimensions != 0)
|
|
345
|
-
parsed.failed("The number of dimensions must be equal to two");
|
|
346
|
-
else
|
|
347
|
-
parsed.result = index_at::haversine(accuracy, config);
|
|
348
|
-
} else
|
|
349
|
-
parsed.failed("Type-punned index only supports: l2sq, ip, cos, haversine");
|
|
350
|
-
return parsed;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
enum class isa_t {
|
|
354
|
-
auto_k,
|
|
355
|
-
neon_k,
|
|
356
|
-
sve_k,
|
|
357
|
-
avx2_k,
|
|
358
|
-
avx512_k,
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
inline char const* isa_name(isa_t isa) {
|
|
362
|
-
switch (isa) {
|
|
363
|
-
case isa_t::auto_k: return "auto";
|
|
364
|
-
case isa_t::neon_k: return "neon";
|
|
365
|
-
case isa_t::sve_k: return "sve";
|
|
366
|
-
case isa_t::avx2_k: return "avx2";
|
|
367
|
-
case isa_t::avx512_k: return "avx512";
|
|
368
|
-
default: return "";
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
inline std::size_t bytes_per_scalar(accuracy_t accuracy) {
|
|
373
|
-
switch (accuracy) {
|
|
374
|
-
case accuracy_t::f32_k: return 4;
|
|
375
|
-
case accuracy_t::f16_k: return 2;
|
|
376
|
-
case accuracy_t::f64_k: return 8;
|
|
377
|
-
case accuracy_t::f8_k: return 1;
|
|
378
|
-
default: return 0;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
inline bool supports_arm_sve() {
|
|
383
|
-
#if defined(USEARCH_IS_ARM)
|
|
384
|
-
#if defined(USEARCH_IS_LINUX)
|
|
385
|
-
unsigned long capabilities = getauxval(AT_HWCAP);
|
|
386
|
-
if (capabilities & HWCAP_SVE)
|
|
387
|
-
return true;
|
|
388
|
-
#endif
|
|
389
|
-
#endif
|
|
390
|
-
return false;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
template <typename from_scalar_at, typename to_scalar_at> struct cast_gt {
|
|
394
|
-
inline bool operator()(byte_t const* input, std::size_t bytes_in_input, byte_t* output) const {
|
|
395
|
-
from_scalar_at const* typed_input = reinterpret_cast<from_scalar_at const*>(input);
|
|
396
|
-
to_scalar_at* typed_output = reinterpret_cast<to_scalar_at*>(output);
|
|
397
|
-
std::transform( //
|
|
398
|
-
typed_input, typed_input + bytes_in_input / sizeof(from_scalar_at), typed_output,
|
|
399
|
-
[](from_scalar_at from) { return to_scalar_at(from); });
|
|
400
|
-
return true;
|
|
401
|
-
}
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
template <> struct cast_gt<f32_t, f32_t> {
|
|
405
|
-
bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
template <> struct cast_gt<f64_t, f64_t> {
|
|
409
|
-
bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
template <> struct cast_gt<f16_bits_t, f16_bits_t> {
|
|
413
|
-
bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
|
|
414
|
-
};
|
|
415
|
-
|
|
416
|
-
template <> struct cast_gt<f8_bits_t, f8_bits_t> {
|
|
417
|
-
bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
|
|
418
|
-
};
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* @brief Oversimplified type-punned index for equidimensional floating-point
|
|
422
|
-
* vectors with automatic down-casting and hardware acceleration.
|
|
423
|
-
*/
|
|
424
|
-
template <typename label_at = std::int64_t, typename id_at = std::uint32_t> //
|
|
425
|
-
class punned_gt {
|
|
426
|
-
public:
|
|
427
|
-
using label_t = label_at;
|
|
428
|
-
using id_t = id_at;
|
|
429
|
-
using distance_t = punned_distance_t;
|
|
430
|
-
|
|
431
|
-
private:
|
|
432
|
-
/// @brief Schema: input buffer, bytes in input buffer, output buffer.
|
|
433
|
-
using cast_t = std::function<bool(byte_t const*, std::size_t, byte_t*)>;
|
|
434
|
-
/// @brief Punned index.
|
|
435
|
-
using index_t = index_gt<punned_stateful_metric_t, label_t, id_t, byte_t>;
|
|
436
|
-
/// @brief A type-punned metric and metadata about present isa support.
|
|
437
|
-
struct metric_and_meta_t {
|
|
438
|
-
punned_stateful_metric_t metric;
|
|
439
|
-
isa_t acceleration;
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
using member_iterator_t = typename index_t::member_iterator_t;
|
|
443
|
-
using member_citerator_t = typename index_t::member_citerator_t;
|
|
444
|
-
using member_ref_t = typename index_t::member_ref_t;
|
|
445
|
-
using member_cref_t = typename index_t::member_cref_t;
|
|
446
|
-
|
|
447
|
-
std::size_t dimensions_ = 0;
|
|
448
|
-
std::size_t casted_vector_bytes_ = 0;
|
|
449
|
-
accuracy_t accuracy_ = accuracy_t::f32_k;
|
|
450
|
-
isa_t acceleration_ = isa_t::auto_k;
|
|
451
|
-
|
|
452
|
-
index_t* typed_{};
|
|
453
|
-
mutable std::vector<byte_t> cast_buffer_{};
|
|
454
|
-
struct casts_t {
|
|
455
|
-
cast_t from_f8{};
|
|
456
|
-
cast_t from_f16{};
|
|
457
|
-
cast_t from_f32{};
|
|
458
|
-
cast_t from_f64{};
|
|
459
|
-
cast_t to_f8{};
|
|
460
|
-
cast_t to_f16{};
|
|
461
|
-
cast_t to_f32{};
|
|
462
|
-
cast_t to_f64{};
|
|
463
|
-
} casts_;
|
|
464
|
-
|
|
465
|
-
punned_stateful_metric_t root_metric_;
|
|
466
|
-
|
|
467
|
-
mutable std::vector<std::size_t> available_threads_;
|
|
468
|
-
mutable std::mutex available_threads_mutex_;
|
|
469
|
-
|
|
470
|
-
using shared_mutex_t = std::mutex; // TODO: Find an OS-compatible solution
|
|
471
|
-
using shared_lock_t = std::unique_lock<shared_mutex_t>;
|
|
472
|
-
using unique_lock_t = std::unique_lock<shared_mutex_t>;
|
|
473
|
-
|
|
474
|
-
mutable shared_mutex_t lookup_table_mutex_;
|
|
475
|
-
tsl::robin_map<label_t, id_t> lookup_table_;
|
|
476
|
-
|
|
477
|
-
public:
|
|
478
|
-
using search_result_t = typename index_t::search_result_t;
|
|
479
|
-
using add_result_t = typename index_t::add_result_t;
|
|
480
|
-
using serialization_result_t = typename index_t::serialization_result_t;
|
|
481
|
-
using stats_t = typename index_t::stats_t;
|
|
482
|
-
|
|
483
|
-
punned_gt() = default;
|
|
484
|
-
punned_gt(punned_gt&& other) { swap(other); }
|
|
485
|
-
punned_gt& operator=(punned_gt&& other) {
|
|
486
|
-
swap(other);
|
|
487
|
-
return *this;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
~punned_gt() { aligned_index_free_(typed_); }
|
|
491
|
-
|
|
492
|
-
void swap(punned_gt& other) {
|
|
493
|
-
std::swap(dimensions_, other.dimensions_);
|
|
494
|
-
std::swap(casted_vector_bytes_, other.casted_vector_bytes_);
|
|
495
|
-
std::swap(accuracy_, other.accuracy_);
|
|
496
|
-
std::swap(acceleration_, other.acceleration_);
|
|
497
|
-
std::swap(typed_, other.typed_);
|
|
498
|
-
std::swap(cast_buffer_, other.cast_buffer_);
|
|
499
|
-
std::swap(casts_, other.casts_);
|
|
500
|
-
std::swap(root_metric_, other.root_metric_);
|
|
501
|
-
std::swap(available_threads_, other.available_threads_);
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
static index_config_t optimize(index_config_t config) { return index_t::optimize(config); }
|
|
505
|
-
|
|
506
|
-
std::size_t dimensions() const { return dimensions_; }
|
|
507
|
-
std::size_t connectivity() const { return typed_->connectivity(); }
|
|
508
|
-
std::size_t size() const { return typed_->size(); }
|
|
509
|
-
std::size_t capacity() const { return typed_->capacity(); }
|
|
510
|
-
index_config_t const& config() const { return typed_->config(); }
|
|
511
|
-
index_limits_t const& limits() const { return typed_->limits(); }
|
|
512
|
-
void clear() { return typed_->clear(); }
|
|
513
|
-
void change_expansion_add(std::size_t n) { typed_->change_expansion_add(n); }
|
|
514
|
-
void change_expansion_search(std::size_t n) { typed_->change_expansion_search(n); }
|
|
515
|
-
|
|
516
|
-
member_citerator_t cbegin() const { return typed_->cbegin(); }
|
|
517
|
-
member_citerator_t cend() const { return typed_->cend(); }
|
|
518
|
-
member_citerator_t begin() const { return typed_->begin(); }
|
|
519
|
-
member_citerator_t end() const { return typed_->end(); }
|
|
520
|
-
member_iterator_t begin() { return typed_->begin(); }
|
|
521
|
-
member_iterator_t end() { return typed_->end(); }
|
|
522
|
-
|
|
523
|
-
stats_t stats() const { return typed_->stats(); }
|
|
524
|
-
stats_t stats(std::size_t level) const { return typed_->stats(level); }
|
|
525
|
-
|
|
526
|
-
accuracy_t accuracy() const { return accuracy_; }
|
|
527
|
-
isa_t acceleration() const { return acceleration_; }
|
|
528
|
-
|
|
529
|
-
serialization_result_t save(char const* path) const { return typed_->save(path); }
|
|
530
|
-
serialization_result_t load(char const* path) { return typed_->load(path); }
|
|
531
|
-
serialization_result_t view(char const* path) { return typed_->view(path); }
|
|
532
|
-
bool reserve(index_limits_t limits) { return typed_->reserve(limits); }
|
|
533
|
-
|
|
534
|
-
std::size_t memory_usage(std::size_t allocator_entry_bytes = default_allocator_entry_bytes()) const {
|
|
535
|
-
return typed_->memory_usage(allocator_entry_bytes);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
bool contains(label_t label) const {
|
|
539
|
-
shared_lock_t lock(lookup_table_mutex_);
|
|
540
|
-
return lookup_table_.contains(label);
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
void export_labels(label_t* labels, std::size_t limit) const {
|
|
544
|
-
shared_lock_t lock(lookup_table_mutex_);
|
|
545
|
-
for (auto it = lookup_table_.begin(); it != lookup_table_.end() && limit; ++it, ++labels, --limit)
|
|
546
|
-
*labels = it->first;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// clang-format off
|
|
550
|
-
add_result_t add(label_t label, f8_bits_t const* vector) { return add_(label, vector, casts_.from_f8); }
|
|
551
|
-
add_result_t add(label_t label, f16_bits_t const* vector) { return add_(label, vector, casts_.from_f16); }
|
|
552
|
-
add_result_t add(label_t label, f32_t const* vector) { return add_(label, vector, casts_.from_f32); }
|
|
553
|
-
add_result_t add(label_t label, f64_t const* vector) { return add_(label, vector, casts_.from_f64); }
|
|
554
|
-
|
|
555
|
-
add_result_t add(label_t label, f8_bits_t const* vector, add_config_t config) { return add_(label, vector, config, casts_.from_f8); }
|
|
556
|
-
add_result_t add(label_t label, f16_bits_t const* vector, add_config_t config) { return add_(label, vector, config, casts_.from_f16); }
|
|
557
|
-
add_result_t add(label_t label, f32_t const* vector, add_config_t config) { return add_(label, vector, config, casts_.from_f32); }
|
|
558
|
-
add_result_t add(label_t label, f64_t const* vector, add_config_t config) { return add_(label, vector, config, casts_.from_f64); }
|
|
559
|
-
|
|
560
|
-
search_result_t search(f8_bits_t const* vector, std::size_t wanted) const { return search_(vector, wanted, casts_.from_f8); }
|
|
561
|
-
search_result_t search(f16_bits_t const* vector, std::size_t wanted) const { return search_(vector, wanted, casts_.from_f16); }
|
|
562
|
-
search_result_t search(f32_t const* vector, std::size_t wanted) const { return search_(vector, wanted, casts_.from_f32); }
|
|
563
|
-
search_result_t search(f64_t const* vector, std::size_t wanted) const { return search_(vector, wanted, casts_.from_f64); }
|
|
564
|
-
|
|
565
|
-
search_result_t search(f8_bits_t const* vector, std::size_t wanted, search_config_t config) const { return search_(vector, wanted, config, casts_.from_f8); }
|
|
566
|
-
search_result_t search(f16_bits_t const* vector, std::size_t wanted, search_config_t config) const { return search_(vector, wanted, config, casts_.from_f16); }
|
|
567
|
-
search_result_t search(f32_t const* vector, std::size_t wanted, search_config_t config) const { return search_(vector, wanted, config, casts_.from_f32); }
|
|
568
|
-
search_result_t search(f64_t const* vector, std::size_t wanted, search_config_t config) const { return search_(vector, wanted, config, casts_.from_f64); }
|
|
569
|
-
|
|
570
|
-
search_result_t search_around(label_t hint, f8_bits_t const* vector, std::size_t wanted) const { return search_around_(hint, vector, wanted, casts_.from_f8); }
|
|
571
|
-
search_result_t search_around(label_t hint, f16_bits_t const* vector, std::size_t wanted) const { return search_around_(hint, vector, wanted, casts_.from_f16); }
|
|
572
|
-
search_result_t search_around(label_t hint, f32_t const* vector, std::size_t wanted) const { return search_around_(hint, vector, wanted, casts_.from_f32); }
|
|
573
|
-
search_result_t search_around(label_t hint, f64_t const* vector, std::size_t wanted) const { return search_around_(hint, vector, wanted, casts_.from_f64); }
|
|
574
|
-
|
|
575
|
-
search_result_t search_around(label_t hint, f8_bits_t const* vector, std::size_t wanted, search_config_t config) const { return search_around_(hint, vector, wanted, config, casts_.from_f8); }
|
|
576
|
-
search_result_t search_around(label_t hint, f16_bits_t const* vector, std::size_t wanted, search_config_t config) const { return search_around_(hint, vector, wanted, config, casts_.from_f16); }
|
|
577
|
-
search_result_t search_around(label_t hint, f32_t const* vector, std::size_t wanted, search_config_t config) const { return search_around_(hint, vector, wanted, config, casts_.from_f32); }
|
|
578
|
-
search_result_t search_around(label_t hint, f64_t const* vector, std::size_t wanted, search_config_t config) const { return search_around_(hint, vector, wanted, config, casts_.from_f64); }
|
|
579
|
-
|
|
580
|
-
void reconstruct(label_t label, f8_bits_t* vector) const { return reconstruct_(label, vector, casts_.to_f8); }
|
|
581
|
-
void reconstruct(label_t label, f16_bits_t* vector) const { return reconstruct_(label, vector, casts_.to_f16); }
|
|
582
|
-
void reconstruct(label_t label, f32_t* vector) const { return reconstruct_(label, vector, casts_.to_f32); }
|
|
583
|
-
void reconstruct(label_t label, f64_t* vector) const { return reconstruct_(label, vector, casts_.to_f64); }
|
|
584
|
-
|
|
585
|
-
static punned_gt ip(std::size_t dimensions, accuracy_t accuracy = accuracy_t::f16_k, index_config_t config = {}) { return make_(dimensions, accuracy, ip_metric_(dimensions, accuracy), make_casts_(accuracy), config); }
|
|
586
|
-
static punned_gt l2sq(std::size_t dimensions, accuracy_t accuracy = accuracy_t::f16_k, index_config_t config = {}) { return make_(dimensions, accuracy, l2_metric_(dimensions, accuracy), make_casts_(accuracy), config); }
|
|
587
|
-
static punned_gt cos(std::size_t dimensions, accuracy_t accuracy = accuracy_t::f32_k, index_config_t config = {}) { return make_(dimensions, accuracy, cos_metric_(dimensions, accuracy), make_casts_(accuracy), config); }
|
|
588
|
-
static punned_gt haversine(accuracy_t accuracy = accuracy_t::f32_k, index_config_t config = {}) { return make_(2, accuracy, haversine_metric_(accuracy), make_casts_(accuracy), config); }
|
|
589
|
-
// clang-format on
|
|
590
|
-
|
|
591
|
-
static punned_gt udf( //
|
|
592
|
-
std::size_t dimensions, punned_stateful_metric_t metric, //
|
|
593
|
-
accuracy_t accuracy = accuracy_t::f32_k, index_config_t config = {}) {
|
|
594
|
-
return make_(dimensions, accuracy, {metric, isa_t::auto_k}, make_casts_(accuracy), config);
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
punned_gt fork() const {
|
|
598
|
-
punned_gt result;
|
|
599
|
-
|
|
600
|
-
result.dimensions_ = dimensions_;
|
|
601
|
-
result.accuracy_ = accuracy_;
|
|
602
|
-
result.acceleration_ = acceleration_;
|
|
603
|
-
result.casted_vector_bytes_ = casted_vector_bytes_;
|
|
604
|
-
result.cast_buffer_ = cast_buffer_;
|
|
605
|
-
result.casts_ = casts_;
|
|
606
|
-
|
|
607
|
-
result.root_metric_ = root_metric_;
|
|
608
|
-
index_t* raw = aligned_index_alloc_();
|
|
609
|
-
new (raw) index_t(config(), root_metric_);
|
|
610
|
-
result.typed_ = raw;
|
|
611
|
-
|
|
612
|
-
return result;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
private:
|
|
616
|
-
static index_t* aligned_index_alloc_() {
|
|
617
|
-
#if defined(USEARCH_IS_WINDOWS)
|
|
618
|
-
return (index_t*)_aligned_malloc(64 * divide_round_up<64>(sizeof(index_t)), 64);
|
|
619
|
-
#else
|
|
620
|
-
return (index_t*)aligned_alloc(64, 64 * divide_round_up<64>(sizeof(index_t)));
|
|
621
|
-
#endif
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
static void aligned_index_free_(index_t* raw) {
|
|
625
|
-
#if defined(USEARCH_IS_WINDOWS)
|
|
626
|
-
_aligned_free(raw);
|
|
627
|
-
#else
|
|
628
|
-
free(raw);
|
|
629
|
-
#endif
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
struct thread_lock_t {
|
|
633
|
-
punned_gt const& parent;
|
|
634
|
-
std::size_t thread_id;
|
|
635
|
-
|
|
636
|
-
~thread_lock_t() { parent.thread_unlock_(thread_id); }
|
|
637
|
-
};
|
|
638
|
-
|
|
639
|
-
thread_lock_t thread_lock_() const {
|
|
640
|
-
available_threads_mutex_.lock();
|
|
641
|
-
std::size_t thread_id = available_threads_.back();
|
|
642
|
-
available_threads_.pop_back();
|
|
643
|
-
available_threads_mutex_.unlock();
|
|
644
|
-
return {*this, thread_id};
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
void thread_unlock_(std::size_t thread_id) const {
|
|
648
|
-
available_threads_mutex_.lock();
|
|
649
|
-
available_threads_.push_back(thread_id);
|
|
650
|
-
available_threads_mutex_.unlock();
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
template <typename scalar_at>
|
|
654
|
-
add_result_t add_(label_t label, scalar_at const* vector, add_config_t config, cast_t const& cast) {
|
|
655
|
-
byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
|
|
656
|
-
std::size_t vector_bytes = dimensions_ * sizeof(scalar_at);
|
|
657
|
-
|
|
658
|
-
byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * config.thread;
|
|
659
|
-
bool casted = cast(vector_data, vector_bytes, casted_data);
|
|
660
|
-
if (casted)
|
|
661
|
-
vector_data = casted_data, vector_bytes = casted_vector_bytes_, config.store_vector = true;
|
|
662
|
-
|
|
663
|
-
add_result_t result = typed_->add(label, {vector_data, vector_bytes}, config);
|
|
664
|
-
{
|
|
665
|
-
unique_lock_t lock(lookup_table_mutex_);
|
|
666
|
-
lookup_table_.emplace(label, result.id);
|
|
667
|
-
}
|
|
668
|
-
return result;
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
template <typename scalar_at>
|
|
672
|
-
search_result_t search_( //
|
|
673
|
-
scalar_at const* vector, std::size_t wanted, //
|
|
674
|
-
search_config_t config, cast_t const& cast) const {
|
|
675
|
-
|
|
676
|
-
byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
|
|
677
|
-
std::size_t vector_bytes = dimensions_ * sizeof(scalar_at);
|
|
678
|
-
|
|
679
|
-
byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * config.thread;
|
|
680
|
-
bool casted = cast(vector_data, vector_bytes, casted_data);
|
|
681
|
-
if (casted)
|
|
682
|
-
vector_data = casted_data, vector_bytes = casted_vector_bytes_;
|
|
683
|
-
|
|
684
|
-
return typed_->search({vector_data, vector_bytes}, wanted, config);
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
template <typename scalar_at>
|
|
688
|
-
search_result_t search_around_( //
|
|
689
|
-
label_t hint, scalar_at const* vector, std::size_t wanted, //
|
|
690
|
-
search_config_t config, cast_t const& cast) const {
|
|
691
|
-
|
|
692
|
-
byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
|
|
693
|
-
std::size_t vector_bytes = dimensions_ * sizeof(scalar_at);
|
|
694
|
-
|
|
695
|
-
byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * config.thread;
|
|
696
|
-
bool casted = cast(vector_data, vector_bytes, casted_data);
|
|
697
|
-
if (casted)
|
|
698
|
-
vector_data = casted_data, vector_bytes = casted_vector_bytes_;
|
|
699
|
-
|
|
700
|
-
return typed_->search_around(static_cast<id_t>(hint), {vector_data, vector_bytes}, wanted, config);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
id_t lookup_id_(label_t label) const {
|
|
704
|
-
shared_lock_t lock(lookup_table_mutex_);
|
|
705
|
-
return lookup_table_.at(label);
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
template <typename scalar_at> void reconstruct_(label_t label, scalar_at* reconstructed, cast_t const& cast) const {
|
|
709
|
-
id_t id = lookup_id_(label);
|
|
710
|
-
member_citerator_t iterator = typed_->cbegin() + id;
|
|
711
|
-
member_cref_t member = *iterator;
|
|
712
|
-
byte_t const* casted_vector = reinterpret_cast<byte_t const*>(member.vector.data());
|
|
713
|
-
bool casted = cast(casted_vector, casted_vector_bytes_, (byte_t*)reconstructed);
|
|
714
|
-
if (!casted)
|
|
715
|
-
std::memcpy(reconstructed, casted_vector, casted_vector_bytes_);
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
template <typename scalar_at> add_result_t add_(label_t label, scalar_at const* vector, cast_t const& cast) {
|
|
719
|
-
thread_lock_t lock = thread_lock_();
|
|
720
|
-
add_config_t add_config;
|
|
721
|
-
add_config.thread = lock.thread_id;
|
|
722
|
-
return add_(label, vector, add_config, cast);
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
template <typename scalar_at>
|
|
726
|
-
search_result_t search_( //
|
|
727
|
-
scalar_at const* vector, std::size_t wanted, //
|
|
728
|
-
cast_t const& cast) const {
|
|
729
|
-
thread_lock_t lock = thread_lock_();
|
|
730
|
-
search_config_t search_config;
|
|
731
|
-
search_config.thread = lock.thread_id;
|
|
732
|
-
return search_(vector, wanted, search_config, cast);
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
template <typename scalar_at>
|
|
736
|
-
search_result_t search_around_( //
|
|
737
|
-
label_t hint, scalar_at const* vector, std::size_t wanted, //
|
|
738
|
-
cast_t const& cast) const {
|
|
739
|
-
thread_lock_t lock = thread_lock_();
|
|
740
|
-
search_config_t search_config;
|
|
741
|
-
search_config.thread = lock.thread_id;
|
|
742
|
-
return search_around_(hint, vector, wanted, search_config, cast);
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
static punned_gt make_( //
|
|
746
|
-
std::size_t dimensions, accuracy_t accuracy, //
|
|
747
|
-
metric_and_meta_t metric_and_meta, casts_t casts, //
|
|
748
|
-
index_config_t config) {
|
|
749
|
-
|
|
750
|
-
std::size_t hardware_threads = std::thread::hardware_concurrency();
|
|
751
|
-
punned_gt result;
|
|
752
|
-
result.dimensions_ = dimensions;
|
|
753
|
-
result.accuracy_ = accuracy;
|
|
754
|
-
result.casted_vector_bytes_ = bytes_per_scalar(accuracy) * dimensions;
|
|
755
|
-
result.cast_buffer_.resize(hardware_threads * result.casted_vector_bytes_);
|
|
756
|
-
result.casts_ = casts;
|
|
757
|
-
result.acceleration_ = metric_and_meta.acceleration;
|
|
758
|
-
result.root_metric_ = metric_and_meta.metric;
|
|
759
|
-
|
|
760
|
-
// Fill the thread IDs.
|
|
761
|
-
result.available_threads_.resize(hardware_threads);
|
|
762
|
-
std::iota(result.available_threads_.begin(), result.available_threads_.end(), 0ul);
|
|
763
|
-
|
|
764
|
-
// Available since C11, but only C++17, so we use the C version.
|
|
765
|
-
index_t* raw = aligned_index_alloc_();
|
|
766
|
-
new (raw) index_t(config, metric_and_meta.metric);
|
|
767
|
-
result.typed_ = raw;
|
|
768
|
-
return result;
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
template <typename to_scalar_at> static casts_t make_casts_() {
|
|
772
|
-
casts_t result;
|
|
773
|
-
result.from_f8 = cast_gt<f8_bits_t, to_scalar_at>{};
|
|
774
|
-
result.from_f16 = cast_gt<f16_bits_t, to_scalar_at>{};
|
|
775
|
-
result.from_f32 = cast_gt<f32_t, to_scalar_at>{};
|
|
776
|
-
result.from_f64 = cast_gt<f64_t, to_scalar_at>{};
|
|
777
|
-
result.to_f8 = cast_gt<to_scalar_at, f8_bits_t>{};
|
|
778
|
-
result.to_f16 = cast_gt<to_scalar_at, f16_bits_t>{};
|
|
779
|
-
result.to_f32 = cast_gt<to_scalar_at, f32_t>{};
|
|
780
|
-
result.to_f64 = cast_gt<to_scalar_at, f64_t>{};
|
|
781
|
-
return result;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
static casts_t make_casts_(accuracy_t accuracy) {
|
|
785
|
-
switch (accuracy) {
|
|
786
|
-
case accuracy_t::f64_k: return make_casts_<f64_t>();
|
|
787
|
-
case accuracy_t::f32_k: return make_casts_<f32_t>();
|
|
788
|
-
case accuracy_t::f16_k: return make_casts_<f16_bits_t>();
|
|
789
|
-
case accuracy_t::f8_k: return make_casts_<f8_bits_t>();
|
|
790
|
-
default: return {};
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
template <typename scalar_at, typename typed_at> //
|
|
795
|
-
static punned_stateful_metric_t pun_metric_(typed_at metric) {
|
|
796
|
-
return [=](byte_t const* a, byte_t const* b, std::size_t bytes, std::size_t) -> float {
|
|
797
|
-
return metric((scalar_at const*)a, (scalar_at const*)b, bytes / sizeof(scalar_at));
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
static metric_and_meta_t ip_metric_f32_(std::size_t dimensions) {
|
|
802
|
-
(void)dimensions;
|
|
803
|
-
#if defined(USEARCH_USE_SIMSIMD)
|
|
804
|
-
#if defined(USEARCH_IS_X86)
|
|
805
|
-
if (dimensions % 4 == 0)
|
|
806
|
-
return {
|
|
807
|
-
pun_metric_<simsimd_f32_t>([](simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) {
|
|
808
|
-
return 1.f - simsimd_dot_f32x4avx2(a, b, d);
|
|
809
|
-
}),
|
|
810
|
-
isa_t::avx2_k,
|
|
811
|
-
};
|
|
812
|
-
#elif defined(USEARCH_IS_ARM)
|
|
813
|
-
if (supports_arm_sve())
|
|
814
|
-
return {
|
|
815
|
-
pun_metric_<simsimd_f32_t>([](simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) {
|
|
816
|
-
return 1.f - simsimd_dot_f32sve(a, b, d);
|
|
817
|
-
}),
|
|
818
|
-
isa_t::sve_k,
|
|
819
|
-
};
|
|
820
|
-
if (dimensions % 4 == 0)
|
|
821
|
-
return {
|
|
822
|
-
pun_metric_<simsimd_f32_t>([](simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) {
|
|
823
|
-
return 1.f - simsimd_dot_f32x4neon(a, b, d);
|
|
824
|
-
}),
|
|
825
|
-
isa_t::neon_k,
|
|
826
|
-
};
|
|
827
|
-
#endif
|
|
828
|
-
#endif
|
|
829
|
-
return {pun_metric_<f32_t>(ip_gt<f32_t>{}), isa_t::auto_k};
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
static metric_and_meta_t cos_metric_f16_(std::size_t dimensions) {
|
|
833
|
-
(void)dimensions;
|
|
834
|
-
#if defined(USEARCH_USE_SIMSIMD)
|
|
835
|
-
#if defined(USEARCH_IS_X86)
|
|
836
|
-
if (dimensions % 16 == 0)
|
|
837
|
-
return {
|
|
838
|
-
pun_metric_<simsimd_f16_t>([](simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) {
|
|
839
|
-
return 1.f - simsimd_cos_f16x16avx512(a, b, d);
|
|
840
|
-
}),
|
|
841
|
-
isa_t::avx512_k,
|
|
842
|
-
};
|
|
843
|
-
#elif defined(USEARCH_IS_ARM)
|
|
844
|
-
if (dimensions % 4 == 0)
|
|
845
|
-
return {
|
|
846
|
-
pun_metric_<simsimd_f16_t>([](simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) {
|
|
847
|
-
return 1.f - simsimd_cos_f16x4neon(a, b, d);
|
|
848
|
-
}),
|
|
849
|
-
isa_t::neon_k,
|
|
850
|
-
};
|
|
851
|
-
#endif
|
|
852
|
-
#endif
|
|
853
|
-
return {pun_metric_<f16_bits_t>(cos_gt<f16_bits_t, f32_t>{}), isa_t::auto_k};
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
static metric_and_meta_t ip_metric_(std::size_t dimensions, accuracy_t accuracy) {
|
|
857
|
-
switch (accuracy) {
|
|
858
|
-
case accuracy_t::f32_k:
|
|
859
|
-
// The two most common numeric types for the most common metric have optimized versions
|
|
860
|
-
return ip_metric_f32_(dimensions);
|
|
861
|
-
case accuracy_t::f16_k:
|
|
862
|
-
// Dot-product accumulates error, Cosine-distance normalizes it
|
|
863
|
-
return cos_metric_f16_(dimensions);
|
|
864
|
-
|
|
865
|
-
case accuracy_t::f8_k: return {pun_metric_<f8_bits_t>(cos_f8_t{}), isa_t::auto_k};
|
|
866
|
-
case accuracy_t::f64_k: return {pun_metric_<f64_t>(ip_gt<f64_t>{}), isa_t::auto_k};
|
|
867
|
-
default: return {};
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
static metric_and_meta_t l2_metric_(std::size_t, accuracy_t accuracy) {
|
|
872
|
-
switch (accuracy) {
|
|
873
|
-
case accuracy_t::f8_k: return {pun_metric_<f8_bits_t>(l2sq_f8_t{}), isa_t::auto_k};
|
|
874
|
-
case accuracy_t::f16_k: return {pun_metric_<f16_bits_t>(l2sq_gt<f16_bits_t, f32_t>{}), isa_t::auto_k};
|
|
875
|
-
case accuracy_t::f32_k: return {pun_metric_<f32_t>(l2sq_gt<f32_t>{}), isa_t::auto_k};
|
|
876
|
-
case accuracy_t::f64_k: return {pun_metric_<f64_t>(l2sq_gt<f64_t>{}), isa_t::auto_k};
|
|
877
|
-
default: return {};
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
static metric_and_meta_t cos_metric_(std::size_t dimensions, accuracy_t accuracy) {
|
|
882
|
-
switch (accuracy) {
|
|
883
|
-
case accuracy_t::f8_k: return {pun_metric_<f8_bits_t>(cos_f8_t{}), isa_t::auto_k};
|
|
884
|
-
case accuracy_t::f16_k: return cos_metric_f16_(dimensions);
|
|
885
|
-
case accuracy_t::f32_k: return {pun_metric_<f32_t>(cos_gt<f32_t>{}), isa_t::auto_k};
|
|
886
|
-
case accuracy_t::f64_k: return {pun_metric_<f64_t>(cos_gt<f64_t>{}), isa_t::auto_k};
|
|
887
|
-
default: return {};
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
static metric_and_meta_t haversine_metric_(accuracy_t accuracy) {
|
|
892
|
-
switch (accuracy) {
|
|
893
|
-
case accuracy_t::f8_k: return {pun_metric_<f8_bits_t>(haversine_gt<f8_bits_t, f32_t>{}), isa_t::auto_k};
|
|
894
|
-
case accuracy_t::f16_k: return {pun_metric_<f16_bits_t>(haversine_gt<f16_bits_t, f32_t>{}), isa_t::auto_k};
|
|
895
|
-
case accuracy_t::f32_k: return {pun_metric_<f32_t>(haversine_gt<f32_t>{}), isa_t::auto_k};
|
|
896
|
-
case accuracy_t::f64_k: return {pun_metric_<f64_t>(haversine_gt<f64_t>{}), isa_t::auto_k};
|
|
897
|
-
default: return {};
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
};
|
|
901
|
-
|
|
902
|
-
using punned_small_t = punned_gt<std::int64_t, std::uint32_t>;
|
|
903
|
-
using punned_big_t = punned_gt<uuid_t, uint40_t>;
|
|
904
|
-
|
|
905
|
-
} // namespace usearch
|
|
906
|
-
} // namespace unum
|