usearch 2.7.3 → 2.7.7
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/workflows/Dockerfile.libs +4 -6
- package/.github/workflows/prerelease.yml +4 -1
- package/.github/workflows/release.yml +8 -28
- package/.github/workflows/update_version.sh +1 -0
- package/CITATION.cff +2 -2
- package/CMakeLists.txt +1 -1
- package/Cargo.toml +1 -1
- package/README.md +4 -4
- package/binding.gyp +1 -1
- package/build.gradle +20 -5
- package/conanfile.py +1 -1
- package/cpp/bench.cpp +1 -1
- package/csharp/nuget/nuget-package.props +1 -1
- package/docs/compilation.md +0 -2
- package/golang/README.md +0 -1
- package/include/usearch/index.hpp +1 -1
- package/include/usearch/index_plugins.hpp +128 -247
- package/package.json +1 -1
- package/python/lib.cpp +3 -4
- package/simsimd/.github/workflows/prerelease.yml +33 -58
- package/simsimd/.github/workflows/release.yml +22 -9
- package/simsimd/.github/workflows/update_version.sh +3 -1
- package/simsimd/.releaserc +9 -1
- package/simsimd/.vscode/launch.json +3 -0
- package/simsimd/.vscode/settings.json +15 -2
- package/simsimd/.vscode/tasks.json +2 -2
- package/simsimd/CMakeLists.txt +36 -55
- package/simsimd/README.md +163 -109
- package/simsimd/binding.gyp +18 -0
- package/simsimd/cpp/bench.cxx +187 -0
- package/simsimd/go.mod +3 -0
- package/simsimd/golang/bench_test.go +40 -0
- package/simsimd/golang/simsimd.go +47 -0
- package/simsimd/golang/unit_test.go +40 -0
- package/simsimd/include/simsimd/binary.h +186 -0
- package/simsimd/include/simsimd/probability.h +645 -0
- package/simsimd/include/simsimd/simsimd.h +335 -575
- package/simsimd/include/simsimd/spatial.h +924 -0
- package/simsimd/include/simsimd/types.h +196 -0
- package/simsimd/javascript/bench.js +117 -0
- package/simsimd/javascript/lib.c +102 -0
- package/simsimd/javascript/simsimd.js +45 -0
- package/simsimd/javascript/test.js +29 -0
- package/simsimd/package.json +31 -2
- package/simsimd/pyproject.toml +5 -6
- package/simsimd/python/bench.ipynb +187 -0
- package/simsimd/python/bench.py +268 -0
- package/simsimd/python/lib.c +453 -72
- package/simsimd/python/test.py +167 -35
- package/simsimd/setup.py +51 -8
- package/wasmer.toml +1 -1
- package/simsimd/bench.cxx +0 -137
- package/simsimd/include/simsimd/simsimd_bio.h +0 -54
- package/simsimd/include/simsimd/simsimd_chem.h +0 -167
- package/simsimd/include/simsimd/simsimd_text.h +0 -92
- package/simsimd/python/run.sh +0 -1
- package/simsimd/python/test.c +0 -109
package/simsimd/python/test.py
CHANGED
|
@@ -1,38 +1,170 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import pytest
|
|
2
|
+
import numpy as np
|
|
3
3
|
import simsimd as simd
|
|
4
|
+
import scipy.spatial.distance as spd
|
|
5
|
+
|
|
6
|
+
SIMSIMD_RTOL = 2
|
|
7
|
+
SIMSIMD_ATOL = 3e-1
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_pointers_availability():
|
|
11
|
+
"""Tests the availability of pre-compiled functions for compatibility with USearch."""
|
|
12
|
+
assert simd.pointer_to_sqeuclidean("f32") != 0
|
|
13
|
+
assert simd.pointer_to_cosine("f32") != 0
|
|
14
|
+
assert simd.pointer_to_inner("f32") != 0
|
|
15
|
+
|
|
16
|
+
assert simd.pointer_to_sqeuclidean("f16") != 0
|
|
17
|
+
assert simd.pointer_to_cosine("f16") != 0
|
|
18
|
+
assert simd.pointer_to_inner("f16") != 0
|
|
19
|
+
|
|
20
|
+
assert simd.pointer_to_sqeuclidean("i8") != 0
|
|
21
|
+
assert simd.pointer_to_cosine("i8") != 0
|
|
22
|
+
assert simd.pointer_to_inner("i8") != 0
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.mark.repeat(50)
|
|
26
|
+
@pytest.mark.parametrize("ndim", [3, 97, 1536])
|
|
27
|
+
@pytest.mark.parametrize("dtype", [np.float32, np.float16])
|
|
28
|
+
def test_dot(ndim, dtype):
|
|
29
|
+
"""Compares the simd.dot() function with numpy.dot(), measuring the accuracy error for f16, and f32 types."""
|
|
30
|
+
a = np.random.randn(ndim).astype(dtype)
|
|
31
|
+
b = np.random.randn(ndim).astype(dtype)
|
|
32
|
+
a /= np.linalg.norm(a)
|
|
33
|
+
b /= np.linalg.norm(b)
|
|
34
|
+
|
|
35
|
+
expected = 1 - np.inner(a, b)
|
|
36
|
+
result = simd.inner(a, b)
|
|
37
|
+
|
|
38
|
+
np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.mark.repeat(50)
|
|
42
|
+
@pytest.mark.parametrize("ndim", [3, 97, 1536])
|
|
43
|
+
@pytest.mark.parametrize("dtype", [np.float32, np.float16])
|
|
44
|
+
def test_sqeuclidean(ndim, dtype):
|
|
45
|
+
"""Compares the simd.sqeuclidean() function with scipy.spatial.distance.sqeuclidean(), measuring the accuracy error for f16, and f32 types."""
|
|
46
|
+
a = np.random.randn(ndim).astype(dtype)
|
|
47
|
+
b = np.random.randn(ndim).astype(dtype)
|
|
48
|
+
|
|
49
|
+
expected = spd.sqeuclidean(a, b)
|
|
50
|
+
result = simd.sqeuclidean(a, b)
|
|
51
|
+
|
|
52
|
+
np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@pytest.mark.repeat(50)
|
|
56
|
+
@pytest.mark.parametrize("ndim", [3, 97, 1536])
|
|
57
|
+
@pytest.mark.parametrize("dtype", [np.float32, np.float16])
|
|
58
|
+
def test_cosine(ndim, dtype):
|
|
59
|
+
"""Compares the simd.cosine() function with scipy.spatial.distance.cosine(), measuring the accuracy error for f16, and f32 types."""
|
|
60
|
+
a = np.random.randn(ndim).astype(dtype)
|
|
61
|
+
b = np.random.randn(ndim).astype(dtype)
|
|
62
|
+
|
|
63
|
+
expected = spd.cosine(a, b)
|
|
64
|
+
result = simd.cosine(a, b)
|
|
65
|
+
|
|
66
|
+
np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@pytest.mark.parametrize("ndim", [3, 97, 1536])
|
|
70
|
+
@pytest.mark.parametrize("dtype", [np.float32, np.float16])
|
|
71
|
+
def test_cosine_zero_vector(ndim, dtype):
|
|
72
|
+
"""Tests the simd.cosine() function with zero vectors, to catch division by zero errors."""
|
|
73
|
+
a = np.zeros(ndim, dtype=dtype)
|
|
74
|
+
b = np.random.randn(ndim).astype(dtype)
|
|
75
|
+
|
|
76
|
+
# SciPy raises: "RuntimeWarning: invalid value encountered in scalar divide"
|
|
77
|
+
with pytest.raises(RuntimeWarning):
|
|
78
|
+
expected = spd.cosine(a, b)
|
|
79
|
+
|
|
80
|
+
expected = 1
|
|
81
|
+
result = simd.cosine(a, b)
|
|
82
|
+
|
|
83
|
+
assert result == expected, f"Expected {expected}, but got {result}"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@pytest.mark.repeat(50)
|
|
87
|
+
@pytest.mark.parametrize("ndim", [3, 97, 1536])
|
|
88
|
+
def test_hamming(ndim):
|
|
89
|
+
"""Compares the simd.hamming() function with scipy.spatial.distance.hamming."""
|
|
90
|
+
a = np.packbits(np.random.randint(0, 2, ndim).astype(np.uint8))
|
|
91
|
+
b = np.packbits(np.random.randint(0, 2, ndim).astype(np.uint8))
|
|
92
|
+
|
|
93
|
+
expected = spd.hamming(a, b)
|
|
94
|
+
result = simd.hamming(a, b)
|
|
95
|
+
|
|
96
|
+
np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@pytest.mark.repeat(50)
|
|
100
|
+
@pytest.mark.parametrize("ndim", [3, 97, 1536])
|
|
101
|
+
def test_jaccard(ndim):
|
|
102
|
+
"""Compares the simd.jaccard() function with scipy.spatial.distance.jaccard."""
|
|
103
|
+
a = np.packbits(np.random.randint(0, 2, ndim).astype(np.uint8))
|
|
104
|
+
b = np.packbits(np.random.randint(0, 2, ndim).astype(np.uint8))
|
|
105
|
+
|
|
106
|
+
expected = spd.jaccard(a, b)
|
|
107
|
+
result = simd.jaccard(a, b)
|
|
108
|
+
|
|
109
|
+
np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@pytest.mark.parametrize("ndim", [3, 97, 1536])
|
|
113
|
+
@pytest.mark.parametrize("dtype", [np.float32, np.float16])
|
|
114
|
+
def test_batch(ndim, dtype):
|
|
115
|
+
"""Compares the simd.simd.sqeuclidean() function with scipy.spatial.distance.sqeuclidean() for a batch of vectors, measuring the accuracy error for f16, and f32 types."""
|
|
116
|
+
|
|
117
|
+
# Distance between matrixes A (N x D scalars) and B (N x D scalars) is an array with N floats.
|
|
118
|
+
A = np.random.randn(10, ndim).astype(dtype)
|
|
119
|
+
B = np.random.randn(10, ndim).astype(dtype)
|
|
120
|
+
result_np = [spd.sqeuclidean(A[i], B[i]) for i in range(10)]
|
|
121
|
+
result_simd = simd.sqeuclidean(A, B)
|
|
122
|
+
assert np.allclose(result_simd, result_np, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
|
|
123
|
+
|
|
124
|
+
# Distance between matrixes A (N x D scalars) and B (1 x D scalars) is an array with N floats.
|
|
125
|
+
B = np.random.randn(1, ndim).astype(dtype)
|
|
126
|
+
result_np = [spd.sqeuclidean(A[i], B[0]) for i in range(10)]
|
|
127
|
+
result_simd = simd.sqeuclidean(A, B)
|
|
128
|
+
assert np.allclose(result_simd, result_np, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
|
|
129
|
+
|
|
130
|
+
# Distance between matrixes A (1 x D scalars) and B (N x D scalars) is an array with N floats.
|
|
131
|
+
A = np.random.randn(1, ndim).astype(dtype)
|
|
132
|
+
B = np.random.randn(10, ndim).astype(dtype)
|
|
133
|
+
result_np = [spd.sqeuclidean(A[0], B[i]) for i in range(10)]
|
|
134
|
+
result_simd = simd.sqeuclidean(A, B)
|
|
135
|
+
assert np.allclose(result_simd, result_np, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
|
|
136
|
+
|
|
137
|
+
# Distance between matrix A (N x D scalars) and array B (D scalars) is an array with N floats.
|
|
138
|
+
A = np.random.randn(10, ndim).astype(dtype)
|
|
139
|
+
B = np.random.randn(ndim).astype(dtype)
|
|
140
|
+
result_np = [spd.sqeuclidean(A[i], B) for i in range(10)]
|
|
141
|
+
result_simd = simd.sqeuclidean(A, B)
|
|
142
|
+
assert np.allclose(result_simd, result_np, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
|
|
143
|
+
|
|
144
|
+
# Distance between matrix B (N x D scalars) and array A (D scalars) is an array with N floats.
|
|
145
|
+
B = np.random.randn(10, ndim).astype(dtype)
|
|
146
|
+
A = np.random.randn(ndim).astype(dtype)
|
|
147
|
+
result_np = [spd.sqeuclidean(B[i], A) for i in range(10)]
|
|
148
|
+
result_simd = simd.sqeuclidean(B, A)
|
|
149
|
+
assert np.allclose(result_simd, result_np, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@pytest.mark.parametrize("ndim", [3, 97, 1536])
|
|
153
|
+
@pytest.mark.parametrize("dtype", [np.float32, np.float16])
|
|
154
|
+
@pytest.mark.parametrize("metric", ["sqeuclidean", "cosine"])
|
|
155
|
+
def test_cdist(ndim, dtype, metric):
|
|
156
|
+
"""Compares the simd.cdist() function with scipy.spatial.distance.cdist(), measuring the accuracy error for f16, and f32 types using sqeuclidean and cosine metrics."""
|
|
157
|
+
|
|
158
|
+
# Create random matrices A (M x D) and B (N x D).
|
|
159
|
+
M, N = 10, 15 # or any other sizes you deem appropriate
|
|
160
|
+
A = np.random.randn(M, ndim).astype(dtype)
|
|
161
|
+
B = np.random.randn(N, ndim).astype(dtype)
|
|
162
|
+
|
|
163
|
+
# Compute cdist using scipy.
|
|
164
|
+
expected = spd.cdist(A, B, metric)
|
|
4
165
|
|
|
166
|
+
# Compute cdist using simd.
|
|
167
|
+
result = simd.cdist(A, B, metric=metric)
|
|
5
168
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
assert simd.to_int(simd.tanimoto_b1x8_naive) != 0
|
|
9
|
-
assert simd.to_int(simd.tanimoto_maccs_naive) != 0
|
|
10
|
-
|
|
11
|
-
# Arm Neon variants should be precompiled for any 64-bit Arm machine:
|
|
12
|
-
if platform.machine() == "arm64":
|
|
13
|
-
assert simd.to_int(simd.cos_f16x4_neon) != 0
|
|
14
|
-
assert simd.to_int(simd.cos_i8x16_neon) != 0
|
|
15
|
-
assert simd.to_int(simd.cos_f32x4_neon) != 0
|
|
16
|
-
assert simd.to_int(simd.dot_f32x4_neon) != 0
|
|
17
|
-
assert simd.to_int(simd.tanimoto_maccs_neon) != 0
|
|
18
|
-
|
|
19
|
-
# Arm SVE variants should be precompiled for 64-bit Arm machines running Linux:
|
|
20
|
-
if platform.machine() == "arm64" and platform.system().lower() == "linux":
|
|
21
|
-
assert simd.to_int(simd.dot_f32_sve) != 0
|
|
22
|
-
assert simd.to_int(simd.cos_f32_sve) != 0
|
|
23
|
-
assert simd.to_int(simd.cos_f16_sve) != 0
|
|
24
|
-
assert simd.to_int(simd.l2sq_f32_sve) != 0
|
|
25
|
-
assert simd.to_int(simd.l2sq_f16_sve) != 0
|
|
26
|
-
assert simd.to_int(simd.hamming_b1x8_sve) != 0
|
|
27
|
-
assert simd.to_int(simd.hamming_b1x128_sve) != 0
|
|
28
|
-
assert simd.to_int(simd.tanimoto_maccs_sve) != 0
|
|
29
|
-
|
|
30
|
-
# x86 AVX2 variants are precompiled for every 64-bit x86 platform:
|
|
31
|
-
if platform.machine() == "x86_64":
|
|
32
|
-
assert simd.to_int(simd.cos_f32x4_avx2) != 0
|
|
33
|
-
|
|
34
|
-
# x86 AVX-512 variants are precompiled for 64-bit x86 machines running Linux:
|
|
35
|
-
if platform.machine() == "x86_64" and platform.system().lower() == "linux":
|
|
36
|
-
assert simd.to_int(simd.cos_f16x16_avx512) != 0
|
|
37
|
-
assert simd.to_int(simd.hamming_b1x128_avx512) != 0
|
|
38
|
-
assert simd.to_int(simd.tanimoto_maccs_avx512) != 0
|
|
169
|
+
# Assert they're close.
|
|
170
|
+
np.testing.assert_allclose(expected, result, atol=SIMSIMD_ATOL, rtol=SIMSIMD_RTOL)
|
package/simsimd/setup.py
CHANGED
|
@@ -1,10 +1,59 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import sys
|
|
3
|
+
import platform
|
|
2
4
|
from os.path import dirname
|
|
3
5
|
from setuptools import setup, Extension
|
|
4
6
|
|
|
7
|
+
import glob
|
|
8
|
+
import numpy
|
|
9
|
+
|
|
5
10
|
__version__ = open("VERSION", "r").read().strip()
|
|
6
11
|
__lib_name__ = "simsimd"
|
|
7
12
|
|
|
13
|
+
compile_args = []
|
|
14
|
+
link_args = []
|
|
15
|
+
macros_args = []
|
|
16
|
+
|
|
17
|
+
if sys.platform == "linux":
|
|
18
|
+
compile_args.append("-std=c11")
|
|
19
|
+
compile_args.append("-O3")
|
|
20
|
+
compile_args.append("-ffast-math")
|
|
21
|
+
compile_args.append("-fdiagnostics-color=always")
|
|
22
|
+
|
|
23
|
+
# Disable warnings
|
|
24
|
+
compile_args.append("-w")
|
|
25
|
+
|
|
26
|
+
# Enable OpenMP for Linux
|
|
27
|
+
compile_args.append("-fopenmp")
|
|
28
|
+
link_args.append("-lgomp")
|
|
29
|
+
|
|
30
|
+
if sys.platform == "darwin":
|
|
31
|
+
compile_args.append("-std=c11")
|
|
32
|
+
compile_args.append("-O3")
|
|
33
|
+
compile_args.append("-ffast-math")
|
|
34
|
+
|
|
35
|
+
# Disable warnings
|
|
36
|
+
compile_args.append("-w")
|
|
37
|
+
|
|
38
|
+
if sys.platform == "win32":
|
|
39
|
+
compile_args.append("/std:c11")
|
|
40
|
+
compile_args.append("/O2")
|
|
41
|
+
compile_args.append("/fp:fast")
|
|
42
|
+
|
|
43
|
+
# Dealing with MinGW linking errors
|
|
44
|
+
# https://cibuildwheel.readthedocs.io/en/stable/faq/#windows-importerror-dll-load-failed-the-specific-module-could-not-be-found
|
|
45
|
+
compile_args.append("/d2FH4-")
|
|
46
|
+
|
|
47
|
+
ext_modules = [
|
|
48
|
+
Extension(
|
|
49
|
+
"simsimd",
|
|
50
|
+
sources=["python/lib.c"],
|
|
51
|
+
include_dirs=["include", numpy.get_include()],
|
|
52
|
+
extra_compile_args=compile_args,
|
|
53
|
+
extra_link_args=link_args,
|
|
54
|
+
define_macros=macros_args,
|
|
55
|
+
),
|
|
56
|
+
]
|
|
8
57
|
|
|
9
58
|
this_directory = os.path.abspath(dirname(__file__))
|
|
10
59
|
with open(os.path.join(this_directory, "README.md"), "r", encoding="utf8") as f:
|
|
@@ -16,7 +65,7 @@ setup(
|
|
|
16
65
|
author="Ash Vardanian",
|
|
17
66
|
author_email="1983160+ashvardanian@users.noreply.github.com",
|
|
18
67
|
url="https://github.com/ashvardanian/simsimd",
|
|
19
|
-
description="
|
|
68
|
+
description="Vector Similarity Functions 3x-200x Faster than SciPy and NumPy",
|
|
20
69
|
long_description=long_description,
|
|
21
70
|
long_description_content_type="text/markdown",
|
|
22
71
|
license="Apache-2.0",
|
|
@@ -29,12 +78,6 @@ setup(
|
|
|
29
78
|
"Programming Language :: C",
|
|
30
79
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
31
80
|
],
|
|
32
|
-
ext_modules=
|
|
33
|
-
Extension(
|
|
34
|
-
"simsimd",
|
|
35
|
-
sources=["python/lib.c"],
|
|
36
|
-
include_dirs=["include"],
|
|
37
|
-
)
|
|
38
|
-
],
|
|
81
|
+
ext_modules=ext_modules,
|
|
39
82
|
zip_safe=False,
|
|
40
83
|
)
|
package/wasmer.toml
CHANGED
package/simsimd/bench.cxx
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
#include <thread>
|
|
2
|
-
|
|
3
|
-
#include <benchmark/benchmark.h>
|
|
4
|
-
|
|
5
|
-
#include <simsimd/simsimd.h>
|
|
6
|
-
#include <simsimd/simsimd_chem.h>
|
|
7
|
-
|
|
8
|
-
namespace bm = benchmark;
|
|
9
|
-
|
|
10
|
-
static const std::size_t threads_k = std::thread::hardware_concurrency();
|
|
11
|
-
static constexpr std::size_t time_k = 10;
|
|
12
|
-
static constexpr std::size_t bytes_k = 1024;
|
|
13
|
-
|
|
14
|
-
template <typename return_at, typename... args_at>
|
|
15
|
-
constexpr std::size_t number_of_arguments(return_at (*f)(args_at...)) {
|
|
16
|
-
return sizeof...(args_at);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
template <typename scalar_at, std::size_t bytes_per_vector_ak = bytes_k,
|
|
20
|
-
typename metric_at = void> //
|
|
21
|
-
static void measure(bm::State& state, metric_at metric) {
|
|
22
|
-
|
|
23
|
-
constexpr ::size_t dimensions_ak = bytes_per_vector_ak / sizeof(scalar_at);
|
|
24
|
-
alignas(64) scalar_at a[dimensions_ak]{};
|
|
25
|
-
alignas(64) scalar_at b[dimensions_ak]{};
|
|
26
|
-
float c{};
|
|
27
|
-
|
|
28
|
-
std::fill_n(a, dimensions_ak, static_cast<scalar_at>(1));
|
|
29
|
-
std::fill_n(b, dimensions_ak, static_cast<scalar_at>(2));
|
|
30
|
-
|
|
31
|
-
std::size_t iterations = 0;
|
|
32
|
-
for (auto _ : state)
|
|
33
|
-
if constexpr (number_of_arguments(metric_at{}) == 3)
|
|
34
|
-
bm::DoNotOptimize((c = metric(a, b, dimensions_ak))), iterations++;
|
|
35
|
-
else
|
|
36
|
-
bm::DoNotOptimize((c = metric(a, b))), iterations++;
|
|
37
|
-
|
|
38
|
-
state.counters["bytes"] = bm::Counter(iterations * bytes_per_vector_ak * 2u, bm::Counter::kIsRate);
|
|
39
|
-
state.counters["pairs"] = bm::Counter(iterations, bm::Counter::kIsRate);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
template <typename scalar_at, std::size_t bytes_per_vector_ak = bytes_k, typename metric_at = void>
|
|
43
|
-
void register_(char const* name, metric_at distance_func) {
|
|
44
|
-
bm::RegisterBenchmark(name, measure<scalar_at, bytes_per_vector_ak, metric_at>, distance_func)
|
|
45
|
-
->Threads(1)
|
|
46
|
-
->MinTime(time_k);
|
|
47
|
-
bm::RegisterBenchmark(name, measure<scalar_at, bytes_per_vector_ak, metric_at>, distance_func)
|
|
48
|
-
->Threads(threads_k)
|
|
49
|
-
->MinTime(time_k);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
simsimd_f32_t cos_f32_naive(simsimd_f32_t* v1, simsimd_f32_t* v2, std::size_t n) {
|
|
53
|
-
simsimd_f32_t inner_product = 0;
|
|
54
|
-
simsimd_f32_t magnitude1 = 0;
|
|
55
|
-
simsimd_f32_t magnitude2 = 0;
|
|
56
|
-
for (std::size_t i = 0; i != n; ++i) {
|
|
57
|
-
inner_product += v1[i] * v2[i];
|
|
58
|
-
magnitude1 += v1[i] * v1[i];
|
|
59
|
-
magnitude2 += v2[i] * v2[i];
|
|
60
|
-
}
|
|
61
|
-
return 1 - inner_product / (std::sqrt(magnitude1) * std::sqrt(magnitude2));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
int main(int argc, char** argv) {
|
|
65
|
-
|
|
66
|
-
bool compiled_with_sve = false;
|
|
67
|
-
bool compiled_with_neon = false;
|
|
68
|
-
bool compiled_with_avx2 = false;
|
|
69
|
-
bool compiled_with_avx512popcnt = false;
|
|
70
|
-
|
|
71
|
-
#if defined(__ARM_FEATURE_SVE)
|
|
72
|
-
compiled_with_sve = true;
|
|
73
|
-
#endif
|
|
74
|
-
#if defined(__ARM_NEON)
|
|
75
|
-
compiled_with_neon = true;
|
|
76
|
-
#endif
|
|
77
|
-
#if defined(__AVX2__)
|
|
78
|
-
compiled_with_avx2 = true;
|
|
79
|
-
#endif
|
|
80
|
-
#if defined(__AVX512VPOPCNTDQ__)
|
|
81
|
-
compiled_with_avx512popcnt = true;
|
|
82
|
-
#endif
|
|
83
|
-
|
|
84
|
-
// Log supported functionality
|
|
85
|
-
char const* flags[2] = {"false", "true"};
|
|
86
|
-
std::printf("Benchmarking Similarity Measures\n");
|
|
87
|
-
std::printf("\n");
|
|
88
|
-
std::printf("- Arm NEON support enabled: %s\n", flags[compiled_with_neon]);
|
|
89
|
-
std::printf("- Arm SVE support enabled: %s\n", flags[compiled_with_sve]);
|
|
90
|
-
std::printf("- x86 AVX2 support enabled: %s\n", flags[compiled_with_avx2]);
|
|
91
|
-
std::printf("- x86 AVX512VPOPCNTDQ support enabled: %s\n", flags[compiled_with_avx512popcnt]);
|
|
92
|
-
std::printf("Default vector length: %zu bytes\n", bytes_k);
|
|
93
|
-
std::printf("\n");
|
|
94
|
-
|
|
95
|
-
// Run the benchmarks
|
|
96
|
-
bm::Initialize(&argc, argv);
|
|
97
|
-
if (bm::ReportUnrecognizedArguments(argc, argv))
|
|
98
|
-
return 1;
|
|
99
|
-
|
|
100
|
-
register_<std::uint8_t, 21>("tanimoto_maccs_naive", simsimd_tanimoto_maccs_naive);
|
|
101
|
-
register_<simsimd_f32_t>("cos_f32_naive", cos_f32_naive);
|
|
102
|
-
|
|
103
|
-
#if SIMSIMD_TARGET_ARM_SVE
|
|
104
|
-
register_<simsimd_f32_t>("dot_f32_sve", simsimd_dot_f32_sve);
|
|
105
|
-
register_<simsimd_f32_t>("cos_f32_sve", simsimd_cos_f32_sve);
|
|
106
|
-
register_<std::int16_t>("cos_f16_sve", simsimd_cos_f16_sve);
|
|
107
|
-
register_<simsimd_f32_t>("l2sq_f32_sve", simsimd_l2sq_f32_sve);
|
|
108
|
-
register_<std::int16_t>("l2sq_f16_sve", simsimd_l2sq_f16_sve);
|
|
109
|
-
register_<std::uint8_t>("hamming_b1x8_sve", simsimd_hamming_b1x8_sve);
|
|
110
|
-
register_<std::uint8_t>("hamming_b1x128_sve", simsimd_hamming_b1x128_sve);
|
|
111
|
-
register_<std::uint8_t, 21>("tanimoto_maccs_sve", simsimd_tanimoto_maccs_sve);
|
|
112
|
-
#endif
|
|
113
|
-
|
|
114
|
-
#if SIMSIMD_TARGET_ARM_NEON
|
|
115
|
-
register_<simsimd_f32_t>("dot_f32x4_neon", simsimd_dot_f32x4_neon);
|
|
116
|
-
register_<std::int16_t>("cos_f16x4_neon", simsimd_cos_f16x4_neon);
|
|
117
|
-
register_<std::int8_t>("cos_i8x16_neon", simsimd_cos_i8x16_neon);
|
|
118
|
-
register_<std::int8_t>("l2sq_i8x16_neon", simsimd_l2sq_i8x16_neon);
|
|
119
|
-
register_<simsimd_f32_t>("cos_f32x4_neon", simsimd_cos_f32x4_neon);
|
|
120
|
-
register_<std::uint8_t, 21>("tanimoto_maccs_neon", simsimd_tanimoto_maccs_neon);
|
|
121
|
-
#endif
|
|
122
|
-
|
|
123
|
-
#if SIMSIMD_TARGET_X86_AVX2
|
|
124
|
-
register_<simsimd_f32_t>("dot_f32x4_avx2", simsimd_dot_f32x4_avx2);
|
|
125
|
-
register_<simsimd_f32_t>("cos_f32x4_avx2", simsimd_cos_f32x4_avx2);
|
|
126
|
-
#endif
|
|
127
|
-
|
|
128
|
-
#if SIMSIMD_TARGET_X86_AVX512
|
|
129
|
-
register_<std::int16_t>("cos_f16x16_avx512", simsimd_cos_f16x16_avx512);
|
|
130
|
-
register_<std::uint8_t>("hamming_b1x128_avx512", simsimd_hamming_b1x128_avx512);
|
|
131
|
-
register_<std::uint8_t, 21>("tanimoto_maccs_avx512", simsimd_tanimoto_maccs_avx512);
|
|
132
|
-
#endif
|
|
133
|
-
|
|
134
|
-
bm::RunSpecifiedBenchmarks();
|
|
135
|
-
bm::Shutdown();
|
|
136
|
-
return 0;
|
|
137
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @brief Collection of Biology-oriented Similarity Measures, SIMD-accelerated with SSE, AVX, NEON, SVE.
|
|
3
|
-
*
|
|
4
|
-
* @author Ash Vardanian
|
|
5
|
-
* @date August 8, 2023
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
#pragma once
|
|
9
|
-
|
|
10
|
-
#include <simsimd/simsimd_chem.h>
|
|
11
|
-
|
|
12
|
-
#ifdef _MSC_VER
|
|
13
|
-
#define hash32
|
|
14
|
-
#define hash64
|
|
15
|
-
#else
|
|
16
|
-
#define hash32
|
|
17
|
-
#define hash64
|
|
18
|
-
#endif
|
|
19
|
-
|
|
20
|
-
#ifdef __cplusplus
|
|
21
|
-
extern "C" {
|
|
22
|
-
#endif
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @brief Used to compute the similarity of two DNA or RNA sequences in "TwoBit" form.
|
|
26
|
-
*
|
|
27
|
-
* By design uses a separate bit-set for every string, expanding the compressed
|
|
28
|
-
* representation into bitsets, and then computes the tanimoto distance on those.
|
|
29
|
-
*
|
|
30
|
-
* Optimal `length_hash` should be derived from `max(a_length, b_length)`, as
|
|
31
|
-
*/
|
|
32
|
-
inline static simsimd_f32_t simsimd_tanimoto_twobit( //
|
|
33
|
-
uint8_t const* a_chars, uint8_t const* b_chars, //
|
|
34
|
-
size_t a_length, size_t b_length, //
|
|
35
|
-
uint8_t* a_hash, uint8_t* b_hash, size_t length_hash) {
|
|
36
|
-
|
|
37
|
-
// uint64 -> 32-gram
|
|
38
|
-
// uint32 -> 16-gram
|
|
39
|
-
uint64_t last_word = 0;
|
|
40
|
-
for (size_t i = 0; i + 8 <= a_length; i += 8) {
|
|
41
|
-
uint64_t a_word;
|
|
42
|
-
memcpy(&a_word, a_chars, 8);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
size_t b_words = (b_length + 7) / 8;
|
|
46
|
-
|
|
47
|
-
return simsimd_tanimoto_b1x8_naive(a_hash, b_hash, length_hash);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
#undef popcount64
|
|
51
|
-
|
|
52
|
-
#ifdef __cplusplus
|
|
53
|
-
}
|
|
54
|
-
#endif
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @brief Collection of Chemistry-oriented Similarity Measures, SIMD-accelerated with SSE, AVX, NEON, SVE.
|
|
3
|
-
*
|
|
4
|
-
* @author Ashot Vardanian
|
|
5
|
-
* @date July 1, 2023
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
#pragma once
|
|
9
|
-
#include <string.h> // `memcpy`
|
|
10
|
-
|
|
11
|
-
#include <simsimd/simsimd.h>
|
|
12
|
-
|
|
13
|
-
#ifdef _MSC_VER
|
|
14
|
-
#include <intrin.h>
|
|
15
|
-
#define popcount64 __popcnt64
|
|
16
|
-
#else
|
|
17
|
-
#define popcount64 __builtin_popcountll
|
|
18
|
-
#endif
|
|
19
|
-
|
|
20
|
-
#ifdef __cplusplus
|
|
21
|
-
extern "C" {
|
|
22
|
-
#endif
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @brief Optimized version for Tanimoto distance on @b exactly 166 bits,
|
|
26
|
-
* forming 21 incomplete bytes for the MACCS fingerprints used in
|
|
27
|
-
* computation chemistry.
|
|
28
|
-
*/
|
|
29
|
-
inline static simsimd_f32_t //
|
|
30
|
-
simsimd_tanimoto_maccs_naive(uint8_t const* a_chars, uint8_t const* b_chars) {
|
|
31
|
-
unsigned long a[3] = {0};
|
|
32
|
-
unsigned long b[3] = {0};
|
|
33
|
-
memcpy(&a[0], a_chars, 21);
|
|
34
|
-
memcpy(&b[0], b_chars, 21);
|
|
35
|
-
float and_count = popcount64(a[0] & b[0]) + popcount64(a[1] & b[1]) + popcount64(a[2] & b[2]);
|
|
36
|
-
float or_count = popcount64(a[0] | b[0]) + popcount64(a[1] | b[1]) + popcount64(a[2] | b[2]);
|
|
37
|
-
return 1 - and_count / or_count;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* @brief Weird Tanimoto distance implementation for concatenated MACCS and ECFP4
|
|
42
|
-
* representations, forming a grand-total of 56 bytes. The last chunk is evaluated
|
|
43
|
-
* if only the first one matches well.
|
|
44
|
-
*
|
|
45
|
-
* - First 166 bits in 21 bytes is a MACCS vector.
|
|
46
|
-
* - After that 3 empty bytes of a padding are expected.
|
|
47
|
-
* - After which, 256 bytes (32 words) of ECFP4 follow.
|
|
48
|
-
*/
|
|
49
|
-
inline static simsimd_f32_t //
|
|
50
|
-
simsimd_tanimoto_conditional_naive(uint8_t const* a_chars, uint8_t const* b_chars) {
|
|
51
|
-
float const threshold = 0.2;
|
|
52
|
-
unsigned long const* a = (unsigned long const*)(a_chars);
|
|
53
|
-
unsigned long const* b = (unsigned long const*)(b_chars);
|
|
54
|
-
float and_count = popcount64(a[0] & b[0]) + popcount64(a[1] & b[1]) + popcount64(a[2] & b[2]);
|
|
55
|
-
float or_count = popcount64(a[0] | b[0]) + popcount64(a[1] | b[1]) + popcount64(a[2] | b[2]);
|
|
56
|
-
float result = 1 - and_count / or_count;
|
|
57
|
-
if (result > threshold)
|
|
58
|
-
return result;
|
|
59
|
-
|
|
60
|
-
// Start comparing ECFP4
|
|
61
|
-
and_count = 0, or_count = 0;
|
|
62
|
-
for (int i = 0; i != 32; ++i)
|
|
63
|
-
and_count += popcount64(a[3 + i] & b[3 + i]), or_count += popcount64(a[3 + i] | b[3 + i]);
|
|
64
|
-
result = 1 - and_count / or_count;
|
|
65
|
-
return result * threshold;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
#if SIMSIMD_TARGET_ARM
|
|
69
|
-
#if SIMSIMD_TARGET_ARM_NEON
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* @brief Optimized version for Tanimoto distance on @b exactly 166 bits,
|
|
73
|
-
* forming 21 incomplete bytes for the MACCS fingerprints used in
|
|
74
|
-
* computation chemistry, accelerated for Arm NEON ISA.
|
|
75
|
-
*/
|
|
76
|
-
inline static simsimd_f32_t //
|
|
77
|
-
simsimd_tanimoto_maccs_neon(uint8_t const* a_chars, uint8_t const* b_chars) {
|
|
78
|
-
unsigned char a[32] = {0};
|
|
79
|
-
unsigned char b[32] = {0};
|
|
80
|
-
memcpy(&a[0], a_chars, 21);
|
|
81
|
-
memcpy(&b[0], b_chars, 21);
|
|
82
|
-
uint8x16_t a_first = vld1q_u8(&a[0]);
|
|
83
|
-
uint8x16_t a_second = vld1q_u8(&a[16]);
|
|
84
|
-
uint8x16_t b_first = vld1q_u8(&b[0]);
|
|
85
|
-
uint8x16_t b_second = vld1q_u8(&b[16]);
|
|
86
|
-
uint8x16_t a_and_b_first = vandq_u8(a_first, b_first);
|
|
87
|
-
uint8x16_t a_and_b_second = vandq_u8(a_second, b_second);
|
|
88
|
-
uint8x16_t a_or_b_first = vorrq_u8(a_first, b_first);
|
|
89
|
-
uint8x16_t a_or_b_second = vorrq_u8(a_second, b_second);
|
|
90
|
-
float and_populations = vaddvq_u8(vaddq_u8(vcntq_u8(a_and_b_first), vcntq_u8(a_and_b_second)));
|
|
91
|
-
float or_populations = vaddvq_u8(vaddq_u8(vcntq_u8(a_or_b_first), vcntq_u8(a_or_b_second)));
|
|
92
|
-
return 1 - and_populations / or_populations;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
#endif // SIMSIMD_TARGET_ARM_NEON
|
|
96
|
-
|
|
97
|
-
#if SIMSIMD_TARGET_ARM_SVE
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* @brief Optimized version for Tanimoto distance on @b exactly 166 bits,
|
|
101
|
-
* forming 21 incomplete bytes for the MACCS fingerprints used in
|
|
102
|
-
* computation chemistry, accelerated for Arm NEON ISA.
|
|
103
|
-
*/
|
|
104
|
-
__attribute__((target("+sve"))) inline static simsimd_f32_t //
|
|
105
|
-
simsimd_tanimoto_maccs_sve(uint8_t const* a_chars, uint8_t const* b_chars) {
|
|
106
|
-
svbool_t pg_vec = svwhilelt_b8(0ul, 21ul);
|
|
107
|
-
svuint8_t a_vec = svld1_u8(pg_vec, (uint8_t const*)a_chars);
|
|
108
|
-
svuint8_t b_vec = svld1_u8(pg_vec, (uint8_t const*)b_chars);
|
|
109
|
-
svuint8_t a_and_b_vec = svand_u8_m(pg_vec, a_vec, b_vec);
|
|
110
|
-
svuint8_t a_or_b_vec = svorr_u8_m(pg_vec, a_vec, b_vec);
|
|
111
|
-
svuint8_t and_populations_vec = svcnt_u8_x(pg_vec, a_and_b_vec);
|
|
112
|
-
svuint8_t or_populations_vec = svcnt_u8_x(pg_vec, a_or_b_vec);
|
|
113
|
-
|
|
114
|
-
// We are gonna need at most two cycles like this, regardless of the architecture,
|
|
115
|
-
// assuming the SVE registers are at least 128 bits (16 bytes), and we onlu need 21 bytes.
|
|
116
|
-
pg_vec = svwhilelt_b8(svcntb(), 21ul);
|
|
117
|
-
a_vec = svld1_u8(pg_vec, (uint8_t const*)a_chars + svcntb());
|
|
118
|
-
b_vec = svld1_u8(pg_vec, (uint8_t const*)b_chars + svcntb());
|
|
119
|
-
a_and_b_vec = svand_u8_m(pg_vec, a_vec, b_vec);
|
|
120
|
-
a_or_b_vec = svorr_u8_m(pg_vec, a_vec, b_vec);
|
|
121
|
-
and_populations_vec = svadd_u8_m(pg_vec, svcnt_u8_x(pg_vec, a_and_b_vec), and_populations_vec);
|
|
122
|
-
or_populations_vec = svadd_u8_m(pg_vec, svcnt_u8_x(pg_vec, a_or_b_vec), or_populations_vec);
|
|
123
|
-
|
|
124
|
-
float and_populations = svaddv_u8(svptrue_b8(), and_populations_vec);
|
|
125
|
-
float or_populations = svaddv_u8(svptrue_b8(), or_populations_vec);
|
|
126
|
-
return 1 - and_populations / or_populations;
|
|
127
|
-
}
|
|
128
|
-
#endif
|
|
129
|
-
#endif // SIMSIMD_TARGET_ARM
|
|
130
|
-
|
|
131
|
-
#if SIMSIMD_TARGET_X86
|
|
132
|
-
#if SIMSIMD_TARGET_X86_AVX512
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* @brief Optimized version for Tanimoto distance on @b exactly 166 bits,
|
|
136
|
-
* forming 21 incomplete bytes for the MACCS fingerprints used in
|
|
137
|
-
* computation chemistry, accelerated with AVX-512 population count
|
|
138
|
-
* instructions.
|
|
139
|
-
*/
|
|
140
|
-
__attribute__((target("avx512vpopcntdq"))) //
|
|
141
|
-
__attribute__((target("avx512vl"))) //
|
|
142
|
-
__attribute__((target("avx512bw"))) //
|
|
143
|
-
__attribute__((target("avx512f"))) inline static simsimd_f32_t //
|
|
144
|
-
simsimd_tanimoto_maccs_avx512(uint8_t const* a, uint8_t const* b) {
|
|
145
|
-
__m256i a_vec = _mm256_maskz_loadu_epi8(0b11111111111111111111100000000000, a);
|
|
146
|
-
__m256i b_vec = _mm256_maskz_loadu_epi8(0b11111111111111111111100000000000, b);
|
|
147
|
-
__m256i and_vec = _mm256_and_si256(a_vec, b_vec);
|
|
148
|
-
__m256i or_vec = _mm256_or_si256(a_vec, b_vec);
|
|
149
|
-
__m256i and_counts_vec = _mm256_popcnt_epi64(and_vec);
|
|
150
|
-
__m256i or_counts_vec = _mm256_popcnt_epi64(or_vec);
|
|
151
|
-
|
|
152
|
-
float and_counts = _mm256_extract_epi64(and_counts_vec, 0) + _mm256_extract_epi64(and_counts_vec, 1) +
|
|
153
|
-
_mm256_extract_epi64(and_counts_vec, 2) + _mm256_extract_epi64(and_counts_vec, 3);
|
|
154
|
-
float or_counts = _mm256_extract_epi64(or_counts_vec, 0) + _mm256_extract_epi64(or_counts_vec, 1) +
|
|
155
|
-
_mm256_extract_epi64(or_counts_vec, 2) + _mm256_extract_epi64(or_counts_vec, 3);
|
|
156
|
-
|
|
157
|
-
return 1 - and_counts / or_counts;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
#endif // SIMSIMD_TARGET_X86_AVX512
|
|
161
|
-
#endif // SIMSIMD_TARGET_X86
|
|
162
|
-
|
|
163
|
-
#undef popcount64
|
|
164
|
-
|
|
165
|
-
#ifdef __cplusplus
|
|
166
|
-
}
|
|
167
|
-
#endif
|