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 CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "usearch"
3
- version = "0.19.1"
3
+ version = "0.19.2"
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/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.1'
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usearch",
3
- "version": "0.19.1",
3
+ "version": "0.19.2",
4
4
  "description": "Smaller & Faster Single-File Vector Search Engine from Unum",
5
5
  "author": "Ashot Vardanian",
6
6
  "license": "Apache 2.0",
@@ -1,14 +1,13 @@
1
- import os
2
- import logging
3
1
  import itertools
4
- from typing import List, Optional, Iterable
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.eval import Evaluation, Dataset, AddTask
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
- if jit:
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
- jit=jit,
36
+ metric=metric,
37
37
  dtype=dtype,
38
38
  expansion_add=expansion_add,
39
39
  expansion_search=expansion_search,
@@ -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 recall_members, random_vectors
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, batch_size: int, index_type: ScalarKind, numpy_type: str
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, metric: MetricKind, connectivity: int, batch_size: 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
 
@@ -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
@@ -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, metric: MetricKind = MetricKind.Cos, dtype: ScalarKind = ScalarKind.F32
11
+ ndim: int,
12
+ metric: MetricKind = MetricKind.Cos,
13
+ dtype: ScalarKind = ScalarKind.F32,
29
14
  ) -> CompiledMetric:
30
- """JIT-compiles a distance metric specifically tuned for the target hardware
31
- and number of dimensions.
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 None
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
  )