usearch 0.11.1 → 0.12.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.
@@ -37,12 +37,8 @@ jobs:
37
37
  pip install pytest numpy
38
38
  - name: Build locally
39
39
  run: python -m pip install .
40
- - name: Test with PyTest on NIX
41
- if: matrix.os != 'windows-2022'
40
+ - name: Test with PyTest
42
41
  run: pytest python/scripts/test.py
43
- - name: Test with PyTest on Windows
44
- if: matrix.os == 'windows-2022'
45
- run: pytest python\scripts\test.py
46
42
 
47
43
  test_javascript:
48
44
  name: Test JavaScript
@@ -11,7 +11,7 @@ env:
11
11
 
12
12
  # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13
13
  permissions:
14
- contents: read
14
+ contents: write
15
15
  pages: write
16
16
  id-token: write
17
17
 
@@ -27,13 +27,33 @@ jobs:
27
27
  - uses: actions/setup-node@v3
28
28
  - run: npm install --save-dev @semantic-release/exec @semantic-release/git conventional-changelog-eslint semantic-release && npx semantic-release
29
29
 
30
+ rebase:
31
+ name: Rebase from main into main-dev
32
+ needs: versioning
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - name: Checkout the latest code
36
+ uses: actions/checkout@v3
37
+ with:
38
+ fetch-depth: 0
39
+
40
+ - name: Perform Rebase
41
+ run: |
42
+ git checkout main-dev
43
+ git rebase main
44
+ - name: Push changes
45
+ uses: ad-m/github-push-action@master
46
+ with:
47
+ github_token: ${{ secrets.GITHUB_TOKEN }}
48
+ branch: main-dev
49
+
30
50
  build_wheels:
31
51
  name: Build Wheels for ${{ matrix.os }}
32
52
  needs: versioning
33
53
  runs-on: ${{ matrix.os }}
34
54
  strategy:
35
55
  matrix:
36
- os: [ubuntu-20.04, macOS-11]
56
+ os: [ubuntu-20.04, macOS-11, windows-2022]
37
57
 
38
58
  steps:
39
59
  - uses: actions/checkout@v3
package/.gitignore ADDED
@@ -0,0 +1,84 @@
1
+ # Ignore all files without an extension
2
+ *
3
+ !/**/
4
+ !*.*
5
+
6
+ # Temporary files
7
+ .DS_Store
8
+ *.fbin
9
+ *.ibin
10
+ datasets
11
+ bin
12
+ *.usearch
13
+
14
+ # C++ builds
15
+ build
16
+ build_release
17
+ build_debug
18
+ _deps
19
+ CMakeCache.txt
20
+ CMakeFiles
21
+ Makefile
22
+ cmake_install.cmake
23
+
24
+ # Profiling with GProf and `perf` on Linux
25
+ gmon.out
26
+ perf.data
27
+
28
+ # Python wrappers
29
+ __pycache__
30
+ *.egg-info
31
+ dist/
32
+ wheelhouse/
33
+
34
+ # JS wrappers
35
+ package-lock.json
36
+ node_modules/
37
+
38
+ # Rust builds
39
+ Cargo.lock
40
+ target/
41
+
42
+ # Java binings
43
+ .gradle
44
+ *.class
45
+
46
+ # ObjC and Swift
47
+ .build
48
+ .swiftpm
49
+
50
+ # Prerequisites
51
+ *.d
52
+
53
+ # Compiled Object files
54
+ *.slo
55
+ *.lo
56
+ *.o
57
+ *.obj
58
+
59
+ # Precompiled Headers
60
+ *.gch
61
+ *.pch
62
+
63
+ # Compiled Dynamic libraries
64
+ *.so
65
+ *.dylib
66
+ *.dll
67
+
68
+ # Fortran module files
69
+ *.mod
70
+ *.smod
71
+
72
+ # Allow golang go.mod
73
+ !go.mod
74
+
75
+ # Compiled Static libraries
76
+ *.lai
77
+ *.la
78
+ *.a
79
+ *.lib
80
+
81
+ # Executables
82
+ *.exe
83
+ *.out
84
+ *.app
@@ -50,7 +50,13 @@
50
50
  "stopAtEntry": false,
