usearch 2.13.5 → 2.14.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.14.0},
553
548
  year = {2023},
554
549
  month = oct,
555
550
  }
@@ -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 14
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
 
@@ -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,19 +70,26 @@ struct uuid_t {
70
70
  };
71
71
 
72
72
  class f16_bits_t;
73
+ class bf16_bits_t;
73
74
 
74
75
  #if !USEARCH_USE_FP16LIB
75
76
  #if USEARCH_USE_SIMSIMD
76
77
  using f16_native_t = simsimd_f16_t;
78
+ using bf16_native_t = simsimd_bf16_t;
77
79
  #elif defined(USEARCH_DEFINED_ARM)
78
80
  using f16_native_t = __fp16;
81
+ using bf16_native_t = __fp16; // No better choice on most compilers!
79
82
  #else
80
83
  using f16_native_t = _Float16;
84
+ using bf16_native_t = _Float16; // No better choice on most compilers!
81
85
  #endif
82
86
  using f16_t = f16_native_t;
87
+ using bf16_t = bf16_native_t;
83
88
  #else
84
89
  using f16_native_t = void;
90
+ using bf16_native_t = void;
85
91
  using f16_t = f16_bits_t;
92
+ using bf16_t = bf16_bits_t;
86
93
  #endif
87
94
 
88
95
  using f64_t = double;
