usearch 0.2.3 → 0.4.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/python/test.py CHANGED
@@ -1,51 +1,93 @@
1
+ import pytest
1
2
  import numpy as np
2
3
 
4
+
3
5
  from usearch.index import Index, SetsIndex
4
- from numba import cfunc, types, carray
5
6
 
6
- # Showcases how to use Numba to JIT-compile similarity measures for USearch.
7
- # https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#c-callbacks
7
+ dimensions = [13, 99, 100, 256]
8
+ sizes = [1, 2, 100]
9
+ types = ['f32', 'f16', 'f8', 'f64']
10
+
11
+
12
+ @pytest.mark.parametrize('ndim', dimensions)
13
+ @pytest.mark.parametrize('dtype', types)
14
+ def test_cos(ndim: int, dtype: str):
15
+ index = Index(ndim=ndim, metric='cos', dtype=dtype)
16
+ vector = np.random.uniform(0, 0.7, (index.ndim)).astype(np.float32)
17
+ index.add(42, vector)
18
+ matches, distances, count = index.search(vector, 10)
19
+
20
+ assert len(matches) == count
21
+ assert len(distances) == count
22
+ assert count == 1
23
+ assert matches[0] == 42
24
+ assert distances[0] == pytest.approx(0, abs=1e-3)
25
+
26
+
27
+ @pytest.mark.parametrize('ndim', dimensions)
28
+ @pytest.mark.parametrize('size', sizes)
29
+ @pytest.mark.parametrize('dtype', types)
30
+ def test_cos_batch(ndim: int, size: int, dtype: str):
31
+ index = Index(ndim=ndim, metric='cos', dtype=dtype)
32
+
33
+ vectors = np.random.uniform(
34
+ 0, 0.3, (size, index.ndim)).astype(np.float32)
35
+ labels = np.array(range(size), dtype=np.longlong)
36
+
37
+ index.add(labels, vectors)
38
+ matches, distances, count = index.search(vectors, 10)
39
+
40
+ assert matches.shape[0] == distances.shape[0]
41
+ assert count.shape[0] == size
42
+
43
+
44
+ @pytest.mark.parametrize('ndim', dimensions)
45
+ @pytest.mark.parametrize('size', sizes)
46
+ def test_user_defined_function(ndim: int, size: int):
47
+
48
+ try:
49
+ from numba import cfunc, types, carray
50
+ except ImportError:
51
+ return
52
+
53
+ # Showcases how to use Numba to JIT-compile similarity measures for USearch.
54
+ # https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#c-callbacks
55
+ signature = types.float32(
56
+ types.CPointer(types.float32),
57
+ types.CPointer(types.float32),
58
+ types.size_t, types.size_t)
8
59
 
9
- signature = types.float32(
10
- types.CPointer(types.float32),
11
- types.CPointer(types.float32),
12
- types.size_t, types.size_t)
60
+ @cfunc(signature)
61
+ def python_dot(a, b, n, m):
62
+ a_array = carray(a, n)
63
+ b_array = carray(b, n)
64
+ c = 0.0
65
+ for i in range(n):
66
+ c += a_array[i] * b_array[i]
67
+ return c
13
68
 
69
+ index = Index(ndim=ndim, metric_pointer=python_dot.address)
14
70
 
15
- @cfunc(signature)
16
- def python_dot(a, b, n, m):
17
- a_array = carray(a, n)
18
- b_array = carray(b, n)
19
- c = 0.0
20
- for i in range(n):
21
- c += a_array[i] * b_array[i]
22
- return c
71
+ vectors = np.random.uniform(
72
+ 0, 0.3, (size, index.ndim)).astype(np.float32)
73
+ labels = np.array(range(size), dtype=np.longlong)
23
74
 
75
+ index.add(labels, vectors)
76
+ matches, distances, count = index.search(vectors, 10)
24
77
 
25
- count_vectors = 100
26
- count_dimensions = 96
27
- vectors = np.random.uniform(
28
- 0, 0.3, (count_vectors, count_dimensions)).astype(np.float32)
29
- labels = np.array(range(count_vectors), dtype=np.longlong)
78
+ assert matches.shape[0] == distances.shape[0]
79
+ assert count.shape[0] == size
30
80
 
