usearch 0.18.6 → 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.
- package/Cargo.toml +1 -1
- package/README.md +8 -5
- package/conanfile.py +1 -1
- package/docs/compilation.md +4 -2
- package/include/usearch/index_punned_helpers.hpp +5 -1
- package/package.json +1 -1
- package/python/scripts/bench.ipynb +2117 -17
- package/python/scripts/bench.py +7 -0
- package/python/usearch/index.py +7 -6
- package/python/usearch/numba.py +26 -16
- package/simsimd/include/simsimd/simsimd.h +20 -6
package/python/scripts/bench.py
CHANGED
|
@@ -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:
|
package/python/usearch/index.py
CHANGED
|
@@ -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,
|
package/python/usearch/numba.py
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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
|
-
#
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if dtype in
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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);
|