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