usearch 2.11.0 → 2.11.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/README.md +27 -8
- package/c/README.md +6 -3
- package/cpp/README.md +12 -1
- package/cpp/test.cpp +3 -2
- package/include/usearch/index.hpp +1 -1
- package/include/usearch/index_dense.hpp +5 -0
- package/package.json +1 -1
- package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
- package/prebuilds/linux-arm64/usearch.node +0 -0
- package/prebuilds/linux-x64/usearch.node +0 -0
- package/prebuilds/win32-ia32/usearch.node +0 -0
- package/prebuilds/win32-x64/usearch.node +0 -0
package/README.md
CHANGED
|
@@ -94,9 +94,10 @@ USearch is compact and broadly compatible without sacrificing performance, prima
|
|
|
94
94
|
| Supported metrics ² | 9 fixed metrics | any metric | extendible |
|
|
95
95
|
| Supported languages ³ | C++, Python | 10 languages | portable |
|
|
96
96
|
| Supported ID types ⁴ | 32-bit, 64-bit | 32-bit, 40-bit, 64-bit | efficient |
|
|
97
|
-
|
|
|
98
|
-
|
|
|
99
|
-
|
|
|
97
|
+
| Filtering ⁵ | ban-lists | any predicates | composable |
|
|
98
|
+
| Required dependencies ⁶ | BLAS, OpenMP | - | light-weight |
|
|
99
|
+
| Bindings ⁷ | SWIG | Native | low-latency |
|
|
100
|
+
| Python binding size ⁸ | [~ 10 MB][faiss-weight] | [< 1 MB][usearch-weight] | deployable |
|
|
100
101
|
|
|
101
102
|
[sloc]: https://en.wikipedia.org/wiki/Source_lines_of_code
|
|
102
103
|
[faiss-weight]: https://pypi.org/project/faiss-cpu/#files
|
|
@@ -107,9 +108,10 @@ USearch is compact and broadly compatible without sacrificing performance, prima
|
|
|
107
108
|
> ² User-defined metrics allow you to customize your search for various applications, from GIS to creating custom metrics for composite embeddings from multiple AI models or hybrid full-text and semantic search.
|
|
108
109
|
> ³ With USearch, you can reuse the same preconstructed index in various programming languages.
|
|
109
110
|
> ⁴ The 40-bit integer allows you to store 4B+ vectors without allocating 8 bytes for every neighbor reference in the proximity graph.
|
|
110
|
-
> ⁵
|
|
111
|
-
> ⁶
|
|
112
|
-
> ⁷
|
|
111
|
+
> ⁵ With USearch the index can be combined with arbitrary external containers, like Bloom filters or third-party databases, to filter out irrelevant keys during index traversal.
|
|
112
|
+
> ⁶ Lack of obligatory dependencies makes USearch much more portable.
|
|
113
|
+
> ⁷ Native bindings introduce lower call latencies than more straightforward approaches.
|
|
114
|
+
> ⁸ Lighter bindings make downloads and deployments faster.
|
|
113
115
|
|
|
114
116
|
[intel-benchmarks]: https://www.unum.cloud/blog/2023-11-07-scaling-vector-search-with-intel
|
|
115
117
|
|
|
@@ -192,7 +194,7 @@ When compared to FAISS's `IndexFlatL2` in Google Colab, __[USearch may offer up
|
|
|
192
194
|
- `faiss.IndexFlatL2`: __55.3 ms__.
|
|
193
195
|
- `usearch.index.search`: __2.54 ms__.
|
|
194
196
|
|
|
195
|
-
## User-Defined
|
|
197
|
+
## User-Defined Metrics
|
|
196
198
|
|
|
197
199
|
While most vector search packages concentrate on just two metrics, "Inner Product distance" and "Euclidean distance", USearch allows arbitrary user-defined metrics.
|
|
198
200
|
This flexibility allows you to customize your search for various applications, from computing geospatial coordinates with the rare [Haversine][haversine] distance to creating custom metrics for composite embeddings from multiple AI models, like joint image-text embeddings.
|
|
@@ -228,6 +230,23 @@ So you can apply it in [obscure][obscure] applications, like searching for simil
|
|
|
228
230
|
[cppyy]: https://cppyy.readthedocs.io/en/latest/
|
|
229
231
|
[peachpy]: https://github.com/Maratyszcza/PeachPy
|
|
230
232
|
|
|
233
|
+
## Filtering and Predicate Functions
|
|
234
|
+
|
|
235
|
+
Sometimes you may want to cross-reference search-results against some external database or filter them based on some criteria.
|
|
236
|
+
In most engines, you'd have to manually perform paging requests, successively filtering the results.
|
|
237
|
+
In USearch you can simply pass a predicate function to the search method, which will be applied directly during graph traversal.
|
|
238
|
+
In Rust that would look like this:
|
|
239
|
+
|
|
240
|
+
```rust
|
|
241
|
+
let is_odd = |key: Key| key % 2 == 1;
|
|
242
|
+
let query = vec![0.2, 0.1, 0.2, 0.1, 0.3];
|
|
243
|
+
let results = index.filtered_search(&query, 10, is_odd).unwrap();
|
|
244
|
+
assert!(
|
|
245
|
+
results.keys.iter().all(|&key| key % 2 == 1),
|
|
246
|
+
"All keys must be odd"
|
|
247
|
+
);
|
|
248
|
+
```
|
|
249
|
+
|
|
231
250
|
## Memory Efficiency, Downcasting, and Quantization
|
|
232
251
|
|
|
233
252
|
Training a quantization model and dimension-reduction is a common approach to accelerate vector search.
|
|
@@ -452,7 +471,7 @@ doi = {10.5281/zenodo.7949416},
|
|
|
452
471
|
author = {Vardanian, Ash},
|
|
453
472
|
title = {{USearch by Unum Cloud}},
|
|
454
473
|
url = {https://github.com/unum-cloud/usearch},
|
|
455
|
-
version = {2.11.
|
|
474
|
+
version = {2.11.1},
|
|
456
475
|
year = {2023},
|
|
457
476
|
month = oct,
|
|
458
477
|
}
|
package/c/README.md
CHANGED
|
@@ -42,7 +42,7 @@ int main() {
|
|
|
42
42
|
|
|
43
43
|
// Search:
|
|
44
44
|
usearch_key_t found_keys[10];
|
|
45
|
-
|
|
45
|
+
usearch_distance_t found_distances[10];
|
|
46
46
|
size_t found_count = usearch_search(
|
|
47
47
|
index, &vector[0], usearch_scalar_f32_k, 10,
|
|
48
48
|
&found_keys[0], &found_distances[0], &error);
|
|
@@ -113,7 +113,8 @@ simsimd_distance_t callback(void const* a, void const* b, void* state) {
|
|
|
113
113
|
// Your custom metric implementation here
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
void callback_state = NULL;
|
|
117
|
+
usearch_change_metric(index, callback, callback_state, usearch_metric_unknown_k, &error);
|
|
117
118
|
```
|
|
118
119
|
|
|
119
120
|
You can always revert back to one of the native metrics by calling:
|
|
@@ -133,8 +134,10 @@ int is_odd(usearch_key_t key, void* state) {
|
|
|
133
134
|
return key % 2;
|
|
134
135
|
}
|
|
135
136
|
|
|
137
|
+
usearch_key_t found_keys[10];
|
|
138
|
+
usearch_distance_t found_distances[10];
|
|
136
139
|
usearch_filtered_search(
|
|
137
|
-
index, &
|
|
140
|
+
index, &query[0], usearch_scalar_f32_k, 10,
|
|
138
141
|
&is_odd, NULL, // no state needed for this callback
|
|
139
142
|
&found_keys[0], &found_distances[0], &error);
|
|
140
143
|
```
|
package/cpp/README.md
CHANGED
|
@@ -71,8 +71,19 @@ Instead of spawning additional threads within USearch, we focus on the thread sa
|
|
|
71
71
|
|
|
72
72
|
During initialization, we allocate enough temporary memory for all the cores on the machine.
|
|
73
73
|
On the call, the user can supply the identifier of the current thread, making this library easy to integrate with OpenMP and similar tools.
|
|
74
|
+
Here is how parallel indexing may look like, when dealing with the low-level engine:
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
```cpp
|
|
77
|
+
std::size_t executor_threads = std::thread::hardware_concurrency() * 4;
|
|
78
|
+
executor_default_t executor(executor_threads);
|
|
79
|
+
|
|
80
|
+
index.reserve(index_limits_t {vectors.size(), executor.size()});
|
|
81
|
+
executor.fixed(vectors.size(), [&](std::size_t thread, std::size_t task) {
|
|
82
|
+
index.add(task, vectors[task + 3].data(), index_update_config_t { .thread = thread });
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Aside from the `executor_default_t`, you can take advantage of one of the provided "executors" to parallelize the search:
|
|
76
87
|
|
|
77
88
|
- `executor_openmp_t`, that would use OpenMP under the hood.
|
|
78
89
|
- `executor_stl_t`, that will spawn `std::thread` instances.
|
package/cpp/test.cpp
CHANGED
|
@@ -114,8 +114,9 @@ void test_cosine(index_at& index, std::vector<std::vector<scalar_at>> const& vec
|
|
|
114
114
|
expect(std::equal(vector_second, vector_second + dimensions, vec_recovered_from_load.data()));
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
// Try batch requests
|
|
118
|
-
|
|
117
|
+
// Try batch requests, heavily obersubscribing the CPU cores
|
|
118
|
+
std::size_t executor_threads = std::thread::hardware_concurrency() * 4;
|
|
119
|
+
executor_default_t executor(executor_threads);
|
|
119
120
|
index.reserve({vectors.size(), executor.size()});
|
|
120
121
|
executor.fixed(vectors.size() - 3, [&](std::size_t thread, std::size_t task) {
|
|
121
122
|
if constexpr (punned_ak) {
|
|
@@ -742,6 +742,11 @@ class index_dense_gt {
|
|
|
742
742
|
unique_lock_t lock(slot_lookup_mutex_);
|
|
743
743
|
slot_lookup_.reserve(limits.members);
|
|
744
744
|
vectors_lookup_.resize(limits.members);
|
|
745
|
+
|
|
746
|
+
// During reserve, no insertions may be happening, so we can safely overwrite the whole collection.
|
|
747
|
+
std::unique_lock<std::mutex> available_threads_lock(available_threads_mutex_);
|
|
748
|
+
available_threads_.resize(limits.threads());
|
|
749
|
+
std::iota(available_threads_.begin(), available_threads_.end(), 0ul);
|
|
745
750
|
}
|
|
746
751
|
return typed_->reserve(limits);
|
|
747
752
|
}
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|