usearch 2.13.3 → 2.13.5
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/README.md +11 -3
- package/c/usearch.h +11 -0
- package/include/usearch/index.hpp +1 -1
- package/include/usearch/index_dense.hpp +11 -6
- package/include/usearch/index_plugins.hpp +101 -62
- package/package.json +1 -1
- package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
- package/prebuilds/linux-arm64/usearch.node +0 -0
- package/prebuilds/linux-x64/usearch.node +0 -0
- package/simsimd/include/simsimd/binary.h +5 -5
- package/simsimd/include/simsimd/dot.h +16 -0
- package/simsimd/include/simsimd/simsimd.h +16 -0
- package/simsimd/include/simsimd/spatial.h +63 -8
package/README.md
CHANGED
|
@@ -254,7 +254,7 @@ assert!(
|
|
|
254
254
|
Training a quantization model and dimension-reduction is a common approach to accelerate vector search.
|
|
255
255
|
Those, however, are only sometimes reliable, can significantly affect the statistical properties of your data, and require regular adjustments if your distribution shifts.
|
|
256
256
|
Instead, we have focused on high-precision arithmetic over low-precision downcasted vectors.
|
|
257
|
-
The same index, and `add` and `search` operations will automatically down-cast or up-cast between `f64_t`, `f32_t`, `f16_t`, `i8_t`, and single-bit representations.
|
|
257
|
+
The same index, and `add` and `search` operations will automatically down-cast or up-cast between `f64_t`, `f32_t`, `f16_t`, `i8_t`, and single-bit `b1x8_t` representations.
|
|
258
258
|
You can use the following command to check, if hardware acceleration is enabled:
|
|
259
259
|
|
|
260
260
|
```sh
|
|
@@ -264,11 +264,19 @@ $ python -c 'from usearch.index import Index; print(Index(ndim=166, metric="tani
|
|
|
264
264
|
> ice
|
|
265
265
|
```
|
|
266
266
|
|
|
267
|
+
In most cases, it's recommended to use half-precision floating-point numbers on modern hardware.
|
|
268
|
+
When quantization is enabled, the "get"-like functions won't be able to recover the original data, so you may want to replicate the original vectors elsewhere.
|
|
269
|
+
When quantizing to `i8_t` integers, note that it's only valid for cosine-like metrics.
|
|
270
|
+
As part of the quantization process, the vectors are normalized to unit length and later scaled to [-127, 127] range to occupy the full 8-bit range.
|
|
271
|
+
When quantizing to `b1x8_t` single-bit representations, note that it's only valid for binary metrics like Jaccard, Hamming, etc.
|
|
272
|
+
As part of the quantization process, the scalar components greater than zero are set to `true`, and the rest to `false`.
|
|
273
|
+
|
|
274
|
+

|
|
275
|
+
|
|
267
276
|
Using smaller numeric types will save you RAM needed to store the vectors, but you can also compress the neighbors lists forming our proximity graphs.
|
|
268
277
|
By default, 32-bit `uint32_t` is used to enumerate those, which is not enough if you need to address over 4 Billion entries.
|
|
269
278
|
For such cases we provide a custom `uint40_t` type, that will still be 37.5% more space-efficient than the commonly used 8-byte integers, and will scale up to 1 Trillion entries.
|
|
270
279
|
|
|
271
|
-

|
|
272
280
|
|
|
273
281
|
## `Indexes` for Multi-Index Lookups
|
|
274
282
|
|
|
@@ -541,7 +549,7 @@ doi = {10.5281/zenodo.7949416},
|
|
|
541
549
|
author = {Vardanian, Ash},
|
|
542
550
|
title = {{USearch by Unum Cloud}},
|
|
543
551
|
url = {https://github.com/unum-cloud/usearch},
|
|
544
|
-
version = {2.13.
|
|
552
|
+
version = {2.13.5},
|
|
545
553
|
year = {2023},
|
|
546
554
|
month = oct,
|
|
547
555
|
}
|
package/c/usearch.h
CHANGED
|
@@ -72,6 +72,17 @@ USEARCH_EXPORT typedef struct usearch_init_options_t {
|
|
|
72
72
|
usearch_metric_t metric;
|
|
73
73
|
/**
|
|
74
74
|
* @brief The scalar kind used for quantization of vector data during indexing.
|
|
75
|
+
* In most cases, on modern hardware, it's recommended to use half-precision floating-point numbers.
|
|
76
|
+
* When quantization is enabled, the "get"-like functions won't be able to recover the original data,
|
|
77
|
+
* so you may want to replicate the original vectors elsewhere.
|
|
78
|
+
*
|
|
79
|
+
* Quantizing to integers is also possible, but it's important to note that it's only valid for cosine-like
|
|
80
|
+
* metrics. As part of the quantization process, the vectors are normalized to unit length and later scaled
|
|
81
|
+
* to @b [-127,127] range to occupy the full 8-bit range.
|
|
82
|
+
*
|
|
83
|
+
* Quantizing to 1-bit booleans is also possible, but it's only valid for binary metrics like Jaccard, Hamming,
|
|
84
|
+
* etc. As part of the quantization process, the scalar components greater than zero are set to `true`, and the
|
|
85
|
+
* rest to `false`.
|
|
75
86
|
*/
|
|
76
87
|
usearch_scalar_kind_t quantization;
|
|
77
88
|
/**
|
|
@@ -95,7 +95,7 @@ struct index_dense_head_result_t {
|
|
|
95
95
|
* @brief Configuration settings for the construction of dense
|
|
96
96
|
* equidimensional vector indexes.
|
|
97
97
|
*
|
|
98
|
-
* Unlike the underlying `index_gt` class,
|
|
98
|
+
* Unlike the underlying `index_gt` class, incorporates the
|
|
99
99
|
* `::expansion_add` and `::expansion_search` parameters passed
|
|
100
100
|
* separately for the lower-level engine.
|
|
101
101
|
*/
|
|
@@ -207,8 +207,11 @@ struct index_dense_metadata_result_t {
|
|
|
207
207
|
}
|
|
208
208
|
};
|
|
209
209
|
|
|
210
|
+
/**
|
|
211
|
+
* @brief Fixes serialized scalar-kind codes for pre-v2.10 versions, until we can upgrade to v3.
|
|
212
|
+
* The old enum `scalar_kind_t` is defined without explicit constants from 0.
|
|
213
|
+
*/
|
|
210
214
|
inline scalar_kind_t convert_pre_2_10_scalar_kind(scalar_kind_t scalar_kind) noexcept {
|
|
211
|
-
// The old enum `scalar_kind_t` is defined without explicit constants from 0
|
|
212
215
|
switch (static_cast<std::underlying_type<scalar_kind_t>::type>(scalar_kind)) {
|
|
213
216
|
case 0: return scalar_kind_t::unknown_k;
|
|
214
217
|
case 1: return scalar_kind_t::b1x8_k;
|
|
@@ -229,13 +232,15 @@ inline scalar_kind_t convert_pre_2_10_scalar_kind(scalar_kind_t scalar_kind) noe
|
|
|
229
232
|
}
|
|
230
233
|
}
|
|
231
234
|
|
|
235
|
+
/**
|
|
236
|
+
* @brief Fixes the metadata for pre-v2.10 versions, until we can upgrade to v3.
|
|
237
|
+
* Originates from: https://github.com/unum-cloud/usearch/issues/423
|
|
238
|
+
*/
|
|
232
239
|
inline void fix_pre_2_10_metadata(index_dense_head_t& head) {
|
|
233
|
-
// deal with incompatibility with pre-2.10 versions: https://github.com/unum-cloud/usearch/issues/423
|
|
234
240
|
if (head.version_major == 2 && head.version_minor < 10) {
|
|
235
241
|
head.kind_scalar = convert_pre_2_10_scalar_kind(head.kind_scalar);
|
|
236
242
|
head.kind_key = convert_pre_2_10_scalar_kind(head.kind_key);
|
|
237
243
|
head.kind_compressed_slot = convert_pre_2_10_scalar_kind(head.kind_compressed_slot);
|
|
238
|
-
// update minor versions so that no repeated conversion occurs
|
|
239
244
|
head.version_minor = 10;
|
|
240
245
|
head.version_patch = 0;
|
|
241
246
|
}
|
|
@@ -316,7 +321,7 @@ inline index_dense_metadata_result_t index_dense_metadata_from_path(char const*
|
|
|
316
321
|
/**
|
|
317
322
|
* @brief Extracts metadata from a pre-constructed index serialized into an in-memory buffer.
|
|
318
323
|
*/
|
|
319
|
-
inline index_dense_metadata_result_t index_dense_metadata_from_buffer(memory_mapped_file_t file,
|
|
324
|
+
inline index_dense_metadata_result_t index_dense_metadata_from_buffer(memory_mapped_file_t const& file,
|
|
320
325
|
std::size_t offset = 0) noexcept {
|
|
321
326
|
index_dense_metadata_result_t result;
|
|
322
327
|
|
|
@@ -324,7 +329,7 @@ inline index_dense_metadata_result_t index_dense_metadata_from_buffer(memory_map
|
|
|
324
329
|
if (offset + sizeof(index_dense_head_buffer_t) >= file.size())
|
|
325
330
|
return result.failed("End of file reached!");
|
|
326
331
|
|
|
327
|
-
byte_t
|
|
332
|
+
byte_t const* file_data = file.data() + offset;
|
|
328
333
|
std::size_t const file_size = file.size() - offset;
|
|
329
334
|
std::memcpy(&result.head_buffer, file_data, sizeof(index_dense_head_buffer_t));
|
|
330
335
|
|
|
@@ -70,7 +70,6 @@ struct uuid_t {
|
|
|
70
70
|
};
|
|
71
71
|
|
|
72
72
|
class f16_bits_t;
|
|
73
|
-
class i8_converted_t;
|
|
74
73
|
|
|
75
74
|
#if !USEARCH_USE_FP16LIB
|
|
76
75
|
#if USEARCH_USE_SIMSIMD
|
|
@@ -99,6 +98,9 @@ using i32_t = std::int32_t;
|
|
|
99
98
|
using i16_t = std::int16_t;
|
|
100
99
|
using i8_t = std::int8_t;
|
|
101
100
|
|
|
101
|
+
/**
|
|
102
|
+
* @brief Enumerates the most commonly used distance metrics, mostly for dense vector representations.
|
|
103
|
+
*/
|
|
102
104
|
enum class metric_kind_t : std::uint8_t {
|
|
103
105
|
unknown_k = 0,
|
|
104
106
|
// Classics:
|
|
@@ -111,13 +113,19 @@ enum class metric_kind_t : std::uint8_t {
|
|
|
111
113
|
haversine_k = 'h',
|
|
112
114
|
divergence_k = 'd',
|
|
113
115
|
|
|
114
|
-
// Sets:
|
|
115
|
-
jaccard_k = 'j',
|
|
116
|
+
// Dense Sets:
|
|
116
117
|
hamming_k = 'b',
|
|
117
118
|
tanimoto_k = 't',
|
|
118
119
|
sorensen_k = 's',
|
|
120
|
+
|
|
121
|
+
// Sparse Sets:
|
|
122
|
+
jaccard_k = 'j',
|
|
119
123
|
};
|
|
120
124
|
|
|
125
|
+
/**
|
|
126
|
+
* @brief Enumerates the most commonly used scalar types, mostly for dense vector representations.
|
|
127
|
+
* Doesn't include logical types, like complex numbers or quaternions.
|
|
128
|
+
*/
|
|
121
129
|
enum class scalar_kind_t : std::uint8_t {
|
|
122
130
|
unknown_k = 0,
|
|
123
131
|
// Custom:
|
|
@@ -198,33 +206,63 @@ inline bool str_equals(char const* begin, std::size_t len, char const* other_beg
|
|
|
198
206
|
|
|
199
207
|
inline std::size_t bits_per_scalar(scalar_kind_t scalar_kind) noexcept {
|
|
200
208
|
switch (scalar_kind) {
|
|
209
|
+
case scalar_kind_t::uuid_k: return 128;
|
|
210
|
+
case scalar_kind_t::u40_k: return 40;
|
|
211
|
+
case scalar_kind_t::b1x8_k: return 1;
|
|
212
|
+
case scalar_kind_t::u64_k: return 64;
|
|
213
|
+
case scalar_kind_t::i64_k: return 64;
|
|
201
214
|
case scalar_kind_t::f64_k: return 64;
|
|
215
|
+
case scalar_kind_t::u32_k: return 32;
|
|
216
|
+
case scalar_kind_t::i32_k: return 32;
|
|
202
217
|
case scalar_kind_t::f32_k: return 32;
|
|
218
|
+
case scalar_kind_t::u16_k: return 16;
|
|
219
|
+
case scalar_kind_t::i16_k: return 16;
|
|
203
220
|
case scalar_kind_t::f16_k: return 16;
|
|
221
|
+
case scalar_kind_t::u8_k: return 8;
|
|
204
222
|
case scalar_kind_t::i8_k: return 8;
|
|
205
|
-
case scalar_kind_t::
|
|
223
|
+
case scalar_kind_t::f8_k: return 8;
|
|
206
224
|
default: return 0;
|
|
207
225
|
}
|
|
208
226
|
}
|
|
209
227
|
|
|
210
228
|
inline std::size_t bits_per_scalar_word(scalar_kind_t scalar_kind) noexcept {
|
|
211
229
|
switch (scalar_kind) {
|
|
230
|
+
case scalar_kind_t::uuid_k: return 128;
|
|
231
|
+
case scalar_kind_t::u40_k: return 40;
|
|
232
|
+
case scalar_kind_t::b1x8_k: return 8;
|
|
233
|
+
case scalar_kind_t::u64_k: return 64;
|
|
234
|
+
case scalar_kind_t::i64_k: return 64;
|
|
212
235
|
case scalar_kind_t::f64_k: return 64;
|
|
236
|
+
case scalar_kind_t::u32_k: return 32;
|
|
237
|
+
case scalar_kind_t::i32_k: return 32;
|
|
213
238
|
case scalar_kind_t::f32_k: return 32;
|
|
239
|
+
case scalar_kind_t::u16_k: return 16;
|
|
240
|
+
case scalar_kind_t::i16_k: return 16;
|
|
214
241
|
case scalar_kind_t::f16_k: return 16;
|
|
242
|
+
case scalar_kind_t::u8_k: return 8;
|
|
215
243
|
case scalar_kind_t::i8_k: return 8;
|
|
216
|
-
case scalar_kind_t::
|
|
244
|
+
case scalar_kind_t::f8_k: return 8;
|
|
217
245
|
default: return 0;
|
|
218
246
|
}
|
|
219
247
|
}
|
|
220
248
|
|
|
221
249
|
inline char const* scalar_kind_name(scalar_kind_t scalar_kind) noexcept {
|
|
222
250
|
switch (scalar_kind) {
|
|
251
|
+
case scalar_kind_t::uuid_k: return "uuid";
|
|
252
|
+
case scalar_kind_t::u40_k: return "u40";
|
|
253
|
+
case scalar_kind_t::b1x8_k: return "b1x8";
|
|
254
|
+
case scalar_kind_t::u64_k: return "u64";
|
|
255
|
+
case scalar_kind_t::i64_k: return "i64";
|
|
256
|
+
case scalar_kind_t::f64_k: return "f64";
|
|
257
|
+
case scalar_kind_t::u32_k: return "u32";
|
|
258
|
+
case scalar_kind_t::i32_k: return "i32";
|
|
223
259
|
case scalar_kind_t::f32_k: return "f32";
|
|
260
|
+
case scalar_kind_t::u16_k: return "u16";
|
|
261
|
+
case scalar_kind_t::i16_k: return "i16";
|
|
224
262
|
case scalar_kind_t::f16_k: return "f16";
|
|
225
|
-
case scalar_kind_t::
|
|
263
|
+
case scalar_kind_t::u8_k: return "u8";
|
|
226
264
|
case scalar_kind_t::i8_k: return "i8";
|
|
227
|
-
case scalar_kind_t::
|
|
265
|
+
case scalar_kind_t::f8_k: return "f8";
|
|
228
266
|
default: return "";
|
|
229
267
|
}
|
|
230
268
|
}
|
|
@@ -242,8 +280,8 @@ inline char const* metric_kind_name(metric_kind_t metric) noexcept {
|
|
|
242
280
|
case metric_kind_t::hamming_k: return "hamming";
|
|
243
281
|
case metric_kind_t::tanimoto_k: return "tanimoto";
|
|
244
282
|
case metric_kind_t::sorensen_k: return "sorensen";
|
|
283
|
+
default: return "";
|
|
245
284
|
}
|
|
246
|
-
return "";
|
|
247
285
|
}
|
|
248
286
|
inline expected_gt<scalar_kind_t> scalar_kind_from_name(char const* name, std::size_t len) {
|
|
249
287
|
expected_gt<scalar_kind_t> parsed;
|
|
@@ -333,8 +371,7 @@ class f16_bits_t {
|
|
|
333
371
|
inline operator float() const noexcept { return f16_to_f32(uint16_); }
|
|
334
372
|
inline explicit operator bool() const noexcept { return f16_to_f32(uint16_) > 0.5f; }
|
|
335
373
|
|
|
336
|
-
inline f16_bits_t(
|
|
337
|
-
inline f16_bits_t(std::int8_t v) noexcept : uint16_(v) {}
|
|
374
|
+
inline f16_bits_t(std::int8_t v) noexcept : uint16_(f32_to_f16(v)) {}
|
|
338
375
|
inline f16_bits_t(bool v) noexcept : uint16_(f32_to_f16(v)) {}
|
|
339
376
|
inline f16_bits_t(float v) noexcept : uint16_(f32_to_f16(v)) {}
|
|
340
377
|
inline f16_bits_t(double v) noexcept : uint16_(f32_to_f16(static_cast<float>(v))) {}
|
|
@@ -345,14 +382,14 @@ class f16_bits_t {
|
|
|
345
382
|
inline f16_bits_t operator-(f16_bits_t other) const noexcept { return {float(*this) - float(other)}; }
|
|
346
383
|
inline f16_bits_t operator*(f16_bits_t other) const noexcept { return {float(*this) * float(other)}; }
|
|
347
384
|
inline f16_bits_t operator/(f16_bits_t other) const noexcept { return {float(*this) / float(other)}; }
|
|
348
|
-
inline
|
|
349
|
-
inline
|
|
350
|
-
inline
|
|
351
|
-
inline
|
|
352
|
-
inline
|
|
353
|
-
inline
|
|
354
|
-
inline
|
|
355
|
-
inline
|
|
385
|
+
inline float operator+(float other) const noexcept { return float(*this) + other; }
|
|
386
|
+
inline float operator-(float other) const noexcept { return float(*this) - other; }
|
|
387
|
+
inline float operator*(float other) const noexcept { return float(*this) * other; }
|
|
388
|
+
inline float operator/(float other) const noexcept { return float(*this) / other; }
|
|
389
|
+
inline double operator+(double other) const noexcept { return float(*this) + other; }
|
|
390
|
+
inline double operator-(double other) const noexcept { return float(*this) - other; }
|
|
391
|
+
inline double operator*(double other) const noexcept { return float(*this) * other; }
|
|
392
|
+
inline double operator/(double other) const noexcept { return float(*this) / other; }
|
|
356
393
|
|
|
357
394
|
inline f16_bits_t& operator+=(float v) noexcept {
|
|
358
395
|
uint16_ = f32_to_f16(v + f16_to_f32(uint16_));
|
|
@@ -907,7 +944,7 @@ template <> struct cast_gt<b1x8_t, b1x8_t> {
|
|
|
907
944
|
static bool try_(byte_t const*, std::size_t, byte_t*) noexcept { return false; }
|
|
908
945
|
};
|
|
909
946
|
|
|
910
|
-
template <typename from_scalar_at> struct
|
|
947
|
+
template <typename from_scalar_at> struct cast_to_b1x8_gt {
|
|
911
948
|
inline static bool try_(byte_t const* input, std::size_t dim, byte_t* output) noexcept {
|
|
912
949
|
from_scalar_at const* typed_input = reinterpret_cast<from_scalar_at const*>(input);
|
|
913
950
|
unsigned char* typed_output = reinterpret_cast<unsigned char*>(output);
|
|
@@ -928,7 +965,7 @@ template <typename from_scalar_at> struct cast_gt<from_scalar_at, b1x8_t> {
|
|
|
928
965
|
}
|
|
929
966
|
};
|
|
930
967
|
|
|
931
|
-
template <typename to_scalar_at> struct
|
|
968
|
+
template <typename to_scalar_at> struct cast_from_b1x8_gt {
|
|
932
969
|
static bool try_(byte_t const* input, std::size_t dim, byte_t* output) noexcept {
|
|
933
970
|
unsigned char const* typed_input = reinterpret_cast<unsigned char const*>(input);
|
|
934
971
|
to_scalar_at* typed_output = reinterpret_cast<to_scalar_at*>(output);
|
|
@@ -940,53 +977,55 @@ template <typename to_scalar_at> struct cast_gt<b1x8_t, to_scalar_at> {
|
|
|
940
977
|
}
|
|
941
978
|
};
|
|
942
979
|
|
|
943
|
-
|
|
944
|
-
*
|
|
945
|
-
*
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
980
|
+
template <typename from_scalar_at> struct cast_to_i8_gt {
|
|
981
|
+
inline static bool try_(byte_t const* input, std::size_t dim, byte_t* output) noexcept {
|
|
982
|
+
from_scalar_at const* typed_input = reinterpret_cast<from_scalar_at const*>(input);
|
|
983
|
+
std::int8_t* typed_output = reinterpret_cast<std::int8_t*>(output);
|
|
984
|
+
// Unlike other casting mechanisms, switching to small range integers is a two step procedure.
|
|
985
|
+
// First we want to estimate the magnitude of the vector to scale into [-1.0, 1.0] interval,
|
|
986
|
+
// instead of clamping. And then we scale the values into the [-127, 127] range.
|
|
987
|
+
// ! This makes an assumption, that the distance metric is dot-product-like, which may not
|
|
988
|
+
// ! be true in many cases, so it's recommended to avoid automatic casting from floats to
|
|
989
|
+
// ! integers.
|
|
990
|
+
double magnitude = 0.0;
|
|
991
|
+
for (std::size_t i = 0; i != dim; ++i)
|
|
992
|
+
magnitude += (double)typed_input[i] * (double)typed_input[i];
|
|
993
|
+
magnitude = std::sqrt(magnitude);
|
|
994
|
+
for (std::size_t i = 0; i != dim; ++i)
|
|
995
|
+
typed_output[i] =
|
|
996
|
+
static_cast<std::int8_t>(usearch::clamp<double>(typed_input[i] * 127.0 / magnitude, -127.0, 127.0));
|
|
997
|
+
return true;
|
|
998
|
+
}
|
|
999
|
+
};
|
|
949
1000
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
inline i8_converted_t(i8_converted_t&&) = default;
|
|
959
|
-
inline i8_converted_t& operator=(i8_converted_t&&) = default;
|
|
960
|
-
inline i8_converted_t(i8_converted_t const&) = default;
|
|
961
|
-
inline i8_converted_t& operator=(i8_converted_t const&) = default;
|
|
962
|
-
|
|
963
|
-
inline operator f16_t() const noexcept { return static_cast<f16_t>(f32_t(int8_) / divisor_k); }
|
|
964
|
-
inline operator f32_t() const noexcept { return f32_t(int8_) / divisor_k; }
|
|
965
|
-
inline operator f64_t() const noexcept { return f64_t(int8_) / divisor_k; }
|
|
966
|
-
inline explicit operator bool() const noexcept { return int8_ > (max_k / 2); }
|
|
967
|
-
inline explicit operator std::int8_t() const noexcept { return int8_; }
|
|
968
|
-
inline explicit operator std::int16_t() const noexcept { return int8_; }
|
|
969
|
-
inline explicit operator std::int32_t() const noexcept { return int8_; }
|
|
970
|
-
inline explicit operator std::int64_t() const noexcept { return int8_; }
|
|
971
|
-
|
|
972
|
-
// Even with `f16_t` arguments we may want to use `f32_t` for clamping and comparions.
|
|
973
|
-
inline i8_converted_t(f16_t v)
|
|
974
|
-
: int8_(static_cast<std::int8_t>(usearch::clamp<f32_t>(v * divisor_k, min_k, max_k))) {}
|
|
975
|
-
inline i8_converted_t(f32_t v)
|
|
976
|
-
: int8_(static_cast<std::int8_t>(usearch::clamp<f32_t>(v * divisor_k, min_k, max_k))) {}
|
|
977
|
-
inline i8_converted_t(f64_t v)
|
|
978
|
-
: int8_(static_cast<std::int8_t>(usearch::clamp<f64_t>(v * divisor_k, min_k, max_k))) {}
|
|
1001
|
+
template <typename to_scalar_at> struct cast_from_i8_gt {
|
|
1002
|
+
static bool try_(byte_t const* input, std::size_t dim, byte_t* output) noexcept {
|
|
1003
|
+
std::int8_t const* typed_input = reinterpret_cast<std::int8_t const*>(input);
|
|
1004
|
+
to_scalar_at* typed_output = reinterpret_cast<to_scalar_at*>(output);
|
|
1005
|
+
for (std::size_t i = 0; i != dim; ++i)
|
|
1006
|
+
typed_output[i] = static_cast<to_scalar_at>(typed_input[i]) / 127.f;
|
|
1007
|
+
return true;
|
|
1008
|
+
}
|
|
979
1009
|
};
|
|
980
1010
|
|
|
981
|
-
|
|
1011
|
+
template <> struct cast_gt<i8_t, f16_t> : public cast_from_i8_gt<f16_t> {};
|
|
1012
|
+
template <> struct cast_gt<i8_t, f32_t> : public cast_from_i8_gt<f32_t> {};
|
|
1013
|
+
template <> struct cast_gt<i8_t, f64_t> : public cast_from_i8_gt<f64_t> {};
|
|
1014
|
+
|
|
1015
|
+
template <> struct cast_gt<f16_t, i8_t> : public cast_to_i8_gt<f16_t> {};
|
|
1016
|
+
template <> struct cast_gt<f32_t, i8_t> : public cast_to_i8_gt<f32_t> {};
|
|
1017
|
+
template <> struct cast_gt<f64_t, i8_t> : public cast_to_i8_gt<f64_t> {};
|
|
1018
|
+
|
|
1019
|
+
template <> struct cast_gt<b1x8_t, f16_t> : public cast_from_b1x8_gt<f16_t> {};
|
|
1020
|
+
template <> struct cast_gt<b1x8_t, f32_t> : public cast_from_b1x8_gt<f32_t> {};
|
|
1021
|
+
template <> struct cast_gt<b1x8_t, f64_t> : public cast_from_b1x8_gt<f64_t> {};
|
|
982
1022
|
|
|
983
|
-
template <> struct cast_gt<
|
|
984
|
-
template <> struct cast_gt<
|
|
985
|
-
template <> struct cast_gt<
|
|
1023
|
+
template <> struct cast_gt<f16_t, b1x8_t> : public cast_to_b1x8_gt<f16_t> {};
|
|
1024
|
+
template <> struct cast_gt<f32_t, b1x8_t> : public cast_to_b1x8_gt<f32_t> {};
|
|
1025
|
+
template <> struct cast_gt<f64_t, b1x8_t> : public cast_to_b1x8_gt<f64_t> {};
|
|
986
1026
|
|
|
987
|
-
template <> struct cast_gt<
|
|
988
|
-
template <> struct cast_gt<
|
|
989
|
-
template <> struct cast_gt<f64_t, i8_t> : public cast_gt<f64_t, i8_converted_t> {};
|
|
1027
|
+
template <> struct cast_gt<b1x8_t, i8_t> : public cast_from_b1x8_gt<i8_t> {};
|
|
1028
|
+
template <> struct cast_gt<i8_t, b1x8_t> : public cast_to_b1x8_gt<i8_t> {};
|
|
990
1029
|
|
|
991
1030
|
/* Don't complain if the vectorization of the inner loops fails:
|
|
992
1031
|
*
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -73,7 +73,7 @@ SIMSIMD_PUBLIC void simsimd_jaccard_b8_serial(simsimd_b8_t const* a, simsimd_b8_
|
|
|
73
73
|
simsimd_i32_t intersection = 0, union_ = 0;
|
|
74
74
|
for (simsimd_size_t i = 0; i != n_words; ++i)
|
|
75
75
|
intersection += simsimd_popcount_b8(a[i] & b[i]), union_ += simsimd_popcount_b8(a[i] | b[i]);
|
|
76
|
-
*result = (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ :
|
|
76
|
+
*result = (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ : 1;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
#if SIMSIMD_TARGET_ARM
|
|
@@ -110,7 +110,7 @@ SIMSIMD_PUBLIC void simsimd_jaccard_b8_neon(simsimd_b8_t const* a, simsimd_b8_t
|
|
|
110
110
|
// Handle the tail
|
|
111
111
|
for (; i != n_words; ++i)
|
|
112
112
|
intersection += simsimd_popcount_b8(a[i] & b[i]), union_ += simsimd_popcount_b8(a[i] | b[i]);
|
|
113
|
-
*result = (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ :
|
|
113
|
+
*result = (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ : 1;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
#pragma clang attribute pop
|
|
@@ -148,7 +148,7 @@ SIMSIMD_PUBLIC void simsimd_jaccard_b8_sve(simsimd_b8_t const* a, simsimd_b8_t c
|
|
|
148
148
|
union_ += svaddv_u8(svptrue_b8(), svcnt_u8_x(svptrue_b8(), svorr_u8_m(svptrue_b8(), a_vec, b_vec)));
|
|
149
149
|
i += svcntb();
|
|
150
150
|
} while (i < n_words);
|
|
151
|
-
*result = (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ :
|
|
151
|
+
*result = (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ : 1;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
#pragma clang attribute pop
|
|
@@ -213,7 +213,7 @@ simsimd_jaccard_b8_ice_cycle:
|
|
|
213
213
|
|
|
214
214
|
simsimd_size_t intersection = _mm512_reduce_add_epi64(intersection_vec),
|
|
215
215
|
union_ = _mm512_reduce_add_epi64(union_vec);
|
|
216
|
-
*result = (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ :
|
|
216
|
+
*result = (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ : 1;
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
#pragma clang attribute pop
|
|
@@ -245,7 +245,7 @@ SIMSIMD_PUBLIC void simsimd_jaccard_b8_haswell(simsimd_b8_t const* a, simsimd_b8
|
|
|
245
245
|
union_ += _mm_popcnt_u64(*(simsimd_u64_t const*)a | *(simsimd_u64_t const*)b);
|
|
246
246
|
for (; n_words; --n_words, ++a, ++b)
|
|
247
247
|
intersection += _mm_popcnt_u32(*a & *b), union_ += _mm_popcnt_u32(*a | *b);
|
|
248
|
-
*result = (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ :
|
|
248
|
+
*result = (union_ != 0) ? 1 - (simsimd_f32_t)intersection / (simsimd_f32_t)union_ : 1;
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
#pragma clang attribute pop
|
|
@@ -858,6 +858,22 @@ inline simsimd_f64_t _mm256_reduce_add_ps_dbl(__m256 vec) {
|
|
|
858
858
|
return _mm_cvtsd_f64(sum128);
|
|
859
859
|
}
|
|
860
860
|
|
|
861
|
+
SIMSIMD_PUBLIC void simsimd_dot_f32_haswell(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n,
|
|
862
|
+
simsimd_distance_t* results) {
|
|
863
|
+
|
|
864
|
+
__m256 ab_vec = _mm256_setzero_ps();
|
|
865
|
+
simsimd_size_t i = 0;
|
|
866
|
+
for (; i + 8 <= n; i += 8) {
|
|
867
|
+
__m256 a_vec = _mm256_loadu_ps(a + i);
|
|
868
|
+
__m256 b_vec = _mm256_loadu_ps(b + i);
|
|
869
|
+
ab_vec = _mm256_fmadd_ps(a_vec, b_vec, ab_vec);
|
|
870
|
+
}
|
|
871
|
+
simsimd_f64_t ab = _mm256_reduce_add_ps_dbl(ab_vec);
|
|
872
|
+
for (; i < n; ++i)
|
|
873
|
+
ab += a[i] * b[i];
|
|
874
|
+
*results = ab;
|
|
875
|
+
}
|
|
876
|
+
|
|
861
877
|
SIMSIMD_PUBLIC void simsimd_dot_f32c_haswell(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n,
|
|
862
878
|
simsimd_distance_t* results) {
|
|
863
879
|
|
|
@@ -527,6 +527,16 @@ SIMSIMD_PUBLIC void simsimd_find_metric_punned( //
|
|
|
527
527
|
default: break;
|
|
528
528
|
}
|
|
529
529
|
#endif
|
|
530
|
+
#if SIMSIMD_TARGET_HASWELL
|
|
531
|
+
if (viable & simsimd_cap_haswell_k)
|
|
532
|
+
switch (kind) {
|
|
533
|
+
case simsimd_metric_dot_k: *m = (m_t)&simsimd_dot_f32_haswell, *c = simsimd_cap_haswell_k; return;
|
|
534
|
+
case simsimd_metric_cos_k: *m = (m_t)&simsimd_cos_f32_haswell, *c = simsimd_cap_haswell_k; return;
|
|
535
|
+
case simsimd_metric_l2sq_k: *m = (m_t)&simsimd_l2sq_f32_haswell, *c = simsimd_cap_haswell_k; return;
|
|
536
|
+
default: break;
|
|
537
|
+
}
|
|
538
|
+
#endif
|
|
539
|
+
|
|
530
540
|
if (viable & simsimd_cap_serial_k)
|
|
531
541
|
switch (kind) {
|
|
532
542
|
case simsimd_metric_dot_k: *m = (m_t)&simsimd_dot_f32_serial, *c = simsimd_cap_serial_k; return;
|
|
@@ -1120,6 +1130,8 @@ SIMSIMD_PUBLIC void simsimd_dot_f32(simsimd_f32_t const* a, simsimd_f32_t const*
|
|
|
1120
1130
|
simsimd_dot_f32_neon(a, b, n, d);
|
|
1121
1131
|
#elif SIMSIMD_TARGET_SKYLAKE
|
|
1122
1132
|
simsimd_dot_f32_skylake(a, b, n, d);
|
|
1133
|
+
#elif SIMSIMD_TARGET_HASWELL
|
|
1134
|
+
simsimd_dot_f32_haswell(a, b, n, d);
|
|
1123
1135
|
#else
|
|
1124
1136
|
simsimd_dot_f32_serial(a, b, n, d);
|
|
1125
1137
|
#endif
|
|
@@ -1292,6 +1304,8 @@ SIMSIMD_PUBLIC void simsimd_cos_f32(simsimd_f32_t const* a, simsimd_f32_t const*
|
|
|
1292
1304
|
simsimd_cos_f32_neon(a, b, n, d);
|
|
1293
1305
|
#elif SIMSIMD_TARGET_SKYLAKE
|
|
1294
1306
|
simsimd_cos_f32_skylake(a, b, n, d);
|
|
1307
|
+
#elif SIMSIMD_TARGET_HASWELL
|
|
1308
|
+
simsimd_cos_f32_haswell(a, b, n, d);
|
|
1295
1309
|
#else
|
|
1296
1310
|
simsimd_cos_f32_serial(a, b, n, d);
|
|
1297
1311
|
#endif
|
|
@@ -1338,6 +1352,8 @@ SIMSIMD_PUBLIC void simsimd_l2sq_f32(simsimd_f32_t const* a, simsimd_f32_t const
|
|
|
1338
1352
|
simsimd_l2sq_f32_neon(a, b, n, d);
|
|
1339
1353
|
#elif SIMSIMD_TARGET_SKYLAKE
|
|
1340
1354
|
simsimd_l2sq_f32_skylake(a, b, n, d);
|
|
1355
|
+
#elif SIMSIMD_TARGET_HASWELL
|
|
1356
|
+
simsimd_l2sq_f32_haswell(a, b, n, d);
|
|
1341
1357
|
#else
|
|
1342
1358
|
simsimd_l2sq_f32_serial(a, b, n, d);
|
|
1343
1359
|
#endif
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
|
|
28
28
|
#include "types.h"
|
|
29
29
|
|
|
30
|
+
#include "dot.h" // `_mm256_reduce_add_ps_dbl`
|
|
31
|
+
|
|
30
32
|
#ifdef __cplusplus
|
|
31
33
|
extern "C" {
|
|
32
34
|
#endif
|
|
@@ -93,6 +95,8 @@ SIMSIMD_PUBLIC void simsimd_l2sq_f16_haswell(simsimd_f16_t const* a, simsimd_f16
|
|
|
93
95
|
SIMSIMD_PUBLIC void simsimd_cos_f16_haswell(simsimd_f16_t const* a, simsimd_f16_t const* b, simsimd_size_t n, simsimd_distance_t*);
|
|
94
96
|
SIMSIMD_PUBLIC void simsimd_l2sq_bf16_haswell(simsimd_bf16_t const* a, simsimd_bf16_t const* b, simsimd_size_t n, simsimd_distance_t*);
|
|
95
97
|
SIMSIMD_PUBLIC void simsimd_cos_bf16_haswell(simsimd_bf16_t const* a, simsimd_bf16_t const* b, simsimd_size_t n, simsimd_distance_t*);
|
|
98
|
+
SIMSIMD_PUBLIC void simsimd_l2sq_f32_haswell(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n, simsimd_distance_t*);
|
|
99
|
+
SIMSIMD_PUBLIC void simsimd_cos_f32_haswell(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n, simsimd_distance_t*);
|
|
96
100
|
|
|
97
101
|
/* SIMD-powered backends for AVX512 CPUs of Skylake generation and newer, using 32-bit arithmetic over 512-bit words.
|
|
98
102
|
* Skylake was launched in 2015, and discontinued in 2019. Skylake had support for F, CD, VL, DQ, and BW extensions,
|
|
@@ -839,8 +843,7 @@ SIMSIMD_PUBLIC void simsimd_cos_f16_haswell(simsimd_f16_t const* a, simsimd_f16_
|
|
|
839
843
|
__m128 b2_sqrt_recip = _mm_rsqrt_ss(_mm_set_ss((float)b2));
|
|
840
844
|
__m128 result_vec = _mm_mul_ss(a2_sqrt_recip, b2_sqrt_recip); // Multiply the reciprocal square roots
|
|
841
845
|
result_vec = _mm_mul_ss(result_vec, _mm_set_ss((float)ab)); // Multiply by ab
|
|
842
|
-
|
|
843
|
-
*result = ab != 0 ? _mm_cvtss_f32(result_vec) : 1; // Extract the final result
|
|
846
|
+
*result = ab != 0 ? 1 - _mm_cvtss_f32(result_vec) : 1; // Extract the final result
|
|
844
847
|
}
|
|
845
848
|
|
|
846
849
|
SIMSIMD_PUBLIC void simsimd_l2sq_bf16_haswell(simsimd_bf16_t const* a, simsimd_bf16_t const* b, simsimd_size_t n,
|
|
@@ -946,8 +949,7 @@ SIMSIMD_PUBLIC void simsimd_cos_bf16_haswell(simsimd_bf16_t const* a, simsimd_bf
|
|
|
946
949
|
__m128 b2_sqrt_recip = _mm_rsqrt_ss(_mm_set_ss((float)b2));
|
|
947
950
|
__m128 result_vec = _mm_mul_ss(a2_sqrt_recip, b2_sqrt_recip); // Multiply the reciprocal square roots
|
|
948
951
|
result_vec = _mm_mul_ss(result_vec, _mm_set_ss((float)ab)); // Multiply by ab
|
|
949
|
-
|
|
950
|
-
*result = ab != 0 ? _mm_cvtss_f32(result_vec) : 1; // Extract the final result
|
|
952
|
+
*result = ab != 0 ? 1 - _mm_cvtss_f32(result_vec) : 1; // Extract the final result
|
|
951
953
|
}
|
|
952
954
|
|
|
953
955
|
SIMSIMD_PUBLIC void simsimd_l2sq_i8_haswell(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t n,
|
|
@@ -1058,7 +1060,60 @@ SIMSIMD_PUBLIC void simsimd_cos_i8_haswell(simsimd_i8_t const* a, simsimd_i8_t c
|
|
|
1058
1060
|
// Compute cosine similarity: ab / sqrt(a2 * b2)
|
|
1059
1061
|
__m128 denom = _mm_mul_ss(a2_sqrt_recip, b2_sqrt_recip); // Reciprocal of sqrt(a2 * b2)
|
|
1060
1062
|
__m128 result_vec = _mm_mul_ss(_mm_set_ss((float)ab), denom); // ab * reciprocal of sqrt(a2 * b2)
|
|
1061
|
-
*result = ab != 0 ? 1 - _mm_cvtss_f32(result_vec) :
|
|
1063
|
+
*result = ab != 0 ? 1 - _mm_cvtss_f32(result_vec) : 1; // Extract the final result
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
SIMSIMD_PUBLIC void simsimd_l2sq_f32_haswell(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n,
|
|
1067
|
+
simsimd_distance_t* result) {
|
|
1068
|
+
|
|
1069
|
+
__m256 d2_vec = _mm256_setzero_ps();
|
|
1070
|
+
simsimd_size_t i = 0;
|
|
1071
|
+
for (; i + 8 <= n; i += 8) {
|
|
1072
|
+
__m256 a_vec = _mm256_loadu_ps(a + i);
|
|
1073
|
+
__m256 b_vec = _mm256_loadu_ps(b + i);
|
|
1074
|
+
__m256 d_vec = _mm256_sub_ps(a_vec, b_vec);
|
|
1075
|
+
d2_vec = _mm256_fmadd_ps(d_vec, d_vec, d2_vec);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
simsimd_f64_t d2 = _mm256_reduce_add_ps_dbl(d2_vec);
|
|
1079
|
+
for (; i < n; ++i) {
|
|
1080
|
+
float d = a[i] - b[i];
|
|
1081
|
+
d2 += d * d;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
*result = d2;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
SIMSIMD_PUBLIC void simsimd_cos_f32_haswell(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size_t n,
|
|
1088
|
+
simsimd_distance_t* result) {
|
|
1089
|
+
|
|
1090
|
+
__m256 ab_vec = _mm256_setzero_ps();
|
|
1091
|
+
__m256 a2_vec = _mm256_setzero_ps();
|
|
1092
|
+
__m256 b2_vec = _mm256_setzero_ps();
|
|
1093
|
+
simsimd_size_t i = 0;
|
|
1094
|
+
for (; i + 8 <= n; i += 8) {
|
|
1095
|
+
__m256 a_vec = _mm256_loadu_ps(a + i);
|
|
1096
|
+
__m256 b_vec = _mm256_loadu_ps(b + i);
|
|
1097
|
+
ab_vec = _mm256_fmadd_ps(a_vec, b_vec, ab_vec);
|
|
1098
|
+
a2_vec = _mm256_fmadd_ps(a_vec, a_vec, a2_vec);
|
|
1099
|
+
b2_vec = _mm256_fmadd_ps(b_vec, b_vec, b2_vec);
|
|
1100
|
+
}
|
|
1101
|
+
simsimd_f64_t ab = _mm256_reduce_add_ps_dbl(ab_vec);
|
|
1102
|
+
simsimd_f64_t a2 = _mm256_reduce_add_ps_dbl(a2_vec);
|
|
1103
|
+
simsimd_f64_t b2 = _mm256_reduce_add_ps_dbl(b2_vec);
|
|
1104
|
+
for (; i < n; ++i) {
|
|
1105
|
+
float ai = a[i], bi = b[i];
|
|
1106
|
+
ab += ai * bi, a2 += ai * ai, b2 += bi * bi;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// Compute the reciprocal of the square roots
|
|
1110
|
+
__m128 a2_sqrt_recip = _mm_rsqrt_ss(_mm_set_ss((float)a2));
|
|
1111
|
+
__m128 b2_sqrt_recip = _mm_rsqrt_ss(_mm_set_ss((float)b2));
|
|
1112
|
+
|
|
1113
|
+
// Compute cosine similarity: ab / sqrt(a2 * b2)
|
|
1114
|
+
__m128 denom = _mm_mul_ss(a2_sqrt_recip, b2_sqrt_recip); // Reciprocal of sqrt(a2 * b2)
|
|
1115
|
+
__m128 result_vec = _mm_mul_ss(_mm_set_ss((float)ab), denom); // ab * reciprocal of sqrt(a2 * b2)
|
|
1116
|
+
*result = ab != 0 ? 1 - _mm_cvtss_f32(result_vec) : 1; // Extract the final result
|
|
1062
1117
|
}
|
|
1063
1118
|
|
|
1064
1119
|
#pragma clang attribute pop
|
|
@@ -1282,7 +1337,7 @@ simsimd_cos_bf16_genoa_cycle:
|
|
|
1282
1337
|
__m128 rsqrts = _mm_rsqrt14_ps(_mm_set_ps(0.f, 0.f, a2 + 1.e-9f, b2 + 1.e-9f));
|
|
1283
1338
|
simsimd_f32_t rsqrt_a2 = _mm_cvtss_f32(rsqrts);
|
|
1284
1339
|
simsimd_f32_t rsqrt_b2 = _mm_cvtss_f32(_mm_shuffle_ps(rsqrts, rsqrts, _MM_SHUFFLE(0, 0, 0, 1)));
|
|
1285
|
-
*result = ab != 0 ? 1 - ab * rsqrt_a2 * rsqrt_b2 :
|
|
1340
|
+
*result = ab != 0 ? 1 - ab * rsqrt_a2 * rsqrt_b2 : 1;
|
|
1286
1341
|
}
|
|
1287
1342
|
|
|
1288
1343
|
#pragma clang attribute pop
|
|
@@ -1350,7 +1405,7 @@ simsimd_cos_f16_sapphire_cycle:
|
|
|
1350
1405
|
__m128 rsqrts = _mm_rsqrt14_ps(_mm_set_ps(0.f, 0.f, a2 + 1.e-9f, b2 + 1.e-9f));
|
|
1351
1406
|
simsimd_f32_t rsqrt_a2 = _mm_cvtss_f32(rsqrts);
|
|
1352
1407
|
simsimd_f32_t rsqrt_b2 = _mm_cvtss_f32(_mm_shuffle_ps(rsqrts, rsqrts, _MM_SHUFFLE(0, 0, 0, 1)));
|
|
1353
|
-
*result = ab != 0 ? 1 - ab * rsqrt_a2 * rsqrt_b2 :
|
|
1408
|
+
*result = ab != 0 ? 1 - ab * rsqrt_a2 * rsqrt_b2 : 1;
|
|
1354
1409
|
}
|
|
1355
1410
|
|
|
1356
1411
|
#pragma clang attribute pop
|
|
@@ -1440,7 +1495,7 @@ simsimd_cos_i8_ice_cycle:
|
|
|
1440
1495
|
__m128 rsqrts = _mm_maskz_rsqrt14_ps(0xFF, _mm_set_ps(0.f, 0.f, a2 + 1.e-9f, b2 + 1.e-9f));
|
|
1441
1496
|
simsimd_f32_t rsqrt_a2 = _mm_cvtss_f32(rsqrts);
|
|
1442
1497
|
simsimd_f32_t rsqrt_b2 = _mm_cvtss_f32(_mm_shuffle_ps(rsqrts, rsqrts, _MM_SHUFFLE(0, 0, 0, 1)));
|
|
1443
|
-
*result = ab != 0 ? 1 - ab * rsqrt_a2 * rsqrt_b2 :
|
|
1498
|
+
*result = ab != 0 ? 1 - ab * rsqrt_a2 * rsqrt_b2 : 1;
|
|
1444
1499
|
}
|
|
1445
1500
|
|
|
1446
1501
|
#pragma clang attribute pop
|