usearch 0.18.5 → 0.18.8

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.
@@ -21,12 +21,15 @@ def bench_speed(
21
21
  connectivity: int = DEFAULT_CONNECTIVITY,
22
22
  expansion_add: int = DEFAULT_EXPANSION_ADD,
23
23
  expansion_search: int = DEFAULT_EXPANSION_SEARCH,
24
+ jit: bool = False,
24
25
  train: bool = False,
25
26
  ) -> pd.DataFrame:
26
27
 
27
28
  # Build various indexes:
28
29
  indexes = []
29
30
  jit_options = [False]
31
+ if jit:
32
+ jit_options.append(jit)
30
33
  dtype_options = ['f32', 'f16', 'f8']
31
34
  for jit, dtype in itertools.product(jit_options, dtype_options):
32
35
 
@@ -39,6 +42,10 @@ def bench_speed(
39
42
  connectivity=connectivity,
40
43
  path='USearch' + ['', '+JIT'][jit] + ':' + dtype,
41
44
  )
45
+
46
+ # Skip the cases, where JIT-ing is impossible
47
+ if jit and not index.jit:
48
+ continue
42
49
  indexes.append(index)
43
50
 
44
51
  # Add FAISS indexes to the mix:
@@ -186,7 +186,7 @@ def test_bitwise_index(bits: int, metric: MetricKind, connectivity: int, batch_s
186
186
 
187
187
  index.add(labels, bit_vectors)
188
188
  assert np.allclose(index.get_vectors(labels), byte_vectors, atol=0.1)
189
- assert np.alltrue(index.get_vectors(labels, ScalarKind.B1) == bit_vectors)
189
+ assert np.all(index.get_vectors(labels, ScalarKind.B1) == bit_vectors)
190
190
 
191
191
  index.search(bit_vectors, 10)
192
192
 
@@ -184,6 +184,13 @@ class Index:
184
184
  :type view: bool, optional
185
185
  """
186
186
 
187
+ if metric in MetricKindBitwise:
188
+ assert dtype is None or dtype == ScalarKind.B1
189
+ dtype = ScalarKind.B1
190
+ else:
191
+ dtype = _normalize_dtype(dtype)
192
+
193
+
187
194
  if metric is None:
188
195
  metric = MetricKind.IP
189
196
  elif isinstance(metric, str):
@@ -230,12 +237,6 @@ class Index:
230
237
  raise ValueError(
231
238
  'The `metric` must be Numba callback or a `MetricKind`')
232
239
 
233
- if metric in MetricKindBitwise:
234
- assert dtype is None or dtype == ScalarKind.B1
235
- dtype = ScalarKind.B1
236
- else:
237
- dtype = _normalize_dtype(dtype)
238
-
239
240
  self._compiled = _CompiledIndex(
240
241
  ndim=ndim,
241
242
  metric=self._metric_kind,
@@ -8,9 +8,12 @@ from typing import Callable
8
8
  import numpy as np
9
9
  from numba import cfunc, types, carray
10
10
 
11
- from usearch.compiled import MetricKind
11
+ from usearch.compiled import MetricKind, ScalarKind
12
12
 
13
13
 
14
+ signature_i8args = types.float32(types.CPointer(
15
+ types.int8), types.CPointer(types.int8))
16
+
14
17
  signature_f16args = types.float32(types.CPointer(
15
18
  types.float16), types.CPointer(types.float16))
16
19
 
@@ -23,7 +26,7 @@ signature_f64args = types.float32(types.CPointer(
23
26
 
24
27
  def jit(ndim: int,
25
28
  metric: MetricKind = MetricKind.Cos,
26
- dtype: np.generic = np.float32) -> Callable:
29
+ dtype: ScalarKind = ScalarKind.F32) -> Callable:
27
30
  """JIT-compiles a distance metric specifically tuned for the target hardware
28
31
  and number of dimensions.
29
32
 
@@ -31,22 +34,25 @@ def jit(ndim: int,
31
34
  of `ctypes` to support half-precision.
32
35
  https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#c-callbacks
33
36
  """
34
- normalize_dtype = {
37
+ assert isinstance(metric, MetricKind)
38
+ assert isinstance(dtype, ScalarKind)
39
+
40
+ numba_supported_types = (
41
+ ScalarKind.F8,
35
42
  # Half-precision is still unsupported
36
43
  # https://github.com/numba/numba/issues/4402
37
- # 'f16': np.float16,
38
- 'f32': np.float32,
39
- 'f64': np.float64,
40
- }
41
- if dtype in normalize_dtype.keys():
42
- dtype = normalize_dtype[dtype]
43
- if dtype not in normalize_dtype.values():
44
+ # ScalarKind.F16: np.float16,
45
+ ScalarKind.F32,
46
+ ScalarKind.F64,
47
+ )
48
+ if dtype not in numba_supported_types:
44
49
  return None
45
50
 
46
51
  scalar_kind_to_accumulator_type = {
47
- np.float16: types.float32,
48
- np.float32: types.float32,
49
- np.float64: types.float64,
52
+ ScalarKind.F8: types.int32,
53
+ ScalarKind.F16: types.float16,
54
+ ScalarKind.F32: types.float32,
55
+ ScalarKind.F64: types.float64,
50
56
  }
51
57
  accumulator = scalar_kind_to_accumulator_type[dtype]
52
58
 
@@ -87,9 +93,10 @@ def jit(ndim: int,
87
93
  return types.float32(ab_delta_sq)
88
94
 
89
95
  scalar_kind_to_signature = {
90
- np.float16: signature_f16args,
91
- np.float32: signature_f32args,
92
- np.float64: signature_f64args,
96
+ ScalarKind.F8: signature_i8args,
97
+ ScalarKind.F16: signature_f16args,
98
+ ScalarKind.F32: signature_f32args,
99
+ ScalarKind.F64: signature_f64args,
93
100
  }
94
101
 
95
102
  metric_kind_to_function = {
@@ -98,4 +105,7 @@ def jit(ndim: int,
98
105
  MetricKind.L2sq: numba_l2sq,
99
106
  }
100
107
 
108
+ if dtype == ScalarKind.F8 and metric == MetricKind.IP:
109
+ metric = MetricKind.Cos
110
+
101
111
  return cfunc(scalar_kind_to_signature[dtype])(metric_kind_to_function[metric])
@@ -185,19 +185,33 @@ inline static simsimd_f32_t simsimd_cos_f16x4neon(simsimd_f16_t const* a, simsim
185
185
 
186
186
  inline static simsimd_f32_t simsimd_cos_i8x16neon(int8_t const* a, int8_t const* b, size_t d) {
187
187
  #if defined(__ARM_NEON)
188
- int8x16_t const* a8x16 = reinterpret_cast<int8x16_t const*>(a);
189
- int8x16_t const* b8x16 = reinterpret_cast<int8x16_t const*>(b);
190
-
191
188
  int32x4_t ab_vec = vdupq_n_s32(0);
192
189
  int32x4_t a2_vec = vdupq_n_s32(0);
193
190
  int32x4_t b2_vec = vdupq_n_s32(0);
194
- for (size_t i = 0; i != d / 16; ++i) {
195
- int8x16_t a_vec = a8x16[i];
196
- int8x16_t b_vec = b8x16[i];
191
+
192
+ #if 0 // This 128-bit `vdot_s32` intrinsic is often unavailable, so we use the 64-bit `vdot_s32`.
193
+ for (size_t i = 0; i != d; i += 16) {
194
+ int8x16_t a_vec = vld1q_s8(a + i);
195
+ int8x16_t b_vec = vld1q_s8(b + i);
197
196
  ab_vec = vdotq_s32(ab_vec, a_vec, b_vec);
198
197
  a2_vec = vdotq_s32(a2_vec, a_vec, a_vec);
199
198
  b2_vec = vdotq_s32(b2_vec, b_vec, b_vec);
200
199
  }
200
+ #else
201
+ for (size_t i = 0; i != d; i += 8) {
202
+ int16x8_t a_vec = vmovl_s8(vld1_s8(a + i));
203
+ int16x8_t b_vec = vmovl_s8(vld1_s8(b + i));
204
+ int16x8_t ab_part_vec = vmulq_s16(a_vec, b_vec);
205
+ int16x8_t a2_part_vec = vmulq_s16(a_vec, a_vec);
206
+ int16x8_t b2_part_vec = vmulq_s16(b_vec, b_vec);
207
+ ab_vec =
208
+ vaddq_s32(ab_vec, vaddq_s32(vmovl_s16(vget_high_s16(ab_part_vec)), vmovl_s16(vget_low_s16(ab_part_vec))));
209
+ a2_vec =
210
+ vaddq_s32(a2_vec, vaddq_s32(vmovl_s16(vget_high_s16(a2_part_vec)), vmovl_s16(vget_low_s16(a2_part_vec))));
211
+ b2_vec =
212
+ vaddq_s32(b2_vec, vaddq_s32(vmovl_s16(vget_high_s16(b2_part_vec)), vmovl_s16(vget_low_s16(b2_part_vec))));
213
+ }
214
+ #endif
201
215
 
202
216
  int32x2_t ab_part = vadd_s32(vget_high_s32(ab_vec), vget_low_s32(ab_vec));
203
217
  int32_t ab = vget_lane_s32(vpadd_s32(ab_part, ab_part), 0);