usearch 0.20.0 → 0.21.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/.github/workflows/prerelease.yml +27 -0
- package/Cargo.toml +1 -1
- package/README.md +20 -1
- package/conanfile.py +1 -1
- package/cpp/CMakeLists.txt +6 -2
- package/cpp/bench.cpp +41 -4
- package/docs/benchmarks.md +14 -12
- package/include/usearch/index.hpp +475 -56
- package/include/usearch/index_punned_dense.hpp +68 -10
- package/include/usearch/index_punned_helpers.hpp +28 -3
- package/package.json +1 -1
- package/python/lib.cpp +57 -5
- package/python/scripts/join.py +151 -0
- package/python/scripts/test.py +19 -0
- package/python/usearch/eval.py +11 -3
- package/python/usearch/index.py +119 -10
- package/python/usearch/join.py +0 -98
|
@@ -17,6 +17,33 @@ permissions:
|
|
|
17
17
|
|
|
18
18
|
jobs:
|
|
19
19
|
|
|
20
|
+
build_test:
|
|
21
|
+
name: Test USearch
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v3
|
|
25
|
+
with:
|
|
26
|
+
ref: main-dev
|
|
27
|
+
- run: git submodule update --init --recursive
|
|
28
|
+
- name: Prepare Environment
|
|
29
|
+
run: |
|
|
30
|
+
sudo apt update &&
|
|
31
|
+
sudo apt install -y cmake g++-12 build-essential libjemalloc-dev
|
|
32
|
+
- name: Build
|
|
33
|
+
run: |
|
|
34
|
+
cmake -B ./build_release \
|
|
35
|
+
-DCMAKE_CXX_COMPILER="g++-12" \
|
|
36
|
+
-DCMAKE_BUILD_TYPE=Release \
|
|
37
|
+
-DUSEARCH_BUILD_TEST=1 \
|
|
38
|
+
-DUSEARCH_USE_OPENMP=1 \
|
|
39
|
+
-DUSEARCH_USE_SIMSIMD=1 \
|
|
40
|
+
-DUSEARCH_USE_JEMALLOC=1 \
|
|
41
|
+
-DUSEARCH_BUILD_BENCHMARK=0 &&
|
|
42
|
+
make -C ./build_release -j
|
|
43
|
+
- name: Run tests
|
|
44
|
+
run: ./build_release/test
|
|
45
|
+
|
|
46
|
+
|
|
20
47
|
test_python_311:
|
|
21
48
|
name: Test Python
|
|
22
49
|
runs-on: ${{ matrix.os }}
|
package/Cargo.toml
CHANGED
package/README.md
CHANGED
|
@@ -759,9 +759,28 @@ index = Index(ndim=2048, metric=MetricKind.BitwiseTanimoto)
|
|
|
759
759
|
labels = np.arange(len(molecules))
|
|
760
760
|
|
|
761
761
|
index.add(labels, fingerprints)
|
|
762
|
-
matches = index.search(fingerprints, 10)
|
|
762
|
+
matches: Matches = index.search(fingerprints, 10)
|
|
763
763
|
```
|
|
764
764
|
|
|
765
|
+
Of if you need a bit of our SIMD superpowers - take some Chemsitry-oriented precompiled metrics from [SimSIMD][simsimd].
|
|
766
|
+
|
|
767
|
+
```python
|
|
768
|
+
import simsimd as sisi
|
|
769
|
+
|
|
770
|
+
index = Index(
|
|
771
|
+
ndim=166, # MACCS fingerprints are 166-dimensional
|
|
772
|
+
metric=CompiledMetric(
|
|
773
|
+
pointer=sisi.to_int(sisi.tanimoto_maccs_neon), # For Arm Neon
|
|
774
|
+
signature=MetricSignature.ArrayArray,
|
|
775
|
+
kind=MetricKind.Tanimoto,
|
|
776
|
+
),
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
index.add(42, np.packbits([1] * 166))
|
|
780
|
+
matches: Matches = index.search(np.packbits([1] * 166))
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
[simsimd]: https://github.com/ashvardanian/simsimd
|
|
765
784
|
[smiles]: https://en.wikipedia.org/wiki/Simplified_molecular-input_line-entry_system
|
|
766
785
|
[rdkit-fingerprints]: https://www.rdkit.org/docs/RDKit_Book.html#additional-information-about-the-fingerprints
|
|
767
786
|
|
package/conanfile.py
CHANGED
|
@@ -4,7 +4,7 @@ from conans import ConanFile
|
|
|
4
4
|
class USearchConan(ConanFile):
|
|
5
5
|
|
|
6
6
|
name = 'USearch'
|
|
7
|
-
version = '0.
|
|
7
|
+
version = '0.21.0'
|
|
8
8
|
license = 'Apache 2.0'
|
|
9
9
|
url = 'https://github.com/unum-cloud/usearch'
|
|
10
10
|
description = 'Smaller & Faster Single-File Vector Search Engine from Unum'
|
package/cpp/CMakeLists.txt
CHANGED
|
@@ -13,7 +13,9 @@ endif()
|
|
|
13
13
|
# https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_COMPILER_ID.html
|
|
14
14
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
|
15
15
|
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
|
|
16
|
-
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -
|
|
16
|
+
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g")
|
|
17
|
+
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O3 -g")
|
|
18
|
+
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fsanitize=leak -fsanitize=alignment -fsanitize=undefined")
|
|
17
19
|
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wextra -Wno-conversion -Wno-unknown-pragmas")
|
|
18
20
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
|
|
19
21
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmax-errors=1")
|
|
@@ -22,7 +24,9 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
|
|
22
24
|
|
|
23
25
|
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
|
24
26
|
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
|
|
25
|
-
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -
|
|
27
|
+
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g")
|
|
28
|
+
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O3 -g")
|
|
29
|
+
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fsanitize=leak -fsanitize=alignment -fsanitize=undefined")
|
|
26
30
|
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wfatal-errors")
|
|
27
31
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")
|
|
28
32
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic")
|
package/cpp/bench.cpp
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
#include <sys/stat.h> // `stat`
|
|
26
26
|
|
|
27
|
+
#include <algorithm>
|
|
27
28
|
#include <csignal>
|
|
28
29
|
#include <cstdio>
|
|
29
30
|
#include <iostream> // `std::cerr`
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
#include <string> // `std::to_string`
|
|
33
34
|
#include <thread> // `std::thread::hardware_concurrency()`
|
|
34
35
|
#include <variant> // `std::monostate`
|
|
36
|
+
#include <vector>
|
|
35
37
|
|
|
36
38
|
#include <clipp.h> // Command Line Interface
|
|
37
39
|
#include <omp.h> // `omp_set_num_threads()`
|
|
@@ -243,10 +245,10 @@ struct running_stats_printer_t {
|
|
|
243
245
|
std::size_t new_progress = progress.load();
|
|
244
246
|
if (new_progress - last_printed_progress < step)
|
|
245
247
|
return;
|
|
246
|
-
print(new_progress);
|
|
248
|
+
print(new_progress, total);
|
|
247
249
|
}
|
|
248
250
|
|
|
249
|
-
void print(std::size_t progress) {
|
|
251
|
+
void print(std::size_t progress, std::size_t total) {
|
|
250
252
|
|
|
251
253
|
constexpr char bars_k[] = "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||";
|
|
252
254
|
constexpr std::size_t bars_len_k = 60;
|
|
@@ -267,6 +269,7 @@ struct running_stats_printer_t {
|
|
|
267
269
|
|
|
268
270
|
last_printed_progress = progress;
|
|
269
271
|
last_printed_time = time_new;
|
|
272
|
+
this->total = total;
|
|
270
273
|
}
|
|
271
274
|
};
|
|
272
275
|
|
|
@@ -332,6 +335,8 @@ template <typename dataset_at, typename index_at> //
|
|
|
332
335
|
static void single_shot(dataset_at& dataset, index_at& index, bool construct = true) {
|
|
333
336
|
using label_t = typename index_at::label_t;
|
|
334
337
|
using distance_t = typename index_at::distance_t;
|
|
338
|
+
using join_result_t = typename index_at::join_result_t;
|
|
339
|
+
constexpr std::size_t missing_label = std::numeric_limits<label_t>::max();
|
|
335
340
|
|
|
336
341
|
std::printf("\n");
|
|
337
342
|
std::printf("------------\n");
|
|
@@ -348,7 +353,7 @@ static void single_shot(dataset_at& dataset, index_at& index, bool construct = t
|
|
|
348
353
|
search_many(index, dataset.queries_count(), dataset.query(0), dataset.dimensions(), dataset.neighborhood_size(),
|
|
349
354
|
found_neighbors.data(), found_distances.data());
|
|
350
355
|
|
|
351
|
-
// Evaluate quality
|
|
356
|
+
// Evaluate search quality
|
|
352
357
|
std::size_t recall_at_1 = 0, recall_full = 0;
|
|
353
358
|
for (std::size_t i = 0; i != dataset.queries_count(); ++i) {
|
|
354
359
|
auto expected = dataset.neighborhood(i);
|
|
@@ -360,6 +365,38 @@ static void single_shot(dataset_at& dataset, index_at& index, bool construct = t
|
|
|
360
365
|
std::printf("Recall@1 %.2f %%\n", recall_at_1 * 100.f / dataset.queries_count());
|
|
361
366
|
std::printf("Recall %.2f %%\n", recall_full * 100.f / dataset.queries_count());
|
|
362
367
|
|
|
368
|
+
// Perform joins
|
|
369
|
+
std::vector<label_t> man_to_woman(dataset.vectors_count());
|
|
370
|
+
std::vector<label_t> woman_to_man(dataset.vectors_count());
|
|
371
|
+
std::size_t join_attempts = 0;
|
|
372
|
+
{
|
|
373
|
+
index_at& men = index;
|
|
374
|
+
index_at women = index.copy().index;
|
|
375
|
+
std::fill(man_to_woman.begin(), man_to_woman.end(), missing_label);
|
|
376
|
+
std::fill(woman_to_man.begin(), woman_to_man.end(), missing_label);
|
|
377
|
+
{
|
|
378
|
+
executor_default_t executor(index.limits().threads());
|
|
379
|
+
running_stats_printer_t printer{1, "Join"};
|
|
380
|
+
join_result_t result = index_at::join( //
|
|
381
|
+
men, women, join_config_t{executor.size()}, //
|
|
382
|
+
man_to_woman.data(), woman_to_man.data(), //
|
|
383
|
+
executor, [&](std::size_t progress, std::size_t total) {
|
|
384
|
+
if (progress % 1000 == 0)
|
|
385
|
+
printer.print(progress, total);
|
|
386
|
+
});
|
|
387
|
+
join_attempts = result.cycles;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// Evaluate join quality
|
|
391
|
+
std::size_t recall_join = 0, unmatched_count = 0;
|
|
392
|
+
for (std::size_t i = 0; i != index.size(); ++i) {
|
|
393
|
+
recall_join += man_to_woman[i] == i;
|
|
394
|
+
unmatched_count += man_to_woman[i] == missing_label;
|
|
395
|
+
}
|
|
396
|
+
std::printf("Recall Joins %.2f %%\n", recall_join * 100.f / index.size());
|
|
397
|
+
std::printf("Unmatched %.2f %% (%zu items)\n", unmatched_count * 100.f / index.size(), unmatched_count);
|
|
398
|
+
std::printf("Proposals %.2f / man (%zu total)\n", join_attempts * 1.f / index.size(), join_attempts);
|
|
399
|
+
|
|
363
400
|
// Paginate
|
|
364
401
|
std::vector<vector_id_t> hints(dataset.queries_count());
|
|
365
402
|
for (std::size_t i = 0; i != hints.size(); ++i)
|
|
@@ -491,7 +528,7 @@ void run_punned(dataset_at& dataset, args_t const& args, index_config_t config,
|
|
|
491
528
|
|
|
492
529
|
std::printf("Will benchmark an on-disk view\n");
|
|
493
530
|
|
|
494
|
-
index_at index_view = index.fork();
|
|
531
|
+
index_at index_view = index.fork().index;
|
|
495
532
|
index_view.view(args.path_output.c_str());
|
|
496
533
|
single_shot(dataset, index_view, false);
|
|
497
534
|
}
|
package/docs/benchmarks.md
CHANGED
|
@@ -117,21 +117,23 @@ OPTIONS
|
|
|
117
117
|
BigANN benchmark is a good starting point, if you are searching for large collections of high-dimensional vectors.
|
|
118
118
|
Those often come with precomputed ground-truth neighbors, which is handy for recall evaluation.
|
|
119
119
|
|
|
120
|
-
| Dataset
|
|
121
|
-
|
|
|
122
|
-
| [Unum UForm
|
|
123
|
-
| [
|
|
124
|
-
|
|
|
125
|
-
|
|
|
126
|
-
| [Microsoft
|
|
127
|
-
| [
|
|
128
|
-
| [Yandex
|
|
129
|
-
|
|
|
130
|
-
|
|
|
120
|
+
| Dataset | Scalar Type | Dimensions | Metric | Size |
|
|
121
|
+
| :----------------------------------------- | :---------: | :--------: | :----: | :-------: |
|
|
122
|
+
| [Unum UForm Creative Captions][unum-cc-3m] | float32 | 256 | IP | 3 GB |
|
|
123
|
+
| [Unum UForm Wiki][unum-wiki-1m] | float32 | 256 | IP | 1 GB |
|
|
124
|
+
| [Yandex Text-to-Image Sample][unum-t2i] | float32 | 200 | Cos | 1 GB |
|
|
125
|
+
| | | | | |
|
|
126
|
+
| [Microsoft SPACEV][spacev] | int8 | 100 | L2 | 93 GB |
|
|
127
|
+
| [Microsoft Turing-ANNS][turing] | float32 | 100 | L2 | 373 GB |
|
|
128
|
+
| [Yandex Deep1B][deep] | float32 | 96 | L2 | 358 GB |
|
|
129
|
+
| [Yandex Text-to-Image][t2i] | float32 | 200 | Cos | 750 GB |
|
|
130
|
+
| | | | | |
|
|
131
|
+
| [ViT-L/12 LAION][laion] | float32 | 2048 | Cos | 2 - 10 TB |
|
|
131
132
|
|
|
132
133
|
Luckily, smaller samples of those datasets are available.
|
|
133
134
|
|
|
134
|
-
[unum-
|
|
135
|
+
[unum-cc-3m]: https://huggingface.co/datasets/unum-cloud/ann-cc-3m
|
|
136
|
+
[unum-wiki-1m]: https://huggingface.co/datasets/unum-cloud/ann-wiki-1m
|
|
135
137
|
[unum-t2i]: https://huggingface.co/datasets/unum-cloud/ann-t2i-1m
|
|
136
138
|
[spacev]: https://github.com/microsoft/SPTAG/tree/main/datasets/SPACEV1B
|
|
137
139
|
[turing]: https://learning2hash.github.io/publications/microsoftturinganns1B/
|