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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "usearch"
3
- version = "0.20.0"
3
+ version = "0.21.0"
4
4
  authors = ["Ashot Vardanian <1983160+ashvardanian@users.noreply.github.com>"]
5
5
  description = "Smaller & Faster Single-File Vector Search Engine from Unum"
6
6
  edition = "2021"
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.20.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'
@@ -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} -fsanitize=address")
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} -fsanitize=address")
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
  }
@@ -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 | Scalar Type | Dimensions | Metric | Size |
121
- | :-------------------------------------- | :---------: | :--------: | :----: | :-------: |
122
- | [Unum UForm Wiki][unum-wiki] | float32 | 256 | IP | 1 GB |
123
- | [Yandex Text-to-Image Sample][unum-t2i] | float32 | 200 | Cos | 1 GB |
124
- | | | | | |
125
- | [Microsoft SPACEV][spacev] | int8 | 100 | L2 | 93 GB |
126
- | [Microsoft Turing-ANNS][turing] | float32 | 100 | L2 | 373 GB |
127
- | [Yandex Deep1B][deep] | float32 | 96 | L2 | 358 GB |
128
- | [Yandex Text-to-Image][t2i] | float32 | 200 | Cos | 750 GB |
129
- | | | | | |
130
- | [ViT-L/12 LAION][laion] | float32 | 2048 | Cos | 2 - 10 TB |
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-wiki]: https://huggingface.co/datasets/unum-cloud/ann-wiki-1m
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/