usearch 0.1.0 → 0.1.4
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 +32 -3
- package/CMakeListsInstructuons.md +7 -2
- package/{cargo.toml → Cargo.toml} +6 -2
- package/README.md +12 -4
- package/VERSION +1 -1
- package/build.rs +5 -3
- package/include/usearch/usearch.hpp +12 -12
- package/package.json +4 -1
- package/src/advanced.hpp +60 -21
- package/src/bench.cpp +15 -11
- package/src/lib.rs +83 -0
- package/src/python.cpp +9 -9
- package/src/rust.cpp +64 -15
- package/src/rust.hpp +46 -0
- package/src/main.rs +0 -42
- package/src/rust.h +0 -22
|
@@ -55,8 +55,8 @@ jobs:
|
|
|
55
55
|
path: ./wheelhouse/*.whl
|
|
56
56
|
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
name: Publish
|
|
58
|
+
publish_python:
|
|
59
|
+
name: Publish Python
|
|
60
60
|
runs-on: ubuntu-20.04
|
|
61
61
|
needs: build_wheels
|
|
62
62
|
environment:
|
|
@@ -78,6 +78,35 @@ jobs:
|
|
|
78
78
|
verbose: true
|
|
79
79
|
print_hash: true
|
|
80
80
|
|
|
81
|
+
publish_javascript:
|
|
82
|
+
name: Publish JavaScript
|
|
83
|
+
runs-on: ubuntu-latest
|
|
84
|
+
steps:
|
|
85
|
+
- uses: actions/checkout@v3
|
|
86
|
+
- run: git submodule update --init --recursive
|
|
87
|
+
- uses: actions/setup-node@v3
|
|
88
|
+
with:
|
|
89
|
+
node-version: 18
|
|
90
|
+
- run: npm install
|
|
91
|
+
- run: npm ci
|
|
92
|
+
- run: npm test
|
|
93
|
+
- uses: JS-DevTools/npm-publish@v2
|
|
94
|
+
with:
|
|
95
|
+
token: ${{ secrets.NPM_TOKEN }}
|
|
96
|
+
|
|
97
|
+
publish_rust:
|
|
98
|
+
name: Publish Rust
|
|
99
|
+
runs-on: ubuntu-latest
|
|
100
|
+
steps:
|
|
101
|
+
- uses: actions/checkout@v3
|
|
102
|
+
- run: git submodule update --init --recursive
|
|
103
|
+
- uses: actions-rs/toolchain@v1
|
|
104
|
+
with:
|
|
105
|
+
toolchain: stable
|
|
106
|
+
override: true
|
|
107
|
+
- uses: katyo/publish-crates@v2
|
|
108
|
+
with:
|
|
109
|
+
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
|
81
110
|
|
|
82
111
|
deploy_docs:
|
|
83
112
|
name: Deploy Docs
|
|
@@ -85,7 +114,7 @@ jobs:
|
|
|
85
114
|
name: github-pages
|
|
86
115
|
url: ${{ steps.deployment.outputs.page_url }}
|
|
87
116
|
runs-on: ubuntu-22.04
|
|
88
|
-
needs:
|
|
117
|
+
needs: publish_python
|
|
89
118
|
steps:
|
|
90
119
|
- name: Checkout
|
|
91
120
|
uses: actions/checkout@v3
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "usearch"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.4"
|
|
4
4
|
authors = ["Ashot Vardanian <1983160+ashvardanian@users.noreply.github.com>"]
|
|
5
5
|
description = "The Tiny Vector Search Engine from Unum"
|
|
6
6
|
edition = "2021"
|
|
7
7
|
license = "Apache-2.0"
|
|
8
|
-
publish =
|
|
8
|
+
publish = true
|
|
9
9
|
repository = "https://github.com/unum-cloud/usearch"
|
|
10
10
|
|
|
11
|
+
[lib]
|
|
12
|
+
name = "usearch"
|
|
13
|
+
path = "src/lib.rs"
|
|
14
|
+
|
|
11
15
|
[dependencies]
|
|
12
16
|
cxx = "1.0"
|
|
13
17
|
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Compact, yet Powerful<br/>
|
|
|
17
17
|
<a href="https://github.com/unum-cloud/usearch"><img height="25" src="https://github.com/unum-cloud/.github/raw/main/assets/github.svg" alt="GitHub"></a>
|
|
18
18
|
</p>
|
|
19
19
|
|
|
20
|
-
- [x] Single C++11 header, easily extendible.
|
|
20
|
+
- [x] Single C++11 header implementation, easily extendible.
|
|
21
21
|
- [x] 4B+ sized space efficient point-clouds with `uint40_t`.
|
|
22
22
|
- [x] Half-precision support with [`maratyszcza/fp16`](https://github.com/maratyszcza/fp16).
|
|
23
23
|
- [x] View from disk, without loading into RAM.
|
|
@@ -29,14 +29,16 @@ Compact, yet Powerful<br/>
|
|
|
29
29
|
- [x] Don't copy vectors if not needed.
|
|
30
30
|
- [x] Bring your threads.
|
|
31
31
|
- [x] Multiple vectors per label.
|
|
32
|
-
- [x]
|
|
33
|
-
- [
|
|
32
|
+
- [x] Python bindings: `pip install usearch`
|
|
33
|
+
- [x] JavaScript bindings: `npm install usearch`
|
|
34
|
+
- [x] Rust bindings: `cargo add usearch`
|
|
34
35
|
- [ ] Wolfram language bindings.
|
|
35
36
|
- [x] For Linux: GCC, Clang.
|
|
36
37
|
- [x] For MacOS: Apple Clang.
|
|
37
38
|
- [ ] For Windows.
|
|
39
|
+
- [ ] Multi-index lookups in Python.
|
|
40
|
+
- [ ] Thread-safe `reserve`.
|
|
38
41
|
- [ ] Distributed construction.
|
|
39
|
-
- [ ] Multi-index lookups.
|
|
40
42
|
|
|
41
43
|
## Usage
|
|
42
44
|
|
|
@@ -67,3 +69,9 @@ FetchContent_MakeAvailable(usearch)
|
|
|
67
69
|
### Bring your Threads
|
|
68
70
|
|
|
69
71
|
## Performance
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
## TODO
|
|
75
|
+
|
|
76
|
+
- JavaScript: Allow calling from "worker threads".
|
|
77
|
+
- Rust: Allow passing a custom thread ID.
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.4
|
package/build.rs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
fn main() {
|
|
2
|
-
cxx_build::bridge("src/
|
|
2
|
+
cxx_build::bridge("src/lib.rs")
|
|
3
3
|
.file("src/rust.cpp")
|
|
4
4
|
.flag_if_supported("-std=c++11")
|
|
5
5
|
.include("include")
|
|
@@ -8,7 +8,9 @@ fn main() {
|
|
|
8
8
|
.include("simsimd/include")
|
|
9
9
|
.compile("usearch");
|
|
10
10
|
|
|
11
|
-
println!("cargo:rerun-if-changed=src/
|
|
11
|
+
println!("cargo:rerun-if-changed=src/lib.rs");
|
|
12
12
|
println!("cargo:rerun-if-changed=src/rust.cpp");
|
|
13
|
-
println!("cargo:rerun-if-changed=src/rust.
|
|
13
|
+
println!("cargo:rerun-if-changed=src/rust.hpp");
|
|
14
|
+
println!("cargo:rerun-if-changed=src/advanced.hpp");
|
|
15
|
+
println!("cargo:rerun-if-changed=include/usearch/usearch.hpp");
|
|
14
16
|
}
|
|
@@ -59,9 +59,9 @@ using f32_t = float;
|
|
|
59
59
|
*/
|
|
60
60
|
enum class f16_t : std::uint16_t {};
|
|
61
61
|
|
|
62
|
-
template <typename at>
|
|
62
|
+
template <typename at> at angle_to_radians(at angle) noexcept { return angle * M_PI / 180.f; }
|
|
63
63
|
|
|
64
|
-
template <typename at>
|
|
64
|
+
template <typename at> at square(at value) noexcept { return value * value; }
|
|
65
65
|
|
|
66
66
|
inline std::size_t ceil2(std::size_t v) noexcept {
|
|
67
67
|
v--;
|
|
@@ -223,22 +223,22 @@ template <typename scalar_at> struct pearson_correlation_gt {
|
|
|
223
223
|
template <typename scalar_at> struct haversine_gt {
|
|
224
224
|
using scalar_t = scalar_at;
|
|
225
225
|
using result_type = scalar_t;
|
|
226
|
-
static_assert(std::
|
|
226
|
+
static_assert(!std::is_integral<scalar_t>::value, "Latitude and longitude must be floating-node");
|
|
227
227
|
|
|
228
228
|
inline scalar_t operator()(scalar_t const* a, scalar_t const* b, std::size_t = 2, std::size_t = 2) const noexcept {
|
|
229
229
|
scalar_t lat_a = a[0], lon_a = a[1];
|
|
230
230
|
scalar_t lat_b = b[0], lon_b = b[1];
|
|
231
231
|
|
|
232
|
-
scalar_t lat_delta = angle_to_radians(lat_b - lat_a);
|
|
233
|
-
scalar_t lon_delta = angle_to_radians(lon_b - lon_a);
|
|
232
|
+
scalar_t lat_delta = angle_to_radians<scalar_t>(lat_b - lat_a);
|
|
233
|
+
scalar_t lon_delta = angle_to_radians<scalar_t>(lon_b - lon_a);
|
|
234
234
|
|
|
235
|
-
scalar_t converted_lat_a = angle_to_radians(lat_a);
|
|
236
|
-
scalar_t converted_lat_b = angle_to_radians(lat_b);
|
|
235
|
+
scalar_t converted_lat_a = angle_to_radians<scalar_t>(lat_a);
|
|
236
|
+
scalar_t converted_lat_b = angle_to_radians<scalar_t>(lat_b);
|
|
237
237
|
|
|
238
|
-
scalar_t x = square(std::sin(lat_delta / 2)) +
|
|
239
|
-
std::cos(converted_lat_a) * std::cos(converted_lat_b) * square(std::sin(lon_delta / 2));
|
|
238
|
+
scalar_t x = square(std::sin(lat_delta / 2.f)) +
|
|
239
|
+
std::cos(converted_lat_a) * std::cos(converted_lat_b) * square(std::sin(lon_delta / 2.f));
|
|
240
240
|
|
|
241
|
-
return std::atan2(std::sqrt(x), std::sqrt(1 - x));
|
|
241
|
+
return std::atan2(std::sqrt(x), std::sqrt(1.f - x));
|
|
242
242
|
}
|
|
243
243
|
};
|
|
244
244
|
|
|
@@ -506,7 +506,7 @@ struct config_t {
|
|
|
506
506
|
/// @brief Hyper-parameter controlling the quality of indexing.
|
|
507
507
|
/// Defaults to 40 in FAISS and 200 in hnswlib.
|
|
508
508
|
/// > It is called `efConstruction` in the paper.
|
|
509
|
-
static constexpr std::size_t
|
|
509
|
+
static constexpr std::size_t expansion_add_default_k = 128;
|
|
510
510
|
|
|
511
511
|
/// @brief Hyper-parameter controlling the quality of search.
|
|
512
512
|
/// Defaults to 16 in FAISS and 10 in hnswlib.
|
|
@@ -514,7 +514,7 @@ struct config_t {
|
|
|
514
514
|
static constexpr std::size_t expansion_search_default_k = 64;
|
|
515
515
|
|
|
516
516
|
std::size_t connectivity = connectivity_default_k;
|
|
517
|
-
std::size_t expansion_add =
|
|
517
|
+
std::size_t expansion_add = expansion_add_default_k;
|
|
518
518
|
std::size_t expansion_search = expansion_search_default_k;
|
|
519
519
|
|
|
520
520
|
///
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "usearch",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "The Mighty Tiny Vector Search Engine with Automatic Quantization and Hardware Acceleration",
|
|
5
5
|
"author": "Ashot Vardanian",
|
|
6
6
|
"license": "Apache 2.0",
|
|
@@ -15,5 +15,8 @@
|
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"bindings": "~1.2.1",
|
|
17
17
|
"node-addon-api": "^3.0.0"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "node src/test.js"
|
|
18
21
|
}
|
|
19
22
|
}
|
package/src/advanced.hpp
CHANGED
|
@@ -59,6 +59,14 @@ class f16_converted_t {
|
|
|
59
59
|
inline f16_converted_t operator-(f16_converted_t other) const noexcept { return {float(*this) - float(other)}; }
|
|
60
60
|
inline f16_converted_t operator*(f16_converted_t other) const noexcept { return {float(*this) * float(other)}; }
|
|
61
61
|
inline f16_converted_t operator/(f16_converted_t other) const noexcept { return {float(*this) / float(other)}; }
|
|
62
|
+
inline f16_converted_t operator+(float other) const noexcept { return {float(*this) + other}; }
|
|
63
|
+
inline f16_converted_t operator-(float other) const noexcept { return {float(*this) - other}; }
|
|
64
|
+
inline f16_converted_t operator*(float other) const noexcept { return {float(*this) * other}; }
|
|
65
|
+
inline f16_converted_t operator/(float other) const noexcept { return {float(*this) / other}; }
|
|
66
|
+
inline f16_converted_t operator+(double other) const noexcept { return {float(*this) + other}; }
|
|
67
|
+
inline f16_converted_t operator-(double other) const noexcept { return {float(*this) - other}; }
|
|
68
|
+
inline f16_converted_t operator*(double other) const noexcept { return {float(*this) * other}; }
|
|
69
|
+
inline f16_converted_t operator/(double other) const noexcept { return {float(*this) / other}; }
|
|
62
70
|
|
|
63
71
|
inline f16_converted_t& operator+=(float v) noexcept {
|
|
64
72
|
uint16_ = fp16_ieee_from_fp32_value(v + fp16_ieee_to_fp32_value(uint16_));
|
|
@@ -164,6 +172,26 @@ enum class accuracy_t {
|
|
|
164
172
|
i8q100_k,
|
|
165
173
|
};
|
|
166
174
|
|
|
175
|
+
inline bool str_equals(char const* begin, size_t len, char const* other_begin) noexcept {
|
|
176
|
+
size_t other_len = strlen(other_begin);
|
|
177
|
+
return len == other_len && strncmp(begin, other_begin, len) == 0;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
inline accuracy_t accuracy(char const* str, size_t len) {
|
|
181
|
+
accuracy_t accuracy;
|
|
182
|
+
if (str_equals(str, len, "f32"))
|
|
183
|
+
accuracy = accuracy_t::f32_k;
|
|
184
|
+
else if (str_equals(str, len, "f64"))
|
|
185
|
+
accuracy = accuracy_t::f64_k;
|
|
186
|
+
else if (str_equals(str, len, "f16"))
|
|
187
|
+
accuracy = accuracy_t::f16_k;
|
|
188
|
+
else if (str_equals(str, len, "i8q100"))
|
|
189
|
+
accuracy = accuracy_t::i8q100_k;
|
|
190
|
+
else
|
|
191
|
+
throw std::runtime_error("Unknown type, choose: f32, f16, f64, i8q100");
|
|
192
|
+
return accuracy;
|
|
193
|
+
}
|
|
194
|
+
|
|
167
195
|
enum class isa_t {
|
|
168
196
|
auto_k,
|
|
169
197
|
neon_k,
|
|
@@ -252,11 +280,11 @@ class auto_index_gt {
|
|
|
252
280
|
};
|
|
253
281
|
|
|
254
282
|
std::size_t dimensions_ = 0;
|
|
283
|
+
std::size_t casted_vector_bytes_ = 0;
|
|
255
284
|
accuracy_t accuracy_ = accuracy_t::f32_k;
|
|
256
|
-
std::unique_ptr<index_t> index_;
|
|
257
|
-
|
|
258
285
|
isa_t acceleration_ = isa_t::auto_k;
|
|
259
|
-
|
|
286
|
+
|
|
287
|
+
std::unique_ptr<index_t> index_;
|
|
260
288
|
mutable std::vector<byte_t> cast_buffer_;
|
|
261
289
|
struct casts_t {
|
|
262
290
|
cast_t from_i8q100{};
|
|
@@ -269,6 +297,13 @@ class auto_index_gt {
|
|
|
269
297
|
config_t root_config_;
|
|
270
298
|
|
|
271
299
|
public:
|
|
300
|
+
auto_index_gt() = default;
|
|
301
|
+
auto_index_gt(auto_index_gt&& other)
|
|
302
|
+
: dimensions_(other.dimensions_), casted_vector_bytes_(other.casted_vector_bytes_), accuracy_(other.accuracy_),
|
|
303
|
+
acceleration_(other.acceleration_), index_(std::move(other.index_)),
|
|
304
|
+
cast_buffer_(std::move(other.cast_buffer_)), casts_(std::move(other.casts_)),
|
|
305
|
+
root_metric_(std::move(other.root_metric_)), root_config_(std::move(other.root_config_)) {}
|
|
306
|
+
|
|
272
307
|
std::size_t dimensions() const noexcept { return dimensions_; }
|
|
273
308
|
std::size_t connectivity() const noexcept { return index_->connectivity(); }
|
|
274
309
|
std::size_t size() const noexcept { return index_->size(); }
|
|
@@ -292,20 +327,12 @@ class auto_index_gt {
|
|
|
292
327
|
std::size_t search(f16_converted_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, std::size_t thread = 0) const { return search(vector, wanted, matches, distances, thread, casts_.from_f16); }
|
|
293
328
|
std::size_t search(f32_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, std::size_t thread = 0) const { return search(vector, wanted, matches, distances, thread, casts_.from_f32); }
|
|
294
329
|
std::size_t search(f64_t const* vector, std::size_t wanted, label_t* matches, distance_t* distances, std::size_t thread = 0) const { return search(vector, wanted, matches, distances, thread, casts_.from_f64); }
|
|
295
|
-
// clang-format on
|
|
296
330
|
|
|
297
|
-
static auto_index_gt ip(std::size_t dimensions, accuracy_t accuracy = accuracy_t::f16_k, config_t config = {}) {
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
return make(dimensions, accuracy, l2_metric(dimensions, accuracy), make_casts(accuracy), config);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
static auto_index_gt cos(std::size_t dimensions, accuracy_t accuracy = accuracy_t::f32_k, config_t config = {}) {
|
|
306
|
-
return {};
|
|
307
|
-
}
|
|
308
|
-
static auto_index_gt haversine(accuracy_t accuracy = accuracy_t::f32_k, config_t config = {}) { return {}; }
|
|
331
|
+
static auto_index_gt ip(std::size_t dimensions, accuracy_t accuracy = accuracy_t::f16_k, config_t config = {}) { return make(dimensions, accuracy, ip_metric(dimensions, accuracy), make_casts(accuracy), config); }
|
|
332
|
+
static auto_index_gt l2(std::size_t dimensions, accuracy_t accuracy = accuracy_t::f16_k, config_t config = {}) { return make(dimensions, accuracy, l2_metric(dimensions, accuracy), make_casts(accuracy), config); }
|
|
333
|
+
static auto_index_gt cos(std::size_t dimensions, accuracy_t accuracy = accuracy_t::f32_k, config_t config = {}) { return make(dimensions, accuracy, cos_metric(dimensions, accuracy), make_casts(accuracy), config); }
|
|
334
|
+
static auto_index_gt haversine(accuracy_t accuracy = accuracy_t::f32_k, config_t config = {}) { return make(2, accuracy, haversine_metric(accuracy), make_casts(accuracy), config); }
|
|
335
|
+
// clang-format on
|
|
309
336
|
|
|
310
337
|
auto_index_gt fork() const {
|
|
311
338
|
auto_index_gt result;
|
|
@@ -339,8 +366,10 @@ class auto_index_gt {
|
|
|
339
366
|
}
|
|
340
367
|
|
|
341
368
|
template <typename scalar_at>
|
|
342
|
-
std::size_t search(
|
|
343
|
-
|
|
369
|
+
std::size_t search( //
|
|
370
|
+
scalar_at const* vector, std::size_t wanted, //
|
|
371
|
+
label_t* matches, distance_t* distances, //
|
|
372
|
+
std::size_t thread, cast_t const& cast) const {
|
|
344
373
|
|
|
345
374
|
byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
|
|
346
375
|
std::size_t vector_bytes = dimensions_ * sizeof(scalar_at);
|
|
@@ -412,7 +441,7 @@ class auto_index_gt {
|
|
|
412
441
|
if (supports_arm_sve())
|
|
413
442
|
return {
|
|
414
443
|
pun_metric<simsimd_f32_t>([](simsimd_f32_t const* a, simsimd_f32_t const* b, size_t d) noexcept {
|
|
415
|
-
|
|
444
|
+
return 1 - simsimd_dot_f32sve(a, b, d);
|
|
416
445
|
}),
|
|
417
446
|
isa_t::sve_k,
|
|
418
447
|
};
|
|
@@ -461,7 +490,7 @@ class auto_index_gt {
|
|
|
461
490
|
}
|
|
462
491
|
}
|
|
463
492
|
|
|
464
|
-
static metric_and_meta_t l2_metric(std::size_t
|
|
493
|
+
static metric_and_meta_t l2_metric(std::size_t, accuracy_t accuracy) {
|
|
465
494
|
switch (accuracy) {
|
|
466
495
|
case accuracy_t::i8q100_k: return {};
|
|
467
496
|
case accuracy_t::f16_k: return {pun_metric<f16_converted_t>(l2_squared_gt<f16_converted_t>{}), isa_t::auto_k};
|
|
@@ -471,7 +500,7 @@ class auto_index_gt {
|
|
|
471
500
|
}
|
|
472
501
|
}
|
|
473
502
|
|
|
474
|
-
static metric_and_meta_t cos_metric(std::size_t
|
|
503
|
+
static metric_and_meta_t cos_metric(std::size_t, accuracy_t accuracy) {
|
|
475
504
|
switch (accuracy) {
|
|
476
505
|
case accuracy_t::i8q100_k: return {};
|
|
477
506
|
case accuracy_t::f16_k: return {pun_metric<f16_converted_t>(cos_gt<f16_converted_t>{}), isa_t::auto_k};
|
|
@@ -480,6 +509,16 @@ class auto_index_gt {
|
|
|
480
509
|
default: return {};
|
|
481
510
|
}
|
|
482
511
|
}
|
|
512
|
+
|
|
513
|
+
static metric_and_meta_t haversine_metric(accuracy_t accuracy) {
|
|
514
|
+
switch (accuracy) {
|
|
515
|
+
case accuracy_t::i8q100_k: return {};
|
|
516
|
+
case accuracy_t::f16_k: return {pun_metric<f16_converted_t>(haversine_gt<f16_converted_t>{}), isa_t::auto_k};
|
|
517
|
+
case accuracy_t::f32_k: return {pun_metric<f32_t>(haversine_gt<f32_t>{}), isa_t::auto_k};
|
|
518
|
+
case accuracy_t::f64_k: return {pun_metric<f64_t>(haversine_gt<f64_t>{}), isa_t::auto_k};
|
|
519
|
+
default: return {};
|
|
520
|
+
}
|
|
521
|
+
}
|
|
483
522
|
};
|
|
484
523
|
|
|
485
524
|
using auto_index_t = auto_index_gt<std::int64_t, std::uint32_t>;
|
package/src/bench.cpp
CHANGED
|
@@ -256,7 +256,7 @@ struct args_t {
|
|
|
256
256
|
std::string path_neighbors;
|
|
257
257
|
|
|
258
258
|
std::size_t connectivity = config_t::connectivity_default_k;
|
|
259
|
-
std::size_t expansion_add = config_t::
|
|
259
|
+
std::size_t expansion_add = config_t::expansion_add_default_k;
|
|
260
260
|
std::size_t expansion_search = config_t::expansion_search_default_k;
|
|
261
261
|
std::size_t threads = std::thread::hardware_concurrency();
|
|
262
262
|
|
|
@@ -273,6 +273,19 @@ struct args_t {
|
|
|
273
273
|
bool metric_haversine = false;
|
|
274
274
|
};
|
|
275
275
|
|
|
276
|
+
template <typename index_at, typename dataset_at> //
|
|
277
|
+
index_at type_punned_index_for_metric(dataset_at& dataset, args_t const& args, config_t config, accuracy_t accuracy) {
|
|
278
|
+
if (args.metric_ip)
|
|
279
|
+
return index_at::ip(dataset.dimensions(), accuracy, config);
|
|
280
|
+
if (args.metric_l2)
|
|
281
|
+
return index_at::l2(dataset.dimensions(), accuracy, config);
|
|
282
|
+
if (args.metric_cos)
|
|
283
|
+
return index_at::cos(dataset.dimensions(), accuracy, config);
|
|
284
|
+
if (args.metric_haversine)
|
|
285
|
+
return index_at::haversine(accuracy, config);
|
|
286
|
+
return index_at{};
|
|
287
|
+
}
|
|
288
|
+
|
|
276
289
|
template <typename index_at, typename dataset_at> //
|
|
277
290
|
void run_type_punned(dataset_at& dataset, args_t const& args, config_t config) {
|
|
278
291
|
|
|
@@ -282,16 +295,7 @@ void run_type_punned(dataset_at& dataset, args_t const& args, config_t config) {
|
|
|
282
295
|
if (args.quantize_i8)
|
|
283
296
|
accuracy = accuracy_t::i8q100_k;
|
|
284
297
|
|
|
285
|
-
index_at index;
|
|
286
|
-
if (args.metric_ip)
|
|
287
|
-
index = index_at::ip(dataset.dimensions(), accuracy, config);
|
|
288
|
-
if (args.metric_l2)
|
|
289
|
-
index = index_at::l2(dataset.dimensions(), accuracy, config);
|
|
290
|
-
if (args.metric_cos)
|
|
291
|
-
index = index_at::cos(dataset.dimensions(), accuracy, config);
|
|
292
|
-
if (args.metric_haversine)
|
|
293
|
-
index = index_at::haversine(accuracy, config);
|
|
294
|
-
|
|
298
|
+
index_at index{type_punned_index_for_metric<index_at>(dataset, args, config, accuracy)};
|
|
295
299
|
std::printf("- Hardware acceleration: %s\n", isa(index.acceleration()));
|
|
296
300
|
std::printf("-- Will benchmark in-memory\n");
|
|
297
301
|
|
package/src/lib.rs
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#[cxx::bridge]
|
|
2
|
+
pub mod ffi {
|
|
3
|
+
|
|
4
|
+
// Shared structs with fields visible to both languages.
|
|
5
|
+
struct Matches {
|
|
6
|
+
count: usize,
|
|
7
|
+
labels: Vec<u32>,
|
|
8
|
+
distances: Vec<f32>,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// C++ types and signatures exposed to Rust.
|
|
12
|
+
unsafe extern "C++" {
|
|
13
|
+
include!("rust.hpp");
|
|
14
|
+
|
|
15
|
+
type Index;
|
|
16
|
+
|
|
17
|
+
fn new_ip(dimensions: usize, quantization: &str, connectivity: usize, expansion_add: usize, expansion_search: usize) -> Result<UniquePtr<Index>>;
|
|
18
|
+
fn new_l2(dimensions: usize, quantization: &str, connectivity: usize, expansion_add: usize, expansion_search: usize) -> Result<UniquePtr<Index>>;
|
|
19
|
+
fn new_cos(dimensions: usize, quantization: &str, connectivity: usize, expansion_add: usize, expansion_search: usize) -> Result<UniquePtr<Index>>;
|
|
20
|
+
fn new_haversine(quantization: &str, connectivity: usize, expansion_add: usize, expansion_search: usize) -> Result<UniquePtr<Index>>;
|
|
21
|
+
|
|
22
|
+
fn reserve(self: &Index, capacity: usize) -> Result<()>;
|
|
23
|
+
|
|
24
|
+
fn dimensions(self: &Index) -> usize;
|
|
25
|
+
fn connectivity(self: &Index) -> usize;
|
|
26
|
+
fn size(self: &Index) -> usize;
|
|
27
|
+
fn capacity(self: &Index) -> usize;
|
|
28
|
+
|
|
29
|
+
fn add(self: &Index, label: u32, vector: &[f32]) -> Result<()>;
|
|
30
|
+
fn search(self: &Index, query: &[f32], count: usize) -> Result<Matches>;
|
|
31
|
+
|
|
32
|
+
fn add_in_thread(self: &Index, label: u32, vector: &[f32], thread: usize) -> Result<()>;
|
|
33
|
+
fn search_in_thread(self: &Index, query: &[f32], count: usize, thread: usize) -> Result<Matches>;
|
|
34
|
+
|
|
35
|
+
fn save(self: &Index, path: &str) -> Result<()>;
|
|
36
|
+
fn load(self: &Index, path: &str) -> Result<()>;
|
|
37
|
+
fn view(self: &Index, path: &str) -> Result<()>;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
#[cfg(test)]
|
|
43
|
+
mod tests {
|
|
44
|
+
use crate::ffi::new_ip;
|
|
45
|
+
use crate::ffi::new_l2;
|
|
46
|
+
use crate::ffi::new_cos;
|
|
47
|
+
use crate::ffi::new_haversine;
|
|
48
|
+
|
|
49
|
+
#[test]
|
|
50
|
+
fn integration() {
|
|
51
|
+
|
|
52
|
+
let quant = "f16";
|
|
53
|
+
let index = new_ip(5, &quant, 0, 0, 0).unwrap();
|
|
54
|
+
|
|
55
|
+
assert!(index.reserve(10).is_ok());
|
|
56
|
+
assert!(index.capacity() >= 10);
|
|
57
|
+
assert!(index.connectivity() != 0);
|
|
58
|
+
assert_eq!(index.dimensions(), 5);
|
|
59
|
+
assert_eq!(index.size(), 0);
|
|
60
|
+
|
|
61
|
+
let first: [f32; 5] = [0.2, 0.1, 0.2, 0.1, 0.3];
|
|
62
|
+
let second: [f32; 5] = [0.2, 0.1, 0.2, 0.1, 0.3];
|
|
63
|
+
|
|
64
|
+
assert!(index.add(42, &first).is_ok());
|
|
65
|
+
assert!(index.add(43, &second).is_ok());
|
|
66
|
+
assert_eq!(index.size(), 2);
|
|
67
|
+
|
|
68
|
+
// Read back the tags
|
|
69
|
+
let results = index.search(&first, 10).unwrap();
|
|
70
|
+
assert_eq!(results.count, 2);
|
|
71
|
+
|
|
72
|
+
// Validate serialization
|
|
73
|
+
assert!(index.save("index.rust.usearch").is_ok());
|
|
74
|
+
assert!(index.load("index.rust.usearch").is_ok());
|
|
75
|
+
assert!(index.view("index.rust.usearch").is_ok());
|
|
76
|
+
|
|
77
|
+
// Make sure every function is called at least once
|
|
78
|
+
assert!(new_ip(5, &quant, 0, 0, 0).is_ok());
|
|
79
|
+
assert!(new_l2(5, &quant, 0, 0, 0).is_ok());
|
|
80
|
+
assert!(new_cos(5, &quant, 0, 0, 0).is_ok());
|
|
81
|
+
assert!(new_haversine(&quant, 0, 0, 0).is_ok());
|
|
82
|
+
}
|
|
83
|
+
}
|
package/src/python.cpp
CHANGED
|
@@ -195,15 +195,15 @@ PYBIND11_MODULE(usearch, m) {
|
|
|
195
195
|
|
|
196
196
|
auto i = py::class_<native_index_t>(m, "Index");
|
|
197
197
|
|
|
198
|
-
i.def(py::init(&make_index),
|
|
199
|
-
py::kw_only(),
|
|
200
|
-
py::arg("dim"),
|
|
201
|
-
py::arg("capacity") = 0,
|
|
202
|
-
py::arg("dtype") = std::string("f32"),
|
|
203
|
-
py::arg("metric") = std::string("ip"),
|
|
204
|
-
py::arg("connectivity") = config_t::connectivity_default_k,
|
|
205
|
-
py::arg("expansion_add") = config_t::
|
|
206
|
-
py::arg("expansion_search") = config_t::expansion_search_default_k
|
|
198
|
+
i.def(py::init(&make_index), //
|
|
199
|
+
py::kw_only(), //
|
|
200
|
+
py::arg("dim"), //
|
|
201
|
+
py::arg("capacity") = 0, //
|
|
202
|
+
py::arg("dtype") = std::string("f32"), //
|
|
203
|
+
py::arg("metric") = std::string("ip"), //
|
|
204
|
+
py::arg("connectivity") = config_t::connectivity_default_k, //
|
|
205
|
+
py::arg("expansion_add") = config_t::expansion_add_default_k, //
|
|
206
|
+
py::arg("expansion_search") = config_t::expansion_search_default_k //
|
|
207
207
|
);
|
|
208
208
|
|
|
209
209
|
i.def( //
|
package/src/rust.cpp
CHANGED
|
@@ -1,23 +1,72 @@
|
|
|
1
|
-
#include "rust.
|
|
2
|
-
|
|
3
|
-
#include "advanced.hpp"
|
|
1
|
+
#include "usearch/src/rust.hpp"
|
|
2
|
+
#include "usearch/src/lib.rs.h"
|
|
4
3
|
|
|
5
4
|
using namespace unum::usearch;
|
|
6
5
|
using namespace unum;
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
using native_index_t = typename Index::native_index_t;
|
|
8
|
+
|
|
9
|
+
Index::Index(std::shared_ptr<native_index_t> index) : index_(index) {}
|
|
10
|
+
|
|
11
|
+
void Index::add_in_thread(uint32_t label, rust::Slice<float const> vector, size_t thread) const {
|
|
12
|
+
index_->add(label, vector.data(), thread, true);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
Matches Index::search_in_thread(rust::Slice<float const> vector, size_t count, size_t thread) const {
|
|
16
|
+
Matches results;
|
|
17
|
+
results.labels.reserve(count);
|
|
18
|
+
results.distances.reserve(count);
|
|
19
|
+
for (size_t i = 0; i != count; ++i)
|
|
20
|
+
results.labels.push_back(0), results.distances.push_back(0);
|
|
21
|
+
results.count = index_->search(vector.data(), count, results.labels.data(), results.distances.data(), thread);
|
|
22
|
+
results.labels.truncate(results.count);
|
|
23
|
+
results.distances.truncate(results.count);
|
|
24
|
+
return results;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
void Index::add(uint32_t label, rust::Slice<float const> vector) const { return add_in_thread(label, vector, 0); }
|
|
28
|
+
Matches Index::search(rust::Slice<float const> vector, size_t count) const {
|
|
29
|
+
return search_in_thread(vector, count, 0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
void Index::reserve(size_t capacity) const { index_->reserve(capacity); }
|
|
33
|
+
|
|
34
|
+
size_t Index::dimensions() const { return index_->dimensions(); }
|
|
35
|
+
size_t Index::connectivity() const { return index_->connectivity(); }
|
|
36
|
+
size_t Index::size() const { return index_->size(); }
|
|
37
|
+
size_t Index::capacity() const { return index_->capacity(); }
|
|
38
|
+
|
|
39
|
+
void Index::save(rust::Str path) const { index_->save(std::string(path).c_str()); }
|
|
40
|
+
void Index::load(rust::Str path) const { index_->load(std::string(path).c_str()); }
|
|
41
|
+
void Index::view(rust::Str path) const { index_->view(std::string(path).c_str()); }
|
|
42
|
+
|
|
43
|
+
accuracy_t accuracy(rust::Str quant) { return accuracy(quant.data(), quant.size()); }
|
|
44
|
+
|
|
45
|
+
std::unique_ptr<Index> wrap(native_index_t&& index) {
|
|
46
|
+
std::shared_ptr<native_index_t> native = std::make_shared<native_index_t>(std::move(index));
|
|
47
|
+
return std::unique_ptr<Index>(new Index(native));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
config_t config(size_t connectivity, size_t exp_add, size_t exp_search) {
|
|
51
|
+
config_t result;
|
|
52
|
+
result.connectivity = connectivity ? connectivity : config_t::connectivity_default_k;
|
|
53
|
+
result.expansion_add = exp_add ? exp_add : config_t::expansion_add_default_k;
|
|
54
|
+
result.expansion_search = exp_search ? exp_search : config_t::expansion_search_default_k;
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
12
57
|
|
|
13
|
-
|
|
58
|
+
std::unique_ptr<Index> new_ip(size_t dims, rust::Str quant, size_t connectivity, size_t exp_add, size_t exp_search) {
|
|
59
|
+
return wrap(native_index_t::ip(dims, accuracy(quant), config(connectivity, exp_add, exp_search)));
|
|
60
|
+
}
|
|
14
61
|
|
|
15
|
-
|
|
16
|
-
|
|
62
|
+
std::unique_ptr<Index> new_l2(size_t dims, rust::Str quant, size_t connectivity, size_t exp_add, size_t exp_search) {
|
|
63
|
+
return wrap(native_index_t::l2(dims, accuracy(quant), config(connectivity, exp_add, exp_search)));
|
|
64
|
+
}
|
|
17
65
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
uint64_t Index::capacity() const { return impl->native_.capacity(); }
|
|
66
|
+
std::unique_ptr<Index> new_cos(size_t dims, rust::Str quant, size_t connectivity, size_t exp_add, size_t exp_search) {
|
|
67
|
+
return wrap(native_index_t::cos(dims, accuracy(quant), config(connectivity, exp_add, exp_search)));
|
|
68
|
+
}
|
|
22
69
|
|
|
23
|
-
std::unique_ptr<Index>
|
|
70
|
+
std::unique_ptr<Index> new_haversine(rust::Str quant, size_t connectivity, size_t exp_add, size_t exp_search) {
|
|
71
|
+
return wrap(native_index_t::haversine(accuracy(quant), config(connectivity, exp_add, exp_search)));
|
|
72
|
+
}
|
package/src/rust.hpp
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include "rust/cxx.h"
|
|
3
|
+
|
|
4
|
+
#include <memory> // `std::shared_ptr`
|
|
5
|
+
|
|
6
|
+
#include "advanced.hpp"
|
|
7
|
+
|
|
8
|
+
struct Matches;
|
|
9
|
+
|
|
10
|
+
enum class Metric;
|
|
11
|
+
|
|
12
|
+
enum class Accuracy;
|
|
13
|
+
|
|
14
|
+
class Index {
|
|
15
|
+
public:
|
|
16
|
+
using label_t = std::uint32_t;
|
|
17
|
+
using distance_t = unum::usearch::punned_distance_t;
|
|
18
|
+
using native_index_t = unum::usearch::auto_index_gt<label_t>;
|
|
19
|
+
|
|
20
|
+
Index(std::shared_ptr<native_index_t> index);
|
|
21
|
+
|
|
22
|
+
void reserve(size_t) const;
|
|
23
|
+
|
|
24
|
+
void add(uint32_t label, rust::Slice<float const> vector) const;
|
|
25
|
+
void add_in_thread(uint32_t label, rust::Slice<float const> vector, size_t thread) const;
|
|
26
|
+
|
|
27
|
+
Matches search(rust::Slice<float const> vector, size_t count) const;
|
|
28
|
+
Matches search_in_thread(rust::Slice<float const> vector, size_t count, size_t thread) const;
|
|
29
|
+
|
|
30
|
+
size_t dimensions() const;
|
|
31
|
+
size_t connectivity() const;
|
|
32
|
+
size_t size() const;
|
|
33
|
+
size_t capacity() const;
|
|
34
|
+
|
|
35
|
+
void save(rust::Str path) const;
|
|
36
|
+
void load(rust::Str path) const;
|
|
37
|
+
void view(rust::Str path) const;
|
|
38
|
+
|
|
39
|
+
private:
|
|
40
|
+
std::shared_ptr<native_index_t> index_;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
std::unique_ptr<Index> new_ip(size_t dims, rust::Str quant, size_t connectivity, size_t exp_add, size_t exp_search);
|
|
44
|
+
std::unique_ptr<Index> new_l2(size_t dims, rust::Str quant, size_t connectivity, size_t exp_add, size_t exp_search);
|
|
45
|
+
std::unique_ptr<Index> new_cos(size_t dims, rust::Str quant, size_t connectivity, size_t exp_add, size_t exp_search);
|
|
46
|
+
std::unique_ptr<Index> new_haversine(rust::Str quant, size_t connectivity, size_t exp_add, size_t exp_search);
|
package/src/main.rs
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
#[cxx::bridge]
|
|
2
|
-
mod ffi {
|
|
3
|
-
|
|
4
|
-
// Shared structs with fields visible to both languages.
|
|
5
|
-
struct Results {
|
|
6
|
-
size: usize,
|
|
7
|
-
labels: Vec<u32>,
|
|
8
|
-
distances: Vec<f32>,
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// C++ types and signatures exposed to Rust.
|
|
12
|
-
unsafe extern "C++" {
|
|
13
|
-
include!("rust.h");
|
|
14
|
-
|
|
15
|
-
type Index;
|
|
16
|
-
|
|
17
|
-
fn new_index() -> UniquePtr<Index>;
|
|
18
|
-
|
|
19
|
-
fn dim(&self) -> u64;
|
|
20
|
-
fn connectivity(&self) -> u64;
|
|
21
|
-
fn size(&self) -> u64;
|
|
22
|
-
fn capacity(&self) -> u64;
|
|
23
|
-
|
|
24
|
-
fn add(&self, label: u64, vector: &[f32]);
|
|
25
|
-
fn search(&self, query: &[f32], count: u64) -> Results;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
fn main() {
|
|
31
|
-
let client = ffi::new_index();
|
|
32
|
-
|
|
33
|
-
// Upload a blob.
|
|
34
|
-
let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()];
|
|
35
|
-
let mut buf = MultiBuf { chunks, pos: 0 };
|
|
36
|
-
let blobid = client.add(&mut buf);
|
|
37
|
-
println!("blobid = {}", blobid);
|
|
38
|
-
|
|
39
|
-
// Read back the tags.
|
|
40
|
-
let search = client.search(blobid);
|
|
41
|
-
println!("tags = {:?}", search.tags);
|
|
42
|
-
}
|
package/src/rust.h
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
#include "rust/cxx.h"
|
|
3
|
-
|
|
4
|
-
struct Results;
|
|
5
|
-
|
|
6
|
-
class Index {
|
|
7
|
-
public:
|
|
8
|
-
Index();
|
|
9
|
-
void add(uint64_t label, rust::Slice<float const> vector) const;
|
|
10
|
-
Results search(rust::Slice<float const> vector, uint64_t count) const;
|
|
11
|
-
|
|
12
|
-
uint64_t dim() const;
|
|
13
|
-
uint64_t connectivity() const;
|
|
14
|
-
uint64_t size() const;
|
|
15
|
-
uint64_t capacity() const;
|
|
16
|
-
|
|
17
|
-
private:
|
|
18
|
-
class impl;
|
|
19
|
-
std::shared_ptr<impl> impl;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
std::unique_ptr<Index> new_index();
|