usearch 2.7.7 → 2.8.0
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/.dockerignore +93 -0
- package/.github/workflows/Dockerfile.libs +6 -2
- package/.github/workflows/prerelease.yml +242 -245
- package/.github/workflows/release.yml +390 -356
- package/.gitignore +1 -0
- package/.vscode/settings.json +3 -1
- package/CITATION.cff +1 -1
- package/CMakeLists.txt +35 -5
- package/Cargo.toml +1 -1
- package/README.md +1 -1
- package/build.gradle +6 -0
- package/c/README.md +3 -3
- package/c/lib.cpp +75 -11
- package/c/usearch.h +22 -1
- package/conanfile.py +1 -1
- package/cpp/README.md +2 -1
- package/cpp/bench.cpp +3 -0
- package/cpp/test.cpp +12 -12
- package/csharp/nuget/nuget-package.props +1 -1
- package/docs/compilation.md +5 -13
- package/golang/lib.go +4 -1
- package/include/usearch/index.hpp +31 -30
- package/include/usearch/index_dense.hpp +49 -48
- package/include/usearch/index_plugins.hpp +70 -8
- package/java/cloud/unum/usearch/Index.java +118 -26
- package/java/cloud/unum/usearch/cloud_unum_usearch_Index.cpp +8 -8
- package/javascript/usearch.js +1 -0
- package/objc/USearchObjective.mm +4 -1
- package/objc/include/USearchObjective.h +1 -0
- package/package.json +1 -1
- package/python/lib.cpp +3 -2
- package/python/scripts/bench.ipynb +24 -2
- package/python/usearch/index.py +8 -3
- package/rust/lib.cpp +14 -12
- package/rust/lib.hpp +13 -13
- package/rust/lib.rs +46 -0
- package/setup.py +15 -3
- package/simsimd/.github/workflows/prerelease.yml +7 -8
- package/simsimd/.github/workflows/release.yml +18 -20
- package/simsimd/README.md +13 -8
- package/simsimd/binding.gyp +1 -1
- package/simsimd/cpp/bench.cxx +17 -3
- package/simsimd/include/simsimd/binary.h +1 -3
- package/simsimd/include/simsimd/probability.h +234 -361
- package/simsimd/include/simsimd/simsimd.h +95 -65
- package/simsimd/include/simsimd/spatial.h +0 -3
- package/simsimd/include/simsimd/types.h +12 -7
- package/simsimd/javascript/test.js +23 -5
- package/simsimd/package.json +2 -2
- package/wasmer.toml +1 -1
- package/wolfram/lib.cpp +1 -1
- package/csharp/eng/build_lib.sh +0 -65
- package/csharp/eng/clean.sh +0 -12
package/rust/lib.cpp
CHANGED
|
@@ -8,6 +8,7 @@ using index_t = index_dense_t;
|
|
|
8
8
|
using add_result_t = typename index_t::add_result_t;
|
|
9
9
|
using search_result_t = typename index_t::search_result_t;
|
|
10
10
|
using labeling_result_t = typename index_t::labeling_result_t;
|
|
11
|
+
using vector_key_t = typename index_dense_t::vector_key_t;
|
|
11
12
|
|
|
12
13
|
template <typename scalar_at> Matches search_(index_dense_t& index, scalar_at const* vec, size_t count) {
|
|
13
14
|
Matches matches;
|
|
@@ -26,20 +27,20 @@ template <typename scalar_at> Matches search_(index_dense_t& index, scalar_at co
|
|
|
26
27
|
NativeIndex::NativeIndex(std::unique_ptr<index_t> index) : index_(std::move(index)) {}
|
|
27
28
|
|
|
28
29
|
// clang-format off
|
|
29
|
-
void NativeIndex::add_i8(
|
|
30
|
-
void NativeIndex::add_f16(
|
|
31
|
-
void NativeIndex::add_f32(
|
|
32
|
-
void NativeIndex::add_f64(
|
|
30
|
+
void NativeIndex::add_i8(vector_key_t key, rust::Slice<int8_t const> vec) const { index_->add(key, vec.data()).error.raise(); }
|
|
31
|
+
void NativeIndex::add_f16(vector_key_t key, rust::Slice<uint16_t const> vec) const { index_->add(key, (f16_t const*)vec.data()).error.raise(); }
|
|
32
|
+
void NativeIndex::add_f32(vector_key_t key, rust::Slice<float const> vec) const { index_->add(key, vec.data()).error.raise(); }
|
|
33
|
+
void NativeIndex::add_f64(vector_key_t key, rust::Slice<double const> vec) const { index_->add(key, vec.data()).error.raise(); }
|
|
33
34
|
|
|
34
35
|
Matches NativeIndex::search_i8(rust::Slice<int8_t const> vec, size_t count) const { return search_(*index_, vec.data(), count); }
|
|
35
36
|
Matches NativeIndex::search_f16(rust::Slice<uint16_t const> vec, size_t count) const { return search_(*index_, (f16_t const*)vec.data(), count); }
|
|
36
37
|
Matches NativeIndex::search_f32(rust::Slice<float const> vec, size_t count) const { return search_(*index_, vec.data(), count); }
|
|
37
38
|
Matches NativeIndex::search_f64(rust::Slice<double const> vec, size_t count) const { return search_(*index_, vec.data(), count); }
|
|
38
39
|
|
|
39
|
-
size_t NativeIndex::get_i8(
|
|
40
|
-
size_t NativeIndex::get_f16(
|
|
41
|
-
size_t NativeIndex::get_f32(
|
|
42
|
-
size_t NativeIndex::get_f64(
|
|
40
|
+
size_t NativeIndex::get_i8(vector_key_t key, rust::Slice<int8_t> vec) const { if (vec.size() % dimensions()) throw std::invalid_argument("Vector length must be a multiple of index dimensionality"); return index_->get(key, vec.data(), vec.size() / dimensions()); }
|
|
41
|
+
size_t NativeIndex::get_f16(vector_key_t key, rust::Slice<uint16_t> vec) const { if (vec.size() % dimensions()) throw std::invalid_argument("Vector length must be a multiple of index dimensionality"); return index_->get(key, (f16_t*)vec.data(), vec.size() / dimensions()); }
|
|
42
|
+
size_t NativeIndex::get_f32(vector_key_t key, rust::Slice<float> vec) const { if (vec.size() % dimensions()) throw std::invalid_argument("Vector length must be a multiple of index dimensionality"); return index_->get(key, vec.data(), vec.size() / dimensions()); }
|
|
43
|
+
size_t NativeIndex::get_f64(vector_key_t key, rust::Slice<double> vec) const { if (vec.size() % dimensions()) throw std::invalid_argument("Vector length must be a multiple of index dimensionality"); return index_->get(key, vec.data(), vec.size() / dimensions()); }
|
|
43
44
|
// clang-format on
|
|
44
45
|
|
|
45
46
|
size_t NativeIndex::expansion_add() const { return index_->expansion_add(); }
|
|
@@ -47,20 +48,20 @@ size_t NativeIndex::expansion_search() const { return index_->expansion_search()
|
|
|
47
48
|
void NativeIndex::change_expansion_add(size_t n) const { index_->change_expansion_add(n); }
|
|
48
49
|
void NativeIndex::change_expansion_search(size_t n) const { index_->change_expansion_search(n); }
|
|
49
50
|
|
|
50
|
-
size_t NativeIndex::remove(
|
|
51
|
+
size_t NativeIndex::remove(vector_key_t key) const {
|
|
51
52
|
labeling_result_t result = index_->remove(key);
|
|
52
53
|
result.error.raise();
|
|
53
54
|
return result.completed;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
size_t NativeIndex::rename(
|
|
57
|
+
size_t NativeIndex::rename(vector_key_t from, vector_key_t to) const {
|
|
57
58
|
labeling_result_t result = index_->rename(from, to);
|
|
58
59
|
result.error.raise();
|
|
59
60
|
return result.completed;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
size_t NativeIndex::count(
|
|
63
|
-
bool NativeIndex::contains(
|
|
63
|
+
size_t NativeIndex::count(vector_key_t key) const { return index_->count(key); }
|
|
64
|
+
bool NativeIndex::contains(vector_key_t key) const { return index_->contains(key); }
|
|
64
65
|
|
|
65
66
|
void NativeIndex::reserve(size_t capacity) const { index_->reserve(capacity); }
|
|
66
67
|
|
|
@@ -106,6 +107,7 @@ metric_kind_t rust_to_cpp_metric(MetricKind value) {
|
|
|
106
107
|
case MetricKind::Cos: return metric_kind_t::cos_k;
|
|
107
108
|
case MetricKind::Pearson: return metric_kind_t::pearson_k;
|
|
108
109
|
case MetricKind::Haversine: return metric_kind_t::haversine_k;
|
|
110
|
+
case MetricKind::Divergence: return metric_kind_t::divergence_k;
|
|
109
111
|
case MetricKind::Hamming: return metric_kind_t::hamming_k;
|
|
110
112
|
case MetricKind::Tanimoto: return metric_kind_t::tanimoto_k;
|
|
111
113
|
case MetricKind::Sorensen: return metric_kind_t::sorensen_k;
|
package/rust/lib.hpp
CHANGED
|
@@ -15,36 +15,36 @@ class NativeIndex {
|
|
|
15
15
|
using index_dense_t = unum::usearch::index_dense_t;
|
|
16
16
|
using add_result_t = typename index_dense_t::add_result_t;
|
|
17
17
|
using search_result_t = typename index_dense_t::search_result_t;
|
|
18
|
-
using
|
|
18
|
+
using vector_key_t = typename index_dense_t::vector_key_t;
|
|
19
19
|
|
|
20
20
|
NativeIndex(std::unique_ptr<index_dense_t> index);
|
|
21
21
|
|
|
22
22
|
void reserve(size_t) const;
|
|
23
23
|
|
|
24
|
-
void add_i8(
|
|
25
|
-
void add_f16(
|
|
26
|
-
void add_f32(
|
|
27
|
-
void add_f64(
|
|
24
|
+
void add_i8(vector_key_t key, rust::Slice<int8_t const> vector) const;
|
|
25
|
+
void add_f16(vector_key_t key, rust::Slice<uint16_t const> vector) const;
|
|
26
|
+
void add_f32(vector_key_t key, rust::Slice<float const> vector) const;
|
|
27
|
+
void add_f64(vector_key_t key, rust::Slice<double const> vector) const;
|
|
28
28
|
|
|
29
29
|
Matches search_i8(rust::Slice<int8_t const> vector, size_t count) const;
|
|
30
30
|
Matches search_f16(rust::Slice<uint16_t const> vector, size_t count) const;
|
|
31
31
|
Matches search_f32(rust::Slice<float const> vector, size_t count) const;
|
|
32
32
|
Matches search_f64(rust::Slice<double const> vector, size_t count) const;
|
|
33
33
|
|
|
34
|
-
size_t get_i8(
|
|
35
|
-
size_t get_f16(
|
|
36
|
-
size_t get_f32(
|
|
37
|
-
size_t get_f64(
|
|
34
|
+
size_t get_i8(vector_key_t key, rust::Slice<int8_t> vector) const;
|
|
35
|
+
size_t get_f16(vector_key_t key, rust::Slice<uint16_t> vector) const;
|
|
36
|
+
size_t get_f32(vector_key_t key, rust::Slice<float> vector) const;
|
|
37
|
+
size_t get_f64(vector_key_t key, rust::Slice<double> vector) const;
|
|
38
38
|
|
|
39
39
|
size_t expansion_add() const;
|
|
40
40
|
size_t expansion_search() const;
|
|
41
41
|
void change_expansion_add(size_t n) const;
|
|
42
42
|
void change_expansion_search(size_t n) const;
|
|
43
43
|
|
|
44
|
-
size_t count(
|
|
45
|
-
size_t remove(
|
|
46
|
-
size_t rename(
|
|
47
|
-
bool contains(
|
|
44
|
+
size_t count(vector_key_t key) const;
|
|
45
|
+
size_t remove(vector_key_t key) const;
|
|
46
|
+
size_t rename(vector_key_t from, vector_key_t to) const;
|
|
47
|
+
bool contains(vector_key_t key) const;
|
|
48
48
|
|
|
49
49
|
size_t dimensions() const;
|
|
50
50
|
size_t connectivity() const;
|
package/rust/lib.rs
CHANGED
|
@@ -432,6 +432,7 @@ pub fn new_index(options: &ffi::IndexOptions) -> Result<Index, cxx::Exception> {
|
|
|
432
432
|
mod tests {
|
|
433
433
|
use crate::ffi::IndexOptions;
|
|
434
434
|
use crate::ffi::MetricKind;
|
|
435
|
+
use crate::ffi::ScalarKind;
|
|
435
436
|
|
|
436
437
|
use crate::new_index;
|
|
437
438
|
use crate::Index;
|
|
@@ -466,6 +467,51 @@ mod tests {
|
|
|
466
467
|
assert!(result.is_err());
|
|
467
468
|
}
|
|
468
469
|
|
|
470
|
+
#[test]
|
|
471
|
+
fn test_add_remove_vector() {
|
|
472
|
+
let mut options = IndexOptions::default();
|
|
473
|
+
options.dimensions = 4;
|
|
474
|
+
options.metric = MetricKind::IP;
|
|
475
|
+
options.quantization = ScalarKind::F64;
|
|
476
|
+
options.connectivity = 10;
|
|
477
|
+
options.expansion_add = 128;
|
|
478
|
+
options.expansion_search = 3;
|
|
479
|
+
let index = Index::new(&options).unwrap();
|
|
480
|
+
assert!(index.reserve(10).is_ok());
|
|
481
|
+
assert_eq!(index.capacity(), 10);
|
|
482
|
+
|
|
483
|
+
let first: [f32; 4] = [0.2, 0.1, 0.2, 0.1];
|
|
484
|
+
let second: [f32; 4] = [0.3, 0.2, 0.4, 0.0];
|
|
485
|
+
|
|
486
|
+
// IDs until 18446744073709551615 should be fine:
|
|
487
|
+
let id1 = 483367403120493160;
|
|
488
|
+
let id2 = 483367403120558696;
|
|
489
|
+
let id3 = 483367403120624232;
|
|
490
|
+
let id4 = 483367403120624233;
|
|
491
|
+
|
|
492
|
+
assert!(index.add(id1, &first).is_ok());
|
|
493
|
+
let mut found_slice = [0.0 as f32; 4];
|
|
494
|
+
assert_eq!(index.get(id1, &mut found_slice).unwrap(), 1);
|
|
495
|
+
assert!(index.remove(id1).is_ok());
|
|
496
|
+
|
|
497
|
+
assert!(index.add(id2, &second).is_ok());
|
|
498
|
+
let mut found_slice = [0.0 as f32; 4];
|
|
499
|
+
assert_eq!(index.get(id2, &mut found_slice).unwrap(), 1);
|
|
500
|
+
assert!(index.remove(id2).is_ok());
|
|
501
|
+
|
|
502
|
+
assert!(index.add(id3, &second).is_ok());
|
|
503
|
+
let mut found_slice = [0.0 as f32; 4];
|
|
504
|
+
assert_eq!(index.get(id3, &mut found_slice).unwrap(), 1);
|
|
505
|
+
assert!(index.remove(id3).is_ok());
|
|
506
|
+
|
|
507
|
+
assert!(index.add(id4, &second).is_ok());
|
|
508
|
+
let mut found_slice = [0.0 as f32; 4];
|
|
509
|
+
assert_eq!(index.get(id4, &mut found_slice).unwrap(), 1);
|
|
510
|
+
assert!(index.remove(id4).is_ok());
|
|
511
|
+
|
|
512
|
+
assert_eq!(index.size(), 0);
|
|
513
|
+
}
|
|
514
|
+
|
|
469
515
|
#[test]
|
|
470
516
|
fn integration() {
|
|
471
517
|
let mut options = IndexOptions::default();
|
package/setup.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
|
+
import subprocess
|
|
3
4
|
from setuptools import setup
|
|
4
5
|
|
|
5
6
|
from pybind11.setup_helpers import Pybind11Extension
|
|
@@ -9,6 +10,13 @@ compile_args = []
|
|
|
9
10
|
link_args = []
|
|
10
11
|
macros_args = []
|
|
11
12
|
|
|
13
|
+
def is_gcc():
|
|
14
|
+
try:
|
|
15
|
+
compiler_version = subprocess.check_output(["c++", "--version"], universal_newlines=True)
|
|
16
|
+
return "gcc" in compiler_version.lower()
|
|
17
|
+
except Exception:
|
|
18
|
+
return False
|
|
19
|
+
|
|
12
20
|
if sys.platform == "linux":
|
|
13
21
|
compile_args.append("-std=c++17")
|
|
14
22
|
compile_args.append("-O3") # Maximize performance
|
|
@@ -21,9 +29,13 @@ if sys.platform == "linux":
|
|
|
21
29
|
|
|
22
30
|
macros_args.append(("USEARCH_USE_NATIVE_F16", "0"))
|
|
23
31
|
macros_args.append(("USEARCH_USE_SIMSIMD", "1"))
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
|
|
33
|
+
if is_gcc():
|
|
34
|
+
macros_args.append(("USEARCH_USE_OPENMP", "1"))
|
|
35
|
+
compile_args.append("-fopenmp")
|
|
36
|
+
link_args.append("-lgomp")
|
|
37
|
+
else:
|
|
38
|
+
macros_args.append(("USEARCH_USE_OPENMP", "0"))
|
|
27
39
|
|
|
28
40
|
if sys.platform == "darwin":
|
|
29
41
|
# MacOS 10.15 or higher is needed for `aligned_alloc` support.
|
|
@@ -16,7 +16,6 @@ permissions:
|
|
|
16
16
|
contents: read
|
|
17
17
|
|
|
18
18
|
jobs:
|
|
19
|
-
|
|
20
19
|
test_python:
|
|
21
20
|
name: Test Python ${{ matrix.python-version }} on ${{ matrix.architecture }} ${{ matrix.os }}
|
|
22
21
|
runs-on: ${{ matrix.os }}
|
|
@@ -34,7 +33,7 @@ jobs:
|
|
|
34
33
|
uses: actions/setup-python@v4
|
|
35
34
|
with:
|
|
36
35
|
python-version: ${{ matrix.python-version }}
|
|
37
|
-
|
|
36
|
+
|
|
38
37
|
- name: Install dependencies
|
|
39
38
|
run: |
|
|
40
39
|
python -m pip install --no-cache-dir --upgrade pip
|
|
@@ -58,24 +57,24 @@ jobs:
|
|
|
58
57
|
- name: Test with PyTest
|
|
59
58
|
run: |
|
|
60
59
|
python -c "import simsimd; print(simsimd.get_capabilities())"
|
|
61
|
-
pytest python/test.py
|
|
62
|
-
|
|
60
|
+
pytest python/test.py -s -x -v
|
|
61
|
+
|
|
63
62
|
test_javascript:
|
|
64
63
|
name: Test JavaScript
|
|
65
64
|
runs-on: ubuntu-latest
|
|
66
65
|
strategy:
|
|
67
66
|
matrix:
|
|
67
|
+
os: [ubuntu-22.04, macOS-11, windows-2022]
|
|
68
68
|
node-version: [18.x]
|
|
69
69
|
steps:
|
|
70
|
-
|
|
71
70
|
- uses: actions/checkout@v4
|
|
72
71
|
- name: Set up Node.js
|
|
73
72
|
uses: actions/setup-node@v3
|
|
74
73
|
with:
|
|
75
|
-
node-version:
|
|
76
|
-
|
|
74
|
+
node-version: "18.x"
|
|
75
|
+
|
|
77
76
|
- name: Build locally
|
|
78
|
-
run: npm
|
|
77
|
+
run: npm install
|
|
79
78
|
|
|
80
79
|
- name: Test
|
|
81
80
|
run: npm test
|
|
@@ -16,17 +16,15 @@ permissions:
|
|
|
16
16
|
id-token: write
|
|
17
17
|
|
|
18
18
|
jobs:
|
|
19
|
-
|
|
20
19
|
versioning:
|
|
21
20
|
name: Semantic Release
|
|
22
21
|
runs-on: ubuntu-latest
|
|
23
22
|
steps:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
- uses: actions/checkout@v3
|
|
24
|
+
with:
|
|
25
|
+
persist-credentials: false
|
|
26
|
+
- uses: actions/setup-node@v3
|
|
27
|
+
- run: npm install --save-dev @semantic-release/exec @semantic-release/git conventional-changelog-eslint semantic-release && npx semantic-release
|
|
30
28
|
|
|
31
29
|
rebase:
|
|
32
30
|
name: Rebase Dev. Branch
|
|
@@ -40,9 +38,10 @@ jobs:
|
|
|
40
38
|
|
|
41
39
|
- name: Perform rebase
|
|
42
40
|
run: |
|
|
41
|
+
git fetch origin main
|
|
43
42
|
git checkout main-dev
|
|
44
|
-
git rebase main
|
|
45
|
-
|
|
43
|
+
git rebase origin/main
|
|
44
|
+
|
|
46
45
|
- name: Push changes
|
|
47
46
|
uses: CasperWA/push-protected@v2
|
|
48
47
|
with:
|
|
@@ -58,19 +57,19 @@ jobs:
|
|
|
58
57
|
strategy:
|
|
59
58
|
fail-fast: false
|
|
60
59
|
matrix:
|
|
61
|
-
os: [ubuntu-
|
|
60
|
+
os: [ubuntu-22.04, macOS-11, windows-2022]
|
|
62
61
|
steps:
|
|
63
62
|
- uses: actions/checkout@v3
|
|
64
63
|
with:
|
|
65
|
-
ref:
|
|
64
|
+
ref: "main"
|
|
66
65
|
- uses: actions/setup-python@v3
|
|
67
|
-
|
|
66
|
+
|
|
68
67
|
- name: Setup Docker
|
|
69
|
-
if: matrix.os == 'ubuntu-
|
|
68
|
+
if: matrix.os == 'ubuntu-22.04'
|
|
70
69
|
uses: docker-practice/actions-setup-docker@master
|
|
71
|
-
|
|
70
|
+
|
|
72
71
|
- name: Setup QEMU
|
|
73
|
-
if: matrix.os == 'ubuntu-
|
|
72
|
+
if: matrix.os == 'ubuntu-22.04'
|
|
74
73
|
uses: docker/setup-qemu-action@v2.1.0
|
|
75
74
|
|
|
76
75
|
- name: Install CIBuildWheel
|
|
@@ -82,12 +81,11 @@ jobs:
|
|
|
82
81
|
- uses: actions/upload-artifact@v3
|
|
83
82
|
with:
|
|
84
83
|
path: ./wheelhouse/*.whl
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
|
|
87
85
|
publish_python:
|
|
88
86
|
name: Publish Python
|
|
89
87
|
needs: build_wheels
|
|
90
|
-
runs-on: ubuntu-
|
|
88
|
+
runs-on: ubuntu-22.04
|
|
91
89
|
environment:
|
|
92
90
|
name: pypi
|
|
93
91
|
url: https://pypi.org/p/simsimd
|
|
@@ -110,11 +108,11 @@ jobs:
|
|
|
110
108
|
publish_javascript:
|
|
111
109
|
name: Publish JavaScript
|
|
112
110
|
needs: versioning
|
|
113
|
-
runs-on: ubuntu-
|
|
111
|
+
runs-on: ubuntu-22.04
|
|
114
112
|
steps:
|
|
115
113
|
- uses: actions/checkout@v3
|
|
116
114
|
with:
|
|
117
|
-
ref:
|
|
115
|
+
ref: "main"
|
|
118
116
|
- run: git submodule update --init --recursive
|
|
119
117
|
- uses: actions/setup-node@v3
|
|
120
118
|
with:
|
package/simsimd/README.md
CHANGED
|
@@ -7,6 +7,7 @@ SimSIMD leverages SIMD intrinsics, capabilities that only select compilers effec
|
|
|
7
7
|
- ✅ __3-200x faster__ than NumPy and SciPy distance functions.
|
|
8
8
|
- ✅ Euclidean (L2), Inner Product, and Cosine (Angular) spatial distances.
|
|
9
9
|
- ✅ Hamming (~ Manhattan) and Jaccard (~ Tanimoto) binary distances.
|
|
10
|
+
- ✅ Kullback-Leibler and Jensen–Shannon divergences for probability distributions.
|
|
10
11
|
- ✅ Single-precision `f32`, half-precision `f16`, `i8`, and binary vectors.
|
|
11
12
|
- ✅ Compatible with GCC and Clang on MacOS and Linux, and MinGW on Windows.
|
|
12
13
|
- ✅ Compatible with NumPy, PyTorch, TensorFlow, and other tensors.
|
|
@@ -28,18 +29,19 @@ Given 1000 embeddings from OpenAI Ada API with 1536 dimensions, running on the A
|
|
|
28
29
|
| `numpy.inner` | `inner` | __2 x__ | __9 x__ | __18 x__ |
|
|
29
30
|
| `scipy.spatial.distance.cosine` | `cosine` | __32 x__ | __79 x__ | __133 x__ |
|
|
30
31
|
| `scipy.spatial.distance.sqeuclidean` | `sqeuclidean` | __5 x__ | __26 x__ | __17 x__ |
|
|
31
|
-
| `scipy.spatial.distance.jensenshannon` | `jensenshannon` |
|
|
32
|
+
| `scipy.spatial.distance.jensenshannon` | `jensenshannon` | __31 x__ | __53 x__ | |
|
|
32
33
|
| `scipy.special.kl_div` | `kullbackleibler` | __21 x__ | __18 x__ | |
|
|
33
34
|
|
|
34
35
|
### Intel Sapphire Rapids
|
|
35
36
|
|
|
36
|
-
On the Intel Sapphire Rapids platform, SimSIMD was benchmarked against
|
|
37
|
+
On the Intel Sapphire Rapids platform, SimSIMD was benchmarked against auto-vectorized code using GCC 12. GCC handles single-precision `float` and `int8_t` well. However, it fails on `_Float16` arrays, which has been part of the C language since 2011.
|
|
37
38
|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
40
|
-
| `cosine`
|
|
41
|
-
| `sqeuclidean`
|
|
42
|
-
| `inner`
|
|
39
|
+
| | GCC 12 `f32` | GCC 12 `f16` | SimSIMD `f16` | `f16` improvement |
|
|
40
|
+
| :-------------- | -----------: | -----------: | ------------: | ----------------: |
|
|
41
|
+
| `cosine` | 3.28 M/s | _336.29 k/s_ | _6.88 M/s_ | __20 x__ |
|
|
42
|
+
| `sqeuclidean` | 4.62 M/s | _147.25 k/s_ | _5.32 M/s_ | __36 x__ |
|
|
43
|
+
| `inner` | 3.81 M/s | _192.02 k/s_ | _5.99 M/s_ | __31 x__ |
|
|
44
|
+
| `jensenshannon` | 1.18 M/s | _18.13 k/s_ | _2.14 M/s_ | __118 x__ |
|
|
43
45
|
|
|
44
46
|
__Technical Insights__:
|
|
45
47
|
|
|
@@ -168,7 +170,10 @@ Should you wish to integrate SimSIMD within USearch, simply compile USearch with
|
|
|
168
170
|
__To rerun experiments__ utilize the following command:
|
|
169
171
|
|
|
170
172
|
```sh
|
|
171
|
-
cmake -DCMAKE_BUILD_TYPE=Release -DSIMSIMD_BUILD_BENCHMARKS=1 -B ./build_release
|
|
173
|
+
cmake -DCMAKE_BUILD_TYPE=Release -DSIMSIMD_BUILD_BENCHMARKS=1 -B ./build_release
|
|
174
|
+
cmake --build build_release --config Release
|
|
175
|
+
./build_release/simsimd_bench
|
|
176
|
+
./build_release/simsimd_bench --benchmark_filter=js
|
|
172
177
|
```
|
|
173
178
|
|
|
174
179
|
__To test and benchmark with Python bindings__:
|
package/simsimd/binding.gyp
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"target_name": "simsimd",
|
|
5
5
|
"sources": ["javascript/lib.c"],
|
|
6
6
|
"include_dirs": ["include"],
|
|
7
|
+
"defines": ["SIMSIMD_NATIVE_F16=0"],
|
|
7
8
|
"cflags": [
|
|
8
9
|
"-std=c11",
|
|
9
10
|
"-ffast-math",
|
|
@@ -11,7 +12,6 @@
|
|
|
11
12
|
"-Wno-maybe-uninitialized",
|
|
12
13
|
"-Wno-cast-function-type",
|
|
13
14
|
"-Wno-switch",
|
|
14
|
-
"-DSIMSIMD_NATIVE_F16=0",
|
|
15
15
|
],
|
|
16
16
|
}
|
|
17
17
|
]
|
package/simsimd/cpp/bench.cxx
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
#include <benchmark/benchmark.h>
|
|
5
5
|
|
|
6
|
-
#define SIMSIMD_RSQRT
|
|
7
|
-
#define SIMSIMD_LOG
|
|
6
|
+
#define SIMSIMD_RSQRT sqrtf
|
|
7
|
+
#define SIMSIMD_LOG logf
|
|
8
8
|
#include <simsimd/simsimd.h>
|
|
9
9
|
|
|
10
10
|
namespace bm = benchmark;
|
|
@@ -44,7 +44,7 @@ template <typename scalar_at, std::size_t dimensions_ak> struct vectors_pair_gt
|
|
|
44
44
|
a2_sum = std::sqrt(a2_sum);
|
|
45
45
|
b2_sum = std::sqrt(b2_sum);
|
|
46
46
|
for (std::size_t i = 0; i != dimensions_ak; ++i)
|
|
47
|
-
a[i]
|
|
47
|
+
a[i] = static_cast<scalar_at>(a[i] / a2_sum), b[i] = static_cast<scalar_at>(b[i] / b2_sum);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
};
|
|
@@ -129,10 +129,14 @@ int main(int argc, char** argv) {
|
|
|
129
129
|
register_<simsimd_f16_t>("neon_f16_ip", simsimd_neon_f16_ip, simsimd_accurate_f16_ip);
|
|
130
130
|
register_<simsimd_f16_t>("neon_f16_cos", simsimd_neon_f16_cos, simsimd_accurate_f16_cos);
|
|
131
131
|
register_<simsimd_f16_t>("neon_f16_l2sq", simsimd_neon_f16_l2sq, simsimd_accurate_f16_l2sq);
|
|
132
|
+
register_<simsimd_f16_t>("neon_f16_kl", simsimd_neon_f16_kl, simsimd_accurate_f16_kl);
|
|
133
|
+
register_<simsimd_f16_t>("neon_f16_js", simsimd_neon_f16_js, simsimd_accurate_f16_js);
|
|
132
134
|
|
|
133
135
|
register_<simsimd_f32_t>("neon_f32_ip", simsimd_neon_f32_ip, simsimd_accurate_f32_ip);
|
|
134
136
|
register_<simsimd_f32_t>("neon_f32_cos", simsimd_neon_f32_cos, simsimd_accurate_f32_cos);
|
|
135
137
|
register_<simsimd_f32_t>("neon_f32_l2sq", simsimd_neon_f32_l2sq, simsimd_accurate_f32_l2sq);
|
|
138
|
+
register_<simsimd_f32_t>("neon_f32_kl", simsimd_neon_f32_kl, simsimd_accurate_f32_kl);
|
|
139
|
+
register_<simsimd_f32_t>("neon_f32_js", simsimd_neon_f32_js, simsimd_accurate_f32_js);
|
|
136
140
|
|
|
137
141
|
register_<simsimd_i8_t>("neon_i8_cos", simsimd_neon_i8_cos, simsimd_accurate_i8_cos);
|
|
138
142
|
register_<simsimd_i8_t>("neon_i8_l2sq", simsimd_neon_i8_l2sq, simsimd_accurate_i8_l2sq);
|
|
@@ -152,6 +156,8 @@ int main(int argc, char** argv) {
|
|
|
152
156
|
register_<simsimd_f16_t>("avx2_f16_ip", simsimd_avx2_f16_ip, simsimd_accurate_f16_ip);
|
|
153
157
|
register_<simsimd_f16_t>("avx2_f16_cos", simsimd_avx2_f16_cos, simsimd_accurate_f16_cos);
|
|
154
158
|
register_<simsimd_f16_t>("avx2_f16_l2sq", simsimd_avx2_f16_l2sq, simsimd_accurate_f16_l2sq);
|
|
159
|
+
register_<simsimd_f16_t>("avx2_f16_kl", simsimd_avx2_f16_kl, simsimd_accurate_f16_kl);
|
|
160
|
+
register_<simsimd_f16_t>("avx2_f16_js", simsimd_avx2_f16_js, simsimd_accurate_f16_js);
|
|
155
161
|
|
|
156
162
|
register_<simsimd_i8_t>("avx2_i8_cos", simsimd_avx2_i8_cos, simsimd_accurate_i8_cos);
|
|
157
163
|
register_<simsimd_i8_t>("avx2_i8_l2sq", simsimd_avx2_i8_l2sq, simsimd_accurate_i8_l2sq);
|
|
@@ -161,6 +167,8 @@ int main(int argc, char** argv) {
|
|
|
161
167
|
register_<simsimd_f16_t>("avx512_f16_ip", simsimd_avx512_f16_ip, simsimd_accurate_f16_ip);
|
|
162
168
|
register_<simsimd_f16_t>("avx512_f16_cos", simsimd_avx512_f16_cos, simsimd_accurate_f16_cos);
|
|
163
169
|
register_<simsimd_f16_t>("avx512_f16_l2sq", simsimd_avx512_f16_l2sq, simsimd_accurate_f16_l2sq);
|
|
170
|
+
register_<simsimd_f16_t>("avx512_f16_kl", simsimd_avx512_f16_kl, simsimd_accurate_f16_kl);
|
|
171
|
+
register_<simsimd_f16_t>("avx512_f16_js", simsimd_avx512_f16_js, simsimd_accurate_f16_js);
|
|
164
172
|
|
|
165
173
|
register_<simsimd_i8_t>("avx512_i8_cos", simsimd_avx512_i8_cos, simsimd_accurate_i8_cos);
|
|
166
174
|
register_<simsimd_i8_t>("avx512_i8_l2sq", simsimd_avx512_i8_l2sq, simsimd_accurate_i8_l2sq);
|
|
@@ -168,15 +176,21 @@ int main(int argc, char** argv) {
|
|
|
168
176
|
register_<simsimd_f32_t>("avx512_f32_ip", simsimd_avx512_f32_ip, simsimd_accurate_f32_ip);
|
|
169
177
|
register_<simsimd_f32_t>("avx512_f32_cos", simsimd_avx512_f32_cos, simsimd_accurate_f32_cos);
|
|
170
178
|
register_<simsimd_f32_t>("avx512_f32_l2sq", simsimd_avx512_f32_l2sq, simsimd_accurate_f32_l2sq);
|
|
179
|
+
register_<simsimd_f32_t>("avx512_f32_kl", simsimd_avx512_f32_kl, simsimd_accurate_f32_kl);
|
|
180
|
+
register_<simsimd_f32_t>("avx512_f32_js", simsimd_avx512_f32_js, simsimd_accurate_f32_js);
|
|
171
181
|
#endif
|
|
172
182
|
|
|
173
183
|
register_<simsimd_f16_t>("serial_f16_ip", simsimd_serial_f16_ip, simsimd_accurate_f16_ip);
|
|
174
184
|
register_<simsimd_f16_t>("serial_f16_cos", simsimd_serial_f16_cos, simsimd_accurate_f16_cos);
|
|
175
185
|
register_<simsimd_f16_t>("serial_f16_l2sq", simsimd_serial_f16_l2sq, simsimd_accurate_f16_l2sq);
|
|
186
|
+
register_<simsimd_f16_t>("serial_f16_kl", simsimd_serial_f16_kl, simsimd_accurate_f16_kl);
|
|
187
|
+
register_<simsimd_f16_t>("serial_f16_js", simsimd_serial_f16_js, simsimd_accurate_f16_js);
|
|
176
188
|
|
|
177
189
|
register_<simsimd_f32_t>("serial_f32_ip", simsimd_serial_f32_ip, simsimd_accurate_f32_ip);
|
|
178
190
|
register_<simsimd_f32_t>("serial_f32_cos", simsimd_serial_f32_cos, simsimd_accurate_f32_cos);
|
|
179
191
|
register_<simsimd_f32_t>("serial_f32_l2sq", simsimd_serial_f32_l2sq, simsimd_accurate_f32_l2sq);
|
|
192
|
+
register_<simsimd_f32_t>("serial_f32_kl", simsimd_serial_f32_kl, simsimd_accurate_f32_kl);
|
|
193
|
+
register_<simsimd_f32_t>("serial_f32_js", simsimd_serial_f32_js, simsimd_accurate_f32_js);
|
|
180
194
|
|
|
181
195
|
register_<simsimd_i8_t>("serial_i8_cos", simsimd_serial_i8_cos, simsimd_accurate_i8_cos);
|
|
182
196
|
register_<simsimd_i8_t>("serial_i8_l2sq", simsimd_serial_i8_l2sq, simsimd_accurate_i8_l2sq);
|
|
@@ -136,12 +136,11 @@ simsimd_sve_b8_jaccard(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_siz
|
|
|
136
136
|
__attribute__((target("avx512vpopcntdq,avx512vl,avx512bw,avx512f"))) //
|
|
137
137
|
inline static simsimd_f32_t
|
|
138
138
|
simsimd_avx512_b8_hamming(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
|
|
139
|
-
__m512i differences_vec = _mm512_setzero_si512()
|
|
139
|
+
__m512i differences_vec = _mm512_setzero_si512();
|
|
140
140
|
for (simsimd_size_t i = 0; i < n_words; i += 64) {
|
|
141
141
|
|
|
142
142
|
// Compute mask for tail elements
|
|
143
143
|
__mmask64 mask = (i + 64 <= n_words) ? 0xFFFFFFFFFFFFFFFF : (((1ull << (n_words - i)) - 1ull));
|
|
144
|
-
|
|
145
144
|
__m512i a_vec = _mm512_maskz_loadu_epi8(mask, a + i);
|
|
146
145
|
__m512i b_vec = _mm512_maskz_loadu_epi8(mask, b + i);
|
|
147
146
|
__m512i xor_vec = _mm512_xor_si512(a_vec, b_vec);
|
|
@@ -161,7 +160,6 @@ simsimd_avx512_b8_jaccard(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_
|
|
|
161
160
|
|
|
162
161
|
// Compute mask for tail elements
|
|
163
162
|
__mmask64 mask = (i + 64 <= n_words) ? 0xFFFFFFFFFFFFFFFF : (((1ull << (n_words - i)) - 1ull));
|
|
164
|
-
|
|
165
163
|
__m512i a_vec = _mm512_maskz_loadu_epi8(mask, a + i);
|
|
166
164
|
__m512i b_vec = _mm512_maskz_loadu_epi8(mask, b + i);
|
|
167
165
|
__m512i and_vec = _mm512_and_si512(a_vec, b_vec);
|