usearch 0.1.8 → 0.1.10
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/.vscode/settings.json +1 -0
- package/CMakeLists.txt +26 -8
- package/CMakeListsInstructions.md +49 -33
- package/Cargo.toml +2 -2
- package/LICENSE +201 -0
- package/README.md +306 -20
- package/VERSION +1 -1
- package/assets/usearch-approaches-transparent.png +0 -0
- package/assets/usearch-approaches-white.png +0 -0
- package/assets/usearch-neighbor-types.png +0 -0
- package/build.gradle +1 -1
- package/include/usearch/usearch.hpp +24 -0
- package/java/test/IndexTest.java +6 -1
- package/package.json +2 -2
- package/python/bench.py +42 -69
- package/python/lib.cpp +17 -11
- package/python/test.py +31 -6
- package/python/usearch/client.py +34 -0
- package/python/usearch/io.py +67 -0
- package/python/usearch/server.py +51 -0
- package/setup.py +1 -1
- package/simsimd/.vscode/settings.json +3 -1
- package/simsimd/include/simsimd/simsimd.h +14 -7
- package/src/advanced.hpp +86 -20
- package/src/bench.cpp +38 -3
- package/wolfram/CMakeLists.txt +17 -0
- package/wolfram/lib.cpp +139 -4
- package/wolfram/test.wls +27 -0
- package/docs/Makefile +0 -20
package/python/test.py
CHANGED
|
@@ -2,19 +2,44 @@ import numpy as np
|
|
|
2
2
|
|
|
3
3
|
import usearch
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
from numba import cfunc, types, carray
|
|
6
|
+
|
|
7
|
+
# Showcases how to use Numba to JIT-compile similarity measures for USearch.
|
|
8
|
+
# https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#c-callbacks
|
|
9
|
+
|
|
10
|
+
signature = types.float32(
|
|
11
|
+
types.CPointer(types.float32),
|
|
12
|
+
types.CPointer(types.float32),
|
|
13
|
+
types.size_t, types.size_t)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@cfunc(signature)
|
|
17
|
+
def python_dot(a, b, n, m):
|
|
18
|
+
a_array = carray(a, n)
|
|
19
|
+
b_array = carray(b, n)
|
|
20
|
+
c = 0.0
|
|
21
|
+
for i in range(n):
|
|
22
|
+
c += a_array[i] * b_array[i]
|
|
23
|
+
return c
|
|
7
24
|
|
|
8
|
-
index = usearch.Index(dim=96)
|
|
9
|
-
print(index)
|
|
10
25
|
|
|
26
|
+
count_vectors = 100
|
|
27
|
+
count_dimensions = 96
|
|
11
28
|
vectors = np.random.uniform(
|
|
12
29
|
0, 0.3, (count_vectors, count_dimensions)).astype(np.float32)
|
|
13
30
|
labels = np.array(range(count_vectors), dtype=np.longlong)
|
|
14
|
-
print('will insert', labels.shape, vectors.shape)
|
|
15
31
|
|
|
32
|
+
index_udf = usearch.Index(ndim=96, metric_pointer=python_dot.address)
|
|
33
|
+
index_udf.add(labels, vectors, copy=True)
|
|
34
|
+
results = index_udf.search(vectors, 10)
|
|
35
|
+
print('found', results[0].shape, results[1].shape, results[2].shape)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
index = usearch.Index(ndim=96)
|
|
16
39
|
index.add(labels, vectors, copy=True)
|
|
40
|
+
assert index.ndim == count_dimensions
|
|
41
|
+
assert index.capacity >= count_vectors
|
|
42
|
+
assert len(index) == count_vectors
|
|
17
43
|
|
|
18
|
-
print('will search')
|
|
19
44
|
results = index.search(vectors, 10)
|
|
20
45
|
print('found', results[0].shape, results[1].shape, results[2].shape)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from ucall.client import Client
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class IndexClient:
|
|
6
|
+
|
|
7
|
+
def __init__(self, uri: str = '127.0.0.1', port: int = 8545, use_http: bool = True) -> None:
|
|
8
|
+
self.client = Client(uri=uri, port=port, use_http=use_http)
|
|
9
|
+
|
|
10
|
+
def add(self, labels: np.array, vectors: np.array):
|
|
11
|
+
if isinstance(labels, int):
|
|
12
|
+
return self.client.add_one(label=labels, vectors=vectors)
|
|
13
|
+
else:
|
|
14
|
+
return self.client.add_many(labels=labels, vectors=vectors)
|
|
15
|
+
|
|
16
|
+
def search(self, vectors: np.array, count: int) -> tuple[np.array, np.array, np.array]:
|
|
17
|
+
matches = []
|
|
18
|
+
distances = []
|
|
19
|
+
counts = []
|
|
20
|
+
# return self.client.search_one(vectors=vectors, count=count)
|
|
21
|
+
return matches, distances, counts
|
|
22
|
+
|
|
23
|
+
def __len__(self):
|
|
24
|
+
return self.client.size()
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def ndim(self):
|
|
28
|
+
return self.client.ndim()
|
|
29
|
+
|
|
30
|
+
def capacity(self):
|
|
31
|
+
return self.client.capacity()
|
|
32
|
+
|
|
33
|
+
def connectivity(self):
|
|
34
|
+
return self.client.connectivity()
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def load_matrix(filename: str, start_row: int = 0, count_rows: int = None, view: bool = False):
|
|
6
|
+
"""
|
|
7
|
+
Read *.ibin, *.hbin, *.fbin, *.dbin files with matrixes.
|
|
8
|
+
Args:
|
|
9
|
+
:param filename (str): path to the matrix file
|
|
10
|
+
:param start_row (int): start reading vectors from this index
|
|
11
|
+
:param count_rows (int): number of vectors to read. If None, read all vectors
|
|
12
|
+
Returns:
|
|
13
|
+
Parsed matrix (numpy.ndarray)
|
|
14
|
+
"""
|
|
15
|
+
dtype = np.float32
|
|
16
|
+
scalar_size = 4
|
|
17
|
+
if filename.endswith('.fbin'):
|
|
18
|
+
dtype = np.float32
|
|
19
|
+
scalar_size = 4
|
|
20
|
+
elif filename.endswith('.dbin'):
|
|
21
|
+
dtype = np.float64
|
|
22
|
+
scalar_size = 8
|
|
23
|
+
elif filename.endswith('.hbin'):
|
|
24
|
+
dtype = np.float16
|
|
25
|
+
scalar_size = 2
|
|
26
|
+
elif filename.endswith('.ibin'):
|
|
27
|
+
dtype = np.int32
|
|
28
|
+
scalar_size = 4
|
|
29
|
+
else:
|
|
30
|
+
raise Exception('Unknown file type')
|
|
31
|
+
|
|
32
|
+
with open(filename, 'rb') as f:
|
|
33
|
+
rows, cols = np.fromfile(f, count=2, dtype=np.int32)
|
|
34
|
+
rows = (rows - start_row) if count_rows is None else count_rows
|
|
35
|
+
row_offset = start_row * scalar_size * cols
|
|
36
|
+
|
|
37
|
+
if view:
|
|
38
|
+
return np.memmap(f, dtype=dtype, mode='r', offset=8+row_offset, shape=(rows, cols))
|
|
39
|
+
else:
|
|
40
|
+
return np.fromfile(f, count=rows * cols, dtype=dtype, offset=row_offset).reshape(rows, cols)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def save_matrix(vecs: np.array, filename: str):
|
|
44
|
+
"""
|
|
45
|
+
Write *.ibin, *.hbin, *.fbin, *.dbin files with matrixes.
|
|
46
|
+
Args:
|
|
47
|
+
:param vecs (numpy.array): the matrix to serialize
|
|
48
|
+
:param filename (str): path to the matrix file
|
|
49
|
+
"""
|
|
50
|
+
dtype = np.float32
|
|
51
|
+
if filename.endswith('.fbin'):
|
|
52
|
+
dtype = np.float32
|
|
53
|
+
elif filename.endswith('.dbin'):
|
|
54
|
+
dtype = np.float64
|
|
55
|
+
elif filename.endswith('.hbin'):
|
|
56
|
+
dtype = np.float16
|
|
57
|
+
elif filename.endswith('.ibin'):
|
|
58
|
+
dtype = np.int32
|
|
59
|
+
else:
|
|
60
|
+
raise Exception('Unknown file type')
|
|
61
|
+
|
|
62
|
+
assert len(vecs.shape) == 2, 'Input array must have 2 dimensions'
|
|
63
|
+
with open(filename, 'wb') as f:
|
|
64
|
+
nvecs, dim = vecs.shape
|
|
65
|
+
f.write(struct.pack('<i', nvecs))
|
|
66
|
+
f.write(struct.pack('<i', dim))
|
|
67
|
+
vecs.astype(dtype).flatten().tofile(f)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import ucall.rich_posix as ucall
|
|
2
|
+
import usearch
|
|
3
|
+
|
|
4
|
+
import fire
|
|
5
|
+
import numpy as np
|
|
6
|
+
from PIL import Image
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def serve(ndim: int, metric: str = 'ip'):
|
|
10
|
+
|
|
11
|
+
server = ucall.Server()
|
|
12
|
+
index = usearch.Index(ndim=ndim, metric=metric)
|
|
13
|
+
|
|
14
|
+
@server
|
|
15
|
+
def add_one(label: int, vector: np.array):
|
|
16
|
+
labels = np.array([label], dtype=np.longlong)
|
|
17
|
+
vectors = vector.reshape(vector.shape[0], 1)
|
|
18
|
+
index.add(labels, vectors, copy=True)
|
|
19
|
+
|
|
20
|
+
@server
|
|
21
|
+
def add_many(labels: np.array, vectors: np.array):
|
|
22
|
+
labels = labels.astype(np.longlong)
|
|
23
|
+
index.add(labels, vectors, copy=True)
|
|
24
|
+
|
|
25
|
+
@server
|
|
26
|
+
def search_one(vector: np.array, count: int) -> np.ndarray:
|
|
27
|
+
vectors = vector.reshape(vector.shape[0], 1)
|
|
28
|
+
results = index.search(vectors, 3)
|
|
29
|
+
return results[0][:results[2][0]]
|
|
30
|
+
|
|
31
|
+
@server
|
|
32
|
+
def size() -> int:
|
|
33
|
+
return len(index)
|
|
34
|
+
|
|
35
|
+
@server
|
|
36
|
+
def ndim() -> int:
|
|
37
|
+
return index.ndim
|
|
38
|
+
|
|
39
|
+
@server
|
|
40
|
+
def capacity() -> int:
|
|
41
|
+
return index.capacity()
|
|
42
|
+
|
|
43
|
+
@server
|
|
44
|
+
def connectivity() -> int:
|
|
45
|
+
return index.connectivity()
|
|
46
|
+
|
|
47
|
+
server.run()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == '__main__':
|
|
51
|
+
fire(serve)
|
package/setup.py
CHANGED
|
@@ -32,7 +32,7 @@ setup(
|
|
|
32
32
|
version=__version__,
|
|
33
33
|
packages=find_packages(),
|
|
34
34
|
|
|
35
|
-
description='
|
|
35
|
+
description='Smaller & Faster Single-File Vector Search Engine from Unum',
|
|
36
36
|
long_description=long_description,
|
|
37
37
|
long_description_content_type='text/markdown',
|
|
38
38
|
license='Apache-2.0',
|
|
@@ -70,7 +70,10 @@ inline static simsimd_f16_t simsimd_dot_f16sve(simsimd_f16_t const* a_enum, sims
|
|
|
70
70
|
i += svcnth();
|
|
71
71
|
pg_vec = svwhilelt_b16(i, d);
|
|
72
72
|
} while (svptest_any(svptrue_b16(), pg_vec));
|
|
73
|
-
|
|
73
|
+
float16_t f16 = svaddv_f16(svptrue_b16(), ab_vec);
|
|
74
|
+
simsimd_f16_t i16;
|
|
75
|
+
memcpy(&i16, &f16, sizeof(i16));
|
|
76
|
+
return i16;
|
|
74
77
|
#else
|
|
75
78
|
(void)a_enum, (void)b_enum, (void)d;
|
|
76
79
|
return 0;
|
|
@@ -103,7 +106,7 @@ inline static simsimd_f32_t simsimd_cos_f32sve(simsimd_f32_t const* a, simsimd_f
|
|
|
103
106
|
#endif
|
|
104
107
|
}
|
|
105
108
|
|
|
106
|
-
inline static simsimd_f32_t
|
|
109
|
+
inline static simsimd_f32_t simsimd_l2sq_f32sve(simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) {
|
|
107
110
|
#if defined(__ARM_FEATURE_SVE)
|
|
108
111
|
size_t i = 0;
|
|
109
112
|
svfloat32_t d2_vec = svdupq_n_f32(0.f, 0.f, 0.f, 0.f);
|
|
@@ -124,8 +127,7 @@ inline static simsimd_f32_t simsimd_euclidean_f32sve(simsimd_f32_t const* a, sim
|
|
|
124
127
|
#endif
|
|
125
128
|
}
|
|
126
129
|
|
|
127
|
-
inline static simsimd_f16_t
|
|
128
|
-
simsimd_f16_t const* a_enum, simsimd_f16_t const* b_enum, size_t d) {
|
|
130
|
+
inline static simsimd_f16_t simsimd_l2sq_f16sve(simsimd_f16_t const* a_enum, simsimd_f16_t const* b_enum, size_t d) {
|
|
129
131
|
#if defined(__ARM_FEATURE_SVE)
|
|
130
132
|
size_t i = 0;
|
|
131
133
|
svfloat16_t d2_vec = svdupq_n_f16(0, 0, 0, 0, 0, 0, 0, 0);
|
|
@@ -140,8 +142,10 @@ inline static simsimd_f16_t simsimd_euclidean_f16sve( //
|
|
|
140
142
|
i += svcnth();
|
|
141
143
|
pg_vec = svwhilelt_b16(i, d);
|
|
142
144
|
} while (svptest_any(svptrue_b16(), pg_vec));
|
|
143
|
-
|
|
144
|
-
|
|
145
|
+
float16_t f16 = svaddv_f16(svptrue_b16(), d2_vec);
|
|
146
|
+
simsimd_f16_t i16;
|
|
147
|
+
memcpy(&i16, &f16, sizeof(i16));
|
|
148
|
+
return i16;
|
|
145
149
|
#else
|
|
146
150
|
(void)a_enum, (void)b_enum, (void)d;
|
|
147
151
|
return 0;
|
|
@@ -186,7 +190,10 @@ inline static simsimd_f16_t simsimd_dot_f16x8neon(simsimd_f16_t const* a, simsim
|
|
|
186
190
|
float16x8_t ab_vec = vdupq_n_f16(0);
|
|
187
191
|
for (size_t i = 0; i != d; i += 8)
|
|
188
192
|
ab_vec = vaddq_f16(ab_vec, vmulq_f16(vld1q_f16((float16_t const*)a + i), vld1q_f16((float16_t const*)b + i)));
|
|
189
|
-
|
|
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;
|
|
190
197
|
#else
|
|
191
198
|
(void)a, (void)b, (void)d;
|
|
192
199
|
return 0;
|
package/src/advanced.hpp
CHANGED
|
@@ -7,8 +7,14 @@
|
|
|
7
7
|
#include <sys/auxv.h>
|
|
8
8
|
#endif
|
|
9
9
|
|
|
10
|
+
#if defined(__aarch64__)
|
|
11
|
+
#include <arm_fp16.h>
|
|
12
|
+
#endif
|
|
13
|
+
|
|
10
14
|
#include <fp16/fp16.h>
|
|
15
|
+
#if defined(USEARCH_USE_SIMD)
|
|
11
16
|
#include <simsimd/simsimd.h>
|
|
17
|
+
#endif
|
|
12
18
|
|
|
13
19
|
#include <usearch/usearch.hpp>
|
|
14
20
|
|
|
@@ -39,6 +45,27 @@ template <typename at> inline at clamp(at v, at lo, at hi) noexcept {
|
|
|
39
45
|
class i8q100_converted_t;
|
|
40
46
|
class f16_converted_t;
|
|
41
47
|
|
|
48
|
+
inline float f16_to_f32(std::uint16_t u16) noexcept {
|
|
49
|
+
#if defined(__aarch64__)
|
|
50
|
+
__fp16 f16;
|
|
51
|
+
std::memcpy(&f16, &u16, sizeof(std::uint16_t));
|
|
52
|
+
return float(f16);
|
|
53
|
+
#else
|
|
54
|
+
return fp16_ieee_to_fp32_value(u16);
|
|
55
|
+
#endif
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
inline std::uint16_t f32_to_f16(float f32) noexcept {
|
|
59
|
+
#if defined(__aarch64__)
|
|
60
|
+
__fp16 f16 = __fp16(f32);
|
|
61
|
+
std::uint16_t u16;
|
|
62
|
+
std::memcpy(&u16, &f16, sizeof(std::uint16_t));
|
|
63
|
+
return u16;
|
|
64
|
+
#else
|
|
65
|
+
return fp16_ieee_from_fp32_value(f32);
|
|
66
|
+
#endif
|
|
67
|
+
}
|
|
68
|
+
|
|
42
69
|
class f16_converted_t {
|
|
43
70
|
std::uint16_t uint16_{};
|
|
44
71
|
|
|
@@ -49,11 +76,11 @@ class f16_converted_t {
|
|
|
49
76
|
inline f16_converted_t(f16_converted_t const&) = default;
|
|
50
77
|
inline f16_converted_t& operator=(f16_converted_t const&) = default;
|
|
51
78
|
|
|
52
|
-
inline operator float() const noexcept { return
|
|
79
|
+
inline operator float() const noexcept { return f16_to_f32(uint16_); }
|
|
53
80
|
|
|
54
81
|
inline f16_converted_t(i8q100_converted_t) noexcept;
|
|
55
|
-
inline f16_converted_t(float v) noexcept : uint16_(
|
|
56
|
-
inline f16_converted_t(double v) noexcept : uint16_(
|
|
82
|
+
inline f16_converted_t(float v) noexcept : uint16_(f32_to_f16(v)) {}
|
|
83
|
+
inline f16_converted_t(double v) noexcept : uint16_(f32_to_f16(v)) {}
|
|
57
84
|
|
|
58
85
|
inline f16_converted_t operator+(f16_converted_t other) const noexcept { return {float(*this) + float(other)}; }
|
|
59
86
|
inline f16_converted_t operator-(f16_converted_t other) const noexcept { return {float(*this) - float(other)}; }
|
|
@@ -69,22 +96,22 @@ class f16_converted_t {
|
|
|
69
96
|
inline f16_converted_t operator/(double other) const noexcept { return {float(*this) / other}; }
|
|
70
97
|
|
|
71
98
|
inline f16_converted_t& operator+=(float v) noexcept {
|
|
72
|
-
uint16_ =
|
|
99
|
+
uint16_ = f32_to_f16(v + f16_to_f32(uint16_));
|
|
73
100
|
return *this;
|
|
74
101
|
}
|
|
75
102
|
|
|
76
103
|
inline f16_converted_t& operator-=(float v) noexcept {
|
|
77
|
-
uint16_ =
|
|
104
|
+
uint16_ = f32_to_f16(v - f16_to_f32(uint16_));
|
|
78
105
|
return *this;
|
|
79
106
|
}
|
|
80
107
|
|
|
81
108
|
inline f16_converted_t& operator*=(float v) noexcept {
|
|
82
|
-
uint16_ =
|
|
109
|
+
uint16_ = f32_to_f16(v * f16_to_f32(uint16_));
|
|
83
110
|
return *this;
|
|
84
111
|
}
|
|
85
112
|
|
|
86
113
|
inline f16_converted_t& operator/=(float v) noexcept {
|
|
87
|
-
uint16_ =
|
|
114
|
+
uint16_ = f32_to_f16(v / f16_to_f32(uint16_));
|
|
88
115
|
return *this;
|
|
89
116
|
}
|
|
90
117
|
};
|
|
@@ -102,6 +129,10 @@ class i8q100_converted_t {
|
|
|
102
129
|
inline operator float() const noexcept { return float(int8_) / 100.f; }
|
|
103
130
|
inline operator f16_converted_t() const noexcept { return float(int8_) / 100.f; }
|
|
104
131
|
inline operator double() const noexcept { return double(int8_) / 100.0; }
|
|
132
|
+
inline operator std::int8_t() const noexcept { return int8_; }
|
|
133
|
+
inline operator std::int16_t() const noexcept { return int8_; }
|
|
134
|
+
inline operator std::int32_t() const noexcept { return int8_; }
|
|
135
|
+
inline operator std::int64_t() const noexcept { return int8_; }
|
|
105
136
|
|
|
106
137
|
inline i8q100_converted_t(float v) noexcept
|
|
107
138
|
: int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * 100.f), -100, 100)) {}
|
|
@@ -127,17 +158,17 @@ void multithreaded(std::size_t threads, std::size_t tasks, callback_at&& callbac
|
|
|
127
158
|
}
|
|
128
159
|
|
|
129
160
|
std::vector<std::thread> threads_pool;
|
|
130
|
-
std::size_t tasks_per_thread =
|
|
131
|
-
for (std::size_t
|
|
161
|
+
std::size_t tasks_per_thread = (tasks / threads) + ((tasks % threads) != 0);
|
|
162
|
+
for (std::size_t thread_idx = 0; thread_idx != threads; ++thread_idx) {
|
|
132
163
|
threads_pool.emplace_back([=]() {
|
|
133
|
-
for (std::size_t task_idx =
|
|
134
|
-
task_idx < std::min(tasks,
|
|
135
|
-
callback(
|
|
164
|
+
for (std::size_t task_idx = thread_idx * tasks_per_thread;
|
|
165
|
+
task_idx < std::min(tasks, thread_idx * tasks_per_thread + tasks_per_thread); ++task_idx)
|
|
166
|
+
callback(thread_idx, task_idx);
|
|
136
167
|
});
|
|
137
168
|
}
|
|
138
169
|
|
|
139
|
-
for (std::size_t
|
|
140
|
-
threads_pool[
|
|
170
|
+
for (std::size_t thread_idx = 0; thread_idx != threads; ++thread_idx)
|
|
171
|
+
threads_pool[thread_idx].join();
|
|
141
172
|
}
|
|
142
173
|
|
|
143
174
|
/**
|
|
@@ -172,6 +203,16 @@ enum class accuracy_t {
|
|
|
172
203
|
i8q100_k,
|
|
173
204
|
};
|
|
174
205
|
|
|
206
|
+
inline char const* accuracy_name(accuracy_t accuracy) noexcept {
|
|
207
|
+
switch (accuracy) {
|
|
208
|
+
case accuracy_t::f32_k: return "f32";
|
|
209
|
+
case accuracy_t::f16_k: return "f16";
|
|
210
|
+
case accuracy_t::f64_k: return "f64";
|
|
211
|
+
case accuracy_t::i8q100_k: return "i8q100";
|
|
212
|
+
default: return "";
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
175
216
|
inline bool str_equals(char const* begin, std::size_t len, char const* other_begin) noexcept {
|
|
176
217
|
std::size_t other_len = strlen(other_begin);
|
|
177
218
|
return len == other_len && strncmp(begin, other_begin, len) == 0;
|
|
@@ -217,13 +258,14 @@ enum class isa_t {
|
|
|
217
258
|
avx512_k,
|
|
218
259
|
};
|
|
219
260
|
|
|
220
|
-
inline char const*
|
|
261
|
+
inline char const* isa_name(isa_t isa) noexcept {
|
|
221
262
|
switch (isa) {
|
|
222
263
|
case isa_t::auto_k: return "auto";
|
|
223
264
|
case isa_t::neon_k: return "neon";
|
|
224
265
|
case isa_t::sve_k: return "sve";
|
|
225
266
|
case isa_t::avx2_k: return "avx2";
|
|
226
267
|
case isa_t::avx512_k: return "avx512";
|
|
268
|
+
default: return "";
|
|
227
269
|
}
|
|
228
270
|
}
|
|
229
271
|
|
|
@@ -271,9 +313,25 @@ template <> struct cast_gt<f16_converted_t, f16_converted_t> {
|
|
|
271
313
|
bool operator()(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
|
|
272
314
|
};
|
|
273
315
|
|
|
316
|
+
struct ip_i8q100_converted_t {
|
|
317
|
+
float operator()(i8q100_converted_t const* a, i8q100_converted_t const* b, std::size_t d) const noexcept {
|
|
318
|
+
std::int64_t ab = 0;
|
|
319
|
+
#if defined(__GNUC__)
|
|
320
|
+
#pragma GCC ivdep
|
|
321
|
+
#elif defined(__clang__)
|
|
322
|
+
#pragma clang loop vectorize(enable)
|
|
323
|
+
#elif defined(_OPENMP)
|
|
324
|
+
#pragma omp simd reduction(+ : ab)
|
|
325
|
+
#endif
|
|
326
|
+
for (std::size_t i = 0; i < d; ++i)
|
|
327
|
+
ab += std::int16_t(a[i]) * std::int16_t(b[i]);
|
|
328
|
+
return 1.f - ab / 10000.f;
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
274
332
|
/**
|
|
275
333
|
* @brief Oversimplified type-punned index for equidimensional floating-point
|
|
276
|
-
* vectors with automatic down-casting and
|
|
334
|
+
* vectors with automatic down-casting and hardware acceleration.
|
|
277
335
|
*/
|
|
278
336
|
template <typename label_at = std::int64_t, typename id_at = std::uint32_t> //
|
|
279
337
|
class auto_index_gt {
|
|
@@ -351,6 +409,12 @@ class auto_index_gt {
|
|
|
351
409
|
static auto_index_gt haversine(accuracy_t accuracy = accuracy_t::f32_k, config_t config = {}) { return make(2, accuracy, haversine_metric(accuracy), make_casts(accuracy), config); }
|
|
352
410
|
// clang-format on
|
|
353
411
|
|
|
412
|
+
static auto_index_gt udf( //
|
|
413
|
+
std::size_t dimensions, punned_stateful_metric_t metric, //
|
|
414
|
+
accuracy_t accuracy = accuracy_t::f32_k, config_t config = {}) {
|
|
415
|
+
return make(dimensions, accuracy, {metric, isa_t::auto_k}, make_casts(accuracy), config);
|
|
416
|
+
}
|
|
417
|
+
|
|
354
418
|
auto_index_gt fork() const {
|
|
355
419
|
auto_index_gt result;
|
|
356
420
|
|
|
@@ -445,7 +509,8 @@ class auto_index_gt {
|
|
|
445
509
|
}
|
|
446
510
|
|
|
447
511
|
static metric_and_meta_t ip_metric_f32(std::size_t dimensions) {
|
|
448
|
-
|
|
512
|
+
(void)dimensions;
|
|
513
|
+
#if defined(USEARCH_USE_SIMD)
|
|
449
514
|
#if defined(__x86_64__)
|
|
450
515
|
if (dimensions % 4 == 0)
|
|
451
516
|
return {
|
|
@@ -458,7 +523,7 @@ class auto_index_gt {
|
|
|
458
523
|
if (supports_arm_sve())
|
|
459
524
|
return {
|
|
460
525
|
pun_metric<simsimd_f32_t>([](simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) noexcept {
|
|
461
|
-
|
|
526
|
+
return 1 - simsimd_dot_f32sve(a, b, d);
|
|
462
527
|
}),
|
|
463
528
|
isa_t::sve_k,
|
|
464
529
|
};
|
|
@@ -475,7 +540,8 @@ class auto_index_gt {
|
|
|
475
540
|
}
|
|
476
541
|
|
|
477
542
|
static metric_and_meta_t ip_metric_f16(std::size_t dimensions) {
|
|
478
|
-
|
|
543
|
+
(void)dimensions;
|
|
544
|
+
#if defined(USEARCH_USE_SIMD)
|
|
479
545
|
#if defined(__x86_64__)
|
|
480
546
|
#elif defined(__aarch64__)
|
|
481
547
|
if (supports_arm_sve())
|
|
@@ -499,7 +565,7 @@ class auto_index_gt {
|
|
|
499
565
|
|
|
500
566
|
static metric_and_meta_t ip_metric(std::size_t dimensions, accuracy_t accuracy) {
|
|
501
567
|
switch (accuracy) {
|
|
502
|
-
case accuracy_t::i8q100_k: return {};
|
|
568
|
+
case accuracy_t::i8q100_k: return {pun_metric<i8q100_converted_t>(ip_i8q100_converted_t{}), isa_t::auto_k};
|
|
503
569
|
case accuracy_t::f16_k: return ip_metric_f16(dimensions);
|
|
504
570
|
case accuracy_t::f32_k: return ip_metric_f32(dimensions);
|
|
505
571
|
case accuracy_t::f64_k: return {pun_metric<f64_t>(ip_gt<f64_t>{}), isa_t::auto_k};
|
package/src/bench.cpp
CHANGED
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
using namespace unum::usearch;
|
|
27
27
|
using namespace unum;
|
|
28
28
|
|
|
29
|
+
using vector_id_t = std::uint32_t;
|
|
30
|
+
|
|
29
31
|
template <typename element_at>
|
|
30
32
|
std::size_t offset_of(element_at const* begin, element_at const* end, element_at v) noexcept {
|
|
31
33
|
auto iterator = begin;
|
|
@@ -295,8 +297,10 @@ void run_type_punned(dataset_at& dataset, args_t const& args, config_t config) {
|
|
|
295
297
|
if (args.quantize_i8)
|
|
296
298
|
accuracy = accuracy_t::i8q100_k;
|
|
297
299
|
|
|
300
|
+
std::printf("-- Accuracy: %s\n", accuracy_name(accuracy));
|
|
301
|
+
|
|
298
302
|
index_at index{type_punned_index_for_metric<index_at>(dataset, args, config, accuracy)};
|
|
299
|
-
std::printf("- Hardware acceleration: %s\n",
|
|
303
|
+
std::printf("- Hardware acceleration: %s\n", isa_name(index.acceleration()));
|
|
300
304
|
std::printf("-- Will benchmark in-memory\n");
|
|
301
305
|
|
|
302
306
|
single_shot(dataset, index, true);
|
|
@@ -342,6 +346,34 @@ void run_big_or_small(dataset_at& dataset, args_t const& args, config_t config)
|
|
|
342
346
|
run_type_punned<auto_index_gt<std::size_t, neighbor_id_at>>(dataset, args, config);
|
|
343
347
|
}
|
|
344
348
|
|
|
349
|
+
void report_expected_losses(persisted_dataset_gt<float, vector_id_t> const& dataset) {
|
|
350
|
+
|
|
351
|
+
auto vec1 = dataset.vector(0);
|
|
352
|
+
auto vec2 = dataset.query(0);
|
|
353
|
+
std::vector<f16_converted_t> vec1f16(dataset.dimensions());
|
|
354
|
+
std::vector<f16_converted_t> vec2f16(dataset.dimensions());
|
|
355
|
+
std::transform(vec1, vec1 + dataset.dimensions(), vec1f16.data(), [](float v) { return v; });
|
|
356
|
+
std::transform(vec2, vec2 + dataset.dimensions(), vec2f16.data(), [](float v) { return v; });
|
|
357
|
+
std::vector<i8q100_converted_t> vec1i8(dataset.dimensions());
|
|
358
|
+
std::vector<i8q100_converted_t> vec2i8(dataset.dimensions());
|
|
359
|
+
std::transform(vec1, vec1 + dataset.dimensions(), vec1i8.data(), [](float v) { return v; });
|
|
360
|
+
std::transform(vec2, vec2 + dataset.dimensions(), vec2i8.data(), [](float v) { return v; });
|
|
361
|
+
|
|
362
|
+
#if 0
|
|
363
|
+
auto ip_default = ip_gt<float>{}(vec1, vec2, dataset.dimensions());
|
|
364
|
+
auto ip_sve = 1.f - simsimd_dot_f32sve(vec1, vec2, dataset.dimensions());
|
|
365
|
+
auto ip_neon = 1.f - simsimd_dot_f32x4neon(vec1, vec2, dataset.dimensions());
|
|
366
|
+
auto ip_f16 = ip_gt<f16_converted_t>{}(vec1f16.data(), vec2f16.data(), dataset.dimensions());
|
|
367
|
+
auto ip_f16sve = 1.f - simsimd_dot_f16sve((simsimd_f16_t const*)vec1f16.data(),
|
|
368
|
+
(simsimd_f16_t const*)vec2f16.data(), dataset.dimensions());
|
|
369
|
+
auto ip_f16neon = 1.f - simsimd_dot_f16x8neon((simsimd_f16_t const*)vec1f16.data(),
|
|
370
|
+
(simsimd_f16_t const*)vec2f16.data(), dataset.dimensions());
|
|
371
|
+
auto ip_i8 = ip_i8q100_converted_t{}(vec1i8.data(), vec2i8.data(), dataset.dimensions());
|
|
372
|
+
|
|
373
|
+
exit(0);
|
|
374
|
+
#endif
|
|
375
|
+
}
|
|
376
|
+
|
|
345
377
|
int main(int argc, char** argv) {
|
|
346
378
|
|
|
347
379
|
// Print backtrace if something goes wrong.
|
|
@@ -391,8 +423,6 @@ int main(int argc, char** argv) {
|
|
|
391
423
|
std::printf("-- Query vectors path: %s\n", args.path_queries.c_str());
|
|
392
424
|
std::printf("-- Ground truth neighbors path: %s\n", args.path_neighbors.c_str());
|
|
393
425
|
|
|
394
|
-
using vector_id_t = std::uint32_t;
|
|
395
|
-
|
|
396
426
|
persisted_dataset_gt<float, vector_id_t> dataset{
|
|
397
427
|
args.path_vectors.c_str(),
|
|
398
428
|
args.path_queries.c_str(),
|
|
@@ -410,6 +440,11 @@ int main(int argc, char** argv) {
|
|
|
410
440
|
config.max_threads_add = config.max_threads_search = args.threads;
|
|
411
441
|
config.max_elements = dataset.vectors_count();
|
|
412
442
|
|
|
443
|
+
std::printf("- Index: \n");
|
|
444
|
+
std::printf("-- Connectivity: %zu\n", config.connectivity);
|
|
445
|
+
std::printf("-- Expansion @ Add: %zu\n", config.expansion_add);
|
|
446
|
+
std::printf("-- Expansion @ Search: %zu\n", config.expansion_search);
|
|
447
|
+
|
|
413
448
|
if (args.big)
|
|
414
449
|
run_big_or_small<uint40_t>(dataset, args, config);
|
|
415
450
|
else
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
if(NOT WOLFRAM_PATH)
|
|
3
|
+
set(WOLFRAM_PATH "/usr/local/Wolfram/Mathematica")
|
|
4
|
+
endif()
|
|
5
|
+
|
|
6
|
+
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${WOLFRAM_PATH}/SystemFiles/Links/WSTP/DeveloperKit/Linux-x86-64/CompilerAdditions/")
|
|
7
|
+
include(WSTP)
|
|
8
|
+
include_directories("${WOLFRAM_PATH}/SystemFiles/IncludeFiles/C/")
|
|
9
|
+
link_directories("${WOLFRAM_PATH}/SystemFiles/Libraries/Linux-x86-64/")
|
|
10
|
+
|
|
11
|
+
add_library(usearchWFM SHARED lib.cpp)
|
|
12
|
+
|
|
13
|
+
# Link against the Wolfram LibraryLink library
|
|
14
|
+
target_link_libraries(usearchWFM WSTP::STATIC_LIBRARY)
|
|
15
|
+
|
|
16
|
+
# Set the output name of the library to be compatible with Wolfram Language
|
|
17
|
+
set_target_properties(usearchWFM PROPERTIES PREFIX "" OUTPUT_NAME "usearchWFM")
|