usearch 2.25.1 → 2.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/c/lib.cpp +28 -13
- package/cpp/test.cpp +169 -0
- package/include/usearch/index.hpp +364 -58
- package/include/usearch/index_dense.hpp +155 -50
- package/include/usearch/index_plugins.hpp +156 -79
- package/javascript/lib.cpp +4 -2
- package/numkong/CITATION.cff +1 -1
- package/numkong/Cargo.toml +1 -1
- package/numkong/bench/bench_cross_amx.cpp +12 -0
- package/numkong/bench/bench_mesh.cpp +12 -0
- package/numkong/bench/bench_spatial.cpp +0 -3
- package/numkong/binding.gyp +18 -0
- package/numkong/build.rs +1 -0
- package/numkong/c/dispatch_e5m2.c +23 -3
- package/numkong/include/numkong/capabilities.h +1 -1
- package/numkong/include/numkong/cast/README.md +3 -0
- package/numkong/include/numkong/cast/haswell.h +28 -64
- package/numkong/include/numkong/cast/serial.h +17 -0
- package/numkong/include/numkong/cast/skylake.h +67 -52
- package/numkong/include/numkong/cast.h +1 -0
- package/numkong/include/numkong/dot/README.md +1 -0
- package/numkong/include/numkong/dot/haswell.h +92 -13
- package/numkong/include/numkong/dot/serial.h +15 -0
- package/numkong/include/numkong/dot/skylake.h +61 -14
- package/numkong/include/numkong/dots/README.md +2 -0
- package/numkong/include/numkong/dots/graniteamx.h +434 -0
- package/numkong/include/numkong/dots/haswell.h +28 -28
- package/numkong/include/numkong/dots/sapphireamx.h +1 -1
- package/numkong/include/numkong/dots/serial.h +23 -8
- package/numkong/include/numkong/dots/skylake.h +28 -23
- package/numkong/include/numkong/dots.h +12 -0
- package/numkong/include/numkong/each/serial.h +18 -1
- package/numkong/include/numkong/geospatial/serial.h +14 -3
- package/numkong/include/numkong/maxsim/serial.h +15 -0
- package/numkong/include/numkong/mesh/README.md +50 -44
- package/numkong/include/numkong/mesh/genoa.h +462 -0
- package/numkong/include/numkong/mesh/haswell.h +806 -933
- package/numkong/include/numkong/mesh/neon.h +871 -943
- package/numkong/include/numkong/mesh/neonbfdot.h +382 -522
- package/numkong/include/numkong/mesh/neonfhm.h +676 -0
- package/numkong/include/numkong/mesh/rvv.h +404 -319
- package/numkong/include/numkong/mesh/serial.h +204 -162
- package/numkong/include/numkong/mesh/skylake.h +1029 -1585
- package/numkong/include/numkong/mesh/v128relaxed.h +403 -377
- package/numkong/include/numkong/mesh.h +38 -0
- package/numkong/include/numkong/reduce/serial.h +15 -1
- package/numkong/include/numkong/sparse/serial.h +17 -2
- package/numkong/include/numkong/spatial/genoa.h +0 -68
- package/numkong/include/numkong/spatial/haswell.h +98 -56
- package/numkong/include/numkong/spatial/serial.h +15 -0
- package/numkong/include/numkong/spatial/skylake.h +114 -54
- package/numkong/include/numkong/spatial.h +0 -12
- package/numkong/include/numkong/spatials/graniteamx.h +128 -0
- package/numkong/include/numkong/spatials/serial.h +18 -1
- package/numkong/include/numkong/spatials/skylake.h +2 -2
- package/numkong/include/numkong/spatials.h +17 -0
- package/numkong/include/numkong/tensor.hpp +107 -23
- package/numkong/javascript/@numkong-darwin-arm64/package.json +1 -1
- package/numkong/javascript/@numkong-darwin-x64/package.json +1 -1
- package/numkong/javascript/@numkong-linux-arm64/package.json +1 -1
- package/numkong/javascript/@numkong-linux-x64/package.json +1 -1
- package/numkong/javascript/@numkong-win32-arm64/package.json +1 -1
- package/numkong/javascript/@numkong-win32-x64/package.json +1 -1
- package/numkong/javascript/numkong.c +3 -2
- package/numkong/package.json +7 -7
- package/numkong/test/test.cu +841 -0
- package/numkong/test/test_cross_amx.cpp +13 -0
- package/numkong/test/test_dlpack.py +492 -0
- package/numkong/test/test_mesh.cpp +12 -0
- package/numkong/test/test_spatial.cpp +0 -3
- package/numkong/test/test_tensor.cpp +87 -27
- package/package.json +2 -2
- 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/prebuilds/win32-x64/usearch.node +0 -0
|
@@ -276,15 +276,22 @@ inline index_dense_metadata_result_t index_dense_metadata_from_path(char const*
|
|
|
276
276
|
|
|
277
277
|
std::uint32_t dimensions_u32[2]{0};
|
|
278
278
|
std::memcpy(dimensions_u32, result.head_buffer, sizeof(dimensions_u32));
|
|
279
|
-
|
|
279
|
+
checked_size_result_t offset_if_u32 =
|
|
280
|
+
checked_mul_add(std::size_t(dimensions_u32[0]), std::size_t(dimensions_u32[1]), sizeof(dimensions_u32));
|
|
280
281
|
|
|
281
282
|
std::uint64_t dimensions_u64[2]{0};
|
|
282
283
|
std::memcpy(dimensions_u64, result.head_buffer, sizeof(dimensions_u64));
|
|
283
|
-
|
|
284
|
+
checked_size_result_t rows_if_u64 = checked_size_from_u64(dimensions_u64[0]);
|
|
285
|
+
checked_size_result_t columns_if_u64 = checked_size_from_u64(dimensions_u64[1]);
|
|
286
|
+
checked_size_result_t offset_if_u64 =
|
|
287
|
+
rows_if_u64 && columns_if_u64 ? checked_mul_add(rows_if_u64.value, columns_if_u64.value, sizeof(dimensions_u64))
|
|
288
|
+
: checked_size_overflow();
|
|
284
289
|
|
|
285
290
|
// Check if it starts with 32-bit
|
|
286
|
-
|
|
287
|
-
|
|
291
|
+
checked_size_result_t head_offset_if_u32 =
|
|
292
|
+
offset_if_u32 ? checked_add(offset_if_u32.value, sizeof(index_dense_head_buffer_t)) : offset_if_u32;
|
|
293
|
+
if (head_offset_if_u32 && head_offset_if_u32.value < file_size) {
|
|
294
|
+
if (std::fseek(file.get(), static_cast<long>(offset_if_u32.value), SEEK_SET) != 0)
|
|
288
295
|
return result.failed(std::strerror(errno));
|
|
289
296
|
read = std::fread(result.head_buffer, sizeof(index_dense_head_buffer_t), 1, file.get());
|
|
290
297
|
if (!read)
|
|
@@ -299,8 +306,10 @@ inline index_dense_metadata_result_t index_dense_metadata_from_path(char const*
|
|
|
299
306
|
}
|
|
300
307
|
|
|
301
308
|
// Check if it starts with 64-bit
|
|
302
|
-
|
|
303
|
-
|
|
309
|
+
checked_size_result_t head_offset_if_u64 =
|
|
310
|
+
offset_if_u64 ? checked_add(offset_if_u64.value, sizeof(index_dense_head_buffer_t)) : offset_if_u64;
|
|
311
|
+
if (head_offset_if_u64 && head_offset_if_u64.value < file_size) {
|
|
312
|
+
if (std::fseek(file.get(), static_cast<long>(offset_if_u64.value), SEEK_SET) != 0)
|
|
304
313
|
return result.failed(std::strerror(errno));
|
|
305
314
|
read = std::fread(result.head_buffer, sizeof(index_dense_head_buffer_t), 1, file.get());
|
|
306
315
|
if (!read)
|
|
@@ -341,15 +350,22 @@ inline index_dense_metadata_result_t index_dense_metadata_from_buffer(memory_map
|
|
|
341
350
|
// Check if it starts with 32-bit
|
|
342
351
|
std::uint32_t dimensions_u32[2]{0};
|
|
343
352
|
std::memcpy(dimensions_u32, result.head_buffer, sizeof(dimensions_u32));
|
|
344
|
-
|
|
353
|
+
checked_size_result_t offset_if_u32 =
|
|
354
|
+
checked_mul_add(std::size_t(dimensions_u32[0]), std::size_t(dimensions_u32[1]), sizeof(dimensions_u32));
|
|
345
355
|
|
|
346
356
|
std::uint64_t dimensions_u64[2]{0};
|
|
347
357
|
std::memcpy(dimensions_u64, result.head_buffer, sizeof(dimensions_u64));
|
|
348
|
-
|
|
358
|
+
checked_size_result_t rows_if_u64 = checked_size_from_u64(dimensions_u64[0]);
|
|
359
|
+
checked_size_result_t columns_if_u64 = checked_size_from_u64(dimensions_u64[1]);
|
|
360
|
+
checked_size_result_t offset_if_u64 =
|
|
361
|
+
rows_if_u64 && columns_if_u64 ? checked_mul_add(rows_if_u64.value, columns_if_u64.value, sizeof(dimensions_u64))
|
|
362
|
+
: checked_size_overflow();
|
|
349
363
|
|
|
350
364
|
// Check if it starts with 32-bit
|
|
351
|
-
|
|
352
|
-
|
|
365
|
+
checked_size_result_t head_offset_if_u32 =
|
|
366
|
+
offset_if_u32 ? checked_add(offset_if_u32.value, sizeof(index_dense_head_buffer_t)) : offset_if_u32;
|
|
367
|
+
if (head_offset_if_u32 && head_offset_if_u32.value < file_size) {
|
|
368
|
+
std::memcpy(&result.head_buffer, file_data + offset_if_u32.value, sizeof(index_dense_head_buffer_t));
|
|
353
369
|
result.config.exclude_vectors = false;
|
|
354
370
|
result.config.use_64_bit_dimensions = false;
|
|
355
371
|
if (std::memcmp(result.head_buffer, default_magic(), std::strlen(default_magic())) == 0)
|
|
@@ -357,8 +373,10 @@ inline index_dense_metadata_result_t index_dense_metadata_from_buffer(memory_map
|
|
|
357
373
|
}
|
|
358
374
|
|
|
359
375
|
// Check if it starts with 64-bit
|
|
360
|
-
|
|
361
|
-
|
|
376
|
+
checked_size_result_t head_offset_if_u64 =
|
|
377
|
+
offset_if_u64 ? checked_add(offset_if_u64.value, sizeof(index_dense_head_buffer_t)) : offset_if_u64;
|
|
378
|
+
if (head_offset_if_u64 && head_offset_if_u64.value < file_size) {
|
|
379
|
+
std::memcpy(&result.head_buffer, file_data + offset_if_u64.value, sizeof(index_dense_head_buffer_t));
|
|
362
380
|
result.config.exclude_vectors = false;
|
|
363
381
|
result.config.use_64_bit_dimensions = true;
|
|
364
382
|
if (std::memcmp(result.head_buffer, default_magic(), std::strlen(default_magic())) == 0)
|
|
@@ -532,6 +550,9 @@ class index_dense_gt {
|
|
|
532
550
|
: parent(other.parent), thread_id(other.thread_id), engaged(other.engaged) {
|
|
533
551
|
other.engaged = false;
|
|
534
552
|
}
|
|
553
|
+
explicit operator bool() const noexcept {
|
|
554
|
+
return parent.typed_ && thread_id != any_thread() && thread_id < parent.typed_->limits().threads();
|
|
555
|
+
}
|
|
535
556
|
};
|
|
536
557
|
|
|
537
558
|
public:
|
|
@@ -635,16 +656,14 @@ class index_dense_gt {
|
|
|
635
656
|
* @param[in] metric One of the provided or an @b ad-hoc metric, type-punned.
|
|
636
657
|
* @param[in] config The index configuration (optional).
|
|
637
658
|
* @param[in] free_key The key used for freed vectors (optional).
|
|
659
|
+
* @param[in] limits Initial reservation. Default sizes the thread pool to
|
|
660
|
+
* @c hardware_concurrency(); pass @c {unreserved} to skip.
|
|
638
661
|
* @return An instance of ::index_dense_gt or error, wrapped in a `state_result_t`.
|
|
639
|
-
*
|
|
640
|
-
* ! If the `metric` isn't provided in this method, it has to be set with
|
|
641
|
-
* ! the `change_metric` method before the index can be used. Alternatively,
|
|
642
|
-
* ! if you are loading an existing index, the metric will be set automatically.
|
|
643
662
|
*/
|
|
644
663
|
static state_result_t make( //
|
|
645
664
|
metric_t metric = {}, //
|
|
646
665
|
index_dense_config_t config = {}, //
|
|
647
|
-
vector_key_t free_key = default_free_value<vector_key_t>()) {
|
|
666
|
+
vector_key_t free_key = default_free_value<vector_key_t>(), index_limits_t limits = {}) {
|
|
648
667
|
|
|
649
668
|
if (metric.missing())
|
|
650
669
|
return state_result_t{}.failed("Metric won't be initialized!");
|
|
@@ -659,16 +678,15 @@ class index_dense_gt {
|
|
|
659
678
|
index_dense_gt& index = result.index;
|
|
660
679
|
index.config_ = config;
|
|
661
680
|
index.free_key_ = free_key;
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
if (metric) {
|
|
665
|
-
scalar_kind_t scalar_kind = metric.scalar_kind();
|
|
666
|
-
index.casts_ = casts_punned_t::make(scalar_kind);
|
|
667
|
-
index.metric_ = metric;
|
|
668
|
-
}
|
|
681
|
+
index.casts_ = casts_punned_t::make(metric.scalar_kind());
|
|
682
|
+
index.metric_ = metric;
|
|
669
683
|
|
|
670
684
|
new (raw) index_t(config);
|
|
671
685
|
index.typed_ = raw;
|
|
686
|
+
|
|
687
|
+
if (!index.try_reserve(limits))
|
|
688
|
+
return state_result_t{}.failed("Failed to reserve memory for the index!");
|
|
689
|
+
|
|
672
690
|
return result;
|
|
673
691
|
}
|
|
674
692
|
|
|
@@ -704,7 +722,34 @@ class index_dense_gt {
|
|
|
704
722
|
|
|
705
723
|
// The metric and its properties
|
|
706
724
|
metric_t const& metric() const { return metric_; }
|
|
707
|
-
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* @brief Replaces the active distance metric, resizing the per-thread cast
|
|
728
|
+
* buffer and rebuilding the cast dispatch table if the new metric
|
|
729
|
+
* changes @c bytes_per_vector() or @c scalar_kind().
|
|
730
|
+
* @return @c false if the cast buffer can't be re-allocated; the metric is
|
|
731
|
+
* left untouched in that case so the index stays consistent.
|
|
732
|
+
*/
|
|
733
|
+
bool try_change_metric(metric_t metric) noexcept {
|
|
734
|
+
checked_size_result_t needed_bytes = checked_mul(limits().threads(), metric.bytes_per_vector());
|
|
735
|
+
if (!needed_bytes)
|
|
736
|
+
return false;
|
|
737
|
+
if (needed_bytes.value > cast_buffer_.size()) {
|
|
738
|
+
cast_buffer_t new_buffer(needed_bytes.value);
|
|
739
|
+
if (!new_buffer)
|
|
740
|
+
return false;
|
|
741
|
+
cast_buffer_ = std::move(new_buffer);
|
|
742
|
+
}
|
|
743
|
+
casts_ = casts_punned_t::make(metric.scalar_kind());
|
|
744
|
+
metric_ = std::move(metric);
|
|
745
|
+
return true;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/// @brief Throwing counterpart of @ref try_change_metric.
|
|
749
|
+
void change_metric(metric_t metric) {
|
|
750
|
+
if (!try_change_metric(std::move(metric)))
|
|
751
|
+
usearch_raise_runtime_error("failed to grow cast buffer for the new metric");
|
|
752
|
+
}
|
|
708
753
|
|
|
709
754
|
scalar_kind_t scalar_kind() const { return metric_.scalar_kind(); }
|
|
710
755
|
metric_kind_t metric_kind() const { return metric_.metric_kind(); }
|
|
@@ -729,6 +774,45 @@ class index_dense_gt {
|
|
|
729
774
|
return typed_->stats(stats_per_level, max_level);
|
|
730
775
|
}
|
|
731
776
|
|
|
777
|
+
using neighbors_view_t = typename index_t::neighbors_view_t;
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* @brief Returns a read-only range over the neighbors of @p key at the given
|
|
781
|
+
* graph @p level.
|
|
782
|
+
*
|
|
783
|
+
* For multi-key indexes, returns the neighbors of the first matching
|
|
784
|
+
* slot; iterate through `cbegin()`/`cend()` and call the underlying
|
|
785
|
+
* `index_gt::neighbors(member_citerator_t, std::size_t)` overload to
|
|
786
|
+
* inspect every copy of a duplicated key.
|
|
787
|
+
*
|
|
788
|
+
* @return An empty view if the key is not present, or if @p level exceeds
|
|
789
|
+
* the node's level.
|
|
790
|
+
* @warning The view aliases the node's adjacency tape. Hold no concurrent
|
|
791
|
+
* `add`/`update`/`remove` for the lifetime of the view.
|
|
792
|
+
*/
|
|
793
|
+
neighbors_view_t neighbors(vector_key_t key, std::size_t level) const {
|
|
794
|
+
usearch_assert_m(config().enable_key_lookups, "Key lookups are disabled");
|
|
795
|
+
shared_lock_t lookup_lock(slot_lookup_mutex_);
|
|
796
|
+
auto matching_slots = slot_lookup_.equal_range(key_and_slot_t::any_slot(key));
|
|
797
|
+
if (matching_slots.first == matching_slots.second)
|
|
798
|
+
return {};
|
|
799
|
+
compressed_slot_t slot = (*matching_slots.first).slot;
|
|
800
|
+
return typed_->neighbors(slot, level);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* @brief Returns the top graph level at which @p key is present, or zero
|
|
805
|
+
* if the key is not in the index.
|
|
806
|
+
*/
|
|
807
|
+
std::size_t level_of(vector_key_t key) const {
|
|
808
|
+
usearch_assert_m(config().enable_key_lookups, "Key lookups are disabled");
|
|
809
|
+
shared_lock_t lookup_lock(slot_lookup_mutex_);
|
|
810
|
+
auto matching_slots = slot_lookup_.equal_range(key_and_slot_t::any_slot(key));
|
|
811
|
+
if (matching_slots.first == matching_slots.second)
|
|
812
|
+
return 0;
|
|
813
|
+
return typed_->level_of((*matching_slots.first).slot);
|
|
814
|
+
}
|
|
815
|
+
|
|
732
816
|
dynamic_allocator_t const& allocator() const { return typed_->dynamic_allocator(); }
|
|
733
817
|
vector_key_t const& free_key() const { return free_key_; }
|
|
734
818
|
|
|
@@ -935,6 +1019,8 @@ class index_dense_gt {
|
|
|
935
1019
|
|
|
936
1020
|
index_cluster_config_t cluster_config;
|
|
937
1021
|
thread_lock_t lock = thread_lock_(thread);
|
|
1022
|
+
if (!lock)
|
|
1023
|
+
return cluster_result_t{}.failed("Reserve capacity ahead of searches!");
|
|
938
1024
|
cluster_config.thread = lock.thread_id;
|
|
939
1025
|
cluster_config.expansion = config_.expansion_search;
|
|
940
1026
|
metric_proxy_t metric{*this};
|
|
@@ -994,7 +1080,10 @@ class index_dense_gt {
|
|
|
994
1080
|
available_threads_.push(i);
|
|
995
1081
|
|
|
996
1082
|
// Allocate a buffer for the casted vectors.
|
|
997
|
-
|
|
1083
|
+
checked_size_result_t cast_buffer_bytes = checked_mul(limits.threads(), metric_.bytes_per_vector());
|
|
1084
|
+
if (!cast_buffer_bytes)
|
|
1085
|
+
return false;
|
|
1086
|
+
cast_buffer_t cast_buffer(cast_buffer_bytes.value);
|
|
998
1087
|
if (!cast_buffer)
|
|
999
1088
|
return false;
|
|
1000
1089
|
cast_buffer_ = std::move(cast_buffer);
|
|
@@ -1019,7 +1108,8 @@ class index_dense_gt {
|
|
|
1019
1108
|
std::unique_lock<std::mutex> free_lock(free_keys_mutex_);
|
|
1020
1109
|
typed_->clear();
|
|
1021
1110
|
slot_lookup_.clear();
|
|
1022
|
-
|
|
1111
|
+
// Tape pointers are about to be invalidated by the reset below.
|
|
1112
|
+
std::fill(vectors_lookup_.begin(), vectors_lookup_.end(), nullptr);
|
|
1023
1113
|
free_keys_.clear();
|
|
1024
1114
|
vectors_tape_allocator_.reset();
|
|
1025
1115
|
}
|
|
@@ -1144,8 +1234,9 @@ class index_dense_gt {
|
|
|
1144
1234
|
serialization_config_t config = {}, //
|
|
1145
1235
|
progress_at&& progress = {}) {
|
|
1146
1236
|
|
|
1147
|
-
//
|
|
1148
|
-
|
|
1237
|
+
// Preserve any explicit thread counts from the prior index state; fall
|
|
1238
|
+
// back to library defaults when they were never set (e.g. `unreserved`).
|
|
1239
|
+
index_limits_t new_limits = typed_ ? typed_->limits().with_thread_defaults() : index_limits_t{};
|
|
1149
1240
|
reset();
|
|
1150
1241
|
|
|
1151
1242
|
// Infer the new index size
|
|
@@ -1206,9 +1297,10 @@ class index_dense_gt {
|
|
|
1206
1297
|
|
|
1207
1298
|
config_.multi = head.multi;
|
|
1208
1299
|
metric_ = metric_t::builtin(head.dimensions, head.kind_metric, head.kind_scalar);
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1300
|
+
checked_size_result_t cast_buffer_bytes = checked_mul(new_limits.threads(), metric_.bytes_per_vector());
|
|
1301
|
+
if (!cast_buffer_bytes)
|
|
1302
|
+
return result.failed("Failed to allocate memory for the casts");
|
|
1303
|
+
cast_buffer_ = cast_buffer_t(cast_buffer_bytes.value);
|
|
1212
1304
|
if (!cast_buffer_)
|
|
1213
1305
|
return result.failed("Failed to allocate memory for the casts");
|
|
1214
1306
|
casts_ = casts_punned_t::make(head.kind_scalar);
|
|
@@ -1227,14 +1319,13 @@ class index_dense_gt {
|
|
|
1227
1319
|
return result;
|
|
1228
1320
|
if (typed_->size() != static_cast<std::size_t>(matrix_rows))
|
|
1229
1321
|
return result.failed("Index size and the number of vectors doesn't match");
|
|
1230
|
-
|
|
1231
|
-
if (!typed_->try_reserve(
|
|
1322
|
+
new_limits.members = static_cast<std::size_t>(matrix_rows);
|
|
1323
|
+
if (!typed_->try_reserve(new_limits))
|
|
1232
1324
|
return result.failed("Failed to reserve memory for the index");
|
|
1233
1325
|
|
|
1234
|
-
// After the index is loaded,
|
|
1235
|
-
// match the limits of the underlying engine.
|
|
1326
|
+
// After the index is loaded, resize `available_threads_` to match the new limits.
|
|
1236
1327
|
available_threads_t available_threads;
|
|
1237
|
-
std::size_t max_threads =
|
|
1328
|
+
std::size_t max_threads = new_limits.threads();
|
|
1238
1329
|
if (!available_threads.reserve(max_threads))
|
|
1239
1330
|
return result.failed("Failed to allocate memory for the available threads!");
|
|
1240
1331
|
for (std::size_t i = 0; i < max_threads; i++)
|
|
@@ -1258,8 +1349,9 @@ class index_dense_gt {
|
|
|
1258
1349
|
std::size_t offset = 0, serialization_config_t config = {}, //
|
|
1259
1350
|
progress_at&& progress = {}) {
|
|
1260
1351
|
|
|
1261
|
-
//
|
|
1262
|
-
|
|
1352
|
+
// Preserve any explicit thread counts from the prior index state; fall
|
|
1353
|
+
// back to library defaults when they were never set (e.g. `unreserved`).
|
|
1354
|
+
index_limits_t new_limits = typed_ ? typed_->limits().with_thread_defaults() : index_limits_t{};
|
|
1263
1355
|
reset();
|
|
1264
1356
|
|
|
1265
1357
|
serialization_result_t result = file.open_if_not();
|
|
@@ -1323,8 +1415,10 @@ class index_dense_gt {
|
|
|
1323
1415
|
config_.multi = head.multi;
|
|
1324
1416
|
metric_ = metric_t::builtin(head.dimensions, head.kind_metric, head.kind_scalar);
|
|
1325
1417
|
// available_threads_.size() will be updated to old_limits.threads() later in this
|
|
1326
|
-
|
|
1327
|
-
|
|
1418
|
+
checked_size_result_t cast_buffer_bytes = checked_mul(new_limits.threads(), metric_.bytes_per_vector());
|
|
1419
|
+
if (!cast_buffer_bytes)
|
|
1420
|
+
return result.failed("Failed to allocate memory for the casts");
|
|
1421
|
+
cast_buffer_ = cast_buffer_t(cast_buffer_bytes.value);
|
|
1328
1422
|
if (!cast_buffer_)
|
|
1329
1423
|
return result.failed("Failed to allocate memory for the casts");
|
|
1330
1424
|
casts_ = casts_punned_t::make(head.kind_scalar);
|
|
@@ -1344,8 +1438,8 @@ class index_dense_gt {
|
|
|
1344
1438
|
return result;
|
|
1345
1439
|
if (typed_->size() != static_cast<std::size_t>(matrix_rows))
|
|
1346
1440
|
return result.failed("Index size and the number of vectors doesn't match");
|
|
1347
|
-
|
|
1348
|
-
if (!typed_->try_reserve(
|
|
1441
|
+
new_limits.members = static_cast<std::size_t>(matrix_rows);
|
|
1442
|
+
if (!typed_->try_reserve(new_limits))
|
|
1349
1443
|
return result.failed("Failed to reserve memory for the index");
|
|
1350
1444
|
|
|
1351
1445
|
// Address the vectors
|
|
@@ -1356,10 +1450,9 @@ class index_dense_gt {
|
|
|
1356
1450
|
for (std::uint64_t slot = 0; slot != matrix_rows; ++slot)
|
|
1357
1451
|
vectors_lookup_[slot] = (byte_t*)vectors_buffer.data() + matrix_cols * slot;
|
|
1358
1452
|
|
|
1359
|
-
// After the index is
|
|
1360
|
-
// match the limits of the underlying engine.
|
|
1453
|
+
// After the index is viewed, resize `available_threads_` to match the new limits.
|
|
1361
1454
|
available_threads_t available_threads;
|
|
1362
|
-
std::size_t max_threads =
|
|
1455
|
+
std::size_t max_threads = new_limits.threads();
|
|
1363
1456
|
if (!available_threads.reserve(max_threads))
|
|
1364
1457
|
return result.failed("Failed to allocate memory for the available threads!");
|
|
1365
1458
|
for (std::size_t i = 0; i < max_threads; i++)
|
|
@@ -1624,6 +1717,11 @@ class index_dense_gt {
|
|
|
1624
1717
|
*/
|
|
1625
1718
|
labeling_result_t rename(vector_key_t from, vector_key_t to) {
|
|
1626
1719
|
labeling_result_t result;
|
|
1720
|
+
if (from == to) {
|
|
1721
|
+
result.completed = count(from);
|
|
1722
|
+
return result;
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1627
1725
|
unique_lock_t lookup_lock(slot_lookup_mutex_);
|
|
1628
1726
|
|
|
1629
1727
|
if (!multi() && slot_lookup_.contains(key_and_slot_t::any_slot(to)))
|
|
@@ -2043,10 +2141,9 @@ class index_dense_gt {
|
|
|
2043
2141
|
if (thread_id != any_thread())
|
|
2044
2142
|
return {*this, thread_id, false};
|
|
2045
2143
|
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
available_threads_mutex_.unlock();
|
|
2144
|
+
std::unique_lock<std::mutex> lock(available_threads_mutex_);
|
|
2145
|
+
if (!available_threads_.try_pop(thread_id))
|
|
2146
|
+
return {*this, any_thread(), false};
|
|
2050
2147
|
return {*this, thread_id, true};
|
|
2051
2148
|
}
|
|
2052
2149
|
|
|
@@ -2067,6 +2164,8 @@ class index_dense_gt {
|
|
|
2067
2164
|
|
|
2068
2165
|
// Cast the vector, if needed for compatibility with `metric_`
|
|
2069
2166
|
thread_lock_t lock = thread_lock_(thread);
|
|
2167
|
+
if (!lock)
|
|
2168
|
+
return add_result_t{}.failed("Reserve capacity ahead of insertions!");
|
|
2070
2169
|
byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
|
|
2071
2170
|
{
|
|
2072
2171
|
byte_t* casted_data = cast_buffer_.data() + metric_.bytes_per_vector() * lock.thread_id;
|
|
@@ -2120,6 +2219,8 @@ class index_dense_gt {
|
|
|
2120
2219
|
|
|
2121
2220
|
// Cast the vector, if needed for compatibility with `metric_`
|
|
2122
2221
|
thread_lock_t lock = thread_lock_(thread);
|
|
2222
|
+
if (!lock)
|
|
2223
|
+
return search_result_t{*this}.failed("Reserve capacity ahead of searches!");
|
|
2123
2224
|
byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
|
|
2124
2225
|
{
|
|
2125
2226
|
byte_t* casted_data = cast_buffer_.data() + metric_.bytes_per_vector() * lock.thread_id;
|
|
@@ -2156,6 +2257,8 @@ class index_dense_gt {
|
|
|
2156
2257
|
|
|
2157
2258
|
// Cast the vector, if needed for compatibility with `metric_`
|
|
2158
2259
|
thread_lock_t lock = thread_lock_(thread);
|
|
2260
|
+
if (!lock)
|
|
2261
|
+
return cluster_result_t{}.failed("Reserve capacity ahead of searches!");
|
|
2159
2262
|
byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
|
|
2160
2263
|
{
|
|
2161
2264
|
byte_t* casted_data = cast_buffer_.data() + metric_.bytes_per_vector() * lock.thread_id;
|
|
@@ -2180,6 +2283,8 @@ class index_dense_gt {
|
|
|
2180
2283
|
|
|
2181
2284
|
// Cast the vector, if needed for compatibility with `metric_`
|
|
2182
2285
|
thread_lock_t lock = thread_lock_(thread);
|
|
2286
|
+
if (!lock)
|
|
2287
|
+
return {};
|
|
2183
2288
|
byte_t const* vector_data = reinterpret_cast<byte_t const*>(vector);
|
|
2184
2289
|
{
|
|
2185
2290
|
byte_t* casted_data = cast_buffer_.data() + metric_.bytes_per_vector() * lock.thread_id;
|