usearch 2.17.0 → 2.19.4
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 +6 -2
- package/c/lib.cpp +5 -0
- package/c/module.modulemap +4 -0
- package/c/usearch.h +13 -7
- package/cpp/README.md +1 -1
- package/cpp/bench.cpp +1 -1
- package/cpp/test.cpp +110 -16
- package/include/usearch/index.hpp +48 -35
- package/include/usearch/index_dense.hpp +34 -19
- package/include/usearch/index_plugins.hpp +29 -7
- package/javascript/README.md +8 -6
- package/javascript/dist/cjs/usearch.d.ts +9 -4
- package/javascript/dist/cjs/usearch.js +23 -8
- package/javascript/dist/esm/usearch.d.ts +9 -4
- package/javascript/dist/esm/usearch.js +21 -7
- package/javascript/lib.cpp +137 -59
- package/javascript/usearch.test.js +359 -192
- package/javascript/usearch.ts +35 -11
- 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/CONTRIBUTING.md +23 -8
- package/simsimd/Cargo.lock +1 -1
- package/simsimd/Cargo.toml +1 -1
- package/simsimd/README.md +49 -9
- package/simsimd/build.rs +7 -3
- package/simsimd/c/lib.c +6 -1
- package/simsimd/include/simsimd/binary.h +3 -3
- package/simsimd/include/simsimd/dot.h +2 -2
- package/simsimd/include/simsimd/elementwise.h +26 -17
- package/simsimd/include/simsimd/simsimd.h +125 -49
- package/simsimd/include/simsimd/sparse.h +30 -29
- package/simsimd/include/simsimd/spatial.h +4 -3
- package/simsimd/include/simsimd/types.h +19 -8
- package/simsimd/javascript/lib.c +0 -1
- package/simsimd/package-lock.json +411 -362
- package/simsimd/package.json +9 -9
- package/simsimd/rust/lib.rs +63 -29
- package/simsimd/scripts/bench.cxx +13 -0
- package/simsimd/scripts/bench_cosine.rs +8 -0
- package/simsimd/scripts/bench_native.rs +4 -0
- package/simsimd/scripts/bench_sqeuclidean.rs +8 -0
- package/simsimd/scripts/test.c +22 -0
- package/simsimd/scripts/test.py +80 -4
- package/simsimd/swift/SimSIMD.swift +17 -0
- package/simsimd/swift/Test.swift +36 -0
|
@@ -624,7 +624,7 @@ class index_dense_gt {
|
|
|
624
624
|
}
|
|
625
625
|
operator index_dense_gt&&() && {
|
|
626
626
|
if (error)
|
|
627
|
-
|
|
627
|
+
usearch_raise_runtime_error(error.what());
|
|
628
628
|
return std::move(index);
|
|
629
629
|
}
|
|
630
630
|
};
|
|
@@ -757,39 +757,45 @@ class index_dense_gt {
|
|
|
757
757
|
};
|
|
758
758
|
|
|
759
759
|
// clang-format off
|
|
760
|
-
add_result_t add(vector_key_t key, b1x8_t const* vector, std::size_t thread = any_thread(), bool
|
|
761
|
-
add_result_t add(vector_key_t key, i8_t const* vector, std::size_t thread = any_thread(), bool
|
|
762
|
-
add_result_t add(vector_key_t key, f16_t const* vector, std::size_t thread = any_thread(), bool
|
|
763
|
-
add_result_t add(vector_key_t key,
|
|
764
|
-
add_result_t add(vector_key_t key,
|
|
760
|
+
add_result_t add(vector_key_t key, b1x8_t const* vector, std::size_t thread = any_thread(), bool copy_vector = true) { return add_(key, vector, thread, copy_vector, casts_.from.b1x8); }
|
|
761
|
+
add_result_t add(vector_key_t key, i8_t const* vector, std::size_t thread = any_thread(), bool copy_vector = true) { return add_(key, vector, thread, copy_vector, casts_.from.i8); }
|
|
762
|
+
add_result_t add(vector_key_t key, f16_t const* vector, std::size_t thread = any_thread(), bool copy_vector = true) { return add_(key, vector, thread, copy_vector, casts_.from.f16); }
|
|
763
|
+
add_result_t add(vector_key_t key, bf16_t const* vector, std::size_t thread = any_thread(), bool copy_vector = true) { return add_(key, vector, thread, copy_vector, casts_.from.bf16); }
|
|
764
|
+
add_result_t add(vector_key_t key, f32_t const* vector, std::size_t thread = any_thread(), bool copy_vector = true) { return add_(key, vector, thread, copy_vector, casts_.from.f32); }
|
|
765
|
+
add_result_t add(vector_key_t key, f64_t const* vector, std::size_t thread = any_thread(), bool copy_vector = true) { return add_(key, vector, thread, copy_vector, casts_.from.f64); }
|
|
765
766
|
|
|
766
767
|
search_result_t search(b1x8_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, dummy_predicate_t {}, thread, exact, casts_.from.b1x8); }
|
|
767
768
|
search_result_t search(i8_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, dummy_predicate_t {}, thread, exact, casts_.from.i8); }
|
|
768
769
|
search_result_t search(f16_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, dummy_predicate_t {}, thread, exact, casts_.from.f16); }
|
|
770
|
+
search_result_t search(bf16_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, dummy_predicate_t {}, thread, exact, casts_.from.bf16); }
|
|
769
771
|
search_result_t search(f32_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, dummy_predicate_t {}, thread, exact, casts_.from.f32); }
|
|
770
772
|
search_result_t search(f64_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, dummy_predicate_t {}, thread, exact, casts_.from.f64); }
|
|
771
773
|
|
|
772
774
|
template <typename predicate_at> search_result_t filtered_search(b1x8_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from.b1x8); }
|
|
773
775
|
template <typename predicate_at> search_result_t filtered_search(i8_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from.i8); }
|
|
774
776
|
template <typename predicate_at> search_result_t filtered_search(f16_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from.f16); }
|
|
777
|
+
template <typename predicate_at> search_result_t filtered_search(bf16_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from.bf16); }
|
|
775
778
|
template <typename predicate_at> search_result_t filtered_search(f32_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from.f32); }
|
|
776
779
|
template <typename predicate_at> search_result_t filtered_search(f64_t const* vector, std::size_t wanted, predicate_at&& predicate, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, std::forward<predicate_at>(predicate), thread, exact, casts_.from.f64); }
|
|
777
780
|
|
|
778
781
|
std::size_t get(vector_key_t key, b1x8_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to.b1x8); }
|
|
779
782
|
std::size_t get(vector_key_t key, i8_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to.i8); }
|
|
780
783
|
std::size_t get(vector_key_t key, f16_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to.f16); }
|
|
784
|
+
std::size_t get(vector_key_t key, bf16_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to.bf16); }
|
|
781
785
|
std::size_t get(vector_key_t key, f32_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to.f32); }
|
|
782
786
|
std::size_t get(vector_key_t key, f64_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to.f64); }
|
|
783
787
|
|
|
784
788
|
cluster_result_t cluster(b1x8_t const* vector, std::size_t level, std::size_t thread = any_thread()) const { return cluster_(vector, level, thread, casts_.from.b1x8); }
|
|
785
789
|
cluster_result_t cluster(i8_t const* vector, std::size_t level, std::size_t thread = any_thread()) const { return cluster_(vector, level, thread, casts_.from.i8); }
|
|
786
790
|
cluster_result_t cluster(f16_t const* vector, std::size_t level, std::size_t thread = any_thread()) const { return cluster_(vector, level, thread, casts_.from.f16); }
|
|
791
|
+
cluster_result_t cluster(bf16_t const* vector, std::size_t level, std::size_t thread = any_thread()) const { return cluster_(vector, level, thread, casts_.from.bf16); }
|
|
787
792
|
cluster_result_t cluster(f32_t const* vector, std::size_t level, std::size_t thread = any_thread()) const { return cluster_(vector, level, thread, casts_.from.f32); }
|
|
788
793
|
cluster_result_t cluster(f64_t const* vector, std::size_t level, std::size_t thread = any_thread()) const { return cluster_(vector, level, thread, casts_.from.f64); }
|
|
789
794
|
|
|
790
795
|
aggregated_distances_t distance_between(vector_key_t key, b1x8_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to.b1x8); }
|
|
791
796
|
aggregated_distances_t distance_between(vector_key_t key, i8_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to.i8); }
|
|
792
797
|
aggregated_distances_t distance_between(vector_key_t key, f16_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to.f16); }
|
|
798
|
+
aggregated_distances_t distance_between(vector_key_t key, bf16_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to.bf16); }
|
|
793
799
|
aggregated_distances_t distance_between(vector_key_t key, f32_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to.f32); }
|
|
794
800
|
aggregated_distances_t distance_between(vector_key_t key, f64_t const* vector, std::size_t thread = any_thread()) const { return distance_between_(key, vector, thread, casts_.to.f64); }
|
|
795
801
|
// clang-format on
|
|
@@ -940,7 +946,7 @@ class index_dense_gt {
|
|
|
940
946
|
|
|
941
947
|
void reserve(index_limits_t limits) {
|
|
942
948
|
if (!try_reserve(limits))
|
|
943
|
-
|
|
949
|
+
usearch_raise_runtime_error("failed to reserve memory");
|
|
944
950
|
}
|
|
945
951
|
|
|
946
952
|
/**
|
|
@@ -1070,8 +1076,9 @@ class index_dense_gt {
|
|
|
1070
1076
|
|
|
1071
1077
|
/**
|
|
1072
1078
|
* @brief Parses the index from file to RAM.
|
|
1073
|
-
* @param[in]
|
|
1079
|
+
* @param[in] input The input stream to read from.
|
|
1074
1080
|
* @param[in] config Configuration parameters for imports.
|
|
1081
|
+
* @param[in] progress Callback to report the execution progress.
|
|
1075
1082
|
* @return Outcome descriptor explicitly convertible to boolean.
|
|
1076
1083
|
*/
|
|
1077
1084
|
template <typename input_callback_at, typename progress_at = dummy_progress_t>
|
|
@@ -1141,7 +1148,9 @@ class index_dense_gt {
|
|
|
1141
1148
|
|
|
1142
1149
|
config_.multi = head.multi;
|
|
1143
1150
|
metric_ = metric_t::builtin(head.dimensions, head.kind_metric, head.kind_scalar);
|
|
1144
|
-
|
|
1151
|
+
// available_threads_.size() will be updated to old_limits.threads() later in this
|
|
1152
|
+
// method, so use that as the number of threads to prepare for.
|
|
1153
|
+
cast_buffer_ = cast_buffer_t(old_limits.threads() * metric_.bytes_per_vector());
|
|
1145
1154
|
if (!cast_buffer_)
|
|
1146
1155
|
return result.failed("Failed to allocate memory for the casts");
|
|
1147
1156
|
casts_ = casts_punned_t::make(head.kind_scalar);
|
|
@@ -1180,8 +1189,10 @@ class index_dense_gt {
|
|
|
1180
1189
|
|
|
1181
1190
|
/**
|
|
1182
1191
|
* @brief Parses the index from file, without loading it into RAM.
|
|
1183
|
-
* @param[in]
|
|
1192
|
+
* @param[in] file The input file to read from.
|
|
1193
|
+
* @param[in] offset The offset in the file to start reading from.
|
|
1184
1194
|
* @param[in] config Configuration parameters for imports.
|
|
1195
|
+
* @param[in] progress Callback to report the execution progress.
|
|
1185
1196
|
* @return Outcome descriptor explicitly convertible to boolean.
|
|
1186
1197
|
*/
|
|
1187
1198
|
template <typename progress_at = dummy_progress_t>
|
|
@@ -1253,7 +1264,9 @@ class index_dense_gt {
|
|
|
1253
1264
|
|
|
1254
1265
|
config_.multi = head.multi;
|
|
1255
1266
|
metric_ = metric_t::builtin(head.dimensions, head.kind_metric, head.kind_scalar);
|
|
1256
|
-
|
|
1267
|
+
// available_threads_.size() will be updated to old_limits.threads() later in this
|
|
1268
|
+
// method, so use that as the number of threads to prepare for.
|
|
1269
|
+
cast_buffer_ = cast_buffer_t(old_limits.threads() * metric_.bytes_per_vector());
|
|
1257
1270
|
if (!cast_buffer_)
|
|
1258
1271
|
return result.failed("Failed to allocate memory for the casts");
|
|
1259
1272
|
casts_ = casts_punned_t::make(head.kind_scalar);
|
|
@@ -1301,8 +1314,9 @@ class index_dense_gt {
|
|
|
1301
1314
|
|
|
1302
1315
|
/**
|
|
1303
1316
|
* @brief Saves the index to a file.
|
|
1304
|
-
* @param[in]
|
|
1317
|
+
* @param[in] file The output file to write to.
|
|
1305
1318
|
* @param[in] config Configuration parameters for exports.
|
|
1319
|
+
* @param[in] progress Callback to report the execution progress.
|
|
1306
1320
|
* @return Outcome descriptor explicitly convertible to boolean.
|
|
1307
1321
|
*/
|
|
1308
1322
|
template <typename progress_at = dummy_progress_t>
|
|
@@ -1356,8 +1370,9 @@ class index_dense_gt {
|
|
|
1356
1370
|
|
|
1357
1371
|
/**
|
|
1358
1372
|
* @brief Parses the index from file to RAM.
|
|
1359
|
-
* @param[in]
|
|
1373
|
+
* @param[in] file The input file to read from.
|
|
1360
1374
|
* @param[in] config Configuration parameters for imports.
|
|
1375
|
+
* @param[in] progress Progress callback.
|
|
1361
1376
|
* @return Outcome descriptor explicitly convertible to boolean.
|
|
1362
1377
|
*/
|
|
1363
1378
|
template <typename progress_at = dummy_progress_t>
|
|
@@ -1694,12 +1709,12 @@ class index_dense_gt {
|
|
|
1694
1709
|
compaction_result_t isolate(executor_at&& executor = executor_at{}, progress_at&& progress = progress_at{}) {
|
|
1695
1710
|
compaction_result_t result;
|
|
1696
1711
|
std::atomic<std::size_t> pruned_edges;
|
|
1697
|
-
auto
|
|
1712
|
+
auto allow = [&](member_cref_t const& member) noexcept {
|
|
1698
1713
|
bool freed = member.key == free_key_;
|
|
1699
1714
|
pruned_edges += freed;
|
|
1700
|
-
return freed;
|
|
1715
|
+
return !freed;
|
|
1701
1716
|
};
|
|
1702
|
-
typed_->isolate(
|
|
1717
|
+
typed_->isolate(allow, std::forward<executor_at>(executor), std::forward<progress_at>(progress));
|
|
1703
1718
|
result.pruned_edges = pruned_edges;
|
|
1704
1719
|
return result;
|
|
1705
1720
|
}
|
|
@@ -1959,6 +1974,7 @@ class index_dense_gt {
|
|
|
1959
1974
|
|
|
1960
1975
|
result.computed_distances = computed_distances;
|
|
1961
1976
|
result.visited_members = visited_members;
|
|
1977
|
+
result.clusters = unique_clusters;
|
|
1962
1978
|
|
|
1963
1979
|
(void)progress;
|
|
1964
1980
|
return result;
|
|
@@ -1986,14 +2002,13 @@ class index_dense_gt {
|
|
|
1986
2002
|
template <typename scalar_at>
|
|
1987
2003
|
add_result_t add_( //
|
|
1988
2004
|
vector_key_t key, scalar_at const* vector, //
|
|
1989
|
-
std::size_t thread, bool
|
|
2005
|
+
std::size_t thread, bool copy_vector, cast_punned_t const& cast) {
|
|
1990
2006
|
|
|
1991
2007
|
if (!multi() && config().enable_key_lookups && contains(key))
|
|
1992
2008
|
return add_result_t{}.failed("Duplicate keys not allowed in high-level wrappers");
|
|
1993
2009
|
|
|
1994
2010
|
// Cast the vector, if needed for compatibility with `metric_`
|
|
1995
2011
|
thread_lock_t lock = thread_lock_(thread);
|
|
1996
|
-
bool copy_vector = !config_.exclude_vectors || force_vector_copy;
|
|
1997
2012
|
byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
|
|
1998
2013
|
{
|
|
1999
2014
|
byte_t* casted_data = cast_buffer_.data() + metric_.bytes_per_vector() * lock.thread_id;
|
|
@@ -2255,4 +2270,4 @@ static join_result_t join( //
|
|
|
2255
2270
|
}
|
|
2256
2271
|
|
|
2257
2272
|
} // namespace usearch
|
|
2258
|
-
} // namespace unum
|
|
2273
|
+
} // namespace unum
|
|
@@ -483,7 +483,7 @@ class f16_bits_t {
|
|
|
483
483
|
inline operator float() const noexcept { return f16_to_f32(uint16_); }
|
|
484
484
|
inline explicit operator bool() const noexcept { return f16_to_f32(uint16_) > 0.5f; }
|
|
485
485
|
|
|
486
|
-
inline f16_bits_t(
|
|
486
|
+
inline f16_bits_t(int v) noexcept : uint16_(f32_to_f16(v)) {}
|
|
487
487
|
inline f16_bits_t(bool v) noexcept : uint16_(f32_to_f16(v)) {}
|
|
488
488
|
inline f16_bits_t(float v) noexcept : uint16_(f32_to_f16(v)) {}
|
|
489
489
|
inline f16_bits_t(double v) noexcept : uint16_(f32_to_f16(static_cast<float>(v))) {}
|
|
@@ -524,6 +524,11 @@ class f16_bits_t {
|
|
|
524
524
|
}
|
|
525
525
|
};
|
|
526
526
|
|
|
527
|
+
#if USEARCH_USE_OPENMP
|
|
528
|
+
#pragma omp declare reduction(+ : unum::usearch::f16_bits_t : omp_out = omp_out + omp_in) \
|
|
529
|
+
initializer(omp_priv = unum::usearch::f16_bits_t())
|
|
530
|
+
#endif
|
|
531
|
+
|
|
527
532
|
/**
|
|
528
533
|
* @brief Numeric type for brain-floating point half-precision floating point.
|
|
529
534
|
* If hardware support isn't available, falls back to a hardware
|
|
@@ -542,7 +547,7 @@ class bf16_bits_t {
|
|
|
542
547
|
inline operator float() const noexcept { return bf16_to_f32(uint16_); }
|
|
543
548
|
inline explicit operator bool() const noexcept { return bf16_to_f32(uint16_) > 0.5f; }
|
|
544
549
|
|
|
545
|
-
inline bf16_bits_t(
|
|
550
|
+
inline bf16_bits_t(int v) noexcept : uint16_(f32_to_bf16(v)) {}
|
|
546
551
|
inline bf16_bits_t(bool v) noexcept : uint16_(f32_to_bf16(v)) {}
|
|
547
552
|
inline bf16_bits_t(float v) noexcept : uint16_(f32_to_bf16(v)) {}
|
|
548
553
|
inline bf16_bits_t(double v) noexcept : uint16_(f32_to_bf16(static_cast<float>(v))) {}
|
|
@@ -581,8 +586,18 @@ class bf16_bits_t {
|
|
|
581
586
|
uint16_ = f32_to_bf16(v / bf16_to_f32(uint16_));
|
|
582
587
|
return *this;
|
|
583
588
|
}
|
|
589
|
+
|
|
590
|
+
inline bf16_bits_t& operator=(float v) noexcept {
|
|
591
|
+
uint16_ = f32_to_bf16(v);
|
|
592
|
+
return *this;
|
|
593
|
+
}
|
|
584
594
|
};
|
|
585
595
|
|
|
596
|
+
#if USEARCH_USE_OPENMP
|
|
597
|
+
#pragma omp declare reduction(+ : unum::usearch::bf16_bits_t : omp_out = omp_out + omp_in) \
|
|
598
|
+
initializer(omp_priv = unum::usearch::bf16_bits_t())
|
|
599
|
+
#endif
|
|
600
|
+
|
|
586
601
|
/**
|
|
587
602
|
* @brief An STL-based executor or a "thread-pool" for parallel execution.
|
|
588
603
|
* Isn't efficient for small batches, as it recreates the threads on every call.
|
|
@@ -760,7 +775,9 @@ class executor_openmp_t {
|
|
|
760
775
|
template <typename thread_aware_function_at>
|
|
761
776
|
void parallel(thread_aware_function_at&& thread_aware_function) noexcept(false) {
|
|
762
777
|
#pragma omp parallel
|
|
763
|
-
{
|
|
778
|
+
{
|
|
779
|
+
thread_aware_function(omp_get_thread_num());
|
|
780
|
+
}
|
|
764
781
|
}
|
|
765
782
|
};
|
|
766
783
|
|
|
@@ -1223,6 +1240,7 @@ struct casts_punned_t {
|
|
|
1223
1240
|
cast_punned_t b1x8{};
|
|
1224
1241
|
cast_punned_t i8{};
|
|
1225
1242
|
cast_punned_t f16{};
|
|
1243
|
+
cast_punned_t bf16{};
|
|
1226
1244
|
cast_punned_t f32{};
|
|
1227
1245
|
cast_punned_t f64{};
|
|
1228
1246
|
|
|
@@ -1231,7 +1249,7 @@ struct casts_punned_t {
|
|
|
1231
1249
|
case scalar_kind_t::f64_k: return f64;
|
|
1232
1250
|
case scalar_kind_t::f32_k: return f32;
|
|
1233
1251
|
case scalar_kind_t::f16_k: return f16;
|
|
1234
|
-
case scalar_kind_t::bf16_k: return
|
|
1252
|
+
case scalar_kind_t::bf16_k: return bf16;
|
|
1235
1253
|
case scalar_kind_t::i8_k: return i8;
|
|
1236
1254
|
case scalar_kind_t::b1x8_k: return b1x8;
|
|
1237
1255
|
default: return nullptr;
|
|
@@ -1246,12 +1264,14 @@ struct casts_punned_t {
|
|
|
1246
1264
|
result.from.b1x8 = &cast_gt<b1x8_t, scalar_at>::try_;
|
|
1247
1265
|
result.from.i8 = &cast_gt<i8_t, scalar_at>::try_;
|
|
1248
1266
|
result.from.f16 = &cast_gt<f16_t, scalar_at>::try_;
|
|
1267
|
+
result.from.bf16 = &cast_gt<bf16_t, scalar_at>::try_;
|
|
1249
1268
|
result.from.f32 = &cast_gt<f32_t, scalar_at>::try_;
|
|
1250
1269
|
result.from.f64 = &cast_gt<f64_t, scalar_at>::try_;
|
|
1251
1270
|
|
|
1252
1271
|
result.to.b1x8 = &cast_gt<scalar_at, b1x8_t>::try_;
|
|
1253
1272
|
result.to.i8 = &cast_gt<scalar_at, i8_t>::try_;
|
|
1254
1273
|
result.to.f16 = &cast_gt<scalar_at, f16_t>::try_;
|
|
1274
|
+
result.to.bf16 = &cast_gt<scalar_at, bf16_t>::try_;
|
|
1255
1275
|
result.to.f32 = &cast_gt<scalar_at, f32_t>::try_;
|
|
1256
1276
|
result.to.f64 = &cast_gt<scalar_at, f64_t>::try_;
|
|
1257
1277
|
|
|
@@ -1283,6 +1303,8 @@ struct casts_punned_t {
|
|
|
1283
1303
|
|
|
1284
1304
|
/**
|
|
1285
1305
|
* @brief Inner (Dot) Product distance.
|
|
1306
|
+
* Vectors should be normalized to unit length,
|
|
1307
|
+
* otherwise `::metric_cos_gt` should be used instead.
|
|
1286
1308
|
*/
|
|
1287
1309
|
template <typename scalar_at = float, typename result_at = scalar_at> struct metric_ip_gt {
|
|
1288
1310
|
using scalar_t = scalar_at;
|
|
@@ -2566,7 +2588,7 @@ class flat_hash_multi_set_gt {
|
|
|
2566
2588
|
// Allocate new memory
|
|
2567
2589
|
data_ = (char*)allocator_t{}.allocate(other.buckets_ * bytes_per_bucket());
|
|
2568
2590
|
if (!data_)
|
|
2569
|
-
|
|
2591
|
+
usearch_raise_runtime_error("failed memory allocation");
|
|
2570
2592
|
|
|
2571
2593
|
// Copy metadata
|
|
2572
2594
|
buckets_ = other.buckets_;
|
|
@@ -2606,7 +2628,7 @@ class flat_hash_multi_set_gt {
|
|
|
2606
2628
|
// Allocate new memory
|
|
2607
2629
|
data_ = (char*)allocator_t{}.allocate(other.buckets_ * bytes_per_bucket());
|
|
2608
2630
|
if (!data_)
|
|
2609
|
-
|
|
2631
|
+
usearch_raise_runtime_error("failed memory allocation");
|
|
2610
2632
|
|
|
2611
2633
|
// Copy metadata
|
|
2612
2634
|
buckets_ = other.buckets_;
|
|
@@ -2979,7 +3001,7 @@ class flat_hash_multi_set_gt {
|
|
|
2979
3001
|
|
|
2980
3002
|
void reserve(std::size_t capacity) {
|
|
2981
3003
|
if (!try_reserve(capacity))
|
|
2982
|
-
|
|
3004
|
+
usearch_raise_runtime_error("failed to reserve memory");
|
|
2983
3005
|
}
|
|
2984
3006
|
|
|
2985
3007
|
bool try_emplace(element_t const& element) noexcept {
|
package/javascript/README.md
CHANGED
|
@@ -10,12 +10,6 @@ For Node.js environments, install USearch using `npm`:
|
|
|
10
10
|
npm install usearch
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
For front-end applications using WASM, use the Wasmer package manager:
|
|
14
|
-
|
|
15
|
-
```sh
|
|
16
|
-
wasmer install unum/usearch
|
|
17
|
-
```
|
|
18
|
-
|
|
19
13
|
## Quickstart
|
|
20
14
|
|
|
21
15
|
Create an index, add vectors, and perform searches with ease:
|
|
@@ -78,6 +72,14 @@ const batchResults = index.search(vectors, 2);
|
|
|
78
72
|
const firstMatch = batchResults.get(0);
|
|
79
73
|
```
|
|
80
74
|
|
|
75
|
+
Multi-threading is supported for batch operations:
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
const threads_count = 0; // Zero for auto-detection or pass an unsigned integer
|
|
79
|
+
index.add(keys, vectors, threads_count);
|
|
80
|
+
const batchResults = index.search(vectors, 2, threads_count);
|
|
81
|
+
```
|
|
82
|
+
|
|
81
83
|
## Index Introspection
|
|
82
84
|
|
|
83
85
|
Inspect and interact with the index:
|
|
@@ -115,10 +115,12 @@ export declare class Index {
|
|
|
115
115
|
* If a single key is provided, it is associated with all provided vectors.
|
|
116
116
|
* @param {Float32Array|Float64Array|Int8Array} vectors - Input matrix representing vectors,
|
|
117
117
|
* matrix of size n * d, where n is the number of vectors, and d is their dimensionality.
|
|
118
|
+
* @param {number} [threads=0] - Optional, default is 0. Number of threads to use for indexing.
|
|
119
|
+
* If set to 0, the number of threads is determined automatically.
|
|
118
120
|
* @throws Will throw an error if the length of keys doesn't match the number of vectors
|
|
119
121
|
* or if it's not a single key.
|
|
120
122
|
*/
|
|
121
|
-
add(keys: bigint | bigint[] | BigUint64Array, vectors: Vector): void;
|
|
123
|
+
add(keys: bigint | bigint[] | BigUint64Array, vectors: Vector, threads?: number): void;
|
|
122
124
|
/**
|
|
123
125
|
* Perform a k-nearest neighbor search on the index.
|
|
124
126
|
*
|
|
@@ -136,11 +138,12 @@ export declare class Index {
|
|
|
136
138
|
*
|
|
137
139
|
* @param {Float32Array|Float64Array|Int8Array|Array<Array<number>>} vectors - Input matrix representing query vectors, can be a TypedArray or an array of TypedArray.
|
|
138
140
|
* @param {number} k - The number of nearest neighbors to search for each query vector.
|
|
141
|
+
* @param {number} [threads=0] - Optional, default is 0. Number of threads to use for searching. If set to 0, the number of threads is determined automatically.
|
|
139
142
|
* @return {Matches|BatchMatches} - Search results for one or more queries, containing keys, distances, and counts of the matches found.
|
|
140
143
|
* @throws Will throw an error if `k` is not a positive integer or if the size of the vectors is not a multiple of dimensions.
|
|
141
144
|
* @throws Will throw an error if `vectors` is not a valid input type (TypedArray or an array of TypedArray) or if its flattened size is not a multiple of dimensions.
|
|
142
145
|
*/
|
|
143
|
-
search(vectors: VectorOrMatrix, k: number): Matches | BatchMatches;
|
|
146
|
+
search(vectors: VectorOrMatrix, k: number, threads?: number): Matches | BatchMatches;
|
|
144
147
|
/**
|
|
145
148
|
* Verifies the presence of one or more keys in the index.
|
|
146
149
|
*
|
|
@@ -218,6 +221,7 @@ export declare class Index {
|
|
|
218
221
|
* @param {number} dimensions - The dimensionality of the vectors in both the dataset and the queries. It defines the number of elements in each vector.
|
|
219
222
|
* @param {number} count - The number of nearest neighbors to return for each query. If the dataset contains fewer vectors than the specified count, the result will contain only the available vectors.
|
|
220
223
|
* @param {MetricKind} metric - The distance metric to be used for the search.
|
|
224
|
+
* @param {number} [threads=0] - Optional, default is 0. The number of threads to use for the search. If set to 0, the number of threads is determined automatically.
|
|
221
225
|
* @return {Matches|BatchMatches} - Returns a `Matches` or `BatchMatches` object containing the results of the search.
|
|
222
226
|
* @throws Will throw an error if `dimensions` and `count` are not positive integers.
|
|
223
227
|
* @throws Will throw an error if `metric` is not a valid MetricKind.
|
|
@@ -231,15 +235,16 @@ export declare class Index {
|
|
|
231
235
|
* const dimensions = 2; // The number of elements in each vector.
|
|
232
236
|
* const count = 1; // The number of nearest neighbors to return for each query.
|
|
233
237
|
* const metric = MetricKind.IP; // Using the Inner Product distance metric.
|
|
238
|
+
* const threads = 0; // How many threads to use to perform the search.
|
|
234
239
|
*
|
|
235
|
-
* const result = exactSearch(dataset, queries, dimensions, count, metric);
|
|
240
|
+
* const result = exactSearch(dataset, queries, dimensions, count, metric, threads);
|
|
236
241
|
* // result might be:
|
|
237
242
|
* // {
|
|
238
243
|
* // keys: BigUint64Array [ 1n ],
|
|
239
244
|
* // distances: Float32Array [ some_value ],
|
|
240
245
|
* // }
|
|
241
246
|
*/
|
|
242
|
-
declare function exactSearch(dataset: VectorOrMatrix, queries: VectorOrMatrix, dimensions: number, count: number, metric: MetricKind): Matches | BatchMatches;
|
|
247
|
+
export declare function exactSearch(dataset: VectorOrMatrix, queries: VectorOrMatrix, dimensions: number, count: number, metric: MetricKind, threads?: number): Matches | BatchMatches;
|
|
243
248
|
declare const usearch: {
|
|
244
249
|
Index: typeof Index;
|
|
245
250
|
MetricKind: typeof MetricKind;
|
|
@@ -38,7 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
};
|
|
39
39
|
var _Index_compiledIndex;
|
|
40
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
-
exports.Index = exports.BatchMatches = exports.Matches = exports.ScalarKind = exports.MetricKind = void 0;
|
|
41
|
+
exports.exactSearch = exports.Index = exports.BatchMatches = exports.Matches = exports.ScalarKind = exports.MetricKind = void 0;
|
|
42
42
|
const node_gyp_build_1 = __importDefault(require("node-gyp-build"));
|
|
43
43
|
const path = __importStar(require("path"));
|
|
44
44
|
const fs_1 = require("fs");
|
|
@@ -266,10 +266,12 @@ class Index {
|
|
|
266
266
|
* If a single key is provided, it is associated with all provided vectors.
|
|
267
267
|
* @param {Float32Array|Float64Array|Int8Array} vectors - Input matrix representing vectors,
|
|
268
268
|
* matrix of size n * d, where n is the number of vectors, and d is their dimensionality.
|
|
269
|
+
* @param {number} [threads=0] - Optional, default is 0. Number of threads to use for indexing.
|
|
270
|
+
* If set to 0, the number of threads is determined automatically.
|
|
269
271
|
* @throws Will throw an error if the length of keys doesn't match the number of vectors
|
|
270
272
|
* or if it's not a single key.
|
|
271
273
|
*/
|
|
272
|
-
add(keys, vectors) {
|
|
274
|
+
add(keys, vectors, threads = 0) {
|
|
273
275
|
let normalizedKeys = normalizeKeys(keys);
|
|
274
276
|
let normalizedVectors = normalizeVectors(vectors, __classPrivateFieldGet(this, _Index_compiledIndex, "f").dimensions());
|
|
275
277
|
let countVectors = normalizedVectors.length / __classPrivateFieldGet(this, _Index_compiledIndex, "f").dimensions();
|
|
@@ -281,8 +283,11 @@ class Index {
|
|
|
281
283
|
else if (normalizedKeys.length !== countVectors) {
|
|
282
284
|
throw new Error(`The length of keys (${normalizedKeys.length}) must match the number of vectors (${countVectors}) or be a single key.`);
|
|
283
285
|
}
|
|
286
|
+
if ((!Number.isNaN(threads) && typeof threads !== "number") || threads < 0) {
|
|
287
|
+
throw new Error("`threads` must be a non-negative integer representing the number of threads to use for searching.");
|
|
288
|
+
}
|
|
284
289
|
// Call the compiled method
|
|
285
|
-
__classPrivateFieldGet(this, _Index_compiledIndex, "f").add(normalizedKeys, normalizedVectors);
|
|
290
|
+
__classPrivateFieldGet(this, _Index_compiledIndex, "f").add(normalizedKeys, normalizedVectors, threads);
|
|
286
291
|
}
|
|
287
292
|
/**
|
|
288
293
|
* Perform a k-nearest neighbor search on the index.
|
|
@@ -301,17 +306,21 @@ class Index {
|
|
|
301
306
|
*
|
|
302
307
|
* @param {Float32Array|Float64Array|Int8Array|Array<Array<number>>} vectors - Input matrix representing query vectors, can be a TypedArray or an array of TypedArray.
|
|
303
308
|
* @param {number} k - The number of nearest neighbors to search for each query vector.
|
|
309
|
+
* @param {number} [threads=0] - Optional, default is 0. Number of threads to use for searching. If set to 0, the number of threads is determined automatically.
|
|
304
310
|
* @return {Matches|BatchMatches} - Search results for one or more queries, containing keys, distances, and counts of the matches found.
|
|
305
311
|
* @throws Will throw an error if `k` is not a positive integer or if the size of the vectors is not a multiple of dimensions.
|
|
306
312
|
* @throws Will throw an error if `vectors` is not a valid input type (TypedArray or an array of TypedArray) or if its flattened size is not a multiple of dimensions.
|
|
307
313
|
*/
|
|
308
|
-
search(vectors, k) {
|
|
314
|
+
search(vectors, k, threads = 0) {
|
|
309
315
|
if ((!Number.isNaN(k) && typeof k !== "number") || k <= 0) {
|
|
310
316
|
throw new Error("`k` must be a positive integer representing the number of nearest neighbors to search for.");
|
|
311
317
|
}
|
|
318
|
+
if ((!Number.isNaN(threads) && typeof threads !== "number") || threads < 0) {
|
|
319
|
+
throw new Error("`threads` must be a non-negative integer representing the number of threads to use for searching.");
|
|
320
|
+
}
|
|
312
321
|
const normalizedVectors = normalizeVectors(vectors, __classPrivateFieldGet(this, _Index_compiledIndex, "f").dimensions());
|
|
313
322
|
// Call the compiled method and create Matches or BatchMatches object with the result
|
|
314
|
-
const result = __classPrivateFieldGet(this, _Index_compiledIndex, "f").search(normalizedVectors, k);
|
|
323
|
+
const result = __classPrivateFieldGet(this, _Index_compiledIndex, "f").search(normalizedVectors, k, threads);
|
|
315
324
|
const countInQueries = normalizedVectors.length / Number(__classPrivateFieldGet(this, _Index_compiledIndex, "f").dimensions());
|
|
316
325
|
const batchMatches = new BatchMatches(...result, k);
|
|
317
326
|
if (countInQueries === 1) {
|
|
@@ -441,6 +450,7 @@ _Index_compiledIndex = new WeakMap();
|
|
|
441
450
|
* @param {number} dimensions - The dimensionality of the vectors in both the dataset and the queries. It defines the number of elements in each vector.
|
|
442
451
|
* @param {number} count - The number of nearest neighbors to return for each query. If the dataset contains fewer vectors than the specified count, the result will contain only the available vectors.
|
|
443
452
|
* @param {MetricKind} metric - The distance metric to be used for the search.
|
|
453
|
+
* @param {number} [threads=0] - Optional, default is 0. The number of threads to use for the search. If set to 0, the number of threads is determined automatically.
|
|
444
454
|
* @return {Matches|BatchMatches} - Returns a `Matches` or `BatchMatches` object containing the results of the search.
|
|
445
455
|
* @throws Will throw an error if `dimensions` and `count` are not positive integers.
|
|
446
456
|
* @throws Will throw an error if `metric` is not a valid MetricKind.
|
|
@@ -454,21 +464,25 @@ _Index_compiledIndex = new WeakMap();
|
|
|
454
464
|
* const dimensions = 2; // The number of elements in each vector.
|
|
455
465
|
* const count = 1; // The number of nearest neighbors to return for each query.
|
|
456
466
|
* const metric = MetricKind.IP; // Using the Inner Product distance metric.
|
|
467
|
+
* const threads = 0; // How many threads to use to perform the search.
|
|
457
468
|
*
|
|
458
|
-
* const result = exactSearch(dataset, queries, dimensions, count, metric);
|
|
469
|
+
* const result = exactSearch(dataset, queries, dimensions, count, metric, threads);
|
|
459
470
|
* // result might be:
|
|
460
471
|
* // {
|
|
461
472
|
* // keys: BigUint64Array [ 1n ],
|
|
462
473
|
* // distances: Float32Array [ some_value ],
|
|
463
474
|
* // }
|
|
464
475
|
*/
|
|
465
|
-
function exactSearch(dataset, queries, dimensions, count, metric) {
|
|
476
|
+
function exactSearch(dataset, queries, dimensions, count, metric, threads = 0) {
|
|
466
477
|
// Validate and normalize the dimensions and count
|
|
467
478
|
dimensions = Number(dimensions);
|
|
468
479
|
count = Number(count);
|
|
469
480
|
if (count <= 0 || dimensions <= 0) {
|
|
470
481
|
throw new Error("Dimensions and count must be positive integers.");
|
|
471
482
|
}
|
|
483
|
+
if ((!Number.isNaN(threads) && typeof threads !== "number") || threads < 0) {
|
|
484
|
+
throw new Error("`threads` must be a non-negative integer representing the number of threads to use for searching.");
|
|
485
|
+
}
|
|
472
486
|
// Validate metric
|
|
473
487
|
if (!Object.values(MetricKind).includes(metric)) {
|
|
474
488
|
throw new Error(`Invalid metric: ${metric}. It must be one of: ${Object.values(MetricKind).join(", ")}`);
|
|
@@ -489,7 +503,7 @@ function exactSearch(dataset, queries, dimensions, count, metric) {
|
|
|
489
503
|
throw new Error("Count must be equal or smaller than the number of vectors in the dataset.");
|
|
490
504
|
}
|
|
491
505
|
// Call the compiled function with the normalized input
|
|
492
|
-
const result = compiled.exactSearch(dataset, queries, dimensions, count, metric);
|
|
506
|
+
const result = compiled.exactSearch(dataset, queries, dimensions, count, metric, threads);
|
|
493
507
|
// Create and return a Matches or BatchMatches object with the result
|
|
494
508
|
if (countInQueries == 1) {
|
|
495
509
|
return new Matches(result[0], result[1]);
|
|
@@ -498,6 +512,7 @@ function exactSearch(dataset, queries, dimensions, count, metric) {
|
|
|
498
512
|
return new BatchMatches(...result, count);
|
|
499
513
|
}
|
|
500
514
|
}
|
|
515
|
+
exports.exactSearch = exactSearch;
|
|
501
516
|
const usearch = {
|
|
502
517
|
Index,
|
|
503
518
|
MetricKind,
|
|
@@ -115,10 +115,12 @@ export declare class Index {
|
|
|
115
115
|
* If a single key is provided, it is associated with all provided vectors.
|
|
116
116
|
* @param {Float32Array|Float64Array|Int8Array} vectors - Input matrix representing vectors,
|
|
117
117
|
* matrix of size n * d, where n is the number of vectors, and d is their dimensionality.
|
|
118
|
+
* @param {number} [threads=0] - Optional, default is 0. Number of threads to use for indexing.
|
|
119
|
+
* If set to 0, the number of threads is determined automatically.
|
|
118
120
|
* @throws Will throw an error if the length of keys doesn't match the number of vectors
|
|
119
121
|
* or if it's not a single key.
|
|
120
122
|
*/
|
|
121
|
-
add(keys: bigint | bigint[] | BigUint64Array, vectors: Vector): void;
|
|
123
|
+
add(keys: bigint | bigint[] | BigUint64Array, vectors: Vector, threads?: number): void;
|
|
122
124
|
/**
|
|
123
125
|
* Perform a k-nearest neighbor search on the index.
|
|
124
126
|
*
|
|
@@ -136,11 +138,12 @@ export declare class Index {
|
|
|
136
138
|
*
|
|
137
139
|
* @param {Float32Array|Float64Array|Int8Array|Array<Array<number>>} vectors - Input matrix representing query vectors, can be a TypedArray or an array of TypedArray.
|
|
138
140
|
* @param {number} k - The number of nearest neighbors to search for each query vector.
|
|
141
|
+
* @param {number} [threads=0] - Optional, default is 0. Number of threads to use for searching. If set to 0, the number of threads is determined automatically.
|
|
139
142
|
* @return {Matches|BatchMatches} - Search results for one or more queries, containing keys, distances, and counts of the matches found.
|
|
140
143
|
* @throws Will throw an error if `k` is not a positive integer or if the size of the vectors is not a multiple of dimensions.
|
|
141
144
|
* @throws Will throw an error if `vectors` is not a valid input type (TypedArray or an array of TypedArray) or if its flattened size is not a multiple of dimensions.
|
|
142
145
|
*/
|
|
143
|
-
search(vectors: VectorOrMatrix, k: number): Matches | BatchMatches;
|
|
146
|
+
search(vectors: VectorOrMatrix, k: number, threads?: number): Matches | BatchMatches;
|
|
144
147
|
/**
|
|
145
148
|
* Verifies the presence of one or more keys in the index.
|
|
146
149
|
*
|
|
@@ -218,6 +221,7 @@ export declare class Index {
|
|
|
218
221
|
* @param {number} dimensions - The dimensionality of the vectors in both the dataset and the queries. It defines the number of elements in each vector.
|
|
219
222
|
* @param {number} count - The number of nearest neighbors to return for each query. If the dataset contains fewer vectors than the specified count, the result will contain only the available vectors.
|
|
220
223
|
* @param {MetricKind} metric - The distance metric to be used for the search.
|
|
224
|
+
* @param {number} [threads=0] - Optional, default is 0. The number of threads to use for the search. If set to 0, the number of threads is determined automatically.
|
|
221
225
|
* @return {Matches|BatchMatches} - Returns a `Matches` or `BatchMatches` object containing the results of the search.
|
|
222
226
|
* @throws Will throw an error if `dimensions` and `count` are not positive integers.
|
|
223
227
|
* @throws Will throw an error if `metric` is not a valid MetricKind.
|
|
@@ -231,15 +235,16 @@ export declare class Index {
|
|
|
231
235
|
* const dimensions = 2; // The number of elements in each vector.
|
|
232
236
|
* const count = 1; // The number of nearest neighbors to return for each query.
|
|
233
237
|
* const metric = MetricKind.IP; // Using the Inner Product distance metric.
|
|
238
|
+
* const threads = 0; // How many threads to use to perform the search.
|
|
234
239
|
*
|
|
235
|
-
* const result = exactSearch(dataset, queries, dimensions, count, metric);
|
|
240
|
+
* const result = exactSearch(dataset, queries, dimensions, count, metric, threads);
|
|
236
241
|
* // result might be:
|
|
237
242
|
* // {
|
|
238
243
|
* // keys: BigUint64Array [ 1n ],
|
|
239
244
|
* // distances: Float32Array [ some_value ],
|
|
240
245
|
* // }
|
|
241
246
|
*/
|
|
242
|
-
declare function exactSearch(dataset: VectorOrMatrix, queries: VectorOrMatrix, dimensions: number, count: number, metric: MetricKind): Matches | BatchMatches;
|
|
247
|
+
export declare function exactSearch(dataset: VectorOrMatrix, queries: VectorOrMatrix, dimensions: number, count: number, metric: MetricKind, threads?: number): Matches | BatchMatches;
|
|
243
248
|
declare const usearch: {
|
|
244
249
|
Index: typeof Index;
|
|
245
250
|
MetricKind: typeof MetricKind;
|