usearch 0.8.0 → 0.9.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.
@@ -160,11 +160,51 @@ jobs:
160
160
  - name: Run tests
161
161
  run: swift test
162
162
 
163
+ # Publishes to both Docker Hub and GitHub Container Registry
164
+ # https://docs.github.com/en/actions/publishing-packages/publishing-docker-images#publishing-images-to-docker-hub-and-github-packages
165
+ publish_docker:
166
+ name: Publish Docker Image
167
+ needs: versioning
168
+ runs-on: ubuntu-latest
169
+ steps:
170
+ - uses: actions/checkout@v3
171
+ with:
172
+ ref: 'main'
173
+ - run: git submodule update --init --recursive
174
+ - name: Login to Docker Hub
175
+ uses: docker/login-action@v2
176
+ with:
177
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
178
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
179
+ - name: Log in to GitHub Registry
180
+ uses: docker/login-action@2
181
+ with:
182
+ registry: ghcr.io
183
+ username: ${{ github.actor }}
184
+ password: ${{ secrets.GITHUB_TOKEN }}
185
+ - name: Set up Docker Buildx
186
+ uses: docker/setup-buildx-action@v2
187
+ - name: Extract tags & labels for Docker
188
+ id: meta
189
+ uses: docker/metadata-action@4
190
+ with:
191
+ images: |
192
+ unum/usearch
193
+ ghcr.io/${{ github.repository }}
194
+ - name: Build and push
195
+ uses: docker/build-push-action@v4
196
+ with:
197
+ context: .
198
+ push: true
199
+ tags: ${{ steps.meta.outputs.tags }}
200
+ labels: ${{ steps.meta.outputs.labels }}
201
+
202
+
163
203
  build_docs:
164
204
  name: Build Docs
165
205
  runs-on: ubuntu-22.04
166
206
  if: ${{ always() }}
167
- needs: [publish_python, publish_javascript, publish_rust, publish_java, publish_swift]
207
+ needs: [publish_python, publish_javascript, publish_rust, publish_java, publish_swift, publish_docker]
168
208
  permissions:
169
209
  contents: write
170
210
  steps:
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "usearch"
3
- version = "0.8.0"
3
+ version = "0.9.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/Dockerfile CHANGED
@@ -1,6 +1,8 @@
1
1
  # syntax=docker/dockerfile:1
2
2
 
3
3
  FROM ubuntu:23.04
4
+ ENV DEBIAN_FRONTEND=noninteractive
5
+
4
6
  RUN apt-get update && \
5
7
  apt-get install -y --no-install-recommends python3.11 python3-pip && \
6
8
  apt-get clean && \
@@ -10,5 +12,5 @@ RUN pip3 install --no-cache-dir --break-system-packages ucall usearch
10
12
  WORKDIR /usearch
11
13
  COPY python/usearch/server.py server.py
12
14
 
13
- CMD ["python3", "./server.py"]
15
+ ENTRYPOINT ["python3", "./server.py"]
14
16
  EXPOSE 8545
package/README.md CHANGED
@@ -417,7 +417,7 @@ assert!(new_haversine(&quant, 0, 0, 0).is_ok());
417
417
 
418
418
  ```xml
419
419
  <dependency>
420
- <groupId>cloud.unum.usearch</groupId>
420
+ <groupId>cloud.unum</groupId>
421
421
  <artifactId>usearch</artifactId>
422
422
  <version>0.2.3</version>
423
423
  </dependency>
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.8.0
1
+ 0.9.0
package/build.gradle CHANGED
@@ -6,7 +6,7 @@ plugins {
6
6
  id 'maven-publish'
7
7
  }
8
8
 
9
- group "cloud.unum.usearch"
9
+ group "cloud.unum"
10
10
  version = file("VERSION").text.trim()
11
11
 
12
12
  sourceCompatibility = 1.8
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.8.0'
7
+ version = '0.9.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'
@@ -136,4 +136,19 @@ brew install --cask wolfram-engine
136
136
 
137
137
  ```sh
138
138
  docker build -t unum/usearch . && docker run unum/usearch