31
- index_udf = Index(ndim=96, metric_pointer=python_dot.address)
32
- index_udf.add(labels, vectors, copy=True)
33
- results = index_udf.search(vectors, 10)
34
- print('found', results[0].shape, results[1].shape, results[2].shape)
35
81
 
82
+ def test_sets():
36
83
 
37
- index = Index(ndim=96)
38
- index.add(labels, vectors, copy=True)
39
- assert index.ndim == count_dimensions
40
- assert index.capacity >= count_vectors
41
- assert len(index) == count_vectors
84
+ index = SetsIndex()
85
+ index.add(10, np.array([10, 12, 15], dtype=np.uint32))
86
+ index.add(11, np.array([11, 12, 15, 16], dtype=np.uint32))
87
+ results = index.search(np.array([12, 15], dtype=np.uint32), 20)
42
88
 
43
- results = index.search(vectors, 10)
44
- print('found', results[0].shape, results[1].shape, results[2].shape)
89
+ # assert list(results) == [10, 11]
45
90
 
46
91
 
47
- index_sets = SetsIndex()
48
- index_sets.add(10, np.array([10, 12, 15], dtype=np.uint32))
49
- index_sets.add(11, np.array([11, 12, 15, 16], dtype=np.uint32))
50
- results = index_sets.search(np.array([12, 15], dtype=np.uint32), 20)
51
- print(results)
92
+ if __name__ == '__main__':
93
+ pass
package/rust/lib.cpp CHANGED
@@ -9,7 +9,9 @@ using native_index_t = typename Index::native_index_t;
9
9
  Index::Index(std::shared_ptr<native_index_t> index) : index_(index) {}
10
10
 
11
11
  void Index::add_in_thread(uint32_t label, rust::Slice<float const> vector, size_t thread) const {
12
- index_->add(label, vector.data(), thread, true);
12
+ add_config_t config;
13
+ config.thread = thread;
14
+ index_->add(label, vector.data(), config);
13
15
  }
14
16
 