@@ -132,6 +139,7 @@ enum class scalar_kind_t : std::uint8_t {
132
139
  b1x8_k = 1,
133
140
  u40_k = 2,
134
141
  uuid_k = 3,
142
+ bf16_k = 4,
135
143
  // Common:
136
144
  f64_k = 10,
137
145
  f32_k = 11,
@@ -167,6 +175,8 @@ template <typename scalar_at> scalar_kind_t scalar_kind() noexcept {
167
175
  return scalar_kind_t::f32_k;
168
176
  if (std::is_same<scalar_at, f16_t>())
169
177
  return scalar_kind_t::f16_k;
178
+ if (std::is_same<scalar_at, bf16_t>())
179
+ return scalar_kind_t::bf16_k;
170
180
  if (std::is_same<scalar_at, i8_t>())
171
181
  return scalar_kind_t::i8_k;
172
182
  if (std::is_same<scalar_at, u64_t>())
@@ -208,6 +218,7 @@ inline std::size_t bits_per_scalar(scalar_kind_t scalar_kind) noexcept {
208
218
  switch (scalar_kind) {
209
219
  case scalar_kind_t::uuid_k: return 128;
210
220
  case scalar_kind_t::u40_k: return 40;
221
+ case scalar_kind_t::bf16_k: return 16;
211
222
  case scalar_kind_t::b1x8_k: return 1;
212
223
  case scalar_kind_t::u64_k: return 64;
213
224
  case scalar_kind_t::i64_k: return 64;
@@ -229,6 +240,7 @@ inline std::size_t bits_per_scalar_word(scalar_kind_t scalar_kind) noexcept {
229
240
  switch (scalar_kind) {
230
241
  case scalar_kind_t::uuid_k: return 128;
231
242
  case scalar_kind_t::u40_k: return 40;
243
+ case scalar_kind_t::bf16_k: return 16;
232
244
  case scalar_kind_t::b1x8_k: return 8;
233
245
  case scalar_kind_t::u64_k: return 64;
234
246
  case scalar_kind_t::i64_k: return 64;
@@ -250,6 +262,7 @@ inline char const* scalar_kind_name(scalar_kind_t scalar_kind) noexcept {
250
262
  switch (scalar_kind) {
251
263
  case scalar_kind_t::uuid_k: return "uuid";
252
264
  case scalar_kind_t::u40_k: return "u40";
265
+ case scalar_kind_t::bf16_k: return "bf16";
253
266
  case scalar_kind_t::b1x8_k: return "b1x8";
254
267
  case scalar_kind_t::u64_k: return "u64";
255
268
  case scalar_kind_t::i64_k: return "i64";
@@ -291,10 +304,14 @@ inline expected_gt<scalar_kind_t> scalar_kind_from_name(char const* name, std::s
291
304
  parsed.result = scalar_kind_t::f64_k;
292
305
  else if (str_equals(name, len, "f16"))
293
306
  parsed.result = scalar_kind_t::f16_k;
307
+ else if (str_equals(name, len, "bf16"))
308
+ parsed.result = scalar_kind_t::bf16_k;
294
309
  else if (str_equals(name, len, "i8"))
295
310
  parsed.result = scalar_kind_t::i8_k;
311
+ else if (str_equals(name, len, "b1"))
312
+ parsed.result = scalar_kind_t::b1x8_k;
296
313
  else
297
- parsed.failed("Unknown type, choose: f32, f16, f64, i8");
314
+ parsed.failed("Unknown type, choose: f64, f32, f16, bf16, i8, b1");
298
315
  return parsed;
299
316
  }
300
317
 
@@ -327,11 +344,13 @@ inline expected_gt<metric_kind_t> metric_from_name(char const* name, std::size_t
327
344
  "tanimoto, sorensen");
328
345
  return parsed;
329
346
  }
330
-
331
347
  inline expected_gt<metric_kind_t> metric_from_name(char const* name) {
332
348
  return metric_from_name(name, std::strlen(name));
333
349
  }
334
350
 
351
+ /**
352
+ * @brief Convenience function to upcast a half-precision floating point number to a single-precision one.
353
+ */
335
354
  inline float f16_to_f32(std::uint16_t u16) noexcept {
336
355
  #if !USEARCH_USE_FP16LIB
337
356
  f16_native_t f16;
@@ -342,6 +361,9 @@ inline float f16_to_f32(std::uint16_t u16) noexcept {
342
361
  #endif
343
362
  }
344
363
 
364
+ /**
365
+ * @brief Convenience function to downcast a single-precision floating point number to a half-precision one.
366
+ */
345
367
  inline std::uint16_t f32_to_f16(float f32) noexcept {
346
368
  #if !USEARCH_USE_FP16LIB
347
369
  f16_native_t f16 = f16_native_t(f32);
@@ -353,6 +375,36 @@ inline std::uint16_t f32_to_f16(float f32) noexcept {
353
375
  #endif
354
376
  }
355
377
 
378
+ /**
379
+ * @brief Convenience function to upcast a brain-floating point number to a single-precision one.
380
+ * https://github.com/ashvardanian/SimSIMD/blob/ff51434d90c66f916e94ff05b24530b127aa4cff/include/simsimd/types.h#L395-L410
381
+ */
382
+ inline float bf16_to_f32(std::uint16_t u16) noexcept {
383
+ union float_or_unsigned_int_t {
384
+ float f;
385
+ unsigned int i;
386
+ };
387
+ union float_or_unsigned_int_t result_union;
388
+ result_union.i = u16 << 16; // Zero extends the mantissa
389
+ return result_union.f;
390
+ }
391
+
392
+ /**
393
+ * @brief Convenience function to downcast a single-precision floating point number to a brain-floating point one.
394
+ * https://github.com/ashvardanian/SimSIMD/blob/ff51434d90c66f916e94ff05b24530b127aa4cff/include/simsimd/types.h#L412-L425
395
+ */
396
+ inline std::uint16_t f32_to_bf16(float f32) noexcept {
397
+ union float_or_unsigned_int_t {
398
+ float f;
399
+ unsigned int i;
400
+ };
401
+ union float_or_unsigned_int_t value;
402
+ value.f = f32;
403
+ value.i >>= 16;
404
+ value.i &= 0xFFFF;
405
+ return (unsigned short)value.i;
406
+ }
407
+
356
408
  /**
357
409
  * @brief Numeric type for the IEEE 754 half-precision floating point.
358
410
  * If hardware support isn't available, falls back to a hardware
@@ -412,6 +464,65 @@ class f16_bits_t {
412
464
  }
413
465
  };
414
466
 
467
+ /**
468
+ * @brief Numeric type for brain-floating point half-precision floating point.
469
+ * If hardware support isn't available, falls back to a hardware
470
+ * agnostic in-software implementation.
471
+ */
472
+ class bf16_bits_t {
473
+ std::uint16_t uint16_{};
474
+
475
+ public:
476
+ inline bf16_bits_t() noexcept : uint16_(0) {}
477
+ inline bf16_bits_t(bf16_bits_t&&) = default;
478
+ inline bf16_bits_t& operator=(bf16_bits_t&&) = default;
479
+ inline bf16_bits_t(bf16_bits_t const&) = default;
480
+ inline bf16_bits_t& operator=(bf16_bits_t const&) = default;
481
+
482
+ inline operator float() const noexcept { return bf16_to_f32(uint16_); }
483
+ inline explicit operator bool() const noexcept { return bf16_to_f32(uint16_) > 0.5f; }
484
+
485
+ inline bf16_bits_t(std::int8_t v) noexcept : uint16_(f32_to_bf16(v)) {}
486
+ inline bf16_bits_t(bool v) noexcept : uint16_(f32_to_bf16(v)) {}
487
+ inline bf16_bits_t(float v) noexcept : uint16_(f32_to_bf16(v)) {}
488
+ inline bf16_bits_t(double v) noexcept : uint16_(f32_to_bf16(static_cast<float>(v))) {}
489
+
490
+ inline bool operator<(bf16_bits_t const& other) const noexcept { return float(*this) < float(other); }
491
+
492
+ inline bf16_bits_t operator+(bf16_bits_t other) const noexcept { return {float(*this) + float(other)}; }
493
+ inline bf16_bits_t operator-(bf16_bits_t other) const noexcept { return {float(*this) - float(other)}; }
494
+ inline bf16_bits_t operator*(bf16_bits_t other) const noexcept { return {float(*this) * float(other)}; }
495
+ inline bf16_bits_t operator/(bf16_bits_t other) const noexcept { return {float(*this) / float(other)}; }
496
+ inline float operator+(float other) const noexcept { return float(*this) + other; }
497
+ inline float operator-(float other) const noexcept { return float(*this) - other; }
498
+ inline float operator*(float other) const noexcept { return float(*this) * other; }
499
+ inline float operator/(float other) const noexcept { return float(*this) / other; }
500
+ inline double operator+(double other) const noexcept { return float(*this) + other; }
501
+ inline double operator-(double other) const noexcept { return float(*this) - other; }
502
+ inline double operator*(double other) const noexcept { return float(*this) * other; }
503
+ inline double operator/(double other) const noexcept { return float(*this) / other; }
504
+
505
+ inline bf16_bits_t& operator+=(float v) noexcept {
506
+ uint16_ = f32_to_bf16(v + bf16_to_f32(uint16_));
507
+ return *this;
508
+ }
509
+
510
+ inline bf16_bits_t& operator-=(float v) noexcept {
511
+ uint16_ = f32_to_bf16(v - bf16_to_f32(uint16_));
512
+ return *this;
513
+ }
514
+
515
+ inline bf16_bits_t& operator*=(float v) noexcept {
516
+ uint16_ = f32_to_bf16(v * bf16_to_f32(uint16_));
517
+ return *this;
518
+ }
519
+
520
+ inline bf16_bits_t& operator/=(float v) noexcept {
521
+ uint16_ = f32_to_bf16(v / bf16_to_f32(uint16_));
522
+ return *this;
523
+ }
524
+ };
525
+
415
526
  /**
416
527
  * @brief An STL-based executor or a "thread-pool" for parallel execution.
417
528
  * Isn't efficient for small batches, as it recreates the threads on every call.
@@ -1009,18 +1120,22 @@ template <typename to_scalar_at> struct cast_from_i8_gt {
1009
1120
  };
1010
1121
 
1011
1122
  template <> struct cast_gt<i8_t, f16_t> : public cast_from_i8_gt<f16_t> {};
1123
+ template <> struct cast_gt<i8_t, bf16_t> : public cast_from_i8_gt<bf16_t> {};
1012
1124
  template <> struct cast_gt<i8_t, f32_t> : public cast_from_i8_gt<f32_t> {};
1013
1125
  template <> struct cast_gt<i8_t, f64_t> : public cast_from_i8_gt<f64_t> {};
1014
1126
 
1015
1127
  template <> struct cast_gt<f16_t, i8_t> : public cast_to_i8_gt<f16_t> {};
1128
+ template <> struct cast_gt<bf16_t, i8_t> : public cast_to_i8_gt<bf16_t> {};
1016
1129
  template <> struct cast_gt<f32_t, i8_t> : public cast_to_i8_gt<f32_t> {};
1017
1130
  template <> struct cast_gt<f64_t, i8_t> : public cast_to_i8_gt<f64_t> {};
1018
1131
 
1019
1132
  template <> struct cast_gt<b1x8_t, f16_t> : public cast_from_b1x8_gt<f16_t> {};
1133
+ template <> struct cast_gt<b1x8_t, bf16_t> : public cast_from_b1x8_gt<bf16_t> {};
1020
1134
  template <> struct cast_gt<b1x8_t, f32_t> : public cast_from_b1x8_gt<f32_t> {};
1021
1135
  template <> struct cast_gt<b1x8_t, f64_t> : public cast_from_b1x8_gt<f64_t> {};
1022
1136
 
1023
1137
  template <> struct cast_gt<f16_t, b1x8_t> : public cast_to_b1x8_gt<f16_t> {};
1138
+ template <> struct cast_gt<bf16_t, b1x8_t> : public cast_to_b1x8_gt<bf16_t> {};
1024
1139
  template <> struct cast_gt<f32_t, b1x8_t> : public cast_to_b1x8_gt<f32_t> {};
1025
1140
  template <> struct cast_gt<f64_t, b1x8_t> : public cast_to_b1x8_gt<f64_t> {};
1026
1141
 
@@ -1602,6 +1717,7 @@ class metric_punned_t {
1602
1717
  case scalar_kind_t::f32_k: datatype = simsimd_datatype_f32_k; break;
1603
1718
  case scalar_kind_t::f64_k: datatype = simsimd_datatype_f64_k; break;
1604
1719
  case scalar_kind_t::f16_k: datatype = simsimd_datatype_f16_k; break;
1720
+ case scalar_kind_t::bf16_k: datatype = simsimd_datatype_bf16_k; break;
1605
1721
  case scalar_kind_t::i8_k: datatype = simsimd_datatype_i8_k; break;
1606
1722
  case scalar_kind_t::b1x8_k: datatype = simsimd_datatype_b8_k; break;
1607
1723
  default: break;
@@ -1654,9 +1770,10 @@ class metric_punned_t {
1654
1770
  switch (metric_kind_) {
1655
1771
  case metric_kind_t::ip_k: {
1656
1772
  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;
1773
+ case scalar_kind_t::bf16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<bf16_t, f32_t>>; break;
1659
1774
  case scalar_kind_t::i8_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<i8_t, f32_t>>; break;
1775
+ case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<f16_t, f32_t>>; break;
1776
+ case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<f32_t>>; break;
1660
1777
  case scalar_kind_t::f64_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_ip_gt<f64_t>>; break;
1661
1778
  default: metric_ptr_ = 0; break;
1662
1779
  }
@@ -1664,9 +1781,10 @@ class metric_punned_t {
1664
1781
  }
1665
1782
  case metric_kind_t::cos_k: {
1666
1783
  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;
1784
+ case scalar_kind_t::bf16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<bf16_t, f32_t>>; break;
1669
1785
  case scalar_kind_t::i8_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<i8_t, f32_t>>; break;
1786
+ case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<f16_t, f32_t>>; break;
1787
+ case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<f32_t>>; break;
1670
1788
  case scalar_kind_t::f64_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_cos_gt<f64_t>>; break;
1671
1789
  default: metric_ptr_ = 0; break;
1672
1790
  }
@@ -1674,9 +1792,10 @@ class metric_punned_t {
1674
1792
  }
1675
1793
  case metric_kind_t::l2sq_k: {
1676
1794
  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;
1795
+ case scalar_kind_t::bf16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<bf16_t, f32_t>>; break;
1679
1796
  case scalar_kind_t::i8_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<i8_t, f32_t>>; break;
1797
+ case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<f16_t, f32_t>>; break;
1798
+ case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<f32_t>>; break;
1680
1799
  case scalar_kind_t::f64_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_l2sq_gt<f64_t>>; break;
1681
1800
  default: metric_ptr_ = 0; break;
1682
1801
  }
@@ -1684,6 +1803,9 @@ class metric_punned_t {
1684
1803
  }
1685
1804
  case metric_kind_t::pearson_k: {
1686
1805
  switch (scalar_kind_) {
1806
+ case scalar_kind_t::bf16_k:
1807
+ metric_ptr_ = (uptr_t)&equidimensional_<metric_pearson_gt<bf16_t, f32_t>>;
1808
+ break;
1687
1809
  case scalar_kind_t::i8_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_pearson_gt<i8_t, f32_t>>; break;
1688
1810
  case scalar_kind_t::f16_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_pearson_gt<f16_t, f32_t>>; break;
1689
1811
  case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_pearson_gt<f32_t>>; break;
@@ -1694,7 +1816,8 @@ class metric_punned_t {
1694
1816
  }
1695
1817
  case metric_kind_t::haversine_k: {
1696
1818
  switch (scalar_kind_) {
1697
- case scalar_kind_t::f16_k: metric_ptr_ = 0; break; //< Having half-precision 2D coordinates is a bit silly.
1819
+ case scalar_kind_t::bf16_k: metric_ptr_ = 0; break; //< Half-precision 2D vectors are silly.
1820
+ case scalar_kind_t::f16_k: metric_ptr_ = 0; break; //< Half-precision 2D vectors are silly.
1698
1821
  case scalar_kind_t::f32_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_haversine_gt<f32_t>>; break;
1699
1822
  case scalar_kind_t::f64_k: metric_ptr_ = (uptr_t)&equidimensional_<metric_haversine_gt<f64_t>>; break;
1700
1823
  default: metric_ptr_ = 0; break;
@@ -1703,6 +1826,9 @@ class metric_punned_t {
1703
1826
  }
1704
1827
  case metric_kind_t::divergence_k: {
1705
1828
  switch (scalar_kind_) {
1829
+ case scalar_kind_t::bf16_k:
1830
+ metric_ptr_ = (uptr_t)&equidimensional_<metric_divergence_gt<bf16_t, f32_t>>;
1831
+ break;
1706
1832
  case scalar_kind_t::f16_k:
1707
1833
  metric_ptr_ = (uptr_t)&equidimensional_<metric_divergence_gt<f16_t, f32_t>>;
1708
1834
  break;
@@ -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.14.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