139
+ ```
140
+
141
+ For multi-architecture builds and publications:
142
+
143
+ ```sh
144
+ version=$(cat VERSION)
145
+ docker buildx create --use &&
146
+ docker login &&
147
+ docker buildx build \
148
+ --platform "linux/amd64,linux/arm64" \
149
+ --build-arg version=$version \
150
+ --file Dockerfile \
151
+ --tag unum/usearch:$version \
152
+ --tag unum/usearch:latest \
153
+ --push .
139
154
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ashot Vardanian",
6
6
  "license": "Apache 2.0",
package/pyproject.toml CHANGED
@@ -10,7 +10,7 @@ filterwarnings = ["error"]
10
10
 
11
11
  [tool.cibuildwheel]
12
12
  test-requires = ["pytest", "numpy"]
13
- test-command = "pytest {project}/python/test.py"
13
+ test-command = "pytest {project}/python/scripts/test.py"
14
14
  build-verbosity = 0
15
15
  skip = ["*musllinux*", "*i686*", "pp*"]
16
16
 
@@ -1,9 +1,8 @@
1
1
  import time
2
2
  import numpy as np
3
- import fire
3
+ import math
4
4
 
5
- import numpy as np
6
- from numba import cfunc, types, carray
5
+ import fire
7
6
 
8
7
  from usearch.index import Index
9
8
  from usearch.io import load_matrix