15
17
  Matches Index::search_in_thread(rust::Slice<float const> vector, size_t count, size_t thread) const {
@@ -18,7 +20,9 @@ Matches Index::search_in_thread(rust::Slice<float const> vector, size_t count, s
18
20
  results.distances.reserve(count);
19
21
  for (size_t i = 0; i != count; ++i)
20
22
  results.labels.push_back(0), results.distances.push_back(0);
21
- results.count = index_->search(vector.data(), count, results.labels.data(), results.distances.data(), thread);
23
+ search_config_t config;
24
+ config.thread = thread;
25
+ results.count = index_->search(vector.data(), count, results.labels.data(), results.distances.data(), config);
22
26
  results.labels.truncate(results.count);
23
27
  results.distances.truncate(results.count);
24
28
  return results;
@@ -56,30 +56,6 @@ inline static simsimd_f32_t simsimd_dot_f32sve(simsimd_f32_t const* a, simsimd_f
56
56
  #endif
57
57
  }
58
58
 
59
- inline static simsimd_f16_t simsimd_dot_f16sve(simsimd_f16_t const* a_enum, simsimd_f16_t const* b_enum, size_t d) {
60
- #if defined(__ARM_FEATURE_SVE)
61
- size_t i = 0;
62
- svfloat16_t ab_vec = svdupq_n_f16(0, 0, 0, 0, 0, 0, 0, 0);
63
- svbool_t pg_vec = svwhilelt_b16(i, d);
64
- simsimd_f16_t const* a = (simsimd_f16_t const*)(a_enum);
65
- simsimd_f16_t const* b = (simsimd_f16_t const*)(b_enum);
66
- do {
67
- svfloat16_t a_vec = svld1_f16(pg_vec, (float16_t const*)a + i);
68
- svfloat16_t b_vec = svld1_f16(pg_vec, (float16_t const*)b + i);
69
- ab_vec = svmla_f16_x(pg_vec, ab_vec, a_vec, b_vec);
70
- i += svcnth();
71
- pg_vec = svwhilelt_b16(i, d);
72
- } while (svptest_any(svptrue_b16(), pg_vec));
73
- float16_t f16 = svaddv_f16(svptrue_b16(), ab_vec);
74
- simsimd_f16_t i16;
75
- memcpy(&i16, &f16, sizeof(i16));
76
- return i16;
77
- #else
78
- (void)a_enum, (void)b_enum, (void)d;
79
- return 0;
80
- #endif
81
- }
82
-
83
59
  inline static simsimd_f32_t simsimd_cos_f32sve(simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) {
84
60
  #if defined(__ARM_FEATURE_SVE)
85
61
  size_t i = 0;
@@ -176,7 +152,7 @@ inline static simsimd_f32_t simsimd_dot_f32x4neon(simsimd_f32_t const* a, simsim
176
152
  #if defined(__ARM_NEON)
177
153
  float32x4_t ab_vec = vdupq_n_f32(0);
178
154
  for (size_t i = 0; i != d; i += 4)
179
- ab_vec = vmlaq_f32(ab_vec, vld1q_f32(a + i), vld1q_f32(b + i));
155
+ ab_vec = vfmaq_f32(ab_vec, vld1q_f32(a + i), vld1q_f32(b + i));
180
156
  return vaddvq_f32(ab_vec);
181
157
  #else
182
158
  (void)a, (void)b, (void)d;
@@ -184,16 +160,23 @@ inline static simsimd_f32_t simsimd_dot_f32x4neon(simsimd_f32_t const* a, simsim
184
160
  #endif
185
161
  }
186
162
 
187
- inline static simsimd_f16_t simsimd_dot_f16x8neon(simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) {
163
+ inline static simsimd_f32_t simsimd_cos_f16x4neon(simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) {
188
164
  #if defined(__ARM_NEON)
189
- // There is no half-precision FMA, only bfloat16, so we must manually multiply and add.
190
- float16x8_t ab_vec = vdupq_n_f16(0);
191
- for (size_t i = 0; i != d; i += 8)
192
- ab_vec = vaddq_f16(ab_vec, vmulq_f16(vld1q_f16((float16_t const*)a + i), vld1q_f16((float16_t const*)b + i)));
193
- float16_t f16 = vaddvq_f32(vcvt_f32_f16(vget_high_f16(ab_vec))) + vaddvq_f32(vcvt_f32_f16(vget_low_f16(ab_vec)));
194
- simsimd_f16_t i16;
195
- memcpy(&i16, &f16, sizeof(i16));
196
- return i16;
165
+ float32x4_t ab_vec = vdupq_n_f32(0);
166
+ float32x4_t a2_vec = vdupq_n_f32(0);
167
+ float32x4_t b2_vec = vdupq_n_f32(0);
168
+ for (size_t i = 0; i != d; i += 4) {
169
+ float32x4_t a_vec = vcvt_f32_f16(vld1_f16((float16_t const*)a + i));
170
+ float32x4_t b_vec = vcvt_f32_f16(vld1_f16((float16_t const*)b + i));
171
+ // vfmaq_f32(a, b, c) == vaddq_f32(vmulq_f32(b, c), a)
172
+ ab_vec = vfmaq_f32(ab_vec, a_vec, b_vec);
173
+ a2_vec = vfmaq_f32(a2_vec, a_vec, a_vec);
174
+ b2_vec = vfmaq_f32(b2_vec, b_vec, b_vec);
175
+ }
176
+ simsimd_f32_t ab = vaddvq_f32(ab_vec);
177
+ simsimd_f32_t a2 = vaddvq_f32(a2_vec);
178
+ simsimd_f32_t b2 = vaddvq_f32(b2_vec);
179
+ return ab / (sqrt(a2) * sqrt(b2));
197
180
  #else
198
181
  (void)a, (void)b, (void)d;
199
182
  return 0;
@@ -255,15 +238,22 @@ inline static simsimd_f32_t simsimd_dot_f32x4avx2(simsimd_f32_t const* a, simsim
255
238
  #endif
256
239
  }
257
240
 
258
- inline static simsimd_f32_t simsimd_dot_f16x16avx512(simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) {
241
+ inline static simsimd_f32_t simsimd_cos_f16x16avx512(simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) {
259
242
  #if defined(__AVX512F__)
260
243
  __m512 ab_vec = _mm512_set1_ps(0);
261
- for (size_t i = 0; i != d; i += 16)
262
- ab_vec = _mm512_fmadd_ps( //
263
- _mm512_cvtxph_ps(_mm256_loadu_ph(a + i)), //
264
- _mm512_cvtxph_ps(_mm256_loadu_ph(b + i)), //
265
- ab_vec);
266
- return _mm512_reduce_add_ps(ab_vec);
244
+ __m512 a2_vec = _mm512_set1_ps(0);
245
+ __m512 b2_vec = _mm512_set1_ps(0);
246
+ for (size_t i = 0; i != d; i += 16) {
247
+ __m512 a_vec = _mm512_cvtxph_ps(_mm256_loadu_ph(a + i));
248
+ __m512 b_vec = _mm512_cvtxph_ps(_mm256_loadu_ph(b + i));
249
+ ab_vec = _mm512_fmadd_ps(a_vec, b_vec, ab_vec);
250
+ a2_vec = _mm512_fmadd_ps(a_vec, a_vec, a2_vec);
251
+ b2_vec = _mm512_fmadd_ps(b_vec, b_vec, b2_vec);
252
+ }
253
+ simsimd_f32_t ab = _mm512_reduce_add_ps(ab_vec);
254
+ simsimd_f32_t a2 = _mm512_reduce_add_ps(a2_vec);
255
+ simsimd_f32_t b2 = _mm512_reduce_add_ps(b2_vec);
256
+ return ab / (sqrt(a2) * sqrt(b2));
267
257
  #else
268
258
  (void)a, (void)b, (void)d;
269
259
  return 0;
package/src/advanced.hpp CHANGED
@@ -174,6 +174,29 @@ class f8_bits_t {
174
174
 
175
175
  inline f16_bits_t::f16_bits_t(f8_bits_t v) noexcept : f16_bits_t(float(v)) {}
176
176
 
177
+ struct cos_f8_t {
178
+ punned_distance_t operator()(f8_bits_t const* a, f8_bits_t const* b, std::size_t n) const noexcept {
179
+ std::int32_t ab{}, a2{}, b2{};
180
+ for (std::size_t i = 0; i != n; i++) {
181
+ std::int16_t ai{a[i]};
182
+ std::int16_t bi{b[i]};
183
+ ab += ai * bi;
184
+ a2 += square(ai);
185
+ b2 += square(bi);
186
+ }
187
+ return 1.f - ab / (std::sqrt(a2) * std::sqrt(b2));
188
+ }
189
+ };
190
+
191
+ struct l2sq_f8_t {
192
+ punned_distance_t operator()(f8_bits_t const* a, f8_bits_t const* b, std::size_t n) const noexcept {
193
+ std::int32_t ab_deltas_sq{};
194
+ for (std::size_t i = 0; i != n; i++)
195
+ ab_deltas_sq += square(std::int16_t(a[i]) - std::int16_t(b[i]));
196
+ return ab_deltas_sq;
197
+ }
198
+ };
199
+
177
200
  struct uuid_t {
178
201
  std::uint8_t octets[16];
179
202
  };
@@ -446,21 +469,21 @@ class auto_index_gt {
446
469
  void reserve(std::size_t capacity) { index_->reserve(capacity); }
447
470
 
448
471
  // clang-format off
449
- void add(label_t label, f16_bits_t const* vector) { return add(label, vector, true, casts_.from_f16); }
450
- void add(label_t label, f32_t const* vector) { return add(label, vector, true, casts_.from_f32); }
451
- void add(label_t label, f64_t const* vector) { return add(label, vector, true, casts_.from_f64); }
472
+ void add(label_t label, f16_bits_t const* vector) { return add(label, vector, casts_.from_f16); }
473
+ void add(label_t label, f32_t const* vector) { return add(label, vector, casts_.from_f32); }
474
+ void add(label_t label, f64_t const* vector) { return add(label, vector, casts_.from_f64); }
452
475
 
453
- 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); }
454
- void add(label_t label, f32_t const* vector, std::size_t thread, bool copy) { return add(label, vector, thread, copy, casts_.from_f32); }
455
- void add(label_t label, f64_t const* vector, std::size_t thread, bool copy) { return add(label, vector, thread, copy, casts_.from_f64); }
476
+ void add(label_t label, f16_bits_t const* vector, add_config_t config) { return add(label, vector, config, casts_.from_f16); }
477
+ void add(label_t label, f32_t const* vector, add_config_t config) { return add(label, vector, config, casts_.from_f32); }
478
+ void add(label_t label, f64_t const* vector, add_config_t config) { return add(label, vector, config, casts_.from_f64); }
456
479
 
457
480
  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); }
458
481
  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); }
459
482
  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); }
460
483
 
461
- 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); }
462
- 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); }
463
- 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); }
484
+ std::size_t search(f16_bits_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, search_config_t config) const { return search(vector, wanted, matches, distances, config, casts_.from_f16); }
485
+ std::size_t search(f32_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, search_config_t config) const { return search(vector, wanted, matches, distances, config, casts_.from_f32); }
486
+ std::size_t search(f64_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, search_config_t config) const { return search(vector, wanted, matches, distances, config, casts_.from_f64); }
464
487
 
