usearch 0.18.8 → 0.19.1
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/Cargo.toml +1 -1
- package/README.md +187 -40
- package/c/lib.cpp +71 -51
- package/c/test.c +182 -0
- package/c/usearch.h +11 -5
- package/conanfile.py +1 -1
- package/cpp/bench.cpp +6 -6
- package/cpp/test.cpp +11 -9
- package/docs/compilation.md +1 -0
- package/include/usearch/index.hpp +290 -54
- package/include/usearch/index_punned_dense.hpp +125 -108
- package/include/usearch/index_punned_helpers.hpp +2 -12
- package/objc/USearchObjective.mm +4 -0
- package/objc/include/USearchObjective.h +5 -0
- package/package.json +2 -2
- package/python/lib.cpp +234 -199
- package/python/scripts/bench.ipynb +13 -2113
- package/python/scripts/bench.py +45 -29
- package/python/scripts/index_faiss.py +19 -19
- package/python/scripts/test.py +320 -61
- package/python/usearch/eval.py +100 -49
- package/python/usearch/index.py +245 -129
- package/python/usearch/io.py +25 -25
- package/python/usearch/join.py +2 -4
- package/python/usearch/numba.py +22 -18
- package/python/usearch/server.py +38 -26
- package/setup.py +56 -59
- package/simsimd/.vscode/settings.json +2 -1
- package/simsimd/CMakeLists.txt +1 -1
- package/simsimd/README.md +14 -2
- package/simsimd/bench.cxx +44 -27
- package/simsimd/include/simsimd/simsimd.h +2 -1
- package/swift/Test.swift +13 -9
- package/simsimd/include/simsimd/simsimd.hpp +0 -443
package/python/lib.cpp
CHANGED
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
* @copyright Copyright (c) 2023
|
|
14
14
|
*/
|
|
15
15
|
#define PY_SSIZE_T_CLEAN
|
|
16
|
-
#define NOMINMAX
|
|
17
|
-
#include <
|
|
16
|
+
#define NOMINMAX // Some of our dependencies call `std::max(x, y)`, which crashes Windows builds
|
|
17
|
+
#include <limits> // `std::numeric_limits`
|
|
18
|
+
#include <thread> // `std::thread`
|
|
18
19
|
|
|
19
20
|
#include <pybind11/numpy.h>
|
|
20
21
|
#include <pybind11/pybind11.h>
|
|
@@ -24,12 +25,23 @@
|
|
|
24
25
|
using namespace unum::usearch;
|
|
25
26
|
using namespace unum;
|
|
26
27
|
|
|
28
|
+
/**
|
|
29
|
+
* @brief The signature of the user-defined function.
|
|
30
|
+
* Can be just two array pointers, precompiled for a specific array length,
|
|
31
|
+
* or include one or two array sizes as 64-bit unsigned integers.
|
|
32
|
+
*/
|
|
33
|
+
enum class metric_signature_t {
|
|
34
|
+
array_array_k = 0,
|
|
35
|
+
array_array_size_k,
|
|
36
|
+
array_size_array_size_k,
|
|
37
|
+
};
|
|
38
|
+
|
|
27
39
|
namespace py = pybind11;
|
|
28
40
|
using py_shape_t = py::array::ShapeContainer;
|
|
29
41
|
|
|
30
42
|
using label_t = std::uint32_t;
|
|
31
43
|
using distance_t = punned_distance_t;
|
|
32
|
-
using metric_t =
|
|
44
|
+
using metric_t = index_punned_dense_metric_t;
|
|
33
45
|
using id_t = std::uint32_t;
|
|
34
46
|
using big_id_t = std::uint64_t;
|
|
35
47
|
|
|
@@ -50,7 +62,7 @@ struct punned_index_py_t : public punned_index_t {
|
|
|
50
62
|
|
|
51
63
|
using set_member_t = std::uint32_t;
|
|
52
64
|
using set_view_t = span_gt<set_member_t const>;
|
|
53
|
-
using sparse_index_t = index_gt<jaccard_gt<set_member_t>, label_t, id_t
|
|
65
|
+
using sparse_index_t = index_gt<jaccard_gt<set_member_t>, label_t, id_t>;
|
|
54
66
|
|
|
55
67
|
struct sparse_index_py_t : public sparse_index_t {
|
|
56
68
|
using native_t = sparse_index_t;
|
|
@@ -63,32 +75,61 @@ struct sparse_index_py_t : public sparse_index_t {
|
|
|
63
75
|
sparse_index_py_t(native_t&& base) : native_t(std::move(base)) {}
|
|
64
76
|
};
|
|
65
77
|
|
|
66
|
-
template <typename scalar_at>
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
78
|
+
template <typename scalar_at>
|
|
79
|
+
metric_t typed_udf( //
|
|
80
|
+
metric_kind_t kind, metric_signature_t signature, std::uintptr_t metric_uintptr, scalar_kind_t accuracy) {
|
|
81
|
+
//
|
|
82
|
+
metric_t result;
|
|
83
|
+
result.kind_ = kind;
|
|
84
|
+
result.scalar_kind_ = accuracy;
|
|
85
|
+
switch (signature) {
|
|
86
|
+
case metric_signature_t::array_array_k:
|
|
87
|
+
result.func_ = [metric_uintptr](punned_vector_view_t a, punned_vector_view_t b) -> distance_t {
|
|
88
|
+
using metric_raw_t = punned_distance_t (*)(scalar_at const*, scalar_at const*);
|
|
89
|
+
metric_raw_t metric_ptr = reinterpret_cast<metric_raw_t>(metric_uintptr);
|
|
90
|
+
return metric_ptr((scalar_at const*)a.data(), (scalar_at const*)b.data());
|
|
91
|
+
};
|
|
92
|
+
break;
|
|
93
|
+
case metric_signature_t::array_array_size_k:
|
|
94
|
+
result.func_ = [metric_uintptr](punned_vector_view_t a, punned_vector_view_t b) -> distance_t {
|
|
95
|
+
using metric_raw_t = punned_distance_t (*)(scalar_at const*, scalar_at const*, size_t);
|
|
96
|
+
metric_raw_t metric_ptr = reinterpret_cast<metric_raw_t>(metric_uintptr);
|
|
97
|
+
return metric_ptr((scalar_at const*)a.data(), (scalar_at const*)b.data(), a.size() / sizeof(scalar_at));
|
|
98
|
+
};
|
|
99
|
+
break;
|
|
100
|
+
case metric_signature_t::array_size_array_size_k:
|
|
101
|
+
result.func_ = [metric_uintptr](punned_vector_view_t a, punned_vector_view_t b) -> distance_t {
|
|
102
|
+
using metric_raw_t = punned_distance_t (*)(scalar_at const*, size_t, scalar_at const*, size_t);
|
|
103
|
+
metric_raw_t metric_ptr = reinterpret_cast<metric_raw_t>(metric_uintptr);
|
|
104
|
+
return metric_ptr( //
|
|
105
|
+
(scalar_at const*)a.data(), a.size() / sizeof(scalar_at), //
|
|
106
|
+
(scalar_at const*)b.data(), b.size() / sizeof(scalar_at));
|
|
107
|
+
};
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
72
111
|
}
|
|
73
112
|
|
|
74
|
-
metric_t udf(std::
|
|
113
|
+
metric_t udf(metric_kind_t kind, metric_signature_t signature, std::uintptr_t metric_uintptr, scalar_kind_t accuracy) {
|
|
75
114
|
switch (accuracy) {
|
|
76
|
-
case scalar_kind_t::
|
|
77
|
-
case scalar_kind_t::
|
|
78
|
-
case scalar_kind_t::
|
|
79
|
-
case scalar_kind_t::
|
|
115
|
+
case scalar_kind_t::b1x8_k: return typed_udf<b1x8_t>(kind, signature, metric_uintptr, accuracy);
|
|
116
|
+
case scalar_kind_t::f8_k: return typed_udf<f8_bits_t>(kind, signature, metric_uintptr, accuracy);
|
|
117
|
+
case scalar_kind_t::f16_k: return typed_udf<f16_t>(kind, signature, metric_uintptr, accuracy);
|
|
118
|
+
case scalar_kind_t::f32_k: return typed_udf<f32_t>(kind, signature, metric_uintptr, accuracy);
|
|
119
|
+
case scalar_kind_t::f64_k: return typed_udf<f64_t>(kind, signature, metric_uintptr, accuracy);
|
|
80
120
|
default: return {};
|
|
81
121
|
}
|
|
82
122
|
}
|
|
83
123
|
|
|
84
|
-
static punned_index_py_t make_index(
|
|
85
|
-
std::size_t dimensions,
|
|
86
|
-
scalar_kind_t scalar_kind,
|
|
87
|
-
metric_kind_t metric_kind,
|
|
88
|
-
std::size_t connectivity,
|
|
89
|
-
std::size_t expansion_add,
|
|
90
|
-
std::size_t expansion_search,
|
|
91
|
-
|
|
124
|
+
static punned_index_py_t make_index( //
|
|
125
|
+
std::size_t dimensions, //
|
|
126
|
+
scalar_kind_t scalar_kind, //
|
|
127
|
+
metric_kind_t metric_kind, //
|
|
128
|
+
std::size_t connectivity, //
|
|
129
|
+
std::size_t expansion_add, //
|
|
130
|
+
std::size_t expansion_search, //
|
|
131
|
+
metric_signature_t metric_signature, //
|
|
132
|
+
std::uintptr_t metric_uintptr, //
|
|
92
133
|
bool tune) {
|
|
93
134
|
|
|
94
135
|
index_config_t config;
|
|
@@ -99,7 +140,8 @@ static punned_index_py_t make_index( //
|
|
|
99
140
|
|
|
100
141
|
if (metric_uintptr)
|
|
101
142
|
return punned_index_t::make( //
|
|
102
|
-
dimensions, udf(metric_uintptr, scalar_kind), config, scalar_kind,
|
|
143
|
+
dimensions, udf(metric_kind, metric_signature, metric_uintptr, scalar_kind), config, scalar_kind,
|
|
144
|
+
expansion_add, expansion_search);
|
|
103
145
|
else
|
|
104
146
|
return punned_index_t::make(dimensions, metric_kind, config, scalar_kind, expansion_add, expansion_search);
|
|
105
147
|
}
|
|
@@ -115,6 +157,22 @@ static std::unique_ptr<sparse_index_py_t> make_sparse_index( //
|
|
|
115
157
|
return std::unique_ptr<sparse_index_py_t>(new sparse_index_py_t(sparse_index_t(config)));
|
|
116
158
|
}
|
|
117
159
|
|
|
160
|
+
scalar_kind_t numpy_string_to_kind(std::string const& name) {
|
|
161
|
+
// https://docs.python.org/3/library/struct.html#format-characters
|
|
162
|
+
if (name == "B" || name == "<B" || name == "u1" || name == "|u1")
|
|
163
|
+
return scalar_kind_t::b1x8_k;
|
|
164
|
+
else if (name == "b" || name == "<b" || name == "i1" || name == "|i1")
|
|
165
|
+
return scalar_kind_t::f8_k;
|
|
166
|
+
else if (name == "e" || name == "<e" || name == "f2" || name == "<f2")
|
|
167
|
+
return scalar_kind_t::f16_k;
|
|
168
|
+
else if (name == "f" || name == "<f" || name == "f4" || name == "<f4")
|
|
169
|
+
return scalar_kind_t::f32_k;
|
|
170
|
+
else if (name == "d" || name == "<d" || name == "f8" || name == "<f8")
|
|
171
|
+
return scalar_kind_t::f64_k;
|
|
172
|
+
else
|
|
173
|
+
return scalar_kind_t::unknown_k;
|
|
174
|
+
}
|
|
175
|
+
|
|
118
176
|
static void add_one_to_index(punned_index_py_t& index, label_t label, py::buffer vector, bool copy, std::size_t) {
|
|
119
177
|
|
|
120
178
|
py::buffer_info vector_info = vector.request();
|
|
@@ -132,19 +190,37 @@ static void add_one_to_index(punned_index_py_t& index, label_t label, py::buffer
|
|
|
132
190
|
add_config_t config;
|
|
133
191
|
config.store_vector = copy;
|
|
134
192
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
else if (vector_info.format == "f" || vector_info.format == "f4" || vector_info.format == "<f4")
|
|
143
|
-
index.add(label, reinterpret_cast<float const*>(vector_data), config).error.raise();
|
|
144
|
-
else if (vector_info.format == "d" || vector_info.format == "f8" || vector_info.format == "<f8")
|
|
145
|
-
index.add(label, reinterpret_cast<double const*>(vector_data), config).error.raise();
|
|
146
|
-
else
|
|
193
|
+
switch (numpy_string_to_kind(vector_info.format)) {
|
|
194
|
+
case scalar_kind_t::b1x8_k: index.add(label, (b1x8_t const*)(vector_data), config).error.raise(); break;
|
|
195
|
+
case scalar_kind_t::f8_k: index.add(label, (f8_bits_t const*)(vector_data), config).error.raise(); break;
|
|
196
|
+
case scalar_kind_t::f16_k: index.add(label, (f16_t const*)(vector_data), config).error.raise(); break;
|
|
197
|
+
case scalar_kind_t::f32_k: index.add(label, (f32_t const*)(vector_data), config).error.raise(); break;
|
|
198
|
+
case scalar_kind_t::f64_k: index.add(label, (f64_t const*)(vector_data), config).error.raise(); break;
|
|
199
|
+
case scalar_kind_t::unknown_k:
|
|
147
200
|
throw std::invalid_argument("Incompatible scalars in the vector: " + vector_info.format);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
template <typename scalar_at>
|
|
205
|
+
static void add_typed_to_index( //
|
|
206
|
+
punned_index_py_t& index, //
|
|
207
|
+
py::buffer_info const& labels_info, py::buffer_info const& vectors_info, //
|
|
208
|
+
bool copy, std::size_t threads) {
|
|
209
|
+
|
|
210
|
+
Py_ssize_t vectors_count = vectors_info.shape[0];
|
|
211
|
+
char const* vectors_data = reinterpret_cast<char const*>(vectors_info.ptr);
|
|
212
|
+
char const* labels_data = reinterpret_cast<char const*>(labels_info.ptr);
|
|
213
|
+
|
|
214
|
+
executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
215
|
+
add_config_t config;
|
|
216
|
+
config.store_vector = copy;
|
|
217
|
+
config.thread = thread_idx;
|
|
218
|
+
label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
|
|
219
|
+
scalar_at const* vector = reinterpret_cast<scalar_at const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
220
|
+
index.add(label, vector, config).error.raise();
|
|
221
|
+
if (PyErr_CheckSignals() != 0)
|
|
222
|
+
throw py::error_already_set();
|
|
223
|
+
});
|
|
148
224
|
}
|
|
149
225
|
|
|
150
226
|
static void add_many_to_index( //
|
|
@@ -178,58 +254,15 @@ static void add_many_to_index( //
|
|
|
178
254
|
if (index.size() + vectors_count >= index.capacity())
|
|
179
255
|
index.reserve(ceil2(index.size() + vectors_count));
|
|
180
256
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
config.store_vector = copy;
|
|
189
|
-
config.thread = thread_idx;
|
|
190
|
-
label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
|
|
191
|
-
b1x8_t const* vector = reinterpret_cast<b1x8_t const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
192
|
-
index.add(label, vector, config).error.raise();
|
|
193
|
-
});
|
|
194
|
-
else if (vectors_info.format == "b" || vectors_info.format == "i1" || vectors_info.format == "|i1")
|
|
195
|
-
executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
196
|
-
add_config_t config;
|
|
197
|
-
config.store_vector = copy;
|
|
198
|
-
config.thread = thread_idx;
|
|
199
|
-
label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
|
|
200
|
-
f8_bits_t const* vector =
|
|
201
|
-
reinterpret_cast<f8_bits_t const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
202
|
-
index.add(label, vector, config).error.raise();
|
|
203
|
-
});
|
|
204
|
-
else if (vectors_info.format == "e" || vectors_info.format == "f2" || vectors_info.format == "<f2")
|
|
205
|
-
executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
206
|
-
add_config_t config;
|
|
207
|
-
config.store_vector = copy;
|
|
208
|
-
config.thread = thread_idx;
|
|
209
|
-
label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
|
|
210
|
-
f16_t const* vector = reinterpret_cast<f16_t const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
211
|
-
index.add(label, vector, config).error.raise();
|
|
212
|
-
});
|
|
213
|
-
else if (vectors_info.format == "f" || vectors_info.format == "f4" || vectors_info.format == "<f4")
|
|
214
|
-
executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
215
|
-
add_config_t config;
|
|
216
|
-
config.store_vector = copy;
|
|
217
|
-
config.thread = thread_idx;
|
|
218
|
-
label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
|
|
219
|
-
float const* vector = reinterpret_cast<float const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
220
|
-
index.add(label, vector, config).error.raise();
|
|
221
|
-
});
|
|
222
|
-
else if (vectors_info.format == "d" || vectors_info.format == "f8" || vectors_info.format == "<f8")
|
|
223
|
-
executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
224
|
-
add_config_t config;
|
|
225
|
-
config.store_vector = copy;
|
|
226
|
-
config.thread = thread_idx;
|
|
227
|
-
label_t label = *reinterpret_cast<label_t const*>(labels_data + task_idx * labels_info.strides[0]);
|
|
228
|
-
double const* vector = reinterpret_cast<double const*>(vectors_data + task_idx * vectors_info.strides[0]);
|
|
229
|
-
index.add(label, vector, config).error.raise();
|
|
230
|
-
});
|
|
231
|
-
else
|
|
257
|
+
switch (numpy_string_to_kind(vectors_info.format)) {
|
|
258
|
+
case scalar_kind_t::b1x8_k: add_typed_to_index<b1x8_t>(index, labels_info, vectors_info, copy, threads); break;
|
|
259
|
+
case scalar_kind_t::f8_k: add_typed_to_index<f8_bits_t>(index, labels_info, vectors_info, copy, threads); break;
|
|
260
|
+
case scalar_kind_t::f16_k: add_typed_to_index<f16_t>(index, labels_info, vectors_info, copy, threads); break;
|
|
261
|
+
case scalar_kind_t::f32_k: add_typed_to_index<f32_t>(index, labels_info, vectors_info, copy, threads); break;
|
|
262
|
+
case scalar_kind_t::f64_k: add_typed_to_index<f64_t>(index, labels_info, vectors_info, copy, threads); break;
|
|
263
|
+
case scalar_kind_t::unknown_k:
|
|
232
264
|
throw std::invalid_argument("Incompatible scalars in the vectors matrix: " + vectors_info.format);
|
|
265
|
+
}
|
|
233
266
|
}
|
|
234
267
|
|
|
235
268
|
static py::tuple search_one_in_index(punned_index_py_t& index, py::buffer vector, std::size_t wanted, bool exact) {
|
|
@@ -249,29 +282,20 @@ static py::tuple search_one_in_index(punned_index_py_t& index, py::buffer vector
|
|
|
249
282
|
search_config_t config;
|
|
250
283
|
config.exact = exact;
|
|
251
284
|
|
|
252
|
-
|
|
253
|
-
if (vector_info.format == "B" || vector_info.format == "u1" || vector_info.format == "|u1") {
|
|
254
|
-
punned_search_result_t result = index.search(reinterpret_cast<b1x8_t const*>(vector_data), wanted, config);
|
|
255
|
-
result.error.raise();
|
|
256
|
-
count = result.dump_to(&labels_py1d(0), &distances_py1d(0));
|
|
257
|
-
} else if (vector_info.format == "b" || vector_info.format == "i1" || vector_info.format == "|i1") {
|
|
258
|
-
punned_search_result_t result = index.search(reinterpret_cast<f8_bits_t const*>(vector_data), wanted, config);
|
|
259
|
-
result.error.raise();
|
|
260
|
-
count = result.dump_to(&labels_py1d(0), &distances_py1d(0));
|
|
261
|
-
} else if (vector_info.format == "e" || vector_info.format == "f2" || vector_info.format == "<f2") {
|
|
262
|
-
punned_search_result_t result = index.search(reinterpret_cast<f16_t const*>(vector_data), wanted, config);
|
|
285
|
+
auto raise_and_dump = [&](punned_search_result_t result) {
|
|
263
286
|
result.error.raise();
|
|
264
287
|
count = result.dump_to(&labels_py1d(0), &distances_py1d(0));
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
switch (numpy_string_to_kind(vector_info.format)) {
|
|
291
|
+
case scalar_kind_t::b1x8_k: raise_and_dump(index.search((b1x8_t const*)(vector_data), wanted, config)); break;
|
|
292
|
+
case scalar_kind_t::f8_k: raise_and_dump(index.search((f8_bits_t const*)(vector_data), wanted, config)); break;
|
|
293
|
+
case scalar_kind_t::f16_k: raise_and_dump(index.search((f16_t const*)(vector_data), wanted, config)); break;
|
|
294
|
+
case scalar_kind_t::f32_k: raise_and_dump(index.search((f32_t const*)(vector_data), wanted, config)); break;
|
|
295
|
+
case scalar_kind_t::f64_k: raise_and_dump(index.search((f64_t const*)(vector_data), wanted, config)); break;
|
|
296
|
+
case scalar_kind_t::unknown_k:
|
|
274
297
|
throw std::invalid_argument("Incompatible scalars in the query vector: " + vector_info.format);
|
|
298
|
+
}
|
|
275
299
|
|
|
276
300
|
labels_py.resize(py_shape_t{static_cast<Py_ssize_t>(count)});
|
|
277
301
|
distances_py.resize(py_shape_t{static_cast<Py_ssize_t>(count)});
|
|
@@ -283,6 +307,33 @@ static py::tuple search_one_in_index(punned_index_py_t& index, py::buffer vector
|
|
|
283
307
|
return results;
|
|
284
308
|
}
|
|
285
309
|
|
|
310
|
+
template <typename scalar_at>
|
|
311
|
+
static void search_typed( //
|
|
312
|
+
punned_index_py_t& index, py::buffer_info& vectors_info, //
|
|
313
|
+
std::size_t wanted, bool exact, std::size_t threads, //
|
|
314
|
+
py::array_t<label_t>& labels_py, py::array_t<distance_t>& distances_py, py::array_t<Py_ssize_t>& counts_py) {
|
|
315
|
+
|
|
316
|
+
auto labels_py2d = labels_py.template mutable_unchecked<2>();
|
|
317
|
+
auto distances_py2d = distances_py.template mutable_unchecked<2>();
|
|
318
|
+
auto counts_py1d = counts_py.template mutable_unchecked<1>();
|
|
319
|
+
|
|
320
|
+
Py_ssize_t vectors_count = vectors_info.shape[0];
|
|
321
|
+
char const* vectors_data = reinterpret_cast<char const*>(vectors_info.ptr);
|
|
322
|
+
|
|
323
|
+
executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
324
|
+
search_config_t config;
|
|
325
|
+
config.thread = thread_idx;
|
|
326
|
+
config.exact = exact;
|
|
327
|
+
scalar_at const* vector = (scalar_at const*)(vectors_data + task_idx * vectors_info.strides[0]);
|
|
328
|
+
punned_search_result_t result = index.search(vector, wanted, config);
|
|
329
|
+
result.error.raise();
|
|
330
|
+
counts_py1d(task_idx) =
|
|
331
|
+
static_cast<Py_ssize_t>(result.dump_to(&labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
|
|
332
|
+
if (PyErr_CheckSignals() != 0)
|
|
333
|
+
throw py::error_already_set();
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
286
337
|
/**
|
|
287
338
|
* @param vectors Matrix of vectors to search for.
|
|
288
339
|
* @param wanted Number of matches per request.
|
|
@@ -298,7 +349,7 @@ static py::tuple search_many_in_index( //
|
|
|
298
349
|
if (wanted == 0)
|
|
299
350
|
return py::tuple(3);
|
|
300
351
|
|
|
301
|
-
if (index.limits().
|
|
352
|
+
if (index.limits().threads_search < threads)
|
|
302
353
|
throw std::invalid_argument("Can't use that many threads!");
|
|
303
354
|
|
|
304
355
|
py::buffer_info vectors_info = vectors.request();
|
|
@@ -313,76 +364,24 @@ static py::tuple search_many_in_index( //
|
|
|
313
364
|
if (vectors_dimensions != static_cast<Py_ssize_t>(index.scalar_words()))
|
|
314
365
|
throw std::invalid_argument("The number of vector dimensions doesn't match!");
|
|
315
366
|
|
|
316
|
-
py::array_t<label_t>
|
|
317
|
-
py::array_t<distance_t>
|
|
318
|
-
py::array_t<Py_ssize_t>
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
config.thread = thread_idx;
|
|
328
|
-
config.exact = exact;
|
|
329
|
-
b1x8_t const* vector = (b1x8_t const*)(vectors_data + task_idx * vectors_info.strides[0]);
|
|
330
|
-
punned_search_result_t result = index.search(vector, wanted, config);
|
|
331
|
-
result.error.raise();
|
|
332
|
-
counts_py1d(task_idx) =
|
|
333
|
-
static_cast<Py_ssize_t>(result.dump_to(&labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
|
|
334
|
-
});
|
|
335
|
-
else if (vectors_info.format == "b" || vectors_info.format == "i1" || vectors_info.format == "|i1")
|
|
336
|
-
executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
337
|
-
search_config_t config;
|
|
338
|
-
config.thread = thread_idx;
|
|
339
|
-
config.exact = exact;
|
|
340
|
-
f8_bits_t const* vector = (f8_bits_t const*)(vectors_data + task_idx * vectors_info.strides[0]);
|
|
341
|
-
punned_search_result_t result = index.search(vector, wanted, config);
|
|
342
|
-
result.error.raise();
|
|
343
|
-
counts_py1d(task_idx) =
|
|
344
|
-
static_cast<Py_ssize_t>(result.dump_to(&labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
|
|
345
|
-
});
|
|
346
|
-
else if (vectors_info.format == "e" || vectors_info.format == "f2" || vectors_info.format == "<f2")
|
|
347
|
-
executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
348
|
-
search_config_t config;
|
|
349
|
-
config.thread = thread_idx;
|
|
350
|
-
config.exact = exact;
|
|
351
|
-
f16_t const* vector = (f16_t const*)(vectors_data + task_idx * vectors_info.strides[0]);
|
|
352
|
-
punned_search_result_t result = index.search(vector, wanted, config);
|
|
353
|
-
result.error.raise();
|
|
354
|
-
counts_py1d(task_idx) =
|
|
355
|
-
static_cast<Py_ssize_t>(result.dump_to(&labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
|
|
356
|
-
});
|
|
357
|
-
else if (vectors_info.format == "f" || vectors_info.format == "f4" || vectors_info.format == "<f4")
|
|
358
|
-
executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
359
|
-
search_config_t config;
|
|
360
|
-
config.thread = thread_idx;
|
|
361
|
-
config.exact = exact;
|
|
362
|
-
float const* vector = (float const*)(vectors_data + task_idx * vectors_info.strides[0]);
|
|
363
|
-
punned_search_result_t result = index.search(vector, wanted, config);
|
|
364
|
-
result.error.raise();
|
|
365
|
-
counts_py1d(task_idx) =
|
|
366
|
-
static_cast<Py_ssize_t>(result.dump_to(&labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
|
|
367
|
-
});
|
|
368
|
-
else if (vectors_info.format == "d" || vectors_info.format == "f8" || vectors_info.format == "<f8")
|
|
369
|
-
executor_default_t{threads}.execute_bulk(vectors_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
370
|
-
search_config_t config;
|
|
371
|
-
config.thread = thread_idx;
|
|
372
|
-
config.exact = exact;
|
|
373
|
-
double const* vector = (double const*)(vectors_data + task_idx * vectors_info.strides[0]);
|
|
374
|
-
punned_search_result_t result = index.search(vector, wanted, config);
|
|
375
|
-
result.error.raise();
|
|
376
|
-
counts_py1d(task_idx) =
|
|
377
|
-
static_cast<Py_ssize_t>(result.dump_to(&labels_py2d(task_idx, 0), &distances_py2d(task_idx, 0)));
|
|
378
|
-
});
|
|
379
|
-
else
|
|
367
|
+
py::array_t<label_t> ls({vectors_count, static_cast<Py_ssize_t>(wanted)});
|
|
368
|
+
py::array_t<distance_t> ds({vectors_count, static_cast<Py_ssize_t>(wanted)});
|
|
369
|
+
py::array_t<Py_ssize_t> cs(vectors_count);
|
|
370
|
+
|
|
371
|
+
switch (numpy_string_to_kind(vectors_info.format)) {
|
|
372
|
+
case scalar_kind_t::b1x8_k: search_typed<b1x8_t>(index, vectors_info, wanted, exact, threads, ls, ds, cs); break;
|
|
373
|
+
case scalar_kind_t::f8_k: search_typed<f8_bits_t>(index, vectors_info, wanted, exact, threads, ls, ds, cs); break;
|
|
374
|
+
case scalar_kind_t::f16_k: search_typed<f16_t>(index, vectors_info, wanted, exact, threads, ls, ds, cs); break;
|
|
375
|
+
case scalar_kind_t::f32_k: search_typed<f32_t>(index, vectors_info, wanted, exact, threads, ls, ds, cs); break;
|
|
376
|
+
case scalar_kind_t::f64_k: search_typed<f64_t>(index, vectors_info, wanted, exact, threads, ls, ds, cs); break;
|
|
377
|
+
case scalar_kind_t::unknown_k:
|
|
380
378
|
throw std::invalid_argument("Incompatible scalars in the query matrix: " + vectors_info.format);
|
|
379
|
+
}
|
|
381
380
|
|
|
382
381
|
py::tuple results(3);
|
|
383
|
-
results[0] =
|
|
384
|
-
results[1] =
|
|
385
|
-
results[2] =
|
|
382
|
+
results[0] = ls;
|
|
383
|
+
results[1] = ds;
|
|
384
|
+
results[2] = cs;
|
|
386
385
|
return results;
|
|
387
386
|
}
|
|
388
387
|
|
|
@@ -408,7 +407,7 @@ template <typename index_at> py::object get_member(index_at const& index, label_
|
|
|
408
407
|
else if (scalar_kind == scalar_kind_t::f64_k)
|
|
409
408
|
return get_typed_member<f64_t>(index, label);
|
|
410
409
|
else if (scalar_kind == scalar_kind_t::f16_k)
|
|
411
|
-
return get_typed_member<f16_t>(index, label);
|
|
410
|
+
return get_typed_member<f16_t, std::uint16_t>(index, label);
|
|
412
411
|
else if (scalar_kind == scalar_kind_t::f8_k)
|
|
413
412
|
return get_typed_member<f8_bits_t, std::int8_t>(index, label);
|
|
414
413
|
else if (scalar_kind == scalar_kind_t::b1x8_k)
|
|
@@ -417,14 +416,18 @@ template <typename index_at> py::object get_member(index_at const& index, label_
|
|
|
417
416
|
throw std::invalid_argument("Incompatible scalars in the query matrix!");
|
|
418
417
|
}
|
|
419
418
|
|
|
420
|
-
template <typename index_at> py::array_t<label_t> get_labels(index_at const& index) {
|
|
421
|
-
|
|
422
|
-
py::array_t<label_t> result_py(static_cast<Py_ssize_t>(
|
|
419
|
+
template <typename index_at> py::array_t<label_t> get_labels(index_at const& index, std::size_t limit) {
|
|
420
|
+
limit = std::min(index.size(), limit);
|
|
421
|
+
py::array_t<label_t> result_py(static_cast<Py_ssize_t>(limit));
|
|
423
422
|
auto result_py1d = result_py.template mutable_unchecked<1>();
|
|
424
|
-
index.export_labels(&result_py1d(0),
|
|
423
|
+
index.export_labels(&result_py1d(0), limit);
|
|
425
424
|
return result_py;
|
|
426
425
|
}
|
|
427
426
|
|
|
427
|
+
template <typename index_at> py::array_t<label_t> get_all_labels(index_at const& index) {
|
|
428
|
+
return get_labels(index, index.size());
|
|
429
|
+
}
|
|
430
|
+
|
|
428
431
|
template <typename element_at> bool has_duplicates(element_at const* begin, element_at const* end) {
|
|
429
432
|
if (begin == end)
|
|
430
433
|
return false;
|
|
@@ -539,6 +542,11 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
539
542
|
m.attr("USES_SIMSIMD") = py::int_(USEARCH_USE_SIMSIMD);
|
|
540
543
|
m.attr("USES_NATIVE_F16") = py::int_(USEARCH_USE_NATIVE_F16);
|
|
541
544
|
|
|
545
|
+
py::enum_<metric_signature_t>(m, "MetricSignature")
|
|
546
|
+
.value("ArrayArray", metric_signature_t::array_array_k)
|
|
547
|
+
.value("ArrayArraySize", metric_signature_t::array_array_size_k)
|
|
548
|
+
.value("ArraySizeArraySize", metric_signature_t::array_size_array_size_k);
|
|
549
|
+
|
|
542
550
|
py::enum_<metric_kind_t>(m, "MetricKind")
|
|
543
551
|
.value("Unknown", metric_kind_t::unknown_k)
|
|
544
552
|
.value("IP", metric_kind_t::ip_k)
|
|
@@ -558,18 +566,44 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
558
566
|
.value("F8", scalar_kind_t::f8_k)
|
|
559
567
|
.value("B1", scalar_kind_t::b1x8_k);
|
|
560
568
|
|
|
569
|
+
auto h = py::class_<file_head_result_t>(m, "IndexMetadata");
|
|
570
|
+
h.def(py::init([](std::string const& path) {
|
|
571
|
+
file_head_result_t h = index_metadata(path.c_str());
|
|
572
|
+
h.error.raise();
|
|
573
|
+
return h;
|
|
574
|
+
}));
|
|
575
|
+
h.def_property_readonly("version", [](file_head_result_t const& h) {
|
|
576
|
+
return //
|
|
577
|
+
std::to_string(h.version_major) + "." + //
|
|
578
|
+
std::to_string(h.version_minor) + "." + //
|
|
579
|
+
std::to_string(h.version_patch);
|
|
580
|
+
});
|
|
581
|
+
h.def_readonly("metric", &file_head_result_t::metric);
|
|
582
|
+
h.def_readonly("connectivity", &file_head_result_t::connectivity);
|
|
583
|
+
h.def_readonly("max_level", &file_head_result_t::max_level);
|
|
584
|
+
h.def_readonly("vector_alignment", &file_head_result_t::vector_alignment);
|
|
585
|
+
h.def_readonly("bytes_per_label", &file_head_result_t::bytes_per_label);
|
|
586
|
+
h.def_readonly("bytes_per_id", &file_head_result_t::bytes_per_id);
|
|
587
|
+
h.def_readonly("scalar_kind", &file_head_result_t::scalar_kind);
|
|
588
|
+
h.def_readonly("size", &file_head_result_t::size);
|
|
589
|
+
h.def_readonly("entry_idx", &file_head_result_t::entry_idx);
|
|
590
|
+
h.def_readonly("bytes_for_graphs", &file_head_result_t::bytes_for_graphs);
|
|
591
|
+
h.def_readonly("bytes_for_vectors", &file_head_result_t::bytes_for_vectors);
|
|
592
|
+
h.def_readonly("bytes_checksum", &file_head_result_t::bytes_checksum);
|
|
593
|
+
|
|
561
594
|
auto i = py::class_<punned_index_py_t>(m, "Index");
|
|
562
595
|
|
|
563
|
-
i.def(py::init(&make_index),
|
|
564
|
-
py::kw_only(),
|
|
565
|
-
py::arg("ndim") = 0,
|
|
566
|
-
py::arg("dtype") = scalar_kind_t::f32_k,
|
|
567
|
-
py::arg("metric") = metric_kind_t::ip_k,
|
|
568
|
-
py::arg("connectivity") = default_connectivity(),
|
|
569
|
-
py::arg("expansion_add") = default_expansion_add(),
|
|
570
|
-
py::arg("expansion_search") = default_expansion_search(),
|
|
571
|
-
py::arg("
|
|
572
|
-
py::arg("
|
|
596
|
+
i.def(py::init(&make_index), //
|
|
597
|
+
py::kw_only(), //
|
|
598
|
+
py::arg("ndim") = 0, //
|
|
599
|
+
py::arg("dtype") = scalar_kind_t::f32_k, //
|
|
600
|
+
py::arg("metric") = metric_kind_t::ip_k, //
|
|
601
|
+
py::arg("connectivity") = default_connectivity(), //
|
|
602
|
+
py::arg("expansion_add") = default_expansion_add(), //
|
|
603
|
+
py::arg("expansion_search") = default_expansion_search(), //
|
|
604
|
+
py::arg("metric_signature") = metric_signature_t::array_array_k, //
|
|
605
|
+
py::arg("metric_pointer") = 0, //
|
|
606
|
+
py::arg("tune") = false //
|
|
573
607
|
);
|
|
574
608
|
|
|
575
609
|
i.def( //
|
|
@@ -604,23 +638,24 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
604
638
|
i.def_property_readonly("connectivity", &punned_index_py_t::connectivity);
|
|
605
639
|
i.def_property_readonly("capacity", &punned_index_py_t::capacity);
|
|
606
640
|
i.def_property_readonly( //
|
|
607
|
-
"dtype", [](punned_index_py_t const& index) ->
|
|
641
|
+
"dtype", [](punned_index_py_t const& index) -> scalar_kind_t { return index.metric().scalar_kind_; });
|
|
608
642
|
i.def_property_readonly( //
|
|
609
|
-
"memory_usage", [](punned_index_py_t const& index) -> std::size_t { return index.memory_usage(); }
|
|
643
|
+
"memory_usage", [](punned_index_py_t const& index) -> std::size_t { return index.memory_usage(); },
|
|
644
|
+
py::call_guard<py::gil_scoped_release>());
|
|
610
645
|
|
|
611
646
|
i.def_property("expansion_add", &punned_index_py_t::expansion_add, &punned_index_py_t::change_expansion_add);
|
|
612
647
|
i.def_property("expansion_search", &punned_index_py_t::expansion_search,
|
|
613
648
|
&punned_index_py_t::change_expansion_search);
|
|
614
649
|
|
|
615
|
-
i.def_property_readonly("labels", &
|
|
650
|
+
i.def_property_readonly("labels", &get_all_labels<punned_index_py_t>);
|
|
651
|
+
i.def("get_labels", &get_labels<punned_index_py_t>, py::arg("limit") = std::numeric_limits<std::size_t>::max());
|
|
616
652
|
i.def("__contains__", &punned_index_py_t::contains);
|
|
617
|
-
i.def(
|
|
618
|
-
"__getitem__", &get_member<punned_index_py_t>, py::arg("label"), py::arg("dtype") = scalar_kind_t::f32_k);
|
|
653
|
+
i.def("__getitem__", &get_member<punned_index_py_t>, py::arg("label"), py::arg("dtype") = scalar_kind_t::f32_k);
|
|
619
654
|
|
|
620
|
-
i.def("save", &save_index<punned_index_py_t>, py::arg("path"));
|
|
621
|
-
i.def("load", &load_index<punned_index_py_t>, py::arg("path"));
|
|
622
|
-
i.def("view", &view_index<punned_index_py_t>, py::arg("path"));
|
|
623
|
-
i.def("clear", &punned_index_py_t::clear);
|
|
655
|
+
i.def("save", &save_index<punned_index_py_t>, py::arg("path"), py::call_guard<py::gil_scoped_release>());
|
|
656
|
+
i.def("load", &load_index<punned_index_py_t>, py::arg("path"), py::call_guard<py::gil_scoped_release>());
|
|
657
|
+
i.def("view", &view_index<punned_index_py_t>, py::arg("path"), py::call_guard<py::gil_scoped_release>());
|
|
658
|
+
i.def("clear", &punned_index_py_t::clear, py::call_guard<py::gil_scoped_release>());
|
|
624
659
|
|
|
625
660
|
auto si = py::class_<sparse_index_py_t>(m, "SparseIndex");
|
|
626
661
|
|