usearch 2.9.0 → 2.9.2
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/CITATION.cff +1 -1
- package/CONTRIBUTING.md +36 -2
- package/Cargo.toml +1 -1
- package/README.md +12 -15
- package/VERSION +1 -1
- package/c/lib.cpp +19 -17
- package/c/test.c +80 -38
- package/c/usearch.h +15 -15
- package/conanfile.py +1 -1
- package/include/usearch/index.hpp +5 -5
- package/include/usearch/index_dense.hpp +8 -4
- package/include/usearch/index_plugins.hpp +52 -32
- package/javascript/types/usearch.d.ts.map +1 -1
- package/javascript/usearch.js +4 -1
- package/package.json +3 -2
- package/simsimd/CONTRIBUTING.md +88 -0
- package/simsimd/Cargo.lock +4 -3
- package/simsimd/Cargo.toml +10 -2
- package/simsimd/README.md +102 -60
- package/simsimd/include/simsimd/binary.h +44 -0
- package/simsimd/include/simsimd/complex.h +174 -0
- package/simsimd/include/simsimd/simsimd.h +18 -9
- package/simsimd/include/simsimd/spatial.h +128 -36
- package/simsimd/include/simsimd/types.h +3 -2
- package/simsimd/package-lock.json +5 -5
- package/simsimd/package.json +1 -1
- package/simsimd/rust/benches/native.rs +12 -9
- package/simsimd/rust/lib.c +32 -55
- package/simsimd/rust/lib.rs +374 -56
- package/stringzilla/CONTRIBUTING.md +17 -0
- package/stringzilla/Cargo.lock +1 -1
- package/stringzilla/Cargo.toml +1 -1
- package/stringzilla/README.md +175 -34
- package/stringzilla/VERSION +1 -1
- package/stringzilla/build.rs +77 -4
- package/stringzilla/c/lib.c +33 -0
- package/stringzilla/cli/README.md +30 -13
- package/stringzilla/cli/__init__.py +0 -0
- package/stringzilla/cli/split.py +66 -33
- package/stringzilla/cli/wc.py +101 -21
- package/stringzilla/include/stringzilla/experimental.h +122 -1
- package/stringzilla/include/stringzilla/stringzilla.h +262 -132
- package/stringzilla/include/stringzilla/stringzilla.hpp +13 -5
- package/stringzilla/package.json +1 -1
- package/stringzilla/rust/lib.rs +673 -162
- package/wasmer.toml +1 -1
|
@@ -926,7 +926,7 @@ class i8_converted_t {
|
|
|
926
926
|
std::int8_t int8_{};
|
|
927
927
|
|
|
928
928
|
public:
|
|
929
|
-
constexpr static
|
|
929
|
+
constexpr static f32_t divisor_k = 100.f;
|
|
930
930
|
constexpr static std::int8_t min_k = -100;
|
|
931
931
|
constexpr static std::int8_t max_k = 100;
|
|
932
932
|
|
|
@@ -938,9 +938,9 @@ class i8_converted_t {
|
|
|
938
938
|
inline i8_converted_t(i8_converted_t const&) = default;
|
|
939
939
|
inline i8_converted_t& operator=(i8_converted_t const&) = default;
|
|
940
940
|
|
|
941
|
-
inline operator
|
|
942
|
-
inline operator
|
|
943
|
-
inline operator
|
|
941
|
+
inline operator f16_t() const noexcept { return static_cast<f16_t>(f32_t(int8_) / divisor_k); }
|
|
942
|
+
inline operator f32_t() const noexcept { return f32_t(int8_) / divisor_k; }
|
|
943
|
+
inline operator f64_t() const noexcept { return f64_t(int8_) / divisor_k; }
|
|
944
944
|
inline explicit operator bool() const noexcept { return int8_ > (max_k / 2); }
|
|
945
945
|
inline explicit operator std::int8_t() const noexcept { return int8_; }
|
|
946
946
|
inline explicit operator std::int16_t() const noexcept { return int8_; }
|
|
@@ -949,9 +949,9 @@ class i8_converted_t {
|
|
|
949
949
|
|
|
950
950
|
inline i8_converted_t(f16_t v)
|
|
951
951
|
: int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
|
|
952
|
-
inline i8_converted_t(
|
|
952
|
+
inline i8_converted_t(f32_t v)
|
|
953
953
|
: int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
|
|
954
|
-
inline i8_converted_t(
|
|
954
|
+
inline i8_converted_t(f64_t v)
|
|
955
955
|
: int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
|
|
956
956
|
};
|
|
957
957
|
|
|
@@ -1006,10 +1006,11 @@ template <typename scalar_at = float, typename result_at = scalar_at> struct met
|
|
|
1006
1006
|
#elif defined(USEARCH_DEFINED_GCC)
|
|
1007
1007
|
#pragma GCC ivdep
|
|
1008
1008
|
#endif
|
|
1009
|
-
for (std::size_t i = 0; i != dim; ++i)
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1009
|
+
for (std::size_t i = 0; i != dim; ++i) {
|
|
1010
|
+
result_t ai = static_cast<result_t>(a[i]);
|
|
1011
|
+
result_t bi = static_cast<result_t>(b[i]);
|
|
1012
|
+
ab += ai * bi, a2 += square(ai), b2 += square(bi);
|
|
1013
|
+
}
|
|
1013
1014
|
|
|
1014
1015
|
result_t result_if_zero[2][2];
|
|
1015
1016
|
result_if_zero[0][0] = 1 - ab / (std::sqrt(a2) * std::sqrt(b2));
|
|
@@ -1036,8 +1037,11 @@ template <typename scalar_at = float, typename result_at = scalar_at> struct met
|
|
|
1036
1037
|
#elif defined(USEARCH_DEFINED_GCC)
|
|
1037
1038
|
#pragma GCC ivdep
|
|
1038
1039
|
#endif
|
|
1039
|
-
for (std::size_t i = 0; i != dim; ++i)
|
|
1040
|
-
|
|
1040
|
+
for (std::size_t i = 0; i != dim; ++i) {
|
|
1041
|
+
result_t ai = static_cast<result_t>(a[i]);
|
|
1042
|
+
result_t bi = static_cast<result_t>(b[i]);
|
|
1043
|
+
ab_deltas_sq += square(ai - bi);
|
|
1044
|
+
}
|
|
1041
1045
|
return ab_deltas_sq;
|
|
1042
1046
|
}
|
|
1043
1047
|
};
|
|
@@ -1095,9 +1099,10 @@ template <typename scalar_at = std::uint64_t, typename result_at = float> struct
|
|
|
1095
1099
|
#elif defined(USEARCH_DEFINED_GCC)
|
|
1096
1100
|
#pragma GCC ivdep
|
|
1097
1101
|
#endif
|
|
1098
|
-
for (std::size_t i = 0; i != words; ++i)
|
|
1099
|
-
and_count += std::bitset<bits_per_word_k>(a[i] & b[i]).count()
|
|
1100
|
-
|
|
1102
|
+
for (std::size_t i = 0; i != words; ++i) {
|
|
1103
|
+
and_count += std::bitset<bits_per_word_k>(a[i] & b[i]).count();
|
|
1104
|
+
or_count += std::bitset<bits_per_word_k>(a[i] | b[i]).count();
|
|
1105
|
+
}
|
|
1101
1106
|
return 1 - result_t(and_count) / or_count;
|
|
1102
1107
|
}
|
|
1103
1108
|
};
|
|
@@ -1126,9 +1131,10 @@ template <typename scalar_at = std::uint64_t, typename result_at = float> struct
|
|
|
1126
1131
|
#elif defined(USEARCH_DEFINED_GCC)
|
|
1127
1132
|
#pragma GCC ivdep
|
|
1128
1133
|
#endif
|
|
1129
|
-
for (std::size_t i = 0; i != words; ++i)
|
|
1130
|
-
and_count += std::bitset<bits_per_word_k>(a[i] & b[i]).count()
|
|
1131
|
-
|
|
1134
|
+
for (std::size_t i = 0; i != words; ++i) {
|
|
1135
|
+
and_count += std::bitset<bits_per_word_k>(a[i] & b[i]).count();
|
|
1136
|
+
any_count += std::bitset<bits_per_word_k>(a[i]).count() + std::bitset<bits_per_word_k>(b[i]).count();
|
|
1137
|
+
}
|
|
1132
1138
|
return 1 - 2 * result_t(and_count) / any_count;
|
|
1133
1139
|
}
|
|
1134
1140
|
};
|
|
@@ -1159,13 +1165,20 @@ template <typename scalar_at = std::int32_t, typename result_at = float> struct
|
|
|
1159
1165
|
};
|
|
1160
1166
|
|
|
1161
1167
|
/**
|
|
1162
|
-
* @brief Measures Pearson Correlation between two sequences.
|
|
1168
|
+
* @brief Measures Pearson Correlation between two sequences in a single pass.
|
|
1163
1169
|
*/
|
|
1164
1170
|
template <typename scalar_at = float, typename result_at = float> struct metric_pearson_gt {
|
|
1165
1171
|
using scalar_t = scalar_at;
|
|
1166
1172
|
using result_t = result_at;
|
|
1167
1173
|
|
|
1168
1174
|
inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t dim) const noexcept {
|
|
1175
|
+
// The correlation coefficient can't be defined for one or zero-dimensional data.
|
|
1176
|
+
if (dim <= 1)
|
|
1177
|
+
return 0;
|
|
1178
|
+
// Conventional Pearson Correlation Coefficient definiton subtracts the mean value of each
|
|
1179
|
+
// sequence from each element, before dividing them. WikiPedia article suggests a convenient
|
|
1180
|
+
// single-pass algorithm for calculating sample correlations, though depending on the numbers
|
|
1181
|
+
// involved, it can sometimes be numerically unstable.
|
|
1169
1182
|
result_t a_sum{}, b_sum{}, ab_sum{};
|
|
1170
1183
|
result_t a_sq_sum{}, b_sq_sum{};
|
|
1171
1184
|
#if USEARCH_USE_OPENMP
|
|
@@ -1176,15 +1189,20 @@ template <typename scalar_at = float, typename result_at = float> struct metric_
|
|
|
1176
1189
|
#pragma GCC ivdep
|
|
1177
1190
|
#endif
|
|
1178
1191
|
for (std::size_t i = 0; i != dim; ++i) {
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1192
|
+
result_t ai = static_cast<result_t>(a[i]);
|
|
1193
|
+
result_t bi = static_cast<result_t>(b[i]);
|
|
1194
|
+
a_sum += ai;
|
|
1195
|
+
b_sum += bi;
|
|
1196
|
+
ab_sum += ai * bi;
|
|
1197
|
+
a_sq_sum += ai * ai;
|
|
1198
|
+
b_sq_sum += bi * bi;
|
|
1184
1199
|
}
|
|
1185
|
-
result_t denom =
|
|
1186
|
-
|
|
1187
|
-
|
|
1200
|
+
result_t denom = (dim * a_sq_sum - a_sum * a_sum) * (dim * b_sq_sum - b_sum * b_sum);
|
|
1201
|
+
if (denom == 0)
|
|
1202
|
+
return 0;
|
|
1203
|
+
result_t corr = dim * ab_sum - a_sum * b_sum;
|
|
1204
|
+
denom = std::sqrt(denom);
|
|
1205
|
+
return -corr / denom;
|
|
1188
1206
|
}
|
|
1189
1207
|
};
|
|
1190
1208
|
|
|
@@ -1197,7 +1215,7 @@ template <typename scalar_at = float, typename result_at = float> struct metric_
|
|
|
1197
1215
|
|
|
1198
1216
|
inline result_t operator()(scalar_t const* p, scalar_t const* q, std::size_t dim) const noexcept {
|
|
1199
1217
|
result_t kld_pm{}, kld_qm{};
|
|
1200
|
-
|
|
1218
|
+
result_t epsilon = std::numeric_limits<result_t>::epsilon();
|
|
1201
1219
|
#if USEARCH_USE_OPENMP
|
|
1202
1220
|
#pragma omp simd reduction(+ : kld_pm, kld_qm)
|
|
1203
1221
|
#elif defined(USEARCH_DEFINED_CLANG)
|
|
@@ -1206,9 +1224,11 @@ template <typename scalar_at = float, typename result_at = float> struct metric_
|
|
|
1206
1224
|
#pragma GCC ivdep
|
|
1207
1225
|
#endif
|
|
1208
1226
|
for (std::size_t i = 0; i != dim; ++i) {
|
|
1209
|
-
result_t
|
|
1210
|
-
|
|
1211
|
-
|
|
1227
|
+
result_t pi = static_cast<result_t>(p[i]);
|
|
1228
|
+
result_t qi = static_cast<result_t>(q[i]);
|
|
1229
|
+
result_t mi = (pi + qi) / 2 + epsilon;
|
|
1230
|
+
kld_pm += pi * std::log((pi + epsilon) / mi);
|
|
1231
|
+
kld_qm += qi * std::log((qi + epsilon) / mi);
|
|
1212
1232
|
}
|
|
1213
1233
|
return (kld_pm + kld_qm) / 2;
|
|
1214
1234
|
}
|
|
@@ -1253,7 +1273,7 @@ struct l2sq_i8_t {
|
|
|
1253
1273
|
#endif
|
|
1254
1274
|
for (std::size_t i = 0; i != dim; i++)
|
|
1255
1275
|
ab_deltas_sq += square(std::int16_t(a[i]) - std::int16_t(b[i]));
|
|
1256
|
-
return ab_deltas_sq;
|
|
1276
|
+
return static_cast<result_t>(ab_deltas_sq);
|
|
1257
1277
|
}
|
|
1258
1278
|
};
|
|
1259
1279
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usearch.d.ts","sourceRoot":"","sources":["../usearch.js"],"names":[],"mappings":"AAsIA;IAEI;;;;;;;;;;;OAWG;IACH;oBATkC,MAAM;gBAAU,UAAU;sBAAiC,UAAU;sBAAiC,MAAM;uBAAqB,MAAM;0BAAwB,MAAM;eAAa,OAAO;gBAChN,UAAU,iBACV,UAAU,iBACV,MAAM,kBACN,MAAM,qBACN,MAAM,UACN,OAAO,
|
|
1
|
+
{"version":3,"file":"usearch.d.ts","sourceRoot":"","sources":["../usearch.js"],"names":[],"mappings":"AAsIA;IAEI;;;;;;;;;;;OAWG;IACH;oBATkC,MAAM;gBAAU,UAAU;sBAAiC,UAAU;sBAAiC,MAAM;uBAAqB,MAAM;0BAAwB,MAAM;eAAa,OAAO;gBAChN,UAAU,iBACV,UAAU,iBACV,MAAM,kBACN,MAAM,qBACN,MAAM,UACN,OAAO,EAmCjB;IADG,oBAAwI;IAG5I;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,UAPW,MAAM,GAAC,MAAM,EAAE,GAAC,cAAc,WAE9B,YAAY,GAAC,YAAY,GAAC,SAAS,QAoB7C;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,gBANW,YAAY,GAAC,YAAY,GAAC,SAAS,GAAC,MAAM,MAAM,MAAM,CAAC,CAAC,KACxD,MAAM,GACL,OAAO,GAAC,YAAY,CAqB/B;IAED;;;;;;;;;OASG;IACH,eAJW,MAAM,GAAC,MAAM,EAAE,GAAC,cAAc,GAC7B,OAAO,GAAC,OAAO,EAAE,CAU5B;IAED;;;;;;OAMG;IACH,YAJW,MAAM,GAAC,MAAM,EAAE,GAAC,cAAc,GAC7B,MAAM,GAAC,MAAM,EAAE,CAU1B;IAED;;;;;;;;;OASG;IACH,aAJW,MAAM,GAAC,MAAM,EAAE,GAAC,cAAc,GAC7B,MAAM,GAAC,MAAM,EAAE,CAU1B;IAED;;;OAGG;IACH,cAFY,MAAM,CAEsC;IAExD;;;OAGG;IACH,gBAFY,MAAM,CAE0C;IAE5D;;;OAGG;IACH,QAFY,MAAM,CAE0B;IAE5C;;;OAGG;IACH,YAFY,MAAM,CAEkC;IAEpD;;;;OAIG;IACH,WAHW,MAAM,QAMhB;IAED;;;;OAIG;IACH,WAHW,MAAM,QAMhB;IAED;;;;OAIG;IACH,WAHW,MAAM,QAMhB;CACJ;;;;yBA5WS,MAAM;;;;;;;;;;;;;;;;;yBAmBN,MAAM;;;;;;;;;;AAYhB;;GAEG;AACH;IACI;;;;;OAKG;IACH,kBAHW,cAAc,aACd,YAAY,EAKtB;IAFG,qBAAgB;IAChB,wBAA0B;CAEjC;AAED;;GAEG;AACH;IACI;;;;;;;OAOG;IACH,kBALW,cAAc,aACd,YAAY,UACZ,cAAc,KACd,MAAM,EAOhB;IAJG,qBAAgB;IAChB,wBAA0B;IAC1B,uBAAoB;IACpB,UAAU;IAGd;;;;;OAKG;IACH,OAHW,MAAM,GACJ,OAAO,CAQnB;CACJ;AA+RD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qCA1BW,YAAY,GAAC,YAAY,GAAC,SAAS,GAAC,MAAM,MAAM,MAAM,CAAC,CAAC,WACxD,YAAY,GAAC,YAAY,GAAC,SAAS,GAAC,MAAM,MAAM,MAAM,CAAC,CAAC,cACxD,MAAM,SACN,MAAM,UACN,UAAU,GACT,OAAO,GAAC,YAAY,CA0D/B"}
|
package/javascript/usearch.js
CHANGED
|
@@ -158,7 +158,10 @@ class Index {
|
|
|
158
158
|
throw new Error("Invalid arguments. Expected either individual arguments or a single object argument.");
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
if (
|
|
161
|
+
if ((typeof dimensions !== 'bigint' && (!Number.isInteger(dimensions) || dimensions <= 0)) ||
|
|
162
|
+
(typeof connectivity !== 'bigint' && (!Number.isInteger(connectivity) || connectivity < 0)) ||
|
|
163
|
+
(typeof expansion_add !== 'bigint' && (!Number.isInteger(expansion_add) || expansion_add < 0)) ||
|
|
164
|
+
(typeof expansion_search !== 'bigint' && (!Number.isInteger(expansion_search) || expansion_search < 0))) {
|
|
162
165
|
throw new Error("`dimensions`, `connectivity`, `expansion_add`, and `expansion_search` must be non-negative integers, with `dimensions` being positive.");
|
|
163
166
|
}
|
|
164
167
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "usearch",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.2",
|
|
4
4
|
"description": "Smaller & Faster Single-File Vector Search Engine from Unum",
|
|
5
5
|
"author": "Ash Vardanian (https://ashvardanian.com/)",
|
|
6
6
|
"license": "Apache 2.0",
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"exports": {
|
|
35
35
|
".": {
|
|
36
36
|
"types": "./javascript/types/usearch.d.ts",
|
|
37
|
-
"require": "./javascript/usearch.js"
|
|
37
|
+
"require": "./javascript/usearch.js",
|
|
38
|
+
"import": "./javascript/usearch.js"
|
|
38
39
|
},
|
|
39
40
|
"./package.json": "./package.json"
|
|
40
41
|
},
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## C and C++
|
|
4
|
+
|
|
5
|
+
To rerun experiments utilize the following command:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
cmake -DCMAKE_BUILD_TYPE=Release -DSIMSIMD_BUILD_BENCHMARKS=1 -B ./build_release
|
|
9
|
+
cmake --build build_release --config Release
|
|
10
|
+
./build_release/simsimd_bench
|
|
11
|
+
./build_release/simsimd_bench --benchmark_filter=js
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Python
|
|
15
|
+
|
|
16
|
+
Testing:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
pip install -e . # to install the package in editable mode
|
|
20
|
+
pytest python/test.py -s -x -Wd # to run tests
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Here, `-s` will output the logs.
|
|
24
|
+
The `-x` will stop on the first failure.
|
|
25
|
+
The `-Wd` will silence overflows and runtime warnings.
|
|
26
|
+
|
|
27
|
+
Benchmarking:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
pip install numpy scipy scikit-learn # for comparison baselines
|
|
31
|
+
python python/bench.py # to run default benchmarks
|
|
32
|
+
python python/bench.py --n 1000 --ndim 1000000 # batch size and dimensions
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Before merging your changes you may want to test your changes against the entire matrix of Python versions USearch supports.
|
|
36
|
+
For that you need the `cibuildwheel`, which is tricky to use on MacOS and Windows, as it would target just the local environment.
|
|
37
|
+
Still, if you have Docker running on any desktop OS, you can use it to build and test the Python bindings for all Python versions for Linux:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
pip install cibuildwheel
|
|
41
|
+
cibuildwheel
|
|
42
|
+
cibuildwheel --platform linux # works on any OS and builds all Linux backends
|
|
43
|
+
cibuildwheel --platform linux --archs x86_64 # 64-bit x86, the most common on desktop and servers
|
|
44
|
+
cibuildwheel --platform linux --archs aarch64 # 64-bit Arm for mobile devices, Apple M-series, and AWS Graviton
|
|
45
|
+
cibuildwheel --platform linux --archs i686 # 32-bit Linux
|
|
46
|
+
cibuildwheel --platform linux --archs s390x # emulating big-endian IBM Z
|
|
47
|
+
cibuildwheel --platform macos # works only on MacOS
|
|
48
|
+
cibuildwheel --platform windows # works only on Windows
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
You may need root previligies for multi-architecture builds:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
sudo $(which cibuildwheel) --platform linux
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
On Windows and MacOS, to avoid frequent path resolution issues, you may want to use:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
python -m cibuildwheel --platform windows
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Rust
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
cargo test
|
|
67
|
+
cargo bench
|
|
68
|
+
open ./target/criterion/report/index.html
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## JavaScript
|
|
72
|
+
|
|
73
|
+
Testing and benchmarking:
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
npm install --dev
|
|
77
|
+
npm test
|
|
78
|
+
npm run bench
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## GoLang
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
cd golang
|
|
85
|
+
go test # To test
|
|
86
|
+
go test -run=^$ -bench=. -benchmem # To benchmark
|
|
87
|
+
```
|
|
88
|
+
|
package/simsimd/Cargo.lock
CHANGED
|
@@ -210,9 +210,9 @@ dependencies = [
|
|
|
210
210
|
|
|
211
211
|
[[package]]
|
|
212
212
|
name = "half"
|
|
213
|
-
version = "2.
|
|
213
|
+
version = "2.4.0"
|
|
214
214
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
215
|
-
checksum = "
|
|
215
|
+
checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e"
|
|
216
216
|
dependencies = [
|
|
217
217
|
"cfg-if",
|
|
218
218
|
"crunchy",
|
|
@@ -496,10 +496,11 @@ dependencies = [
|
|
|
496
496
|
|
|
497
497
|
[[package]]
|
|
498
498
|
name = "simsimd"
|
|
499
|
-
version = "3.
|
|
499
|
+
version = "3.8.1"
|
|
500
500
|
dependencies = [
|
|
501
501
|
"cc",
|
|
502
502
|
"criterion",
|
|
503
|
+
"half",
|
|
503
504
|
"rand",
|
|
504
505
|
]
|
|
505
506
|
|
package/simsimd/Cargo.toml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "simsimd"
|
|
3
3
|
description = "Fastest SIMD-Accelerated Vector Similarity Functions for x86 and Arm"
|
|
4
|
-
version = "3.
|
|
4
|
+
version = "3.9.0"
|
|
5
5
|
edition = "2021"
|
|
6
6
|
license = "Apache-2.0"
|
|
7
7
|
authors = [
|
|
@@ -12,7 +12,13 @@ repository = "https://github.com/ashvardanian/SimSIMD"
|
|
|
12
12
|
documentation = "https://docs.rs/simsimd"
|
|
13
13
|
homepage = "https://ashvardanian.com/posts/simsimd-faster-scipy"
|
|
14
14
|
keywords = ["simd", "search", "linear-algebra", "vector"]
|
|
15
|
-
categories = [
|
|
15
|
+
categories = [
|
|
16
|
+
"mathematics",
|
|
17
|
+
"hardware-support",
|
|
18
|
+
"no-std",
|
|
19
|
+
"wasm",
|
|
20
|
+
"external-ffi-bindings",
|
|
21
|
+
]
|
|
16
22
|
|
|
17
23
|
[lib]
|
|
18
24
|
name = "simsimd"
|
|
@@ -21,6 +27,7 @@ path = "rust/lib.rs"
|
|
|
21
27
|
[build-dependencies]
|
|
22
28
|
cc = "1.0.83"
|
|
23
29
|
|
|
30
|
+
|
|
24
31
|
[[bench]]
|
|
25
32
|
name = "cosine"
|
|
26
33
|
harness = false
|
|
@@ -34,3 +41,4 @@ path = "rust/benches/sqeuclidean.rs"
|
|
|
34
41
|
[dev-dependencies]
|
|
35
42
|
criterion = { version = "0.5.1" }
|
|
36
43
|
rand = { version = "0.8.5" }
|
|
44
|
+
half = { version = "2.4.0" }
|
package/simsimd/README.md
CHANGED
|
@@ -1,20 +1,41 @@
|
|
|
1
1
|
# SimSIMD 📏
|
|
2
2
|
|
|
3
3
|
<div>
|
|
4
|
-
<a href="https://pepy.tech/project/simsimd">
|
|
5
|
-
<
|
|
6
|
-
|
|
4
|
+
<a href="https://pepy.tech/project/simsimd">
|
|
5
|
+
<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">
|
|
6
|
+
</a>
|
|
7
|
+
<a href="https://www.npmjs.com/package/simsimd">
|
|
8
|
+
<img alt="npm" src="https://img.shields.io/npm/dy/simsimd?label=npm%20dowloads">
|
|
9
|
+
</a>
|
|
10
|
+
<a href="https://crates.io/crates/simsimd">
|
|
11
|
+
<img alt="rust" src="https://img.shields.io/crates/d/simsimd?logo=rust" />
|
|
12
|
+
</a>
|
|
7
13
|
<img alt="GitHub code size in bytes" src="https://img.shields.io/github/languages/code-size/ashvardanian/simsimd">
|
|
14
|
+
<a href="https://github.com/ashvardanian/SimSIMD/actions/workflows/release.yml">
|
|
15
|
+
<img alt="GitHub Actions Ubuntu" src="https://img.shields.io/github/actions/workflow/status/ashvardanian/SimSIMD/release.yml?branch=main&label=Ubuntu&logo=github&color=blue">
|
|
16
|
+
</a>
|
|
17
|
+
<a href="https://github.com/ashvardanian/SimSIMD/actions/workflows/release.yml">
|
|
18
|
+
<img alt="GitHub Actions Windows" src="https://img.shields.io/github/actions/workflow/status/ashvardanian/SimSIMD/release.yml?branch=main&label=Windows&logo=windows&color=blue">
|
|
19
|
+
</a>
|
|
20
|
+
<a href="https://github.com/ashvardanian/SimSIMD/actions/workflows/release.yml">
|
|
21
|
+
<img alt="GitHub Actions MacOS" src="https://img.shields.io/github/actions/workflow/status/ashvardanian/SimSIMD/release.yml?branch=main&label=MacOS&logo=apple&color=blue">
|
|
22
|
+
</a>
|
|
23
|
+
<a href="https://github.com/ashvardanian/SimSIMD/actions/workflows/release.yml">
|
|
24
|
+
<img alt="GitHub Actions CentOS Linux" src="https://img.shields.io/github/actions/workflow/status/ashvardanian/SimSIMD/release.yml?branch=main&label=CentOS&logo=centos&color=blue">
|
|
25
|
+
</a>
|
|
26
|
+
|
|
8
27
|
</div>
|
|
9
28
|
|
|
29
|
+
|
|
10
30
|
## Hardware-Accelerated Similarity Metrics and Distance Functions
|
|
11
31
|
|
|
12
32
|
- 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).
|
|
14
|
-
- Targets ARM NEON, SVE, x86 AVX2, AVX-512 (VNNI, FP16) hardware backends.
|
|
15
|
-
- Zero-copy compatible with NumPy, PyTorch, TensorFlow, and other tensors.
|
|
16
33
|
- Handles `f64` double-, `f32` single-, and `f16` half-precision, `i8` integral, and binary vectors.
|
|
34
|
+
- Targets ARM NEON, SVE, x86 AVX2, AVX-512 (VNNI, FP16) hardware backends.
|
|
35
|
+
- Bindings for [Python](#using-simsimd-in-python), [Rust](#using-simsimd-in-rust) and [JavaScript](#using-simsimd-in-javascript).
|
|
17
36
|
- __Up to 200x faster__ than [`scipy.spatial.distance`][scipy] and [`numpy.inner`][numpy].
|
|
37
|
+
- [Supports more Python versions][compatibility] than SciPy and NumPy.
|
|
38
|
+
- Zero-copy compatible with NumPy, PyTorch, TensorFlow, and other tensors.
|
|
18
39
|
- Used in [USearch](https://github.com/unum-cloud/usearch) and several DBMS products.
|
|
19
40
|
|
|
20
41
|
__Implemented distance functions__ include:
|
|
@@ -25,6 +46,7 @@ __Implemented distance functions__ include:
|
|
|
25
46
|
|
|
26
47
|
[scipy]: https://docs.scipy.org/doc/scipy/reference/spatial.distance.html#module-scipy.spatial.distance
|
|
27
48
|
[numpy]: https://numpy.org/doc/stable/reference/generated/numpy.inner.html
|
|
49
|
+
[compatibility]: https://pypi.org/project/simsimd/#files
|
|
28
50
|
|
|
29
51
|
__Technical Insights__ and related articles:
|
|
30
52
|
|
|
@@ -67,6 +89,11 @@ __Broader Benchmarking Results__:
|
|
|
67
89
|
|
|
68
90
|
## Using SimSIMD in Python
|
|
69
91
|
|
|
92
|
+
The package is intended to replace the usage of `numpy.inner`, `numpy.dot`, and `scipy.spatial.distance`.
|
|
93
|
+
Aside from drastic performance improvements, SimSIMD significantly improves accuracy in mixed precision setups.
|
|
94
|
+
NumPy and SciPy, processing `i8` or `f16` vectors, will use the same types for accumulators, while SimSIMD can combine `i8` enumeration, `i16` multiplication, and `i32` accumulation to entirely avoid overflows.
|
|
95
|
+
The same applies to processing `f16` values with `f32` precision.
|
|
96
|
+
|
|
70
97
|
### Installation
|
|
71
98
|
|
|
72
99
|
```sh
|
|
@@ -85,6 +112,14 @@ dist = simsimd.cosine(vec1, vec2)
|
|
|
85
112
|
```
|
|
86
113
|
|
|
87
114
|
Supported functions include `cosine`, `inner`, `sqeuclidean`, `hamming`, and `jaccard`.
|
|
115
|
+
Unlike SciPy, SimSIMD allows explicitly stating the precision of the input vectors, which is especially useful for mixed-precision setups.
|
|
116
|
+
|
|
117
|
+
```py
|
|
118
|
+
dist = simsimd.cosine(vec1, vec2, "i8")
|
|
119
|
+
dist = simsimd.cosine(vec1, vec2, "f16")
|
|
120
|
+
dist = simsimd.cosine(vec1, vec2, "f32")
|
|
121
|
+
dist = simsimd.cosine(vec1, vec2, "f64")
|
|
122
|
+
```
|
|
88
123
|
|
|
89
124
|
### Distance Between 2 Batches
|
|
90
125
|
|
|
@@ -150,20 +185,73 @@ To install, add the following to your `Cargo.toml`:
|
|
|
150
185
|
simsimd = "..."
|
|
151
186
|
```
|
|
152
187
|
|
|
153
|
-
|
|
188
|
+
Before using the SimSIMD library, ensure you have imported the necessary traits and types into your Rust source file.
|
|
189
|
+
The library provides several traits for different distance/similarity kinds - `SpatialSimilarity`, `BinarySimilarity`, and `ProbabilitySimilarity`.
|
|
154
190
|
|
|
155
191
|
```rust
|
|
156
|
-
use simsimd::
|
|
192
|
+
use simsimd::SpatialSimilarity;
|
|
157
193
|
|
|
158
194
|
fn main() {
|
|
159
|
-
let vector_a = vec![1.0, 2.0, 3.0];
|
|
160
|
-
let vector_b = vec![4.0, 5.0, 6.0];
|
|
195
|
+
let vector_a: Vec<f32> = vec![1.0, 2.0, 3.0];
|
|
196
|
+
let vector_b: Vec<f32> = vec![4.0, 5.0, 6.0];
|
|
161
197
|
|
|
162
|
-
|
|
163
|
-
|
|
198
|
+
// Compute the cosine similarity between vector_a and vector_b
|
|
199
|
+
let cosine_similarity = f32::cosine(&vector_a, &vector_b)
|
|
200
|
+
.expect("Vectors must be of the same length");
|
|
164
201
|
|
|
165
|
-
|
|
166
|
-
|
|
202
|
+
println!("Cosine Similarity: {}", cosine_similarity);
|
|
203
|
+
|
|
204
|
+
// Compute the squared Euclidean distance between vector_a and vector_b
|
|
205
|
+
let sq_euclidean_distance = f32::sqeuclidean(&vector_a, &vector_b)
|
|
206
|
+
.expect("Vectors must be of the same length");
|
|
207
|
+
|
|
208
|
+
println!("Squared Euclidean Distance: {}", sq_euclidean_distance);
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Similarly, one can compute bit-level distance functions between slices of unsigned integers:
|
|
213
|
+
|
|
214
|
+
```rust
|
|
215
|
+
use simsimd::BinarySimilarity;
|
|
216
|
+
|
|
217
|
+
fn main() {
|
|
218
|
+
let vector_a = &[0b11110000, 0b00001111, 0b10101010];
|
|
219
|
+
let vector_b = &[0b11110000, 0b00001111, 0b01010101];
|
|
220
|
+
|
|
221
|
+
// Compute the Hamming distance between vector_a and vector_b
|
|
222
|
+
let hamming_distance = u8::hamming(&vector_a, &vector_b)
|
|
223
|
+
.expect("Vectors must be of the same length");
|
|
224
|
+
|
|
225
|
+
println!("Hamming Distance: {}", hamming_distance);
|
|
226
|
+
|
|
227
|
+
// Compute the Jaccard distance between vector_a and vector_b
|
|
228
|
+
let jaccard_distance = u8::jaccard(&vector_a, &vector_b)
|
|
229
|
+
.expect("Vectors must be of the same length");
|
|
230
|
+
|
|
231
|
+
println!("Jaccard Distance: {}", jaccard_distance);
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Rust has no native support for half-precision floating-point numbers, but SimSIMD provides a `f16` type for this purpose.
|
|
236
|
+
It doesn't have any functionality and is a `transparent` wrapper around `u16`, so it can be used with `half`, or any other half-precision library.
|
|
237
|
+
|
|
238
|
+
```rust
|
|
239
|
+
use simsimd::SpatialSimilarity;
|
|
240
|
+
use simsimd::f16 as SimF16;
|
|
241
|
+
use half::f16 as HalfF16;
|
|
242
|
+
|
|
243
|
+
fn main() {
|
|
244
|
+
let vector_a: Vec<HalfF16> = ...
|
|
245
|
+
let vector_b: Vec<HalfF16> = ...
|
|
246
|
+
|
|
247
|
+
let buffer_a: &[SimF16] = unsafe { std::slice::from_raw_parts(a_half.as_ptr() as *const SimF16, a_half.len()) };
|
|
248
|
+
let buffer_b: &[SimF16] = unsafe { std::slice::from_raw_parts(b_half.as_ptr() as *const SimF16, b_half.len()) };
|
|
249
|
+
|
|
250
|
+
// Compute the cosine similarity between vector_a and vector_b
|
|
251
|
+
let cosine_similarity = SimF16::cosine(&vector_a, &vector_b)
|
|
252
|
+
.expect("Vectors must be of the same length");
|
|
253
|
+
|
|
254
|
+
println!("Cosine Similarity: {}", cosine_similarity);
|
|
167
255
|
}
|
|
168
256
|
```
|
|
169
257
|
|
|
@@ -227,49 +315,3 @@ All of the functions names follow the same pattern: `simsimd_{backend}_{type}_{m
|
|
|
227
315
|
- The metric can be `cos`, `ip`, `l2sq`, `hamming`, `jaccard`, `kl`, or `js`.
|
|
228
316
|
|
|
229
317
|
In case you want to avoid hard-coding the backend, you can use the `simsimd_metric_punned_t` to pun the function pointer, and `simsimd_capabilities` function to get the available backends at runtime.
|
|
230
|
-
|
|
231
|
-
## Benchmarking and Contributing
|
|
232
|
-
|
|
233
|
-
__To rerun experiments__ utilize the following command:
|
|
234
|
-
|
|
235
|
-
```sh
|
|
236
|
-
cmake -DCMAKE_BUILD_TYPE=Release -DSIMSIMD_BUILD_BENCHMARKS=1 -B ./build_release
|
|
237
|
-
cmake --build build_release --config Release
|
|
238
|
-
./build_release/simsimd_bench
|
|
239
|
-
./build_release/simsimd_bench --benchmark_filter=js
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
__To test and benchmark with Python bindings__:
|
|
243
|
-
|
|
244
|
-
```sh
|
|
245
|
-
pip install -e .
|
|
246
|
-
pytest python/test.py -s -x
|
|
247
|
-
|
|
248
|
-
pip install numpy scipy scikit-learn # for comparison baselines
|
|
249
|
-
python python/bench.py # to run default benchmarks
|
|
250
|
-
python python/bench.py --n 1000 --ndim 1000000 # batch size and dimensions
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
__To test and benchmark JavaScript bindings__:
|
|
254
|
-
|
|
255
|
-
```sh
|
|
256
|
-
npm install --dev
|
|
257
|
-
npm test
|
|
258
|
-
npm run bench
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
__To test and benchmark GoLang bindings__:
|
|
262
|
-
|
|
263
|
-
```sh
|
|
264
|
-
cd golang
|
|
265
|
-
go test # To test
|
|
266
|
-
go test -run=^$ -bench=. -benchmem # To benchmark
|
|
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
|
-
```
|
|
@@ -190,6 +190,50 @@ simsimd_avx512_b8_jaccard_cycle:
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
#endif // SIMSIMD_TARGET_X86_AVX512
|
|
193
|
+
|
|
194
|
+
/*
|
|
195
|
+
* @file x86_avx2_b8.h
|
|
196
|
+
* @brief x86 AVX2 implementation of the most common similarity metrics for bitsets.
|
|
197
|
+
* @author Ash Vardanian
|
|
198
|
+
*
|
|
199
|
+
* - Implements: Hamming and Jaccard similarity.
|
|
200
|
+
* - Requires compiler capabilities: avx2.
|
|
201
|
+
*
|
|
202
|
+
* AVX2 provides no built-in functionality for popcount, so we use the BMI2 instruction set.
|
|
203
|
+
* Wojciech Mu la, Nathan Kurz and Daniel Lemire propose an alternative solution based on Harley-Seal
|
|
204
|
+
* transforms and lookup tables. Those are very fast on large vectors, but on short vectors they lose
|
|
205
|
+
* to built-in scalar population counts. https://arxiv.org/pdf/1611.07612.pdf
|
|
206
|
+
*/
|
|
207
|
+
|
|
208
|
+
#if SIMSIMD_TARGET_X86_AVX2
|
|
209
|
+
|
|
210
|
+
__attribute__((target("popcnt"))) //
|
|
211
|
+
inline static simsimd_f32_t
|
|
212
|
+
simsimd_avx2_b8_hamming(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
|
|
213
|
+
// x86 supports unaligned loads and works just fine with the scalar version for small vectors.
|
|
214
|
+
simsimd_size_t differences = 0;
|
|
215
|
+
for (; n_words >= 8; n_words -= 8, a += 8, b += 8)
|
|
216
|
+
differences += _mm_popcnt_u64(*(simsimd_u64_t const*)a ^ *(simsimd_u64_t const*)b);
|
|
217
|
+
for (; n_words; --n_words, ++a, ++b)
|
|
218
|
+
differences += _mm_popcnt_u32(*a ^ *b);
|
|
219
|
+
return (simsimd_f32_t)differences;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
__attribute__((target("popcnt"))) //
|
|
223
|
+
inline static simsimd_f32_t
|
|
224
|
+
simsimd_avx2_b8_jaccard(simsimd_b8_t const* a, simsimd_b8_t const* b, simsimd_size_t n_words) {
|
|
225
|
+
// x86 supports unaligned loads and works just fine with the scalar version for small vectors.
|
|
226
|
+
simsimd_size_t intersection = 0, union_ = 0;
|
|
227
|
+
for (; n_words >= 8; n_words -= 8, a += 8, b += 8)
|
|
228
|
+
intersection += _mm_popcnt_u64(*(simsimd_u64_t const*)a & *(simsimd_u64_t const*)b),
|
|
229
|
+
union_ += _mm_popcnt_u64(*(simsimd_u64_t const*)a | *(simsimd_u64_t const*)b);
|
|
230
|
+
for (; n_words; --n_words, ++a, ++b)
|
|
231
|
+
intersection += _mm_popcnt_u32(*a & *b), union_ += _mm_popcnt_u32(*a | *b);
|
|
232
|
+
return (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ : 0;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
#endif // SIMSIMD_TARGET_X86_AVX2
|
|
236
|
+
|
|
193
237
|
#endif // SIMSIMD_TARGET_X86
|
|
194
238
|
|
|
195
239
|
#undef popcount64
|