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/.github/ISSUE_TEMPLATE/bug_report.yml +90 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +64 -0
- package/.github/workflows/release.yml +45 -20
- package/.github/workflows/update_version.sh +6 -0
- package/.releaserc +77 -0
- package/.vscode/settings.json +14 -2
- package/CODE_OF_CONDUCT.md +128 -0
- package/CONTRIBUTING.md +12 -0
- package/Cargo.toml +1 -1
- package/README.md +246 -220
- package/VERSION +1 -1
- package/binding.gyp +13 -9
- package/build.rs +1 -0
- package/conanfile.py +1 -1
- package/docs/Compilation.md +112 -0
- package/docs/benchmarks.md +182 -0
- package/docs/benchmarks.rst +5 -0
- package/docs/compilation.rst +5 -0
- package/docs/conf.dox +2668 -0
- package/docs/index.rst +7 -0
- package/include/usearch/usearch.hpp +454 -238
- package/javascript/lib.cpp +82 -8
- package/objc/USearchObjective.mm +2 -2
- package/objc/include/USearchObjective.h +10 -9
- package/package.json +7 -1
- package/python/bench.py +37 -7
- package/python/lib.cpp +70 -43
- package/python/test.py +78 -36
- package/rust/lib.cpp +6 -2
- package/simsimd/include/simsimd/simsimd.h +31 -41
- package/src/advanced.hpp +68 -40
- package/src/bench.cpp +24 -23
- package/swift/Index+Sugar.swift +1 -0
- package/swift/Test.swift +1 -0
- package/.github/workflows/package.json +0 -82
- package/CMakeListsInstructions.md +0 -185
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
|
-
|
|
7
|
-
|
|
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
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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 =
|
|
38
|
-
index.add(
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
44
|
-
print('found', results[0].shape, results[1].shape, results[2].shape)
|
|
89
|
+
# assert list(results) == [10, 11]
|
|
45
90
|
|
|
46
91
|
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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,
|
|
450
|
-
void add(label_t label, f32_t const* vector) { return add(label, vector,
|
|
451
|
-
void add(label_t label, f64_t const* vector) { return add(label, vector,
|
|
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,
|
|
454
|
-
void add(label_t label, f32_t const* vector,
|
|
455
|
-
void add(label_t label, f64_t const* vector,
|
|
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,
|
|
462
|
-
std::size_t search(f32_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances,
|
|
463
|
-
std::size_t search(f64_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances,
|
|
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,
|
|
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_,
|
|
559
|
+
vector_data = casted_data, vector_bytes = casted_vector_bytes_, config.store_vector = true;
|
|
533
560
|
|
|
534
|
-
index_->add(label, {vector_data, vector_bytes},
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 %
|
|
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 -
|
|
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 (
|
|
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 -
|
|
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>(
|
|
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
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
case accuracy_t::
|
|
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>(
|
|
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>(
|
|
710
|
-
case accuracy_t::f16_k: return
|
|
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
|
-
|
|
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(
|
|
288
|
-
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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 =
|
|
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:
|
|
476
|
-
run_typed<index_gt<
|
|
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:
|
|
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:
|
|
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
|
package/swift/Index+Sugar.swift
CHANGED
package/swift/Test.swift
CHANGED