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/src/advanced.hpp CHANGED
@@ -1,7 +1,18 @@
1
1
  #pragma once
2
- #include <functional>
3
- #include <thread>
4
- #include <vector>
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 i8q100_converted_t;
46
- class f16_converted_t;
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(__aarch64__)
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(__aarch64__)
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 f16_converted_t {
89
+ class f16_bits_t {
70
90
  std::uint16_t uint16_{};
71
91
 
72
92
  public:
73
- inline f16_converted_t() noexcept : uint16_(0) {}
74
- inline f16_converted_t(f16_converted_t&&) = default;
75
- inline f16_converted_t& operator=(f16_converted_t&&) = default;
76
- inline f16_converted_t(f16_converted_t const&) = default;
77
- inline f16_converted_t& operator=(f16_converted_t const&) = default;
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 f16_converted_t(i8q100_converted_t) noexcept;
82
- inline f16_converted_t(float v) noexcept : uint16_(f32_to_f16(v)) {}
83
- inline f16_converted_t(double v) noexcept : uint16_(f32_to_f16(v)) {}
84
-
85
- inline f16_converted_t operator+(f16_converted_t other) const noexcept { return {float(*this) + float(other)}; }
86
- inline f16_converted_t operator-(f16_converted_t other) const noexcept { return {float(*this) - float(other)}; }
87
- inline f16_converted_t operator*(f16_converted_t other) const noexcept { return {float(*this) * float(other)}; }
88
- inline f16_converted_t operator/(f16_converted_t other) const noexcept { return {float(*this) / float(other)}; }
89
- inline f16_converted_t operator+(float other) const noexcept { return {float(*this) + other}; }
90
- inline f16_converted_t operator-(float other) const noexcept { return {float(*this) - other}; }
91
- inline f16_converted_t operator*(float other) const noexcept { return {float(*this) * other}; }
92
- inline f16_converted_t operator/(float other) const noexcept { return {float(*this) / other}; }
93
- inline f16_converted_t operator+(double other) const noexcept { return {float(*this) + other}; }
94
- inline f16_converted_t operator-(double other) const noexcept { return {float(*this) - other}; }
95
- inline f16_converted_t operator*(double other) const noexcept { return {float(*this) * other}; }
96
- inline f16_converted_t operator/(double other) const noexcept { return {float(*this) / other}; }
97
-
98
- inline f16_converted_t& operator+=(float v) noexcept {
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 f16_converted_t& operator-=(float v) noexcept {
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 f16_converted_t& operator*=(float v) noexcept {
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 f16_converted_t& operator/=(float v) noexcept {
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
- class i8q100_converted_t {
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
- inline i8q100_converted_t() noexcept : int8_(0) {}
124
- inline i8q100_converted_t(i8q100_converted_t&&) = default;
125
- inline i8q100_converted_t& operator=(i8q100_converted_t&&) = default;
126
- inline i8q100_converted_t(i8q100_converted_t const&) = default;
127
- inline i8q100_converted_t& operator=(i8q100_converted_t const&) = default;
128
-
129
- inline operator float() const noexcept { return float(int8_) / 100.f; }
130
- inline operator f16_converted_t() const noexcept { return float(int8_) / 100.f; }
131
- inline operator double() const noexcept { return double(int8_) / 100.0; }
132
- inline operator std::int8_t() const noexcept { return int8_; }
133
- inline operator std::int16_t() const noexcept { return int8_; }
134
- inline operator std::int32_t() const noexcept { return int8_; }
135
- inline operator std::int64_t() const noexcept { return int8_; }
136
-
137
- inline i8q100_converted_t(float v) noexcept
138
- : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * 100.f), -100, 100)) {}
139
- inline i8q100_converted_t(double v) noexcept
140
- : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * 100.0), -100, 100)) {}
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 f16_converted_t::f16_converted_t(i8q100_converted_t v) noexcept : f16_converted_t(float(v)) {}
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
- i8q100_k,
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::i8q100_k: return "i8q100";
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, "i8q100"))
230
- accuracy = accuracy_t::i8q100_k;
257
+ else if (str_equals(name, len, "f8"))
258
+ accuracy = accuracy_t::f8_k;
231
259
  else
232
- throw std::runtime_error("Unknown type, choose: f32, f16, f64, i8q100");
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::runtime_error("Unknown distance, choose: l2_sq, ip, cos, hamming, jaccard");
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::i8q100_k: return 1;
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<f16_converted_t, f16_converted_t> {
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
- std::unique_ptr<index_t> index_;
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 from_i8q100{};
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
- : dimensions_(other.dimensions_), casted_vector_bytes_(other.casted_vector_bytes_), accuracy_(other.accuracy_),
378
- acceleration_(other.acceleration_), index_(std::move(other.index_)),
379
- cast_buffer_(std::move(other.cast_buffer_)), casts_(std::move(other.casts_)),
380
- root_metric_(std::move(other.root_metric_)), root_config_(std::move(other.root_config_)) {}
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, f16_converted_t const* vector, std::size_t thread = 0, bool copy = true) { return add(label, vector, thread, copy, casts_.from_f16); }
399
- void add(label_t label, f32_t const* vector, std::size_t thread = 0, bool copy = true) { return add(label, vector, thread, copy, casts_.from_f32); }
400
- void add(label_t label, f64_t const* vector, std::size_t thread = 0, bool copy = true) { return add(label, vector, thread, copy, casts_.from_f64); }
401
-
402
- std::size_t search(f16_converted_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, std::size_t thread = 0) const { return search(vector, wanted, matches, distances, thread, casts_.from_f16); }
403
- std::size_t search(f32_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, std::size_t thread = 0) const { return search(vector, wanted, matches, distances, thread, casts_.from_f32); }
404
- std::size_t search(f64_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, std::size_t thread = 0) const { return search(vector, wanted, matches, distances, thread, casts_.from_f64); }
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
- result.index_.reset(new index_t(result.root_config_, result.root_metric_));
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 max_threads = std::max(config.max_threads_add, config.max_threads_search);
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.from_i8q100 = cast_gt<i8q100_converted_t, to_scalar_at>{};
488
- result.from_f16 = cast_gt<f16_converted_t, to_scalar_at>{};
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<f16_converted_t>();
499
- case accuracy_t::i8q100_k: return make_casts<i8q100_converted_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<f16_converted_t>(ip_gt<f16_converted_t>{}), isa_t::auto_k};
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
- case accuracy_t::i8q100_k: return {pun_metric<i8q100_converted_t>(ip_i8q100_converted_t{}), isa_t::auto_k};
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::i8q100_k: return {};
579
- case accuracy_t::f16_k: return {pun_metric<f16_converted_t>(l2_squared_gt<f16_converted_t>{}), isa_t::auto_k};
580
- case accuracy_t::f32_k: return {pun_metric<f32_t>(l2_squared_gt<f32_t>{}), isa_t::auto_k};
581
- case accuracy_t::f64_k: return {pun_metric<f64_t>(l2_squared_gt<f64_t>{}), isa_t::auto_k};
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::i8q100_k: return {};
589
- case accuracy_t::f16_k: return {pun_metric<f16_converted_t>(cos_gt<f16_converted_t>{}), isa_t::auto_k};
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::i8q100_k: return {};
599
- case accuracy_t::f16_k: return {pun_metric<f16_converted_t>(haversine_gt<f16_converted_t>{}), isa_t::auto_k};
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 {};