usearch 2.15.3 → 2.16.1

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.
Files changed (48) hide show
  1. package/README.md +1 -1
  2. package/cpp/test.cpp +16 -0
  3. package/include/usearch/index.hpp +21 -5
  4. package/include/usearch/index_dense.hpp +6 -1
  5. package/include/usearch/index_plugins.hpp +345 -8
  6. package/javascript/lib.cpp +17 -5
  7. package/javascript/usearch.test.js +190 -21
  8. package/package.json +1 -1
  9. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  10. package/prebuilds/linux-arm64/usearch.node +0 -0
  11. package/prebuilds/linux-x64/usearch.node +0 -0
  12. package/simsimd/.git-blame-ignore-revs +2 -0
  13. package/simsimd/CONTRIBUTING.md +71 -23
  14. package/simsimd/Cargo.lock +1 -1
  15. package/simsimd/Cargo.toml +4 -4
  16. package/simsimd/MANIFEST.in +2 -1
  17. package/simsimd/Package.swift +15 -3
  18. package/simsimd/README.md +104 -35
  19. package/simsimd/binding.gyp +6 -2
  20. package/simsimd/build.rs +2 -0
  21. package/simsimd/c/lib.c +180 -99
  22. package/simsimd/include/simsimd/binary.h +311 -102
  23. package/simsimd/include/simsimd/curved.h +134 -135
  24. package/simsimd/include/simsimd/dot.h +448 -274
  25. package/simsimd/include/simsimd/elementwise.h +1437 -0
  26. package/simsimd/include/simsimd/geospatial.h +2 -2
  27. package/simsimd/include/simsimd/mesh.h +2 -2
  28. package/simsimd/include/simsimd/probability.h +88 -88
  29. package/simsimd/include/simsimd/simsimd.h +1157 -772
  30. package/simsimd/include/simsimd/sparse.h +768 -147
  31. package/simsimd/include/simsimd/spatial.h +955 -376
  32. package/simsimd/include/simsimd/types.h +145 -71
  33. package/simsimd/javascript/lib.c +1 -2
  34. package/simsimd/package.json +4 -4
  35. package/simsimd/rust/lib.rs +14 -1
  36. package/simsimd/scripts/bench.cxx +1070 -0
  37. package/simsimd/scripts/bench.ipynb +187 -0
  38. package/simsimd/{javascript → scripts}/bench.js +1 -1
  39. package/simsimd/{rust/benches/sqeuclidean.rs → scripts/bench_sqeuclidean.rs} +6 -6
  40. package/simsimd/scripts/bench_vectors.py +700 -0
  41. package/simsimd/scripts/bench_vectors_live.py +213 -0
  42. package/simsimd/scripts/test.c +155 -0
  43. package/simsimd/{javascript → scripts}/test.mjs +2 -2
  44. package/simsimd/scripts/test.py +1410 -0
  45. package/simsimd/swift/{source/SimSIMD.swift → SimSIMD.swift} +6 -2
  46. package/simsimd/swift/{tests/SimSIMDTests.swift → Test.swift} +1 -1
  47. /package/simsimd/{rust/benches/cosine.rs → scripts/bench_cosine.rs} +0 -0
  48. /package/simsimd/{rust/benches/native.rs → scripts/bench_native.rs} +0 -0
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Module: bench_vectors_live.py
5
+
6
+ This script visualizes the performance difference between SimSIMD and default
7
+ numerics libraries like NumPy and SimSIMD for the most common kernels.
8
+
9
+ One plot contains information about one distance function, but many data types
10
+ and libraries. For each benchmark, the baseline is the NumPy/SciPy distance function
11
+ applied to `float64` input data. The "x" axis represents the growth in the number of
12
+ dimensions of the input vectors, while the "y" axis represents the speedup factor
13
+ of every kernel against the baseline.
14
+ """
15
+ import os
16
+ import argparse
17
+ from typing import List
18
+
19
+ import numpy as np
20
+
21
+ import perfplot
22
+
23
+ from bench_vectors import (
24
+ metric_families,
25
+ dtype_names,
26
+ Kernel,
27
+ yield_kernels,
28
+ random_matrix,
29
+ )
30
+
31
+
32
+ def ndim_argument(value):
33
+ if value == "default":
34
+ return [2**k for k in range(16)]
35
+ try:
36
+ # Split the input string by commas and convert each part to an integer
37
+ return [int(x) for x in value.split(",")]
38
+ except ValueError:
39
+ raise argparse.ArgumentTypeError("Value must be 'default' or a comma-separated list of integers")
40
+
41
+
42
+ def main():
43
+ # Argument parsing
44
+ parser = argparse.ArgumentParser(description="Visualize Performance Difference between SimSIMD and other libraries")
45
+ parser.add_argument(
46
+ "--ndim-min",
47
+ type=int,
48
+ default=2,
49
+ help="",
50
+ )
51
+ parser.add_argument(
52
+ "--ndim-max",
53
+ type=int,
54
+ default=1024 * 1024,
55
+ help="",
56
+ )
57
+ parser.add_argument(
58
+ "-o",
59
+ "--output-path",
60
+ type=str,
61
+ default=None,
62
+ help="File to save the plot to (default: None, plot is shown live)",
63
+ )
64
+ parser.add_argument(
65
+ "-n",
66
+ "--count",
67
+ type=int,
68
+ default=1,
69
+ help="""Number of vectors per batch (default: 1)
70
+
71
+ By default, when set to 1 the benchmark will generate many vectors of size (ndim, )
72
+ and call the functions on pairs of single vectors: both directly, and through `cdist`.
73
+ Alternatively, for larger batch sizes the benchmark will generate two matrices of
74
+ size (n, ndim) and compute:
75
+
76
+ - batch mode: (n) distances between vectors in identical rows of the two matrices,
77
+ - all-pairs mode: (n^2) distances between all pairs of vectors in the two matrices via `cdist`.
78
+ """,
79
+ )
80
+ parser.add_argument(
81
+ "--mode",
82
+ choices=["batch", "all-pairs"],
83
+ default="batch",
84
+ help="""Choose between 'batch' and 'all-pairs' mode (default: batch)
85
+
86
+ In 'batch' mode, the latency will generate two matrices of size (n, ndim)
87
+ and compute (n) distances between vectors in identical rows of the two matrices.
88
+ In 'all-pairs' mode, the latency will generate two matrices of size (n, ndim)
89
+ and compute (n^2) distances between all pairs of vectors in the two matrices via `cdist`.
90
+ """,
91
+ )
92
+ parser.add_argument(
93
+ "--metric",
94
+ choices=metric_families,
95
+ default="dot",
96
+ help=f"Distance metric to use, profiles `dot` by default",
97
+ )
98
+ parser.add_argument(
99
+ "--dtype",
100
+ choices=dtype_names,
101
+ default="float64",
102
+ help=f"Defines numeric types to latency, profiles `float64` by default",
103
+ )
104
+ parser.add_argument("--scipy", action="store_true", help="Profile SciPy, must be installed")
105
+ parser.add_argument("--scikit", action="store_true", help="Profile scikit-learn, must be installed")
106
+ parser.add_argument("--torch", action="store_true", help="Profile PyTorch, must be installed")
107
+ parser.add_argument("--tf", action="store_true", help="Profile TensorFlow, must be installed")
108
+ parser.add_argument("--jax", action="store_true", help="Profile JAX, must be installed")
109
+
110
+ args = parser.parse_args()
111
+ assert args.count > 0, "Number of vectors per batch must be greater than 0"
112
+ assert args.ndim_min > 0, "Number of dimensions must be greater than 0"
113
+ assert args.ndim_max > 0, "Number of dimensions must be greater than 0"
114
+
115
+ ndim_range = [args.ndim_min]
116
+ while ndim_range[-1] <= args.ndim_max:
117
+ ndim_range.append(ndim_range[-1] * 2)
118
+
119
+ kernels: List[Kernel] = list(
120
+ yield_kernels(
121
+ [args.metric],
122
+ dtype_names,
123
+ include_scipy=args.scipy,
124
+ include_scikit=args.scikit,
125
+ include_torch=args.torch,
126
+ include_tf=args.tf,
127
+ include_jax=args.jax,
128
+ )
129
+ )
130
+ if len(kernels) == 0:
131
+ raise RuntimeError("No kernels found!")
132
+
133
+ def precomputed_flops(ndim: int) -> int:
134
+ if args.mode == "all-pairs":
135
+ return ndim * (args.count**2)
136
+ else:
137
+ return ndim * (args.count)
138
+
139
+ def generate_matrix(ndim: int) -> np.ndarray:
140
+ if args.count == 1:
141
+ return random_matrix(1, ndim, dtype=args.dtype).flatten()
142
+ else:
143
+ return random_matrix(args.count, ndim, dtype=args.dtype)
144
+
145
+ def wrap_binary_function(function):
146
+ def wrapped(A):
147
+ return function(A, A)
148
+
149
+ return wrapped
150
+
151
+ kernel_labels: List[str] = []
152
+ kernel_callables: List[callable] = []
153
+
154
+ # Add SimSIMD kernels
155
+ for kernel in kernels:
156
+ if not kernel.name.startswith("numpy.") and not kernel.name.startswith("scipy."):
157
+ continue
158
+ _, _, function_name = kernel.name.partition(".")
159
+ kernel_labels.append(f"simsimd.{function_name}<{kernel.dtype}>")
160
+ if args.mode == "all-pairs":
161
+ kernel_callables.append(wrap_binary_function(kernel.simsimd_all_pairs_func))
162
+ else:
163
+ kernel_callables.append(wrap_binary_function(kernel.simsimd_func))
164
+
165
+ # Add other kernels
166
+ for kernel in kernels:
167
+ kernel_labels.append(f"{kernel.name}<{kernel.dtype}>")
168
+ if args.mode == "all-pairs":
169
+ kernel_callables.append(wrap_binary_function(kernel.baseline_all_pairs_func))
170
+ elif args.count == 1:
171
+ kernel_callables.append(wrap_binary_function(kernel.baseline_one_to_one_func))
172
+ else:
173
+ kernel_callables.append(wrap_binary_function(kernel.baseline_many_to_many_func))
174
+
175
+ # Filter-out kernels that raise any exceptions
176
+ safe_callables = []
177
+ safe_labels = []
178
+ for kernel_label, kernel_callable in zip(kernel_labels, kernel_callables):
179
+ try:
180
+ kernel_callable(generate_matrix(ndim_range[0]))
181
+ safe_callables.append(kernel_callable)
182
+ safe_labels.append(kernel_label)
183
+ except:
184
+ print(f"Skipping {kernel_label}")
185
+
186
+ print(safe_callables)
187
+ print(safe_labels)
188
+
189
+ # Settings are mostly the same for live charts and exported ones
190
+ profiler_settings = dict(
191
+ setup=generate_matrix,
192
+ kernels=safe_callables,
193
+ labels=safe_labels,
194
+ n_range=ndim_range,
195
+ flops=precomputed_flops,
196
+ xlabel="ndim",
197
+ equality_check=None, # bypass correctness check, we have tests for that
198
+ )
199
+
200
+ # Plot the results
201
+ if args.output_path is not None:
202
+ plot_fp = os.path.abspath(args.output_path)
203
+ profiler_settings["filename"] = plot_fp
204
+ profiler_settings["show_progress"] = False
205
+ results = perfplot.bench(**profiler_settings)
206
+ results.save(plot_fp, transparent=False, bbox_inches="tight", relative_to=0, logy="auto")
207
+ else:
208
+ profiler_settings.pop("flops")
209
+ perfplot.live(**profiler_settings)
210
+
211
+
212
+ if __name__ == "__main__":
213
+ main()
@@ -0,0 +1,155 @@
1
+ /**
2
+ * @file test.c
3
+ * @brief Test focusing only on the simplest functionality.
4
+ */
5
+
6
+ #include <assert.h> // `assert`
7
+ #include <math.h> // `sqrtf`
8
+ #include <stdio.h> // `printf`
9
+
10
+ #define SIMSIMD_NATIVE_F16 0
11
+ #define SIMSIMD_NATIVE_BF16 0
12
+ #include <simsimd/simsimd.h>
13
+
14
+ /**
15
+ * @brief Logs CPU capabilities supported by the current build (compile-time) and runtime.
16
+ */
17
+ void print_capabilities(void) {
18
+ simsimd_capability_t runtime_caps = simsimd_capabilities();
19
+
20
+ // Log supported functionality
21
+ char const *flags[2] = {"false", "true"};
22
+ printf("Benchmarking Similarity Measures\n");
23
+ printf("- Compiler used native F16: %s\n", flags[SIMSIMD_NATIVE_F16]);
24
+ printf("- Compiler used native BF16: %s\n", flags[SIMSIMD_NATIVE_BF16]);
25
+ printf("\n");
26
+ printf("Compile-time settings:\n");
27
+ printf("- Arm NEON support enabled: %s\n", flags[SIMSIMD_TARGET_NEON]);
28
+ printf("- Arm SVE support enabled: %s\n", flags[SIMSIMD_TARGET_SVE]);
29
+ printf("- Arm SVE2 support enabled: %s\n", flags[SIMSIMD_TARGET_SVE2]);
30
+ printf("- x86 Haswell support enabled: %s\n", flags[SIMSIMD_TARGET_HASWELL]);
31
+ printf("- x86 Skylake support enabled: %s\n", flags[SIMSIMD_TARGET_SKYLAKE]);
32
+ printf("- x86 Ice Lake support enabled: %s\n", flags[SIMSIMD_TARGET_ICE]);
33
+ printf("- x86 Genoa support enabled: %s\n", flags[SIMSIMD_TARGET_GENOA]);
34
+ printf("- x86 Sapphire Rapids support enabled: %s\n", flags[SIMSIMD_TARGET_SAPPHIRE]);
35
+ printf("- x86 Turin support enabled: %s\n", flags[SIMSIMD_TARGET_TURIN]);
36
+ printf("- x86 Sierra Forest support enabled: %s\n", flags[SIMSIMD_TARGET_SIERRA]);
37
+ printf("\n");
38
+ printf("Run-time settings:\n");
39
+ printf("- Arm NEON support enabled: %s\n", flags[(runtime_caps & simsimd_cap_neon_k) != 0]);
40
+ printf("- Arm NEON F16 support enabled: %s\n", flags[(runtime_caps & simsimd_cap_neon_f16_k) != 0]);
41
+ printf("- Arm NEON BF16 support enabled: %s\n", flags[(runtime_caps & simsimd_cap_neon_bf16_k) != 0]);
42
+ printf("- Arm NEON I8 support enabled: %s\n", flags[(runtime_caps & simsimd_cap_neon_i8_k) != 0]);
43
+ printf("- Arm SVE support enabled: %s\n", flags[(runtime_caps & simsimd_cap_sve_k) != 0]);
44
+ printf("- Arm SVE F16 support enabled: %s\n", flags[(runtime_caps & simsimd_cap_sve_f16_k) != 0]);
45
+ printf("- Arm SVE BF16 support enabled: %s\n", flags[(runtime_caps & simsimd_cap_sve_bf16_k) != 0]);
46
+ printf("- Arm SVE I8 support enabled: %s\n", flags[(runtime_caps & simsimd_cap_sve_i8_k) != 0]);
47
+ printf("- Arm SVE2 support enabled: %s\n", flags[(runtime_caps & simsimd_cap_sve2_k) != 0]);
48
+ printf("- x86 Haswell support enabled: %s\n", flags[(runtime_caps & simsimd_cap_haswell_k) != 0]);
49
+ printf("- x86 Skylake support enabled: %s\n", flags[(runtime_caps & simsimd_cap_skylake_k) != 0]);
50
+ printf("- x86 Ice Lake support enabled: %s\n", flags[(runtime_caps & simsimd_cap_ice_k) != 0]);
51
+ printf("- x86 Genoa support enabled: %s\n", flags[(runtime_caps & simsimd_cap_genoa_k) != 0]);
52
+ printf("- x86 Sapphire Rapids support enabled: %s\n", flags[(runtime_caps & simsimd_cap_sapphire_k) != 0]);
53
+ printf("- x86 Turin support enabled: %s\n", flags[(runtime_caps & simsimd_cap_turin_k) != 0]);
54
+ printf("\n");
55
+ }
56
+
57
+ /**
58
+ * @brief A trivial test that checks if the utility functions return the expected values.
59
+ */
60
+ void test_utilities(void) {
61
+ simsimd_capability_t capabilities = simsimd_capabilities();
62
+
63
+ int uses_neon = simsimd_uses_neon();
64
+ int uses_sve = simsimd_uses_sve();
65
+ int uses_haswell = simsimd_uses_haswell();
66
+ int uses_skylake = simsimd_uses_skylake();
67
+ int uses_ice = simsimd_uses_ice();
68
+ int uses_genoa = simsimd_uses_genoa();
69
+ int uses_sapphire = simsimd_uses_sapphire();
70
+ int uses_turin = simsimd_uses_turin();
71
+ int uses_sierra = simsimd_uses_sierra();
72
+
73
+ assert(uses_neon == ((capabilities & simsimd_cap_neon_k) != 0));
74
+ assert(uses_sve == ((capabilities & simsimd_cap_sve_k) != 0));
75
+ assert(uses_haswell == ((capabilities & simsimd_cap_haswell_k) != 0));
76
+ assert(uses_skylake == ((capabilities & simsimd_cap_skylake_k) != 0));
77
+ assert(uses_ice == ((capabilities & simsimd_cap_ice_k) != 0));
78
+ assert(uses_genoa == ((capabilities & simsimd_cap_genoa_k) != 0));
79
+ assert(uses_sapphire == ((capabilities & simsimd_cap_sapphire_k) != 0));
80
+ assert(uses_turin == ((capabilities & simsimd_cap_turin_k) != 0));
81
+ assert(uses_sierra == ((capabilities & simsimd_cap_sierra_k) != 0));
82
+ }
83
+
84
+ /**
85
+ * @brief A trivial test that calls every implemented distance function and their dispatch versions
86
+ * on vectors A and B, where A and B are equal.
87
+ */
88
+ void test_distance_from_itself(void) {
89
+ simsimd_f64_t f64s[1536];
90
+ simsimd_f32_t f32s[1536];
91
+ simsimd_f16_t f16s[1536];
92
+ simsimd_bf16_t bf16s[1536];
93
+ simsimd_i8_t i8s[1536];
94
+ simsimd_b8_t b8s[1536 / 8]; // 8 bits per word
95
+ simsimd_distance_t distance;
96
+
97
+ // Cosine distance between two vectors
98
+ simsimd_cos_i8(i8s, i8s, 1536, &distance);
99
+ simsimd_cos_f16(f16s, f16s, 1536, &distance);
100
+ simsimd_cos_bf16(bf16s, bf16s, 1536, &distance);
101
+ simsimd_cos_f32(f32s, f32s, 1536, &distance);
102
+ simsimd_cos_f64(f64s, f64s, 1536, &distance);
103
+
104
+ // Euclidean distance between two vectors
105
+ simsimd_l2sq_i8(i8s, i8s, 1536, &distance);
106
+ simsimd_l2sq_f16(f16s, f16s, 1536, &distance);
107
+ simsimd_l2sq_bf16(bf16s, bf16s, 1536, &distance);
108
+ simsimd_l2sq_f32(f32s, f32s, 1536, &distance);
109
+ simsimd_l2sq_f64(f64s, f64s, 1536, &distance);
110
+
111
+ // Inner product between two vectors
112
+ simsimd_dot_f16(f16s, f16s, 1536, &distance);
113
+ simsimd_dot_bf16(bf16s, bf16s, 1536, &distance);
114
+ simsimd_dot_f32(f32s, f32s, 1536, &distance);
115
+ simsimd_dot_f64(f64s, f64s, 1536, &distance);
116
+
117
+ // Complex inner product between two vectors
118
+ simsimd_dot_f16c(f16s, f16s, 1536, &distance);
119
+ simsimd_dot_bf16c(bf16s, bf16s, 1536, &distance);
120
+ // simsimd_dot_bf16c(bf16s, bf16s, 1536, &distance);
121
+ simsimd_dot_f32c(f32s, f32s, 1536, &distance);
122
+ simsimd_dot_f64c(f64s, f64s, 1536, &distance);
123
+
124
+ // Complex conjugate inner product between two vectors
125
+ simsimd_vdot_f16c(f16s, f16s, 1536, &distance);
126
+ simsimd_vdot_bf16c(bf16s, bf16s, 1536, &distance);
127
+ simsimd_vdot_f32c(f32s, f32s, 1536, &distance);
128
+ simsimd_vdot_f64c(f64s, f64s, 1536, &distance);
129
+
130
+ // Hamming distance between two vectors
131
+ simsimd_hamming_b8(b8s, b8s, 1536 / 8, &distance);
132
+
133
+ // Jaccard distance between two vectors
134
+ simsimd_jaccard_b8(b8s, b8s, 1536 / 8, &distance);
135
+
136
+ // Jensen-Shannon divergence between two vectors
137
+ simsimd_js_f16(f16s, f16s, 1536, &distance);
138
+ simsimd_js_bf16(bf16s, bf16s, 1536, &distance);
139
+ simsimd_js_f32(f32s, f32s, 1536, &distance);
140
+ simsimd_js_f64(f64s, f64s, 1536, &distance);
141
+
142
+ // Kullback-Leibler divergence between two vectors
143
+ simsimd_kl_f16(f16s, f16s, 1536, &distance);
144
+ simsimd_kl_bf16(bf16s, bf16s, 1536, &distance);
145
+ simsimd_kl_f32(f32s, f32s, 1536, &distance);
146
+ simsimd_kl_f64(f64s, f64s, 1536, &distance);
147
+ }
148
+
149
+ int main(int argc, char **argv) {
150
+
151
+ print_capabilities();
152
+ test_utilities();
153
+ test_distance_from_itself();
154
+ return 0;
155
+ }
@@ -23,9 +23,9 @@
23
23
  import test from "node:test";
24
24
  import assert from "node:assert";
25
25
 
26
- import * as simsimd from "./dist/esm/simsimd.js";
26
+ import * as simsimd from "../javascript/dist/esm/simsimd.js";
27
27
 
28
- import * as fallback from "./dist/esm/fallback.js";
28
+ import * as fallback from "../javascript/dist/esm/fallback.js";
29
29
 
30
30
  function assertAlmostEqual(actual, expected, tolerance = 1e-6) {
31
31
  const lowerBound = expected - tolerance;