465
488
  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); }
466
489
  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); }
@@ -495,7 +518,11 @@ class auto_index_gt {
495
518
 
496
519
  private:
497
520
  static index_t* aligned_index_alloc() noexcept {
521
+ #if defined(WINDOWS)
522
+ return (index_t*)_aligned_malloc(64, 64 * divide_round_up<64>(sizeof(index_t)));
523
+ #else
498
524
  return (index_t*)aligned_alloc(64, 64 * divide_round_up<64>(sizeof(index_t)));
525
+ #endif
499
526
  }
500
527
 
501
528
  static void aligned_index_free(index_t* raw) noexcept { free(raw); }
@@ -522,38 +549,40 @@ class auto_index_gt {
522
549
  }
523
550
 
524
551
  template <typename scalar_at>
525
- void add(label_t label, scalar_at const* vector, std::size_t thread, bool copy, cast_t const& cast) {
552
+ void add(label_t label, scalar_at const* vector, add_config_t config, cast_t const& cast) {
526
553
  byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
527
554
  std::size_t vector_bytes = dimensions_ * sizeof(scalar_at);
528
555
 
529
- byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * thread;
556
+ byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * config.thread;
530
557
  bool casted = cast(vector_data, casted_vector_bytes_, casted_data);
531
558
  if (casted)
532
- vector_data = casted_data, vector_bytes = casted_vector_bytes_, copy = true;
559
+ vector_data = casted_data, vector_bytes = casted_vector_bytes_, config.store_vector = true;
533
560
 
534
- index_->add(label, {vector_data, vector_bytes}, thread, copy);
561
+ index_->add(label, {vector_data, vector_bytes}, config);
535
562
  }
536
563
 
537
564
  template <typename scalar_at>
538
565
  std::size_t search( //
539
566
  scalar_at const* vector, std::size_t wanted, //
540
567
  label_t* matches, distance_t* distances, //
541
- std::size_t thread, cast_t const& cast) const {
568
+ search_config_t config, cast_t const& cast) const {
542
569
 
543
570
  byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
544
571
  std::size_t vector_bytes = dimensions_ * sizeof(scalar_at);
545
572
 
546
- byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * thread;
573
+ byte_t* casted_data = cast_buffer_.data() + casted_vector_bytes_ * config.thread;
547
574
  bool casted = cast(vector_data, casted_vector_bytes_, casted_data);
548
575
  if (casted)
549
576
  vector_data = casted_data, vector_bytes = casted_vector_bytes_;
550
577
 
551
- return index_->search({vector_data, vector_bytes}, wanted, matches, distances, thread);
578
+ return index_->search({vector_data, vector_bytes}, wanted, matches, distances, config);
552
579
  }
553
580
 
554
- template <typename scalar_at> void add(label_t label, scalar_at const* vector, bool copy, cast_t const& cast) {
581
+ template <typename scalar_at> void add(label_t label, scalar_at const* vector, cast_t const& cast) {
555
582
  thread_lock_t lock = thread_lock();
556
- return add(label, vector, lock.thread_id, copy, cast);
583
+ add_config_t config;
584
+ config.thread = lock.thread_id;
585
+ return add(label, vector, config, cast);
557
586
  }
558
587
 
559
588
  template <typename scalar_at>
@@ -562,7 +591,9 @@ class auto_index_gt {
562
591
  label_t* matches, distance_t* distances, //
563
592
  cast_t const& cast) const {
564
593
  thread_lock_t lock = thread_lock();
565
- return search(vector, wanted, matches, distances, lock.thread_id, cast);
594
+ search_config_t config;
595
+ config.thread = lock.thread_id;
596
+ return search(vector, wanted, matches, distances, config, cast);
566
597
  }
567
598
 
568
599
  static auto_index_gt make( //
@@ -652,43 +683,40 @@ class auto_index_gt {
652
683
  return {pun_metric<f32_t>(ip_gt<f32_t>{}), isa_t::auto_k};
653
684
  }
654
685
 
655
- static metric_and_meta_t ip_metric_f16(std::size_t dimensions) {
686
+ static metric_and_meta_t cos_metric_f16(std::size_t dimensions) {
656
687
  (void)dimensions;
657
688
  #if defined(USEARCH_USE_SIMD)
658
689
  #if defined(__x86_64__)
659
- if (dimensions % 32 == 0)
690
+ if (dimensions % 16 == 0)
660
691
  return {
661
692
  pun_metric<simsimd_f16_t>([](simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) noexcept {
662
- return 1.f - simsimd_dot_f16x16avx512(a, b, d);
693
+ return 1.f - simsimd_cos_f16x16avx512(a, b, d);
663
694
  }),
664
695
  isa_t::avx512_k,
665
696
  };
666
697
  #elif defined(__aarch64__)
667
- if (supports_arm_sve())
668
- return {
669
- pun_metric<simsimd_f16_t>([](simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) noexcept {
670
- return 1.f - simsimd_dot_f16sve(a, b, d);
671
- }),
672
- isa_t::sve_k,
673
- };
674
- if (dimensions % 8 == 0)
698
+ if (dimensions % 4 == 0)
675
699
  return {
676
700
  pun_metric<simsimd_f16_t>([](simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) noexcept {
677
- return 1.f - simsimd_dot_f16x8neon(a, b, d);
701
+ return 1.f - simsimd_cos_f16x4neon(a, b, d);
678
702
  }),
679
703
  isa_t::neon_k,
680
704
  };
681
705
  #endif
682
706
  #endif
683
- return {pun_metric<f16_bits_t>(ip_gt<f16_bits_t, f32_t>{}), isa_t::auto_k};
707
+ return {pun_metric<f16_bits_t>(cos_gt<f16_bits_t, f32_t>{}), isa_t::auto_k};
684
708
  }
685
709
 
686
710
  static metric_and_meta_t ip_metric(std::size_t dimensions, accuracy_t accuracy) {
687
711
  switch (accuracy) {
688
- // The tow most common numeric types for the most common metric have afew optimized versions:
689
- case accuracy_t::f16_k: return ip_metric_f16(dimensions);
690
- case accuracy_t::f32_k: return ip_metric_f32(dimensions);
691
- case accuracy_t::f8_k: return {pun_metric<f8_bits_t>(ip_gt<f8_bits_t, f32_t>{}), isa_t::auto_k};
712
+ case accuracy_t::f32_k:
713
+ // The two most common numeric types for the most common metric have optimized versions
714
+ return ip_metric_f32(dimensions);
715
+ case accuracy_t::f16_k:
716
+ // Dot-product accumulates error, Cosine-distance normalizes it
717
+ return cos_metric_f16(dimensions);
718
+
719
+ case accuracy_t::f8_k: return {pun_metric<f8_bits_t>(cos_f8_t{}), isa_t::auto_k};
692
720
  case accuracy_t::f64_k: return {pun_metric<f64_t>(ip_gt<f64_t>{}), isa_t::auto_k};
693
721
  default: return {};
694
722
  }
@@ -696,7 +724,7 @@ class auto_index_gt {
696
724
 
697
725
  static metric_and_meta_t l2_metric(std::size_t, accuracy_t accuracy) {
698
726
  switch (accuracy) {
699
- case accuracy_t::f8_k: return {pun_metric<f8_bits_t>(l2sq_gt<f8_bits_t, f32_t>{}), isa_t::auto_k};
727
+ case accuracy_t::f8_k: return {pun_metric<f8_bits_t>(l2sq_f8_t{}), isa_t::auto_k};
700
728
  case accuracy_t::f16_k: return {pun_metric<f16_bits_t>(l2sq_gt<f16_bits_t, f32_t>{}), isa_t::auto_k};
701
729
  case accuracy_t::f32_k: return {pun_metric<f32_t>(l2sq_gt<f32_t>{}), isa_t::auto_k};
702
730
  case accuracy_t::f64_k: return {pun_metric<f64_t>(l2sq_gt<f64_t>{}), isa_t::auto_k};
@@ -704,10 +732,10 @@ class auto_index_gt {
704
732
  }
705
733
  }
706
734
 
707
- static metric_and_meta_t cos_metric(std::size_t, accuracy_t accuracy) {
735
+ static metric_and_meta_t cos_metric(std::size_t dimensions, accuracy_t accuracy) {
708
736
  switch (accuracy) {
709
- case accuracy_t::f8_k: return {pun_metric<f8_bits_t>(cos_gt<f8_bits_t, f32_t>{}), isa_t::auto_k};
710
- case accuracy_t::f16_k: return {pun_metric<f16_bits_t>(cos_gt<f16_bits_t, f32_t>{}), isa_t::auto_k};
737
+ case accuracy_t::f8_k: return {pun_metric<f8_bits_t>(cos_f8_t{}), isa_t::auto_k};
738
+ case accuracy_t::f16_k: return cos_metric_f16(dimensions);
711
739
  case accuracy_t::f32_k: return {pun_metric<f32_t>(cos_gt<f32_t>{}), isa_t::auto_k};
712
740
  case accuracy_t::f64_k: return {pun_metric<f64_t>(cos_gt<f64_t>{}), isa_t::auto_k};
713
741
  default: return {};
package/src/bench.cpp CHANGED
@@ -43,6 +43,7 @@ using namespace unum::usearch;
43
43
  using namespace unum;
44
44
 
45
45
  using vector_id_t = std::uint32_t;
46
+ using vector_view_t = span_gt<float const>;
46
47
 
47
48
  template <typename element_at>
48
49
  std::size_t offset_of(element_at const* begin, element_at const* end, element_at v) noexcept {
@@ -271,12 +272,14 @@ struct running_stats_printer_t {
271
272
  template <typename index_at, typename vector_id_at, typename real_at>
272
273
  void index_many(index_at& native, std::size_t n, vector_id_at const* ids, real_at const* vectors, std::size_t dims) {
273
274
 
274
- using span_t = span_gt<float const>;
275
275
  running_stats_printer_t printer{n, "Indexing"};
276
276
 
277
277
  #pragma omp parallel for schedule(static, 32)
278
278
  for (std::size_t i = 0; i < n; ++i) {
279
- native.add(ids[i], span_t{vectors + dims * i, dims}, omp_get_thread_num(), true);
279
+ add_config_t config;
280
+ config.thread = omp_get_thread_num();
281
+ config.store_vector = true;
282
+ native.add(ids[i], vector_view_t{vectors + dims * i, dims}, config);
280
283
  printer.progress++;
281
284
  if (omp_get_thread_num() == 0)
282
285
  printer.refresh();
@@ -284,18 +287,20 @@ void index_many(index_at& native, std::size_t n, vector_id_at const* ids, real_a
284
287
  }
285
288
 
286
289
  template <typename index_at, typename vector_id_at, typename real_at>
287
- void search_many(index_at& native, std::size_t n, real_at const* vectors, std::size_t dims, std::size_t wanted,
288
- vector_id_at* ids, real_at* distances) {
290
+ void search_many( //
291
+ index_at& native, std::size_t n, real_at const* vectors, std::size_t dims, std::size_t wanted, vector_id_at* ids,
292
+ real_at* distances) {
289
293
 
290
- using span_t = span_gt<float const>;
291
294
  running_stats_printer_t printer{n, "Search"};
292
295
 
293
296
  #pragma omp parallel for schedule(static, 32)
294
297
  for (std::size_t i = 0; i < n; ++i) {
295
- native.search( //
296
- span_t{vectors + dims * i, dims}, wanted, //
297
- ids + wanted * i, distances + wanted * i, //
298
- omp_get_thread_num());
298
+ search_config_t config;
299
+ config.thread = omp_get_thread_num();
300
+ native.search( //
301
+ vector_view_t{vectors + dims * i, dims}, wanted, //
302
+ ids + wanted * i, distances + wanted * i, //
303
+ config);
299
304
  printer.progress++;
300
305
  if (omp_get_thread_num() == 0)
301
306
  printer.refresh();
@@ -400,7 +405,7 @@ struct args_t {
400
405
  bool quantize_f16 = false;
401
406
  bool quantize_f8 = false;
402
407
 
403
- bool metric_ip = true;
408
+ bool metric_ip = false;
404
409
  bool metric_l2 = false;
405
410
  bool metric_cos = false;
406
411
  bool metric_haversine = false;
@@ -408,23 +413,19 @@ struct args_t {
408
413
 
409
414
  template <typename index_at, typename dataset_at> //
410
415
  index_at type_punned_index_for_metric(dataset_at& dataset, args_t const& args, config_t config, accuracy_t accuracy) {
411
- if (args.metric_ip) {
412
- std::printf("-- Metric: Inner Product\n");
413
- return index_at::ip(dataset.dimensions(), accuracy, config);
414
- }
415
416
  if (args.metric_l2) {
416
417
  std::printf("-- Metric: Euclidean\n");
417
418
  return index_at::l2(dataset.dimensions(), accuracy, config);
418
- }
419
- if (args.metric_cos) {
419
+ } else if (args.metric_cos) {
420
420
  std::printf("-- Metric: Angular\n");
421
421
  return index_at::cos(dataset.dimensions(), accuracy, config);
422
- }
423
- if (args.metric_haversine) {
422
+ } else if (args.metric_haversine) {
424
423
  std::printf("-- Metric: Haversine\n");
425
424
  return index_at::haversine(accuracy, config);
425
+ } else {
426
+ std::printf("-- Metric: Inner Product\n");
427
+ return index_at::ip(dataset.dimensions(), accuracy, config);
426
428
  }
427
- throw std::invalid_argument("Unknown metric!");
428
429
  }
429
430
 
430
431
  template <typename index_at, typename dataset_at> //
@@ -472,16 +473,16 @@ template <typename neighbor_id_at, typename dataset_at> //
472
473
  void run_big_or_small(dataset_at& dataset, args_t const& args, config_t config) {
473
474
  if (args.native) {
474
475
  if (args.metric_cos) {
475
- std::printf("-- Metric: Inner Product\n");
476
- run_typed<index_gt<ip_gt<float>, std::size_t, neighbor_id_at>>(dataset, args, config);
476
+ std::printf("-- Metric: Angular\n");
477
+ run_typed<index_gt<cos_gt<float>, std::size_t, neighbor_id_at>>(dataset, args, config);
477
478
  } else if (args.metric_l2) {
478
479
  std::printf("-- Metric: Euclidean\n");
479
480
  run_typed<index_gt<l2sq_gt<float>, std::size_t, neighbor_id_at>>(dataset, args, config);
480
481
  } else if (args.metric_haversine) {
481
- std::printf("-- Metric: Angular\n");
482
+ std::printf("-- Metric: Haversine\n");
482
483
  run_typed<index_gt<haversine_gt<float>, std::size_t, neighbor_id_at>>(dataset, args, config);
483
484
  } else {
484
- std::printf("-- Metric: Haversine\n");
485
+ std::printf("-- Metric: Inner Product\n");
485
486
  run_typed<index_gt<ip_gt<float>, std::size_t, neighbor_id_at>>(dataset, args, config);
486
487
  }
487
488
  } else
@@ -5,6 +5,7 @@
5
5
  // Created by Ashot Vardanian on 5/11/23.
6
6
  //
7
7
 
8
+ @available(iOS 13, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
8
9
  extension Index {
9
10
 
10
11
  public func add(label: UInt32, vector: ArraySlice<Float32>) -> () {
package/swift/Test.swift CHANGED
@@ -8,6 +8,7 @@
8
8
  import Foundation
9
9
  import USearch
10
10
 
11
+ @available(iOS 13, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
11
12
  func testUnit() throws {
12
13
  let index = Index.l2(dimensions: 4, connectivity: 8)
13
14
  let vectorA: [Float32] = [0.3, 0.5, 1.2, 1.4]