From c7b9f1522495e0f0a77155a7c0c6c9e00493415e Mon Sep 17 00:00:00 2001 From: yuejiaointel Date: Fri, 16 Oct 2026 23:34:49 -0700 Subject: [PATCH 1/3] Reset visited set to buffer contents when a batch search resumes When the visited-set filter is enabled and a batch iterator resumes with a larger search window, nodes that were visited but later dropped from the buffer stayed marked as visited and could never be re-inserted. With a selective filter this collapsed recall. Add reset_visited_to_contents() to the search buffers and call it in RestartInitializer before sorting. Add a buffer unit test and a C API dynamic filtered-search recall test. --- bindings/c/tests/c_api_dynamic_index.cpp | 154 ++++++++++++++++++ .../svs/concurrent/dynamic_search_buffer.h | 13 ++ include/svs/concurrent/iterator.h | 5 +- .../svs/index/vamana/dynamic_search_buffer.h | 13 ++ include/svs/index/vamana/iterator.h | 5 +- include/svs/index/vamana/search_buffer.h | 13 ++ tests/svs/index/vamana/search_buffer.cpp | 41 +++++ 7 files changed, 242 insertions(+), 2 deletions(-) diff --git a/bindings/c/tests/c_api_dynamic_index.cpp b/bindings/c/tests/c_api_dynamic_index.cpp index 372ef2158..2a322c44d 100644 --- a/bindings/c/tests/c_api_dynamic_index.cpp +++ b/bindings/c/tests/c_api_dynamic_index.cpp @@ -24,6 +24,12 @@ #include "c_api_test_utils.h" // Standard library +#include +#include +#include +#include +#include +#include #include CATCH_TEST_CASE("C API Dynamic Index", "[c_api][index][dynamic]") { @@ -1045,3 +1051,151 @@ CATCH_TEST_CASE( svs_error_free(error); } } + +namespace { + +// Accepts IDs that are multiples of the modulus stored in `self`. +bool filter_modulus(void* self, size_t id) { + return (id % *static_cast(self)) == 0; +} +float filter_rate_unknown(void* /*self*/) { return 0.0f; } + +// Exact top-k (squared L2) over the IDs that pass `keep`. +template +std::vector restricted_ground_truth( + const std::vector& data, + const float* query, + size_t dimension, + size_t k, + const Keep& keep +) { + std::vector> scored; + for (size_t id = 0; id < data.size() / dimension; ++id) { + if (keep(id)) { + scored.emplace_back( + euclidean_distance(query, data.data() + id * dimension, dimension), id + ); + } + } + const size_t n = std::min(k, scored.size()); + std::partial_sort(scored.begin(), scored.begin() + n, scored.end()); + std::vector top; + for (size_t i = 0; i < n; ++i) { + top.push_back(scored[i].second); + } + return top; +} + +} // namespace + +CATCH_TEST_CASE( + "C API Dynamic Filtered Search recall", "[c_api][index][dynamic][search][filter]" +) { + const size_t NUM_VECTORS = 10000; + const size_t NUM_QUERIES = 20; + const size_t DIMENSION = 32; + const size_t K = 10; + const size_t NUM_THREADS = 4; + const size_t BLOCK_SIZE = 1024 * 1024; + + std::mt19937 rng(123); + std::uniform_real_distribution uniform(0.0f, 1.0f); + std::vector data(NUM_VECTORS * DIMENSION); + std::vector queries(NUM_QUERIES * DIMENSION); + for (auto& x : data) { + x = uniform(rng); + } + for (auto& x : queries) { + x = uniform(rng); + } + std::vector ids(NUM_VECTORS); + for (size_t i = 0; i < NUM_VECTORS; ++i) { + ids[i] = i; + } + + svs_error_h error = svs_error_create(); + svs_algorithm_h algorithm = svs_algorithm_create_vamana(32, 100, 100, error); + CATCH_REQUIRE(algorithm != nullptr); + svs_index_builder_h builder = svs_index_builder_create( + SVS_DISTANCE_METRIC_EUCLIDEAN, DIMENSION, algorithm, error + ); + CATCH_REQUIRE(builder != nullptr); + CATCH_REQUIRE(svs_index_builder_set_threadpool( + builder, SVS_THREADPOOL_KIND_NATIVE, NUM_THREADS, error + )); + svs_index_h index = svs_index_build_dynamic( + builder, data.data(), ids.data(), NUM_VECTORS, BLOCK_SIZE, error + ); + CATCH_REQUIRE(index != nullptr); + svs_search_params_h search_params = svs_search_params_create_vamana(100, error); + CATCH_REQUIRE(search_params != nullptr); + + std::unordered_set deleted; + + // Runs a filtered search and returns the mean recall against exact results restricted + // to the filter and to non-deleted IDs. Also checks that every returned ID is valid. + auto run = [&](svs_id_filter_interface& filter, auto keep) { + svs_search_results_t results = SVS_INIT_SEARCH_RESULTS(); + CATCH_REQUIRE(svs_index_search_topk( + index, queries.data(), NUM_QUERIES, K, &results, search_params, &filter, error + )); + CATCH_REQUIRE(svs_error_ok(error)); + CATCH_REQUIRE(results.num_queries == NUM_QUERIES); + + size_t hits = 0; + size_t expected = 0; + for (size_t q = 0; q < NUM_QUERIES; ++q) { + const size_t num_found = results.offsets[q + 1] - results.offsets[q]; + CATCH_REQUIRE(num_found <= K); + auto truth = restricted_ground_truth( + data, queries.data() + q * DIMENSION, DIMENSION, K, keep + ); + std::unordered_set truth_set(truth.begin(), truth.end()); + expected += truth.size(); + for (size_t j = 0; j < num_found; ++j) { + size_t id = results.indices[results.offsets[q] + j]; + CATCH_REQUIRE(keep(id)); + hits += truth_set.count(id); + } + } + svs_search_results_free(&results); + return static_cast(hits) / static_cast(expected); + }; + + for (size_t modulus : {2, 10, 100}) { + CATCH_SECTION("Recall with 1/" + std::to_string(modulus) + " selectivity") { + svs_id_filter_interface_ops ops = + SVS_INIT_ID_FILTER_OPS(filter_modulus, filter_rate_unknown); + svs_id_filter_interface filter = SVS_MAKE_INTERFACE(&modulus, ops); + auto keep = [&](size_t id) { return id % modulus == 0; }; + CATCH_REQUIRE(run(filter, keep) >= 0.95); + } + } + + CATCH_SECTION("Deleted points are never returned") { + // Delete half of the IDs that pass the filter. + std::vector to_delete; + for (size_t id = 0; id < NUM_VECTORS; id += 20) { + to_delete.push_back(id); + deleted.insert(id); + } + size_t deleted_count = 0; + CATCH_REQUIRE(svs_index_dynamic_delete_points( + index, to_delete.data(), to_delete.size(), &deleted_count, error + )); + CATCH_REQUIRE(deleted_count == to_delete.size()); + + size_t modulus = 10; + svs_id_filter_interface_ops ops = + SVS_INIT_ID_FILTER_OPS(filter_modulus, filter_rate_unknown); + svs_id_filter_interface filter = SVS_MAKE_INTERFACE(&modulus, ops); + auto keep = [&](size_t id) { return id % modulus == 0 && !deleted.count(id); }; + CATCH_REQUIRE(run(filter, keep) >= 0.95); + } + + svs_search_params_free(search_params); + svs_index_free(index); + svs_index_builder_free(builder); + svs_algorithm_free(algorithm); + svs_error_free(error); +} diff --git a/include/svs/concurrent/dynamic_search_buffer.h b/include/svs/concurrent/dynamic_search_buffer.h index d6811b433..24aa960fa 100644 --- a/include/svs/concurrent/dynamic_search_buffer.h +++ b/include/svs/concurrent/dynamic_search_buffer.h @@ -186,6 +186,19 @@ template > class MutableBuffer { best_unvisited_ = 0; } + /// Reset the visited set so it holds exactly the ids currently in the buffer. + /// Used when a search resumes with a larger window: candidates that were seen but + /// dropped from the buffer can then be inserted again. + void reset_visited_to_contents() { + if (!visited_set_enabled()) { + return; + } + visited_->reset(); + for (const auto& neighbor : candidates_) { + visited_->emplace(neighbor.id()); + } + } + size_t capacity() const { return candidates_.capacity(); } /// @brief Return the number of valid elements currently contained in the buffer. diff --git a/include/svs/concurrent/iterator.h b/include/svs/concurrent/iterator.h index 592e5f570..126889d12 100644 --- a/include/svs/concurrent/iterator.h +++ b/include/svs/concurrent/iterator.h @@ -53,7 +53,10 @@ template struct RestartInitializer { return; } - // Otherwise, sort the buffer to prepare for the next search. + // Otherwise, resume from the existing contents. Drop visited-set entries for ids + // no longer in the buffer so they can be re-inserted after the window grows. + buffer.reset_visited_to_contents(); + // Sort the buffer to prepare for the next search. buffer.sort(); } diff --git a/include/svs/index/vamana/dynamic_search_buffer.h b/include/svs/index/vamana/dynamic_search_buffer.h index 9e8f304e3..c0cf6f440 100644 --- a/include/svs/index/vamana/dynamic_search_buffer.h +++ b/include/svs/index/vamana/dynamic_search_buffer.h @@ -221,6 +221,19 @@ template > class MutableBuffer { best_unvisited_ = 0; } + /// Reset the visited set so it holds exactly the ids currently in the buffer. + /// Used when a search resumes with a larger window: candidates that were seen but + /// dropped from the buffer can then be inserted again. + void reset_visited_to_contents() { + if (!visited_set_enabled()) { + return; + } + visited_->reset(); + for (const auto& neighbor : candidates_) { + visited_->emplace(neighbor.id()); + } + } + size_t capacity() const { return candidates_.capacity(); } /// @brief Return the number of valid elements currently contained in the buffer. diff --git a/include/svs/index/vamana/iterator.h b/include/svs/index/vamana/iterator.h index 44d76fb81..20f2d2ccf 100644 --- a/include/svs/index/vamana/iterator.h +++ b/include/svs/index/vamana/iterator.h @@ -53,7 +53,10 @@ template struct RestartInitializer { return; } - // Otherwise, sort the buffer to prepare for the next search. + // Otherwise, resume from the existing contents. Drop visited-set entries for ids + // no longer in the buffer so they can be re-inserted after the window grows. + buffer.reset_visited_to_contents(); + // Sort the buffer to prepare for the next search. buffer.sort(); } diff --git a/include/svs/index/vamana/search_buffer.h b/include/svs/index/vamana/search_buffer.h index 62c255b1b..4bdd4d8c2 100644 --- a/include/svs/index/vamana/search_buffer.h +++ b/include/svs/index/vamana/search_buffer.h @@ -287,6 +287,19 @@ template > class SearchBuffer { best_unvisited_ = 0; } + /// Reset the visited set so it holds exactly the ids currently in the buffer. + /// Used when a search resumes with a larger window: candidates that were seen but + /// dropped from the buffer can then be inserted again. + void reset_visited_to_contents() { + if (!visited_set_enabled()) { + return; + } + visited_->reset(); + for (size_t i = 0; i < size_; ++i) { + visited_->emplace(candidates_[i].id()); + } + } + /// @brief Return the current number of valid elements in the buffer. size_t size() const { return size_; } diff --git a/tests/svs/index/vamana/search_buffer.cpp b/tests/svs/index/vamana/search_buffer.cpp index 8ecb698a5..232696090 100644 --- a/tests/svs/index/vamana/search_buffer.cpp +++ b/tests/svs/index/vamana/search_buffer.cpp @@ -31,6 +31,7 @@ #include #include #include +#include namespace vamana = svs::index::vamana; @@ -308,6 +309,41 @@ template void test_visited_set_interface() { } } +// After a reset, the visited set holds exactly the ids that are still in the buffer. +template void test_reset_visited_to_contents() { + auto x = Buffer{4}; + x.enable_visited_set(); + // Visit more ids than the buffer can hold; the farthest ones get dropped. + for (uint32_t i = 0; i < 20; ++i) { + x.emplace_visited(i); + auto n = detail::convert_to( + svs::lib::Type(), + SearchBufferNeighbor{{i, static_cast(i)}, true, false} + ); + if (i == 0) { + x.push_back(n); + } else { + x.insert(n); + } + } + std::unordered_set in_buffer; + for (const auto& n : x) { + in_buffer.insert(n.id()); + } + CATCH_REQUIRE(in_buffer.size() < 20); + CATCH_REQUIRE(!in_buffer.empty()); + + x.reset_visited_to_contents(); + for (uint32_t i = 0; i < 20; ++i) { + CATCH_REQUIRE(x.is_visited(i) == in_buffer.contains(i)); + } + + // A no-op when the visited set is disabled. + x.disable_visited_set(); + x.reset_visited_to_contents(); + CATCH_REQUIRE(!x.visited_set_enabled()); +} + } // namespace CATCH_TEST_CASE("Testing Search Buffer", "[core][search_buffer]") { @@ -545,6 +581,11 @@ CATCH_TEST_CASE("Testing Search Buffer", "[core][search_buffer]") { test_visited_set_interface>(); } + CATCH_SECTION("Reset Visited To Contents") { + test_reset_visited_to_contents>(); + test_reset_visited_to_contents>(); + } + CATCH_SECTION("Changing Size") { auto x = svs::index::vamana::SearchBuffer(3); CATCH_REQUIRE(x.size() == 0); From 359cfbd82b431a31a2396b85feb0e84b82cecd5b Mon Sep 17 00:00:00 2001 From: yuejiaointel Date: Wed, 28 Oct 2026 12:46:05 -0700 Subject: [PATCH 2/3] Remove the added recall tests --- bindings/c/tests/c_api_dynamic_index.cpp | 154 ----------------------- tests/svs/index/vamana/search_buffer.cpp | 41 ------ 2 files changed, 195 deletions(-) diff --git a/bindings/c/tests/c_api_dynamic_index.cpp b/bindings/c/tests/c_api_dynamic_index.cpp index 2a322c44d..372ef2158 100644 --- a/bindings/c/tests/c_api_dynamic_index.cpp +++ b/bindings/c/tests/c_api_dynamic_index.cpp @@ -24,12 +24,6 @@ #include "c_api_test_utils.h" // Standard library -#include -#include -#include -#include -#include -#include #include CATCH_TEST_CASE("C API Dynamic Index", "[c_api][index][dynamic]") { @@ -1051,151 +1045,3 @@ CATCH_TEST_CASE( svs_error_free(error); } } - -namespace { - -// Accepts IDs that are multiples of the modulus stored in `self`. -bool filter_modulus(void* self, size_t id) { - return (id % *static_cast(self)) == 0; -} -float filter_rate_unknown(void* /*self*/) { return 0.0f; } - -// Exact top-k (squared L2) over the IDs that pass `keep`. -template -std::vector restricted_ground_truth( - const std::vector& data, - const float* query, - size_t dimension, - size_t k, - const Keep& keep -) { - std::vector> scored; - for (size_t id = 0; id < data.size() / dimension; ++id) { - if (keep(id)) { - scored.emplace_back( - euclidean_distance(query, data.data() + id * dimension, dimension), id - ); - } - } - const size_t n = std::min(k, scored.size()); - std::partial_sort(scored.begin(), scored.begin() + n, scored.end()); - std::vector top; - for (size_t i = 0; i < n; ++i) { - top.push_back(scored[i].second); - } - return top; -} - -} // namespace - -CATCH_TEST_CASE( - "C API Dynamic Filtered Search recall", "[c_api][index][dynamic][search][filter]" -) { - const size_t NUM_VECTORS = 10000; - const size_t NUM_QUERIES = 20; - const size_t DIMENSION = 32; - const size_t K = 10; - const size_t NUM_THREADS = 4; - const size_t BLOCK_SIZE = 1024 * 1024; - - std::mt19937 rng(123); - std::uniform_real_distribution uniform(0.0f, 1.0f); - std::vector data(NUM_VECTORS * DIMENSION); - std::vector queries(NUM_QUERIES * DIMENSION); - for (auto& x : data) { - x = uniform(rng); - } - for (auto& x : queries) { - x = uniform(rng); - } - std::vector ids(NUM_VECTORS); - for (size_t i = 0; i < NUM_VECTORS; ++i) { - ids[i] = i; - } - - svs_error_h error = svs_error_create(); - svs_algorithm_h algorithm = svs_algorithm_create_vamana(32, 100, 100, error); - CATCH_REQUIRE(algorithm != nullptr); - svs_index_builder_h builder = svs_index_builder_create( - SVS_DISTANCE_METRIC_EUCLIDEAN, DIMENSION, algorithm, error - ); - CATCH_REQUIRE(builder != nullptr); - CATCH_REQUIRE(svs_index_builder_set_threadpool( - builder, SVS_THREADPOOL_KIND_NATIVE, NUM_THREADS, error - )); - svs_index_h index = svs_index_build_dynamic( - builder, data.data(), ids.data(), NUM_VECTORS, BLOCK_SIZE, error - ); - CATCH_REQUIRE(index != nullptr); - svs_search_params_h search_params = svs_search_params_create_vamana(100, error); - CATCH_REQUIRE(search_params != nullptr); - - std::unordered_set deleted; - - // Runs a filtered search and returns the mean recall against exact results restricted - // to the filter and to non-deleted IDs. Also checks that every returned ID is valid. - auto run = [&](svs_id_filter_interface& filter, auto keep) { - svs_search_results_t results = SVS_INIT_SEARCH_RESULTS(); - CATCH_REQUIRE(svs_index_search_topk( - index, queries.data(), NUM_QUERIES, K, &results, search_params, &filter, error - )); - CATCH_REQUIRE(svs_error_ok(error)); - CATCH_REQUIRE(results.num_queries == NUM_QUERIES); - - size_t hits = 0; - size_t expected = 0; - for (size_t q = 0; q < NUM_QUERIES; ++q) { - const size_t num_found = results.offsets[q + 1] - results.offsets[q]; - CATCH_REQUIRE(num_found <= K); - auto truth = restricted_ground_truth( - data, queries.data() + q * DIMENSION, DIMENSION, K, keep - ); - std::unordered_set truth_set(truth.begin(), truth.end()); - expected += truth.size(); - for (size_t j = 0; j < num_found; ++j) { - size_t id = results.indices[results.offsets[q] + j]; - CATCH_REQUIRE(keep(id)); - hits += truth_set.count(id); - } - } - svs_search_results_free(&results); - return static_cast(hits) / static_cast(expected); - }; - - for (size_t modulus : {2, 10, 100}) { - CATCH_SECTION("Recall with 1/" + std::to_string(modulus) + " selectivity") { - svs_id_filter_interface_ops ops = - SVS_INIT_ID_FILTER_OPS(filter_modulus, filter_rate_unknown); - svs_id_filter_interface filter = SVS_MAKE_INTERFACE(&modulus, ops); - auto keep = [&](size_t id) { return id % modulus == 0; }; - CATCH_REQUIRE(run(filter, keep) >= 0.95); - } - } - - CATCH_SECTION("Deleted points are never returned") { - // Delete half of the IDs that pass the filter. - std::vector to_delete; - for (size_t id = 0; id < NUM_VECTORS; id += 20) { - to_delete.push_back(id); - deleted.insert(id); - } - size_t deleted_count = 0; - CATCH_REQUIRE(svs_index_dynamic_delete_points( - index, to_delete.data(), to_delete.size(), &deleted_count, error - )); - CATCH_REQUIRE(deleted_count == to_delete.size()); - - size_t modulus = 10; - svs_id_filter_interface_ops ops = - SVS_INIT_ID_FILTER_OPS(filter_modulus, filter_rate_unknown); - svs_id_filter_interface filter = SVS_MAKE_INTERFACE(&modulus, ops); - auto keep = [&](size_t id) { return id % modulus == 0 && !deleted.count(id); }; - CATCH_REQUIRE(run(filter, keep) >= 0.95); - } - - svs_search_params_free(search_params); - svs_index_free(index); - svs_index_builder_free(builder); - svs_algorithm_free(algorithm); - svs_error_free(error); -} diff --git a/tests/svs/index/vamana/search_buffer.cpp b/tests/svs/index/vamana/search_buffer.cpp index 232696090..8ecb698a5 100644 --- a/tests/svs/index/vamana/search_buffer.cpp +++ b/tests/svs/index/vamana/search_buffer.cpp @@ -31,7 +31,6 @@ #include #include #include -#include namespace vamana = svs::index::vamana; @@ -309,41 +308,6 @@ template void test_visited_set_interface() { } } -// After a reset, the visited set holds exactly the ids that are still in the buffer. -template void test_reset_visited_to_contents() { - auto x = Buffer{4}; - x.enable_visited_set(); - // Visit more ids than the buffer can hold; the farthest ones get dropped. - for (uint32_t i = 0; i < 20; ++i) { - x.emplace_visited(i); - auto n = detail::convert_to( - svs::lib::Type(), - SearchBufferNeighbor{{i, static_cast(i)}, true, false} - ); - if (i == 0) { - x.push_back(n); - } else { - x.insert(n); - } - } - std::unordered_set in_buffer; - for (const auto& n : x) { - in_buffer.insert(n.id()); - } - CATCH_REQUIRE(in_buffer.size() < 20); - CATCH_REQUIRE(!in_buffer.empty()); - - x.reset_visited_to_contents(); - for (uint32_t i = 0; i < 20; ++i) { - CATCH_REQUIRE(x.is_visited(i) == in_buffer.contains(i)); - } - - // A no-op when the visited set is disabled. - x.disable_visited_set(); - x.reset_visited_to_contents(); - CATCH_REQUIRE(!x.visited_set_enabled()); -} - } // namespace CATCH_TEST_CASE("Testing Search Buffer", "[core][search_buffer]") { @@ -581,11 +545,6 @@ CATCH_TEST_CASE("Testing Search Buffer", "[core][search_buffer]") { test_visited_set_interface>(); } - CATCH_SECTION("Reset Visited To Contents") { - test_reset_visited_to_contents>(); - test_reset_visited_to_contents>(); - } - CATCH_SECTION("Changing Size") { auto x = svs::index::vamana::SearchBuffer(3); CATCH_REQUIRE(x.size() == 0); From dc19a07b6b55580bad2c83f577116ec4effbe83a Mon Sep 17 00:00:00 2001 From: yuejiaointel Date: Wed, 28 Oct 2026 13:40:13 -0700 Subject: [PATCH 3/3] Shorten comments --- include/svs/concurrent/dynamic_search_buffer.h | 5 ++--- include/svs/concurrent/iterator.h | 5 ++--- include/svs/index/vamana/dynamic_search_buffer.h | 5 ++--- include/svs/index/vamana/iterator.h | 5 ++--- include/svs/index/vamana/search_buffer.h | 5 ++--- 5 files changed, 10 insertions(+), 15 deletions(-) diff --git a/include/svs/concurrent/dynamic_search_buffer.h b/include/svs/concurrent/dynamic_search_buffer.h index 24aa960fa..8287da00c 100644 --- a/include/svs/concurrent/dynamic_search_buffer.h +++ b/include/svs/concurrent/dynamic_search_buffer.h @@ -186,9 +186,8 @@ template > class MutableBuffer { best_unvisited_ = 0; } - /// Reset the visited set so it holds exactly the ids currently in the buffer. - /// Used when a search resumes with a larger window: candidates that were seen but - /// dropped from the buffer can then be inserted again. + /// Keep only the ids now in the buffer in the visited set, so dropped ids can be + /// re-inserted. void reset_visited_to_contents() { if (!visited_set_enabled()) { return; diff --git a/include/svs/concurrent/iterator.h b/include/svs/concurrent/iterator.h index 126889d12..15031e20c 100644 --- a/include/svs/concurrent/iterator.h +++ b/include/svs/concurrent/iterator.h @@ -53,10 +53,9 @@ template struct RestartInitializer { return; } - // Otherwise, resume from the existing contents. Drop visited-set entries for ids - // no longer in the buffer so they can be re-inserted after the window grows. + // Let ids dropped from the buffer be inserted again in the larger window. buffer.reset_visited_to_contents(); - // Sort the buffer to prepare for the next search. + // Otherwise, sort the buffer to prepare for the next search. buffer.sort(); } diff --git a/include/svs/index/vamana/dynamic_search_buffer.h b/include/svs/index/vamana/dynamic_search_buffer.h index c0cf6f440..0d0ef55c8 100644 --- a/include/svs/index/vamana/dynamic_search_buffer.h +++ b/include/svs/index/vamana/dynamic_search_buffer.h @@ -221,9 +221,8 @@ template > class MutableBuffer { best_unvisited_ = 0; } - /// Reset the visited set so it holds exactly the ids currently in the buffer. - /// Used when a search resumes with a larger window: candidates that were seen but - /// dropped from the buffer can then be inserted again. + /// Keep only the ids now in the buffer in the visited set, so dropped ids can be + /// re-inserted. void reset_visited_to_contents() { if (!visited_set_enabled()) { return; diff --git a/include/svs/index/vamana/iterator.h b/include/svs/index/vamana/iterator.h index 20f2d2ccf..e7e0a5a1c 100644 --- a/include/svs/index/vamana/iterator.h +++ b/include/svs/index/vamana/iterator.h @@ -53,10 +53,9 @@ template struct RestartInitializer { return; } - // Otherwise, resume from the existing contents. Drop visited-set entries for ids - // no longer in the buffer so they can be re-inserted after the window grows. + // Let ids dropped from the buffer be inserted again in the larger window. buffer.reset_visited_to_contents(); - // Sort the buffer to prepare for the next search. + // Otherwise, sort the buffer to prepare for the next search. buffer.sort(); } diff --git a/include/svs/index/vamana/search_buffer.h b/include/svs/index/vamana/search_buffer.h index 4bdd4d8c2..a6e8b72b4 100644 --- a/include/svs/index/vamana/search_buffer.h +++ b/include/svs/index/vamana/search_buffer.h @@ -287,9 +287,8 @@ template > class SearchBuffer { best_unvisited_ = 0; } - /// Reset the visited set so it holds exactly the ids currently in the buffer. - /// Used when a search resumes with a larger window: candidates that were seen but - /// dropped from the buffer can then be inserted again. + /// Keep only the ids now in the buffer in the visited set, so dropped ids can be + /// re-inserted. void reset_visited_to_contents() { if (!visited_set_enabled()) { return;