usearch 0.18.8 → 0.19.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/Cargo.toml +1 -1
- package/README.md +176 -6
- package/conanfile.py +1 -1
- package/cpp/bench.cpp +2 -2
- package/cpp/test.cpp +11 -9
- package/include/usearch/index.hpp +269 -50
- package/include/usearch/index_punned_dense.hpp +123 -108
- package/include/usearch/index_punned_helpers.hpp +0 -12
- package/package.json +1 -1
- package/python/lib.cpp +116 -40
- 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 +275 -56
- package/python/usearch/eval.py +100 -49
- package/python/usearch/index.py +112 -83
- package/python/usearch/io.py +23 -23
- package/python/usearch/join.py +2 -4
- package/python/usearch/numba.py +22 -18
- package/python/usearch/server.py +38 -26
- 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/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,58 @@ 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 udf(metric_kind_t kind, metric_signature_t signature, std::uintptr_t metric_uintptr) {
|
|
80
|
+
//
|
|
81
|
+
metric_t result;
|
|
82
|
+
result.kind_ = kind;
|
|
83
|
+
switch (signature) {
|
|
84
|
+
case metric_signature_t::array_array_k:
|
|
85
|
+
result.func_ = [metric_uintptr](punned_vector_view_t a, punned_vector_view_t b) -> distance_t {
|
|
86
|
+
using metric_raw_t = punned_distance_t (*)(scalar_at const*, scalar_at const*);
|
|
87
|
+
metric_raw_t metric_ptr = reinterpret_cast<metric_raw_t>(metric_uintptr);
|
|
88
|
+
return metric_ptr((scalar_at const*)a.data(), (scalar_at const*)b.data());
|
|
89
|
+
};
|
|
90
|
+
break;
|
|
91
|
+
case metric_signature_t::array_array_size_k:
|
|
92
|
+
result.func_ = [metric_uintptr](punned_vector_view_t a, punned_vector_view_t b) -> distance_t {
|
|
93
|
+
using metric_raw_t = punned_distance_t (*)(scalar_at const*, scalar_at const*, size_t);
|
|
94
|
+
metric_raw_t metric_ptr = reinterpret_cast<metric_raw_t>(metric_uintptr);
|
|
95
|
+
return metric_ptr((scalar_at const*)a.data(), (scalar_at const*)b.data(), a.size() / sizeof(scalar_at));
|
|
96
|
+
};
|
|
97
|
+
break;
|
|
98
|
+
case metric_signature_t::array_size_array_size_k:
|
|
99
|
+
result.func_ = [metric_uintptr](punned_vector_view_t a, punned_vector_view_t b) -> distance_t {
|
|
100
|
+
using metric_raw_t = punned_distance_t (*)(scalar_at const*, size_t, scalar_at const*, size_t);
|
|
101
|
+
metric_raw_t metric_ptr = reinterpret_cast<metric_raw_t>(metric_uintptr);
|
|
102
|
+
return metric_ptr( //
|
|
103
|
+
(scalar_at const*)a.data(), a.size() / sizeof(scalar_at), //
|
|
104
|
+
(scalar_at const*)b.data(), b.size() / sizeof(scalar_at));
|
|
105
|
+
};
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
72
109
|
}
|
|
73
110
|
|
|
74
|
-
metric_t udf(std::
|
|
111
|
+
metric_t udf(metric_kind_t kind, metric_signature_t signature, std::uintptr_t metric_uintptr, scalar_kind_t accuracy) {
|
|
75
112
|
switch (accuracy) {
|
|
76
|
-
case scalar_kind_t::f8_k: return udf<f8_bits_t>(metric_uintptr);
|
|
77
|
-
case scalar_kind_t::f16_k: return udf<f16_t>(metric_uintptr);
|
|
78
|
-
case scalar_kind_t::f32_k: return udf<f32_t>(metric_uintptr);
|
|
79
|
-
case scalar_kind_t::f64_k: return udf<f64_t>(metric_uintptr);
|
|
113
|
+
case scalar_kind_t::f8_k: return udf<f8_bits_t>(kind, signature, metric_uintptr);
|
|
114
|
+
case scalar_kind_t::f16_k: return udf<f16_t>(kind, signature, metric_uintptr);
|
|
115
|
+
case scalar_kind_t::f32_k: return udf<f32_t>(kind, signature, metric_uintptr);
|
|
116
|
+
case scalar_kind_t::f64_k: return udf<f64_t>(kind, signature, metric_uintptr);
|
|
80
117
|
default: return {};
|
|
81
118
|
}
|
|
82
119
|
}
|
|
83
120
|
|
|
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
|
-
|
|
121
|
+
static punned_index_py_t make_index( //
|
|
122
|
+
std::size_t dimensions, //
|
|
123
|
+
scalar_kind_t scalar_kind, //
|
|
124
|
+
metric_kind_t metric_kind, //
|
|
125
|
+
std::size_t connectivity, //
|
|
126
|
+
std::size_t expansion_add, //
|
|
127
|
+
std::size_t expansion_search, //
|
|
128
|
+
metric_signature_t metric_signature, //
|
|
129
|
+
std::uintptr_t metric_uintptr, //
|
|
92
130
|
bool tune) {
|
|
93
131
|
|
|
94
132
|
index_config_t config;
|
|
@@ -99,7 +137,8 @@ static punned_index_py_t make_index( //
|
|
|
99
137
|
|
|
100
138
|
if (metric_uintptr)
|
|
101
139
|
return punned_index_t::make( //
|
|
102
|
-
dimensions, udf(metric_uintptr, scalar_kind), config, scalar_kind,
|
|
140
|
+
dimensions, udf(metric_kind, metric_signature, metric_uintptr, scalar_kind), config, scalar_kind,
|
|
141
|
+
expansion_add, expansion_search);
|
|
103
142
|
else
|
|
104
143
|
return punned_index_t::make(dimensions, metric_kind, config, scalar_kind, expansion_add, expansion_search);
|
|
105
144
|
}
|
|
@@ -417,14 +456,18 @@ template <typename index_at> py::object get_member(index_at const& index, label_
|
|
|
417
456
|
throw std::invalid_argument("Incompatible scalars in the query matrix!");
|
|
418
457
|
}
|
|
419
458
|
|
|
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>(
|
|
459
|
+
template <typename index_at> py::array_t<label_t> get_labels(index_at const& index, std::size_t limit) {
|
|
460
|
+
limit = std::min(index.size(), limit);
|
|
461
|
+
py::array_t<label_t> result_py(static_cast<Py_ssize_t>(limit));
|
|
423
462
|
auto result_py1d = result_py.template mutable_unchecked<1>();
|
|
424
|
-
index.export_labels(&result_py1d(0),
|
|
463
|
+
index.export_labels(&result_py1d(0), limit);
|
|
425
464
|
return result_py;
|
|
426
465
|
}
|
|
427
466
|
|
|
467
|
+
template <typename index_at> py::array_t<label_t> get_all_labels(index_at const& index) {
|
|
468
|
+
return get_labels(index, index.size());
|
|
469
|
+
}
|
|
470
|
+
|
|
428
471
|
template <typename element_at> bool has_duplicates(element_at const* begin, element_at const* end) {
|
|
429
472
|
if (begin == end)
|
|
430
473
|
return false;
|
|
@@ -539,6 +582,11 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
539
582
|
m.attr("USES_SIMSIMD") = py::int_(USEARCH_USE_SIMSIMD);
|
|
540
583
|
m.attr("USES_NATIVE_F16") = py::int_(USEARCH_USE_NATIVE_F16);
|
|
541
584
|
|
|
585
|
+
py::enum_<metric_signature_t>(m, "MetricSignature")
|
|
586
|
+
.value("ArrayArray", metric_signature_t::array_array_k)
|
|
587
|
+
.value("ArrayArraySize", metric_signature_t::array_array_size_k)
|
|
588
|
+
.value("ArraySizeArraySize", metric_signature_t::array_size_array_size_k);
|
|
589
|
+
|
|
542
590
|
py::enum_<metric_kind_t>(m, "MetricKind")
|
|
543
591
|
.value("Unknown", metric_kind_t::unknown_k)
|
|
544
592
|
.value("IP", metric_kind_t::ip_k)
|
|
@@ -558,18 +606,44 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
558
606
|
.value("F8", scalar_kind_t::f8_k)
|
|
559
607
|
.value("B1", scalar_kind_t::b1x8_k);
|
|
560
608
|
|
|
609
|
+
auto h = py::class_<file_head_result_t>(m, "IndexMetadata");
|
|
610
|
+
h.def(py::init([](std::string const& path) {
|
|
611
|
+
file_head_result_t h = index_metadata(path.c_str());
|
|
612
|
+
h.error.raise();
|
|
613
|
+
return h;
|
|
614
|
+
}));
|
|
615
|
+
h.def_property_readonly("version", [](file_head_result_t const& h) {
|
|
616
|
+
return //
|
|
617
|
+
std::to_string(h.version_major) + "." + //
|
|
618
|
+
std::to_string(h.version_minor) + "." + //
|
|
619
|
+
std::to_string(h.version_patch);
|
|
620
|
+
});
|
|
621
|
+
h.def_readonly("metric", &file_head_result_t::metric);
|
|
622
|
+
h.def_readonly("connectivity", &file_head_result_t::connectivity);
|
|
623
|
+
h.def_readonly("max_level", &file_head_result_t::max_level);
|
|
624
|
+
h.def_readonly("vector_alignment", &file_head_result_t::vector_alignment);
|
|
625
|
+
h.def_readonly("bytes_per_label", &file_head_result_t::bytes_per_label);
|
|
626
|
+
h.def_readonly("bytes_per_id", &file_head_result_t::bytes_per_id);
|
|
627
|
+
h.def_readonly("scalar_kind", &file_head_result_t::scalar_kind);
|
|
628
|
+
h.def_readonly("size", &file_head_result_t::size);
|
|
629
|
+
h.def_readonly("entry_idx", &file_head_result_t::entry_idx);
|
|
630
|
+
h.def_readonly("bytes_for_graphs", &file_head_result_t::bytes_for_graphs);
|
|
631
|
+
h.def_readonly("bytes_for_vectors", &file_head_result_t::bytes_for_vectors);
|
|
632
|
+
h.def_readonly("bytes_checksum", &file_head_result_t::bytes_checksum);
|
|
633
|
+
|
|
561
634
|
auto i = py::class_<punned_index_py_t>(m, "Index");
|
|
562
635
|
|
|
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("
|
|
636
|
+
i.def(py::init(&make_index), //
|
|
637
|
+
py::kw_only(), //
|
|
638
|
+
py::arg("ndim") = 0, //
|
|
639
|
+
py::arg("dtype") = scalar_kind_t::f32_k, //
|
|
640
|
+
py::arg("metric") = metric_kind_t::ip_k, //
|
|
641
|
+
py::arg("connectivity") = default_connectivity(), //
|
|
642
|
+
py::arg("expansion_add") = default_expansion_add(), //
|
|
643
|
+
py::arg("expansion_search") = default_expansion_search(), //
|
|
644
|
+
py::arg("metric_signature") = metric_signature_t::array_array_k, //
|
|
645
|
+
py::arg("metric_pointer") = 0, //
|
|
646
|
+
py::arg("tune") = false //
|
|
573
647
|
);
|
|
574
648
|
|
|
575
649
|
i.def( //
|
|
@@ -604,7 +678,8 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
604
678
|
i.def_property_readonly("connectivity", &punned_index_py_t::connectivity);
|
|
605
679
|
i.def_property_readonly("capacity", &punned_index_py_t::capacity);
|
|
606
680
|
i.def_property_readonly( //
|
|
607
|
-
"dtype",
|
|
681
|
+
"dtype",
|
|
682
|
+
[](punned_index_py_t const& index) -> std::string { return scalar_kind_name(index.metric().scalar_kind_); });
|
|
608
683
|
i.def_property_readonly( //
|
|
609
684
|
"memory_usage", [](punned_index_py_t const& index) -> std::size_t { return index.memory_usage(); });
|
|
610
685
|
|
|
@@ -612,7 +687,8 @@ PYBIND11_MODULE(compiled, m) {
|
|
|
612
687
|
i.def_property("expansion_search", &punned_index_py_t::expansion_search,
|
|
613
688
|
&punned_index_py_t::change_expansion_search);
|
|
614
689
|
|
|
615
|
-
i.def_property_readonly("labels", &
|
|
690
|
+
i.def_property_readonly("labels", &get_all_labels<punned_index_py_t>);
|
|
691
|
+
i.def("get_labels", &get_labels<punned_index_py_t>, py::arg("limit") = std::numeric_limits<std::size_t>::max());
|
|
616
692
|
i.def("__contains__", &punned_index_py_t::contains);
|
|
617
693
|
i.def( //
|
|
618
694
|
"__getitem__", &get_member<punned_index_py_t>, py::arg("label"), py::arg("dtype") = scalar_kind_t::f32_k);
|