usearch 2.13.5 → 2.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <h1 align="center">USearch</h1>
2
2
  <h3 align="center">
3
3
  Smaller & <a href="https://www.unum.cloud/blog/2023-11-07-scaling-vector-search-with-intel">Faster</a> Single-File<br/>
4
- Similarity Search Engine for <a href="https://github.com/ashvardanian/simsimd">Vectors</a> & 🔜 <a href="https://github.com/ashvardanian/stringzilla">Texts</a>
4
+ Similarity Search & Clustering Engine for <a href="https://github.com/ashvardanian/simsimd">Vectors</a> & 🔜 <a href="https://github.com/ashvardanian/stringzilla">Texts</a>
5
5
  </h3>
6
6
  <br/>
7
7
 
@@ -71,13 +71,10 @@ Linux • MacOS • Windows • iOS • WebAssembly •
71
71
 
72
72
  __Technical Insights__ and related articles:
73
73
 
74
- - [Uses Horner's method for polynomial approximations, beating GCC 12 by 119x](https://ashvardanian.com/posts/gcc-12-vs-avx512fp16/).
75
74
  - [Uses Arm SVE and x86 AVX-512's masked loads to eliminate tail `for`-loops](https://ashvardanian.com/posts/simsimd-faster-scipy/#tails-of-the-past-the-significance-of-masked-loads).
76
- - [Uses AVX-512 FP16 for half-precision operations, that few compilers vectorize](https://ashvardanian.com/posts/simsimd-faster-scipy/#the-challenge-of-f16).
77
- - [Substitutes LibC's `sqrt` calls with bithacks using Jan Kadlec's constant](https://ashvardanian.com/posts/simsimd-faster-scipy/#bonus-section-bypassing-sqrt-and-libc-dependencies).
75
+ - [Uses Horner's method for polynomial approximations, beating GCC 12 by 119x](https://ashvardanian.com/posts/gcc-12-vs-avx512fp16/).
78
76
  - [For every language implements a custom separate binding](https://ashvardanian.com/posts/porting-cpp-library-to-ten-languages/).
79
- - [For Python avoids slow PyBind11, and even `PyArg_ParseTuple` for speed](https://ashvardanian.com/posts/pybind11-cpython-tutorial/).
80
- - [For JavaScript uses typed arrays and NAPI for zero-copy calls](https://ashvardanian.com/posts/javascript-ai-vector-search/).
77
+
81
78
 
82
79
  ## Comparison with FAISS
83
80
 
@@ -119,17 +116,15 @@ USearch is compact and broadly compatible without sacrificing performance, prima
119
116
  Base functionality is identical to FAISS, and the interface must be familiar if you have ever investigated Approximate Nearest Neighbors search:
120
117
 
121
118
  ```py
122
- # pip install numpy usearch
119
+ # pip install usearch
123
120
 
124
121
  import numpy as np
125
122
  from usearch.index import Index
126
123
 
127
- index = Index(ndim=3)
128
-
129
- vector = np.array([0.2, 0.6, 0.4])
130
- index.add(42, vector)
131
-
132
- matches = index.search(vector, 10)
124
+ index = Index(ndim=3) # Default settings for 3D vectors
125
+ vector = np.array([0.2, 0.6, 0.4]) # Can be a matrix for batch operations
126
+ index.add(42, vector) # Add one or many vectors in parallel
127
+ matches = index.search(vector, 10) # Find 10 nearest neighbors
133
128
 
134
129
  assert matches[0].key == 42
135
130
  assert matches[0].distance <= 0.001
@@ -137,13 +132,13 @@ assert np.allclose(index[42], vector, atol=0.1) # Ensure high tolerance in mixed
137
132
  ```
138
133
 
139
134
  More settings are always available, and the API is designed to be as flexible as possible.
140
- The default storage/quantization level is hardware-dependant for efficiency, but `f16` is recommended for most modern CPUs.
135
+ The default storage/quantization level is hardware-dependant for efficiency, but `bf16` is recommended for most modern CPUs.
141
136
 
142
137
  ```py
143
138
  index = Index(
144
139
  ndim=3, # Define the number of dimensions in input vectors
145
140
  metric='cos', # Choose 'l2sq', 'ip', 'haversine' or other metric, default = 'cos'
146
- dtype='f16', # Store as 'f64', 'f32', 'f16', 'i8', 'b1'..., default = None
141
+ dtype='bf16', # Store as 'f64', 'f32', 'f16', 'i8', 'b1'..., default = None
147
142
  connectivity=16, # Optional: Limit number of neighbors per graph node
148
143
  expansion_add=128, # Optional: Control the recall of indexing
149
144
  expansion_search=64, # Optional: Control the quality of the search
@@ -549,7 +544,7 @@ doi = {10.5281/zenodo.7949416},
549
544
  author = {Vardanian, Ash},
550
545
  title = {{USearch by Unum Cloud}},
551
546
  url = {https://github.com/unum-cloud/usearch},
552
- version = {2.13.5},
547
+ version = {2.15.0},
553
548
  year = {2023},
554
549
  month = oct,
555
550
  }
package/cpp/bench.cpp CHANGED
@@ -468,6 +468,7 @@ struct args_t {
468
468
 
469
469
  bool big = false;
470
470
 
471
+ bool quantize_bf16 = false;
471
472
  bool quantize_f16 = false;
472
473
  bool quantize_i8 = false;
473
474
  bool quantize_b1 = false;
@@ -500,6 +501,8 @@ struct args_t {
500
501
  }
501
502
 
502
503
  scalar_kind_t quantization() const noexcept {
504
+ if (quantize_bf16)
505
+ return scalar_kind_t::bf16_k;
503
506
  if (quantize_f16)
504
507
  return scalar_kind_t::f16_k;
505
508
  if (quantize_i8)
@@ -611,6 +614,7 @@ int main(int argc, char** argv) {
611
614
  (option("--rows-skip") & value("integer", args.vectors_to_skip)).doc("Number of vectors to skip"),
612
615
  (option("--rows-take") & value("integer", args.vectors_to_take)).doc("Number of vectors to take"),
613
616
  ( //
617
+ option("-bf16", "--bf16quant").set(args.quantize_bf16).doc("Enable `bf16_t` quantization") |
614
618
  option("-f16", "--f16quant").set(args.quantize_f16).doc("Enable `f16_t` quantization") |
615
619
  option("-i8", "--i8quant").set(args.quantize_i8).doc("Enable `i8_t` quantization") |
616
620
  option("-b1", "--b1quant").set(args.quantize_b1).doc("Enable `b1x8_t` quantization")),
@@ -8,8 +8,8 @@
8
8
  #define UNUM_USEARCH_HPP
9
9
 
10
10
  #define USEARCH_VERSION_MAJOR 2
11
- #define USEARCH_VERSION_MINOR 13
12
- #define USEARCH_VERSION_PATCH 5
11
+ #define USEARCH_VERSION_MINOR 15
12
+ #define USEARCH_VERSION_PATCH 0
13
13
 
14
14
  // Inferring C++ version
15
15
  // https://stackoverflow.com/a/61552074
@@ -2870,16 +2870,16 @@ class index_gt {
2870
2870
  result.computed_distances = context.computed_distances;
2871
2871
  result.visited_members = context.iteration_cycles;
2872
2872
 
2873
- // If we are updating the entry node itself, it won't contain any neighbors,
2874
- // so we should traverse a level down to find the closest match.
2875
- if (updated_node_level == max_level_copy)
2876
- updated_node_level--;
2877
-
2878
2873
  // Go down the level, tracking only the closest match;
2879
2874
  // It may even be equal to the `updated_slot`
2880
- compressed_slot_t closest_slot = search_for_one_( //
2881
- value, metric, prefetch, //
2882
- entry_slot_copy, max_level_copy, updated_node_level, context);
2875
+ compressed_slot_t closest_slot =
2876
+ // If we are updating the entry node itself, it won't contain any neighbors,
2877
+ // so we should traverse a level down to find the closest match.
2878
+ updated_node_level == max_level_copy //
2879
+ ? entry_slot_copy
2880
+ : search_for_one_( //
2881
+ value, metric, prefetch, //
2882
+ entry_slot_copy, max_level_copy, updated_node_level, context);
2883
2883
 
2884
2884
  // From `updated_node_level` down - perform proper extensive search
2885
2885
  for (level_t level = (std::min)(updated_node_level, max_level_copy); level >= 0; --level) {
@@ -2943,7 +2943,7 @@ class index_gt {
2943
2943
  config.expansion = default_expansion_search();
2944
2944
 
2945
2945
  // Using references is cleaner, but would result in UBSan false positives
2946
- context_t* context_ptr = contexts_.data() + config.thread;
2946
+ context_t* context_ptr = contexts_.data() ? contexts_.data() + config.thread : nullptr;
2947
2947
  top_candidates_t* top_ptr = context_ptr ? &context_ptr->top_candidates : nullptr;
2948
2948
  search_result_t result{*this, top_ptr};
2949
2949
  if (!nodes_count_.load(std::memory_order_relaxed))
@@ -3697,6 +3697,7 @@ class index_gt {
3697
3697
  inline neighbors_ref_t neighbors_base_(node_t node) const noexcept { return {node.neighbors_tape()}; }
3698
3698
 
3699
3699
  inline neighbors_ref_t neighbors_non_base_(node_t node, level_t level) const noexcept {
3700
+ usearch_assert_m(level > 0 && level <= node.level(), "Linking to missing level");
3700
3701
  return {node.neighbors_tape() + pre_.neighbors_base_bytes + (level - 1) * pre_.neighbors_bytes};
3701
3702
  }
3702
3703
 
@@ -950,7 +950,7 @@ class index_dense_gt {
950
950
 
951
951
  void reserve(index_limits_t limits) {
952
952
  if (!try_reserve(limits))
953
- throw std::bad_alloc();
953
+ __usearch_raise_runtime_error("failed to reserve memory");
954
954
  }
955
955
 
956
956
  /**
@@ -2240,6 +2240,7 @@ class index_dense_gt {
2240
2240
  case scalar_kind_t::f64_k: return make_casts_<f64_t>();
2241
2241
  case scalar_kind_t::f32_k: return make_casts_<f32_t>();
2242
2242
  case scalar_kind_t::f16_k: return make_casts_<f16_t>();
2243
+ case scalar_kind_t::bf16_k: return make_casts_<bf16_t>();
2243
2244
  case scalar_kind_t::i8_k: return make_casts_<i8_t>();
2244
2245
  case scalar_kind_t::b1x8_k: return make_casts_<b1x8_t>();
2245
2246
  default: return {};
@@ -70,20 +70,10 @@ struct uuid_t {
70
70
  };
71
71
 
72
72
  class f16_bits_t;
73
+ class bf16_bits_t;
73
74
 
74
- #if !USEARCH_USE_FP16LIB
75
- #if USEARCH_USE_SIMSIMD
76
- using f16_native_t = simsimd_f16_t;
77
- #elif defined(USEARCH_DEFINED_ARM)
78
- using f16_native_t = __fp16;
79
- #else
80
- using f16_native_t = _Float16;
81
- #endif
82
- using f16_t = f16_native_t;
83
- #else
84
- using f16_native_t = void;
85
75
  using f16_t = f16_bits_t;
86
- #endif
76
+ using bf16_t = bf16_bits_t;
87
77
 
88
78
  using f64_t = double;
89
79
  using f32_t = float;
@@ -132,6 +122,7 @@ enum class scalar_kind_t : std::uint8_t {
132
122
  b1x8_k = 1,
133
123
  u40_k = 2,
134
124
  uuid_k = 3,
125
+ bf16_k = 4,
135
126
  // Common:
136
127
  f64_k = 10,
137
128
  f32_k = 11,
@@ -148,12 +139,6 @@ enum class scalar_kind_t : std::uint8_t {
148
139
  i8_k = 23,
149
140
  };
150
141
 
151
- enum class prefetching_kind_t {
152
- none_k,
153
- cpu_k,
154
- io_uring_k,
155
- };
156
-
157
142
  template <typename scalar_at> scalar_kind_t scalar_kind() noexcept {
158
143
  if (std::is_same<scalar_at, b1x8_t>())
159
144
  return scalar_kind_t::b1x8_k;
@@ -167,6 +152,8 @@ template <typename scalar_at> scalar_kind_t scalar_kind() noexcept {
167
152
  return scalar_kind_t::f32_k;
168
153
  if (std::is_same<scalar_at, f16_t>())
169
154
  return scalar_kind_t::f16_k;
155
+ if (std::is_same<scalar_at, bf16_t>())
156
+ return scalar_kind_t::bf16_k;
170
157
  if (std::is_same<scalar_at, i8_t>())
171
158
  return scalar_kind_t::i8_k;
172
159
  if (std::is_same<scalar_at, u64_t>())
@@ -208,6 +195,7 @@ inline std::size_t bits_per_scalar(scalar_kind_t scalar_kind) noexcept {
208
195
  switch (scalar_kind) {
209
196
  case scalar_kind_t::uuid_k: return 128;
210
197
  case scalar_kind_t::u40_k: return 40;
198
+ case scalar_kind_t::bf16_k: return 16;
211
199
  case scalar_kind_t::b1x8_k: return 1;
212
200
  case scalar_kind_t::u64_k: return 64;
213
201
  case scalar_kind_t::i64_k: return 64;
@@ -229,6 +217,7 @@ inline std::size_t bits_per_scalar_word(scalar_kind_t scalar_kind) noexcept {
229
217
  switch (scalar_kind) {
230
218
  case scalar_kind_t::uuid_k: return 128;
231
219
  case scalar_kind_t::u40_k: return 40;
220
+ case scalar_kind_t::bf16_k: return 16;
232
221
  case scalar_kind_t::b1x8_k: return 8;
233
222
  case scalar_kind_t::u64_k: return 64;
234
223
  case scalar_kind_t::i64_k: return 64;
@@ -250,6 +239,7 @@ inline char const* scalar_kind_name(scalar_kind_t scalar_kind) noexcept {
250
239
  switch (scalar_kind) {
251
240
  case scalar_kind_t::uuid_k: return "uuid";
252
241
  case scalar_kind_t::u40_k: return "u40";
242
+ case scalar_kind_t::bf16_k: return "bf16";
253
243
  case scalar_kind_t::b1x8_k: return "b1x8";
254
244
  case scalar_kind_t::u64_k: return "u64";
255
245
  case scalar_kind_t::i64_k: return "i64";
@@ -291,10 +281,14 @@ inline expected_gt<scalar_kind_t> scalar_kind_from_name(char const* name, std::s
291
281
  parsed.result = scalar_kind_t::f64_k;
292
282
  else if (str_equals(name, len, "f16"))
293
283
  parsed.result = scalar_kind_t::f16_k;
284
+ else if (str_equals(name, len, "bf16"))
285
+ parsed.result = scalar_kind_t::bf16_k;
294
286
  else if (str_equals(name, len, "i8"))
295
287
  parsed.result = scalar_kind_t::i8_k;
288
+ else if (str_equals(name, len, "b1"))
289
+ parsed.result = scalar_kind_t::b1x8_k;
296
290
  else
297
- parsed.failed("Unknown type, choose: f32, f16, f64, i8");
291
+ parsed.failed("Unknown type, choose: f64, f32, f16, bf16, i8, b1");
298
292
  return parsed;
299
293
  }
300
294
 
@@ -327,29 +321,76 @@ inline expected_gt<metric_kind_t> metric_from_name(char const* name, std::size_t
327
321
  "tanimoto, sorensen");
328
322
  return parsed;
329
323
  }
330
-
331
324
  inline expected_gt<metric_kind_t> metric_from_name(char const* name) {
332
325
  return metric_from_name(name, std::strlen(name));
333
326
  }
334
327
 
328
+ /**
329
+ * @brief Convenience function to upcast a half-precision floating point number to a single-precision one.
330
+ */
335
331
  inline float f16_to_f32(std::uint16_t u16) noexcept {
336
- #if !USEARCH_USE_FP16LIB
332
+ #if USEARCH_USE_FP16LIB
333
+ return fp16_ieee_to_fp32_value(u16);
334
+ #elif USEARCH_USE_SIMSIMD
335
+ return simsimd_uncompress_f16(u16);
336
+ #else
337
337
  f16_native_t f16;
338
338
  std::memcpy(&f16, &u16, sizeof(std::uint16_t));
339
339
  return float(f16);
340
- #else
341
- return fp16_ieee_to_fp32_value(u16);
342
340
  #endif
343
341
  }
344
342
 
343
+ /**
344
+ * @brief Convenience function to downcast a single-precision floating point number to a half-precision one.
345
+ */
345
346
  inline std::uint16_t f32_to_f16(float f32) noexcept {
346
- #if !USEARCH_USE_FP16LIB
347
+ #if USEARCH_USE_FP16LIB
348
+ return fp16_ieee_from_fp32_value(f32);
349
+ #elif USEARCH_USE_SIMSIMD
350
+ return simsimd_compress_f16(f32);
351
+ #else
347
352
  f16_native_t f16 = f16_native_t(f32);
348
353
  std::uint16_t u16;
349
354
  std::memcpy(&u16, &f16, sizeof(std::uint16_t));
350
355
  return u16;
356
+ #endif
357
+ }
358
+
359
+ /**
360
+ * @brief Convenience function to upcast a brain-floating point number to a single-precision one.
361
+ * https://github.com/ashvardanian/SimSIMD/blob/ff51434d90c66f916e94ff05b24530b127aa4cff/include/simsimd/types.h#L395-L410
362
+ */
363
+ inline float bf16_to_f32(std::uint16_t u16) noexcept {
364
+ #if USEARCH_USE_SIMSIMD
365
+ return simsimd_uncompress_bf16(u16);
351
366
  #else
352
- return fp16_ieee_from_fp32_value(f32);
367
+ union float_or_unsigned_int_t {
368
+ float f;
369
+ unsigned int i;
370
+ };
371
+ union float_or_unsigned_int_t result_union;
372
+ result_union.i = u16 << 16; // Zero extends the mantissa
373
+ return result_union.f;
374
+ #endif
375
+ }
376
+
377
+ /**
378
+ * @brief Convenience function to downcast a single-precision floating point number to a brain-floating point one.
379
+ * https://github.com/ashvardanian/SimSIMD/blob/ff51434d90c66f916e94ff05b24530b127aa4cff/include/simsimd/types.h#L412-L425
380
+ */
381
+ inline std::uint16_t f32_to_bf16(float f32) noexcept {
382
+ #if USEARCH_USE_SIMSIMD
383
+ return simsimd_compress_bf16(f32);
384
+ #else
385
+ union float_or_unsigned_int_t {
386
+ float f;
387
+ unsigned int i;
388
+ };
389
+ union float_or_unsigned_int_t value;
390
+ value.f = f32;
391
+ value.i >>= 16;
392
+ value.i &= 0xFFFF;
393
+ return (unsigned short)value.i;
353
394
  #endif
354
395
  }
355
396
 
@@ -412,6 +453,65 @@ class f16_bits_t {
412
453
  }
413
454
  };
414
455
 
456
+ /**
457
+ * @brief Numeric type for brain-floating point half-precision floating point.
458
+ * If hardware support isn't available, falls back to a hardware
459
+ * agnostic in-software implementation.
460
+ */
461
+ class bf16_bits_t {
462
+ std::uint16_t uint16_{};
463
+
464
+ public:
465
+ inline bf16_bits_t() noexcept : uint16_(0) {}
466
+ inline bf16_bits_t(bf16_bits_t&&) = default;
467
+ inline bf16_bits_t& operator=(bf16_bits_t&&) = default;
468
+ inline bf16_bits_t(bf16_bits_t const&) = default;
469
+ inline bf16_bits_t& operator=(bf16_bits_t const&) = default;
470
+
471
+ inline operator float() const noexcept { return bf16_to_f32(uint16_); }
472
+ inline explicit operator bool() const noexcept { return bf16_to_f32(uint16_) > 0.5f; }
473
+
474
+ inline bf16_bits_t(std::int8_t v) noexcept : uint16_(f32_to_bf16(v)) {}
475
+ inline bf16_bits_t(bool v) noexcept : uint16_(f32_to_bf16(v)) {}
476
+ inline bf16_bits_t(float v) noexcept : uint16_(f32_to_bf16(v)) {}
477
+ inline bf16_bits_t(double v) noexcept : uint16_(f32_to_bf16(static_cast<float>(v))) {}
478
+
479
+ inline bool operator<(bf16_bits_t const& other) const noexcept { return float(*this) < float(other); }
480
+
481
+ inline bf16_bits_t operator+(bf16_bits_t other) const noexcept { return {float(*this) + float(other)}; }
482
+ inline bf16_bits_t operator-(bf16_bits_t other) const noexcept { return {float(*this) - float(other)}; }
483
+ inline bf16_bits_t operator*(bf16_bits_t other) const noexcept { return {float(*this) * float(other)}; }
484
+ inline bf16_bits_t operator/(bf16_bits_t other) const noexcept { return {float(*this) / float(other)}; }
485
+ inline float operator+(float other) const noexcept { return float(*this) + other; }
486
+ inline float operator-(float other) const noexcept { return float(*this) - other; }
487
+ inline float operator*(float other) const noexcept { return float(*this) * other; }
488
+ inline float operator/(float other) const noexcept { return float(*this) / other; }
489
+ inline double operator+(double other) const noexcept { return float(*this) + other; }
490
+ inline double operator-(double other) const noexcept { return float(*this) - other; }
491
+ inline double operator*(double other) const noexcept { return float(*this) * other; }
492
+ inline double operator/(double other) const noexcept { return float(*this) / other; }
493
+
494
+ inline bf16_bits_t& operator+=(float v) noexcept {
495
+ uint16_ = f32_to_bf16(v + bf16_to_f32(uint16_));
496
+ return *this;
497
+ }
498
+
499
+ inline bf16_bits_t& operator-=(float v) noexcept {
500
+ uint16_ = f32_to_bf16(v - bf16_to_f32(uint16_));
501
+ return *this;
502
+ }
503
+
504
+ inline bf16_bits_t& operator*=(float v) noexcept {
505
+ uint16_ = f32_to_bf16(v * bf16_to_f32(uint16_));
506
+ return *this;
507
+ }
508
+
509
+ inline bf16_bits_t& operator/=(float v) noexcept {
510
+ uint16_ = f32_to_bf16(v / bf16_to_f32(uint16_));
511
+ return *this;
512
+ }
513
+ };
514
+
415
515
  /**
416
516
  * @brief An STL-based executor or a "thread-pool" for parallel execution.
417
517
  * Isn't efficient for small batches, as it recreates the threads on every call.
@@ -936,6 +1036,10 @@ template <> struct cast_gt<f16_bits_t, f16_bits_t> {
936
1036
  static bool try_(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
937
1037
  };
938
1038
 
1039
+ template <> struct cast_gt<bf16_bits_t, bf16_bits_t> {
1040
+ static bool try_(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
1041
+ };
1042
+
939
1043
  template <> struct cast_gt<i8_t, i8_t> {
940
1044
  static bool try_(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
941
1045
  };
@@ -1008,19 +1112,23 @@ template <typename to_scalar_at> struct cast_from_i8_gt {
1008
1112
  }
1009
1113
  };
1010
1114
 
1011
- template <> struct cast_gt<i8_t, f16_t> : public cast_from_i8_gt<f16_t> {};
1115
+ template <> struct cast_gt<i8_t, f16_bits_t> : public cast_from_i8_gt<f16_t> {};
1116
+ template <> struct cast_gt<i8_t, bf16_bits_t> : public cast_from_i8_gt<bf16_t> {};
1012
1117
  template <> struct cast_gt<i8_t, f32_t> : public cast_from_i8_gt<f32_t> {};
1013
1118
  template <> struct cast_gt<i8_t, f64_t> : public cast_from_i8_gt<f64_t> {};
1014
1119
 
1015
- template <> struct cast_gt<f16_t, i8_t> : public cast_to_i8_gt<f16_t> {};
1120
+ template <> struct cast_gt<f16_bits_t, i8_t> : public cast_to_i8_gt<f16_t> {};
1121
+ template <> struct cast_gt<bf16_bits_t, i8_t> : public cast_to_i8_gt<bf16_t> {};
1016
1122
  template <> struct cast_gt<f32_t, i8_t> : public cast_to_i8_gt<f32_t> {};
1017
1123
  template <> struct cast_gt<f64_t, i8_t> : public cast_to_i8_gt<f64_t> {};
1018
1124
 
1019
- template <> struct cast_gt<b1x8_t, f16_t> : public cast_from_b1x8_gt<f16_t> {};
1125
+ template <> struct cast_gt<b1x8_t, f16_bits_t> : public cast_from_b1x8_gt<f16_t> {};
1126
+ template <> struct cast_gt<b1x8_t, bf16_bits_t> : public cast_from_b1x8_gt<bf16_t> {};
1020
1127
  template <> struct cast_gt<b1x8_t, f32_t> : public cast_from_b1x8_gt<f32_t> {};
1021
1128
  template <> struct cast_gt<b1x8_t, f64_t> : public cast_from_b1x8_gt<f64_t> {};
1022
1129
 
1023
- template <> struct cast_gt<f16_t, b1x8_t> : public cast_to_b1x8_gt<f16_t> {};
1130
+ template <> struct cast_gt<f16_bits_t, b1x8_t> : public cast_to_b1x8_gt<f16_t> {};
1131
+ template <> struct cast_gt<bf16_bits_t, b1x8_t> : public cast_to_b1x8_gt<bf16_t> {};
1024
1132
  template <> struct cast_gt<f32_t, b1x8_t> : public cast_to_b1x8_gt<f32_t> {};
1025
1133
  template <> struct cast_gt<f64_t, b1x8_t> : public cast_to_b1x8_gt<f64_t> {};
1026
1134
 
@@ -1602,6 +1710,7 @@ class metric_punned_t {
1602
1710
  case scalar_kind_t::f32_k: datatype = simsimd_datatype_f32_k; break;
1603
1711
  case scalar_kind_t::f64_k: datatype = simsimd_datatype_f64_k; break;
1604
1712
  case scalar_kind_t::f16_k: datatype = simsimd_datatype_f16_k; break;
1713
+ case scalar_kind_t::bf16_k: datatype = simsimd_datatype_bf16_k; break;
1605
1714
  case scalar_kind_t::i8_k: datatype = simsimd_datatype_i8_k; break;
1606
1715
  case scalar_kind_t::b1x8_k: datatype = simsimd_datatype_b8_k; break;
1607
1716
  default: break;
@@ -1654,9 +1763,10 @@ class metric_punned_t {
1654
1763
  switch (metric_kind_) {
1655
1764
  case metric_kind_t::ip_k: {
1656
1765
  switch (scalar_kind_) {
1657
- case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<f32_t>>; break;
1658
- case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<f16_t, f32_t>>; break;
1766
+ case scalar_kind_t::bf16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<bf16_t, f32_t>>; break;
1659
1767
  case scalar_kind_t::i8_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<i8_t, f32_t>>; break;
1768
+ case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<f16_t, f32_t>>; break;
1769
+ case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<f32_t>>; break;
1660
1770
  case scalar_kind_t::f64_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<f64_t>>; break;
1661
1771
  default: metric_ptr_ = 0; break;
1662
1772
  }
@@ -1664,9 +1774,10 @@ class metric_punned_t {
1664
1774
  }
1665
1775
  case metric_kind_t::cos_k: {
1666
1776
  switch (scalar_kind_) {
1667
- case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<f32_t>>; break;
1668
- case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<f16_t, f32_t>>; break;
1777
+ case scalar_kind_t::bf16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<bf16_t, f32_t>>; break;
1669
1778
  case scalar_kind_t::i8_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<i8_t, f32_t>>; break;
1779
+ case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<f16_t, f32_t>>; break;
1780
+ case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<f32_t>>; break;
1670
1781
  case scalar_kind_t::f64_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<f64_t>>; break;
1671
1782
  default: metric_ptr_ = 0; break;
1672
1783
  }
@@ -1674,9 +1785,10 @@ class metric_punned_t {
1674
1785
  }
1675
1786
  case metric_kind_t::l2sq_k: {
1676
1787
  switch (scalar_kind_) {
1677
- case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<f32_t>>; break;
1678
- case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<f16_t, f32_t>>; break;
1788
+ case scalar_kind_t::bf16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<bf16_t, f32_t>>; break;
1679
1789
  case scalar_kind_t::i8_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<i8_t, f32_t>>; break;
1790
+ case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<f16_t, f32_t>>; break;
1791
+ case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<f32_t>>; break;
1680
1792
  case scalar_kind_t::f64_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<f64_t>>; break;
1681
1793
  default: metric_ptr_ = 0; break;
1682
1794
  }
@@ -1684,6 +1796,9 @@ class metric_punned_t {
1684
1796
  }
1685
1797
  case metric_kind_t::pearson_k: {
1686
1798
  switch (scalar_kind_) {
1799
+ case scalar_kind_t::bf16_k:
1800
+ metric_ptr_ = (uptr_t)&equidimensional_<metric_pearson_gt<bf16_t, f32_t>>;
1801
+ break;
1687
1802
  case scalar_kind_t::i8_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_pearson_gt<i8_t, f32_t>>; break;
1688
1803
  case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_pearson_gt<f16_t, f32_t>>; break;
1689
1804
  case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_pearson_gt<f32_t>>; break;
@@ -1694,7 +1809,8 @@ class metric_punned_t {
1694
1809
  }
1695
1810
  case metric_kind_t::haversine_k: {
1696
1811
  switch (scalar_kind_) {
1697
- case scalar_kind_t::f16_k: metric_ptr_ = 0; break; //< Having half-precision 2D coordinates is a bit silly.
1812
+ case scalar_kind_t::bf16_k: metric_ptr_ = 0; break; //< Half-precision 2D vectors are silly.
1813
+ case scalar_kind_t::f16_k: metric_ptr_ = 0; break; //< Half-precision 2D vectors are silly.
1698
1814
  case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_haversine_gt<f32_t>>; break;
1699
1815
  case scalar_kind_t::f64_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_haversine_gt<f64_t>>; break;
1700
1816
  default: metric_ptr_ = 0; break;
@@ -1703,6 +1819,9 @@ class metric_punned_t {
1703
1819
  }
1704
1820
  case metric_kind_t::divergence_k: {
1705
1821
  switch (scalar_kind_) {
1822
+ case scalar_kind_t::bf16_k:
1823
+ metric_ptr_ = (uptr_t)&equidimensional_<metric_divergence_gt<bf16_t, f32_t>>;
1824
+ break;
1706
1825
  case scalar_kind_t::f16_k:
1707
1826
  metric_ptr_ = (uptr_t)&equidimensional_<metric_divergence_gt<f16_t, f32_t>>;
1708
1827
  break;
@@ -1962,7 +2081,7 @@ class flat_hash_multi_set_gt {
1962
2081
  // Allocate new memory
1963
2082
  data_ = (char*)allocator_t{}.allocate(other.buckets_ * bytes_per_bucket());
1964
2083
  if (!data_)
1965
- throw std::bad_alloc();
2084
+ __usearch_raise_runtime_error("failed memory allocation");
1966
2085
 
1967
2086
  // Copy metadata
1968
2087
  buckets_ = other.buckets_;
@@ -2002,7 +2121,7 @@ class flat_hash_multi_set_gt {
2002
2121
  // Allocate new memory
2003
2122
  data_ = (char*)allocator_t{}.allocate(other.buckets_ * bytes_per_bucket());
2004
2123
  if (!data_)
2005
- throw std::bad_alloc();
2124
+ __usearch_raise_runtime_error("failed memory allocation");
2006
2125
 
2007
2126
  // Copy metadata
2008
2127
  buckets_ = other.buckets_;
@@ -2375,7 +2494,7 @@ class flat_hash_multi_set_gt {
2375
2494
 
2376
2495
  void reserve(std::size_t capacity) {
2377
2496
  if (!try_reserve(capacity))
2378
- throw std::bad_alloc();
2497
+ __usearch_raise_runtime_error("failed to reserve memory");
2379
2498
  }
2380
2499
 
2381
2500
  bool try_emplace(element_t const& element) noexcept {
@@ -29,6 +29,7 @@ export declare enum ScalarKind {
29
29
  F32 = "f32",
30
30
  F64 = "f64",
31
31
  F16 = "f16",
32
+ BF16 = "bf16",
32
33
  I8 = "i8",
33
34
  B1 = "b1"
34
35
  }
@@ -74,6 +74,7 @@ var ScalarKind;
74
74
  ScalarKind["F32"] = "f32";
75
75
  ScalarKind["F64"] = "f64";
76
76
  ScalarKind["F16"] = "f16";
77
+ ScalarKind["BF16"] = "bf16";
77
78
  ScalarKind["I8"] = "i8";
78
79
  ScalarKind["B1"] = "b1";
79
80
  })(ScalarKind || (exports.ScalarKind = ScalarKind = {}));
@@ -29,6 +29,7 @@ export declare enum ScalarKind {
29
29
  F32 = "f32",
30
30
  F64 = "f64",
31
31
  F16 = "f16",
32
+ BF16 = "bf16",
32
33
  I8 = "i8",
33
34
  B1 = "b1"
34
35
  }
@@ -33,6 +33,7 @@ export var ScalarKind;
33
33
  ScalarKind["F32"] = "f32";
34
34
  ScalarKind["F64"] = "f64";
35
35
  ScalarKind["F16"] = "f16";
36
+ ScalarKind["BF16"] = "bf16";
36
37
  ScalarKind["I8"] = "i8";
37
38
  ScalarKind["B1"] = "b1";
38
39
  })(ScalarKind || (ScalarKind = {}));
@@ -70,6 +70,7 @@ export enum ScalarKind {
70
70
  F32 = "f32",
71
71
  F64 = "f64",
72
72
  F16 = "f16",
73
+ BF16 = "bf16",
73
74
  I8 = "i8",
74
75
  B1 = "b1",
75
76
  }
@@ -610,12 +611,12 @@ function exactSearch(
610
611
  }
611
612
 
612
613
  const usearch = {
613
- Index,
614
- MetricKind,
615
- ScalarKind,
616
- Matches,
617
- BatchMatches,
618
- exactSearch,
614
+ Index,
615
+ MetricKind,
616
+ ScalarKind,
617
+ Matches,
618
+ BatchMatches,
619
+ exactSearch,
619
620
  };
620
621
  export default usearch;
621
622
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "2.13.5",
3
+ "version": "2.15.0",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ash Vardanian (https://ashvardanian.com/)",
6
6
  "license": "Apache 2.0",
Binary file
Binary file