51
51
  "linux": {
52
52
  "preLaunchTask": "Linux Build C++ Bench Debug",
53
- "MIMode": "gdb"
53
+ "MIMode": "gdb",
54
+ "environment": [
55
+ {
56
+ "name": "MALLOC_CHECK_",
57
+ "value": "2"
58
+ }
59
+ ]
54
60
  },
55
61
  "osx": {
56
62
  "preLaunchTask": "MacOS Build C++ Bench Debug",
@@ -99,9 +99,12 @@
99
99
  "__availability": "cpp",
100
100
  "version": "cpp",
101
101
  "future": "cpp",
102
- "__verbose_abort": "cpp"
102
+ "__verbose_abort": "cpp",
103
+ "shared_mutex": "cpp"
103
104
  },
104
105
  "cSpell.words": [
106
+ "allclose",
107
+ "arange",
105
108
  "astype",
106
109
  "Availible",
107
110
  "BLAS",
@@ -110,11 +113,15 @@
110
113
  "downcasted",
111
114
  "Downcasting",
112
115
  "dtype",
116
+ "equi",
113
117
  "equidimensional",
114
118
  "FAISS",
119
+ "fbin",
120
+ "groundtruth",
115
121
  "hashable",
116
122
  "HNSW",
117
123
  "hnswlib",
124
+ "ibin",
118
125
  "jaccard",
119
126
  "longlong",
120
127
  "memmap",
@@ -122,12 +129,19 @@
122
129
  "ndarray",
123
130
  "ndim",
124
131
  "Numba",
132
+ "numpy",
125
133
  "OPENMP",
126
134
  "preprocess",
135
+ "Println",
136
+ "pytest",
127
137
  "Quickstart",
128
138
  "rtype",
129
139
  "simsimd",
130
140
  "SLOC",
141
+ "sorensen",
142
+ "tanimoto",
143
+ "uninitialize",
144
+ "unumusearch",
131
145
  "usearch",
132
146
  "usecases"
133
147
  ],
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "usearch"
3
- version = "0.11.1"
3
+ version = "0.12.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
@@ -66,11 +66,11 @@ Both USearch and FAISS implement the same HNSW algorithm.
66
66
  But they differ in a lot of design decisions.
67
67
  USearch is designed to be compact and broadly compatible without sacrificing performance.
68
68
 
69
- | | FAISS, `f32` | USearch, `f32` | USearch, `f16` | USearch, `f8` | USearch + Numba, `f32` |
70
- | :--------- | -----------: | -------------: | -------------: | ------------: | ---------------------: |
71
- | Insertions | 76 K/s | 89 K/s | 73 K/s | **137 K/s** | 99 K/s |
72
- | Queries | 118 K/s | 167 K/s | 140 K/s | **281 K/s** | 165 K/s |
73
- | Recall @1 | 99% | 99.2% | 98% | **99.2%** | 99.2% |
69
+ | | FAISS, `f32` | USearch, `f32` | USearch, `f16` | USearch, `f8` |
70
+ | :--------- | -----------: | -------------: | -------------: | ------------: |
71
+ | Insertions | 76 K/s | 105 K/s | 115 K/s | **202 K/s** |
72
+ | Queries | 118 K/s | 174 K/s | 173 K/s | **304 K/s** |
73
+ | Recall @1 | 99% | 99.2% | 99.1% | **99.2%** |
74
74
 
75
75
  > Dataset: 1M vectors sample of the Deep1B dataset.
76
76
  > Hardware: `c7g.metal` AWS instance with 64 cores and DDR5 memory.
@@ -202,9 +202,11 @@ The following distances are pre-packaged:
202
202
  - `ip_gt<scalar_t>` for "Inner Product" or "Dot Product" distance.
203
203
  - `l2sq_gt<scalar_t>` for the squared "L2" or "Euclidean" distance.
204
204
  - `jaccard_gt<scalar_t>` for "Jaccard" distance between two ordered sets of unique elements.
