usearch 2.6.1 → 2.7.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/. npmignore +14 -0
- package/.cmake-format.py +19 -0
- package/.github/workflows/Dockerfile.libs +2 -2
- package/.github/workflows/prerelease.yml +85 -64
- package/.github/workflows/release.yml +17 -15
- package/.gitignore +1 -1
- package/.vscode/launch.json +47 -14
- package/.vscode/settings.json +7 -0
- package/.vscode/tasks.json +44 -8
- package/CITATION.cff +1 -1
- package/CMakeLists.txt +218 -67
- package/Cargo.toml +1 -1
- package/README.md +1 -1
- package/binding.gyp +3 -2
- package/build.gradle +19 -10
- package/c/CMakeLists.txt +21 -48
- package/c/README.md +5 -5
- package/c/lib.cpp +52 -7
- package/c/test.c +137 -94
- package/c/usearch.h +42 -0
- package/cmake/clipp.cmake +0 -1
- package/cmake/jemalloc.cmake +21 -37
- package/conanfile.py +1 -1
- package/cpp/CMakeLists.txt +14 -123
- package/cpp/bench.cpp +0 -12
- package/cpp/test.cpp +29 -1
- package/csharp/nuget/nuget-package.props +1 -1
- package/docs/compilation.md +23 -16
- package/docs/conf.py +1 -1
- package/golang/README.md +52 -51
- package/golang/lib.go +1 -1
- package/golang/lib_test.go +4 -1
- package/include/usearch/index.hpp +3 -3
- package/include/usearch/index_plugins.hpp +345 -160
- package/javascript/lib.cpp +204 -250
- package/javascript/usearch.js +440 -2
- package/javascript/usearch.test.js +37 -0
- package/package.json +10 -5
- package/python/lib.cpp +95 -224
- package/python/scripts/bench_exact.py +2 -2
- package/python/scripts/test_jit.py +2 -13
- package/python/scripts/test_sparse.py +1 -1
- package/python/scripts/test_tooling.py +15 -5
- package/rust/lib.cpp +7 -4
- package/rust/lib.hpp +2 -0
- package/rust/lib.rs +128 -8
- package/setup.py +14 -4
- package/wasm/CMakeLists.txt +4 -12
- package/wasm/lib.cpp +1 -1
- package/wasmer.toml +1 -1
- package/javascript/docs.js +0 -111
- package/javascript/test.js +0 -32
- package/javascript/usearch.d.ts +0 -110
- package/tsconfig.json +0 -8
package/python/lib.cpp
CHANGED
|
@@ -32,17 +32,6 @@
|
|
|
32
32
|
using namespace unum::usearch;
|
|
33
33
|
using namespace unum;
|
|
34
34
|
|
|
35
|
-
/**
|
|
36
|
-
* @brief The signature of the user-defined function.
|
|
37
|
-
* Can be just two array pointers, precompiled for a specific array length,
|
|
38
|
-
* or include one or two array sizes as 64-bit unsigned integers.
|
|
39
|
-
*/
|
|
40
|
-
enum class metric_signature_t {
|
|
41
|
-
array_array_k = 0,
|
|
42
|
-
array_array_size_k,
|
|
43
|
-
array_size_array_size_k,
|
|
44
|
-
};
|
|
45
|
-
|
|
46
35
|
namespace py = pybind11;
|
|
47
36
|
using py_shape_t = py::array::ShapeContainer;
|
|
48
37
|
|
|
@@ -59,6 +48,7 @@ using dense_clustering_result_t = typename index_dense_t::clustering_result_t;
|
|
|
59
48
|
using progress_func_t = std::function<bool(std::size_t /*processed*/, std::size_t /*total*/)>;
|
|
60
49
|
|
|
61
50
|
struct progress_t {
|
|
51
|
+
inline progress_t(nullptr_t = nullptr) : func_(&dummy_progress) {}
|
|
62
52
|
inline progress_t(progress_func_t const& func) : func_(func ? func : &dummy_progress) {}
|
|
63
53
|
inline bool operator()(std::size_t processed, std::size_t total) const noexcept { return func_(processed, total); }
|
|
64
54
|
|
|
@@ -114,70 +104,16 @@ struct dense_indexes_py_t {
|
|
|
114
104
|
}
|
|
115
105
|
};
|
|
116
106
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
case metric_signature_t::array_array_k:
|
|
128
|
-
stl_function = [metric_uintptr](byte_t const* a, byte_t const* b) -> distance_t {
|
|
129
|
-
using metric_raw_t = distance_punned_t (*)(scalar_at const*, scalar_at const*);
|
|
130
|
-
metric_raw_t metric_ptr = reinterpret_cast<metric_raw_t>(metric_uintptr);
|
|
131
|
-
return metric_ptr((scalar_at const*)a, (scalar_at const*)b);
|
|
132
|
-
};
|
|
133
|
-
break;
|
|
134
|
-
case metric_signature_t::array_array_size_k:
|
|
135
|
-
stl_function = [metric_uintptr, scalar_words](byte_t const* a, byte_t const* b) -> distance_t {
|
|
136
|
-
using metric_raw_t = distance_punned_t (*)(scalar_at const*, scalar_at const*, size_t);
|
|
137
|
-
metric_raw_t metric_ptr = reinterpret_cast<metric_raw_t>(metric_uintptr);
|
|
138
|
-
return metric_ptr((scalar_at const*)a, (scalar_at const*)b, scalar_words);
|
|
139
|
-
};
|
|
140
|
-
break;
|
|
141
|
-
case metric_signature_t::array_size_array_size_k:
|
|
142
|
-
stl_function = [metric_uintptr, scalar_words](byte_t const* a, byte_t const* b) -> distance_t {
|
|
143
|
-
using metric_raw_t = distance_punned_t (*)(scalar_at const*, size_t, scalar_at const*, size_t);
|
|
144
|
-
metric_raw_t metric_ptr = reinterpret_cast<metric_raw_t>(metric_uintptr);
|
|
145
|
-
return metric_ptr((scalar_at const*)a, scalar_words, (scalar_at const*)b, scalar_words);
|
|
146
|
-
};
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
return metric_t(stl_function, dimensions, kind, scalar_kind);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
metric_t wrap_user_defined_metric( //
|
|
153
|
-
metric_kind_t kind, metric_signature_t signature, std::uintptr_t metric_uintptr, //
|
|
154
|
-
scalar_kind_t scalar_kind, std::size_t dimensions) {
|
|
155
|
-
|
|
156
|
-
switch (scalar_kind) {
|
|
157
|
-
case scalar_kind_t::b1x8_k:
|
|
158
|
-
return wrap_typed_user_defined_metric<b1x8_t>(kind, signature, metric_uintptr, scalar_kind, dimensions);
|
|
159
|
-
case scalar_kind_t::i8_k:
|
|
160
|
-
return wrap_typed_user_defined_metric<i8_t>(kind, signature, metric_uintptr, scalar_kind, dimensions);
|
|
161
|
-
case scalar_kind_t::f16_k:
|
|
162
|
-
return wrap_typed_user_defined_metric<f16_t>(kind, signature, metric_uintptr, scalar_kind, dimensions);
|
|
163
|
-
case scalar_kind_t::f32_k:
|
|
164
|
-
return wrap_typed_user_defined_metric<f32_t>(kind, signature, metric_uintptr, scalar_kind, dimensions);
|
|
165
|
-
case scalar_kind_t::f64_k:
|
|
166
|
-
return wrap_typed_user_defined_metric<f64_t>(kind, signature, metric_uintptr, scalar_kind, dimensions);
|
|
167
|
-
default: return {};
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
static dense_index_py_t make_index( //
|
|
172
|
-
std::size_t dimensions, //
|
|
173
|
-
scalar_kind_t scalar_kind, //
|
|
174
|
-
std::size_t connectivity, //
|
|
175
|
-
std::size_t expansion_add, //
|
|
176
|
-
std::size_t expansion_search, //
|
|
177
|
-
metric_kind_t metric_kind, //
|
|
178
|
-
metric_signature_t metric_signature, //
|
|
179
|
-
std::uintptr_t metric_uintptr, //
|
|
180
|
-
bool multi, //
|
|
107
|
+
static dense_index_py_t make_index( //
|
|
108
|
+
std::size_t dimensions, //
|
|
109
|
+
scalar_kind_t scalar_kind, //
|
|
110
|
+
std::size_t connectivity, //
|
|
111
|
+
std::size_t expansion_add, //
|
|
112
|
+
std::size_t expansion_search, //
|
|
113
|
+
metric_kind_t metric_kind, //
|
|
114
|
+
metric_punned_signature_t metric_signature, //
|
|
115
|
+
std::uintptr_t metric_uintptr, //
|
|
116
|
+
bool multi, //
|
|
181
117
|
bool enable_key_lookups) {
|
|
182
118
|
|
|
183
119
|
index_dense_config_t config(connectivity, expansion_add, expansion_search);
|
|
@@ -186,7 +122,7 @@ static dense_index_py_t make_index( //
|
|
|
186
122
|
|
|
187
123
|
metric_t metric = //
|
|
188
124
|
metric_uintptr //
|
|
189
|
-
?
|
|
125
|
+
? metric_t(dimensions, metric_uintptr, metric_signature, metric_kind, scalar_kind)
|
|
190
126
|
: metric_t(dimensions, metric_kind, scalar_kind);
|
|
191
127
|
return index_dense_t::make(metric, config);
|
|
192
128
|
}
|
|
@@ -509,105 +445,13 @@ static py::tuple search_many_in_index( //
|
|
|
509
445
|
return results;
|
|
510
446
|
}
|
|
511
447
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
auto keys_py2d = keys_py.template mutable_unchecked<2>();
|
|
520
|
-
auto distances_py2d = distances_py.template mutable_unchecked<2>();
|
|
521
|
-
auto counts_py1d = counts_py.template mutable_unchecked<1>();
|
|
522
|
-
|
|
523
|
-
std::size_t dataset_count = static_cast<std::size_t>(dataset_info.shape[0]);
|
|
524
|
-
std::size_t queries_count = static_cast<std::size_t>(queries_info.shape[0]);
|
|
525
|
-
|
|
526
|
-
byte_t const* dataset_data = reinterpret_cast<byte_t const*>(dataset_info.ptr);
|
|
527
|
-
byte_t const* queries_data = reinterpret_cast<byte_t const*>(queries_info.ptr);
|
|
528
|
-
for (std::size_t query_idx = 0; query_idx != queries_count; ++query_idx)
|
|
529
|
-
counts_py1d(query_idx) = wanted;
|
|
530
|
-
|
|
531
|
-
if (!threads)
|
|
532
|
-
threads = std::thread::hardware_concurrency();
|
|
533
|
-
|
|
534
|
-
std::size_t tasks_count = static_cast<std::size_t>(dataset_count * queries_count);
|
|
535
|
-
executor_default_t executor{threads};
|
|
536
|
-
|
|
537
|
-
// Progress status
|
|
538
|
-
progress_t progress_{progress};
|
|
539
|
-
std::atomic<std::size_t> processed{0};
|
|
540
|
-
|
|
541
|
-
struct dense_key_and_distance_t {
|
|
542
|
-
u32_t offset;
|
|
543
|
-
f32_t distance;
|
|
544
|
-
};
|
|
545
|
-
auto less = [](dense_key_and_distance_t const& a, dense_key_and_distance_t const& b) {
|
|
546
|
-
return a.distance < b.distance;
|
|
547
|
-
};
|
|
548
|
-
|
|
549
|
-
// Allocate temporary memory to store the distance matrix
|
|
550
|
-
// Previous version didn't need temporary memory, but the performance was much lower.
|
|
551
|
-
// In the new design we keep two buffers - original and transposed, as in-place transpositions
|
|
552
|
-
// of non-rectangular matrixes is expensive.
|
|
553
|
-
std::vector<dense_key_and_distance_t> keys_and_distances(tasks_count * 2);
|
|
554
|
-
dense_key_and_distance_t* keys_and_distances_per_dataset = keys_and_distances.data();
|
|
555
|
-
dense_key_and_distance_t* keys_and_distances_per_query = keys_and_distances_per_dataset + tasks_count;
|
|
556
|
-
executor.dynamic(dataset_count, [&](std::size_t thread_idx, std::size_t dataset_idx) {
|
|
557
|
-
byte_t const* dataset = dataset_data + dataset_idx * dataset_info.strides[0];
|
|
558
|
-
for (std::size_t query_idx = 0; query_idx != queries_count; ++query_idx) {
|
|
559
|
-
byte_t const* query = queries_data + query_idx * queries_info.strides[0];
|
|
560
|
-
distance_t distance = metric(dataset, query);
|
|
561
|
-
std::size_t task_idx = dataset_idx * queries_count + query_idx;
|
|
562
|
-
keys_and_distances_per_dataset[task_idx].offset = static_cast<u32_t>(dataset_idx);
|
|
563
|
-
keys_and_distances_per_dataset[task_idx].distance = static_cast<f32_t>(distance);
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
// We don't want to check for signals from multiple threads
|
|
567
|
-
processed += queries_count;
|
|
568
|
-
if (thread_idx == 0)
|
|
569
|
-
if (PyErr_CheckSignals() != 0 || !progress_(processed.load(), tasks_count))
|
|
570
|
-
return false;
|
|
571
|
-
return true;
|
|
572
|
-
});
|
|
573
|
-
if (processed.load() != tasks_count)
|
|
574
|
-
return;
|
|
575
|
-
|
|
576
|
-
// Transpose in a single thread to avoid contention writing into the same memory buffers
|
|
577
|
-
for (std::size_t query_idx = 0; query_idx != queries_count; ++query_idx) {
|
|
578
|
-
for (std::size_t dataset_idx = 0; dataset_idx != dataset_count; ++dataset_idx) {
|
|
579
|
-
std::size_t from_idx = queries_count * dataset_idx + query_idx;
|
|
580
|
-
std::size_t to_idx = dataset_count * query_idx + dataset_idx;
|
|
581
|
-
keys_and_distances_per_query[to_idx] = keys_and_distances_per_dataset[from_idx];
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
// Partial-sort every query result
|
|
586
|
-
executor.fixed(queries_count, [&](std::size_t, std::size_t query_idx) {
|
|
587
|
-
dense_key_t* keys = &keys_py2d(query_idx, 0);
|
|
588
|
-
distance_t* distances = &distances_py2d(query_idx, 0);
|
|
589
|
-
auto start = keys_and_distances_per_query + dataset_count * query_idx;
|
|
590
|
-
if (wanted > 1) {
|
|
591
|
-
std::partial_sort(start, start + wanted, start + dataset_count, less);
|
|
592
|
-
for (std::size_t i = 0; i != wanted; ++i)
|
|
593
|
-
keys[i] = static_cast<dense_key_t>(start[i].offset), distances[i] = start[i].distance;
|
|
594
|
-
} else {
|
|
595
|
-
auto max_it = std::max_element(start, start + dataset_count, less);
|
|
596
|
-
keys[0] = static_cast<dense_key_t>(max_it->offset), distances[0] = max_it->distance;
|
|
597
|
-
}
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
// At the end report the latest numbers, because the reporter thread may be finished earlier
|
|
601
|
-
progress_(processed.load(), tasks_count);
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
static py::tuple search_many_brute_force( //
|
|
605
|
-
py::buffer dataset, py::buffer queries, //
|
|
606
|
-
std::size_t wanted, std::size_t threads, //
|
|
607
|
-
metric_kind_t metric_kind, //
|
|
608
|
-
metric_signature_t metric_signature, //
|
|
609
|
-
std::uintptr_t metric_uintptr, //
|
|
610
|
-
progress_func_t const& progress) {
|
|
448
|
+
static py::tuple search_many_brute_force( //
|
|
449
|
+
py::buffer dataset, py::buffer queries, //
|
|
450
|
+
std::size_t wanted, std::size_t threads, //
|
|
451
|
+
metric_kind_t metric_kind, //
|
|
452
|
+
metric_punned_signature_t metric_signature, //
|
|
453
|
+
std::uintptr_t metric_uintptr, //
|
|
454
|
+
progress_func_t const& progress_func) {
|
|
611
455
|
|
|
612
456
|
if (wanted == 0)
|
|
613
457
|
return py::tuple(5);
|
|
@@ -617,10 +461,13 @@ static py::tuple search_many_brute_force( //
|
|
|
617
461
|
if (dataset_info.ndim != 2 || queries_info.ndim != 2)
|
|
618
462
|
throw std::invalid_argument("Expects a matrix of dataset to add!");
|
|
619
463
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
464
|
+
std::size_t dataset_count = static_cast<std::size_t>(dataset_info.shape[0]);
|
|
465
|
+
std::size_t dataset_dimensions = static_cast<std::size_t>(dataset_info.shape[1]);
|
|
466
|
+
std::size_t dataset_stride = static_cast<std::size_t>(dataset_info.strides[0]);
|
|
467
|
+
std::size_t queries_stride = static_cast<std::size_t>(queries_info.strides[0]);
|
|
468
|
+
std::size_t queries_count = static_cast<std::size_t>(queries_info.shape[0]);
|
|
469
|
+
std::size_t queries_dimensions = static_cast<std::size_t>(queries_info.shape[1]);
|
|
470
|
+
|
|
624
471
|
if (dataset_dimensions != queries_dimensions)
|
|
625
472
|
throw std::invalid_argument("The number of vector dimensions doesn't match!");
|
|
626
473
|
if (wanted > dataset_count)
|
|
@@ -631,26 +478,51 @@ static py::tuple search_many_brute_force( //
|
|
|
631
478
|
if (dataset_kind != queries_kind)
|
|
632
479
|
throw std::invalid_argument("The types of vectors don't match!");
|
|
633
480
|
|
|
634
|
-
py::array_t<dense_key_t> keys_py({queries_count, static_cast<Py_ssize_t>(wanted)});
|
|
635
|
-
py::array_t<distance_t> distances_py({queries_count, static_cast<Py_ssize_t>(wanted)});
|
|
636
|
-
py::array_t<Py_ssize_t> counts_py(queries_count);
|
|
637
|
-
|
|
638
481
|
std::size_t dimensions = static_cast<std::size_t>(queries_dimensions);
|
|
639
482
|
metric_t metric = //
|
|
640
483
|
metric_uintptr //
|
|
641
|
-
?
|
|
484
|
+
? metric_t(dimensions, metric_uintptr, metric_signature, metric_kind, queries_kind)
|
|
642
485
|
: metric_t(dimensions, metric_kind, queries_kind);
|
|
643
486
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
487
|
+
py::array_t<dense_key_t> keys_py({static_cast<Py_ssize_t>(queries_count), static_cast<Py_ssize_t>(wanted)});
|
|
488
|
+
py::array_t<distance_t> distances_py({static_cast<Py_ssize_t>(queries_count), static_cast<Py_ssize_t>(wanted)});
|
|
489
|
+
py::array_t<Py_ssize_t> counts_py(static_cast<Py_ssize_t>(queries_count));
|
|
490
|
+
|
|
491
|
+
auto keys_py2d = keys_py.template mutable_unchecked<2>();
|
|
492
|
+
auto distances_py2d = distances_py.template mutable_unchecked<2>();
|
|
493
|
+
auto counts_py1d = counts_py.template mutable_unchecked<1>();
|
|
494
|
+
|
|
495
|
+
byte_t const* dataset_data = reinterpret_cast<byte_t const*>(dataset_info.ptr);
|
|
496
|
+
byte_t const* queries_data = reinterpret_cast<byte_t const*>(queries_info.ptr);
|
|
497
|
+
for (std::size_t query_idx = 0; query_idx != queries_count; ++query_idx)
|
|
498
|
+
counts_py1d(query_idx) = wanted;
|
|
499
|
+
|
|
500
|
+
if (!threads)
|
|
501
|
+
threads = std::thread::hardware_concurrency();
|
|
502
|
+
|
|
503
|
+
// Dispatch brute-force search
|
|
504
|
+
progress_t progress{progress_func};
|
|
505
|
+
executor_default_t executor{threads};
|
|
506
|
+
exact_search_t search;
|
|
507
|
+
|
|
508
|
+
exact_search_results_t offsets_and_distances = search( //
|
|
509
|
+
dataset_data, dataset_count, dataset_stride, //
|
|
510
|
+
queries_data, queries_count, queries_stride, //
|
|
511
|
+
wanted, metric, executor,
|
|
512
|
+
[&](std::size_t passed, std::size_t total) { return PyErr_CheckSignals() == 0 && progress(passed, total); });
|
|
513
|
+
|
|
514
|
+
if (!offsets_and_distances)
|
|
515
|
+
throw std::bad_alloc();
|
|
516
|
+
|
|
517
|
+
// Export the results
|
|
518
|
+
for (std::size_t query_idx = 0; query_idx != queries_count; ++query_idx) {
|
|
519
|
+
dense_key_t* query_keys = &keys_py2d(query_idx, 0);
|
|
520
|
+
distance_t* query_distances = &distances_py2d(query_idx, 0);
|
|
521
|
+
auto query_result = offsets_and_distances.at(query_idx);
|
|
522
|
+
for (std::size_t i = 0; i != wanted; ++i)
|
|
523
|
+
query_keys[i] = static_cast<dense_key_t>(query_result[i].offset),
|
|
524
|
+
query_distances[i] = query_result[i].distance;
|
|
652
525
|
}
|
|
653
|
-
// clang-format on
|
|
654
526
|
|
|
655
527
|
py::tuple results(5);
|
|
656
528
|
results[0] = keys_py;
|
|
@@ -980,10 +852,9 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
980
852
|
m.attr("USES_SIMSIMD") = py::int_(USEARCH_USE_SIMSIMD);
|
|
981
853
|
m.attr("USES_NATIVE_F16") = py::int_(USEARCH_USE_NATIVE_F16);
|
|
982
854
|
|
|
983
|
-
py::enum_<
|
|
984
|
-
.value("ArrayArray",
|
|
985
|
-
.value("ArrayArraySize",
|
|
986
|
-
.value("ArraySizeArraySize", metric_signature_t::array_size_array_size_k);
|
|
855
|
+
py::enum_<metric_punned_signature_t>(m, "MetricSignature")
|
|
856
|
+
.value("ArrayArray", metric_punned_signature_t::array_array_k)
|
|
857
|
+
.value("ArrayArraySize", metric_punned_signature_t::array_array_size_k);
|
|
987
858
|
|
|
988
859
|
py::enum_<metric_kind_t>(m, "MetricKind")
|
|
989
860
|
.value("Unknown", metric_kind_t::unknown_k)
|
|
@@ -1032,16 +903,16 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
1032
903
|
return index_metadata(meta);
|
|
1033
904
|
});
|
|
1034
905
|
|
|
1035
|
-
m.def("exact_search", &search_many_brute_force,
|
|
1036
|
-
py::arg("dataset"),
|
|
1037
|
-
py::arg("queries"),
|
|
1038
|
-
py::arg("count") = 10,
|
|
1039
|
-
py::kw_only(),
|
|
1040
|
-
py::arg("threads") = 0,
|
|
1041
|
-
py::arg("metric_kind") = metric_kind_t::cos_k,
|
|
1042
|
-
py::arg("metric_signature") =
|
|
1043
|
-
py::arg("metric_pointer") = 0,
|
|
1044
|
-
py::arg("progress") = nullptr
|
|
906
|
+
m.def("exact_search", &search_many_brute_force, //
|
|
907
|
+
py::arg("dataset"), //
|
|
908
|
+
py::arg("queries"), //
|
|
909
|
+
py::arg("count") = 10, //
|
|
910
|
+
py::kw_only(), //
|
|
911
|
+
py::arg("threads") = 0, //
|
|
912
|
+
py::arg("metric_kind") = metric_kind_t::cos_k, //
|
|
913
|
+
py::arg("metric_signature") = metric_punned_signature_t::array_array_k, //
|
|
914
|
+
py::arg("metric_pointer") = 0, //
|
|
915
|
+
py::arg("progress") = nullptr //
|
|
1045
916
|
);
|
|
1046
917
|
|
|
1047
918
|
m.def(
|
|
@@ -1057,18 +928,18 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
1057
928
|
|
|
1058
929
|
auto i = py::class_<dense_index_py_t, std::shared_ptr<dense_index_py_t>>(m, "Index");
|
|
1059
930
|
|
|
1060
|
-
i.def(py::init(&make_index),
|
|
1061
|
-
py::kw_only(),
|
|
1062
|
-
py::arg("ndim") = 0,
|
|
1063
|
-
py::arg("dtype") = scalar_kind_t::f32_k,
|
|
1064
|
-
py::arg("connectivity") = default_connectivity(),
|
|
1065
|
-
py::arg("expansion_add") = default_expansion_add(),
|
|
1066
|
-
py::arg("expansion_search") = default_expansion_search(),
|
|
1067
|
-
py::arg("metric_kind") = metric_kind_t::cos_k,
|
|
1068
|
-
py::arg("metric_signature") =
|
|
1069
|
-
py::arg("metric_pointer") = 0,
|
|
1070
|
-
py::arg("multi") = false,
|
|
1071
|
-
py::arg("enable_key_lookups") = true
|
|
931
|
+
i.def(py::init(&make_index), //
|
|
932
|
+
py::kw_only(), //
|
|
933
|
+
py::arg("ndim") = 0, //
|
|
934
|
+
py::arg("dtype") = scalar_kind_t::f32_k, //
|
|
935
|
+
py::arg("connectivity") = default_connectivity(), //
|
|
936
|
+
py::arg("expansion_add") = default_expansion_add(), //
|
|
937
|
+
py::arg("expansion_search") = default_expansion_search(), //
|
|
938
|
+
py::arg("metric_kind") = metric_kind_t::cos_k, //
|
|
939
|
+
py::arg("metric_signature") = metric_punned_signature_t::array_array_k, //
|
|
940
|
+
py::arg("metric_pointer") = 0, //
|
|
941
|
+
py::arg("multi") = false, //
|
|
942
|
+
py::arg("enable_key_lookups") = true //
|
|
1072
943
|
);
|
|
1073
944
|
|
|
1074
945
|
i.def( //
|
|
@@ -1201,19 +1072,19 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
1201
1072
|
|
|
1202
1073
|
i.def(
|
|
1203
1074
|
"change_metric",
|
|
1204
|
-
[](dense_index_py_t& index, metric_kind_t metric_kind,
|
|
1075
|
+
[](dense_index_py_t& index, metric_kind_t metric_kind, metric_punned_signature_t metric_signature,
|
|
1205
1076
|
std::uintptr_t metric_uintptr) {
|
|
1206
1077
|
scalar_kind_t scalar_kind = index.scalar_kind();
|
|
1207
1078
|
std::size_t dimensions = index.dimensions();
|
|
1208
1079
|
metric_t metric = //
|
|
1209
1080
|
metric_uintptr //
|
|
1210
|
-
?
|
|
1081
|
+
? metric_t(dimensions, metric_uintptr, metric_signature, metric_kind, scalar_kind)
|
|
1211
1082
|
: metric_t(dimensions, metric_kind, scalar_kind);
|
|
1212
1083
|
index.change_metric(std::move(metric));
|
|
1213
1084
|
},
|
|
1214
|
-
py::arg("metric_kind") = metric_kind_t::cos_k,
|
|
1215
|
-
py::arg("metric_signature") =
|
|
1216
|
-
py::arg("metric_pointer") = 0
|
|
1085
|
+
py::arg("metric_kind") = metric_kind_t::cos_k, //
|
|
1086
|
+
py::arg("metric_signature") = metric_punned_signature_t::array_array_k, //
|
|
1087
|
+
py::arg("metric_pointer") = 0 //
|
|
1217
1088
|
);
|
|
1218
1089
|
|
|
1219
1090
|
i.def_property_readonly("hardware_acceleration", [](dense_index_py_t const& index) -> py::str {
|
|
@@ -2,7 +2,7 @@ from time import time
|
|
|
2
2
|
|
|
3
3
|
from usearch.index import search, MetricKind, ScalarKind
|
|
4
4
|
from usearch.compiled import hardware_acceleration
|
|
5
|
-
from faiss import
|
|
5
|
+
from faiss import knn, METRIC_L2
|
|
6
6
|
import numpy as np
|
|
7
7
|
import fire
|
|
8
8
|
|
|
@@ -36,7 +36,7 @@ def run(
|
|
|
36
36
|
print("USearch: ", time() - start)
|
|
37
37
|
|
|
38
38
|
start = time()
|
|
39
|
-
_ = knn(x
|
|
39
|
+
_ = knn(x[:q], x, k, metric=METRIC_L2)[1]
|
|
40
40
|
print("FAISS: ", time() - start)
|
|
41
41
|
|
|
42
42
|
|
|
@@ -58,24 +58,13 @@ def test_index_numba(ndim: int, batch_size: int):
|
|
|
58
58
|
c += a_array[i] * b_array[i]
|
|
59
59
|
return 1 - c
|
|
60
60
|
|
|
61
|
-
@cfunc(signature_four_args)
|
|
62
|
-
def python_inner_product_four_args(a, a_ndim, b, b_ndim):
|
|
63
|
-
a_array = carray(a, a_ndim)
|
|
64
|
-
b_array = carray(b, b_ndim)
|
|
65
|
-
c = 0.0
|
|
66
|
-
for i in range(a_ndim):
|
|
67
|
-
c += a_array[i] * b_array[i]
|
|
68
|
-
return 1 - c
|
|
69
|
-
|
|
70
61
|
functions = [
|
|
71
62
|
python_inner_product_two_args,
|
|
72
63
|
python_inner_product_three_args,
|
|
73
|
-
python_inner_product_four_args,
|
|
74
64
|
]
|
|
75
65
|
signatures = [
|
|
76
66
|
MetricSignature.ArrayArray,
|
|
77
67
|
MetricSignature.ArrayArraySize,
|
|
78
|
-
MetricSignature.ArraySizeArraySize,
|
|
79
68
|
]
|
|
80
69
|
for function, signature in zip(functions, signatures):
|
|
81
70
|
metric = CompiledMetric(
|
|
@@ -133,12 +122,12 @@ def test_index_cppyy(ndim: int, batch_size: int):
|
|
|
133
122
|
functions = [
|
|
134
123
|
cppyy.gbl.inner_product_two_args,
|
|
135
124
|
cppyy.gbl.inner_product_three_args,
|
|
136
|
-
cppyy.gbl.inner_product_four_args,
|
|
125
|
+
# cppyy.gbl.inner_product_four_args,
|
|
137
126
|
]
|
|
138
127
|
signatures = [
|
|
139
128
|
MetricSignature.ArrayArray,
|
|
140
129
|
MetricSignature.ArrayArraySize,
|
|
141
|
-
MetricSignature.ArraySizeArraySize,
|
|
130
|
+
# MetricSignature.ArraySizeArraySize,
|
|
142
131
|
]
|
|
143
132
|
for function, signature in zip(functions, signatures):
|
|
144
133
|
metric = CompiledMetric(
|
|
@@ -8,7 +8,7 @@ from usearch.index import (
|
|
|
8
8
|
)
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
@pytest.mark.parametrize("bits", [
|
|
11
|
+
@pytest.mark.parametrize("bits", [7, 97, 256, 4097])
|
|
12
12
|
@pytest.mark.parametrize("metric", [MetricKind.Tanimoto])
|
|
13
13
|
@pytest.mark.parametrize("connectivity", [3, 13, 50])
|
|
14
14
|
@pytest.mark.parametrize("batch_size", [3, 77])
|
|
@@ -48,7 +48,9 @@ def test_serializing_ibin_matrix(rows: int, cols: int):
|
|
|
48
48
|
|
|
49
49
|
@pytest.mark.parametrize("rows", batch_sizes)
|
|
50
50
|
@pytest.mark.parametrize("cols", dimensions)
|
|
51
|
-
|
|
51
|
+
@pytest.mark.parametrize("k", [1, 5])
|
|
52
|
+
@pytest.mark.parametrize("reordered", [False, True])
|
|
53
|
+
def test_exact_search(rows: int, cols: int, k: int, reordered: bool):
|
|
52
54
|
"""
|
|
53
55
|
Test exact search.
|
|
54
56
|
|
|
@@ -57,13 +59,21 @@ def test_exact_search(rows: int, cols: int):
|
|
|
57
59
|
"""
|
|
58
60
|
original = np.random.rand(rows, cols)
|
|
59
61
|
keys = np.arange(rows)
|
|
60
|
-
|
|
62
|
+
k = min(k, rows)
|
|
63
|
+
|
|
64
|
+
if reordered:
|
|
65
|
+
reordered_keys = np.arange(rows)
|
|
66
|
+
np.random.shuffle(reordered_keys)
|
|
67
|
+
else:
|
|
68
|
+
reordered_keys = keys
|
|
69
|
+
|
|
70
|
+
matches: BatchMatches = search(original, original[reordered_keys], k, exact=True)
|
|
61
71
|
top_matches = (
|
|
62
|
-
[int(m.keys[0]) for m in matches] if rows > 1 else int(matches.keys[0])
|
|
72
|
+
[int(m.keys[0]) for m in matches] if rows > 1 else [int(matches.keys[0])]
|
|
63
73
|
)
|
|
64
|
-
assert
|
|
74
|
+
assert top_matches == list(reordered_keys)
|
|
65
75
|
|
|
66
|
-
matches: Matches = search(original, original[-1],
|
|
76
|
+
matches: Matches = search(original, original[-1], k, exact=True)
|
|
67
77
|
top_match = int(matches.keys[0])
|
|
68
78
|
assert top_match == keys[-1]
|
|
69
79
|
|
package/rust/lib.cpp
CHANGED
|
@@ -36,10 +36,10 @@ Matches NativeIndex::search_f16(rust::Slice<uint16_t const> vec, size_t count) c
|
|
|
36
36
|
Matches NativeIndex::search_f32(rust::Slice<float const> vec, size_t count) const { return search_(*index_, vec.data(), count); }
|
|
37
37
|
Matches NativeIndex::search_f64(rust::Slice<double const> vec, size_t count) const { return search_(*index_, vec.data(), count); }
|
|
38
38
|
|
|
39
|
-
size_t NativeIndex::get_i8(key_t key, rust::Slice<int8_t> vec) const { return index_->get(key, vec.data(), vec.size() / dimensions()); }
|
|
40
|
-
size_t NativeIndex::get_f16(key_t key, rust::Slice<uint16_t> vec) const { return index_->get(key, (f16_t*)vec.data(), vec.size() / dimensions()); }
|
|
41
|
-
size_t NativeIndex::get_f32(key_t key, rust::Slice<float> vec) const { return index_->get(key, vec.data(), vec.size() / dimensions()); }
|
|
42
|
-
size_t NativeIndex::get_f64(key_t key, rust::Slice<double> vec) const { return index_->get(key, vec.data(), vec.size() / dimensions()); }
|
|
39
|
+
size_t NativeIndex::get_i8(key_t key, rust::Slice<int8_t> vec) const { if (vec.size() % dimensions()) throw std::invalid_argument("Vector length must be a multiple of index dimensionality"); return index_->get(key, vec.data(), vec.size() / dimensions()); }
|
|
40
|
+
size_t NativeIndex::get_f16(key_t key, rust::Slice<uint16_t> vec) const { if (vec.size() % dimensions()) throw std::invalid_argument("Vector length must be a multiple of index dimensionality"); return index_->get(key, (f16_t*)vec.data(), vec.size() / dimensions()); }
|
|
41
|
+
size_t NativeIndex::get_f32(key_t key, rust::Slice<float> vec) const { if (vec.size() % dimensions()) throw std::invalid_argument("Vector length must be a multiple of index dimensionality"); return index_->get(key, vec.data(), vec.size() / dimensions()); }
|
|
42
|
+
size_t NativeIndex::get_f64(key_t key, rust::Slice<double> vec) const { if (vec.size() % dimensions()) throw std::invalid_argument("Vector length must be a multiple of index dimensionality"); return index_->get(key, vec.data(), vec.size() / dimensions()); }
|
|
43
43
|
// clang-format on
|
|
44
44
|
|
|
45
45
|
size_t NativeIndex::remove(key_t key) const {
|
|
@@ -71,6 +71,9 @@ void NativeIndex::view(rust::Str path) const {
|
|
|
71
71
|
index_->view(memory_mapped_file_t(std::string(path).c_str())).error.raise();
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
void NativeIndex::reset() const { index_->reset(); }
|
|
75
|
+
size_t NativeIndex::memory_usage() const { return index_->memory_usage(); }
|
|
76
|
+
|
|
74
77
|
void NativeIndex::save_to_buffer(rust::Slice<uint8_t> buffer) const {
|
|
75
78
|
index_->save(memory_mapped_file_t((byte_t*)buffer.data(), buffer.size())).error.raise();
|
|
76
79
|
}
|
package/rust/lib.hpp
CHANGED
|
@@ -50,6 +50,8 @@ class NativeIndex {
|
|
|
50
50
|
void save(rust::Str path) const;
|
|
51
51
|
void load(rust::Str path) const;
|
|
52
52
|
void view(rust::Str path) const;
|
|
53
|
+
void reset() const;
|
|
54
|
+
size_t memory_usage() const;
|
|
53
55
|
|
|
54
56
|
void save_to_buffer(rust::Slice<uint8_t> buffer) const;
|
|
55
57
|
void load_from_buffer(rust::Slice<uint8_t const> buffer) const;
|