From d8cde5dc52832bda3a430851e0d0bac3ae1d92e5 Mon Sep 17 00:00:00 2001 From: gouyt13clear Date: Sun, 1 Nov 2026 00:30:38 +0800 Subject: [PATCH 1/3] refactor!: rename query factor accessors with the g_ prefix Query correction factors now share the g_ prefix already used by g_add() and g_error(): - k1xsumq() -> g_k1xsumq() on SplitSingleQuery, BatchQuery, SplitBatchQuery and symqg::QuantizedQuery - kbxsumq() -> g_kbxsumq() on SplitSingleQuery and SplitBatchQuery - kbase_sumq() -> g_kbase_sumq() on SplitSingleQuery The old names are removed. Parameters, locals and private members that carry the same value are renamed to match. The stable core API v1 is unchanged; only a private member of its prepared query is renamed. Factor values and numerical behavior are unchanged. --- include/rabitqlib/clustering/qgkmeans.hpp | 2 +- include/rabitqlib/core/api.hpp | 2 +- include/rabitqlib/core/encoder.hpp | 8 +++---- include/rabitqlib/core/estimator.hpp | 16 ++++++------- include/rabitqlib/core/query.hpp | 8 +++---- .../fastscan/detail/qg_estimator.hpp | 4 ++-- include/rabitqlib/fastscan/query.hpp | 10 ++++---- include/rabitqlib/index/symqg/qg.hpp | 14 +++++------ src/core/api.cpp | 6 ++--- src/fastscan/estimator_kernels.hpp | 4 ++-- src/index/qg.cpp | 24 +++++++++++-------- tests/failures/compact_codes_failure_test.cpp | 4 ++-- .../clustering/rabitqkmeans_test.cpp | 2 +- tests/unit/rabitqlib/fastscan/api_test.cpp | 12 +++++----- .../unit/rabitqlib/fastscan/fastscan_test.cpp | 6 ++--- tests/unit/rabitqlib/index/hnsw_test.cpp | 2 +- tests/unit/rabitqlib/index/qg_test.cpp | 6 ++--- .../quantization/xy_quantization_test.cpp | 2 +- 18 files changed, 68 insertions(+), 64 deletions(-) diff --git a/include/rabitqlib/clustering/qgkmeans.hpp b/include/rabitqlib/clustering/qgkmeans.hpp index 6115a498..fd2f7d77 100644 --- a/include/rabitqlib/clustering/qgkmeans.hpp +++ b/include/rabitqlib/clustering/qgkmeans.hpp @@ -290,7 +290,7 @@ class QGAssigner { hint, query_sums.cached(i) ); - query_sums.store(i, scratch.batch_query.k1xsumq()); + query_sums.store(i, scratch.batch_query.g_k1xsumq()); if (graph_->is_quantized()) { // Keep objectives and returned distances relative to the original // centroids; graph search scores are quantized estimates. diff --git a/include/rabitqlib/core/api.hpp b/include/rabitqlib/core/api.hpp index 9262a218..b5ad87b3 100644 --- a/include/rabitqlib/core/api.hpp +++ b/include/rabitqlib/core/api.hpp @@ -101,7 +101,7 @@ class QueryState { size_t total_bits_ = 0; MetricType metric_ = METRIC_L2; float g_add_ = 0; - float k1xsumq_ = 0; + float g_k1xsumq_ = 0; float (*extra_ip_)(const float*, const uint8_t*, size_t) = nullptr; static float compute_checked( diff --git a/include/rabitqlib/core/encoder.hpp b/include/rabitqlib/core/encoder.hpp index 32d8bdaf..a5badf01 100644 --- a/include/rabitqlib/core/encoder.hpp +++ b/include/rabitqlib/core/encoder.hpp @@ -295,10 +295,10 @@ inline void reconstruct_full_vec( namespace detail { template inline T full_est_dist_from_ip( - T ip, size_t bits, T f_add, T f_rescale, T g_add, T k1xsumq + T ip, size_t bits, T f_add, T f_rescale, T g_add, T g_k1xsumq ) { return f_add + g_add + - (f_rescale * (ip + k1xsumq * static_cast((1 << bits) - 1))); + (f_rescale * (ip + g_k1xsumq * static_cast((1 << bits) - 1))); } } // namespace detail @@ -312,10 +312,10 @@ inline T full_est_dist( T f_add, T f_rescale, T g_add, - T k1xsumq + T g_k1xsumq ) { return detail::full_est_dist_from_ip( - ip_func(query, quantized_vec, dim), bits, f_add, f_rescale, g_add, k1xsumq + ip_func(query, quantized_vec, dim), bits, f_add, f_rescale, g_add, g_k1xsumq ); } } // namespace rabitqlib::quant diff --git a/include/rabitqlib/core/estimator.hpp b/include/rabitqlib/core/estimator.hpp index b3073241..f414dccb 100644 --- a/include/rabitqlib/core/estimator.hpp +++ b/include/rabitqlib/core/estimator.hpp @@ -42,7 +42,7 @@ inline float split_distance_boosting( (cur_ex.f_rescale_ex() * (static_cast(1 << ex_bits) * ip_x0_qr + ip_func(query_wrapper.rotated_query(), cur_ex.ex_code(), padded_dim) + - query_wrapper.kbxsumq())); + query_wrapper.g_kbxsumq())); return ex_dist; } @@ -77,7 +77,7 @@ inline void split_single_fulldist_direct( (cur_ex.f_rescale_ex() * (static_cast(1 << ex_bits) * ip_x0_qr + ip_func(query_wrapper.rotated_query(), cur_ex.ex_code(), padded_dim) + - query_wrapper.kbxsumq())); + query_wrapper.g_kbxsumq())); lower_bound = estimated_distance - (cur_bin.f_error() * g_error / static_cast(1 << ex_bits)); @@ -106,7 +106,7 @@ inline void split_single_fulldist( (cur_ex.f_rescale_ex() * (static_cast(1 << ex_bits) * ip_x0_qr + ip_func(query_wrapper.rotated_query(), cur_ex.ex_code(), padded_dim) + - query_wrapper.kbxsumq())); + query_wrapper.g_kbxsumq())); lower_bound = estimated_distance - (cur_bin.f_error() * g_error / static_cast(1 << ex_bits)); @@ -134,7 +134,7 @@ inline void split_single_estdist( ); estimated_distance = cur_bin.f_add() + g_add + - (cur_bin.f_rescale() * (ip_x0_qr + query_wrapper.k1xsumq())); + (cur_bin.f_rescale() * (ip_x0_qr + query_wrapper.g_k1xsumq())); lower_bound = estimated_distance - (cur_bin.f_error() * g_error); } @@ -163,7 +163,7 @@ inline void split_single_estdist_direct( ); estimated_distance = cur_bin.f_add() + g_add + - (cur_bin.f_rescale() * (ip_x0_qr + query_wrapper.k1xsumq())); + (cur_bin.f_rescale() * (ip_x0_qr + query_wrapper.g_k1xsumq())); lower_bound = estimated_distance - (cur_bin.f_error() * g_error); } @@ -185,7 +185,7 @@ inline void xy_single_base_dist( ip_base = base_ip_func(query_wrapper.rotated_query(), cur_base.base_code(), padded_dim); estimated_distance = cur_base.f_add() + g_add + - (cur_base.f_rescale() * (ip_base + query_wrapper.kbase_sumq())); + (cur_base.f_rescale() * (ip_base + query_wrapper.g_kbase_sumq())); lower_bound = estimated_distance - (cur_base.f_error() * g_error); } @@ -211,7 +211,7 @@ inline void xy_single_full_dist( if (ex_bits == 0) { estimated_distance = cur_base.f_add() + g_add + - (cur_base.f_rescale() * (ip_base + query_wrapper.kbase_sumq())); + (cur_base.f_rescale() * (ip_base + query_wrapper.g_kbase_sumq())); lower_bound = estimated_distance - (cur_base.f_error() * g_error); return; } @@ -222,7 +222,7 @@ inline void xy_single_full_dist( ex_ip_func(query_wrapper.rotated_query(), cur_ex.ex_code(), padded_dim); estimated_distance = cur_ex.f_add_ex() + g_add + - (cur_ex.f_rescale_ex() * (ip + query_wrapper.kbxsumq())); + (cur_ex.f_rescale_ex() * (ip + query_wrapper.g_kbxsumq())); lower_bound = estimated_distance - (cur_base.f_error() * g_error / static_cast(1 << ex_bits)); diff --git a/include/rabitqlib/core/query.hpp b/include/rabitqlib/core/query.hpp index 46a5746d..1acc7c74 100644 --- a/include/rabitqlib/core/query.hpp +++ b/include/rabitqlib/core/query.hpp @@ -104,12 +104,12 @@ class SplitSingleQuery { [[nodiscard]] T vl() const { return vl_; } - [[nodiscard]] T k1xsumq() const { return G_k1xSumq_; } + [[nodiscard]] T g_k1xsumq() const { return G_k1xSumq_; } - // Equals k1xsumq() when base_bits == 1. - [[nodiscard]] T kbase_sumq() const { return G_kbaseSumq_; } + // Equals g_k1xsumq() when base_bits == 1. + [[nodiscard]] T g_kbase_sumq() const { return G_kbaseSumq_; } - [[nodiscard]] T kbxsumq() const { return G_kbxSumq_; } + [[nodiscard]] T g_kbxsumq() const { return G_kbxSumq_; } [[nodiscard]] T g_add() const { return G_add_; } diff --git a/include/rabitqlib/fastscan/detail/qg_estimator.hpp b/include/rabitqlib/fastscan/detail/qg_estimator.hpp index a578e40e..5fcfcc96 100644 --- a/include/rabitqlib/fastscan/detail/qg_estimator.hpp +++ b/include/rabitqlib/fastscan/detail/qg_estimator.hpp @@ -33,10 +33,10 @@ static inline void qg_batch_estdist_impl( const T delta = query_wrapper.delta(); const T sum_vl_lut = query_wrapper.sum_vl_lut(); const T g_add = query_wrapper.g_add(); - const T k1xsumq = query_wrapper.k1xsumq(); + const T g_k1xsumq = query_wrapper.g_k1xsumq(); for (size_t lane = 0; lane < fastscan::kBatchSize; ++lane) { const T ip = - delta * static_cast(accumulated_values[lane]) + sum_vl_lut + k1xsumq; + delta * static_cast(accumulated_values[lane]) + sum_vl_lut + g_k1xsumq; estimated_distances[lane] = f_add_values[lane] + g_add + f_rescale_values[lane] * ip; } diff --git a/include/rabitqlib/fastscan/query.hpp b/include/rabitqlib/fastscan/query.hpp index 417e4b5e..624c4d0c 100644 --- a/include/rabitqlib/fastscan/query.hpp +++ b/include/rabitqlib/fastscan/query.hpp @@ -56,7 +56,7 @@ class BatchQuery { [[nodiscard]] T sum_vl_lut() const { return lookup_table_.sum_vl(); } - [[nodiscard]] T k1xsumq() const { return G_k1xSumq_; } + [[nodiscard]] T g_k1xsumq() const { return G_k1xSumq_; } [[nodiscard]] T g_add() const { return G_add_; } @@ -78,7 +78,7 @@ class BatchQuery { size_t padded_dim, T* lut_scratch, MetricType metric_type = METRIC_L2, - const T* k1xsumq = nullptr + const T* g_k1xsumq = nullptr ) { validate_metric_type(metric_type); // Reject invalid input before changing the correction metadata. @@ -86,7 +86,7 @@ class BatchQuery { metric_type_ = metric_type; G_k1xSumq_ = - k1xsumq != nullptr ? *k1xsumq : query_correction(rotated_query, padded_dim); + g_k1xsumq != nullptr ? *g_k1xsumq : query_correction(rotated_query, padded_dim); G_add_ = 0; } @@ -201,9 +201,9 @@ class SplitBatchQuery { [[nodiscard]] T sum_vl_lut() const { return lookup_table_.sum_vl(); } - [[nodiscard]] T k1xsumq() const { return G_k1xSumq_; } + [[nodiscard]] T g_k1xsumq() const { return G_k1xSumq_; } - [[nodiscard]] T kbxsumq() const { return G_kbxSumq_; } + [[nodiscard]] T g_kbxsumq() const { return G_kbxSumq_; } [[nodiscard]] T g_add() const { return G_add_; } diff --git a/include/rabitqlib/index/symqg/qg.hpp b/include/rabitqlib/index/symqg/qg.hpp index e9c591cb..64365818 100644 --- a/include/rabitqlib/index/symqg/qg.hpp +++ b/include/rabitqlib/index/symqg/qg.hpp @@ -29,7 +29,7 @@ namespace rabitqlib::symqg { class QuantizedQuery { private: const float* rotated_query_; - float k1xsumq_; + float g_k1xsumq_; float g_add_; public: @@ -41,7 +41,7 @@ class QuantizedQuery { ); QuantizedQuery(const float* rotated_query, const QuantizedQuery& prepared); [[nodiscard]] const float* rotated_query() const; - [[nodiscard]] float k1xsumq() const; + [[nodiscard]] float g_k1xsumq() const; [[nodiscard]] float g_add() const; }; @@ -62,7 +62,7 @@ void search_with_workspace( float* distances, SearchScratchView scratch, PID hint = kPidMax, - const float* k1xsumq = nullptr + const float* g_k1xsumq = nullptr ); } // namespace detail @@ -77,7 +77,7 @@ class QuantizedGraph { float* distances, detail::SearchScratchView scratch, PID hint, - const float* k1xsumq + const float* g_k1xsumq ); friend struct QGConstructionTestAccess; @@ -181,7 +181,7 @@ class QuantizedGraph { float* distances, detail::SearchScratchView scratch, PID hint, - const float* k1xsumq + const float* g_k1xsumq ); float point_distance( @@ -340,7 +340,7 @@ class QuantizedGraph { * std::invalid_argument. Concurrent searches require separate scratch * buffers, including visited sets. * - * An optional k1xsumq must come from BatchQuery::k1xsumq() for the same + * An optional g_k1xsumq must come from BatchQuery::g_k1xsumq() for the same * query and this graph's rotation. A null pointer recomputes the sum. */ void search_with_scratch( @@ -356,7 +356,7 @@ class QuantizedGraph { buffer::SearchBuffer& result_pool, VisitedSet& visited, PID hint = kPidMax, - const float* k1xsumq = nullptr + const float* g_k1xsumq = nullptr ); [[nodiscard]] size_t padded_dim() const { return padded_dim_; } diff --git a/src/core/api.cpp b/src/core/api.cpp index 88cfdf9e..37c6ea92 100644 --- a/src/core/api.cpp +++ b/src/core/api.cpp @@ -389,13 +389,13 @@ QueryState prepare_query( state.total_bits_ = parameters.total_bits; state.metric_ = parameters.metric; state.extra_ip_ = select_excode_ipfunc(parameters.total_bits - 1); - state.k1xsumq_ = std::accumulate(query, query + parameters.dim, 0.0F) * -0.5F; + state.g_k1xsumq_ = std::accumulate(query, query + parameters.dim, 0.0F) * -0.5F; state.g_add_ = parameters.metric == METRIC_L2 ? (centroid == nullptr ? l2norm_sqr(query, parameters.dim) : euclidean_sqr(query, centroid, parameters.dim)) : (centroid == nullptr ? 0.0F : -dot_product(query, centroid, parameters.dim)); - if (!std::isfinite(state.g_add_) || !std::isfinite(state.k1xsumq_)) { + if (!std::isfinite(state.g_add_) || !std::isfinite(state.g_k1xsumq_)) { throw std::overflow_error("Query factors are not representable in float32"); } return state; @@ -446,7 +446,7 @@ float QueryState::compute_checked( factors.add, factors.rescale, query_wrapper.g_add_, - query_wrapper.k1xsumq_ + query_wrapper.g_k1xsumq_ ); if (!std::isfinite(distance)) { throw std::overflow_error("Estimated distance is not representable in float32"); diff --git a/src/fastscan/estimator_kernels.hpp b/src/fastscan/estimator_kernels.hpp index d78fe836..e9cf897d 100644 --- a/src/fastscan/estimator_kernels.hpp +++ b/src/fastscan/estimator_kernels.hpp @@ -47,12 +47,12 @@ inline void split_batch_estdist_impl( const float delta = query_wrapper.delta(); const float sum_vl_lut = query_wrapper.sum_vl_lut(); const float g_add = query_wrapper.g_add(); - const float k1xsumq = query_wrapper.k1xsumq(); + const float g_k1xsumq = query_wrapper.g_k1xsumq(); const float g_error = query_wrapper.g_error(); for (size_t lane = 0; lane < fastscan::kBatchSize; ++lane) { const float ip = delta * static_cast(accumulated_values[lane]) + sum_vl_lut; const float estimated = - f_add_values[lane] + g_add + f_rescale_values[lane] * (ip + k1xsumq); + f_add_values[lane] + g_add + f_rescale_values[lane] * (ip + g_k1xsumq); ip_x0_qr[lane] = ip; estimated_distances[lane] = estimated; lower_bounds[lane] = estimated - f_error_values[lane] * g_error; diff --git a/src/index/qg.cpp b/src/index/qg.cpp index e3253e09..f1eff8d2 100644 --- a/src/index/qg.cpp +++ b/src/index/qg.cpp @@ -54,14 +54,16 @@ QuantizedQuery::QuantizedQuery( MetricType metric_type ) : rotated_query_(rotated_query) { - k1xsumq_ = std::accumulate(rotated_query, rotated_query + padded_dim, 0.0F) / -2; + g_k1xsumq_ = std::accumulate(rotated_query, rotated_query + padded_dim, 0.0F) / -2; g_add_ = metric_type == METRIC_IP ? -dot_product(rotated_query, centroid, padded_dim) : euclidean_sqr(rotated_query, centroid, padded_dim); } QuantizedQuery::QuantizedQuery(const float* rotated_query, const QuantizedQuery& prepared) - : rotated_query_(rotated_query), k1xsumq_(prepared.k1xsumq_), g_add_(prepared.g_add_) {} + : rotated_query_(rotated_query) + , g_k1xsumq_(prepared.g_k1xsumq_) + , g_add_(prepared.g_add_) {} const float* QuantizedQuery::rotated_query() const { return rotated_query_; } -float QuantizedQuery::k1xsumq() const { return k1xsumq_; } +float QuantizedQuery::g_k1xsumq() const { return g_k1xsumq_; } float QuantizedQuery::g_add() const { return g_add_; } size_t QuantizedGraph::checked_add(size_t lhs, size_t rhs) { @@ -695,7 +697,7 @@ float QuantizedGraph::quantized_distance( data.f_add_ex(), data.f_rescale_ex(), query.g_add(), - query.k1xsumq() + query.g_k1xsumq() ); } @@ -780,7 +782,7 @@ void QuantizedGraph::search_with_scratch( buffer::SearchBuffer& result_pool, VisitedSet& visited, PID hint, - const float* k1xsumq + const float* g_k1xsumq ) { detail::search_with_workspace( *this, @@ -796,7 +798,7 @@ void QuantizedGraph::search_with_scratch( result_pool, visited}, hint, - k1xsumq + g_k1xsumq ); } @@ -808,11 +810,13 @@ void detail::search_with_workspace( float* distances, detail::SearchScratchView scratch, PID hint, - const float* k1xsumq + const float* g_k1xsumq ) { graph.validate_search(query, k, result_ids, distances, graph.default_ef_search_); check_coordinates(query, graph.dim_, graph.dim_, "QuantizedGraph search query"); - graph.search_with_scratch_impl(query, k, result_ids, distances, scratch, hint, k1xsumq); + graph.search_with_scratch_impl( + query, k, result_ids, distances, scratch, hint, g_k1xsumq + ); } void QuantizedGraph::search_with_scratch_impl( @@ -822,7 +826,7 @@ void QuantizedGraph::search_with_scratch_impl( float* __restrict__ distances, detail::SearchScratchView scratch, PID hint, - const float* k1xsumq + const float* g_k1xsumq ) { if (scratch.rotated_query == nullptr || scratch.estimated_distances == nullptr || scratch.lookup_table == nullptr) { @@ -844,7 +848,7 @@ void QuantizedGraph::search_with_scratch_impl( } const auto* quantized = quantized_query ? &*quantized_query : nullptr; scratch.batch_query.reset( - scratch.rotated_query, padded_dim_, scratch.lookup_table, metric_type_, k1xsumq + scratch.rotated_query, padded_dim_, scratch.lookup_table, metric_type_, g_k1xsumq ); scratch.search_pool.clear(); scratch.result_pool.clear(); diff --git a/tests/failures/compact_codes_failure_test.cpp b/tests/failures/compact_codes_failure_test.cpp index c74c077a..45981c36 100644 --- a/tests/failures/compact_codes_failure_test.cpp +++ b/tests/failures/compact_codes_failure_test.cpp @@ -370,8 +370,8 @@ TEST(SplitBatchQueryAllocation, InitialPreparationAndGrowthFailurePreserveState) EXPECT_EQ(query.use_hacc(), original.use_hacc()); EXPECT_EQ(query.delta(), original.delta()); EXPECT_EQ(query.sum_vl_lut(), original.sum_vl_lut()); - EXPECT_EQ(query.k1xsumq(), original.k1xsumq()); - EXPECT_EQ(query.kbxsumq(), original.kbxsumq()); + EXPECT_EQ(query.g_k1xsumq(), original.g_k1xsumq()); + EXPECT_EQ(query.g_kbxsumq(), original.g_kbxsumq()); EXPECT_EQ(query.g_add(), original.g_add()); EXPECT_EQ(query.g_error(), original.g_error()); if (prepared) { diff --git a/tests/unit/rabitqlib/clustering/rabitqkmeans_test.cpp b/tests/unit/rabitqlib/clustering/rabitqkmeans_test.cpp index 3054e9df..d7d25c22 100644 --- a/tests/unit/rabitqlib/clustering/rabitqkmeans_test.cpp +++ b/tests/unit/rabitqlib/clustering/rabitqkmeans_test.cpp @@ -105,7 +105,7 @@ std::vector scalar_rabitq_assignments( accumulator += prepared.lut()[group * 16 + code]; } const float ip = prepared.delta() * static_cast(accumulator) + - prepared.sum_vl_lut() + prepared.k1xsumq(); + prepared.sum_vl_lut() + prepared.g_k1xsumq(); const float estimate = batch.f_add()[index] + prepared.g_add() + batch.f_rescale()[index] * ip; if (estimate < best[point]) { diff --git a/tests/unit/rabitqlib/fastscan/api_test.cpp b/tests/unit/rabitqlib/fastscan/api_test.cpp index dcba9c26..970b4705 100644 --- a/tests/unit/rabitqlib/fastscan/api_test.cpp +++ b/tests/unit/rabitqlib/fastscan/api_test.cpp @@ -187,8 +187,8 @@ TEST(FastScanApiTest, SplitQueryResetMatchesFreshPreparationAndScoring) { EXPECT_EQ(reused.g_error(), 0); EXPECT_EQ(reused.delta(), fresh.delta()); EXPECT_EQ(reused.sum_vl_lut(), fresh.sum_vl_lut()); - EXPECT_EQ(reused.k1xsumq(), fresh.k1xsumq()); - EXPECT_EQ(reused.kbxsumq(), fresh.kbxsumq()); + EXPECT_EQ(reused.g_k1xsumq(), fresh.g_k1xsumq()); + EXPECT_EQ(reused.g_kbxsumq(), fresh.g_kbxsumq()); const size_t bytes = dim * 4 * (hacc ? 2 : 1); EXPECT_TRUE(std::equal(fresh.lut(), fresh.lut() + bytes, reused.lut())); scratch.fill(123); @@ -306,7 +306,7 @@ TEST(FastScanApiTest, SplitQueryInitializationValidatesParametersConsistently) { metric, hacc ); - EXPECT_EQ(query.kbxsumq(), fresh.kbxsumq()); + EXPECT_EQ(query.g_kbxsumq(), fresh.g_kbxsumq()); EXPECT_EQ(query.use_hacc(), fresh.use_hacc()); } } @@ -380,8 +380,8 @@ TEST(FastScanApiTest, RejectedSplitQueryResetPreservesPreparedState) { EXPECT_EQ(query.use_hacc(), original.use_hacc()); EXPECT_EQ(query.delta(), original.delta()); EXPECT_EQ(query.sum_vl_lut(), original.sum_vl_lut()); - EXPECT_EQ(query.k1xsumq(), original.k1xsumq()); - EXPECT_EQ(query.kbxsumq(), original.kbxsumq()); + EXPECT_EQ(query.g_k1xsumq(), original.g_k1xsumq()); + EXPECT_EQ(query.g_kbxsumq(), original.g_kbxsumq()); EXPECT_EQ(query.g_add(), original.g_add()); EXPECT_EQ(query.g_error(), original.g_error()); EXPECT_TRUE(std::equal(bytes.begin(), bytes.end(), query.lut())); @@ -513,7 +513,7 @@ TEST(FastScanApiTest, DoubleEncodingQueryAndScoringWithNonzeroCentroid) { values.data(), dim, 3, scratch.data(), hacc_scratch.data(), metric, hacc ); EXPECT_EQ(reset_split.delta(), split.delta()); - EXPECT_EQ(reset_split.kbxsumq(), split.kbxsumq()); + EXPECT_EQ(reset_split.g_kbxsumq(), split.g_kbxsumq()); EXPECT_TRUE( std::equal(split.lut(), split.lut() + bytes, reset_split.lut()) ); diff --git a/tests/unit/rabitqlib/fastscan/fastscan_test.cpp b/tests/unit/rabitqlib/fastscan/fastscan_test.cpp index 25260ff2..4e02cd5d 100644 --- a/tests/unit/rabitqlib/fastscan/fastscan_test.cpp +++ b/tests/unit/rabitqlib/fastscan/fastscan_test.cpp @@ -186,7 +186,7 @@ TEST(FastScanBatchEstimatorTest, RejectedQueryResetPreservesMetricAndCorrections } query.set_g_add(9); EXPECT_EQ(query.g_add(), metric == METRIC_L2 ? 9 : 8); - EXPECT_EQ(query.k1xsumq(), -16.0F); + EXPECT_EQ(query.g_k1xsumq(), -16.0F); } } @@ -648,7 +648,7 @@ TEST(FastScanBatchEstimatorTest, BackendsMatchScalarCorrectionAcrossChunksAndTai expected_dist[lane] = static_cast(batch.f_add()[lane]) + q.g_add() + static_cast(batch.f_rescale()[lane]) * - (expected_ip[lane] + q.k1xsumq()); + (expected_ip[lane] + q.g_k1xsumq()); expected_low[lane] = expected_dist[lane] - static_cast(batch.f_error()[lane]) * q.g_error(); @@ -698,7 +698,7 @@ TEST(FastScanHighAccuracyTest, AccumulatesAcrossChunks) { const int32_t scalar_accumulator = int32_t{65535} * static_cast(kDim / 4); const float expected_ip = q_obj.delta() * static_cast(scalar_accumulator) + q_obj.sum_vl_lut(); - const float expected_distance = expected_ip + q_obj.k1xsumq(); + const float expected_distance = expected_ip + q_obj.g_k1xsumq(); std::array estimated{}; std::array lower{}; diff --git a/tests/unit/rabitqlib/index/hnsw_test.cpp b/tests/unit/rabitqlib/index/hnsw_test.cpp index 98bcd986..aaa46a3d 100644 --- a/tests/unit/rabitqlib/index/hnsw_test.cpp +++ b/tests/unit/rabitqlib/index/hnsw_test.cpp @@ -121,7 +121,7 @@ TEST(HnswQuantQueryTest, MovesPreserveOwnedQueryAndLeaveSourceReusable) { EXPECT_EQ(retained.id, 7U); EXPECT_EQ(retained.prepared->rotated_query(), retained.rotated.data()); EXPECT_EQ(retained.prepared->rotated_query()[63], 0.5F); - EXPECT_EQ(retained.prepared->kbxsumq(), -240.0F); + EXPECT_EQ(retained.prepared->g_kbxsumq(), -240.0F); EXPECT_EQ(retained.prepared->g_add(), -2.0F); auto& alias = retained; retained = std::move(alias); diff --git a/tests/unit/rabitqlib/index/qg_test.cpp b/tests/unit/rabitqlib/index/qg_test.cpp index 260bc1e3..dbf7983b 100644 --- a/tests/unit/rabitqlib/index/qg_test.cpp +++ b/tests/unit/rabitqlib/index/qg_test.cpp @@ -462,11 +462,11 @@ TEST(BatchQueryTest, PreparedSumPreservesLookupTableAndMetric) { } for (const MetricType metric : {METRIC_L2, METRIC_IP}) { reference.reset(query.data(), dimension, scratch.data(), metric); - const float prepared_sum = reference.k1xsumq(); + const float prepared_sum = reference.g_k1xsumq(); prepared.set_g_add(17.0F); prepared.reset(query.data(), dimension, scratch.data(), metric, &prepared_sum); EXPECT_EQ(prepared.g_add(), 0.0F); - EXPECT_EQ(prepared.k1xsumq(), reference.k1xsumq()); + EXPECT_EQ(prepared.g_k1xsumq(), reference.g_k1xsumq()); EXPECT_EQ(prepared.delta(), reference.delta()); EXPECT_EQ(prepared.sum_vl_lut(), reference.sum_vl_lut()); EXPECT_TRUE( @@ -1620,7 +1620,7 @@ TEST(QgEstimatorTest, AccumulatesBeyondUint16Range) { } const float expected = q_obj.delta() * static_cast(scalar_accumulator) + - q_obj.sum_vl_lut() + q_obj.k1xsumq(); + q_obj.sum_vl_lut() + q_obj.g_k1xsumq(); std::array estimated{}; qg_batch_estdist(batch_data.data(), q_obj, padded_dim, estimated.data()); diff --git a/tests/unit/rabitqlib/quantization/xy_quantization_test.cpp b/tests/unit/rabitqlib/quantization/xy_quantization_test.cpp index 867a635d..6ce1e5c7 100644 --- a/tests/unit/rabitqlib/quantization/xy_quantization_test.cpp +++ b/tests/unit/rabitqlib/quantization/xy_quantization_test.cpp @@ -436,7 +436,7 @@ TEST(XyQuantizationTest, StorageCostsBasePlusExtraBitsOnly) { // End-to-end backward-compat check: at base_bits=1, quantize_xy_single + // xy_single_base_dist + split_distance_boosting must agree with today's production -// formula ((1< Date: Sun, 1 Nov 2026 00:30:38 +0800 Subject: [PATCH 2/3] docs: use public factor names and consistent example variables The estimator page now names data factors after the f_ layout accessors and query factors after the g_ query accessors instead of F_/G_ labels that do not exist in the code. Examples use estimated_distance, error_bound, lower_bound and upper_bound (with an _ex suffix for the full-code refinement). Cluster-ID arrays in the IVF page and indexing samples are named cluster_ids instead of gt_type/cids, the unused gt_type alias in the SymphonyQG indexing sample is removed, the Python IVF example passes max_elements, and the naming table drops node_id, which the code does not use. --- docs/development.md | 4 +- docs/docs/index/ivf.md | 12 ++--- docs/docs/rabitq/estimator.md | 78 +++++++++++++++-------------- docs/docs/rabitq/quantizer.md | 16 +++--- sample/cpp/hnsw_rabitq_indexing.cpp | 10 ++-- sample/cpp/ivf_rabitq_indexing.cpp | 10 ++-- sample/cpp/symqg_indexing.cpp | 1 - 7 files changed, 66 insertions(+), 65 deletions(-) diff --git a/docs/development.md b/docs/development.md index 5e049d76..0da369cc 100644 --- a/docs/development.md +++ b/docs/development.md @@ -604,7 +604,7 @@ These conventions apply to new internal variables and compatible renaming work. | --- | --- | | Dimensions and counts | `dim`, `padded_dim`, `num_points`, `num_queries`, `num_clusters` | | Index state | `max_elements_`, `num_stored_points_`, `num_points()` | -| Identity and outputs | `internal_id`, `node_id`, `label`, `cluster_id`, `result_ids`, `distances` | +| Identity and outputs | `internal_id`, `label`, `cluster_id`, `result_ids`, `distances` | | Search | `candidate_set`, `search_pool`, `result_pool`, `ef_search`, `ef_construction`, `nprobe` | | Storage | `*_offset_`; record/link byte sizes named after the stored object | | Parallelism and encoding | `num_threads`, `num_workers`, `fast_quantization` | @@ -630,7 +630,7 @@ counts from capacity only after construction completes. **Point identity** -`internal_id` or `node_id` for graph slots; `label` for a caller-visible HNSW label; +`internal_id` for graph slots; `label` for a caller-visible HNSW label; `cluster_id` for centroid routing. Never assume an external label equals an internal slot. **Search outputs** diff --git a/docs/docs/index/ivf.md b/docs/docs/index/ivf.md index 82775916..5fb9ca2a 100644 --- a/docs/docs/index/ivf.md +++ b/docs/docs/index/ivf.md @@ -78,7 +78,7 @@ For example, split SIFT into 4,096 clusters using squared L2 distance: ``` After files are prepared, load them into memory. The snippet below uses -`const char*` paths `data_file`, `centroids_file`, and `cids_file`. Include the +`const char*` paths `data_file`, `centroids_file`, and `cluster_ids_file`. Include the Eigen aliases and I/O helpers explicitly; `ivf.hpp` does not import them. ```c++ @@ -89,15 +89,15 @@ Eigen aliases and I/O helpers explicitly; `ivf.hpp` does not import them. using PID = rabitqlib::PID; using data_type = rabitqlib::RowMajorArray; -using gt_type = rabitqlib::RowMajorArray; +using cluster_ids_type = rabitqlib::RowMajorArray; data_type data; data_type centroids; -gt_type cids; +cluster_ids_type cluster_ids; rabitqlib::load_vecs(data_file, data); rabitqlib::load_vecs(centroids_file, centroids); -rabitqlib::load_vecs(cids_file, cids); +rabitqlib::load_vecs(cluster_ids_file, cluster_ids); ``` See the [complete indexing example](https://github.com/VectorDB-NTU/RaBitQ-Library/blob/main/sample/cpp/ivf_rabitq_indexing.cpp) @@ -125,7 +125,7 @@ The optional final C++ constructor argument selects `InitializerType::Auto`, `initializer="auto"`, `"flat"`, `"flat_rabitq"`, or `"hnsw"`: ```python -index = IvfIndex(dim, num_points, num_clusters, nbits=4, +index = IvfIndex(dim, max_elements, num_clusters, nbits=4, initializer="flat_rabitq") print(index.initializer) # resolved routing type ``` @@ -196,7 +196,7 @@ void IVF::construct( For example: ```c++ -ivf.construct(data.data(), centroids.data(), cids.data(), true); +ivf.construct(data.data(), centroids.data(), cluster_ids.data(), true); ``` ## Querying diff --git a/docs/docs/rabitq/estimator.md b/docs/docs/rabitq/estimator.md index 048e7a5e..54817aa9 100644 --- a/docs/docs/rabitq/estimator.md +++ b/docs/docs/rabitq/estimator.md @@ -17,7 +17,7 @@ factors. For API usage, see [Stable core API](core_api.md) or the advanced | $\mathbf{\bar o}$ | Unit-length quantized residual direction, expressed in the original padded domain. | | $\mathbf{q}_r'$ | The query in the quantized coordinate system: $\mathbf{q}_r'=P^{-1}\mathbf{q}_r$. | | $S_q$ | $S_q=\sum_{i=1}^{D} \mathbf{q}'_{r}[i].$ | -| $\Delta_x$ | Residual-norm scale defined below; distinct from the estimator factor `F_rescale`. | +| $\Delta_x$ | Residual-norm scale defined below; distinct from the estimator factor `f_rescale`. | | $\mathbf{x}_u$ | Unsigned integer codes.| ## Quantization @@ -91,16 +91,17 @@ $$ \end{align} $$ -Store the following factors (`F_` for data and `G_` for the query). +Store the following factors. Data factors use the `f_` accessors of the code layouts; +query factors use the `g_` accessors of the prepared query objects. | Name (Type) of Variable | Description | | ----------------- | ----------- | -| `F_add (float)`| $\|\mathbf{o}_r-\mathbf{c}\|^2+2\| \mathbf{o}_r-\mathbf{c}\| \frac{\left< \mathbf{\bar o}, \mathbf{c}\right>}{\left<\mathbf{\bar o},\mathbf{o} \right>}$ | -| `F_rescale (float)`| $-2\frac{\Delta_x}{\left<\mathbf{\bar o},\mathbf{o} \right>}$| -| `F_error (float)`| $\ 2 \|\mathbf{o}_r-\mathbf{c}\| \cdot \sqrt{\frac{1 - \left< \mathbf{\bar o},\mathbf{o}\right>^2}{\left< \mathbf{\bar o},\mathbf{o}\right>^2}} \frac{\epsilon_{0}}{\sqrt{D-1}}$| -| `G_add (float)`| $\|\mathbf{q}_r-\mathbf{c}\|^2$ | -| `G_kBxSumq (float)`| $c_BS_q$| -| `G_error (float)`| $\| \mathbf{q}_r-\mathbf{c}\|$| +| `f_add (float)`| $\|\mathbf{o}_r-\mathbf{c}\|^2+2\| \mathbf{o}_r-\mathbf{c}\| \frac{\left< \mathbf{\bar o}, \mathbf{c}\right>}{\left<\mathbf{\bar o},\mathbf{o} \right>}$ | +| `f_rescale (float)`| $-2\frac{\Delta_x}{\left<\mathbf{\bar o},\mathbf{o} \right>}$| +| `f_error (float)`| $\ 2 \|\mathbf{o}_r-\mathbf{c}\| \cdot \sqrt{\frac{1 - \left< \mathbf{\bar o},\mathbf{o}\right>^2}{\left< \mathbf{\bar o},\mathbf{o}\right>^2}} \frac{\epsilon_{0}}{\sqrt{D-1}}$| +| `g_add (float)`| $\|\mathbf{q}_r-\mathbf{c}\|^2$ | +| `g_kbxsumq (float)`| $c_BS_q$| +| `g_error (float)`| $\| \mathbf{q}_r-\mathbf{c}\|$| ### Estimator of inner product @@ -126,17 +127,18 @@ $$ \end{align} $$ -Store the following factors (`F_` for data and `G_` for the query). +Store the following factors. Data factors use the `f_` accessors of the code layouts; +query factors use the `g_` accessors of the prepared query objects. | Name (Type) of Variable | Description | | ----------------- | ----------- | -| `F_add (float)`| $1-\left< \mathbf{o}_r-\mathbf{c},\mathbf{c}\right>+\| \mathbf{o}_r-\mathbf{c}\| \frac{\left< \mathbf{\bar o}, \mathbf{c}\right>}{\left<\mathbf{\bar o},\mathbf{o} \right>}$ | -| `F_rescale (float)`| $-\frac{\Delta_x}{\left<\mathbf{\bar o},\mathbf{o} \right>}$| -| `F_error (float)`| $\|\mathbf{o}_r-\mathbf{c}\| \cdot \sqrt{\frac{1 - \left< \mathbf{\bar o},\mathbf{o}\right>^2}{\left< \mathbf{\bar o},\mathbf{o}\right>^2}} \frac{\epsilon_{0}}{\sqrt{D-1}}$| -| `G_add (float)`| $-\left< \mathbf{q}_r,\mathbf{c}\right>$ | -| `G_kBxSumq (float)`| $c_BS_q$| -| `G_k1xSumq (float)`| $c_1S_q$| -| `G_error (float)`| $\| \mathbf{q}_r-\mathbf{c}\|$| +| `f_add (float)`| $1-\left< \mathbf{o}_r-\mathbf{c},\mathbf{c}\right>+\| \mathbf{o}_r-\mathbf{c}\| \frac{\left< \mathbf{\bar o}, \mathbf{c}\right>}{\left<\mathbf{\bar o},\mathbf{o} \right>}$ | +| `f_rescale (float)`| $-\frac{\Delta_x}{\left<\mathbf{\bar o},\mathbf{o} \right>}$| +| `f_error (float)`| $\|\mathbf{o}_r-\mathbf{c}\| \cdot \sqrt{\frac{1 - \left< \mathbf{\bar o},\mathbf{o}\right>^2}{\left< \mathbf{\bar o},\mathbf{o}\right>^2}} \frac{\epsilon_{0}}{\sqrt{D-1}}$| +| `g_add (float)`| $-\left< \mathbf{q}_r,\mathbf{c}\right>$ | +| `g_kbxsumq (float)`| $c_BS_q$| +| `g_k1xsumq (float)`| $c_1S_q$| +| `g_error (float)`| $\| \mathbf{q}_r-\mathbf{c}\|$| ## Implementation @@ -150,25 +152,25 @@ when $B=1$. ```cpp // Compute the estimated distance -// Note that G_add is dependent on the centroid. -float estimated_distance = F_add + G_add + F_rescale * (ip + G_kBxSumq); +// Note that g_add is dependent on the centroid. +float estimated_distance = f_add + g_add + f_rescale * (ip + g_kbxsumq); -// Compute the error bound -// Note that G_error is dependent on the centroid. -float error_bound = F_error * G_error; +// Compute the error bound +// Note that g_error is dependent on the centroid. +float error_bound = f_error * g_error; // Compute the lower and upper bounds of the estimated distance -float lb_dist = estimated_distance - error_bound; -float ub_dist = estimated_distance + error_bound; +float lower_bound = estimated_distance - error_bound; +float upper_bound = estimated_distance + error_bound; ``` ### Incremental distance estimation Split codes separate the sign bit from the remaining $B-1$ extra bits. Score the sign code first, then refine selected candidates with the extra bits. -Each part has its own factors. The binary layout stores `F_add`, -`F_rescale`, and `F_error`; the extended layout stores `F_add_ex` and -`F_rescale_ex`. For $B>1$, the logical codes satisfy +Each part has its own factors. The binary layout stores `f_add`, +`f_rescale`, and `f_error`; the extended layout stores `f_add_ex` and +`f_rescale_ex`. For $B>1$, the logical codes satisfy $\mathbf{x}_u=2^{B-1}\mathbf{x}_{\mathrm{bin}}+\mathbf{x}_{\mathrm{ex}}$, which gives the refined distance formula below. @@ -176,8 +178,8 @@ The split-code implementation uses the binary error factor divided by $2^{B-1}$ as its refined pruning margin. This is an approximation, not an identity for the full-code error factor in the tables above: that factor depends on the full-code direction's own $\langle\mathbf{\bar o},\mathbf{o}\rangle$. -The split layout does not store a separate `F_error_ex`. -The query factors are `G_add`, `G_error`, `G_kBxSumq` and `G_k1xSumq`. +The split layout does not store a separate `f_error_ex`. +The query factors are `g_add`, `g_error`, `g_kbxsumq` and `g_k1xsumq`. Here `ip_bin` and `ip_ex` are the inner products of the sign and extra-bit codes, respectively, with the rotated query. These equations use exact code/query inner products. Binary filtering may instead @@ -187,17 +189,17 @@ that additional error. ```cpp // 1-bit dist -float estimated_distance = F_add + G_add + F_rescale * (ip_bin + G_k1xSumq); -float bound = F_error * G_error; -float ub_dist = estimated_distance + bound; -float lb_dist = estimated_distance - bound; +float estimated_distance = f_add + g_add + f_rescale * (ip_bin + g_k1xsumq); +float error_bound = f_error * g_error; +float upper_bound = estimated_distance + error_bound; +float lower_bound = estimated_distance - error_bound; // boost to full-bit dist float ex_scale = static_cast(1 << (bits - 1)); -float ex_est_dist = - F_add_ex + G_add + - F_rescale_ex * (ex_scale * ip_bin + ip_ex + G_kBxSumq); -float ex_bound = F_error * G_error / ex_scale; -float ex_ub_dist = ex_est_dist + ex_bound; -float ex_lb_dist = ex_est_dist - ex_bound; +float estimated_distance_ex = + f_add_ex + g_add + + f_rescale_ex * (ex_scale * ip_bin + ip_ex + g_kbxsumq); +float error_bound_ex = f_error * g_error / ex_scale; +float upper_bound_ex = estimated_distance_ex + error_bound_ex; +float lower_bound_ex = estimated_distance_ex - error_bound_ex; ``` diff --git a/docs/docs/rabitq/quantizer.md b/docs/docs/rabitq/quantizer.md index 59e4593e..f5a7609c 100644 --- a/docs/docs/rabitq/quantizer.md +++ b/docs/docs/rabitq/quantizer.md @@ -194,7 +194,7 @@ Insert the following before `return 0` in the indexing example above. // See the Estimation guide for these query factors. float c_1 = -static_cast((1 << 1) - 1) / 2.F; - float k1xsumq = c_1 * std::accumulate(query.begin(), query.end(), 0.F); + float g_k1xsumq = c_1 * std::accumulate(query.begin(), query.end(), 0.F); float g_add = rabitqlib::euclidean_sqr(query.data(), centroid.data(), dim); float estimated_distance = rabitqlib::quant::full_est_dist( @@ -206,7 +206,7 @@ Insert the following before `return 0` in the indexing example above. f_add, f_rescale, g_add, - k1xsumq + g_k1xsumq ); float gt_dist = rabitqlib::euclidean_sqr(query.data(), vector.data(), dim); @@ -273,7 +273,7 @@ initialized `batch_data`, `centroid`, `dim`, and `batch_size`. rabitqlib::BatchQuery query_wrapper(query.data(), dim); - // G_add is the squared L2 distance between the query and the centroid. + // g_add is the squared L2 distance between the query and the centroid. // This standalone example omits rotation, so both operands are raw vectors. // In an index that applies rotation, rotate both with the same transformation. query_wrapper.set_g_add( @@ -400,8 +400,8 @@ Insert this before `return 0` in the indexing example, reusing its data and dime auto ip_func = rabitqlib::select_excode_ipfunc(bits - 1); // Compute more accurate distance based on full codes. - float est_dist_ex; - float low_dist_ex; + float estimated_distance_ex; + float lower_bound_ex; float ip_x0_qr_ex; rabitqlib::split_single_fulldist( @@ -411,8 +411,8 @@ Insert this before `return 0` in the indexing example, reusing its data and dime query_wrapper, dim, bits - 1, - est_dist_ex, - low_dist_ex, + estimated_distance_ex, + lower_bound_ex, ip_x0_qr_ex, distance_sqr, distance_norm @@ -512,7 +512,7 @@ Insert this before `return 0` in the indexing example, reusing its buffers and d query.data(), dim, bits - 1, rabitqlib::METRIC_L2, true ); - // SplitBatchQuery expects the L2 norm and squares it internally for G_add. + // SplitBatchQuery expects the L2 norm and squares it internally for g_add. // This example omits rotation, so query and centroid are both raw vectors. float distance_norm = std::sqrt(rabitqlib::euclidean_sqr(query.data(), centroid.data(), dim)); diff --git a/sample/cpp/hnsw_rabitq_indexing.cpp b/sample/cpp/hnsw_rabitq_indexing.cpp index 84c8be63..f1e1d540 100644 --- a/sample/cpp/hnsw_rabitq_indexing.cpp +++ b/sample/cpp/hnsw_rabitq_indexing.cpp @@ -15,7 +15,7 @@ using PID = rabitqlib::PID; using index_type = rabitqlib::hnsw::HierarchicalNSW; using data_type = rabitqlib::RowMajorArray; -using gt_type = rabitqlib::RowMajorArray; +using cluster_ids_type = rabitqlib::RowMajorArray; int run(int argc, char* argv[]) { if (argc < 8) { @@ -36,7 +36,7 @@ int run(int argc, char* argv[]) { char* data_file = argv[1]; char* centroid_file = argv[2]; - char* cid_file = argv[3]; + char* cluster_ids_file = argv[3]; size_t m = atoi(argv[4]); size_t ef = atoi(argv[5]); size_t total_bits = atoi(argv[6]); @@ -66,11 +66,11 @@ int run(int argc, char* argv[]) { data_type data; data_type centroids; - gt_type cluster_id; + cluster_ids_type cluster_ids; rabitqlib::load_vecs(data_file, data); rabitqlib::load_vecs(centroid_file, centroids); - rabitqlib::load_vecs(cid_file, cluster_id); + rabitqlib::load_vecs(cluster_ids_file, cluster_ids); size_t num_points = data.rows(); size_t dim = data.cols(); @@ -88,7 +88,7 @@ int run(int argc, char* argv[]) { centroids.data(), num_points, data.data(), - cluster_id.data(), + cluster_ids.data(), 0, faster_quant ); diff --git a/sample/cpp/ivf_rabitq_indexing.cpp b/sample/cpp/ivf_rabitq_indexing.cpp index f29e6061..2f04ebc6 100644 --- a/sample/cpp/ivf_rabitq_indexing.cpp +++ b/sample/cpp/ivf_rabitq_indexing.cpp @@ -15,7 +15,7 @@ using PID = rabitqlib::PID; using index_type = rabitqlib::ivf::IVF; using data_type = rabitqlib::RowMajorArray; -using gt_type = rabitqlib::RowMajorArray; +using cluster_ids_type = rabitqlib::RowMajorArray; int run(int argc, char** argv) { if (argc < 6) { @@ -55,17 +55,17 @@ int run(int argc, char** argv) { char* data_file = argv[1]; char* centroids_file = argv[2]; - char* cids_file = argv[3]; + char* cluster_ids_file = argv[3]; size_t total_bits = atoi(argv[4]); char* index_file = argv[5]; data_type data; data_type centroids; - gt_type cids; + cluster_ids_type cluster_ids; rabitqlib::load_vecs(data_file, data); rabitqlib::load_vecs(centroids_file, centroids); - rabitqlib::load_vecs(cids_file, cids); + rabitqlib::load_vecs(cluster_ids_file, cluster_ids); size_t num_points = data.rows(); size_t dim = data.cols(); @@ -77,7 +77,7 @@ int run(int argc, char** argv) { rabitqlib::StopW stopw; index_type ivf(num_points, dim, k, total_bits, metric_type); - ivf.construct(data.data(), centroids.data(), cids.data(), faster_quant); + ivf.construct(data.data(), centroids.data(), cluster_ids.data(), faster_quant); float miniutes = stopw.get_elapsed_mili() / 1000 / 60; std::cout << "ivf constructed \n"; ivf.save(index_file); diff --git a/sample/cpp/symqg_indexing.cpp b/sample/cpp/symqg_indexing.cpp index aed9a5ee..872614bb 100644 --- a/sample/cpp/symqg_indexing.cpp +++ b/sample/cpp/symqg_indexing.cpp @@ -20,7 +20,6 @@ using PID = rabitqlib::PID; using index_type = rabitqlib::symqg::QuantizedGraph; using data_type = rabitqlib::RowMajorArray; -using gt_type = rabitqlib::RowMajorArray; int run(int argc, char** argv) { if (argc < 5) { From 671c6f14ca8ca6787cbf55b9e3529c6e144ad3e7 Mon Sep 17 00:00:00 2001 From: gouyt13clear Date: Sun, 1 Nov 2026 00:49:42 +0800 Subject: [PATCH 3/3] test(hnsw): check aggregate self-recall after parallel construction HNSW does not guarantee connectivity. In 500 local builds of this test's data with 8 threads, about half left 1-2 of the 512 points unreachable, while 1- and 2-thread builds left none. The test required each of the first 16 points to find itself, so it failed whenever an unreachable point fell in that range, as in an ASan CI run. Query every point instead, check that each result is a valid ID with a finite distance, and require at least 99% self-recall (at most 5 misses; the local worst case was 2). --- tests/unit/rabitqlib/index/hnsw_test.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/unit/rabitqlib/index/hnsw_test.cpp b/tests/unit/rabitqlib/index/hnsw_test.cpp index aaa46a3d..b59546df 100644 --- a/tests/unit/rabitqlib/index/hnsw_test.cpp +++ b/tests/unit/rabitqlib/index/hnsw_test.cpp @@ -236,7 +236,6 @@ TEST(HnswOneBitSearchTest, UsesBinaryEstimateForEntryPoint) { TEST(HnswConstructionTest, ParallelConstructionProducesSearchableIndex) { constexpr size_t kDim = 64; constexpr size_t kCount = 512; - constexpr size_t kNumQueries = 16; std::mt19937 generator(42); std::uniform_real_distribution distribution(-1, 1); std::vector data(kCount * kDim); @@ -248,13 +247,19 @@ TEST(HnswConstructionTest, ParallelConstructionProducesSearchableIndex) { HierarchicalNSW index(kCount, kDim, 4, 8, 50); index.construct(1, centroid.data(), kCount, data.data(), cluster_ids.data(), 8, false); - const auto results = index.search(data.data(), kNumQueries, 1, kCount, 1); - ASSERT_EQ(results.size(), kNumQueries); - for (size_t i = 0; i < kNumQueries; ++i) { + // HNSW does not guarantee connectivity, so parallel insertion may leave a few + // points unreachable. Check every result and the aggregate self-recall instead. + const auto results = index.search(data.data(), kCount, 1, kCount, 1); + ASSERT_EQ(results.size(), kCount); + size_t self_hits = 0; + for (size_t i = 0; i < kCount; ++i) { ASSERT_EQ(results[i].size(), 1U); - EXPECT_EQ(results[i][0].second, i); + EXPECT_LT(results[i][0].second, kCount); EXPECT_TRUE(std::isfinite(results[i][0].first)); + self_hits += results[i][0].second == i ? 1U : 0U; } + // 500 local 8-thread builds missed at most 2 of 512 points. + EXPECT_GE(self_hits, kCount * 99 / 100); } // Builds an index over the first `built` of `total` random vectors, leaving the