@@ -51,25 +50,6 @@ def bench_usearch(index, vectors: np.array, queries: np.array, neighbors: np.arr
51
50
  recall_at_one /= queries.shape[0]
52
51
  print(f'- Recall@1: {recall_at_one * 100:.2f} %')
53
52
 
54
- # Showcases how to use Numba to JIT-compile similarity measures for USearch.
55
- # https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#c-callbacks
56
-
57
-
58
- signature_f32 = types.float32(
59
- types.CPointer(types.float32),
60
- types.CPointer(types.float32),
61
- types.size_t, types.size_t)
62
-
63
-
64
- @cfunc(signature_f32)
65
- def inner_product_f32(a, b, n, m):
66
- a_array = carray(a, n)
67
- b_array = carray(b, n)
68
- c = types.float32(0)
69
- for i in range(n):
70
- c += a_array[i] * b_array[i]
71
- return types.float32(1 - c)
72
-
73
53
 
74
54
  def main(
75
55
  vectors: str,
@@ -94,16 +74,6 @@ def main(
94
74
  )
95
75
  bench_usearch(index, vectors_mat, queries_mat, neighbors_mat)
96
76
 
97
- print('USearch: HNSW + Numba JIT')
98
- index = Index(
99
- ndim=dim,
100
- expansion_add=expansion_add,
101
- expansion_search=expansion_search,
102
- connectivity=connectivity,
103
- metric_pointer=inner_product_f32.address,
104
- )
105
- bench_usearch(index, vectors_mat, queries_mat, neighbors_mat)
106
-
107
77
  print('USearch: HNSW + Half Precision')
108
78
  index = Index(
109
79
  ndim=dim,
@@ -124,27 +94,94 @@ def main(
124
94
  )
125
95
  bench_usearch(index, vectors_mat, queries_mat, neighbors_mat)
126
96
 
97
+ # Showcases how to use Numba to JIT-compile similarity measures for USearch.
98
+ # https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#c-callbacks
99
+ try:
100
+ from numba import cfunc, types, carray
101
+
102
+ signature_f32 = types.float32(
103
+ types.CPointer(types.float32),
104
+ types.CPointer(types.float32),
105
+ types.size_t, types.size_t)
106
+
107
+ @cfunc(signature_f32)
108
+ def inner_product_f32(a, b, n, m):
109
+ a_array = carray(a, n)
110
+ b_array = carray(b, n)
111
+ c = types.float32(0)
112
+ for i in range(n):
113
+ c += a_array[i] * b_array[i]
114
+ return types.float32(1 - c)
115
+
116
+ signature_f8 = types.float32(
117
+ types.CPointer(types.int8),
118
+ types.CPointer(types.int8),
119
+ types.size_t, types.size_t)
120
+
121
+ @cfunc(signature_f8)
122
+ def cos_f8(a, b, n, m):
123
+ a_array = carray(a, n)
124
+ b_array = carray(b, n)
125
+ c = types.int32(0)
126
+ a_sq = types.int32(0)
127
+ b_sq = types.int32(0)
128
+ for i in range(n):
129
+ ai = types.int16(a_array[i])
130
+ bi = types.int16(b_array[i])
131
+ c += ai * bi
132
+ a_sq += ai * ai
133
+ b_sq += bi * bi
134
+ a_norm = math.sqrt(types.float32(a_sq))
135
+ b_norm = math.sqrt(types.float32(b_sq))
136
+ c_norm = types.float32(c) / (a_norm * b_norm)
137
+ return types.float32(1 - c_norm)
138
+
139
+ print('USearch: HNSW + Numba JIT')
140
+ index = Index(
141
+ ndim=dim,
142
+ expansion_add=expansion_add,
143
+ expansion_search=expansion_search,
144
+ connectivity=connectivity,
145
+ metric_pointer=inner_product_f32.address,
146
+ )
147
+ bench_usearch(index, vectors_mat, queries_mat, neighbors_mat)
148
+
149
+ print('USearch: HNSW + Quarter Precision + Numba JIT')
150
+ index = Index(
151
+ ndim=dim,
152
+ dtype='f8',
153
+ expansion_add=expansion_add,
154
+ expansion_search=expansion_search,
155
+ connectivity=connectivity,
156
+ metric_pointer=cos_f8.address,
157
+ )
158
+ bench_usearch(index, vectors_mat, queries_mat, neighbors_mat)
159
+ except ImportError:
160
+ print('Skipping Numba benchmarks')
161
+
127
162
  # Don't depend on the FAISS installation for benchmarks
128
163
  try:
129
164
  from faiss import IndexHNSWFlat, IndexIVFPQ
165
+
166
+ print('FAISS: HNSW')
167
+ index = IndexHNSWFlat(dim, connectivity)
168
+ index.hnsw.efSearch = expansion_search
169
+ index.hnsw.efConstruction = expansion_add
170
+ bench_faiss(index, vectors_mat, queries_mat, neighbors_mat)
171
+
172
+ print('FAISS: HNSW + IVFPQ')
173
+ # Number of inverted lists (number of partitions or cells).
174
+ nlist = 10000
175
+ nsegment = 16 # Number of segments for PQ (number of subquantizers).
176
+ nbit = 8 # Number of bits to encode each segment.
177
+ coarse_quantizer = IndexHNSWFlat(dim, connectivity)
178
+ index = IndexIVFPQ(coarse_quantizer, dim, nlist, nsegment, nbit)
179
+ index.train(vectors_mat)
180
+ index.nprobe = 10
181
+ bench_faiss(index, vectors_mat, queries_mat, neighbors_mat)
182
+
130
183
  except ImportError:
131
- return
132
-
133
- print('FAISS: HNSW')
134
- index = IndexHNSWFlat(dim, connectivity)
135
- index.hnsw.efSearch = expansion_search
136
- index.hnsw.efConstruction = expansion_add
137
- bench_faiss(index, vectors_mat, queries_mat, neighbors_mat)
138
-
139
- print('FAISS: HNSW + IVFPQ')
140
- nlist = 10000 # Number of inverted lists (number of partitions or cells).
141
- nsegment = 16 # Number of segments for PQ (number of subquantizers).
142
- nbit = 8 # Number of bits to encode each segment.
143
- coarse_quantizer = IndexHNSWFlat(dim, connectivity)
144
- index = IndexIVFPQ(coarse_quantizer, dim, nlist, nsegment, nbit)
145
- index.train(vectors_mat)
146
- index.nprobe = 10
147
- bench_faiss(index, vectors_mat, queries_mat, neighbors_mat)
184
+ print('Skipping FAISS benchmarks')
148
185
 
149
186
 
150
187
  if __name__ == '__main__':
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  import time
3
3
  import numpy as np
4
+
4
5
  import fire
5
6
  import plotly.express as px
6
7
  import numpy as np