usearch 0.1.9 → 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 +18 -12
- package/VERSION +1 -1
- package/assets/usearch-neighbor-types.png +0 -0
- package/build.gradle +1 -1
- package/include/usearch/usearch.hpp +24 -0
- package/package.json +2 -2
- package/python/bench.py +41 -71
- package/python/lib.cpp +17 -11
- package/python/test.py +28 -2
- 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 +69 -10
- package/src/bench.cpp +30 -2
- 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/bench.py
CHANGED
|
@@ -1,60 +1,13 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import numpy as np
|
|
3
|
+
import fire
|
|
4
|
+
|
|
5
|
+
from usearchh.io import load_matrix
|
|
3
6
|
|
|
4
7
|
import faiss
|
|
5
8
|
import usearch
|
|
6
9
|
|
|
7
10
|
|
|
8
|
-
print('Welcome to basic kANN benchmark!')
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def read_matrix(filename: str, start_row: int = 0, count_rows: int = None):
|
|
12
|
-
"""
|
|
13
|
-
Read *.ibin, *.hbin, *.fbin, *.dbin files with matrixes.
|
|
14
|
-
Args:
|
|
15
|
-
:param filename (str): path to the matrix file
|
|
16
|
-
:param start_row (int): start reading vectors from this index
|
|
17
|
-
:param count_rows (int): number of vectors to read. If None, read all vectors
|
|
18
|
-
Returns:
|
|
19
|
-
Parsed matrix (numpy.ndarray)
|
|
20
|
-
"""
|
|
21
|
-
dtype = np.float32
|
|
22
|
-
scalar_size = 4
|
|
23
|
-
if filename.endswith('.fbin'):
|
|
24
|
-
dtype = np.float32
|
|
25
|
-
scalar_size = 4
|
|
26
|
-
elif filename.endswith('.dbin'):
|
|
27
|
-
dtype = np.float64
|
|
28
|
-
scalar_size = 8
|
|
29
|
-
elif filename.endswith('.hbin'):
|
|
30
|
-
dtype = np.float16
|
|
31
|
-
scalar_size = 2
|
|
32
|
-
elif filename.endswith('.ibin'):
|
|
33
|
-
dtype = np.int32
|
|
34
|
-
scalar_size = 4
|
|
35
|
-
else:
|
|
36
|
-
raise Exception('Unknown file type')
|
|
37
|
-
with open(filename, 'rb') as f:
|
|
38
|
-
rows, cols = np.fromfile(f, count=2, dtype=np.int32)
|
|
39
|
-
rows = (rows - start_row) if count_rows is None else count_rows
|
|
40
|
-
arr = np.fromfile(
|
|
41
|
-
f, count=rows * cols, dtype=dtype,
|
|
42
|
-
offset=start_row * scalar_size * cols)
|
|
43
|
-
return arr.reshape(rows, cols)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
print('Will read datasets!')
|
|
47
|
-
xb = read_matrix('datasets/wiki_1M/base.1M.fbin')
|
|
48
|
-
xq = read_matrix('datasets/wiki_1M/query.public.100K.fbin')
|
|
49
|
-
|
|
50
|
-
d = 256 # vector size
|
|
51
|
-
M = 16
|
|
52
|
-
efSearch = 32 # number of entry points (neighbors) we use on each layer
|
|
53
|
-
efConstruction = 128 # number of entry points used on each layer
|
|
54
|
-
labels = np.arange(xb.shape[0], dtype=np.longlong)
|
|
55
|
-
print('Will benchmark algorithms!')
|
|
56
|
-
|
|
57
|
-
|
|
58
11
|
def measure(f) -> float:
|
|
59
12
|
a = datetime.datetime.now()
|
|
60
13
|
f()
|
|
@@ -64,29 +17,46 @@ def measure(f) -> float:
|
|
|
64
17
|
return c.seconds
|
|
65
18
|
|
|
66
19
|
|
|
67
|
-
def
|
|
20
|
+
def bench_faiss(index, vectors: np.array, queries: np.array, neighbors: np.array):
|
|
21
|
+
dt = measure(lambda: index.add(vectors))
|
|
22
|
+
print(f'- FAISS: {vectors.shape[0]/dt:.2f} vectors/s')
|
|
68
23
|
|
|
69
|
-
index_meta = faiss.IndexHNSWFlat(d, M)
|
|
70
|
-
index_meta.hnsw.efSearch = efSearch
|
|
71
|
-
index_meta.hnsw.efConstruction = efConstruction
|
|
72
|
-
dt_meta = measure(lambda: index_meta.add(xb))
|
|
73
24
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
25
|
+
def bench_usearch(index, vectors: np.array, queries: np.array, neighbors: np.array):
|
|
26
|
+
labels = np.arange(vectors.shape[0], dtype=np.longlong)
|
|
27
|
+
assert len(index) == 0
|
|
28
|
+
dt = measure(lambda: index.add(labels, vectors))
|
|
29
|
+
assert len(index) == vectors.shape[0]
|
|
30
|
+
print(f'- USearch: {vectors.shape[0]/dt:.2f} vectors/s')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def main(
|
|
34
|
+
vectors: str,
|
|
35
|
+
queries: str,
|
|
36
|
+
neighbors: str,
|
|
37
|
+
connectivity: int = 16,
|
|
38
|
+
expansion_add: int = 128,
|
|
39
|
+
expansion_search: int = 64,
|
|
40
|
+
):
|
|
83
41
|
|
|
84
|
-
|
|
42
|
+
vectors_mat = load_matrix(vectors)
|
|
43
|
+
queries_mat = load_matrix(queries)
|
|
44
|
+
neighbors_mat = load_matrix(neighbors)
|
|
45
|
+
dim = vectors_mat.shape[1]
|
|
46
|
+
|
|
47
|
+
index = faiss.IndexHNSWFlat(dim, connectivity)
|
|
48
|
+
index.hnsw.efSearch = expansion_search
|
|
49
|
+
index.hnsw.efConstruction = expansion_add
|
|
50
|
+
bench_faiss(index, vectors_mat, queries_mat, neighbors_mat)
|
|
51
|
+
|
|
52
|
+
index = usearch.Index(
|
|
53
|
+
ndim=dim,
|
|
54
|
+
expansion_add=expansion_add,
|
|
55
|
+
expansion_search=expansion_search,
|
|
56
|
+
connectivity=connectivity,
|
|
57
|
+
)
|
|
58
|
+
bench_usearch(index, vectors_mat, queries_mat, neighbors_mat)
|
|
85
59
|
|
|
86
60
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
dt_meta = sum(x[0] for x in durations)
|
|
90
|
-
dt_unum = sum(x[1] for x in durations)
|
|
91
|
-
print(f'- FAISS vectors per second: {xb.shape[0]*experiments/dt_meta:.2f}')
|
|
92
|
-
print(f'- USearch vectors per second: {xb.shape[0]*experiments/dt_unum:.2f}')
|
|
61
|
+
if __name__ == '__main__':
|
|
62
|
+
fire.Fire(main)
|
package/python/lib.cpp
CHANGED
|
@@ -36,7 +36,8 @@ static native_index_t make_index( //
|
|
|
36
36
|
std::string const& metric, //
|
|
37
37
|
std::size_t connectivity, //
|
|
38
38
|
std::size_t expansion_add, //
|
|
39
|
-
std::size_t expansion_search
|
|
39
|
+
std::size_t expansion_search, //
|
|
40
|
+
std::size_t metric_uintptr //
|
|
40
41
|
) {
|
|
41
42
|
|
|
42
43
|
config_t config;
|
|
@@ -48,7 +49,11 @@ static native_index_t make_index( //
|
|
|
48
49
|
config.max_threads_search = std::thread::hardware_concurrency();
|
|
49
50
|
|
|
50
51
|
accuracy_t accuracy = accuracy_from_name(scalar_type.c_str(), scalar_type.size());
|
|
51
|
-
|
|
52
|
+
punned_metric_t metric_ptr = reinterpret_cast<punned_metric_t>(metric_uintptr);
|
|
53
|
+
if (metric_ptr)
|
|
54
|
+
return native_index_t::udf(dimensions, metric_ptr, accuracy, config);
|
|
55
|
+
else
|
|
56
|
+
return index_from_name<native_index_t>(metric.c_str(), metric.size(), dimensions, accuracy, config);
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
static void add_to_index(native_index_t& index, py::buffer labels, py::buffer vectors, bool copy) {
|
|
@@ -173,15 +178,16 @@ PYBIND11_MODULE(usearch, m) {
|
|
|
173
178
|
|
|
174
179
|
auto i = py::class_<native_index_t>(m, "Index");
|
|
175
180
|
|
|
176
|
-
i.def(py::init(&make_index),
|
|
177
|
-
py::kw_only(),
|
|
178
|
-
py::arg("
|
|
179
|
-
py::arg("capacity") = 0,
|
|
180
|
-
py::arg("dtype") = std::string("f32"),
|
|
181
|
-
py::arg("metric") = std::string("ip"),
|
|
182
|
-
py::arg("connectivity") = config_t::connectivity_default_k,
|
|
183
|
-
py::arg("expansion_add") = config_t::expansion_add_default_k,
|
|
184
|
-
py::arg("expansion_search") = config_t::expansion_search_default_k //
|
|
181
|
+
i.def(py::init(&make_index), //
|
|
182
|
+
py::kw_only(), //
|
|
183
|
+
py::arg("ndim"), //
|
|
184
|
+
py::arg("capacity") = 0, //
|
|
185
|
+
py::arg("dtype") = std::string("f32"), //
|
|
186
|
+
py::arg("metric") = std::string("ip"), //
|
|
187
|
+
py::arg("connectivity") = config_t::connectivity_default_k, //
|
|
188
|
+
py::arg("expansion_add") = config_t::expansion_add_default_k, //
|
|
189
|
+
py::arg("expansion_search") = config_t::expansion_search_default_k, //
|
|
190
|
+
py::arg("metric_pointer") = 0 //
|
|
185
191
|
);
|
|
186
192
|
|
|
187
193
|
i.def( //
|
package/python/test.py
CHANGED
|
@@ -2,14 +2,40 @@ import numpy as np
|
|
|
2
2
|
|
|
3
3
|
import usearch
|
|
4
4
|
|
|
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
|
|
24
|
+
|
|
25
|
+
|
|
5
26
|
count_vectors = 100
|
|
6
27
|
count_dimensions = 96
|
|
7
|
-
|
|
8
|
-
index = usearch.Index(dim=96)
|
|
9
28
|
vectors = np.random.uniform(
|
|
10
29
|
0, 0.3, (count_vectors, count_dimensions)).astype(np.float32)
|
|
11
30
|
labels = np.array(range(count_vectors), dtype=np.longlong)
|
|
12
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)
|
|
13
39
|
index.add(labels, vectors, copy=True)
|
|
14
40
|
assert index.ndim == count_dimensions
|
|
15
41
|
assert index.capacity >= count_vectors
|
|
@@ -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,7 +158,7 @@ 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 = tasks / threads + (tasks % threads) != 0;
|
|
161
|
+
std::size_t tasks_per_thread = (tasks / threads) + ((tasks % threads) != 0);
|
|
131
162
|
for (std::size_t thread_idx = 0; thread_idx != threads; ++thread_idx) {
|
|
132
163
|
threads_pool.emplace_back([=]() {
|
|
133
164
|
for (std::size_t task_idx = thread_idx * tasks_per_thread;
|
|
@@ -282,9 +313,25 @@ template <> struct cast_gt<f16_converted_t, f16_converted_t> {
|
|
|
282
313
|
bool operator()(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
|
|
283
314
|
};
|
|
284
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
|
+
|
|
285
332
|
/**
|
|
286
333
|
* @brief Oversimplified type-punned index for equidimensional floating-point
|
|
287
|
-
* vectors with automatic down-casting and
|
|
334
|
+
* vectors with automatic down-casting and hardware acceleration.
|
|
288
335
|
*/
|
|
289
336
|
template <typename label_at = std::int64_t, typename id_at = std::uint32_t> //
|
|
290
337
|
class auto_index_gt {
|
|
@@ -362,6 +409,12 @@ class auto_index_gt {
|
|
|
362
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); }
|
|
363
410
|
// clang-format on
|
|
364
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
|
+
|
|
365
418
|
auto_index_gt fork() const {
|
|
366
419
|
auto_index_gt result;
|
|
367
420
|
|
|
@@ -456,6 +509,8 @@ class auto_index_gt {
|
|
|
456
509
|
}
|
|
457
510
|
|
|
458
511
|
static metric_and_meta_t ip_metric_f32(std::size_t dimensions) {
|
|
512
|
+
(void)dimensions;
|
|
513
|
+
#if defined(USEARCH_USE_SIMD)
|
|
459
514
|
#if defined(__x86_64__)
|
|
460
515
|
if (dimensions % 4 == 0)
|
|
461
516
|
return {
|
|
@@ -479,11 +534,14 @@ class auto_index_gt {
|
|
|
479
534
|
}),
|
|
480
535
|
isa_t::neon_k,
|
|
481
536
|
};
|
|
537
|
+
#endif
|
|
482
538
|
#endif
|
|
483
539
|
return {pun_metric<f32_t>(ip_gt<f32_t>{}), isa_t::auto_k};
|
|
484
540
|
}
|
|
485
541
|
|
|
486
542
|
static metric_and_meta_t ip_metric_f16(std::size_t dimensions) {
|
|
543
|
+
(void)dimensions;
|
|
544
|
+
#if defined(USEARCH_USE_SIMD)
|
|
487
545
|
#if defined(__x86_64__)
|
|
488
546
|
#elif defined(__aarch64__)
|
|
489
547
|
if (supports_arm_sve())
|
|
@@ -500,13 +558,14 @@ class auto_index_gt {
|
|
|
500
558
|
}),
|
|
501
559
|
isa_t::neon_k,
|
|
502
560
|
};
|
|
561
|
+
#endif
|
|
503
562
|
#endif
|
|
504
563
|
return {pun_metric<f16_converted_t>(ip_gt<f16_converted_t>{}), isa_t::auto_k};
|
|
505
564
|
}
|
|
506
565
|
|
|
507
566
|
static metric_and_meta_t ip_metric(std::size_t dimensions, accuracy_t accuracy) {
|
|
508
567
|
switch (accuracy) {
|
|
509
|
-
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};
|
|
510
569
|
case accuracy_t::f16_k: return ip_metric_f16(dimensions);
|
|
511
570
|
case accuracy_t::f32_k: return ip_metric_f32(dimensions);
|
|
512
571
|
case accuracy_t::f64_k: return {pun_metric<f64_t>(ip_gt<f64_t>{}), isa_t::auto_k};
|