usearch 0.19.1 → 0.19.2
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/Cargo.toml +1 -1
- package/conanfile.py +1 -1
- package/package.json +1 -1
- package/python/scripts/bench.py +10 -10
- package/python/scripts/test.py +15 -7
- package/python/usearch/index.py +7 -25
- package/python/usearch/numba.py +28 -22
package/Cargo.toml
CHANGED
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.19.
|
|
7
|
+
version = '0.19.2'
|
|
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/package.json
CHANGED
package/python/scripts/bench.py
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import logging
|
|
3
1
|
import itertools
|
|
4
|
-
from typing import List
|
|
2
|
+
from typing import List
|
|
5
3
|
from dataclasses import asdict
|
|
6
4
|
|
|
7
5
|
import numpy as np
|
|
8
6
|
import pandas as pd
|
|
9
7
|
|
|
10
|
-
from usearch.index import Index, Label
|
|
11
|
-
from usearch.
|
|
8
|
+
from usearch.index import Index, Label, MetricKind, ScalarKind
|
|
9
|
+
from usearch.numba import jit as njit
|
|
10
|
+
from usearch.eval import Evaluation, AddTask
|
|
12
11
|
from usearch.index import (
|
|
13
12
|
DEFAULT_CONNECTIVITY,
|
|
14
13
|
DEFAULT_EXPANSION_ADD,
|
|
@@ -26,14 +25,15 @@ def bench_speed(
|
|
|
26
25
|
) -> pd.DataFrame:
|
|
27
26
|
# Build various indexes:
|
|
28
27
|
indexes = []
|
|
29
|
-
jit_options = [False]
|
|
30
|
-
|
|
31
|
-
jit_options.append(jit)
|
|
32
|
-
dtype_options = ["f32", "f16", "f8"]
|
|
28
|
+
jit_options = [False, True] if jit else [False]
|
|
29
|
+
dtype_options = [ScalarKind.F32, ScalarKind.F16, ScalarKind.F8]
|
|
33
30
|
for jit, dtype in itertools.product(jit_options, dtype_options):
|
|
31
|
+
metric = MetricKind.IP
|
|
32
|
+
if jit:
|
|
33
|
+
metric = njit(eval.ndim, metric, dtype)
|
|
34
34
|
index = Index(
|
|
35
35
|
ndim=eval.ndim,
|
|
36
|
-
|
|
36
|
+
metric=metric,
|
|
37
37
|
dtype=dtype,
|
|
38
38
|
expansion_add=expansion_add,
|
|
39
39
|
expansion_search=expansion_search,
|
package/python/scripts/test.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import importlib.util
|
|
3
|
+
|
|
2
4
|
import pytest
|
|
3
5
|
import numpy as np
|
|
4
6
|
|
|
5
7
|
from usearch.io import load_matrix, save_matrix
|
|
6
|
-
from usearch.eval import
|
|
8
|
+
from usearch.eval import random_vectors
|
|
9
|
+
from usearch.numba import jit as njit
|
|
7
10
|
|
|
8
11
|
from usearch.index import (
|
|
9
12
|
Index,
|
|
@@ -13,9 +16,7 @@ from usearch.index import (
|
|
|
13
16
|
MetricSignature,
|
|
14
17
|
CompiledMetric,
|
|
15
18
|
Matches,
|
|
16
|
-
MetricKindBitwise,
|
|
17
19
|
)
|
|
18
|
-
from usearch.index import _normalize_dtype
|
|
19
20
|
from usearch.index import (
|
|
20
21
|
DEFAULT_CONNECTIVITY,
|
|
21
22
|
DEFAULT_EXPANSION_ADD,
|
|
@@ -34,7 +35,7 @@ index_types = [
|
|
|
34
35
|
numpy_types = [np.float32, np.float64, np.float16]
|
|
35
36
|
|
|
36
37
|
connectivity_options = [3, 13, 50, DEFAULT_CONNECTIVITY]
|
|
37
|
-
jit_options = [False]
|
|
38
|
+
jit_options = [False, True] if importlib.util.find_spec("spam") is not None else [False]
|
|
38
39
|
continuous_metrics = [
|
|
39
40
|
MetricKind.Cos,
|
|
40
41
|
MetricKind.L2sq,
|
|
@@ -80,6 +81,8 @@ def test_index(
|
|
|
80
81
|
connectivity: int,
|
|
81
82
|
jit: bool,
|
|
82
83
|
):
|
|
84
|
+
if jit:
|
|
85
|
+
metric = njit(ndim, metric, index_type)
|
|
83
86
|
index = Index(
|
|
84
87
|
metric=metric,
|
|
85
88
|
ndim=ndim,
|
|
@@ -87,7 +90,6 @@ def test_index(
|
|
|
87
90
|
connectivity=connectivity,
|
|
88
91
|
expansion_add=DEFAULT_EXPANSION_ADD,
|
|
89
92
|
expansion_search=DEFAULT_EXPANSION_SEARCH,
|
|
90
|
-
jit=jit,
|
|
91
93
|
)
|
|
92
94
|
assert index.ndim == ndim
|
|
93
95
|
assert index.connectivity == connectivity
|
|
@@ -176,7 +178,10 @@ def test_index_batch(
|
|
|
176
178
|
@pytest.mark.parametrize("index_type", index_types)
|
|
177
179
|
@pytest.mark.parametrize("numpy_type", numpy_types)
|
|
178
180
|
def test_exact_recall(
|
|
179
|
-
metric: MetricKind,
|
|
181
|
+
metric: MetricKind,
|
|
182
|
+
batch_size: int,
|
|
183
|
+
index_type: ScalarKind,
|
|
184
|
+
numpy_type: str,
|
|
180
185
|
):
|
|
181
186
|
ndim: int = batch_size
|
|
182
187
|
index = Index(ndim=ndim, metric=metric, dtype=index_type)
|
|
@@ -436,7 +441,10 @@ def test_index_peachpy(ndim: int, batch_size: int):
|
|
|
436
441
|
@pytest.mark.parametrize("connectivity", connectivity_options)
|
|
437
442
|
@pytest.mark.parametrize("batch_size", batch_sizes)
|
|
438
443
|
def test_bitwise_index(
|
|
439
|
-
bits: int,
|
|
444
|
+
bits: int,
|
|
445
|
+
metric: MetricKind,
|
|
446
|
+
connectivity: int,
|
|
447
|
+
batch_size: int,
|
|
440
448
|
):
|
|
441
449
|
index = Index(ndim=bits, metric=metric, connectivity=connectivity)
|
|
442
450
|
|
package/python/usearch/index.py
CHANGED
|
@@ -167,10 +167,9 @@ class Index:
|
|
|
167
167
|
|
|
168
168
|
def __init__(
|
|
169
169
|
self,
|
|
170
|
-
ndim: int,
|
|
170
|
+
ndim: int = 0,
|
|
171
171
|
metric: Union[str, MetricKind, CompiledMetric] = MetricKind.IP,
|
|
172
|
-
dtype: Optional[str] = None,
|
|
173
|
-
jit: bool = False,
|
|
172
|
+
dtype: Optional[Union[str, ScalarKind]] = None,
|
|
174
173
|
connectivity: int = DEFAULT_CONNECTIVITY,
|
|
175
174
|
expansion_add: int = DEFAULT_EXPANSION_ADD,
|
|
176
175
|
expansion_search: int = DEFAULT_EXPANSION_SEARCH,
|
|
@@ -182,6 +181,10 @@ class Index:
|
|
|
182
181
|
|
|
183
182
|
:param ndim: Number of vector dimensions
|
|
184
183
|
:type ndim: int
|
|
184
|
+
Required for some metrics, optional for others.
|
|
185
|
+
Haversine, for example, only applies to 2-dimensional latitude/longitude
|
|
186
|
+
coordinates. Angular (Cos) and Euclidean (L2sq), obviously, apply to
|
|
187
|
+
vectors with arbitrary number of dimensions.
|
|
185
188
|
|
|
186
189
|
:param metric: Distance function, defaults to MetricKind.IP
|
|
187
190
|
:type metric: Union[MetricKind, Callable, str], optional
|
|
@@ -191,18 +194,12 @@ class Index:
|
|
|
191
194
|
Not every kind is JIT-able. For Jaccard distance, use `SparseIndex`.
|
|
192
195
|
|
|
193
196
|
:param dtype: Scalar type for internal vector storage, defaults to None
|
|
194
|
-
:type dtype: str, optional
|
|
197
|
+
:type dtype: Optional[Union[str, ScalarKind]], optional
|
|
195
198
|
For continuous metrics can be: f16, f32, f64, or f8.
|
|
196
199
|
For bitwise metrics it's implementation-defined, and can't change.
|
|
197
200
|
Example: you can use the `f16` index with `f32` vectors in Euclidean space,
|
|
198
201
|
which will be automatically downcasted.
|
|
199
202
|
|
|
200
|
-
:param jit: Enable Numba to JIT compile the metric, defaults to False
|
|
201
|
-
:type jit: bool, optional
|
|
202
|
-
This can result in up-to 3x performance difference on very large vectors
|
|
203
|
-
and very recent hardware, as the Python module is compiled with high
|
|
204
|
-
compatibility in mind and avoids very fancy assembly instructions.
|
|
205
|
-
|
|
206
203
|
:param connectivity: Connections per node in HNSW, defaults to None
|
|
207
204
|
:type connectivity: Optional[int], optional
|
|
208
205
|
Hyper-parameter for the number of Graph connections
|
|
@@ -231,21 +228,6 @@ class Index:
|
|
|
231
228
|
"""
|
|
232
229
|
|
|
233
230
|
metric = _normalize_metric(metric)
|
|
234
|
-
if isinstance(metric, MetricKind) and jit:
|
|
235
|
-
try:
|
|
236
|
-
from usearch.numba import jit
|
|
237
|
-
except ImportError:
|
|
238
|
-
raise ModuleNotFoundError(
|
|
239
|
-
"To use JIT install Numba with `pip install numba`."
|
|
240
|
-
"Alternatively, reinstall with `pip install usearch[jit]`"
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
metric = jit(
|
|
244
|
-
ndim=ndim,
|
|
245
|
-
metric=metric,
|
|
246
|
-
dtype=_normalize_dtype(dtype, metric),
|
|
247
|
-
)
|
|
248
|
-
|
|
249
231
|
if isinstance(metric, MetricKind):
|
|
250
232
|
self._metric_kind = metric
|
|
251
233
|
self._metric_jit = None
|
package/python/usearch/numba.py
CHANGED
|
@@ -4,31 +4,19 @@
|
|
|
4
4
|
# into the primary `Index` class, connecting USearch with Numba.
|
|
5
5
|
from math import sqrt
|
|
6
6
|
|
|
7
|
-
from numba import cfunc, types, carray
|
|
8
|
-
|
|
9
7
|
from usearch.index import MetricKind, ScalarKind, MetricSignature, CompiledMetric
|
|
10
8
|
|
|
11
9
|
|
|
12
|
-
signature_i8args = types.float32(types.CPointer(types.int8), types.CPointer(types.int8))
|
|
13
|
-
|
|
14
|
-
signature_f16args = types.float32(
|
|
15
|
-
types.CPointer(types.float16), types.CPointer(types.float16)
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
signature_f32args = types.float32(
|
|
19
|
-
types.CPointer(types.float32), types.CPointer(types.float32)
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
signature_f64args = types.float32(
|
|
23
|
-
types.CPointer(types.float64), types.CPointer(types.float64)
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
|
|
27
10
|
def jit(
|
|
28
|
-
ndim: int,
|
|
11
|
+
ndim: int,
|
|
12
|
+
metric: MetricKind = MetricKind.Cos,
|
|
13
|
+
dtype: ScalarKind = ScalarKind.F32,
|
|
29
14
|
) -> CompiledMetric:
|
|
30
|
-
"""JIT-compiles
|
|
31
|
-
|
|
15
|
+
"""JIT-compiles the metric for target hardware and number of dimensions.
|
|
16
|
+
|
|
17
|
+
This can result in up-to 3x performance difference on very large vectors
|
|
18
|
+
and very recent hardware, as the Python module is compiled with high
|
|
19
|
+
compatibility in mind and avoids very fancy assembly instructions.
|
|
32
20
|
|
|
33
21
|
Uses Numba `cfunc` functionality, annotating it with Numba `types` instead
|
|
34
22
|
of `ctypes` to support half-precision.
|
|
@@ -37,6 +25,24 @@ def jit(
|
|
|
37
25
|
assert isinstance(metric, MetricKind)
|
|
38
26
|
assert isinstance(dtype, ScalarKind)
|
|
39
27
|
|
|
28
|
+
from numba import cfunc, types, carray
|
|
29
|
+
|
|
30
|
+
signature_i8args = types.float32(
|
|
31
|
+
types.CPointer(types.int8), types.CPointer(types.int8)
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
signature_f16args = types.float32(
|
|
35
|
+
types.CPointer(types.float16), types.CPointer(types.float16)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
signature_f32args = types.float32(
|
|
39
|
+
types.CPointer(types.float32), types.CPointer(types.float32)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
signature_f64args = types.float32(
|
|
43
|
+
types.CPointer(types.float64), types.CPointer(types.float64)
|
|
44
|
+
)
|
|
45
|
+
|
|
40
46
|
numba_supported_types = (
|
|
41
47
|
ScalarKind.F8,
|
|
42
48
|
# Half-precision is still unsupported
|
|
@@ -46,7 +52,7 @@ def jit(
|
|
|
46
52
|
ScalarKind.F64,
|
|
47
53
|
)
|
|
48
54
|
if dtype not in numba_supported_types:
|
|
49
|
-
return
|
|
55
|
+
return metric
|
|
50
56
|
|
|
51
57
|
scalar_kind_to_accumulator_type = {
|
|
52
58
|
ScalarKind.F8: types.int32,
|
|
@@ -109,7 +115,7 @@ def jit(
|
|
|
109
115
|
|
|
110
116
|
pointer = cfunc(scalar_kind_to_signature[dtype])(metric_kind_to_function[metric])
|
|
111
117
|
return CompiledMetric(
|
|
112
|
-
pointer=pointer,
|
|
118
|
+
pointer=pointer.address,
|
|
113
119
|
kind=metric,
|
|
114
120
|
signature=MetricSignature.ArrayArray,
|
|
115
121
|
)
|