usearch 0.1.10 → 0.2.2
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 +12 -1
- package/.vscode/launch.json +5 -4
- package/.vscode/settings.json +7 -1
- package/CMakeLists.txt +11 -6
- package/CMakeListsInstructions.md +39 -17
- package/Cargo.toml +1 -1
- package/Package.swift +40 -0
- package/README.md +36 -18
- package/VERSION +1 -1
- package/build.gradle +5 -1
- package/cmake/jemalloc.cmake +0 -2
- package/conanfile.py +18 -0
- package/include/usearch/usearch.hpp +337 -228
- package/javascript/lib.cpp +8 -2
- package/objc/USearchObjective.mm +150 -0
- package/objc/include/USearchObjective.h +69 -0
- package/package.json +1 -1
- package/pyproject.toml +18 -4
- package/python/bench.py +14 -2
- package/python/lib.cpp +353 -43
- package/python/test.py +10 -4
- package/python/usearch/__init__.py +0 -0
- package/rust/lib.cpp +13 -2
- package/setup.py +8 -3
- package/simsimd/include/simsimd/simsimd.h +15 -0
- package/src/advanced.hpp +232 -122
- package/src/bench.cpp +249 -79
- package/swift/Index+Sugar.swift +70 -0
- package/swift/Test.swift +20 -0
- package/swift/USearch.swift +8 -0
- package/java/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/java/gradle/wrapper/gradle-wrapper.properties +0 -6
- package/java/gradlew +0 -244
package/src/advanced.hpp
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
#pragma once
|
|
2
|
-
|
|
3
|
-
#
|
|
4
|
-
#
|
|
2
|
+
|
|
3
|
+
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
|
|
4
|
+
#define _USE_MATH_DEFINES
|
|
5
|
+
|
|
6
|
+
#define WINDOWS
|
|
7
|
+
#endif
|
|
8
|
+
|
|
9
|
+
#include <stdlib.h> // `aligned_alloc`
|
|
10
|
+
|
|
11
|
+
#include <functional> // `std::function`
|
|
12
|
+
#include <numeric> // `std::iota`
|
|
13
|
+
#include <stdexcept> // `std::invalid_argument`
|
|
14
|
+
#include <thread> // `std::thread`
|
|
15
|
+
#include <vector> // `std::vector`
|
|
5
16
|
|
|
6
17
|
#if __linux__
|
|
7
18
|
#include <sys/auxv.h>
|
|
@@ -42,11 +53,15 @@ template <typename at> inline at clamp(at v, at lo, at hi) noexcept {
|
|
|
42
53
|
return usearch::clamp(v, lo, hi, std::less<at>{});
|
|
43
54
|
}
|
|
44
55
|
|
|
45
|
-
class
|
|
46
|
-
class
|
|
56
|
+
class f8_bits_t;
|
|
57
|
+
class f16_bits_t;
|
|
47
58
|
|
|
48
59
|
inline float f16_to_f32(std::uint16_t u16) noexcept {
|
|
49
|
-
#if defined(
|
|
60
|
+
#if defined(__AVX512F__)
|
|
61
|
+
_Float16 f16;
|
|
62
|
+
std::memcpy(&f16, &u16, sizeof(std::uint16_t));
|
|
63
|
+
return float(f16);
|
|
64
|
+
#elif defined(__aarch64__)
|
|
50
65
|
__fp16 f16;
|
|
51
66
|
std::memcpy(&f16, &u16, sizeof(std::uint16_t));
|
|
52
67
|
return float(f16);
|
|
@@ -56,7 +71,12 @@ inline float f16_to_f32(std::uint16_t u16) noexcept {
|
|
|
56
71
|
}
|
|
57
72
|
|
|
58
73
|
inline std::uint16_t f32_to_f16(float f32) noexcept {
|
|
59
|
-
#if defined(
|
|
74
|
+
#if defined(__AVX512F__)
|
|
75
|
+
_Float16 f16 = _Float16(f32);
|
|
76
|
+
std::uint16_t u16;
|
|
77
|
+
std::memcpy(&u16, &f16, sizeof(std::uint16_t));
|
|
78
|
+
return u16;
|
|
79
|
+
#elif defined(__aarch64__)
|
|
60
80
|
__fp16 f16 = __fp16(f32);
|
|
61
81
|
std::uint16_t u16;
|
|
62
82
|
std::memcpy(&u16, &f16, sizeof(std::uint16_t));
|
|
@@ -66,81 +86,89 @@ inline std::uint16_t f32_to_f16(float f32) noexcept {
|
|
|
66
86
|
#endif
|
|
67
87
|
}
|
|
68
88
|
|
|
69
|
-
class
|
|
89
|
+
class f16_bits_t {
|
|
70
90
|
std::uint16_t uint16_{};
|
|
71
91
|
|
|
72
92
|
public:
|
|
73
|
-
inline
|
|
74
|
-
inline
|
|
75
|
-
inline
|
|
76
|
-
inline
|
|
77
|
-
inline
|
|
93
|
+
inline f16_bits_t() noexcept : uint16_(0) {}
|
|
94
|
+
inline f16_bits_t(f16_bits_t&&) = default;
|
|
95
|
+
inline f16_bits_t& operator=(f16_bits_t&&) = default;
|
|
96
|
+
inline f16_bits_t(f16_bits_t const&) = default;
|
|
97
|
+
inline f16_bits_t& operator=(f16_bits_t const&) = default;
|
|
78
98
|
|
|
79
99
|
inline operator float() const noexcept { return f16_to_f32(uint16_); }
|
|
80
100
|
|
|
81
|
-
inline
|
|
82
|
-
inline
|
|
83
|
-
inline
|
|
84
|
-
|
|
85
|
-
inline
|
|
86
|
-
inline
|
|
87
|
-
inline
|
|
88
|
-
inline
|
|
89
|
-
inline
|
|
90
|
-
inline
|
|
91
|
-
inline
|
|
92
|
-
inline
|
|
93
|
-
inline
|
|
94
|
-
inline
|
|
95
|
-
inline
|
|
96
|
-
inline
|
|
97
|
-
|
|
98
|
-
inline
|
|
101
|
+
inline f16_bits_t(f8_bits_t) noexcept;
|
|
102
|
+
inline f16_bits_t(float v) noexcept : uint16_(f32_to_f16(v)) {}
|
|
103
|
+
inline f16_bits_t(double v) noexcept : uint16_(f32_to_f16(v)) {}
|
|
104
|
+
|
|
105
|
+
inline f16_bits_t operator+(f16_bits_t other) const noexcept { return {float(*this) + float(other)}; }
|
|
106
|
+
inline f16_bits_t operator-(f16_bits_t other) const noexcept { return {float(*this) - float(other)}; }
|
|
107
|
+
inline f16_bits_t operator*(f16_bits_t other) const noexcept { return {float(*this) * float(other)}; }
|
|
108
|
+
inline f16_bits_t operator/(f16_bits_t other) const noexcept { return {float(*this) / float(other)}; }
|
|
109
|
+
inline f16_bits_t operator+(float other) const noexcept { return {float(*this) + other}; }
|
|
110
|
+
inline f16_bits_t operator-(float other) const noexcept { return {float(*this) - other}; }
|
|
111
|
+
inline f16_bits_t operator*(float other) const noexcept { return {float(*this) * other}; }
|
|
112
|
+
inline f16_bits_t operator/(float other) const noexcept { return {float(*this) / other}; }
|
|
113
|
+
inline f16_bits_t operator+(double other) const noexcept { return {float(*this) + other}; }
|
|
114
|
+
inline f16_bits_t operator-(double other) const noexcept { return {float(*this) - other}; }
|
|
115
|
+
inline f16_bits_t operator*(double other) const noexcept { return {float(*this) * other}; }
|
|
116
|
+
inline f16_bits_t operator/(double other) const noexcept { return {float(*this) / other}; }
|
|
117
|
+
|
|
118
|
+
inline f16_bits_t& operator+=(float v) noexcept {
|
|
99
119
|
uint16_ = f32_to_f16(v + f16_to_f32(uint16_));
|
|
100
120
|
return *this;
|
|
101
121
|
}
|
|
102
122
|
|
|
103
|
-
inline
|
|
123
|
+
inline f16_bits_t& operator-=(float v) noexcept {
|
|
104
124
|
uint16_ = f32_to_f16(v - f16_to_f32(uint16_));
|
|
105
125
|
return *this;
|
|
106
126
|
}
|
|
107
127
|
|
|
108
|
-
inline
|
|
128
|
+
inline f16_bits_t& operator*=(float v) noexcept {
|
|
109
129
|
uint16_ = f32_to_f16(v * f16_to_f32(uint16_));
|
|
110
130
|
return *this;
|
|
111
131
|
}
|
|
112
132
|
|
|
113
|
-
inline
|
|
133
|
+
inline f16_bits_t& operator/=(float v) noexcept {
|
|
114
134
|
uint16_ = f32_to_f16(v / f16_to_f32(uint16_));
|
|
115
135
|
return *this;
|
|
116
136
|
}
|
|
117
137
|
};
|
|
118
138
|
|
|
119
|
-
|
|
139
|
+
/**
|
|
140
|
+
* @brief Numeric type for uniformly-distributed floating point
|
|
141
|
+
* values within [-1,1] range, quantized to integers [-100,100].
|
|
142
|
+
*/
|
|
143
|
+
class f8_bits_t {
|
|
120
144
|
std::int8_t int8_{};
|
|
121
145
|
|
|
122
146
|
public:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
inline
|
|
128
|
-
|
|
129
|
-
inline operator
|
|
130
|
-
inline
|
|
131
|
-
inline operator
|
|
132
|
-
|
|
133
|
-
inline operator
|
|
134
|
-
inline operator
|
|
135
|
-
inline operator
|
|
136
|
-
|
|
137
|
-
inline
|
|
138
|
-
|
|
139
|
-
inline
|
|
140
|
-
|
|
147
|
+
constexpr static float divisor_k = 100.f;
|
|
148
|
+
constexpr static std::int8_t min_k = -100;
|
|
149
|
+
constexpr static std::int8_t max_k = 100;
|
|
150
|
+
|
|
151
|
+
inline f8_bits_t() noexcept : int8_(0) {}
|
|
152
|
+
inline f8_bits_t(f8_bits_t&&) = default;
|
|
153
|
+
inline f8_bits_t& operator=(f8_bits_t&&) = default;
|
|
154
|
+
inline f8_bits_t(f8_bits_t const&) = default;
|
|
155
|
+
inline f8_bits_t& operator=(f8_bits_t const&) = default;
|
|
156
|
+
|
|
157
|
+
inline operator float() const noexcept { return float(int8_) / divisor_k; }
|
|
158
|
+
inline operator f16_bits_t() const noexcept { return float(int8_) / divisor_k; }
|
|
159
|
+
inline operator double() const noexcept { return double(int8_) / divisor_k; }
|
|
160
|
+
inline explicit operator std::int8_t() const noexcept { return int8_; }
|
|
161
|
+
inline explicit operator std::int16_t() const noexcept { return int8_; }
|
|
162
|
+
inline explicit operator std::int32_t() const noexcept { return int8_; }
|
|
163
|
+
inline explicit operator std::int64_t() const noexcept { return int8_; }
|
|
164
|
+
|
|
165
|
+
inline f8_bits_t(float v) noexcept
|
|
166
|
+
: int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
|
|
167
|
+
inline f8_bits_t(double v) noexcept
|
|
168
|
+
: int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
|
|
141
169
|
};
|
|
142
170
|
|
|
143
|
-
inline
|
|
171
|
+
inline f16_bits_t::f16_bits_t(f8_bits_t v) noexcept : f16_bits_t(float(v)) {}
|
|
144
172
|
|
|
145
173
|
struct uuid_t {
|
|
146
174
|
std::uint8_t octets[16];
|
|
@@ -162,7 +190,7 @@ void multithreaded(std::size_t threads, std::size_t tasks, callback_at&& callbac
|
|
|
162
190
|
for (std::size_t thread_idx = 0; thread_idx != threads; ++thread_idx) {
|
|
163
191
|
threads_pool.emplace_back([=]() {
|
|
164
192
|
for (std::size_t task_idx = thread_idx * tasks_per_thread;
|
|
165
|
-
task_idx < std::min(tasks, thread_idx * tasks_per_thread + tasks_per_thread); ++task_idx)
|
|
193
|
+
task_idx < (std::min)(tasks, thread_idx * tasks_per_thread + tasks_per_thread); ++task_idx)
|
|
166
194
|
callback(thread_idx, task_idx);
|
|
167
195
|
});
|
|
168
196
|
}
|
|
@@ -200,7 +228,7 @@ enum class accuracy_t {
|
|
|
200
228
|
f32_k,
|
|
201
229
|
f16_k,
|
|
202
230
|
f64_k,
|
|
203
|
-
|
|
231
|
+
f8_k,
|
|
204
232
|
};
|
|
205
233
|
|
|
206
234
|
inline char const* accuracy_name(accuracy_t accuracy) noexcept {
|
|
@@ -208,7 +236,7 @@ inline char const* accuracy_name(accuracy_t accuracy) noexcept {
|
|
|
208
236
|
case accuracy_t::f32_k: return "f32";
|
|
209
237
|
case accuracy_t::f16_k: return "f16";
|
|
210
238
|
case accuracy_t::f64_k: return "f64";
|
|
211
|
-
case accuracy_t::
|
|
239
|
+
case accuracy_t::f8_k: return "f8";
|
|
212
240
|
default: return "";
|
|
213
241
|
}
|
|
214
242
|
}
|
|
@@ -226,10 +254,10 @@ inline accuracy_t accuracy_from_name(char const* name, std::size_t len) {
|
|
|
226
254
|
accuracy = accuracy_t::f64_k;
|
|
227
255
|
else if (str_equals(name, len, "f16"))
|
|
228
256
|
accuracy = accuracy_t::f16_k;
|
|
229
|
-
else if (str_equals(name, len, "
|
|
230
|
-
accuracy = accuracy_t::
|
|
257
|
+
else if (str_equals(name, len, "f8"))
|
|
258
|
+
accuracy = accuracy_t::f8_k;
|
|
231
259
|
else
|
|
232
|
-
throw std::
|
|
260
|
+
throw std::invalid_argument("Unknown type, choose: f32, f16, f64, f8");
|
|
233
261
|
return accuracy;
|
|
234
262
|
}
|
|
235
263
|
|
|
@@ -237,16 +265,24 @@ template <typename index_at>
|
|
|
237
265
|
inline index_at index_from_name( //
|
|
238
266
|
char const* name, std::size_t len, std::size_t dimensions, accuracy_t accuracy, config_t const& config) {
|
|
239
267
|
|
|
240
|
-
if (str_equals(name, len, "l2_sq") || str_equals(name, len, "euclidean_sq"))
|
|
268
|
+
if (str_equals(name, len, "l2_sq") || str_equals(name, len, "euclidean_sq")) {
|
|
269
|
+
if (dimensions == 0)
|
|
270
|
+
throw std::invalid_argument("The number of dimensions must be positive");
|
|
241
271
|
return index_at::l2(dimensions, accuracy, config);
|
|
242
|
-
else if (str_equals(name, len, "ip") || str_equals(name, len, "inner") || str_equals(name, len, "dot"))
|
|
272
|
+
} else if (str_equals(name, len, "ip") || str_equals(name, len, "inner") || str_equals(name, len, "dot")) {
|
|
273
|
+
if (dimensions == 0)
|
|
274
|
+
throw std::invalid_argument("The number of dimensions must be positive");
|
|
243
275
|
return index_at::ip(dimensions, accuracy, config);
|
|
244
|
-
else if (str_equals(name, len, "cos") || str_equals(name, len, "angular"))
|
|
276
|
+
} else if (str_equals(name, len, "cos") || str_equals(name, len, "angular")) {
|
|
277
|
+
if (dimensions == 0)
|
|
278
|
+
throw std::invalid_argument("The number of dimensions must be positive");
|
|
245
279
|
return index_at::cos(dimensions, accuracy, config);
|
|
246
|
-
else if (str_equals(name, len, "haversine"))
|
|
280
|
+
} else if (str_equals(name, len, "haversine")) {
|
|
281
|
+
if (dimensions != 2 && dimensions != 0)
|
|
282
|
+
throw std::invalid_argument("The number of dimensions must be equal to two");
|
|
247
283
|
return index_at::haversine(accuracy, config);
|
|
248
|
-
else
|
|
249
|
-
throw std::
|
|
284
|
+
} else
|
|
285
|
+
throw std::invalid_argument("Unknown distance, choose: l2_sq, ip, cos, hamming, jaccard");
|
|
250
286
|
return {};
|
|
251
287
|
}
|
|
252
288
|
|
|
@@ -274,7 +310,7 @@ inline std::size_t bytes_per_scalar(accuracy_t accuracy) noexcept {
|
|
|
274
310
|
case accuracy_t::f32_k: return 4;
|
|
275
311
|
case accuracy_t::f16_k: return 2;
|
|
276
312
|
case accuracy_t::f64_k: return 8;
|
|
277
|
-
case accuracy_t::
|
|
313
|
+
case accuracy_t::f8_k: return 1;
|
|
278
314
|
default: return 0;
|
|
279
315
|
}
|
|
280
316
|
}
|
|
@@ -309,26 +345,10 @@ template <> struct cast_gt<f64_t, f64_t> {
|
|
|
309
345
|
bool operator()(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
|
|
310
346
|
};
|
|
311
347
|
|
|
312
|
-
template <> struct cast_gt<
|
|
348
|
+
template <> struct cast_gt<f16_bits_t, f16_bits_t> {
|
|
313
349
|
bool operator()(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
|
|
314
350
|
};
|
|
315
351
|
|
|
316
|
-
struct ip_i8q100_converted_t {
|
|
317
|
-
float operator()(i8q100_converted_t const* a, i8q100_converted_t const* b, std::size_t d) const noexcept {
|
|
318
|
-
std::int64_t ab = 0;
|
|
319
|
-
#if defined(__GNUC__)
|
|
320
|
-
#pragma GCC ivdep
|
|
321
|
-
#elif defined(__clang__)
|
|
322
|
-
#pragma clang loop vectorize(enable)
|
|
323
|
-
#elif defined(_OPENMP)
|
|
324
|
-
#pragma omp simd reduction(+ : ab)
|
|
325
|
-
#endif
|
|
326
|
-
for (std::size_t i = 0; i < d; ++i)
|
|
327
|
-
ab += std::int16_t(a[i]) * std::int16_t(b[i]);
|
|
328
|
-
return 1.f - ab / 10000.f;
|
|
329
|
-
}
|
|
330
|
-
};
|
|
331
|
-
|
|
332
352
|
/**
|
|
333
353
|
* @brief Oversimplified type-punned index for equidimensional floating-point
|
|
334
354
|
* vectors with automatic down-casting and hardware acceleration.
|
|
@@ -359,10 +379,10 @@ class auto_index_gt {
|
|
|
359
379
|
accuracy_t accuracy_ = accuracy_t::f32_k;
|
|
360
380
|
isa_t acceleration_ = isa_t::auto_k;
|
|
361
381
|
|
|
362
|
-
|
|
363
|
-
mutable std::vector<byte_t> cast_buffer_;
|
|
382
|
+
index_t* index_{};
|
|
383
|
+
mutable std::vector<byte_t> cast_buffer_{};
|
|
364
384
|
struct casts_t {
|
|
365
|
-
cast_t
|
|
385
|
+
cast_t from_f8{};
|
|
366
386
|
cast_t from_f16{};
|
|
367
387
|
cast_t from_f32{};
|
|
368
388
|
cast_t from_f64{};
|
|
@@ -371,22 +391,42 @@ class auto_index_gt {
|
|
|
371
391
|
punned_stateful_metric_t root_metric_;
|
|
372
392
|
config_t root_config_;
|
|
373
393
|
|
|
394
|
+
mutable std::vector<std::size_t> available_threads_;
|
|
395
|
+
mutable std::mutex available_threads_mutex_;
|
|
396
|
+
|
|
374
397
|
public:
|
|
375
398
|
auto_index_gt() = default;
|
|
376
|
-
auto_index_gt(auto_index_gt&& other)
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
399
|
+
auto_index_gt(auto_index_gt&& other) { swap(other); }
|
|
400
|
+
auto_index_gt& operator=(auto_index_gt&& other) noexcept {
|
|
401
|
+
swap(other);
|
|
402
|
+
return *this;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
~auto_index_gt() { aligned_index_free(index_); }
|
|
406
|
+
|
|
407
|
+
void swap(auto_index_gt& other) noexcept {
|
|
408
|
+
std::swap(dimensions_, other.dimensions_);
|
|
409
|
+
std::swap(casted_vector_bytes_, other.casted_vector_bytes_);
|
|
410
|
+
std::swap(accuracy_, other.accuracy_);
|
|
411
|
+
std::swap(acceleration_, other.acceleration_);
|
|
412
|
+
std::swap(index_, other.index_);
|
|
413
|
+
std::swap(cast_buffer_, other.cast_buffer_);
|
|
414
|
+
std::swap(casts_, other.casts_);
|
|
415
|
+
std::swap(root_metric_, other.root_metric_);
|
|
416
|
+
std::swap(root_config_, other.root_config_);
|
|
417
|
+
std::swap(available_threads_, other.available_threads_);
|
|
418
|
+
}
|
|
381
419
|
|
|
382
420
|
std::size_t dimensions() const noexcept { return dimensions_; }
|
|
383
421
|
std::size_t connectivity() const noexcept { return index_->connectivity(); }
|
|
384
422
|
std::size_t size() const noexcept { return index_->size(); }
|
|
385
423
|
std::size_t capacity() const noexcept { return index_->capacity(); }
|
|
424
|
+
config_t const& config() const noexcept { return root_config_; }
|
|
425
|
+
void clear() noexcept { return index_->clear(); }
|
|
386
426
|
|
|
387
427
|
isa_t acceleration() const noexcept { return acceleration_; }
|
|
388
428
|
std::size_t concurrency() const noexcept {
|
|
389
|
-
return std::min(root_config_.max_threads_add, root_config_.max_threads_search);
|
|
429
|
+
return (std::min)(root_config_.max_threads_add, root_config_.max_threads_search);
|
|
390
430
|
}
|
|
391
431
|
|
|
392
432
|
void save(char const* path) const { index_->save(path); }
|
|
@@ -395,13 +435,21 @@ class auto_index_gt {
|
|
|
395
435
|
void reserve(std::size_t capacity) { index_->reserve(capacity); }
|
|
396
436
|
|
|
397
437
|
// clang-format off
|
|
398
|
-
void add(label_t label,
|
|
399
|
-
void add(label_t label, f32_t const* vector
|
|
400
|
-
void add(label_t label, f64_t const* vector
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
438
|
+
void add(label_t label, f16_bits_t const* vector) { return add(label, vector, true, casts_.from_f16); }
|
|
439
|
+
void add(label_t label, f32_t const* vector) { return add(label, vector, true, casts_.from_f32); }
|
|
440
|
+
void add(label_t label, f64_t const* vector) { return add(label, vector, true, casts_.from_f64); }
|
|
441
|
+
|
|
442
|
+
void add(label_t label, f16_bits_t const* vector, std::size_t thread, bool copy) { return add(label, vector, thread, copy, casts_.from_f16); }
|
|
443
|
+
void add(label_t label, f32_t const* vector, std::size_t thread, bool copy) { return add(label, vector, thread, copy, casts_.from_f32); }
|
|
444
|
+
void add(label_t label, f64_t const* vector, std::size_t thread, bool copy) { return add(label, vector, thread, copy, casts_.from_f64); }
|
|
445
|
+
|
|
446
|
+
std::size_t search(f16_bits_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances) const { return search(vector, wanted, matches, distances, casts_.from_f16); }
|
|
447
|
+
std::size_t search(f32_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances) const { return search(vector, wanted, matches, distances, casts_.from_f32); }
|
|
448
|
+
std::size_t search(f64_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances) const { return search(vector, wanted, matches, distances, casts_.from_f64); }
|
|
449
|
+
|
|
450
|
+
std::size_t search(f16_bits_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, std::size_t thread) const { return search(vector, wanted, matches, distances, thread, casts_.from_f16); }
|
|
451
|
+
std::size_t search(f32_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, std::size_t thread) const { return search(vector, wanted, matches, distances, thread, casts_.from_f32); }
|
|
452
|
+
std::size_t search(f64_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, std::size_t thread) const { return search(vector, wanted, matches, distances, thread, casts_.from_f64); }
|
|
405
453
|
|
|
406
454
|
static auto_index_gt ip(std::size_t dimensions, accuracy_t accuracy = accuracy_t::f16_k, config_t config = {}) { return make(dimensions, accuracy, ip_metric(dimensions, accuracy), make_casts(accuracy), config); }
|
|
407
455
|
static auto_index_gt l2(std::size_t dimensions, accuracy_t accuracy = accuracy_t::f16_k, config_t config = {}) { return make(dimensions, accuracy, l2_metric(dimensions, accuracy), make_casts(accuracy), config); }
|
|
@@ -427,12 +475,41 @@ class auto_index_gt {
|
|
|
427
475
|
|
|
428
476
|
result.root_metric_ = root_metric_;
|
|
429
477
|
result.root_config_ = root_config_;
|
|
430
|
-
|
|
478
|
+
index_t* raw = aligned_index_alloc();
|
|
479
|
+
new (raw) index_t(root_config_, root_metric_);
|
|
480
|
+
result.index_ = raw;
|
|
431
481
|
|
|
432
482
|
return result;
|
|
433
483
|
}
|
|
434
484
|
|
|
435
485
|
private:
|
|
486
|
+
static index_t* aligned_index_alloc() noexcept {
|
|
487
|
+
return (index_t*)aligned_alloc(64, 64 * divide_round_up<64>(sizeof(index_t)));
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
static void aligned_index_free(index_t* raw) noexcept { free(raw); }
|
|
491
|
+
|
|
492
|
+
struct thread_lock_t {
|
|
493
|
+
auto_index_gt const& parent;
|
|
494
|
+
std::size_t thread_id;
|
|
495
|
+
|
|
496
|
+
~thread_lock_t() { parent.thread_unlock(thread_id); }
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
thread_lock_t thread_lock() const {
|
|
500
|
+
available_threads_mutex_.lock();
|
|
501
|
+
std::size_t thread_id = available_threads_.back();
|
|
502
|
+
available_threads_.pop_back();
|
|
503
|
+
available_threads_mutex_.unlock();
|
|
504
|
+
return {*this, thread_id};
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
void thread_unlock(std::size_t thread_id) const {
|
|
508
|
+
available_threads_mutex_.lock();
|
|
509
|
+
available_threads_.push_back(thread_id);
|
|
510
|
+
available_threads_mutex_.unlock();
|
|
511
|
+
}
|
|
512
|
+
|
|
436
513
|
template <typename scalar_at>
|
|
437
514
|
void add(label_t label, scalar_at const* vector, std::size_t thread, bool copy, cast_t const& cast) {
|
|
438
515
|
byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
|
|
@@ -463,29 +540,54 @@ class auto_index_gt {
|
|
|
463
540
|
return index_->search({vector_data, vector_bytes}, wanted, matches, distances, thread);
|
|
464
541
|
}
|
|
465
542
|
|
|
543
|
+
template <typename scalar_at> void add(label_t label, scalar_at const* vector, bool copy, cast_t const& cast) {
|
|
544
|
+
thread_lock_t lock = thread_lock();
|
|
545
|
+
return add(label, vector, lock.thread_id, copy, cast);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
template <typename scalar_at>
|
|
549
|
+
std::size_t search( //
|
|
550
|
+
scalar_at const* vector, std::size_t wanted, //
|
|
551
|
+
label_t* matches, distance_t* distances, //
|
|
552
|
+
cast_t const& cast) const {
|
|
553
|
+
thread_lock_t lock = thread_lock();
|
|
554
|
+
return search(vector, wanted, matches, distances, lock.thread_id, cast);
|
|
555
|
+
}
|
|
556
|
+
|
|
466
557
|
static auto_index_gt make( //
|
|
467
558
|
std::size_t dimensions, accuracy_t accuracy, //
|
|
468
559
|
metric_and_meta_t metric_and_meta, casts_t casts, //
|
|
469
560
|
config_t config) {
|
|
470
561
|
|
|
471
|
-
std::size_t
|
|
562
|
+
std::size_t hardware_threads = std::thread::hardware_concurrency();
|
|
563
|
+
config.max_threads_add = config.max_threads_add ? config.max_threads_add : hardware_threads;
|
|
564
|
+
config.max_threads_search = config.max_threads_search ? config.max_threads_search : hardware_threads;
|
|
565
|
+
std::size_t max_threads = (std::max)(config.max_threads_add, config.max_threads_search);
|
|
472
566
|
auto_index_gt result;
|
|
473
567
|
result.dimensions_ = dimensions;
|
|
474
568
|
result.accuracy_ = accuracy;
|
|
475
569
|
result.casted_vector_bytes_ = bytes_per_scalar(accuracy) * dimensions;
|
|
476
570
|
result.cast_buffer_.resize(max_threads * result.casted_vector_bytes_);
|
|
477
571
|
result.casts_ = casts;
|
|
478
|
-
result.index_.reset(new index_t(config, metric_and_meta.metric));
|
|
479
572
|
result.acceleration_ = metric_and_meta.acceleration;
|
|
480
573
|
result.root_metric_ = metric_and_meta.metric;
|
|
481
574
|
result.root_config_ = config;
|
|
575
|
+
|
|
576
|
+
// Fill the thread IDs.
|
|
577
|
+
result.available_threads_.resize(max_threads);
|
|
578
|
+
std::iota(result.available_threads_.begin(), result.available_threads_.end(), 0ul);
|
|
579
|
+
|
|
580
|
+
// Availible since C11, but only C++17, so we use the C version.
|
|
581
|
+
index_t* raw = aligned_index_alloc();
|
|
582
|
+
new (raw) index_t(config, metric_and_meta.metric);
|
|
583
|
+
result.index_ = raw;
|
|
482
584
|
return result;
|
|
483
585
|
}
|
|
484
586
|
|
|
485
587
|
template <typename to_scalar_at> static casts_t make_casts() {
|
|
486
588
|
casts_t result;
|
|
487
|
-
result.
|
|
488
|
-
result.from_f16 = cast_gt<
|
|
589
|
+
result.from_f8 = cast_gt<f8_bits_t, to_scalar_at>{};
|
|
590
|
+
result.from_f16 = cast_gt<f16_bits_t, to_scalar_at>{};
|
|
489
591
|
result.from_f32 = cast_gt<f32_t, to_scalar_at>{};
|
|
490
592
|
result.from_f64 = cast_gt<f64_t, to_scalar_at>{};
|
|
491
593
|
return result;
|
|
@@ -495,8 +597,8 @@ class auto_index_gt {
|
|
|
495
597
|
switch (accuracy) {
|
|
496
598
|
case accuracy_t::f64_k: return make_casts<f64_t>();
|
|
497
599
|
case accuracy_t::f32_k: return make_casts<f32_t>();
|
|
498
|
-
case accuracy_t::f16_k: return make_casts<
|
|
499
|
-
case accuracy_t::
|
|
600
|
+
case accuracy_t::f16_k: return make_casts<f16_bits_t>();
|
|
601
|
+
case accuracy_t::f8_k: return make_casts<f8_bits_t>();
|
|
500
602
|
default: return {};
|
|
501
603
|
}
|
|
502
604
|
}
|
|
@@ -515,7 +617,7 @@ class auto_index_gt {
|
|
|
515
617
|
if (dimensions % 4 == 0)
|
|
516
618
|
return {
|
|
517
619
|
pun_metric<simsimd_f32_t>([](simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) noexcept {
|
|
518
|
-
return 1 - simsimd_dot_f32x4avx2(a, b, d);
|
|
620
|
+
return 1.f - simsimd_dot_f32x4avx2(a, b, d);
|
|
519
621
|
}),
|
|
520
622
|
isa_t::avx2_k,
|
|
521
623
|
};
|
|
@@ -523,14 +625,14 @@ class auto_index_gt {
|
|
|
523
625
|
if (supports_arm_sve())
|
|
524
626
|
return {
|
|
525
627
|
pun_metric<simsimd_f32_t>([](simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) noexcept {
|
|
526
|
-
return 1 - simsimd_dot_f32sve(a, b, d);
|
|
628
|
+
return 1.f - simsimd_dot_f32sve(a, b, d);
|
|
527
629
|
}),
|
|
528
630
|
isa_t::sve_k,
|
|
529
631
|
};
|
|
530
632
|
if (dimensions % 4 == 0)
|
|
531
633
|
return {
|
|
532
634
|
pun_metric<simsimd_f32_t>([](simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) noexcept {
|
|
533
|
-
return 1 - simsimd_dot_f32x4neon(a, b, d);
|
|
635
|
+
return 1.f - simsimd_dot_f32x4neon(a, b, d);
|
|
534
636
|
}),
|
|
535
637
|
isa_t::neon_k,
|
|
536
638
|
};
|
|
@@ -543,31 +645,39 @@ class auto_index_gt {
|
|
|
543
645
|
(void)dimensions;
|
|
544
646
|
#if defined(USEARCH_USE_SIMD)
|
|
545
647
|
#if defined(__x86_64__)
|
|
648
|
+
if (dimensions % 32 == 0)
|
|
649
|
+
return {
|
|
650
|
+
pun_metric<simsimd_f16_t>([](simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) noexcept {
|
|
651
|
+
return 1.f - simsimd_dot_f16x16avx512(a, b, d);
|
|
652
|
+
}),
|
|
653
|
+
isa_t::avx512_k,
|
|
654
|
+
};
|
|
546
655
|
#elif defined(__aarch64__)
|
|
547
656
|
if (supports_arm_sve())
|
|
548
657
|
return {
|
|
549
658
|
pun_metric<simsimd_f16_t>([](simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) noexcept {
|
|
550
|
-
return 1 - simsimd_dot_f16sve(a, b, d);
|
|
659
|
+
return 1.f - simsimd_dot_f16sve(a, b, d);
|
|
551
660
|
}),
|
|
552
661
|
isa_t::sve_k,
|
|
553
662
|
};
|
|
554
663
|
if (dimensions % 8 == 0)
|
|
555
664
|
return {
|
|
556
665
|
pun_metric<simsimd_f16_t>([](simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) noexcept {
|
|
557
|
-
return 1 - simsimd_dot_f16x8neon(a, b, d);
|
|
666
|
+
return 1.f - simsimd_dot_f16x8neon(a, b, d);
|
|
558
667
|
}),
|
|
559
668
|
isa_t::neon_k,
|
|
560
669
|
};
|
|
561
670
|
#endif
|
|
562
671
|
#endif
|
|
563
|
-
return {pun_metric<
|
|
672
|
+
return {pun_metric<f16_bits_t>(ip_gt<f16_bits_t, f32_t>{}), isa_t::auto_k};
|
|
564
673
|
}
|
|
565
674
|
|
|
566
675
|
static metric_and_meta_t ip_metric(std::size_t dimensions, accuracy_t accuracy) {
|
|
567
676
|
switch (accuracy) {
|
|
568
|
-
|
|
677
|
+
// The tow most common numeric types for the most common metric have afew optimized versions:
|
|
569
678
|
case accuracy_t::f16_k: return ip_metric_f16(dimensions);
|
|
570
679
|
case accuracy_t::f32_k: return ip_metric_f32(dimensions);
|
|
680
|
+
case accuracy_t::f8_k: return {pun_metric<f8_bits_t>(ip_gt<f8_bits_t, f32_t>{}), isa_t::auto_k};
|
|
571
681
|
case accuracy_t::f64_k: return {pun_metric<f64_t>(ip_gt<f64_t>{}), isa_t::auto_k};
|
|
572
682
|
default: return {};
|
|
573
683
|
}
|
|
@@ -575,18 +685,18 @@ class auto_index_gt {
|
|
|
575
685
|
|
|
576
686
|
static metric_and_meta_t l2_metric(std::size_t, accuracy_t accuracy) {
|
|
577
687
|
switch (accuracy) {
|
|
578
|
-
case accuracy_t::
|
|
579
|
-
case accuracy_t::f16_k: return {pun_metric<
|
|
580
|
-
case accuracy_t::f32_k: return {pun_metric<f32_t>(
|
|
581
|
-
case accuracy_t::f64_k: return {pun_metric<f64_t>(
|
|
688
|
+
case accuracy_t::f8_k: return {pun_metric<f8_bits_t>(l2sq_gt<f8_bits_t, f32_t>{}), isa_t::auto_k};
|
|
689
|
+
case accuracy_t::f16_k: return {pun_metric<f16_bits_t>(l2sq_gt<f16_bits_t, f32_t>{}), isa_t::auto_k};
|
|
690
|
+
case accuracy_t::f32_k: return {pun_metric<f32_t>(l2sq_gt<f32_t>{}), isa_t::auto_k};
|
|
691
|
+
case accuracy_t::f64_k: return {pun_metric<f64_t>(l2sq_gt<f64_t>{}), isa_t::auto_k};
|
|
582
692
|
default: return {};
|
|
583
693
|
}
|
|
584
694
|
}
|
|
585
695
|
|
|
586
696
|
static metric_and_meta_t cos_metric(std::size_t, accuracy_t accuracy) {
|
|
587
697
|
switch (accuracy) {
|
|
588
|
-
case accuracy_t::
|
|
589
|
-
case accuracy_t::f16_k: return {pun_metric<
|
|
698
|
+
case accuracy_t::f8_k: return {pun_metric<f8_bits_t>(cos_gt<f8_bits_t, f32_t>{}), isa_t::auto_k};
|
|
699
|
+
case accuracy_t::f16_k: return {pun_metric<f16_bits_t>(cos_gt<f16_bits_t, f32_t>{}), isa_t::auto_k};
|
|
590
700
|
case accuracy_t::f32_k: return {pun_metric<f32_t>(cos_gt<f32_t>{}), isa_t::auto_k};
|
|
591
701
|
case accuracy_t::f64_k: return {pun_metric<f64_t>(cos_gt<f64_t>{}), isa_t::auto_k};
|
|
592
702
|
default: return {};
|
|
@@ -595,8 +705,8 @@ class auto_index_gt {
|
|
|
595
705
|
|
|
596
706
|
static metric_and_meta_t haversine_metric(accuracy_t accuracy) {
|
|
597
707
|
switch (accuracy) {
|
|
598
|
-
case accuracy_t::
|
|
599
|
-
case accuracy_t::f16_k: return {pun_metric<
|
|
708
|
+
case accuracy_t::f8_k: return {pun_metric<f8_bits_t>(haversine_gt<f8_bits_t, f32_t>{}), isa_t::auto_k};
|
|
709
|
+
case accuracy_t::f16_k: return {pun_metric<f16_bits_t>(haversine_gt<f16_bits_t, f32_t>{}), isa_t::auto_k};
|
|
600
710
|
case accuracy_t::f32_k: return {pun_metric<f32_t>(haversine_gt<f32_t>{}), isa_t::auto_k};
|
|
601
711
|
case accuracy_t::f64_k: return {pun_metric<f64_t>(haversine_gt<f64_t>{}), isa_t::auto_k};
|
|
602
712
|
default: return {};
|