205
- - `bit_hamming_gt<scalar_t>` for "Hamming" distance, as the number of shared bits in hashes.
205
+ - `bitwise_hamming_gt<scalar_t>` for "Hamming" distance, as the number of shared bits in hashes.
206
+ - `bitwise_tanimoto_gt<scalar_t>` for "Tanimoto" coefficient for bit-strings.
207
+ - `bitwise_sorensen_gt<scalar_t>` for "Dice-Sorensen" coefficient for bit-strings.
206
208
  - `pearson_correlation_gt<scalar_t>` for "Pearson" correlation between probability distributions.
207
- - `haversine_gt<scalar_t>` for "Haversine" or "Great Circle" distance between coordinates.
209
+ - `haversine_gt<scalar_t>` for "Haversine" or "Great Circle" distance between coordinates used in GIS applications.
208
210
 
209
211
  #### Multi-Threading
210
212
 
@@ -311,6 +313,12 @@ def python_dot(a, b, n, m):
311
313
  index = Index(ndim=ndim, metric=python_dot.address)
312
314
  ```
313
315
 
316
+ To use Numba JIT, install USearch with extras:
317
+
318
+ ```sh
319
+ pip install usearch[jit]
320
+ ```
321
+
314
322
  [numba]: https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#c-callbacks
315
323
 
316
324
  #### Tooling
@@ -457,6 +465,33 @@ assert(results.0[0] == 42)
457
465
 
458
466
  ### GoLang
459
467
 
468
+ #### Installation
469
+
470
+ ```golang
471
+ import (
472
+ "github.com/unum-cloud/usearch/golang"
473
+ )
474
+ ```
475
+
476
+ #### Quickstart
477
+
478
+ ```golang
479
+ package main
480
+
481
+ import (
482
+ "fmt"
483
+ "github.com/unum-cloud/usearch/golang"
484
+ )
485
+
486
+ func main() {
487
+ conf := usearch.DefaultConfig(128)
488
+ index := usearch.NewIndex(conf)
489
+ v := make([]float32, 128)
490
+ index.Add(42, v)
491
+ results := index.Search(v, 1)
492
+ }
493
+ ```
494
+
460
495
  ### Wolfram
461
496
 
462
497
  ## TODO
package/binding.gyp CHANGED
@@ -16,8 +16,8 @@
16
16
  "dependencies": [
17
17
  "<!(node -p \"require('node-addon-api').gyp\")"
18
18
  ],
19
- "cflags": ["-fexceptions", "-Wno-unknown-pragmas"],
20
- "cflags_cc": ["-fexceptions", "-Wno-unknown-pragmas", "-std=c++11"],
19
+ "cflags": ["-fexceptions", "-Wno-unknown-pragmas", "-Wno-maybe-uninitialized"],
20
+ "cflags_cc": ["-fexceptions", "-Wno-unknown-pragmas", "-Wno-maybe-uninitialized", "-std=c++11"],
21
21
  "xcode_settings": {
22
22
  "GCC_ENABLE_CPP_EXCEPTIONS": "YES",
23
23
  "CLANG_CXX_LIBRARY": "libc++",
package/c/lib.cpp ADDED
@@ -0,0 +1,141 @@
1
+ #include "../src/punned.hpp"
2
+
3
+ extern "C" {
4
+ #include "usearch.h"
5
+ }
6
+
7
+ using namespace unum::usearch;
8
+ using namespace unum;
9
+
10
+ using label_t = int;
11
+ using distance_t = float;
12
+ using punned_index_t = punned_gt<label_t>;
13
+ using span_t = span_gt<float>;
14
+
15
+ void* usearch_new( //
16
+ usearch_metric_t metric, usearch_accuracy_t accuracy, //
17
+ int dimensions, int capacity, int connectivity, //
18
+ int expansion_add, int expansion_search) {
19
+
20
+ try {
21
+ config_t config;
22
+ config.expansion_add = static_cast<std::size_t>(expansion_add);
23
+ config.expansion_search = static_cast<std::size_t>(expansion_search);
24
+ config.connectivity = static_cast<std::size_t>(connectivity);
25
+ config.max_elements = static_cast<std::size_t>(capacity);
26
+ config.max_threads_add = std::thread::hardware_concurrency();
27
+ config.max_threads_search = std::thread::hardware_concurrency();
28
+
29
+ // TODO: Implement safe buffers for error messages, either here on in
30
+ // the C++ type-punned implementation.
31
+ // static constexpr std::size_t error_limit_k = 128;
32
+ // struct safe_punned_index_t {
33
+ // punned_index_t punned_index;
34
+ // char error_buffers[1];
35
+ // };
36
+ // std::size_t bytes_for_error_messages = error_limit_k * config.max_threads();
37
+ // std::size_t bytes_to_alloc = sizeof(punned_index_t) + bytes_for_error_messages;
38
+ // safe_punned_index_t* result = (safe_punned_index_t*)std::malloc(bytes_to_alloc);
39
+
40
+ accuracy_t accuracy = accuracy_from_name(accuracy_str, accuracy_len);
41
+ common_metric_kind_t metric_kind = common_metric_from_name(metric_str, metric_len);
42
+ punned_index_t index = make_punned<punned_index_t>( //
43
+ metric_kind, static_cast<std::size_t>(dimensions), accuracy, config);
44
+
45
+ punned_index_t* result_ptr = new punned_index_t(std::move(index));
46
+ return result_ptr;
47
+ } catch (std::exception const&) {
48
+ }
49
+ return NULL;
50
+ }
51
+
52
+ void usearch_destroy(punned_index_t* index) { delete index; }
53
+
54
+ char const* usearch_save(punned_index_t* index, char const* path) {
55
+ try {
56
+ index->save(path);
57
+ return NULL;
58
+ } catch (std::exception& e) {
59
+ // q:: added these to make sure I do not pass a potentially dangling e.what()
60
+ // though I realize allocation below may fail and I am not checking for it.
61
+ // what can I do there? Golang side tries to free whatever string is passed from
62
+ // so passing a compile time constant "double fault" string is not an option
63
+ char* res = new char[strlen(e.what()) + 1];
64
+ strcpy(res, e.what());
65
+ return res;
66
+ }
67
+ }
68
+
69
+ char const* usearch_load(punned_index_t* index, char const* path) {
70
+ try {
71
+ index->load(path);
72
+ return NULL;
73
+ } catch (std::exception& e) {
74
+ char* res = new char[strlen(e.what()) + 1];
75
+ strcpy(res, e.what());
76
+ return res;
77
+ }
78
+ }
79
+
80
+ char const* usearch_view(punned_index_t* index, char const* path) {
81
+ try {
82
+ index->view(path);
83
+ return NULL;
84
+ } catch (std::exception& e) {
85
+ char* res = new char[strlen(e.what()) + 1];
86
+ strcpy(res, e.what());
87
+ return res;
88
+ }
89
+ }
90
+
91
+ int usearch_size(punned_index_t* index) { return index->size(); }
92
+ int usearch_connectivity(punned_index_t* index) { return index->connectivity(); }
93
+ int usearch_dimensions(punned_index_t* index) { return index->dimensions(); }
94
+ int usearch_capacity(punned_index_t* index) { return index->capacity(); }
95
+
96
+ char const* usearch_reserve(punned_index_t* index, int capacity) {
97
+ try {
98
+ index->reserve(capacity);
99
+ return NULL;
100
+ } catch (std::exception& e) {
101
+ char* res = new char[strlen(e.what()) + 1];
102
+ strcpy(res, e.what());
103
+ return res;
104
+ }
105
+ }
106
+
107
+ char const* usearch_add(punned_index_t* index, int label, float* vector) {
108
+ // q:: I followed the java example to have try catches everywhere
109
+ // but they are kind of useless as most errors are outside of cpp so
110
+ // those translate into segfaults and are not caught by the runtime
111
+ try {
112
+ index->add(label, vector);
113
+ } catch (std::exception& e) {
114
+ char* res = new char[strlen(e.what()) + 1];
115
+ strcpy(res, e.what());
116
+ return res;
117
+ }
118
+ return NULL;
119
+ }
120
+
121
+ SearchResults usearch_search(punned_index_t* index, float* query, int query_len, int limit) {
122
+ // todo:: this could be allocated as golang slice
123
+ // to avoid a copy. not sure how it interacts with gc
124
+ // that is why doing this now
125
+ label_t* matches_data = new label_t[limit];
126
+
127
+ // todo:: pass the distances to outside world
128
+ distance_t* dist_data = new distance_t[limit];
129
+ SearchResults res{0};
130
+ try {
131
+ span_t vector_span = span_t{query, static_cast<std::size_t>(query_len)};
132
+ res.Len = index->search(vector_span, static_cast<std::size_t>(limit)).dump_to(matches_data, dist_data);
133
+ } catch (std::exception& e) {
134
+ res.Error = new char[strlen(e.what()) + 1];
135
+ strcpy(res.Error, e.what());
136
+ // res.Error = res;
137
+ }
138
+ res.Labels = matches_data;
139
+ res.Distances = dist_data;
140
+ return res;
141
+ }
package/c/usearch.h ADDED
@@ -0,0 +1,43 @@
1
+ #ifndef UNUM_USEARCH_H
2
+ #define UNUM_USEARCH_H
3
+
4
+ #include <stdlib.h>
5
+
6
+ typedef enum {
7
+ usearch_metric_ip_k = 0,
8
+ usearch_metric_l2sq_k = 1,
9
+ usearch_metric_cos_k = 2,
10
+ usearch_metric_haversine_k = 3
11
+ } usearch_metric_t;
12
+
13
+ typedef enum {
14
+ usearch_accuracy_f32_k = 0,
15
+ usearch_accuracy_f16_k = 1,
16
+ usearch_accuracy_f8_k = 2,
17
+ } usearch_accuracy_t;
18
+
19
+ void* usearch_new( //
20
+ usearch_metric_t metric, usearch_accuracy_t accuracy, //
21
+ int dimensions, int capacity, int connectivity, //
22
+ int expansion_add, int expansion_search);
23
+
24
+ void usearch_destroy(void* index);
25
+
26
+ char const* usearch_save(void* index, char const* path);
27
+ char const* usearch_load(void* index, char const* path);
28
+ char const* usearch_view(void* index, char const* path);
29
+
30
+ int usearch_size(void* index);
31
+ int usearch_connectivity(void* index);
32
+ int usearch_dimensions(void* index);
33
+ int usearch_capacity(void* index);
34
+
35
+ char const* usearch_reserve(void* index, int capacity);
36
+ char const* usearch_add(void* index, int label, float const* vec);
37
+
38
+ char const* usearch_search( //
39
+ void* index, //
40
+ float const* query, int limit, //
41
+ int* labels, float* distances, int* count);
42
+
43
+ #endif
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.11.1'
7
+ version = '0.12.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'
@@ -62,8 +62,12 @@ Benchmarking:
62
62
 
63
63
  ## Python
64
64
 
65
+ Use PyTest to validate the build.
66
+ The `-s` option will disable capturing the logs.
67
+ The `-x` option will exit after first failure to simplify debugging.
68
+
65
69
  ```sh
66
- pip install -e . && pytest python/scripts/test.py
70
+ pip install -e . && pytest python/scripts/test.py -s -x
67
71
  ```
68
72
 
69
73
  Linting:
@@ -95,8 +99,7 @@ python python/scripts/bench_params.py connectivity
95
99
  ## JavaScript
96
100
 
97
101
  ```sh
98
- npm install
99
- node javascript/test.js
102
+ npm install && node javascript/test.js
100
103
  npm publish
101
104
  ```
102
105
 
@@ -148,6 +151,14 @@ java -cp . -Djava.library.path=/Users/av/github/usearch/java cloud.unum.usearch.
148
151
  brew install --cask wolfram-engine
149
152
  ```
150
153
 
154
+ ## C
155
+
156
+ ### Linux
157
+
158
+ ```sh
159
+ g++ -shared -fPIC lib.cpp -I ../include/ -I ../fp16/include/ -o libusearch.so
160
+ ```
161
+
151
162
  ## Docker
152
163
 
153
164
  ```sh
package/golang/go.mod ADDED
@@ -0,0 +1,3 @@
1
+ module github.com/unum-cloud/usearch/golang
2
+
3
+ go 1.19