feat: support multi-query batch distance calculation in CalDistanceById#2422
feat: support multi-query batch distance calculation in CalDistanceById#2422LHT129 wants to merge 1 commit into
Conversation
|
/label S-waiting-on-review |
Merge Protections🟢 All 2 merge protections satisfied — ready to merge. Show 2 satisfied protections🟢 Require kind label
🟢 Require version label
|
|
Warning Gemini encountered an error creating the review. You can try again by commenting |
There was a problem hiding this comment.
Pull request overview
Extends the Index::CalDistanceById(const DatasetPtr&, ids, count, bool) API to support multi-query inputs (query->GetNumElements() > 1) and introduces a capability flag to advertise this behavior across selected index implementations.
Changes:
- Updated API documentation to define the multi-query
N × countrow-major distances layout and invalid-ID sentinel behavior. - Added
IndexFeature::SUPPORT_BATCH_CAL_DISTANCE_BY_IDand registered it for several in-scope algorithms. - Implemented a default multi-query loop in
InnerIndexInterface::CalDistanceById(const DatasetPtr&, ...).
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| include/vsag/index.h | Documents multi-query behavior and output layout for CalDistanceById(DatasetPtr, ...). |
| include/vsag/index_features.h | Adds SUPPORT_BATCH_CAL_DISTANCE_BY_ID feature flag. |
| src/algorithm/inner_index_interface.h | Updates interface docs for multi-query batch distance semantics. |
| src/algorithm/inner_index_interface.cpp | Adds default multi-query implementation for CalDistanceById(DatasetPtr, ...). |
| src/algorithm/hgraph/hgraph_build.cpp | Registers the new batch-distance feature for HGraph. |
| src/algorithm/ivf/ivf.cpp | Registers the new batch-distance feature for IVF (fp32 path). |
| src/algorithm/bruteforce/bruteforce.cpp | Registers the new batch-distance feature for BruteForce. |
| src/algorithm/pyramid/pyramid.cpp | Registers the new batch-distance feature for Pyramid. |
| src/algorithm/lazy_hgraph/lazy_hgraph.cpp | Registers the new batch-distance feature for LazyHGraph. |
| src/algorithm/sindi/sindi.cpp | Registers the new batch-distance feature for SINDI. |
ce4e4d8 to
4b0203f
Compare
4b0203f to
c655622
Compare
c655622 to
d2d5afa
Compare
d2d5afa to
ecc9190
Compare
e41ba6f to
25d76bb
Compare
25d76bb to
1b693ec
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
tests/test_index/test_index_calc.cpp:165
- The loop index is
intbuttest_numisint64_t, which can trigger signed/width warnings and inconsistent indexing. Useint64_tfor the loop counter to matchtest_numand the rest of the test.
int64_t test_num = 5;
std::vector<int64_t> mixed_ids(test_num);
for (int i = 0; i < test_num; ++i) {
mixed_ids[i] = (i % 2 == 0) ? shared_ids[i % gt_topK] : -(i + 1);
}
vsag-bot
left a comment
There was a problem hiding this comment.
Automated inline review completed.
Review effort: high (354 changed lines across 18 files).
Submitted 2 inline comments.
Reviewed commit 479c90f.
vsag-bot
left a comment
There was a problem hiding this comment.
Automated inline review completed.
Review effort: high (347 changed lines across 18 files).
Submitted 2 inline comments.
Reviewed commit e972c8c.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
src/algorithm/inner_index_interface.cpp:501
- In the multi-query dense path, calling CalDistanceById(query_ptr, ...) allocates a temporary Dataset for each query and then memcpy()s the distances into the final buffer. This adds per-query allocations and extra copying; you can compute directly into the row buffer via compute_distances_for_ids().
if (query->GetFloat32Vectors() != nullptr) {
const float* query_ptr = query->GetFloat32Vectors() + q * query->GetDim();
auto row_result =
this->CalDistanceById(query_ptr, ids, count, calculate_precise_distance);
std::memcpy(row, row_result->GetDistances(), sizeof(float) * count);
src/algorithm/sindi/sindi.cpp:1860
- Missing-ID handling currently relies on parsing the exception message string ("does not exist or is removed"). This is brittle (message text changes break behavior) and does extra work per missing ID. Prefer checking label_table_->TryGetIdByLabel under label_lookup_mutex_ and only using exceptions for truly exceptional failures.
const std::string_view message(e.what());
if (e.error_.type == ErrorType::INTERNAL_ERROR and
message.find("does not exist or is removed") != std::string_view::npos) {
return -1.0F;
}
vsag-bot
left a comment
There was a problem hiding this comment.
Automated inline review completed.
Review effort: high (356 changed lines across 18 files).
Submitted 2 inline comments.
Reviewed commit 7a63281.
vsag-bot
left a comment
There was a problem hiding this comment.
Automated inline review completed.
Review effort: high (447 changed lines across 18 files).
Submitted 6 inline comments.
Reviewed commit abdae28.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
include/vsag/index_features.h:88
- The new feature comment exceeds the repo’s 100-character line limit (e.g. the
distances[q * count + j] ...line). Please wrap it to keep headers consistent with the project formatting constraints.
SUPPORT_BATCH_CAL_DISTANCE_BY_ID, /**< Supports CalDistanceById(DatasetPtr, ids, count, ...)
with NumElements() > 1. IDs and distances are row-major:
distances[q * count + j] is query q vs ids[q * count + j],
and -1 indicates an invalid id. */
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (3)
src/algorithm/inner_index_interface.cpp:499
- Multi-query dense path allocates a temporary Dataset (and distance buffer) per query row via CalDistanceById(query_ptr, ...) and then memcpy()s into the final buffer. This adds O(num_queries) extra allocations/copies and can dominate runtime for large batches; you can compute directly into the preallocated row buffer using compute_distances_for_ids + CalcDistanceById(query_ptr, id, ...) instead.
if (query->GetFloat32Vectors() != nullptr) {
const float* query_ptr = query->GetFloat32Vectors() + q * query->GetDim();
auto row_result =
this->CalDistanceById(query_ptr, row_ids, count, calculate_precise_distance);
std::memcpy(row, row_result->GetDistances(), sizeof(float) * count);
} else {
src/algorithm/inner_index_interface.cpp:474
- New code uses size_t intermediates for count/overflow calculations, but the repo's hard constraints prefer uint64_t in code changes to avoid macOS compile issues (AGENTS.md:23-24). Consider switching these intermediates to uint64_t and only casting to size_t at the Allocate() boundary after an explicit bounds check.
const auto count_size = static_cast<uint64_t>(count);
const auto num_queries_size = static_cast<uint64_t>(num_queries);
const auto max_distance_count = std::numeric_limits<uint64_t>::max() / sizeof(float);
const bool distance_count_overflows =
count_size != 0 && num_queries_size > max_distance_count / count_size;
CHECK_ARGUMENT(not distance_count_overflows, "CalDistanceById distance buffer size overflows");
src/algorithm/sindi/sindi.cpp:1858
- This new overflow/size calculation uses size_t intermediates, but the repo hard constraints prefer uint64_t in code changes to avoid macOS compile issues (AGENTS.md:23-24). Consider switching to uint64_t for these count computations and only casting to size_t at the Allocate() boundary after a bounds check.
const auto count_size = static_cast<uint64_t>(count);
const auto num_queries_size = static_cast<uint64_t>(num_queries);
const auto max_distance_count = std::numeric_limits<uint64_t>::max() / sizeof(float);
const bool distance_count_overflows =
count_size != 0 && num_queries_size > max_distance_count / count_size;
CHECK_ARGUMENT(not distance_count_overflows, "CalDistanceById distance buffer size overflows");
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
src/algorithm/inner_index_interface.cpp:251
lenis already auint64_t, soif (len > std::numeric_limits<uint64_t>::max())is always false. This makes the subsequentstatic_cast<size_t>(len)potentially truncating on 32-bit platforms and can lead to out-of-bounds reads inmemcpy. Check againststd::numeric_limits<size_t>::max()before casting.
if (len > std::numeric_limits<uint64_t>::max()) {
throw VsagException(
ErrorType::READ_ERROR, "binary read too large for index: ", index_name);
}
const auto copy_len = static_cast<size_t>(len);
src/algorithm/inner_index_interface.cpp:499
- In the dense multi-query path, this allocates a temporary
Datasetfor each query row (CalDistanceById(query_ptr, ...)) and thenmemcpys the results. This adds avoidable per-query allocations/copies in what is likely a hot path. You can compute directly intorowusingCalcDistanceById+compute_distances_for_ids.
const float* query_ptr = query->GetFloat32Vectors() + q * query->GetDim();
auto row_result =
this->CalDistanceById(query_ptr, row_ids, count, calculate_precise_distance);
std::memcpy(row, row_result->GetDistances(), sizeof(float) * count);
} else {
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (2)
src/algorithm/inner_index_interface.cpp:251
- The length overflow check is ineffective:
lenis auint64_t, solen > std::numeric_limits<uint64_t>::max()can never be true. Sincelenis cast tosize_tformemcpy, this should instead validate thatlenfits insize_tto avoid truncation/overflow on 32-bit builds.
if (len > std::numeric_limits<uint64_t>::max()) {
throw VsagException(
ErrorType::READ_ERROR, "binary read too large for index: ", index_name);
}
const auto copy_len = static_cast<size_t>(len);
src/algorithm/inner_index_interface.cpp:498
- For dense multi-query, the default implementation allocates a temporary
Datasetper query row (CalDistanceById(query_ptr, ...)) and thenmemcpys into the final buffer. This adds per-query allocation/copy overhead on what is likely a hot path for batch distance calculation.
if (query->GetFloat32Vectors() != nullptr) {
const float* query_ptr = query->GetFloat32Vectors() + q * query->GetDim();
auto row_result =
this->CalDistanceById(query_ptr, row_ids, count, calculate_precise_distance);
std::memcpy(row, row_result->GetDistances(), sizeof(float) * count);
Extend CalDistanceById(const DatasetPtr&, ids, count, bool) to accept DatasetPtr with NumElements > 1. In multi-query mode, count is the number of IDs per query and ids is a row-major matrix with NumElements * count entries. Result distances use the same row-major layout: distances[i * count + j] is the distance from query i to ids[i * count + j]. - Update public API docs in include/vsag/index.h - Add default multi-query loop in InnerIndexInterface - Add SUPPORT_BATCH_CAL_DISTANCE_BY_ID feature flag - Set feature flag for HGraph, IVF, BruteForce, Pyramid, LazyHGraph, SINDI - DiskANN and HNSW are excluded from this change - Add unit tests for per-query multi-query batch distance - Fix dense query path to use float* overload - Fix SINDI multi-query support Assisted-by: OpenCode Signed-off-by: LHT129 <tianlan.lht@antgroup.com>
Summary
Extend
CalDistanceById(const DatasetPtr&, ids, count, bool)to acceptDatasetPtrwithNumElements > 1, computing distances for each query against its own row of IDs. Theidsinput is a row-major matrix withnum_queries * countentries, wherecountis the number of IDs per query. The result distances buffer has the same row-major layout:distances[i * count + j]= distance from queryitoids[i * count + j].-1indicates an invalid ID.Closes #2421
Changes
include/vsag/index.h: Updated API documentation forCalDistanceById(DatasetPtr, ...)to describe multi-query behaviorinclude/vsag/index_features.h: AddedSUPPORT_BATCH_CAL_DISTANCE_BY_IDfeature flagsrc/algorithm/inner_index_interface.h/.cpp: Implemented default multi-query loop inCalDistanceById(DatasetPtr, ...). WhenNumElements > 1, constructs per-query sub-DatasetPtrs and iterates over each query row with its corresponding row of IDsSUPPORT_BATCH_CAL_DISTANCE_BY_IDfor HGraph, IVF, BruteForce, Pyramid, LazyHGraph, SINDIBackward Compatibility
Fully backward compatible. When
NumElements == 1, behavior is identical to before.Scope