usearch 2.8.16 → 2.9.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.
- package/.github/ISSUE_TEMPLATE/bug_report.yml +1 -1
- package/.github/workflows/prerelease.yml +33 -7
- package/.github/workflows/release.yml +25 -26
- package/.gitmodules +4 -1
- package/CITATION.cff +1 -1
- package/CMakeLists.txt +6 -8
- package/CONTRIBUTING.md +46 -1
- package/Cargo.toml +1 -1
- package/README.md +7 -6
- package/VERSION +1 -1
- package/c/lib.cpp +19 -17
- package/c/test.c +41 -7
- package/c/usearch.h +15 -15
- package/conanfile.py +1 -1
- package/cpp/test.cpp +9 -2
- package/include/usearch/index.hpp +15 -15
- package/include/usearch/index_dense.hpp +8 -4
- package/include/usearch/index_plugins.hpp +34 -23
- package/javascript/usearch.js +1 -1
- package/package.json +3 -2
- package/pyproject.toml +2 -2
- package/setup.py +10 -4
- package/simsimd/.releaserc +3 -2
- package/simsimd/Cargo.lock +708 -0
- package/simsimd/Cargo.toml +36 -0
- package/simsimd/README.md +37 -1
- package/simsimd/build.rs +20 -0
- package/simsimd/include/simsimd/simsimd.h +4 -4
- package/simsimd/include/simsimd/spatial.h +1 -1
- package/simsimd/include/simsimd/types.h +5 -1
- package/simsimd/package.json +1 -1
- package/simsimd/rust/benches/cosine.rs +27 -0
- package/simsimd/rust/benches/native.rs +27 -0
- package/simsimd/rust/benches/sqeuclidean.rs +27 -0
- package/simsimd/rust/lib.c +68 -0
- package/simsimd/rust/lib.rs +232 -0
- package/sqlite/README.md +178 -0
- package/sqlite/lib.cpp +325 -0
- package/stringzilla/CONTRIBUTING.md +429 -0
- package/stringzilla/Cargo.lock +25 -0
- package/stringzilla/Cargo.toml +26 -0
- package/stringzilla/LICENSE +201 -0
- package/stringzilla/README.md +1281 -0
- package/stringzilla/VERSION +1 -0
- package/stringzilla/assets/cover-strinzilla.jpeg +0 -0
- package/stringzilla/assets/meme-stringzilla-v2.jpeg +0 -0
- package/stringzilla/assets/meme-stringzilla-v3.jpeg +0 -0
- package/stringzilla/binding.gyp +16 -0
- package/stringzilla/build.rs +14 -0
- package/stringzilla/c/lib.c +304 -0
- package/stringzilla/cli/README.md +48 -0
- package/stringzilla/cli/split.py +72 -0
- package/stringzilla/cli/wc.py +37 -0
- package/stringzilla/include/stringzilla/experimental.h +724 -0
- package/stringzilla/include/stringzilla/module.modulemap +9 -0
- package/stringzilla/include/stringzilla/stringzilla.h +5240 -0
- package/stringzilla/include/stringzilla/stringzilla.hpp +3830 -0
- package/stringzilla/javascript/lib.c +125 -0
- package/stringzilla/javascript/stringzilla.js +22 -0
- package/stringzilla/package-ci.json +10 -0
- package/stringzilla/package.json +32 -0
- package/stringzilla/rust/lib.rs +362 -0
- package/stringzilla/swift/StringProtocol+StringZilla.swift +280 -0
- package/stringzilla/swift/Test.swift +60 -0
- package/wasmer.toml +1 -1
- package/simsimd/bench.js +0 -51
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "simsimd"
|
|
3
|
+
description = "Fastest SIMD-Accelerated Vector Similarity Functions for x86 and Arm"
|
|
4
|
+
version = "3.8.0"
|
|
5
|
+
edition = "2021"
|
|
6
|
+
license = "Apache-2.0"
|
|
7
|
+
authors = [
|
|
8
|
+
"Ash Vardanian <1983160+ashvardanian@users.noreply.github.com>",
|
|
9
|
+
"Pedro Planel <pplanel@gmail.com>",
|
|
10
|
+
]
|
|
11
|
+
repository = "https://github.com/ashvardanian/SimSIMD"
|
|
12
|
+
documentation = "https://docs.rs/simsimd"
|
|
13
|
+
homepage = "https://ashvardanian.com/posts/simsimd-faster-scipy"
|
|
14
|
+
keywords = ["simd", "search", "linear-algebra", "vector"]
|
|
15
|
+
categories = ["mathematics", "hardware-support", "no-std", "wasm", "external-ffi-bindings"]
|
|
16
|
+
|
|
17
|
+
[lib]
|
|
18
|
+
name = "simsimd"
|
|
19
|
+
path = "rust/lib.rs"
|
|
20
|
+
|
|
21
|
+
[build-dependencies]
|
|
22
|
+
cc = "1.0.83"
|
|
23
|
+
|
|
24
|
+
[[bench]]
|
|
25
|
+
name = "cosine"
|
|
26
|
+
harness = false
|
|
27
|
+
path = "rust/benches/cosine.rs"
|
|
28
|
+
|
|
29
|
+
[[bench]]
|
|
30
|
+
name = "sqeuclidean"
|
|
31
|
+
harness = false
|
|
32
|
+
path = "rust/benches/sqeuclidean.rs"
|
|
33
|
+
|
|
34
|
+
[dev-dependencies]
|
|
35
|
+
criterion = { version = "0.5.1" }
|
|
36
|
+
rand = { version = "0.8.5" }
|
package/simsimd/README.md
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
<div>
|
|
4
4
|
<a href="https://pepy.tech/project/simsimd"> <img alt="PyPI" src="https://static.pepy.tech/personalized-badge/simsimd?period=total&units=abbreviation&left_color=black&right_color=blue&left_text=SimSIMD%20Python%20installs"> </a>
|
|
5
5
|
<a href="https://www.npmjs.com/package/simsimd"> <img alt="npm" src="https://img.shields.io/npm/dy/simsimd?label=npm%20dowloads"> </a>
|
|
6
|
+
<a href="https://crates.io/crates/simsimd"> <img alt="rust" src="https://img.shields.io/crates/d/simsimd?logo=rust" /> </a>
|
|
6
7
|
<img alt="GitHub code size in bytes" src="https://img.shields.io/github/languages/code-size/ashvardanian/simsimd">
|
|
7
8
|
</div>
|
|
8
9
|
|
|
9
10
|
## Hardware-Accelerated Similarity Metrics and Distance Functions
|
|
10
11
|
|
|
11
|
-
- Zero-dependency [header-only C 99](#using-simsimd-in-c) library
|
|
12
|
+
- Zero-dependency [header-only C 99](#using-simsimd-in-c) library.
|
|
13
|
+
- Bindings for [Python](#using-simsimd-in-python), [Rust](#using-simsimd-in-rust) and [JavaScript](#using-simsimd-in-javascript).
|
|
12
14
|
- Targets ARM NEON, SVE, x86 AVX2, AVX-512 (VNNI, FP16) hardware backends.
|
|
13
15
|
- Zero-copy compatible with NumPy, PyTorch, TensorFlow, and other tensors.
|
|
14
16
|
- Handles `f64` double-, `f32` single-, and `f16` half-precision, `i8` integral, and binary vectors.
|
|
@@ -139,6 +141,32 @@ metric = CompiledMetric(
|
|
|
139
141
|
index = Index(256, metric=metric)
|
|
140
142
|
```
|
|
141
143
|
|
|
144
|
+
## Using SimSIMD in Rust
|
|
145
|
+
|
|
146
|
+
To install, add the following to your `Cargo.toml`:
|
|
147
|
+
|
|
148
|
+
```toml
|
|
149
|
+
[dependencies]
|
|
150
|
+
simsimd = "..."
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
To use it:
|
|
154
|
+
|
|
155
|
+
```rust
|
|
156
|
+
use simsimd::{cosine, sqeuclidean};
|
|
157
|
+
|
|
158
|
+
fn main() {
|
|
159
|
+
let vector_a = vec![1.0, 2.0, 3.0];
|
|
160
|
+
let vector_b = vec![4.0, 5.0, 6.0];
|
|
161
|
+
|
|
162
|
+
let distance = cosine(&vector_a, &vector_b);
|
|
163
|
+
println!("Cosine Distance: {}", distance);
|
|
164
|
+
|
|
165
|
+
let distance = sqeuclidean(&vector_a, &vector_b);
|
|
166
|
+
println!("Squared Euclidean Distance: {}", distance);
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
142
170
|
## Using SimSIMD in JavaScript
|
|
143
171
|
|
|
144
172
|
To install, choose one of the following options depending on your environment:
|
|
@@ -237,3 +265,11 @@ cd golang
|
|
|
237
265
|
go test # To test
|
|
238
266
|
go test -run=^$ -bench=. -benchmem # To benchmark
|
|
239
267
|
```
|
|
268
|
+
|
|
269
|
+
__To test and benchmark Rust bindings__:
|
|
270
|
+
|
|
271
|
+
```sh
|
|
272
|
+
cargo test
|
|
273
|
+
cargo bench
|
|
274
|
+
open ./target/criterion/report/index.html
|
|
275
|
+
```
|
package/simsimd/build.rs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
fn main() {
|
|
2
|
+
cc::Build::new()
|
|
3
|
+
.file("rust/lib.c")
|
|
4
|
+
.include("include")
|
|
5
|
+
.flag("-O3")
|
|
6
|
+
.flag("-DSIMSIMD_NATIVE_F16=0")
|
|
7
|
+
.flag("-std=c99") // Enforce C99 standard
|
|
8
|
+
.flag("-pedantic") // Ensure strict compliance with the C standard
|
|
9
|
+
.warnings(false)
|
|
10
|
+
.compile("simsimd");
|
|
11
|
+
|
|
12
|
+
println!("cargo:rerun-if-changed=rust/lib.c");
|
|
13
|
+
println!("cargo:rerun-if-changed=rust/lib.rs");
|
|
14
|
+
println!("cargo:rerun-if-changed=include/simsimd/simsimd.h");
|
|
15
|
+
|
|
16
|
+
println!("cargo:rerun-if-changed=include/simsimd/binary.h");
|
|
17
|
+
println!("cargo:rerun-if-changed=include/simsimd/probability.h");
|
|
18
|
+
println!("cargo:rerun-if-changed=include/simsimd/spatial.h");
|
|
19
|
+
println!("cargo:rerun-if-changed=include/simsimd/types.h");
|
|
20
|
+
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
#define SIMSIMD_H
|
|
16
16
|
|
|
17
17
|
#define SIMSIMD_VERSION_MAJOR 3
|
|
18
|
-
#define SIMSIMD_VERSION_MINOR
|
|
18
|
+
#define SIMSIMD_VERSION_MINOR 8
|
|
19
19
|
#define SIMSIMD_VERSION_PATCH 0
|
|
20
20
|
|
|
21
21
|
#include "binary.h" // Hamming, Jaccard
|
|
@@ -69,7 +69,7 @@ typedef enum {
|
|
|
69
69
|
*/
|
|
70
70
|
typedef enum {
|
|
71
71
|
simsimd_cap_serial_k = 1, ///< Serial (non-SIMD) capability
|
|
72
|
-
simsimd_cap_any_k =
|
|
72
|
+
simsimd_cap_any_k = 0x7FFFFFFF, ///< Mask representing any capability with `INT_MAX`
|
|
73
73
|
|
|
74
74
|
simsimd_cap_arm_neon_k = 1 << 10, ///< ARM NEON capability
|
|
75
75
|
simsimd_cap_arm_sve_k = 1 << 11, ///< ARM SVE capability
|
|
@@ -112,11 +112,11 @@ typedef simsimd_f32_t (*simsimd_metric_punned_t)(void const* a, void const* b, s
|
|
|
112
112
|
* @brief Function to determine the SIMD capabilities of the current machine at @b runtime.
|
|
113
113
|
* @return A bitmask of the SIMD capabilities represented as a `simsimd_capability_t` enum value.
|
|
114
114
|
*/
|
|
115
|
-
inline static simsimd_capability_t simsimd_capabilities() {
|
|
115
|
+
inline static simsimd_capability_t simsimd_capabilities(void) {
|
|
116
116
|
|
|
117
117
|
#if SIMSIMD_TARGET_X86
|
|
118
118
|
|
|
119
|
-
/// The states of 4 registers populated for a specific "cpuid"
|
|
119
|
+
/// The states of 4 registers populated for a specific "cpuid" assembly call
|
|
120
120
|
union four_registers_t {
|
|
121
121
|
int array[4];
|
|
122
122
|
struct separate_t {
|
|
@@ -102,7 +102,7 @@ typedef signed char simsimd_i8_t;
|
|
|
102
102
|
typedef unsigned char simsimd_b8_t;
|
|
103
103
|
typedef unsigned long long simsimd_size_t;
|
|
104
104
|
|
|
105
|
-
#if !defined(SIMSIMD_NATIVE_F16)
|
|
105
|
+
#if !defined(SIMSIMD_NATIVE_F16) || SIMSIMD_NATIVE_F16
|
|
106
106
|
/**
|
|
107
107
|
* @brief Half-precision floating-point type.
|
|
108
108
|
*
|
|
@@ -112,12 +112,16 @@ typedef unsigned long long simsimd_size_t;
|
|
|
112
112
|
*/
|
|
113
113
|
#if (defined(__GNUC__) || defined(__clang__)) && (defined(__ARM_ARCH) || defined(__aarch64__)) && \
|
|
114
114
|
(defined(__ARM_FP16_FORMAT_IEEE))
|
|
115
|
+
#if !defined(SIMSIMD_NATIVE_F16)
|
|
115
116
|
#define SIMSIMD_NATIVE_F16 1
|
|
117
|
+
#endif
|
|
116
118
|
typedef __fp16 simsimd_f16_t;
|
|
117
119
|
#elif ((defined(__GNUC__) || defined(__clang__)) && (defined(__x86_64__) || defined(__i386__)) && \
|
|
118
120
|
(defined(__SSE2__) || defined(__AVX512F__)))
|
|
119
121
|
typedef _Float16 simsimd_f16_t;
|
|
122
|
+
#if !defined(SIMSIMD_NATIVE_F16)
|
|
120
123
|
#define SIMSIMD_NATIVE_F16 1
|
|
124
|
+
#endif
|
|
121
125
|
#else // Unknown compiler or architecture
|
|
122
126
|
#define SIMSIMD_NATIVE_F16 0
|
|
123
127
|
#endif // Unknown compiler or architecture
|
package/simsimd/package.json
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
|
2
|
+
use simsimd::SimSIMD;
|
|
3
|
+
|
|
4
|
+
mod native;
|
|
5
|
+
|
|
6
|
+
const DIMENSIONS: usize = 1536;
|
|
7
|
+
|
|
8
|
+
pub fn cosine_benchmark(c: &mut Criterion) {
|
|
9
|
+
let inputs: (Vec<f32>, Vec<f32>) = (
|
|
10
|
+
native::generate_random_vector(DIMENSIONS),
|
|
11
|
+
native::generate_random_vector(DIMENSIONS),
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
let mut group = c.benchmark_group("SIMD Cosine");
|
|
15
|
+
|
|
16
|
+
for i in 0..=5 {
|
|
17
|
+
group.bench_with_input(BenchmarkId::new("SimSIMD", i), &i, |b, _| {
|
|
18
|
+
b.iter(|| SimSIMD::cosine(&inputs.0, &inputs.1))
|
|
19
|
+
});
|
|
20
|
+
group.bench_with_input(BenchmarkId::new("Rust Native", i), &i, |b, _| {
|
|
21
|
+
b.iter(|| native::cosine_similarity_cpu(&inputs.0, &inputs.1))
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
criterion_group!(benches, cosine_benchmark);
|
|
27
|
+
criterion_main!(benches);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#![allow(unused)]
|
|
2
|
+
use rand::Rng;
|
|
3
|
+
|
|
4
|
+
pub(crate) fn generate_random_vector(dim: usize) -> Vec<f32> {
|
|
5
|
+
(0..dim).map(|_| rand::thread_rng().gen()).collect()
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
pub(crate) fn cosine_similarity_cpu(a: &[f32], b: &[f32]) -> Option<f32> {
|
|
9
|
+
if a.len() != b.len() {
|
|
10
|
+
return None;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let dot_product: f32 = a.iter().zip(b.iter()).map(|(a, b)| a * b).sum();
|
|
14
|
+
let norm_a: f32 = a.iter().map(|a| a.powf(2.0)).sum();
|
|
15
|
+
let norm_b: f32 = b.iter().map(|b| b.powf(2.0)).sum();
|
|
16
|
+
|
|
17
|
+
Some(1.0 - (dot_product / (norm_a.sqrt() * norm_b.sqrt())))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
pub(crate) fn squared_euclidean_cpu(a: &[f32], b: &[f32]) -> f32 {
|
|
21
|
+
assert_eq!(a.len(), b.len());
|
|
22
|
+
|
|
23
|
+
a.iter()
|
|
24
|
+
.zip(b.iter())
|
|
25
|
+
.map(|(x, y)| ((*x) - (*y)) * ((*x) - (*y)))
|
|
26
|
+
.fold(0.0, ::std::ops::Add::add)
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
|
2
|
+
use simsimd::SimSIMD;
|
|
3
|
+
|
|
4
|
+
mod native;
|
|
5
|
+
|
|
6
|
+
const DIMENSIONS: usize = 1536;
|
|
7
|
+
|
|
8
|
+
pub fn sqeuclidean_benchmark(c: &mut Criterion) {
|
|
9
|
+
let inputs: (Vec<f32>, Vec<f32>) = (
|
|
10
|
+
native::generate_random_vector(DIMENSIONS),
|
|
11
|
+
native::generate_random_vector(DIMENSIONS),
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
let mut group = c.benchmark_group("SIMD SqEuclidean");
|
|
15
|
+
|
|
16
|
+
for i in 0..=5 {
|
|
17
|
+
group.bench_with_input(BenchmarkId::new("SimSIMD", i), &i, |b, _| {
|
|
18
|
+
b.iter(|| SimSIMD::sqeuclidean(&inputs.0, &inputs.1))
|
|
19
|
+
});
|
|
20
|
+
group.bench_with_input(BenchmarkId::new("Rust Native", i), &i, |b, _| {
|
|
21
|
+
b.iter(|| native::squared_euclidean_cpu(&inputs.0, &inputs.1))
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
criterion_group!(benches, sqeuclidean_benchmark);
|
|
27
|
+
criterion_main!(benches);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#include <simsimd/simsimd.h>
|
|
2
|
+
|
|
3
|
+
simsimd_capability_t cached_capabilities(void) {
|
|
4
|
+
static simsimd_capability_t static_capabilities = 0;
|
|
5
|
+
if (static_capabilities == 0)
|
|
6
|
+
static_capabilities = simsimd_capabilities();
|
|
7
|
+
return static_capabilities;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
simsimd_f32_t cosine_i8(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t d) {
|
|
11
|
+
static simsimd_metric_punned_t metric = 0;
|
|
12
|
+
if (metric == 0) {
|
|
13
|
+
simsimd_capability_t used_capability;
|
|
14
|
+
simsimd_find_metric_punned(simsimd_metric_cosine_k, simsimd_datatype_i8_k, cached_capabilities(),
|
|
15
|
+
simsimd_cap_any_k, &metric, &used_capability);
|
|
16
|
+
}
|
|
17
|
+
return metric(a, b, d, d);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
simsimd_f32_t cosine_f32(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t d) {
|
|
21
|
+
static simsimd_metric_punned_t metric = 0;
|
|
22
|
+
if (metric == 0) {
|
|
23
|
+
simsimd_capability_t used_capability;
|
|
24
|
+
simsimd_find_metric_punned(simsimd_metric_cosine_k, simsimd_datatype_f32_k, cached_capabilities(),
|
|
25
|
+
simsimd_cap_any_k, &metric, &used_capability);
|
|
26
|
+
}
|
|
27
|
+
return metric(a, b, d, d);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
simsimd_f32_t inner_i8(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t d) {
|
|
31
|
+
static simsimd_metric_punned_t metric = 0;
|
|
32
|
+
if (metric == 0) {
|
|
33
|
+
simsimd_capability_t used_capability;
|
|
34
|
+
simsimd_find_metric_punned(simsimd_metric_inner_k, simsimd_datatype_i8_k, cached_capabilities(),
|
|
35
|
+
simsimd_cap_any_k, &metric, &used_capability);
|
|
36
|
+
}
|
|
37
|
+
return metric(a, b, d, d);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
simsimd_f32_t inner_f32(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t d) {
|
|
41
|
+
static simsimd_metric_punned_t metric = 0;
|
|
42
|
+
if (metric == 0) {
|
|
43
|
+
simsimd_capability_t used_capability;
|
|
44
|
+
simsimd_find_metric_punned(simsimd_metric_inner_k, simsimd_datatype_f32_k, cached_capabilities(),
|
|
45
|
+
simsimd_cap_any_k, &metric, &used_capability);
|
|
46
|
+
}
|
|
47
|
+
return metric(a, b, d, d);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
simsimd_f32_t sqeuclidean_i8(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t d) {
|
|
51
|
+
static simsimd_metric_punned_t metric = 0;
|
|
52
|
+
if (metric == 0) {
|
|
53
|
+
simsimd_capability_t used_capability;
|
|
54
|
+
simsimd_find_metric_punned(simsimd_metric_sqeuclidean_k, simsimd_datatype_i8_k, cached_capabilities(),
|
|
55
|
+
simsimd_cap_any_k, &metric, &used_capability);
|
|
56
|
+
}
|
|
57
|
+
return metric(a, b, d, d);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
simsimd_f32_t sqeuclidean_f32(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t d) {
|
|
61
|
+
static simsimd_metric_punned_t metric = 0;
|
|
62
|
+
if (metric == 0) {
|
|
63
|
+
simsimd_capability_t used_capability;
|
|
64
|
+
simsimd_find_metric_punned(simsimd_metric_sqeuclidean_k, simsimd_datatype_f32_k, cached_capabilities(),
|
|
65
|
+
simsimd_cap_any_k, &metric, &used_capability);
|
|
66
|
+
}
|
|
67
|
+
return metric(a, b, d, d);
|
|
68
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
//! # SimSIMD - Hardware-Accelerated Similarity Metrics and Distance Functions
|
|
2
|
+
//!
|
|
3
|
+
//! * Zero-dependency header-only C 99 library with bindings for Python, JavaScript and Rust.
|
|
4
|
+
//! * Targets ARM NEON, SVE, x86 AVX2, AVX-512 (VNNI, FP16) hardware backends.
|
|
5
|
+
//! * Zero-copy compatible with NumPy, PyTorch, TensorFlow, and other tensors.
|
|
6
|
+
//! * Handles f64 double-, f32 single-, and f16 half-precision, i8 integral, and binary vectors.
|
|
7
|
+
//! * Up to 200x faster than scipy.spatial.distance and numpy.inner.
|
|
8
|
+
//! * Used in USearch and several DBMS products.
|
|
9
|
+
//!
|
|
10
|
+
//! ## Implemented distance functions include:
|
|
11
|
+
//!
|
|
12
|
+
//! * Euclidean (L2), Inner Distance, and Cosine (Angular) spatial distances.
|
|
13
|
+
//! * Hamming (~ Manhattan) and Jaccard (~ Tanimoto) binary distances.
|
|
14
|
+
//! * Kullback-Leibler and Jensen–Shannon divergences for probability distributions.
|
|
15
|
+
//!
|
|
16
|
+
//! The functions in this module are exposed through a trait `SimSIMD`, which is
|
|
17
|
+
//! implemented for types with SIMD support (e.g., i8 and f32). The trait provides
|
|
18
|
+
//! methods for computing cosine similarity, inner product, and squared Euclidean
|
|
19
|
+
//! distance between slices of these types.
|
|
20
|
+
//!
|
|
21
|
+
//! # Example
|
|
22
|
+
//!
|
|
23
|
+
//! ```rust
|
|
24
|
+
//! use simsimd::SimSIMD;
|
|
25
|
+
//!
|
|
26
|
+
//! let a = &[1, 2, 3];
|
|
27
|
+
//! let b = &[4, 5, 6];
|
|
28
|
+
//!
|
|
29
|
+
//! // Compute cosine similarity
|
|
30
|
+
//! let cosine_sim = i8::cosine(a, b);
|
|
31
|
+
//!
|
|
32
|
+
//! // Compute inner product
|
|
33
|
+
//! let inner_product = i8::inner(a, b);
|
|
34
|
+
//!
|
|
35
|
+
//! // Compute squared Euclidean distance
|
|
36
|
+
//! let sqeuclidean_dist = i8::sqeuclidean(a, b);
|
|
37
|
+
//! ```
|
|
38
|
+
//!
|
|
39
|
+
//! # Safety
|
|
40
|
+
//!
|
|
41
|
+
//! The functions declared in the `extern "C"` block are low-level bindings to
|
|
42
|
+
//! SimSIMD implementation. It is crucial to ensure that the input slices have the
|
|
43
|
+
//! same length (`c` parameter) to avoid undefined behavior.
|
|
44
|
+
//!
|
|
45
|
+
//!
|
|
46
|
+
//! # Trait Implementation
|
|
47
|
+
//!
|
|
48
|
+
//! The `SimSIMD` trait is implemented for types with SIMD support and provides
|
|
49
|
+
//! three associated methods:
|
|
50
|
+
//!
|
|
51
|
+
//! - `cosine(a: &[Self], b: &[Self]) -> Option<f32>`: Computes cosine similarity between two slices.
|
|
52
|
+
//! - `inner(a: &[Self], b: &[Self]) -> Option<f32>`: Computes inner product between two slices.
|
|
53
|
+
//! - `sqeuclidean(a: &[Self], b: &[Self]) -> Option<f32>`: Computes squared Euclidean distance between two slices.
|
|
54
|
+
//!
|
|
55
|
+
#![allow(non_camel_case_types)]
|
|
56
|
+
|
|
57
|
+
extern "C" {
|
|
58
|
+
/// Computes cosine similarity for i8 types.
|
|
59
|
+
fn cosine_i8(a: *const i8, b: *const i8, c: usize) -> f32;
|
|
60
|
+
|
|
61
|
+
/// Computes cosine similarity for f32 types.
|
|
62
|
+
fn cosine_f32(a: *const f32, b: *const f32, c: usize) -> f32;
|
|
63
|
+
|
|
64
|
+
/// Computes inner product for i8 types.
|
|
65
|
+
fn inner_i8(a: *const i8, b: *const i8, c: usize) -> f32;
|
|
66
|
+
|
|
67
|
+
/// Computes inner product for f32 types.
|
|
68
|
+
fn inner_f32(a: *const f32, b: *const f32, c: usize) -> f32;
|
|
69
|
+
|
|
70
|
+
/// Computes squared Euclidean distance for i8 types.
|
|
71
|
+
fn sqeuclidean_i8(a: *const i8, b: *const i8, c: usize) -> f32;
|
|
72
|
+
|
|
73
|
+
/// Computes squared Euclidean distance for f32 types.
|
|
74
|
+
fn sqeuclidean_f32(a: *const f32, b: *const f32, c: usize) -> f32;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// A trait for SIMD similarity functions.
|
|
78
|
+
pub trait SimSIMD
|
|
79
|
+
where
|
|
80
|
+
Self: Sized,
|
|
81
|
+
{
|
|
82
|
+
/// Computes cosine similarity between two slices.
|
|
83
|
+
fn cosine(a: &[Self], b: &[Self]) -> Option<f32>;
|
|
84
|
+
|
|
85
|
+
/// Computes inner product between two slices.
|
|
86
|
+
fn inner(a: &[Self], b: &[Self]) -> Option<f32>;
|
|
87
|
+
|
|
88
|
+
/// Computes squared Euclidean distance between two slices.
|
|
89
|
+
fn sqeuclidean(a: &[Self], b: &[Self]) -> Option<f32>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
impl SimSIMD for i8 {
|
|
93
|
+
fn cosine(a: &[Self], b: &[Self]) -> Option<f32> {
|
|
94
|
+
if a.len() != b.len() {
|
|
95
|
+
return None;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let operation = unsafe { cosine_i8(a.as_ptr(), b.as_ptr(), a.len()) };
|
|
99
|
+
|
|
100
|
+
Some(operation)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fn inner(a: &[Self], b: &[Self]) -> Option<f32> {
|
|
104
|
+
if a.len() != b.len() {
|
|
105
|
+
return None;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let operation = unsafe { inner_i8(a.as_ptr(), b.as_ptr(), a.len()) };
|
|
109
|
+
|
|
110
|
+
Some(operation)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fn sqeuclidean(a: &[Self], b: &[Self]) -> Option<f32> {
|
|
114
|
+
if a.len() != b.len() {
|
|
115
|
+
return None;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let operation = unsafe { sqeuclidean_i8(a.as_ptr(), b.as_ptr(), a.len()) };
|
|
119
|
+
|
|
120
|
+
Some(operation)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
impl SimSIMD for f32 {
|
|
125
|
+
fn cosine(a: &[Self], b: &[Self]) -> Option<f32> {
|
|
126
|
+
if a.len() != b.len() {
|
|
127
|
+
return None;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let operation = unsafe { cosine_f32(a.as_ptr(), b.as_ptr(), a.len()) };
|
|
131
|
+
|
|
132
|
+
Some(operation)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
fn inner(a: &[Self], b: &[Self]) -> Option<f32> {
|
|
136
|
+
if a.len() != b.len() {
|
|
137
|
+
return None;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let operation = unsafe { inner_f32(a.as_ptr(), b.as_ptr(), a.len()) };
|
|
141
|
+
|
|
142
|
+
Some(operation)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
fn sqeuclidean(a: &[Self], b: &[Self]) -> Option<f32> {
|
|
146
|
+
if a.len() != b.len() {
|
|
147
|
+
return None;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let operation = unsafe { sqeuclidean_f32(a.as_ptr(), b.as_ptr(), a.len()) };
|
|
151
|
+
|
|
152
|
+
Some(operation)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
#[cfg(test)]
|
|
157
|
+
mod tests {
|
|
158
|
+
use super::*;
|
|
159
|
+
//
|
|
160
|
+
fn assert_almost_equal(left: f32, right: f32, tolerance: f32) {
|
|
161
|
+
let lower = right - tolerance;
|
|
162
|
+
let upper = right + tolerance;
|
|
163
|
+
|
|
164
|
+
assert!(left >= lower && left <= upper);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#[test]
|
|
168
|
+
fn test_cosine_i8() {
|
|
169
|
+
let a = &[3, 97, 127];
|
|
170
|
+
let b = &[3, 97, 127];
|
|
171
|
+
|
|
172
|
+
if let Some(result) = SimSIMD::cosine(a, b) {
|
|
173
|
+
assert_almost_equal(0.00012027938, result, 0.01);
|
|
174
|
+
println!("The result of cosine_i8 is {:.8}", result);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
#[test]
|
|
179
|
+
fn test_cosine_f32() {
|
|
180
|
+
let a = &[1.0, 2.0, 3.0];
|
|
181
|
+
let b = &[1.0, 2.0, 3.0];
|
|
182
|
+
|
|
183
|
+
if let Some(result) = SimSIMD::cosine(a, b) {
|
|
184
|
+
assert_almost_equal(0.004930496, result, 0.01);
|
|
185
|
+
println!("The result of cosine_f32 is {:.8}", result);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
#[test]
|
|
190
|
+
fn test_inner_i8() {
|
|
191
|
+
let a = &[1, 2, 3];
|
|
192
|
+
let b = &[4, 5, 6];
|
|
193
|
+
|
|
194
|
+
if let Some(result) = SimSIMD::inner(a, b) {
|
|
195
|
+
assert_almost_equal(0.029403687, result, 0.01);
|
|
196
|
+
println!("The result of inner_i8 is {:.8}", result);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
#[test]
|
|
201
|
+
fn test_inner_f32() {
|
|
202
|
+
let a = &[1.0, 2.0, 3.0];
|
|
203
|
+
let b = &[4.0, 5.0, 6.0];
|
|
204
|
+
|
|
205
|
+
if let Some(result) = SimSIMD::inner(a, b) {
|
|
206
|
+
assert_almost_equal(-31.0, result, 0.01);
|
|
207
|
+
println!("The result of inner_f32 is {:.8}", result);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
#[test]
|
|
212
|
+
fn test_sqeuclidean_i8() {
|
|
213
|
+
let a = &[1, 2, 3];
|
|
214
|
+
let b = &[4, 5, 6];
|
|
215
|
+
|
|
216
|
+
if let Some(result) = SimSIMD::sqeuclidean(a, b) {
|
|
217
|
+
assert_almost_equal(27.0, result, 0.01);
|
|
218
|
+
println!("The result of sqeuclidean_i8 is {:.8}", result);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
#[test]
|
|
223
|
+
fn test_sqeuclidean_f32() {
|
|
224
|
+
let a = &[1.0, 2.0, 3.0];
|
|
225
|
+
let b = &[4.0, 5.0, 6.0];
|
|
226
|
+
|
|
227
|
+
if let Some(result) = SimSIMD::sqeuclidean(a, b) {
|
|
228
|
+
assert_almost_equal(27.0, result, 0.01);
|
|
229
|
+
println!("The result of sqeuclidean_f32 is {:.8}", result);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|