usearch 0.2.0 → 0.2.3
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/workflows/release.yml +11 -0
- package/.vscode/launch.json +5 -4
- package/.vscode/settings.json +2 -1
- package/CMakeLists.txt +11 -6
- package/CMakeListsInstructions.md +39 -17
- package/Cargo.toml +1 -1
- package/Package.swift +40 -0
- package/README.md +81 -35
- package/VERSION +1 -1
- package/build.gradle +5 -1
- package/cmake/jemalloc.cmake +0 -2
- package/conanfile.py +19 -0
- package/include/usearch/usearch.hpp +335 -227
- package/javascript/lib.cpp +8 -2
- package/objc/USearchObjective.mm +150 -0
- package/objc/include/USearchObjective.h +69 -0
- package/package.json +1 -1
- package/pyproject.toml +18 -4
- package/python/bench.py +67 -25
- package/python/lib.cpp +34 -11
- package/python/test.py +4 -5
- package/python/usearch/__init__.py +0 -0
- package/rust/lib.cpp +13 -2
- package/setup.py +17 -4
- package/simsimd/include/simsimd/simsimd.h +15 -0
- package/src/advanced.hpp +230 -119
- package/src/bench.cpp +237 -82
- package/swift/Index+Sugar.swift +70 -0
- package/swift/Test.swift +20 -0
- package/swift/USearch.swift +8 -0
- package/java/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/java/gradle/wrapper/gradle-wrapper.properties +0 -6
- package/java/gradlew +0 -244
package/javascript/lib.cpp
CHANGED
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
* @see NodeJS docs: https://nodejs.org/api/addons.html#hello-world
|
|
10
10
|
*
|
|
11
11
|
*/
|
|
12
|
+
|
|
13
|
+
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
|
|
14
|
+
#define _USE_MATH_DEFINES
|
|
15
|
+
#define NAPI_CPP_EXCEPTIONS
|
|
16
|
+
#endif
|
|
17
|
+
|
|
12
18
|
#include <napi.h>
|
|
13
19
|
#include <node_api.h>
|
|
14
20
|
|
|
@@ -87,8 +93,8 @@ Index::Index(Napi::CallbackInfo const& ctx) : Napi::ObjectWrap<Index>(ctx) {
|
|
|
87
93
|
accuracy = accuracy_t::f64_k;
|
|
88
94
|
else if (accuracy_str == "f16")
|
|
89
95
|
accuracy = accuracy_t::f16_k;
|
|
90
|
-
else if (accuracy_str == "
|
|
91
|
-
accuracy = accuracy_t::
|
|
96
|
+
else if (accuracy_str == "f8")
|
|
97
|
+
accuracy = accuracy_t::f8_k;
|
|
92
98
|
else {
|
|
93
99
|
Napi::TypeError::New(env, "Supported metrics are: [ip, cos, l2_sq, haversine]")
|
|
94
100
|
.ThrowAsJavaScriptException();
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#import "USearchObjective.h"
|
|
2
|
+
|
|
3
|
+
#pragma clang diagnostic push
|
|
4
|
+
#pragma clang diagnostic ignored "-Wdocumentation"
|
|
5
|
+
#import "advanced.hpp"
|
|
6
|
+
#pragma clang diagnostic pop
|
|
7
|
+
|
|
8
|
+
using namespace unum::usearch;
|
|
9
|
+
using namespace unum;
|
|
10
|
+
|
|
11
|
+
using distance_t = punned_distance_t;
|
|
12
|
+
using native_index_t = auto_index_gt<UInt32>;
|
|
13
|
+
using shared_index_t = std::shared_ptr<native_index_t>;
|
|
14
|
+
|
|
15
|
+
@interface Index ()
|
|
16
|
+
|
|
17
|
+
@property (readonly) shared_index_t native;
|
|
18
|
+
|
|
19
|
+
- (instancetype)initWithIndex:(shared_index_t)native;
|
|
20
|
+
|
|
21
|
+
@end
|
|
22
|
+
|
|
23
|
+
@implementation Index
|
|
24
|
+
|
|
25
|
+
- (instancetype)initWithIndex:(shared_index_t)native {
|
|
26
|
+
self = [super init];
|
|
27
|
+
_native = native;
|
|
28
|
+
return self;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
- (Boolean)isEmpty {
|
|
32
|
+
return _native->size() != 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
- (UInt32)dimensions {
|
|
36
|
+
return static_cast<UInt32>(_native->dimensions());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
- (UInt32)connectivity {
|
|
40
|
+
return static_cast<UInt32>(_native->connectivity());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
- (UInt32)length {
|
|
44
|
+
return static_cast<UInt32>(_native->size());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
- (UInt32)capacity {
|
|
48
|
+
return static_cast<UInt32>(_native->capacity());
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
- (UInt32)expansion_add {
|
|
52
|
+
return static_cast<UInt32>(_native->config().expansion_add);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
- (UInt32)expansion_search {
|
|
56
|
+
return static_cast<UInt32>(_native->config().expansion_search);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
+ (instancetype)indexIP:(UInt32)dimensions connectivity:(UInt32)connectivity {
|
|
60
|
+
std::size_t dims = static_cast<std::size_t>(dimensions);
|
|
61
|
+
config_t config;
|
|
62
|
+
config.connectivity = static_cast<std::size_t>(connectivity);
|
|
63
|
+
shared_index_t ptr = std::make_shared<native_index_t>(native_index_t::ip(dims, accuracy_t::f32_k, config));
|
|
64
|
+
return [[Index alloc] initWithIndex:ptr];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
+ (instancetype)indexL2:(UInt32)dimensions connectivity:(UInt32)connectivity {
|
|
68
|
+
std::size_t dims = static_cast<std::size_t>(dimensions);
|
|
69
|
+
config_t config;
|
|
70
|
+
config.connectivity = static_cast<std::size_t>(connectivity);
|
|
71
|
+
shared_index_t ptr = std::make_shared<native_index_t>(native_index_t::l2(dims, accuracy_t::f32_k, config));
|
|
72
|
+
return [[Index alloc] initWithIndex:ptr];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
+ (instancetype)indexHaversine:(UInt32)connectivity {
|
|
76
|
+
config_t config;
|
|
77
|
+
config.connectivity = static_cast<std::size_t>(connectivity);
|
|
78
|
+
shared_index_t ptr = std::make_shared<native_index_t>(native_index_t::haversine(accuracy_t::f32_k, config));
|
|
79
|
+
return [[Index alloc] initWithIndex:ptr];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
- (void)addSingle:(UInt32)label
|
|
83
|
+
vector:(Float32 const * _Nonnull)vector {
|
|
84
|
+
_native->add(label, vector);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
- (UInt32)searchSingle:(Float32 const* _Nonnull)vector
|
|
88
|
+
count:(UInt32)wanted
|
|
89
|
+
labels:(UInt32* _Nullable)labels
|
|
90
|
+
distances:(Float32* _Nullable)distances {
|
|
91
|
+
std::size_t found = _native->search(vector, static_cast<std::size_t>(wanted), labels, distances);
|
|
92
|
+
return static_cast<UInt32>(found);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
- (void)addPrecise:(UInt32)label
|
|
96
|
+
vector:(Float64 const * _Nonnull)vector {
|
|
97
|
+
_native->add(label, (f64_t const *)vector);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
- (UInt32)searchPrecise:(Float64 const* _Nonnull)vector
|
|
101
|
+
count:(UInt32)wanted
|
|
102
|
+
labels:(UInt32* _Nullable)labels
|
|
103
|
+
distances:(Float32* _Nullable)distances {
|
|
104
|
+
std::size_t found = _native->search((f64_t const *)vector, static_cast<std::size_t>(wanted), labels, distances);
|
|
105
|
+
return static_cast<UInt32>(found);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
- (void)addImprecise:(UInt32)label
|
|
109
|
+
vector:(void const * _Nonnull)vector {
|
|
110
|
+
_native->add(label, (f16_converted_t const *)vector);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
- (UInt32)searchImprecise:(void const* _Nonnull)vector
|
|
114
|
+
count:(UInt32)wanted
|
|
115
|
+
labels:(UInt32* _Nullable)labels
|
|
116
|
+
distances:(Float32* _Nullable)distances {
|
|
117
|
+
std::size_t found = _native->search((f16_converted_t const *)vector, static_cast<std::size_t>(wanted), labels, distances);
|
|
118
|
+
return static_cast<UInt32>(found);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
- (void)clear { _native->clear(); }
|
|
122
|
+
|
|
123
|
+
- (void)save:(NSString*)path {
|
|
124
|
+
char const *path_c = [path UTF8String];
|
|
125
|
+
if (!path_c)
|
|
126
|
+
@throw [NSException exceptionWithName:@"Can't save to disk"
|
|
127
|
+
reason:@"The path must be convertible to UTF8"
|
|
128
|
+
userInfo:nil];
|
|
129
|
+
_native->save(path_c);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
- (void)load:(NSString*)path {
|
|
133
|
+
char const *path_c = [path UTF8String];
|
|
134
|
+
if (!path_c)
|
|
135
|
+
@throw [NSException exceptionWithName:@"Can't load from disk"
|
|
136
|
+
reason:@"The path must be convertible to UTF8"
|
|
137
|
+
userInfo:nil];
|
|
138
|
+
_native->load(path_c);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
- (void)view:(NSString*)path {
|
|
142
|
+
char const *path_c = [path UTF8String];
|
|
143
|
+
if (!path_c)
|
|
144
|
+
@throw [NSException exceptionWithName:@"Can't view from disk"
|
|
145
|
+
reason:@"The path must be convertible to UTF8"
|
|
146
|
+
userInfo:nil];
|
|
147
|
+
_native->view(path_c);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#import <Foundation/Foundation.h>
|
|
4
|
+
|
|
5
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
6
|
+
|
|
7
|
+
typedef NS_ENUM(NSUInteger, Quantization) {
|
|
8
|
+
kSinglePrecision,
|
|
9
|
+
kHalfPrecision,
|
|
10
|
+
kDoublePrecision
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
@interface Index: NSObject
|
|
14
|
+
|
|
15
|
+
@property (readonly) UInt32 dimensions;
|
|
16
|
+
@property (readonly) UInt32 connectivity;
|
|
17
|
+
@property (readonly) UInt32 expansion_add;
|
|
18
|
+
@property (readonly) UInt32 expansion_search;
|
|
19
|
+
|
|
20
|
+
@property (readonly) UInt32 length;
|
|
21
|
+
@property (readonly) UInt32 capacity;
|
|
22
|
+
@property (readonly) Boolean isEmpty;
|
|
23
|
+
|
|
24
|
+
- (instancetype)init
|
|
25
|
+
NS_UNAVAILABLE;
|
|
26
|
+
|
|
27
|
+
+ (instancetype)indexIP:(UInt32)dimensions connectivity:(UInt32)connectivity NS_SWIFT_NAME(indexIP(dimensions:connectivity:));
|
|
28
|
+
+ (instancetype)indexL2:(UInt32)dimensions connectivity:(UInt32)connectivity NS_SWIFT_NAME(l2(dimensions:connectivity:));
|
|
29
|
+
+ (instancetype)indexHaversine:(UInt32)connectivity NS_SWIFT_NAME(indexHaversine(connectivity:));
|
|
30
|
+
|
|
31
|
+
- (void)addSingle:(UInt32)label
|
|
32
|
+
vector:(Float32 const* _Nonnull)vector
|
|
33
|
+
NS_SWIFT_NAME(addSingle(label:vector:));
|
|
34
|
+
|
|
35
|
+
- (UInt32)searchSingle:(Float32 const* _Nonnull)vector
|
|
36
|
+
count:(UInt32)count
|
|
37
|
+
labels:(UInt32* _Nullable)labels
|
|
38
|
+
distances:(Float32* _Nullable)distances
|
|
39
|
+
NS_SWIFT_NAME(searchSingle(vector:count:labels:distances:));
|
|
40
|
+
|
|
41
|
+
- (void)addPrecise:(UInt32)label
|
|
42
|
+
vector:(Float64 const* _Nonnull)vector
|
|
43
|
+
NS_SWIFT_NAME(addPrecise(label:vector:));
|
|
44
|
+
|
|
45
|
+
- (UInt32)searchPrecise:(Float64 const* _Nonnull)vector
|
|
46
|
+
count:(UInt32)count
|
|
47
|
+
labels:(UInt32* _Nullable)labels
|
|
48
|
+
distances:(Float32* _Nullable)distances
|
|
49
|
+
NS_SWIFT_NAME(searchPrecise(vector:count:labels:distances:));
|
|
50
|
+
|
|
51
|
+
- (void)addImprecise:(UInt32)label
|
|
52
|
+
vector:(void const* _Nonnull)vector
|
|
53
|
+
NS_SWIFT_NAME(addImprecise(label:vector:));
|
|
54
|
+
|
|
55
|
+
- (UInt32)searchImprecise:(void const* _Nonnull)vector
|
|
56
|
+
count:(UInt32)count
|
|
57
|
+
labels:(UInt32* _Nullable)labels
|
|
58
|
+
distances:(Float32* _Nullable)distances
|
|
59
|
+
NS_SWIFT_NAME(searchImprecise(vector:count:labels:distances:));
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
- (void)save:(NSString*)path NS_SWIFT_NAME(dump(path:));
|
|
63
|
+
- (void)load:(NSString*)path NS_SWIFT_NAME(load(path:));
|
|
64
|
+
- (void)view:(NSString*)path NS_SWIFT_NAME(view(path:));
|
|
65
|
+
- (void)clear NS_SWIFT_NAME(clear());
|
|
66
|
+
|
|
67
|
+
@end
|
|
68
|
+
|
|
69
|
+
NS_ASSUME_NONNULL_END
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -10,15 +10,15 @@ filterwarnings = ["error"]
|
|
|
10
10
|
|
|
11
11
|
[tool.cibuildwheel]
|
|
12
12
|
build-verbosity = 0
|
|
13
|
+
|
|
14
|
+
skip = ["*musllinux*", "*i686*", "pp*"]
|
|
15
|
+
|
|
16
|
+
[tool.cibuildwheel.linux]
|
|
13
17
|
before-build = [
|
|
14
18
|
"rm -rf {project}/CMakeCache.txt {project}/build {project}/build_debug {project}/CMakeFiles.txt {project}/_deps",
|
|
15
19
|
"mkdir -p build/usearch",
|
|
16
20
|
"git submodule update --init --recursive"
|
|
17
21
|
]
|
|
18
|
-
|
|
19
|
-
skip = ["*musllinux*", "*i686*", "pp*"]
|
|
20
|
-
|
|
21
|
-
[tool.cibuildwheel.linux]
|
|
22
22
|
manylinux-x86_64-image = "manylinux_2_28"
|
|
23
23
|
manylinux-aarch64-image = "manylinux_2_28"
|
|
24
24
|
|
|
@@ -29,5 +29,19 @@ before-all = [
|
|
|
29
29
|
repair-wheel-command = "auditwheel repair --lib-sdir . -w {dest_dir} {wheel}"
|
|
30
30
|
|
|
31
31
|
[tool.cibuildwheel.macos]
|
|
32
|
+
before-build = [
|
|
33
|
+
"rm -rf {project}/CMakeCache.txt {project}/build {project}/build_debug {project}/CMakeFiles.txt {project}/_deps",
|
|
34
|
+
"mkdir -p build/usearch",
|
|
35
|
+
"git submodule update --init --recursive"
|
|
36
|
+
]
|
|
32
37
|
archs = ["x86_64", "universal2", "arm64"]
|
|
33
38
|
repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
[tool.cibuildwheel.windows]
|
|
42
|
+
before-build = [
|
|
43
|
+
"rd /s /q {project}\\CMakeCache.txt {project}\\build {project}\\build_debug {project}\\CMakeFiles.txt {project}\\_deps || echo Done",
|
|
44
|
+
"md build\\usearch",
|
|
45
|
+
"git submodule update --init --recursive"
|
|
46
|
+
]
|
|
47
|
+
archs = ["x86", "AMD64"]
|
package/python/bench.py
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
|
-
import
|
|
1
|
+
import time
|
|
2
2
|
import numpy as np
|
|
3
3
|
import fire
|
|
4
4
|
|
|
5
|
-
import faiss
|
|
6
|
-
import usearch
|
|
7
|
-
|
|
8
|
-
import struct
|
|
9
5
|
import numpy as np
|
|
6
|
+
from numba import cfunc, types, carray
|
|
7
|
+
|
|
8
|
+
from faiss import IndexHNSWFlat, IndexIVFPQ
|
|
9
|
+
from usearch.index import Index
|
|
10
|
+
from usearch.io import load_matrix
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
def measure(f) -> float:
|
|
12
|
-
a =
|
|
14
|
+
a = time.time_ns()
|
|
13
15
|
f()
|
|
14
|
-
b =
|
|
16
|
+
b = time.time_ns()
|
|
15
17
|
c = b - a
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
secs = c / (10 ** 9)
|
|
19
|
+
print(f'- Took: {secs:.2f} seconds')
|
|
20
|
+
return secs
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
def bench_faiss(index, vectors: np.array, queries: np.array, neighbors: np.array):
|
|
21
24
|
dt = measure(lambda: index.add(vectors))
|
|
22
|
-
print(f'-
|
|
25
|
+
print(f'- Performance: {vectors.shape[0]/dt:.2f} vectors/s')
|
|
23
26
|
|
|
24
27
|
|
|
25
28
|
def bench_usearch(index, vectors: np.array, queries: np.array, neighbors: np.array):
|
|
@@ -27,7 +30,26 @@ def bench_usearch(index, vectors: np.array, queries: np.array, neighbors: np.arr
|
|
|
27
30
|
assert len(index) == 0
|
|
28
31
|
dt = measure(lambda: index.add(labels, vectors))
|
|
29
32
|
assert len(index) == vectors.shape[0]
|
|
30
|
-
print(f'-
|
|
33
|
+
print(f'- Performance: {vectors.shape[0]/dt:.2f} vectors/s')
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Showcases how to use Numba to JIT-compile similarity measures for USearch.
|
|
37
|
+
# https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#c-callbacks
|
|
38
|
+
|
|
39
|
+
signature_f32 = types.float32(
|
|
40
|
+
types.CPointer(types.float32),
|
|
41
|
+
types.CPointer(types.float32),
|
|
42
|
+
types.size_t, types.size_t)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@cfunc(signature_f32)
|
|
46
|
+
def inner_product_f32(a, b, n, m):
|
|
47
|
+
a_array = carray(a, n)
|
|
48
|
+
b_array = carray(b, n)
|
|
49
|
+
c = types.float32(0)
|
|
50
|
+
for i in range(n):
|
|
51
|
+
c += a_array[i] * b_array[i]
|
|
52
|
+
return types.float32(1 - c)
|
|
31
53
|
|
|
32
54
|
|
|
33
55
|
def main(
|
|
@@ -44,31 +66,51 @@ def main(
|
|
|
44
66
|
neighbors_mat = load_matrix(neighbors)
|
|
45
67
|
dim = vectors_mat.shape[1]
|
|
46
68
|
|
|
47
|
-
|
|
48
|
-
index =
|
|
69
|
+
print('USearch: HNSW')
|
|
70
|
+
index = Index(
|
|
71
|
+
ndim=dim,
|
|
72
|
+
expansion_add=expansion_add,
|
|
73
|
+
expansion_search=expansion_search,
|
|
74
|
+
connectivity=connectivity,
|
|
75
|
+
)
|
|
76
|
+
bench_usearch(index, vectors_mat, queries_mat, neighbors_mat)
|
|
77
|
+
|
|
78
|
+
print('USearch: HNSW + Numba JIT')
|
|
79
|
+
index = Index(
|
|
80
|
+
ndim=dim,
|
|
81
|
+
expansion_add=expansion_add,
|
|
82
|
+
expansion_search=expansion_search,
|
|
83
|
+
connectivity=connectivity,
|
|
84
|
+
metric_pointer=inner_product_f32.address,
|
|
85
|
+
)
|
|
86
|
+
bench_usearch(index, vectors_mat, queries_mat, neighbors_mat)
|
|
87
|
+
|
|
88
|
+
print('USearch: HNSW + Numba JIT + Half Precision')
|
|
89
|
+
index = Index(
|
|
90
|
+
ndim=dim,
|
|
91
|
+
dtype='f16',
|
|
92
|
+
expansion_add=expansion_add,
|
|
93
|
+
expansion_search=expansion_search,
|
|
94
|
+
connectivity=connectivity,
|
|
95
|
+
)
|
|
96
|
+
bench_usearch(index, vectors_mat, queries_mat, neighbors_mat)
|
|
97
|
+
|
|
98
|
+
print('FAISS: HNSW')
|
|
99
|
+
index = IndexHNSWFlat(dim, connectivity)
|
|
49
100
|
index.hnsw.efSearch = expansion_search
|
|
50
101
|
index.hnsw.efConstruction = expansion_add
|
|
51
102
|
bench_faiss(index, vectors_mat, queries_mat, neighbors_mat)
|
|
52
103
|
|
|
53
|
-
|
|
104
|
+
print('FAISS: HNSW + IVFPQ')
|
|
54
105
|
nlist = 10000 # Number of inverted lists (number of partitions or cells).
|
|
55
106
|
nsegment = 16 # Number of segments for PQ (number of subquantizers).
|
|
56
107
|
nbit = 8 # Number of bits to encode each segment.
|
|
57
|
-
coarse_quantizer =
|
|
58
|
-
index =
|
|
108
|
+
coarse_quantizer = IndexHNSWFlat(dim, connectivity)
|
|
109
|
+
index = IndexIVFPQ(coarse_quantizer, dim, nlist, nsegment, nbit)
|
|
59
110
|
index.train(vectors_mat)
|
|
60
111
|
index.nprobe = 10
|
|
61
112
|
bench_faiss(index, vectors_mat, queries_mat, neighbors_mat)
|
|
62
113
|
|
|
63
|
-
# Test USearch `Index`
|
|
64
|
-
index = usearch.Index(
|
|
65
|
-
ndim=dim,
|
|
66
|
-
expansion_add=expansion_add,
|
|
67
|
-
expansion_search=expansion_search,
|
|
68
|
-
connectivity=connectivity,
|
|
69
|
-
)
|
|
70
|
-
bench_usearch(index, vectors_mat, queries_mat, neighbors_mat)
|
|
71
|
-
|
|
72
114
|
|
|
73
115
|
if __name__ == '__main__':
|
|
74
116
|
fire.Fire(main)
|
package/python/lib.cpp
CHANGED
|
@@ -59,6 +59,25 @@ struct hash_index_w_meta_t : public hash_index_t {
|
|
|
59
59
|
span_gt<hash_word_t const> buffer() const noexcept { return {buffer_.data(), buffer_.size()}; }
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
+
template <typename scalar_at> punned_stateful_metric_t udf(std::size_t metric_uintptr) {
|
|
63
|
+
return [metric_uintptr](byte_t const* a_bytes, byte_t const* b_bytes, std::size_t n, std::size_t m) -> distance_t {
|
|
64
|
+
using metric_t = punned_distance_t (*)(scalar_at const*, scalar_at const*, std::size_t, std::size_t);
|
|
65
|
+
metric_t metric_ptr = reinterpret_cast<metric_t>(metric_uintptr);
|
|
66
|
+
scalar_at const* a = reinterpret_cast<scalar_at const*>(a_bytes);
|
|
67
|
+
scalar_at const* b = reinterpret_cast<scalar_at const*>(b_bytes);
|
|
68
|
+
return metric_ptr(a, b, n / sizeof(scalar_at), m / sizeof(scalar_at));
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
punned_stateful_metric_t udf(std::size_t metric_uintptr, accuracy_t accuracy) {
|
|
73
|
+
switch (accuracy) {
|
|
74
|
+
case accuracy_t::f8_k: return udf<f8_bits_t>(metric_uintptr);
|
|
75
|
+
case accuracy_t::f16_k: return udf<f16_bits_t>(metric_uintptr);
|
|
76
|
+
case accuracy_t::f32_k: return udf<f32_t>(metric_uintptr);
|
|
77
|
+
case accuracy_t::f64_k: return udf<f64_t>(metric_uintptr);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
62
81
|
static native_index_t make_index( //
|
|
63
82
|
std::size_t dimensions, //
|
|
64
83
|
std::size_t capacity, //
|
|
@@ -79,9 +98,8 @@ static native_index_t make_index( //
|
|
|
79
98
|
config.max_threads_search = std::thread::hardware_concurrency();
|
|
80
99
|
|
|
81
100
|
accuracy_t accuracy = accuracy_from_name(scalar_type.c_str(), scalar_type.size());
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return native_index_t::udf(dimensions, metric_ptr, accuracy, config);
|
|
101
|
+
if (metric_uintptr)
|
|
102
|
+
return native_index_t::udf(dimensions, udf(metric_uintptr, accuracy), accuracy, config);
|
|
85
103
|
else
|
|
86
104
|
return index_from_name<native_index_t>(metric.c_str(), metric.size(), dimensions, accuracy, config);
|
|
87
105
|
}
|
|
@@ -137,7 +155,7 @@ static void add_one_to_index(native_index_t& index, label_t label, py::buffer ve
|
|
|
137
155
|
|
|
138
156
|
// https://docs.python.org/3/library/struct.html#format-characters
|
|
139
157
|
if (vector_info.format == "e")
|
|
140
|
-
index.add(label, reinterpret_cast<
|
|
158
|
+
index.add(label, reinterpret_cast<f16_bits_t const*>(vector_data), 0, copy);
|
|
141
159
|
else if (vector_info.format == "f")
|
|
142
160
|
index.add(label, reinterpret_cast<float const*>(vector_data), 0, copy);
|
|
143
161
|
else if (vector_info.format == "d")
|
|
@@ -146,7 +164,9 @@ static void add_one_to_index(native_index_t& index, label_t label, py::buffer ve
|
|
|
146
164
|
throw std::invalid_argument("Incompatible scalars in the vector!");
|
|
147
165
|
}
|
|
148
166
|
|
|
149
|
-
static void add_many_to_index(
|
|
167
|
+
static void add_many_to_index( //
|
|
168
|
+
native_index_t& index, py::buffer labels, py::buffer vectors, //
|
|
169
|
+
bool copy, std::size_t threads = 0) {
|
|
150
170
|
|
|
151
171
|
py::buffer_info labels_info = labels.request();
|
|
152
172
|
py::buffer_info vectors_info = vectors.request();
|
|
@@ -179,8 +199,8 @@ static void add_many_to_index(native_index_t& index, py::buffer labels, py::buff
|
|
|
179
199
|
if (vectors_info.format == "e")
|
|
180
200
|
multithreaded(index.concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
181
201
|
label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
|
|
182
|
-
|
|
183
|
-
reinterpret_cast<
|
|
202
|
+
f16_bits_t const* vector =
|
|
203
|
+
reinterpret_cast<f16_bits_t const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
184
204
|
index.add(label, vector, thread_idx, copy);
|
|
185
205
|
});
|
|
186
206
|
else if (vectors_info.format == "f")
|
|
@@ -216,7 +236,7 @@ static py::tuple search_one_in_index(native_index_t& index, py::buffer vector, s
|
|
|
216
236
|
// https://docs.python.org/3/library/struct.html#format-characters
|
|
217
237
|
if (vector_info.format == "e")
|
|
218
238
|
count = index.search( //
|
|
219
|
-
reinterpret_cast<
|
|
239
|
+
reinterpret_cast<f16_bits_t const*>(vector_data), wanted, &labels_py1d(0), &distances_py1d(0), 0);
|
|
220
240
|
else if (vector_info.format == "f")
|
|
221
241
|
count = index.search( //
|
|
222
242
|
reinterpret_cast<float const*>(vector_data), wanted, &labels_py1d(0), &distances_py1d(0), 0);
|
|
@@ -272,7 +292,7 @@ static py::tuple search_many_in_index(native_index_t& index, py::buffer vectors,
|
|
|
272
292
|
// https://docs.python.org/3/library/struct.html#format-characters
|
|
273
293
|
if (vectors_info.format == "e")
|
|
274
294
|
multithreaded(index.concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
275
|
-
|
|
295
|
+
f16_bits_t const* vector = (f16_bits_t const*)(vectors_data + task_idx * vectors_info.strides[0]);
|
|
276
296
|
counts_py1d(task_idx) = static_cast<Py_ssize_t>(
|
|
277
297
|
index.search(vector, wanted, &labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0), thread_idx));
|
|
278
298
|
});
|
|
@@ -371,7 +391,7 @@ void hash_buffer(hash_index_w_meta_t& index, py::buffer vector) {
|
|
|
371
391
|
throw std::invalid_argument("Array elements must be 16, 32, or 64 bit hashable integers!");
|
|
372
392
|
}
|
|
373
393
|
|
|
374
|
-
PYBIND11_MODULE(
|
|
394
|
+
PYBIND11_MODULE(index, m) {
|
|
375
395
|
m.doc() = "Unum USearch Python bindings";
|
|
376
396
|
|
|
377
397
|
auto i = py::class_<native_index_t>(m, "Index");
|
|
@@ -393,7 +413,8 @@ PYBIND11_MODULE(usearch, m) {
|
|
|
393
413
|
py::arg("labels"), //
|
|
394
414
|
py::arg("vectors"), //
|
|
395
415
|
py::kw_only(), //
|
|
396
|
-
py::arg("copy") = true
|
|
416
|
+
py::arg("copy") = true, //
|
|
417
|
+
py::arg("threads") = 0 //
|
|
397
418
|
);
|
|
398
419
|
|
|
399
420
|
i.def( //
|
|
@@ -415,6 +436,8 @@ PYBIND11_MODULE(usearch, m) {
|
|
|
415
436
|
i.def_property_readonly("ndim", &native_index_t::dimensions);
|
|
416
437
|
i.def_property_readonly("connectivity", &native_index_t::connectivity);
|
|
417
438
|
i.def_property_readonly("capacity", &native_index_t::capacity);
|
|
439
|
+
i.def_property_readonly( //
|
|
440
|
+
"dtype", [](native_index_t const& index) -> std::string { return accuracy_name(index.accuracy()); });
|
|
418
441
|
|
|
419
442
|
i.def("save", &save_index<native_index_t>, py::arg("path"));
|
|
420
443
|
i.def("load", &load_index<native_index_t>, py::arg("path"));
|
package/python/test.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
3
|
+
from usearch.index import Index, SetsIndex
|
|
5
4
|
from numba import cfunc, types, carray
|
|
6
5
|
|
|
7
6
|
# Showcases how to use Numba to JIT-compile similarity measures for USearch.
|
|
@@ -29,13 +28,13 @@ vectors = np.random.uniform(
|
|
|
29
28
|
0, 0.3, (count_vectors, count_dimensions)).astype(np.float32)
|
|
30
29
|
labels = np.array(range(count_vectors), dtype=np.longlong)
|
|
31
30
|
|
|
32
|
-
index_udf =
|
|
31
|
+
index_udf = Index(ndim=96, metric_pointer=python_dot.address)
|
|
33
32
|
index_udf.add(labels, vectors, copy=True)
|
|
34
33
|
results = index_udf.search(vectors, 10)
|
|
35
34
|
print('found', results[0].shape, results[1].shape, results[2].shape)
|
|
36
35
|
|
|
37
36
|
|
|
38
|
-
index =
|
|
37
|
+
index = Index(ndim=96)
|
|
39
38
|
index.add(labels, vectors, copy=True)
|
|
40
39
|
assert index.ndim == count_dimensions
|
|
41
40
|
assert index.capacity >= count_vectors
|
|
@@ -45,7 +44,7 @@ results = index.search(vectors, 10)
|
|
|
45
44
|
print('found', results[0].shape, results[1].shape, results[2].shape)
|
|
46
45
|
|
|
47
46
|
|
|
48
|
-
index_sets =
|
|
47
|
+
index_sets = SetsIndex()
|
|
49
48
|
index_sets.add(10, np.array([10, 12, 15], dtype=np.uint32))
|
|
50
49
|
index_sets.add(11, np.array([11, 12, 15, 16], dtype=np.uint32))
|
|
51
50
|
results = index_sets.search(np.array([12, 15], dtype=np.uint32), 20)
|
|
File without changes
|
package/rust/lib.cpp
CHANGED
|
@@ -24,9 +24,20 @@ Matches Index::search_in_thread(rust::Slice<float const> vector, size_t count, s
|
|
|
24
24
|
return results;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
void Index::add(uint32_t label, rust::Slice<float const> vector) const {
|
|
27
|
+
void Index::add(uint32_t label, rust::Slice<float const> vector) const {
|
|
28
|
+
index_->add(label, vector.data()); //
|
|
29
|
+
}
|
|
30
|
+
|
|
28
31
|
Matches Index::search(rust::Slice<float const> vector, size_t count) const {
|
|
29
|
-
|
|
32
|
+
Matches results;
|
|
33
|
+
results.labels.reserve(count);
|
|
34
|
+
results.distances.reserve(count);
|
|
35
|
+
for (size_t i = 0; i != count; ++i)
|
|
36
|
+
results.labels.push_back(0), results.distances.push_back(0);
|
|
37
|
+
results.count = index_->search(vector.data(), count, results.labels.data(), results.distances.data());
|
|
38
|
+
results.labels.truncate(results.count);
|
|
39
|
+
results.distances.truncate(results.count);
|
|
40
|
+
return results;
|
|
30
41
|
}
|
|
31
42
|
|
|
32
43
|
void Index::reserve(size_t capacity) const { index_->reserve(capacity); }
|
package/setup.py
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
|
-
from setuptools import setup
|
|
3
|
+
from setuptools import setup
|
|
4
4
|
from pybind11.setup_helpers import Pybind11Extension
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
compile_args = ['-std=c++11', '-O3']
|
|
8
|
+
link_args = []
|
|
9
|
+
|
|
10
|
+
if sys.platform == 'linux':
|
|
11
|
+
compile_args.append('-fopenmp')
|
|
12
|
+
link_args.append('-lgomp')
|
|
8
13
|
|
|
9
14
|
if sys.platform == 'darwin':
|
|
10
|
-
|
|
15
|
+
# MacOS 10.15 orhigher is needed for `aligned_alloc` support.
|
|
16
|
+
# https://github.com/unum-cloud/usearch/actions/runs/4975434891/jobs/8902603392
|
|
17
|
+
compile_args.append('-mmacosx-version-min=10.15')
|
|
18
|
+
|
|
19
|
+
if sys.platform == 'win32':
|
|
20
|
+
compile_args = ['/std:c++14', '/O2']
|
|
21
|
+
|
|
11
22
|
|
|
12
23
|
ext_modules = [
|
|
13
24
|
Pybind11Extension(
|
|
14
|
-
'usearch',
|
|
25
|
+
'usearch.index',
|
|
15
26
|
['python/lib.cpp'],
|
|
16
27
|
extra_compile_args=compile_args,
|
|
28
|
+
extra_link_args=link_args,
|
|
17
29
|
),
|
|
18
30
|
]
|
|
19
31
|
|
|
@@ -30,7 +42,8 @@ setup(
|
|
|
30
42
|
|
|
31
43
|
name=__lib_name__,
|
|
32
44
|
version=__version__,
|
|
33
|
-
packages=
|
|
45
|
+
packages=['usearch'],
|
|
46
|
+
package_dir={'usearch': 'python/usearch'},
|
|
34
47
|
|
|
35
48
|
description='Smaller & Faster Single-File Vector Search Engine from Unum',
|
|
36
49
|
long_description=long_description,
|
|
@@ -255,6 +255,21 @@ inline static simsimd_f32_t simsimd_dot_f32x4avx2(simsimd_f32_t const* a, simsim
|
|
|
255
255
|
#endif
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
+
inline static simsimd_f32_t simsimd_dot_f16x16avx512(simsimd_f16_t const* a, simsimd_f16_t const* b, size_t d) {
|
|
259
|
+
#if defined(__AVX512F__)
|
|
260
|
+
__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);
|
|
267
|
+
#else
|
|
268
|
+
(void)a, (void)b, (void)d;
|
|
269
|
+
return 0;
|
|
270
|
+
#endif
|
|
271
|
+
}
|
|
272
|
+
|
|
258
273
|
inline static int32_t simsimd_dot_i8x16avx2(int8_t const* a, int8_t const* b, size_t d) {
|
|
259
274
|
#if defined(__AVX2__)
|
|
260
275
|
__m256i ab_vec = _mm256_set1_epi32(0);
|