usearch 0.2.0 → 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,8 +1,18 @@
1
1
  #pragma once
2
- #include <functional>
3
- #include <stdexcept> // `std::invalid_argument`
4
- #include <thread>
5
- #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`
6
16
 
7
17
  #if __linux__
8
18
  #include <sys/auxv.h>
@@ -43,11 +53,15 @@ template <typename at> inline at clamp(at v, at lo, at hi) noexcept {
43
53
  return usearch::clamp(v, lo, hi, std::less<at>{});
44
54
  }
45
55
 
46
- class i8q100_converted_t;
47
- class f16_converted_t;
56
+ class f8_bits_t;
57
+ class f16_bits_t;
48
58
 
49
59
  inline float f16_to_f32(std::uint16_t u16) noexcept {
50
- #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__)
51
65
  __fp16 f16;
52
66
  std::memcpy(&f16, &u16, sizeof(std::uint16_t));
53
67
  return float(f16);
@@ -57,7 +71,12 @@ inline float f16_to_f32(std::uint16_t u16) noexcept {
57
71
  }
58
72
 
59
73
  inline std::uint16_t f32_to_f16(float f32) noexcept {
60
- #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__)
61
80
  __fp16 f16 = __fp16(f32);
62
81
  std::uint16_t u16;
63
82
  std::memcpy(&u16, &f16, sizeof(std::uint16_t));
@@ -67,81 +86,89 @@ inline std::uint16_t f32_to_f16(float f32) noexcept {
67
86
  #endif
68
87
  }
69
88
 
70
- class f16_converted_t {
89
+ class f16_bits_t {
71
90
  std::uint16_t uint16_{};
72
91
 
73
92
  public:
74
- inline f16_converted_t() noexcept : uint16_(0) {}
75
- inline f16_converted_t(f16_converted_t&&) = default;
76
- inline f16_converted_t& operator=(f16_converted_t&&) = default;
77
- inline f16_converted_t(f16_converted_t const&) = default;
78
- 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;
79
98
 
80
99
  inline operator float() const noexcept { return f16_to_f32(uint16_); }
81
100
 
82
- inline f16_converted_t(i8q100_converted_t) noexcept;
83
- inline f16_converted_t(float v) noexcept : uint16_(f32_to_f16(v)) {}
84
- inline f16_converted_t(double v) noexcept : uint16_(f32_to_f16(v)) {}
85
-
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/(f16_converted_t other) const noexcept { return {float(*this) / float(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/(float 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
- inline f16_converted_t operator/(double other) const noexcept { return {float(*this) / other}; }
98
-
99
- 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 {
100
119
  uint16_ = f32_to_f16(v + f16_to_f32(uint16_));
101
120
  return *this;
102
121
  }
103
122
 
104
- inline f16_converted_t& operator-=(float v) noexcept {
123
+ inline f16_bits_t& operator-=(float v) noexcept {
105
124
  uint16_ = f32_to_f16(v - f16_to_f32(uint16_));
106
125
  return *this;
107
126
  }
108
127
 
109
- inline f16_converted_t& operator*=(float v) noexcept {
128
+ inline f16_bits_t& operator*=(float v) noexcept {
110
129
  uint16_ = f32_to_f16(v * f16_to_f32(uint16_));
111
130
  return *this;
112
131
  }
113
132
 
114
- inline f16_converted_t& operator/=(float v) noexcept {
133
+ inline f16_bits_t& operator/=(float v) noexcept {
115
134
  uint16_ = f32_to_f16(v / f16_to_f32(uint16_));
116
135
  return *this;
117
136
  }
118
137
  };
119
138
 
120
- 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 {
121
144
  std::int8_t int8_{};
122
145
 
123
146
  public:
124
- inline i8q100_converted_t() noexcept : int8_(0) {}
125
- inline i8q100_converted_t(i8q100_converted_t&&) = default;
126
- inline i8q100_converted_t& operator=(i8q100_converted_t&&) = default;
127
- inline i8q100_converted_t(i8q100_converted_t const&) = default;
128
- inline i8q100_converted_t& operator=(i8q100_converted_t const&) = default;
129
-
130
- inline operator float() const noexcept { return float(int8_) / 100.f; }
131
- inline operator f16_converted_t() const noexcept { return float(int8_) / 100.f; }
132
- inline operator double() const noexcept { return double(int8_) / 100.0; }
133
- inline operator std::int8_t() const noexcept { return int8_; }
134
- inline operator std::int16_t() const noexcept { return int8_; }
135
- inline operator std::int32_t() const noexcept { return int8_; }
136
- inline operator std::int64_t() const noexcept { return int8_; }
137
-
138
- inline i8q100_converted_t(float v) noexcept
139
- : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * 100.f), -100, 100)) {}
140
- inline i8q100_converted_t(double v) noexcept
141
- : 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)) {}
142
169
  };
143
170
 
144
- 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)) {}
145
172
 
146
173
  struct uuid_t {
147
174
  std::uint8_t octets[16];
@@ -163,7 +190,7 @@ void multithreaded(std::size_t threads, std::size_t tasks, callback_at&& callbac
163
190
  for (std::size_t thread_idx = 0; thread_idx != threads; ++thread_idx) {
164
191
  threads_pool.emplace_back([=]() {
165
192
  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)
193
+ task_idx < (std::min)(tasks, thread_idx * tasks_per_thread + tasks_per_thread); ++task_idx)
167
194
  callback(thread_idx, task_idx);
168
195
  });
169
196
  }
@@ -201,7 +228,7 @@ enum class accuracy_t {
201
228
  f32_k,
202
229
  f16_k,
203
230
  f64_k,
204
- i8q100_k,
231
+ f8_k,
205
232
  };
206
233
 
207
234
  inline char const* accuracy_name(accuracy_t accuracy) noexcept {
@@ -209,7 +236,7 @@ inline char const* accuracy_name(accuracy_t accuracy) noexcept {
209
236
  case accuracy_t::f32_k: return "f32";
210
237
  case accuracy_t::f16_k: return "f16";
211
238
  case accuracy_t::f64_k: return "f64";
212
- case accuracy_t::i8q100_k: return "i8q100";
239
+ case accuracy_t::f8_k: return "f8";
213
240
  default: return "";
214
241
  }
215
242
  }
@@ -227,10 +254,10 @@ inline accuracy_t accuracy_from_name(char const* name, std::size_t len) {
227
254
  accuracy = accuracy_t::f64_k;
228
255
  else if (str_equals(name, len, "f16"))
229
256
  accuracy = accuracy_t::f16_k;
230
- else if (str_equals(name, len, "i8q100"))
231
- accuracy = accuracy_t::i8q100_k;
257
+ else if (str_equals(name, len, "f8"))
258
+ accuracy = accuracy_t::f8_k;
232
259
  else
233
- throw std::invalid_argument("Unknown type, choose: f32, f16, f64, i8q100");
260
+ throw std::invalid_argument("Unknown type, choose: f32, f16, f64, f8");
234
261
  return accuracy;
235
262
  }
236
263
 
@@ -283,7 +310,7 @@ inline std::size_t bytes_per_scalar(accuracy_t accuracy) noexcept {
283
310
  case accuracy_t::f32_k: return 4;
284
311
  case accuracy_t::f16_k: return 2;
285
312
  case accuracy_t::f64_k: return 8;
286
- case accuracy_t::i8q100_k: return 1;
313
+ case accuracy_t::f8_k: return 1;
287
314
  default: return 0;
288
315
  }
289
316
  }
@@ -318,26 +345,10 @@ template <> struct cast_gt<f64_t, f64_t> {
318
345
  bool operator()(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
319
346
  };
320
347
 
321
- template <> struct cast_gt<f16_converted_t, f16_converted_t> {
348
+ template <> struct cast_gt<f16_bits_t, f16_bits_t> {
322
349
  bool operator()(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
323
350
  };
324
351
 
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
352
  /**
342
353
  * @brief Oversimplified type-punned index for equidimensional floating-point
343
354
  * vectors with automatic down-casting and hardware acceleration.
@@ -368,10 +379,10 @@ class auto_index_gt {
368
379
  accuracy_t accuracy_ = accuracy_t::f32_k;
369
380
  isa_t acceleration_ = isa_t::auto_k;
370
381
 
371
- std::unique_ptr<index_t> index_;
372
- mutable std::vector<byte_t> cast_buffer_;
382
+ index_t* index_{};
383
+ mutable std::vector<byte_t> cast_buffer_{};
373
384
  struct casts_t {
374
- cast_t from_i8q100{};
385
+ cast_t from_f8{};
375
386
  cast_t from_f16{};
376
387
  cast_t from_f32{};
377
388
  cast_t from_f64{};
@@ -380,23 +391,42 @@ class auto_index_gt {
380
391
  punned_stateful_metric_t root_metric_;
381
392
  config_t root_config_;
382
393
 
394
+ mutable std::vector<std::size_t> available_threads_;
395
+ mutable std::mutex available_threads_mutex_;
396
+
383
397
  public:
384
398
  auto_index_gt() = default;
385
- auto_index_gt(auto_index_gt&& other)
386
- : dimensions_(other.dimensions_), casted_vector_bytes_(other.casted_vector_bytes_), accuracy_(other.accuracy_),
387
- acceleration_(other.acceleration_), index_(std::move(other.index_)),
388
- cast_buffer_(std::move(other.cast_buffer_)), casts_(std::move(other.casts_)),
389
- 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
+ }
390
419
 
391
420
  std::size_t dimensions() const noexcept { return dimensions_; }
392
421
  std::size_t connectivity() const noexcept { return index_->connectivity(); }
393
422
  std::size_t size() const noexcept { return index_->size(); }
394
423
  std::size_t capacity() const noexcept { return index_->capacity(); }
424
+ config_t const& config() const noexcept { return root_config_; }
395
425
  void clear() noexcept { return index_->clear(); }
396
426
 
397
427
  isa_t acceleration() const noexcept { return acceleration_; }
398
428
  std::size_t concurrency() const noexcept {
399
- 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);
400
430
  }
401
431
 
402
432
  void save(char const* path) const { index_->save(path); }
@@ -405,13 +435,21 @@ class auto_index_gt {
405
435
  void reserve(std::size_t capacity) { index_->reserve(capacity); }
406
436
 
407
437
  // clang-format off
408
- 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); }
409
- 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); }
410
- 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); }
411
-
412
- 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); }
413
- 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); }
414
- 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); }
415
453
 
416
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); }
417
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); }
@@ -437,12 +475,41 @@ class auto_index_gt {
437
475
 
438
476
  result.root_metric_ = root_metric_;
439
477
  result.root_config_ = root_config_;
440
- 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;
441
481
 
442
482
  return result;
443
483
  }
444
484
 
445
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
+
446
513
  template <typename scalar_at>
447
514
  void add(label_t label, scalar_at const* vector, std::size_t thread, bool copy, cast_t const& cast) {
448
515
  byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
@@ -473,29 +540,54 @@ class auto_index_gt {
473
540
  return index_->search({vector_data, vector_bytes}, wanted, matches, distances, thread);
474
541
  }
475
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
+
476
557
  static auto_index_gt make( //
477
558
  std::size_t dimensions, accuracy_t accuracy, //
478
559
  metric_and_meta_t metric_and_meta, casts_t casts, //
479
560
  config_t config) {
480
561
 
481
- 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);
482
566
  auto_index_gt result;
483
567
  result.dimensions_ = dimensions;
484
568
  result.accuracy_ = accuracy;
485
569
  result.casted_vector_bytes_ = bytes_per_scalar(accuracy) * dimensions;
486
570
  result.cast_buffer_.resize(max_threads * result.casted_vector_bytes_);
487
571
  result.casts_ = casts;
488
- result.index_.reset(new index_t(config, metric_and_meta.metric));
489
572
  result.acceleration_ = metric_and_meta.acceleration;
490
573
  result.root_metric_ = metric_and_meta.metric;
491
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;
492
584
  return result;
493
585
  }
494
586
 
495
587
  template <typename to_scalar_at> static casts_t make_casts() {
496
588
  casts_t result;
497
- result.from_i8q100 = cast_gt<i8q100_converted_t, to_scalar_at>{};
498
- 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>{};
499
591
  result.from_f32 = cast_gt<f32_t, to_scalar_at>{};
500
592
  result.from_f64 = cast_gt<f64_t, to_scalar_at>{};
501
593
  return result;
@@ -505,8 +597,8 @@ class auto_index_gt {
505
597
  switch (accuracy) {
506
598
  case accuracy_t::f64_k: return make_casts<f64_t>();
507
599
  case accuracy_t::f32_k: return make_casts<f32_t>();
508
- case accuracy_t::f16_k: return make_casts<f16_converted_t>();
509
- 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>();
510
602
  default: return {};
511
603
  }
512
604
  }
@@ -525,7 +617,7 @@ class auto_index_gt {
525
617
  if (dimensions % 4 == 0)
526
618
  return {
527
619
  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);
620
+ return 1.f - simsimd_dot_f32x4avx2(a, b, d);
529
621
  }),
530
622
  isa_t::avx2_k,
531
623
  };
@@ -533,14 +625,14 @@ class auto_index_gt {
533
625
  if (supports_arm_sve())
534
626
  return {
535
627
  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);
628
+ return 1.f - simsimd_dot_f32sve(a, b, d);
537
629
  }),
538
630
  isa_t::sve_k,
539
631
  };
540
632
  if (dimensions % 4 == 0)
541
633
  return {
542
634
  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);
635
+ return 1.f - simsimd_dot_f32x4neon(a, b, d);
544
636
  }),
545
637
  isa_t::neon_k,
546
638
  };
@@ -553,31 +645,39 @@ class auto_index_gt {
553
645
  (void)dimensions;
554
646
  #if defined(USEARCH_USE_SIMD)
555
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
+ };
556
655
  #elif defined(__aarch64__)
557
656
  if (supports_arm_sve())
558
657
  return {
559
658
  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);
659
+ return 1.f - simsimd_dot_f16sve(a, b, d);
561
660
  }),
562
661
  isa_t::sve_k,
563
662
  };
564
663
  if (dimensions % 8 == 0)
565
664
  return {
566
665
  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);
666
+ return 1.f - simsimd_dot_f16x8neon(a, b, d);
568
667
  }),
569
668
  isa_t::neon_k,
570
669
  };
571
670
  #endif
572
671
  #endif
573
- 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};
574
673
  }
575
674
 
576
675
  static metric_and_meta_t ip_metric(std::size_t dimensions, accuracy_t accuracy) {
577
676
  switch (accuracy) {
578
- 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:
579
678
  case accuracy_t::f16_k: return ip_metric_f16(dimensions);
580
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};
581
681
  case accuracy_t::f64_k: return {pun_metric<f64_t>(ip_gt<f64_t>{}), isa_t::auto_k};
582
682
  default: return {};
583
683
  }
@@ -585,18 +685,18 @@ class auto_index_gt {
585
685
 
586
686
  static metric_and_meta_t l2_metric(std::size_t, accuracy_t accuracy) {
587
687
  switch (accuracy) {
588
- case accuracy_t::i8q100_k: return {};
589
- case accuracy_t::f16_k: return {pun_metric<f16_converted_t>(l2_squared_gt<f16_converted_t>{}), isa_t::auto_k};
590
- case accuracy_t::f32_k: return {pun_metric<f32_t>(l2_squared_gt<f32_t>{}), isa_t::auto_k};
591
- 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};
592
692
  default: return {};
593
693
  }
594
694
  }
595
695
 
596
696
  static metric_and_meta_t cos_metric(std::size_t, accuracy_t accuracy) {
597
697
  switch (accuracy) {
598
- case accuracy_t::i8q100_k: return {};
599
- 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};
600
700
  case accuracy_t::f32_k: return {pun_metric<f32_t>(cos_gt<f32_t>{}), isa_t::auto_k};
601
701
  case accuracy_t::f64_k: return {pun_metric<f64_t>(cos_gt<f64_t>{}), isa_t::auto_k};
602
702
  default: return {};
@@ -605,8 +705,8 @@ class auto_index_gt {
605
705
 
606
706
  static metric_and_meta_t haversine_metric(accuracy_t accuracy) {
607
707
  switch (accuracy) {
608
- case accuracy_t::i8q100_k: return {};
609
- 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};
610
710
  case accuracy_t::f32_k: return {pun_metric<f32_t>(haversine_gt<f32_t>{}), isa_t::auto_k};
611
711
  case accuracy_t::f64_k: return {pun_metric<f64_t>(haversine_gt<f64_t>{}), isa_t::auto_k};
612
712
  default: return {};