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/javascript/lib.cpp
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
#define NAPI_CPP_EXCEPTIONS
|
|
16
16
|
#endif
|
|
17
17
|
|
|
18
|
+
#include <new> // `std::bad_alloc`
|
|
19
|
+
|
|
18
20
|
#include <napi.h>
|
|
19
21
|
#include <node_api.h>
|
|
20
22
|
|
|
@@ -38,6 +40,10 @@ class Index : public Napi::ObjectWrap<Index> {
|
|
|
38
40
|
Napi::Value GetCapacity(Napi::CallbackInfo const& ctx);
|
|
39
41
|
Napi::Value GetConnectivity(Napi::CallbackInfo const& ctx);
|
|
40
42
|
|
|
43
|
+
void Save(Napi::CallbackInfo const& ctx);
|
|
44
|
+
void Load(Napi::CallbackInfo const& ctx);
|
|
45
|
+
void View(Napi::CallbackInfo const& ctx);
|
|
46
|
+
|
|
41
47
|
void Add(Napi::CallbackInfo const& ctx);
|
|
42
48
|
Napi::Value Search(Napi::CallbackInfo const& ctx);
|
|
43
49
|
|
|
@@ -54,6 +60,9 @@ Napi::Object Index::Init(Napi::Env env, Napi::Object exports) {
|
|
|
54
60
|
InstanceMethod("connectivity", &Index::GetConnectivity),
|
|
55
61
|
InstanceMethod("add", &Index::Add),
|
|
56
62
|
InstanceMethod("search", &Index::Search),
|
|
63
|
+
InstanceMethod("save", &Index::Save),
|
|
64
|
+
InstanceMethod("load", &Index::Load),
|
|
65
|
+
InstanceMethod("view", &Index::View),
|
|
57
66
|
});
|
|
58
67
|
|
|
59
68
|
Napi::FunctionReference* constructor = new Napi::FunctionReference();
|
|
@@ -150,6 +159,57 @@ Napi::Value Index::GetCapacity(Napi::CallbackInfo const& ctx) {
|
|
|
150
159
|
return Napi::Number::New(ctx.Env(), native_->capacity());
|
|
151
160
|
}
|
|
152
161
|
|
|
162
|
+
void Index::Save(Napi::CallbackInfo const& ctx) {
|
|
163
|
+
Napi::Env env = ctx.Env();
|
|
164
|
+
|
|
165
|
+
int length = ctx.Length();
|
|
166
|
+
if (length == 0 || !ctx[0].IsString()) {
|
|
167
|
+
Napi::TypeError::New(env, "Function expects a string path argument").ThrowAsJavaScriptException();
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
std::string path = ctx[0].As<Napi::String>();
|
|
173
|
+
native_->save(path.c_str());
|
|
174
|
+
} catch (...) {
|
|
175
|
+
Napi::TypeError::New(env, "Serialization failed").ThrowAsJavaScriptException();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
void Index::Load(Napi::CallbackInfo const& ctx) {
|
|
180
|
+
Napi::Env env = ctx.Env();
|
|
181
|
+
|
|
182
|
+
int length = ctx.Length();
|
|
183
|
+
if (length == 0 || !ctx[0].IsString()) {
|
|
184
|
+
Napi::TypeError::New(env, "Function expects a string path argument").ThrowAsJavaScriptException();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
std::string path = ctx[0].As<Napi::String>();
|
|
190
|
+
native_->load(path.c_str());
|
|
191
|
+
} catch (...) {
|
|
192
|
+
Napi::TypeError::New(env, "Loading failed").ThrowAsJavaScriptException();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
void Index::View(Napi::CallbackInfo const& ctx) {
|
|
197
|
+
Napi::Env env = ctx.Env();
|
|
198
|
+
|
|
199
|
+
int length = ctx.Length();
|
|
200
|
+
if (length == 0 || !ctx[0].IsString()) {
|
|
201
|
+
Napi::TypeError::New(env, "Function expects a string path argument").ThrowAsJavaScriptException();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
std::string path = ctx[0].As<Napi::String>();
|
|
207
|
+
native_->view(path.c_str());
|
|
208
|
+
} catch (...) {
|
|
209
|
+
Napi::TypeError::New(env, "Memory-mapping failed").ThrowAsJavaScriptException();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
153
213
|
void Index::Add(Napi::CallbackInfo const& ctx) {
|
|
154
214
|
Napi::Env env = ctx.Env();
|
|
155
215
|
if (ctx.Length() < 2 || !ctx[0].IsNumber() || !ctx[1].IsTypedArray())
|
|
@@ -166,7 +226,14 @@ void Index::Add(Napi::CallbackInfo const& ctx) {
|
|
|
166
226
|
|
|
167
227
|
if (native_->size() + 1 >= native_->capacity())
|
|
168
228
|
native_->reserve(ceil2(native_->size() + 1));
|
|
169
|
-
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
native_->add(label, vector);
|
|
232
|
+
} catch (std::bad_alloc const&) {
|
|
233
|
+
Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
|
|
234
|
+
} catch (...) {
|
|
235
|
+
Napi::TypeError::New(env, "Insertion failed").ThrowAsJavaScriptException();
|
|
236
|
+
}
|
|
170
237
|
}
|
|
171
238
|
|
|
172
239
|
Napi::Value Index::Search(Napi::CallbackInfo const& ctx) {
|
|
@@ -190,13 +257,20 @@ Napi::Value Index::Search(Napi::CallbackInfo const& ctx) {
|
|
|
190
257
|
|
|
191
258
|
Napi::Uint32Array matches_js = Napi::Uint32Array::New(env, wanted);
|
|
192
259
|
Napi::Float32Array distances_js = Napi::Float32Array::New(env, wanted);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
260
|
+
try {
|
|
261
|
+
std::size_t count = native_->search(vector, wanted, matches_js.Data(), distances_js.Data());
|
|
262
|
+
Napi::Object result_js = Napi::Object::New(env);
|
|
263
|
+
result_js.Set("labels", matches_js);
|
|
264
|
+
result_js.Set("distances", distances_js);
|
|
265
|
+
result_js.Set("count", Napi::Number::New(env, count));
|
|
266
|
+
return result_js;
|
|
267
|
+
} catch (std::bad_alloc const&) {
|
|
268
|
+
Napi::TypeError::New(env, "Out of memory").ThrowAsJavaScriptException();
|
|
269
|
+
return {};
|
|
270
|
+
} catch (...) {
|
|
271
|
+
Napi::TypeError::New(env, "Search failed").ThrowAsJavaScriptException();
|
|
272
|
+
return {};
|
|
273
|
+
}
|
|
200
274
|
}
|
|
201
275
|
|
|
202
276
|
Napi::Object InitAll(Napi::Env env, Napi::Object exports) { return Index::Init(env, exports); }
|
package/objc/USearchObjective.mm
CHANGED
|
@@ -107,14 +107,14 @@ using shared_index_t = std::shared_ptr<native_index_t>;
|
|
|
107
107
|
|
|
108
108
|
- (void)addImprecise:(UInt32)label
|
|
109
109
|
vector:(void const * _Nonnull)vector {
|
|
110
|
-
_native->add(label, (
|
|
110
|
+
_native->add(label, (f16_bits_t const *)vector);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
- (UInt32)searchImprecise:(void const* _Nonnull)vector
|
|
114
114
|
count:(UInt32)wanted
|
|
115
115
|
labels:(UInt32* _Nullable)labels
|
|
116
116
|
distances:(Float32* _Nullable)distances {
|
|
117
|
-
std::size_t found = _native->search((
|
|
117
|
+
std::size_t found = _native->search((f16_bits_t const *)vector, static_cast<std::size_t>(wanted), labels, distances);
|
|
118
118
|
return static_cast<UInt32>(found);
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -10,6 +10,7 @@ typedef NS_ENUM(NSUInteger, Quantization) {
|
|
|
10
10
|
kDoublePrecision
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
API_AVAILABLE(ios(13.0), macos(10.15), tvos(13.0), watchos(6.0))
|
|
13
14
|
@interface Index: NSObject
|
|
14
15
|
|
|
15
16
|
@property (readonly) UInt32 dimensions;
|
|
@@ -33,9 +34,9 @@ NS_UNAVAILABLE;
|
|
|
33
34
|
NS_SWIFT_NAME(addSingle(label:vector:));
|
|
34
35
|
|
|
35
36
|
- (UInt32)searchSingle:(Float32 const* _Nonnull)vector
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
count:(UInt32)count
|
|
38
|
+
labels:(UInt32* _Nullable)labels
|
|
39
|
+
distances:(Float32* _Nullable)distances
|
|
39
40
|
NS_SWIFT_NAME(searchSingle(vector:count:labels:distances:));
|
|
40
41
|
|
|
41
42
|
- (void)addPrecise:(UInt32)label
|
|
@@ -43,9 +44,9 @@ NS_SWIFT_NAME(searchSingle(vector:count:labels:distances:));
|
|
|
43
44
|
NS_SWIFT_NAME(addPrecise(label:vector:));
|
|
44
45
|
|
|
45
46
|
- (UInt32)searchPrecise:(Float64 const* _Nonnull)vector
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
count:(UInt32)count
|
|
48
|
+
labels:(UInt32* _Nullable)labels
|
|
49
|
+
distances:(Float32* _Nullable)distances
|
|
49
50
|
NS_SWIFT_NAME(searchPrecise(vector:count:labels:distances:));
|
|
50
51
|
|
|
51
52
|
- (void)addImprecise:(UInt32)label
|
|
@@ -53,9 +54,9 @@ NS_SWIFT_NAME(searchPrecise(vector:count:labels:distances:));
|
|
|
53
54
|
NS_SWIFT_NAME(addImprecise(label:vector:));
|
|
54
55
|
|
|
55
56
|
- (UInt32)searchImprecise:(void const* _Nonnull)vector
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
count:(UInt32)count
|
|
58
|
+
labels:(UInt32* _Nullable)labels
|
|
59
|
+
distances:(Float32* _Nullable)distances
|
|
59
60
|
NS_SWIFT_NAME(searchImprecise(vector:count:labels:distances:));
|
|
60
61
|
|
|
61
62
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "usearch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Smaller & Faster Single-File Vector Search Engine from Unum",
|
|
5
5
|
"author": "Ashot Vardanian",
|
|
6
6
|
"license": "Apache 2.0",
|
|
@@ -18,5 +18,11 @@
|
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
20
|
"test": "node javascript/test.js"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@semantic-release/exec": "^6.0.3",
|
|
24
|
+
"@semantic-release/git": "^10.0.1",
|
|
25
|
+
"conventional-changelog-eslint": "^3.0.9",
|
|
26
|
+
"semantic-release": "^21.0.2"
|
|
21
27
|
}
|
|
22
28
|
}
|
package/python/bench.py
CHANGED
|
@@ -12,30 +12,50 @@ from usearch.io import load_matrix
|
|
|
12
12
|
|
|
13
13
|
def measure(f) -> float:
|
|
14
14
|
a = time.time_ns()
|
|
15
|
-
f()
|
|
15
|
+
result = f()
|
|
16
16
|
b = time.time_ns()
|
|
17
17
|
c = b - a
|
|
18
18
|
secs = c / (10 ** 9)
|
|
19
19
|
print(f'- Took: {secs:.2f} seconds')
|
|
20
|
-
return secs
|
|
20
|
+
return secs, result
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def bench_faiss(index, vectors: np.array, queries: np.array, neighbors: np.array):
|
|
24
|
-
dt = measure(lambda: index.add(vectors))
|
|
25
|
-
print(f'- Performance: {vectors.shape[0]/dt:.2f}
|
|
24
|
+
dt, _ = measure(lambda: index.add(vectors))
|
|
25
|
+
print(f'- Performance: {vectors.shape[0]/dt:.2f} insertions/s')
|
|
26
|
+
|
|
27
|
+
dt, results = measure(lambda: index.search(queries, neighbors.shape[1]))
|
|
28
|
+
print(f'- Performance: {queries.shape[0]/dt:.2f} queries/s')
|
|
29
|
+
|
|
30
|
+
recall_at_one = 0.0
|
|
31
|
+
matches = results[1]
|
|
32
|
+
for i in range(queries.shape[0]):
|
|
33
|
+
recall_at_one += neighbors[i, 0] in matches[i, :]
|
|
34
|
+
recall_at_one /= queries.shape[0]
|
|
35
|
+
print(f'- Recall@1: {recall_at_one * 100:.2f} %')
|
|
26
36
|
|
|
27
37
|
|
|
28
38
|
def bench_usearch(index, vectors: np.array, queries: np.array, neighbors: np.array):
|
|
29
39
|
labels = np.arange(vectors.shape[0], dtype=np.longlong)
|
|
30
40
|
assert len(index) == 0
|
|
31
|
-
dt = measure(lambda: index.add(labels, vectors))
|
|
41
|
+
dt, _ = measure(lambda: index.add(labels, vectors))
|
|
32
42
|
assert len(index) == vectors.shape[0]
|
|
33
|
-
print(f'- Performance: {vectors.shape[0]/dt:.2f}
|
|
43
|
+
print(f'- Performance: {vectors.shape[0]/dt:.2f} insertions/s')
|
|
44
|
+
|
|
45
|
+
dt, results = measure(lambda: index.search(queries, neighbors.shape[1]))
|
|
46
|
+
print(f'- Performance: {queries.shape[0]/dt:.2f} queries/s')
|
|
34
47
|
|
|
48
|
+
recall_at_one = 0.0
|
|
49
|
+
matches = results[0]
|
|
50
|
+
for i in range(queries.shape[0]):
|
|
51
|
+
recall_at_one += neighbors[i, 0] in matches[i, :]
|
|
52
|
+
recall_at_one /= queries.shape[0]
|
|
53
|
+
print(f'- Recall@1: {recall_at_one * 100:.2f} %')
|
|
35
54
|
|
|
36
55
|
# Showcases how to use Numba to JIT-compile similarity measures for USearch.
|
|
37
56
|
# https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#c-callbacks
|
|
38
57
|
|
|
58
|
+
|
|
39
59
|
signature_f32 = types.float32(
|
|
40
60
|
types.CPointer(types.float32),
|
|
41
61
|
types.CPointer(types.float32),
|
|
@@ -85,7 +105,7 @@ def main(
|
|
|
85
105
|
)
|
|
86
106
|
bench_usearch(index, vectors_mat, queries_mat, neighbors_mat)
|
|
87
107
|
|
|
88
|
-
print('USearch: HNSW +
|
|
108
|
+
print('USearch: HNSW + Half Precision')
|
|
89
109
|
index = Index(
|
|
90
110
|
ndim=dim,
|
|
91
111
|
dtype='f16',
|
|
@@ -95,6 +115,16 @@ def main(
|
|
|
95
115
|
)
|
|
96
116
|
bench_usearch(index, vectors_mat, queries_mat, neighbors_mat)
|
|
97
117
|
|
|
118
|
+
print('USearch: HNSW + Quarter Precision')
|
|
119
|
+
index = Index(
|
|
120
|
+
ndim=dim,
|
|
121
|
+
dtype='f8',
|
|
122
|
+
expansion_add=expansion_add,
|
|
123
|
+
expansion_search=expansion_search,
|
|
124
|
+
connectivity=connectivity,
|
|
125
|
+
)
|
|
126
|
+
bench_usearch(index, vectors_mat, queries_mat, neighbors_mat)
|
|
127
|
+
|
|
98
128
|
print('FAISS: HNSW')
|
|
99
129
|
index = IndexHNSWFlat(dim, connectivity)
|
|
100
130
|
index.hnsw.efSearch = expansion_search
|
package/python/lib.cpp
CHANGED
|
@@ -145,21 +145,24 @@ static void add_one_to_index(native_index_t& index, label_t label, py::buffer ve
|
|
|
145
145
|
if (vector_info.ndim != 1)
|
|
146
146
|
throw std::invalid_argument("Expects a vector, not a higher-rank tensor!");
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
Py_ssize_t vector_dimensions = vector_info.shape[0];
|
|
149
149
|
char const* vector_data = reinterpret_cast<char const*>(vector_info.ptr);
|
|
150
|
-
if (vector_dimensions != static_cast<
|
|
150
|
+
if (vector_dimensions != static_cast<Py_ssize_t>(index.dimensions()))
|
|
151
151
|
throw std::invalid_argument("The number of vector dimensions doesn't match!");
|
|
152
152
|
|
|
153
153
|
if (index.size() + 1 >= index.capacity())
|
|
154
154
|
index.reserve(ceil2(index.size() + 1));
|
|
155
155
|
|
|
156
|
+
add_config_t config;
|
|
157
|
+
config.store_vector = copy;
|
|
158
|
+
|
|
156
159
|
// https://docs.python.org/3/library/struct.html#format-characters
|
|
157
160
|
if (vector_info.format == "e")
|
|
158
|
-
index.add(label, reinterpret_cast<f16_bits_t const*>(vector_data),
|
|
161
|
+
index.add(label, reinterpret_cast<f16_bits_t const*>(vector_data), config);
|
|
159
162
|
else if (vector_info.format == "f")
|
|
160
|
-
index.add(label, reinterpret_cast<float const*>(vector_data),
|
|
163
|
+
index.add(label, reinterpret_cast<float const*>(vector_data), config);
|
|
161
164
|
else if (vector_info.format == "d")
|
|
162
|
-
index.add(label, reinterpret_cast<double const*>(vector_data),
|
|
165
|
+
index.add(label, reinterpret_cast<double const*>(vector_data), config);
|
|
163
166
|
else
|
|
164
167
|
throw std::invalid_argument("Incompatible scalars in the vector!");
|
|
165
168
|
}
|
|
@@ -180,10 +183,10 @@ static void add_many_to_index( //
|
|
|
180
183
|
if (vectors_info.ndim != 2)
|
|
181
184
|
throw std::invalid_argument("Expects a matrix of vectors to add!");
|
|
182
185
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (vectors_dimensions != static_cast<
|
|
186
|
+
Py_ssize_t labels_count = labels_info.shape[0];
|
|
187
|
+
Py_ssize_t vectors_count = vectors_info.shape[0];
|
|
188
|
+
Py_ssize_t vectors_dimensions = vectors_info.shape[1];
|
|
189
|
+
if (vectors_dimensions != static_cast<Py_ssize_t>(index.dimensions()))
|
|
187
190
|
throw std::invalid_argument("The number of vector dimensions doesn't match!");
|
|
188
191
|
|
|
189
192
|
if (labels_count != vectors_count)
|
|
@@ -198,56 +201,68 @@ static void add_many_to_index( //
|
|
|
198
201
|
// https://docs.python.org/3/library/struct.html#format-characters
|
|
199
202
|
if (vectors_info.format == "e")
|
|
200
203
|
multithreaded(index.concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
204
|
+
add_config_t config;
|
|
205
|
+
config.store_vector = copy;
|
|
206
|
+
config.thread = thread_idx;
|
|
201
207
|
label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
|
|
202
208
|
f16_bits_t const* vector =
|
|
203
209
|
reinterpret_cast<f16_bits_t const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
204
|
-
index.add(label, vector,
|
|
210
|
+
index.add(label, vector, config);
|
|
205
211
|
});
|
|
206
212
|
else if (vectors_info.format == "f")
|
|
207
213
|
multithreaded(index.concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
214
|
+
add_config_t config;
|
|
215
|
+
config.store_vector = copy;
|
|
216
|
+
config.thread = thread_idx;
|
|
208
217
|
label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
|
|
209
218
|
float const* vector = reinterpret_cast<float const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
210
|
-
index.add(label, vector,
|
|
219
|
+
index.add(label, vector, config);
|
|
211
220
|
});
|
|
212
221
|
else if (vectors_info.format == "d")
|
|
213
222
|
multithreaded(index.concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
223
|
+
add_config_t config;
|
|
224
|
+
config.store_vector = copy;
|
|
225
|
+
config.thread = thread_idx;
|
|
214
226
|
label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
|
|
215
227
|
double const* vector = reinterpret_cast<double const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
216
|
-
index.add(label, vector,
|
|
228
|
+
index.add(label, vector, config);
|
|
217
229
|
});
|
|
218
230
|
else
|
|
219
231
|
throw std::invalid_argument("Incompatible scalars in the vectors matrix!");
|
|
220
232
|
}
|
|
221
233
|
|
|
222
|
-
static py::tuple search_one_in_index(native_index_t& index, py::buffer vector, std::size_t wanted) {
|
|
234
|
+
static py::tuple search_one_in_index(native_index_t& index, py::buffer vector, std::size_t wanted, bool exact) {
|
|
223
235
|
|
|
224
236
|
py::buffer_info vector_info = vector.request();
|
|
225
|
-
|
|
237
|
+
Py_ssize_t vector_dimensions = vector_info.shape[0];
|
|
226
238
|
char const* vector_data = reinterpret_cast<char const*>(vector_info.ptr);
|
|
227
|
-
if (vector_dimensions != static_cast<
|
|
239
|
+
if (vector_dimensions != static_cast<Py_ssize_t>(index.dimensions()))
|
|
228
240
|
throw std::invalid_argument("The number of vector dimensions doesn't match!");
|
|
229
241
|
|
|
230
|
-
py::array_t<label_t> labels_py(static_cast<
|
|
231
|
-
py::array_t<distance_t> distances_py(static_cast<
|
|
242
|
+
py::array_t<label_t> labels_py(static_cast<Py_ssize_t>(wanted));
|
|
243
|
+
py::array_t<distance_t> distances_py(static_cast<Py_ssize_t>(wanted));
|
|
232
244
|
std::size_t count{};
|
|
233
245
|
auto labels_py1d = labels_py.mutable_unchecked<1>();
|
|
234
246
|
auto distances_py1d = distances_py.mutable_unchecked<1>();
|
|
235
247
|
|
|
248
|
+
search_config_t config;
|
|
249
|
+
config.exact = exact;
|
|
250
|
+
|
|
236
251
|
// https://docs.python.org/3/library/struct.html#format-characters
|
|
237
252
|
if (vector_info.format == "e")
|
|
238
253
|
count = index.search( //
|
|
239
|
-
reinterpret_cast<f16_bits_t const*>(vector_data), wanted, &labels_py1d(0), &distances_py1d(0),
|
|
254
|
+
reinterpret_cast<f16_bits_t const*>(vector_data), wanted, &labels_py1d(0), &distances_py1d(0), config);
|
|
240
255
|
else if (vector_info.format == "f")
|
|
241
256
|
count = index.search( //
|
|
242
|
-
reinterpret_cast<float const*>(vector_data), wanted, &labels_py1d(0), &distances_py1d(0),
|
|
257
|
+
reinterpret_cast<float const*>(vector_data), wanted, &labels_py1d(0), &distances_py1d(0), config);
|
|
243
258
|
else if (vector_info.format == "d")
|
|
244
259
|
count = index.search( //
|
|
245
|
-
reinterpret_cast<double const*>(vector_data), wanted, &labels_py1d(0), &distances_py1d(0),
|
|
260
|
+
reinterpret_cast<double const*>(vector_data), wanted, &labels_py1d(0), &distances_py1d(0), config);
|
|
246
261
|
else
|
|
247
262
|
throw std::invalid_argument("Incompatible scalars in the query vector!");
|
|
248
263
|
|
|
249
|
-
labels_py.resize(py_shape_t{static_cast<
|
|
250
|
-
distances_py.resize(py_shape_t{static_cast<
|
|
264
|
+
labels_py.resize(py_shape_t{static_cast<Py_ssize_t>(count)});
|
|
265
|
+
distances_py.resize(py_shape_t{static_cast<Py_ssize_t>(count)});
|
|
251
266
|
|
|
252
267
|
py::tuple results(3);
|
|
253
268
|
results[0] = labels_py;
|
|
@@ -265,25 +280,25 @@ static py::tuple search_one_in_index(native_index_t& index, py::buffer vector, s
|
|
|
265
280
|
* 2. matrix of distances,
|
|
266
281
|
* 3. array with match counts.
|
|
267
282
|
*/
|
|
268
|
-
static py::tuple search_many_in_index(native_index_t& index, py::buffer vectors, std::size_t wanted) {
|
|
283
|
+
static py::tuple search_many_in_index(native_index_t& index, py::buffer vectors, std::size_t wanted, bool exact) {
|
|
269
284
|
|
|
270
285
|
if (wanted == 0)
|
|
271
286
|
return py::tuple(3);
|
|
272
287
|
|
|
273
288
|
py::buffer_info vectors_info = vectors.request();
|
|
274
289
|
if (vectors_info.ndim == 1)
|
|
275
|
-
return search_one_in_index(index, vectors, wanted);
|
|
290
|
+
return search_one_in_index(index, vectors, wanted, exact);
|
|
276
291
|
if (vectors_info.ndim != 2)
|
|
277
292
|
throw std::invalid_argument("Expects a matrix of vectors to add!");
|
|
278
293
|
|
|
279
|
-
|
|
280
|
-
|
|
294
|
+
Py_ssize_t vectors_count = vectors_info.shape[0];
|
|
295
|
+
Py_ssize_t vectors_dimensions = vectors_info.shape[1];
|
|
281
296
|
char const* vectors_data = reinterpret_cast<char const*>(vectors_info.ptr);
|
|
282
|
-
if (vectors_dimensions != static_cast<
|
|
297
|
+
if (vectors_dimensions != static_cast<Py_ssize_t>(index.dimensions()))
|
|
283
298
|
throw std::invalid_argument("The number of vector dimensions doesn't match!");
|
|
284
299
|
|
|
285
|
-
py::array_t<label_t> labels_py({vectors_count, static_cast<
|
|
286
|
-
py::array_t<distance_t> distances_py({vectors_count, static_cast<
|
|
300
|
+
py::array_t<label_t> labels_py({vectors_count, static_cast<Py_ssize_t>(wanted)});
|
|
301
|
+
py::array_t<distance_t> distances_py({vectors_count, static_cast<Py_ssize_t>(wanted)});
|
|
287
302
|
py::array_t<Py_ssize_t> counts_py(vectors_count);
|
|
288
303
|
auto labels_py2d = labels_py.mutable_unchecked<2>();
|
|
289
304
|
auto distances_py2d = distances_py.mutable_unchecked<2>();
|
|
@@ -292,21 +307,30 @@ static py::tuple search_many_in_index(native_index_t& index, py::buffer vectors,
|
|
|
292
307
|
// https://docs.python.org/3/library/struct.html#format-characters
|
|
293
308
|
if (vectors_info.format == "e")
|
|
294
309
|
multithreaded(index.concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
310
|
+
search_config_t config;
|
|
311
|
+
config.thread = thread_idx;
|
|
312
|
+
config.exact = exact;
|
|
295
313
|
f16_bits_t const* vector = (f16_bits_t const*)(vectors_data + task_idx * vectors_info.strides[0]);
|
|
296
314
|
counts_py1d(task_idx) = static_cast<Py_ssize_t>(
|
|
297
|
-
index.search(vector, wanted, &labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0),
|
|
315
|
+
index.search(vector, wanted, &labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0), config));
|
|
298
316
|
});
|
|
299
317
|
else if (vectors_info.format == "f")
|
|
300
318
|
multithreaded(index.concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
319
|
+
search_config_t config;
|
|
320
|
+
config.thread = thread_idx;
|
|
321
|
+
config.exact = exact;
|
|
301
322
|
float const* vector = (float const*)(vectors_data + task_idx * vectors_info.strides[0]);
|
|
302
323
|
counts_py1d(task_idx) = static_cast<Py_ssize_t>(
|
|
303
|
-
index.search(vector, wanted, &labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0),
|
|
324
|
+
index.search(vector, wanted, &labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0), config));
|
|
304
325
|
});
|
|
305
326
|
else if (vectors_info.format == "d")
|
|
306
327
|
multithreaded(index.concurrency(), vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
328
|
+
search_config_t config;
|
|
329
|
+
config.thread = thread_idx;
|
|
330
|
+
config.exact = exact;
|
|
307
331
|
double const* vector = (double const*)(vectors_data + task_idx * vectors_info.strides[0]);
|
|
308
332
|
counts_py1d(task_idx) = static_cast<Py_ssize_t>(
|
|
309
|
-
index.search(vector, wanted, &labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0),
|
|
333
|
+
index.search(vector, wanted, &labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0), config));
|
|
310
334
|
});
|
|
311
335
|
else
|
|
312
336
|
throw std::invalid_argument("Incompatible scalars in the query matrix!");
|
|
@@ -364,8 +388,8 @@ inline std::uint64_t hash(std::uint64_t v) noexcept {
|
|
|
364
388
|
template <typename scalar_at>
|
|
365
389
|
inline void hash_typed_buffer(hash_index_w_meta_t& index, py::buffer_info const& vector_info) noexcept {
|
|
366
390
|
char const* vector_data = reinterpret_cast<char const*>(vector_info.ptr);
|
|
367
|
-
|
|
368
|
-
|
|
391
|
+
Py_ssize_t vector_dimensions = vector_info.shape[0];
|
|
392
|
+
Py_ssize_t vector_stride = vector_info.strides[0];
|
|
369
393
|
std::memset(index.buffer_.data(), 0, index.words_ * sizeof(hash_word_t));
|
|
370
394
|
|
|
371
395
|
for (std::size_t i = 0; i != static_cast<std::size_t>(vector_dimensions); ++i) {
|
|
@@ -428,7 +452,8 @@ PYBIND11_MODULE(index, m) {
|
|
|
428
452
|
i.def( //
|
|
429
453
|
"search", &search_many_in_index, //
|
|
430
454
|
py::arg("query"), //
|
|
431
|
-
py::arg("count") = 10
|
|
455
|
+
py::arg("count") = 10, //
|
|
456
|
+
py::arg("exact") = false //
|
|
432
457
|
);
|
|
433
458
|
|
|
434
459
|
i.def("__len__", &native_index_t::size);
|
|
@@ -463,7 +488,9 @@ PYBIND11_MODULE(index, m) {
|
|
|
463
488
|
index.reserve(ceil2(index.size() + 1));
|
|
464
489
|
auto proxy = set.unchecked<1>();
|
|
465
490
|
auto view = set_view_t{proxy.data(0), static_cast<std::size_t>(proxy.shape(0))};
|
|
466
|
-
|
|
491
|
+
add_config_t config;
|
|
492
|
+
config.store_vector = copy;
|
|
493
|
+
index.add(label, view, config);
|
|
467
494
|
}, //
|
|
468
495
|
py::arg("label"), //
|
|
469
496
|
py::arg("set"), //
|
|
@@ -477,10 +504,10 @@ PYBIND11_MODULE(index, m) {
|
|
|
477
504
|
validate_set(set);
|
|
478
505
|
auto proxy = set.unchecked<1>();
|
|
479
506
|
auto view = set_view_t{proxy.data(0), static_cast<std::size_t>(proxy.shape(0))};
|
|
480
|
-
auto labels_py = py::array_t<label_t>(py_shape_t{static_cast<
|
|
507
|
+
auto labels_py = py::array_t<label_t>(py_shape_t{static_cast<Py_ssize_t>(count)});
|
|
481
508
|
auto labels_proxy = labels_py.mutable_unchecked<1>();
|
|
482
|
-
auto found = index.search(view, count, &labels_proxy(0), nullptr
|
|
483
|
-
labels_py.resize(py_shape_t{static_cast<
|
|
509
|
+
auto found = index.search(view, count, &labels_proxy(0), nullptr);
|
|
510
|
+
labels_py.resize(py_shape_t{static_cast<Py_ssize_t>(found)});
|
|
484
511
|
return labels_py;
|
|
485
512
|
},
|
|
486
513
|
py::arg("set"), //
|
|
@@ -515,7 +542,7 @@ PYBIND11_MODULE(index, m) {
|
|
|
515
542
|
if (index.size() + 1 >= index.capacity())
|
|
516
543
|
index.reserve(ceil2(index.size() + 1));
|
|
517
544
|
hash_buffer(index, array);
|
|
518
|
-
index.add(label, index.buffer()
|
|
545
|
+
index.add(label, index.buffer());
|
|
519
546
|
}, //
|
|
520
547
|
py::arg("label"), //
|
|
521
548
|
py::arg("array") //
|
|
@@ -527,10 +554,10 @@ PYBIND11_MODULE(index, m) {
|
|
|
527
554
|
if (index.size() + 1 >= index.capacity())
|
|
528
555
|
index.reserve(ceil2(index.size() + 1));
|
|
529
556
|
hash_buffer(index, array);
|
|
530
|
-
auto labels_py = py::array_t<label_t>(py_shape_t{static_cast<
|
|
557
|
+
auto labels_py = py::array_t<label_t>(py_shape_t{static_cast<Py_ssize_t>(count)});
|
|
531
558
|
auto labels_proxy = labels_py.mutable_unchecked<1>();
|
|
532
|
-
auto found = index.search(index.buffer(), count, &labels_proxy(0), nullptr
|
|
533
|
-
labels_py.resize(py_shape_t{static_cast<
|
|
559
|
+
auto found = index.search(index.buffer(), count, &labels_proxy(0), nullptr);
|
|
560
|
+
labels_py.resize(py_shape_t{static_cast<Py_ssize_t>(found)});
|
|
534
561
|
return labels_py;
|
|
535
562
|
},
|
|
536
563
|
py::arg("array"), //
|