usearch 2.13.4 → 2.13.5

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
@@ -254,7 +254,7 @@ assert!(
254
254
  Training a quantization model and dimension-reduction is a common approach to accelerate vector search.
255
255
  Those, however, are only sometimes reliable, can significantly affect the statistical properties of your data, and require regular adjustments if your distribution shifts.
256
256
  Instead, we have focused on high-precision arithmetic over low-precision downcasted vectors.
257
- The same index, and `add` and `search` operations will automatically down-cast or up-cast between `f64_t`, `f32_t`, `f16_t`, `i8_t`, and single-bit representations.
257
+ The same index, and `add` and `search` operations will automatically down-cast or up-cast between `f64_t`, `f32_t`, `f16_t`, `i8_t`, and single-bit `b1x8_t` representations.
258
258
  You can use the following command to check, if hardware acceleration is enabled:
259
259
 
260
260
  ```sh
@@ -264,11 +264,19 @@ $ python -c 'from usearch.index import Index; print(Index(ndim=166, metric="tani
264
264
  > ice
265
265
  ```
266
266
 
267
+ In most cases, it's recommended to use half-precision floating-point numbers on modern hardware.
268
+ When quantization is enabled, the "get"-like functions won't be able to recover the original data, so you may want to replicate the original vectors elsewhere.
269
+ When quantizing to `i8_t` integers, note that it's only valid for cosine-like metrics.
270
+ As part of the quantization process, the vectors are normalized to unit length and later scaled to [-127, 127] range to occupy the full 8-bit range.
271
+ When quantizing to `b1x8_t` single-bit representations, note that it's only valid for binary metrics like Jaccard, Hamming, etc.
272
+ As part of the quantization process, the scalar components greater than zero are set to `true`, and the rest to `false`.
273
+
274
+ ![USearch uint40_t support](https://github.com/unum-cloud/usearch/blob/main/assets/usearch-neighbor-types.png?raw=true)
275
+
267
276
  Using smaller numeric types will save you RAM needed to store the vectors, but you can also compress the neighbors lists forming our proximity graphs.
268
277
  By default, 32-bit `uint32_t` is used to enumerate those, which is not enough if you need to address over 4 Billion entries.
269
278
  For such cases we provide a custom `uint40_t` type, that will still be 37.5% more space-efficient than the commonly used 8-byte integers, and will scale up to 1 Trillion entries.
270
279
 
271
- ![USearch uint40_t support](https://github.com/unum-cloud/usearch/blob/main/assets/usearch-neighbor-types.png?raw=true)
272
280
 
273
281
  ## `Indexes` for Multi-Index Lookups
274
282
 
@@ -541,7 +549,7 @@ doi = {10.5281/zenodo.7949416},
541
549
  author = {Vardanian, Ash},
542
550
  title = {{USearch by Unum Cloud}},
543
551
  url = {https://github.com/unum-cloud/usearch},
544
- version = {2.13.4},
552
+ version = {2.13.5},
545
553
  year = {2023},
546
554
  month = oct,
547
555
  }
package/c/usearch.h CHANGED
@@ -72,6 +72,17 @@ USEARCH_EXPORT typedef struct usearch_init_options_t {
72
72
  usearch_metric_t metric;
73
73
  /**
74
74
  * @brief The scalar kind used for quantization of vector data during indexing.
75
+ * In most cases, on modern hardware, it's recommended to use half-precision floating-point numbers.
76
+ * When quantization is enabled, the "get"-like functions won't be able to recover the original data,
77
+ * so you may want to replicate the original vectors elsewhere.
78
+ *
79
+ * Quantizing to integers is also possible, but it's important to note that it's only valid for cosine-like
80
+ * metrics. As part of the quantization process, the vectors are normalized to unit length and later scaled
81
+ * to @b [-127,127] range to occupy the full 8-bit range.
82
+ *
83
+ * Quantizing to 1-bit booleans is also possible, but it's only valid for binary metrics like Jaccard, Hamming,
84
+ * etc. As part of the quantization process, the scalar components greater than zero are set to `true`, and the
85
+ * rest to `false`.
75
86
  */
76
87
  usearch_scalar_kind_t quantization;
77
88
  /**
@@ -9,7 +9,7 @@
9
9
 
10
10
  #define USEARCH_VERSION_MAJOR 2
11
11
  #define USEARCH_VERSION_MINOR 13
12
- #define USEARCH_VERSION_PATCH 4
12
+ #define USEARCH_VERSION_PATCH 5
13
13
 
14
14
  // Inferring C++ version
15
15
  // https://stackoverflow.com/a/61552074
@@ -95,7 +95,7 @@ struct index_dense_head_result_t {
95
95
  * @brief Configuration settings for the construction of dense
96
96
  * equidimensional vector indexes.
97
97
  *
98
- * Unlike the underlying `index_gt` class, incroporates the
98
+ * Unlike the underlying `index_gt` class, incorporates the
99
99
  * `::expansion_add` and `::expansion_search` parameters passed
100
100
  * separately for the lower-level engine.
101
101
  */
@@ -207,8 +207,11 @@ struct index_dense_metadata_result_t {
207
207
  }
208
208
  };
209
209
 
210
+ /**
211
+ * @brief Fixes serialized scalar-kind codes for pre-v2.10 versions, until we can upgrade to v3.
212
+ * The old enum `scalar_kind_t` is defined without explicit constants from 0.
213
+ */
210
214
  inline scalar_kind_t convert_pre_2_10_scalar_kind(scalar_kind_t scalar_kind) noexcept {
211
- // The old enum `scalar_kind_t` is defined without explicit constants from 0
212
215
  switch (static_cast<std::underlying_type<scalar_kind_t>::type>(scalar_kind)) {
213
216
  case 0: return scalar_kind_t::unknown_k;
214
217
  case 1: return scalar_kind_t::b1x8_k;
@@ -229,13 +232,15 @@ inline scalar_kind_t convert_pre_2_10_scalar_kind(scalar_kind_t scalar_kind) noe
229
232
  }
230
233
  }
231
234
 
235
+ /**
236
+ * @brief Fixes the metadata for pre-v2.10 versions, until we can upgrade to v3.
237
+ * Originates from: https://github.com/unum-cloud/usearch/issues/423
238
+ */
232
239
  inline void fix_pre_2_10_metadata(index_dense_head_t& head) {
233
- // deal with incompatibility with pre-2.10 versions: https://github.com/unum-cloud/usearch/issues/423
234
240
  if (head.version_major == 2 && head.version_minor < 10) {
235
241
  head.kind_scalar = convert_pre_2_10_scalar_kind(head.kind_scalar);
236
242
  head.kind_key = convert_pre_2_10_scalar_kind(head.kind_key);
237
243
  head.kind_compressed_slot = convert_pre_2_10_scalar_kind(head.kind_compressed_slot);
238
- // update minor versions so that no repeated conversion occurs
239
244
  head.version_minor = 10;
240
245
  head.version_patch = 0;
241
246
  }
@@ -316,7 +321,7 @@ inline index_dense_metadata_result_t index_dense_metadata_from_path(char const*
316
321
  /**
317
322
  * @brief Extracts metadata from a pre-constructed index serialized into an in-memory buffer.
318
323
  */
319
- inline index_dense_metadata_result_t index_dense_metadata_from_buffer(memory_mapped_file_t file,
324
+ inline index_dense_metadata_result_t index_dense_metadata_from_buffer(memory_mapped_file_t const& file,
320
325
  std::size_t offset = 0) noexcept {
321
326
  index_dense_metadata_result_t result;
322
327
 
@@ -324,7 +329,7 @@ inline index_dense_metadata_result_t index_dense_metadata_from_buffer(memory_map
324
329
  if (offset + sizeof(index_dense_head_buffer_t) >= file.size())
325
330
  return result.failed("End of file reached!");
326
331
 
327
- byte_t* const file_data = file.data() + offset;
332
+ byte_t const* file_data = file.data() + offset;
328
333
  std::size_t const file_size = file.size() - offset;
329
334
  std::memcpy(&result.head_buffer, file_data, sizeof(index_dense_head_buffer_t));
330
335
 
@@ -70,7 +70,6 @@ struct uuid_t {
70
70
  };
71
71
 
72
72
  class f16_bits_t;
73
- class i8_converted_t;
74
73
 
75
74
  #if !USEARCH_USE_FP16LIB
76
75
  #if USEARCH_USE_SIMSIMD
@@ -99,6 +98,9 @@ using i32_t = std::int32_t;
99
98
  using i16_t = std::int16_t;
100
99
  using i8_t = std::int8_t;
101
100
 
101
+ /**
102
+ * @brief Enumerates the most commonly used distance metrics, mostly for dense vector representations.
103
+ */
102
104
  enum class metric_kind_t : std::uint8_t {
103
105
  unknown_k = 0,
104
106
  // Classics:
@@ -111,13 +113,19 @@ enum class metric_kind_t : std::uint8_t {
111
113
  haversine_k = 'h',
112
114
  divergence_k = 'd',
113
115
 
114
- // Sets:
115
- jaccard_k = 'j',
116
+ // Dense Sets:
116
117
  hamming_k = 'b',
117
118
  tanimoto_k = 't',
118
119
  sorensen_k = 's',
120
+
121
+ // Sparse Sets:
122
+ jaccard_k = 'j',
119
123
  };
120
124
 
125
+ /**
126
+ * @brief Enumerates the most commonly used scalar types, mostly for dense vector representations.
127
+ * Doesn't include logical types, like complex numbers or quaternions.
128
+ */
121
129
  enum class scalar_kind_t : std::uint8_t {
122
130
  unknown_k = 0,
123
131
  // Custom:
@@ -198,33 +206,63 @@ inline bool str_equals(char const* begin, std::size_t len, char const* other_beg
198
206
 
199
207
  inline std::size_t bits_per_scalar(scalar_kind_t scalar_kind) noexcept {
200
208
  switch (scalar_kind) {
209
+ case scalar_kind_t::uuid_k: return 128;
210
+ case scalar_kind_t::u40_k: return 40;
211
+ case scalar_kind_t::b1x8_k: return 1;
212
+ case scalar_kind_t::u64_k: return 64;
213
+ case scalar_kind_t::i64_k: return 64;
201
214
  case scalar_kind_t::f64_k: return 64;
215
+ case scalar_kind_t::u32_k: return 32;
216
+ case scalar_kind_t::i32_k: return 32;
202
217
  case scalar_kind_t::f32_k: return 32;
218
+ case scalar_kind_t::u16_k: return 16;
219
+ case scalar_kind_t::i16_k: return 16;
203
220
  case scalar_kind_t::f16_k: return 16;
221
+ case scalar_kind_t::u8_k: return 8;
204
222
  case scalar_kind_t::i8_k: return 8;
205
- case scalar_kind_t::b1x8_k: return 1;
223
+ case scalar_kind_t::f8_k: return 8;
206
224
  default: return 0;
207
225
  }
208
226
  }
209
227
 
210
228
  inline std::size_t bits_per_scalar_word(scalar_kind_t scalar_kind) noexcept {
211
229
  switch (scalar_kind) {
230
+ case scalar_kind_t::uuid_k: return 128;
231
+ case scalar_kind_t::u40_k: return 40;
232
+ case scalar_kind_t::b1x8_k: return 8;
233
+ case scalar_kind_t::u64_k: return 64;
234
+ case scalar_kind_t::i64_k: return 64;
212
235
  case scalar_kind_t::f64_k: return 64;
236
+ case scalar_kind_t::u32_k: return 32;
237
+ case scalar_kind_t::i32_k: return 32;
213
238
  case scalar_kind_t::f32_k: return 32;
239
+ case scalar_kind_t::u16_k: return 16;
240
+ case scalar_kind_t::i16_k: return 16;
214
241
  case scalar_kind_t::f16_k: return 16;
242
+ case scalar_kind_t::u8_k: return 8;
215
243
  case scalar_kind_t::i8_k: return 8;
216
- case scalar_kind_t::b1x8_k: return 8;
244
+ case scalar_kind_t::f8_k: return 8;
217
245
  default: return 0;
218
246
  }
219
247
  }
220
248
 
221
249
  inline char const* scalar_kind_name(scalar_kind_t scalar_kind) noexcept {
222
250
  switch (scalar_kind) {
251
+ case scalar_kind_t::uuid_k: return "uuid";
252
+ case scalar_kind_t::u40_k: return "u40";
253
+ case scalar_kind_t::b1x8_k: return "b1x8";
254
+ case scalar_kind_t::u64_k: return "u64";
255
+ case scalar_kind_t::i64_k: return "i64";
256
+ case scalar_kind_t::f64_k: return "f64";
257
+ case scalar_kind_t::u32_k: return "u32";
258
+ case scalar_kind_t::i32_k: return "i32";
223
259
  case scalar_kind_t::f32_k: return "f32";
260
+ case scalar_kind_t::u16_k: return "u16";
261
+ case scalar_kind_t::i16_k: return "i16";
224
262
  case scalar_kind_t::f16_k: return "f16";
225
- case scalar_kind_t::f64_k: return "f64";
263
+ case scalar_kind_t::u8_k: return "u8";
226
264
  case scalar_kind_t::i8_k: return "i8";
227
- case scalar_kind_t::b1x8_k: return "b1x8";
265
+ case scalar_kind_t::f8_k: return "f8";
228
266
  default: return "";
229
267
  }
230
268
  }
@@ -242,8 +280,8 @@ inline char const* metric_kind_name(metric_kind_t metric) noexcept {
242
280
  case metric_kind_t::hamming_k: return "hamming";
243
281
  case metric_kind_t::tanimoto_k: return "tanimoto";
244
282
  case metric_kind_t::sorensen_k: return "sorensen";
283
+ default: return "";
245
284
  }
246
- return "";
247
285
  }
248
286
  inline expected_gt<scalar_kind_t> scalar_kind_from_name(char const* name, std::size_t len) {
249
287
  expected_gt<scalar_kind_t> parsed;
@@ -333,8 +371,7 @@ class f16_bits_t {
333
371
  inline operator float() const noexcept { return f16_to_f32(uint16_); }
334
372
  inline explicit operator bool() const noexcept { return f16_to_f32(uint16_) > 0.5f; }
335
373
 
336
- inline f16_bits_t(i8_converted_t) noexcept;
337
- inline f16_bits_t(std::int8_t v) noexcept : uint16_(v) {}
374
+ inline f16_bits_t(std::int8_t v) noexcept : uint16_(f32_to_f16(v)) {}
338
375
  inline f16_bits_t(bool v) noexcept : uint16_(f32_to_f16(v)) {}
339
376
  inline f16_bits_t(float v) noexcept : uint16_(f32_to_f16(v)) {}
340
377
  inline f16_bits_t(double v) noexcept : uint16_(f32_to_f16(static_cast<float>(v))) {}
@@ -345,14 +382,14 @@ class f16_bits_t {
345
382
  inline f16_bits_t operator-(f16_bits_t other) const noexcept { return {float(*this) - float(other)}; }
346
383
  inline f16_bits_t operator*(f16_bits_t other) const noexcept { return {float(*this) * float(other)}; }
347
384
  inline f16_bits_t operator/(f16_bits_t other) const noexcept { return {float(*this) / float(other)}; }
348
- inline f16_bits_t operator+(float other) const noexcept { return {float(*this) + other}; }
349
- inline f16_bits_t operator-(float other) const noexcept { return {float(*this) - other}; }
350
- inline f16_bits_t operator*(float other) const noexcept { return {float(*this) * other}; }
351
- inline f16_bits_t operator/(float other) const noexcept { return {float(*this) / other}; }
352
- inline f16_bits_t operator+(double other) const noexcept { return {float(*this) + other}; }
353
- inline f16_bits_t operator-(double other) const noexcept { return {float(*this) - other}; }
354
- inline f16_bits_t operator*(double other) const noexcept { return {float(*this) * other}; }
355
- inline f16_bits_t operator/(double other) const noexcept { return {float(*this) / other}; }
385
+ inline float operator+(float other) const noexcept { return float(*this) + other; }
386
+ inline float operator-(float other) const noexcept { return float(*this) - other; }
387
+ inline float operator*(float other) const noexcept { return float(*this) * other; }
388
+ inline float operator/(float other) const noexcept { return float(*this) / other; }
389
+ inline double operator+(double other) const noexcept { return float(*this) + other; }
390
+ inline double operator-(double other) const noexcept { return float(*this) - other; }
391
+ inline double operator*(double other) const noexcept { return float(*this) * other; }
392
+ inline double operator/(double other) const noexcept { return float(*this) / other; }
356
393
 
357
394
  inline f16_bits_t& operator+=(float v) noexcept {
358
395
  uint16_ = f32_to_f16(v + f16_to_f32(uint16_));
@@ -907,7 +944,7 @@ template <> struct cast_gt<b1x8_t, b1x8_t> {
907
944
  static bool try_(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
908
945
  };
909
946
 
910
- template <typename from_scalar_at> struct cast_gt<from_scalar_at, b1x8_t> {
947
+ template <typename from_scalar_at> struct cast_to_b1x8_gt {
911
948
  inline static bool try_(byte_t const* input, std::size_t dim, byte_t* output) noexcept {
912
949
  from_scalar_at const* typed_input = reinterpret_cast<from_scalar_at const*>(input);
913
950
  unsigned char* typed_output = reinterpret_cast<unsigned char*>(output);
@@ -928,7 +965,7 @@ template <typename from_scalar_at> struct cast_gt<from_scalar_at, b1x8_t> {
928
965
  }
929
966
  };
930
967
 
931
- template <typename to_scalar_at> struct cast_gt<b1x8_t, to_scalar_at> {
968
+ template <typename to_scalar_at> struct cast_from_b1x8_gt {
932
969
  static bool try_(byte_t const* input, std::size_t dim, byte_t* output) noexcept {
933
970
  unsigned char const* typed_input = reinterpret_cast<unsigned char const*>(input);
934
971
  to_scalar_at* typed_output = reinterpret_cast<to_scalar_at*>(output);
@@ -940,53 +977,55 @@ template <typename to_scalar_at> struct cast_gt<b1x8_t, to_scalar_at> {
940
977
  }
941
978
  };
942
979
 
943
- /**
944
- * @brief Numeric type for uniformly-distributed floating point
945
- * values within [-1,1] range, quantized to integers [-100,100].
946
- */
947
- class i8_converted_t {
948
- std::int8_t int8_{};
980
+ template <typename from_scalar_at> struct cast_to_i8_gt {
981
+ inline static bool try_(byte_t const* input, std::size_t dim, byte_t* output) noexcept {
982
+ from_scalar_at const* typed_input = reinterpret_cast<from_scalar_at const*>(input);
983
+ std::int8_t* typed_output = reinterpret_cast<std::int8_t*>(output);
984
+ // Unlike other casting mechanisms, switching to small range integers is a two step procedure.
985
+ // First we want to estimate the magnitude of the vector to scale into [-1.0, 1.0] interval,
986
+ // instead of clamping. And then we scale the values into the [-127, 127] range.
987
+ // ! This makes an assumption, that the distance metric is dot-product-like, which may not
988
+ // ! be true in many cases, so it's recommended to avoid automatic casting from floats to
989
+ // ! integers.
990
+ double magnitude = 0.0;
991
+ for (std::size_t i = 0; i != dim; ++i)
992
+ magnitude += (double)typed_input[i] * (double)typed_input[i];
993
+ magnitude = std::sqrt(magnitude);
994
+ for (std::size_t i = 0; i != dim; ++i)
995
+ typed_output[i] =
996
+ static_cast<std::int8_t>(usearch::clamp<double>(typed_input[i] * 127.0 / magnitude, -127.0, 127.0));
997
+ return true;
998
+ }
999
+ };
949
1000
 
950
- public:
951
- constexpr static f32_t divisor_k = 100.f;
952
- constexpr static std::int8_t min_k = -100;
953
- constexpr static std::int8_t max_k = 100;
954
-
955
- inline i8_converted_t() noexcept : int8_(0) {}
956
- inline i8_converted_t(bool v) noexcept : int8_(v ? max_k : 0) {}
957
-
958
- inline i8_converted_t(i8_converted_t&&) = default;
959
- inline i8_converted_t& operator=(i8_converted_t&&) = default;
960
- inline i8_converted_t(i8_converted_t const&) = default;
961
- inline i8_converted_t& operator=(i8_converted_t const&) = default;
962
-
963
- inline operator f16_t() const noexcept { return static_cast<f16_t>(f32_t(int8_) / divisor_k); }
964
- inline operator f32_t() const noexcept { return f32_t(int8_) / divisor_k; }
965
- inline operator f64_t() const noexcept { return f64_t(int8_) / divisor_k; }
966
- inline explicit operator bool() const noexcept { return int8_ > (max_k / 2); }
967
- inline explicit operator std::int8_t() const noexcept { return int8_; }
968
- inline explicit operator std::int16_t() const noexcept { return int8_; }
969
- inline explicit operator std::int32_t() const noexcept { return int8_; }
970
- inline explicit operator std::int64_t() const noexcept { return int8_; }
971
-
972
- // Even with `f16_t` arguments we may want to use `f32_t` for clamping and comparions.
973
- inline i8_converted_t(f16_t v)
974
- : int8_(static_cast<std::int8_t>(usearch::clamp<f32_t>(v * divisor_k, min_k, max_k))) {}
975
- inline i8_converted_t(f32_t v)
976
- : int8_(static_cast<std::int8_t>(usearch::clamp<f32_t>(v * divisor_k, min_k, max_k))) {}
977
- inline i8_converted_t(f64_t v)
978
- : int8_(static_cast<std::int8_t>(usearch::clamp<f64_t>(v * divisor_k, min_k, max_k))) {}
1001
+ template <typename to_scalar_at> struct cast_from_i8_gt {
1002
+ static bool try_(byte_t const* input, std::size_t dim, byte_t* output) noexcept {
1003
+ std::int8_t const* typed_input = reinterpret_cast<std::int8_t const*>(input);
1004
+ to_scalar_at* typed_output = reinterpret_cast<to_scalar_at*>(output);
1005
+ for (std::size_t i = 0; i != dim; ++i)
1006
+ typed_output[i] = static_cast<to_scalar_at>(typed_input[i]) / 127.f;
1007
+ return true;
1008
+ }
979
1009
  };
980
1010
 
981
- f16_bits_t::f16_bits_t(i8_converted_t v) noexcept : uint16_(f32_to_f16(v)) {}
1011
+ template <> struct cast_gt<i8_t, f16_t> : public cast_from_i8_gt<f16_t> {};
1012
+ template <> struct cast_gt<i8_t, f32_t> : public cast_from_i8_gt<f32_t> {};
1013
+ template <> struct cast_gt<i8_t, f64_t> : public cast_from_i8_gt<f64_t> {};
1014
+
1015
+ template <> struct cast_gt<f16_t, i8_t> : public cast_to_i8_gt<f16_t> {};
1016
+ template <> struct cast_gt<f32_t, i8_t> : public cast_to_i8_gt<f32_t> {};
1017
+ template <> struct cast_gt<f64_t, i8_t> : public cast_to_i8_gt<f64_t> {};
1018
+
1019
+ template <> struct cast_gt<b1x8_t, f16_t> : public cast_from_b1x8_gt<f16_t> {};
1020
+ template <> struct cast_gt<b1x8_t, f32_t> : public cast_from_b1x8_gt<f32_t> {};
1021
+ template <> struct cast_gt<b1x8_t, f64_t> : public cast_from_b1x8_gt<f64_t> {};
982
1022
 
983
- template <> struct cast_gt<i8_t, f16_t> : public cast_gt<i8_converted_t, f16_t> {};
984
- template <> struct cast_gt<i8_t, f32_t> : public cast_gt<i8_converted_t, f32_t> {};
985
- template <> struct cast_gt<i8_t, f64_t> : public cast_gt<i8_converted_t, f64_t> {};
1023
+ template <> struct cast_gt<f16_t, b1x8_t> : public cast_to_b1x8_gt<f16_t> {};
1024
+ template <> struct cast_gt<f32_t, b1x8_t> : public cast_to_b1x8_gt<f32_t> {};
1025
+ template <> struct cast_gt<f64_t, b1x8_t> : public cast_to_b1x8_gt<f64_t> {};
986
1026
 
987
- template <> struct cast_gt<f16_t, i8_t> : public cast_gt<f16_t, i8_converted_t> {};
988
- template <> struct cast_gt<f32_t, i8_t> : public cast_gt<f32_t, i8_converted_t> {};
989
- template <> struct cast_gt<f64_t, i8_t> : public cast_gt<f64_t, i8_converted_t> {};
1027
+ template <> struct cast_gt<b1x8_t, i8_t> : public cast_from_b1x8_gt<i8_t> {};
1028
+ template <> struct cast_gt<i8_t, b1x8_t> : public cast_to_b1x8_gt<i8_t> {};
990
1029
 
991
1030
  /* Don't complain if the vectorization of the inner loops fails:
992
1031
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "2.13.4",
3
+ "version": "2.13.5",
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