feat(ivf): support bucket-aligned precise codes - #2520
Conversation
|
/label status/waiting-for-review |
|
Automated pull request review completed. Review effort: Submitted 1 inline comment. |
Merge Protections🟢 All 3 merge protections satisfied — ready to merge. Show 3 satisfied protections🟢 Require kind label
🟢 Require version label
🟢 Require linked issue for feature/bug PRs
|
There was a problem hiding this comment.
Pull request overview
Adds an opt-in IVF feature to store “precise” (reorder) codes in a bucket-aligned layout to improve locality for bucket-scoped reads (especially for disk-backed IVF), while preserving the legacy flat layout as the default for backward compatibility.
Changes:
- Introduces
precise_codes_layout: "flat" | "bucket"(defaultflat) and wires it through IVF parameter parsing/compatibility checks. - Implements bucket-aligned precise-code storage, streaming/serialization support via a new critical streaming tag (
ivf_precise_bucket), and merge/model-export handling. - Adds functional/unit coverage for bucket precise codes (train/add/reorder/distance-by-id/serialization/streaming/merge) and updates English/Chinese docs.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
tests/test_ivf.cpp |
Adds persistent functional tests covering bucket-layout precise codes across IO backends and streaming/merge scenarios. |
src/storage/serialization_tags.h |
Adds IVF_PRECISE_BUCKET streaming tag, name mapping, criticality, and version routing. |
src/inner_string_params.h |
Adds internal parameter keys/values for precise_codes_layout and exposes them via DEFAULT_MAP. |
src/constants.cpp |
Adds public constant strings for IVF_PRECISE_CODES_LAYOUT and its values. |
include/vsag/constants.h |
Exposes the new public IVF constants in the public header. |
src/algorithm/ivf/ivf_parameter.h |
Adds precise_codes_layout to IVF parameters with default flat. |
src/algorithm/ivf/ivf_parameter.cpp |
Parses/validates precise_codes_layout, persists it, and includes it in compatibility checks. |
src/algorithm/ivf/ivf_parameter_test.cpp |
Adds unit tests for defaulting/validation/compatibility of precise_codes_layout. |
src/algorithm/ivf/ivf.h |
Adds bucket-precise reorder helper and new member for precise_bucket_. |
src/algorithm/ivf/ivf.cpp |
Implements bucket-aligned precise bucket initialization, insert/mirror logic, streaming/serialization handling, reorder path, distance-by-id selection, and merge validation. |
docs/docs/en/src/indexes/ivf.md |
Documents precise_codes_layout, supported backends, and behavior with buckets_per_data > 1. |
docs/docs/zh/src/indexes/ivf.md |
Chinese docs update for precise_codes_layout and supported IO backends. |
docs/docs/en/src/advanced/new_serialization.md |
Documents mutual exclusivity of high_precision_codes vs ivf_precise_bucket streaming blocks. |
docs/docs/zh/src/advanced/new_serialization.md |
Same streaming documentation updates in Chinese. |
|
/retest |
vsag-bot
left a comment
There was a problem hiding this comment.
Automated inline review completed.
Review effort: high (673 changed lines across 17 files).
Submitted 2 inline comments.
Reviewed commit e23e117.
e23e117 to
113f3ac
Compare
LHT129
left a comment
There was a problem hiding this comment.
Summary
This PR adds a precise_codes_layout: "bucket" option to IVF indexes, enabling bucket-aligned precise codes for disk-backed IVF. The core idea is to mirror each basic posting with a corresponding precise-code posting in the same bucket at the same offset, so that precise-code reads during reorder are colocated with the basic codes they belong to — avoiding random seeks when the precise codes are on disk.
What's Changed (880 additions, 38 deletions across 20 files)
- Core IVF logic (
ivf.cpp,ivf.h): Newprecise_bucket_member (BucketInterfacePtr),reorder_with_precise_bucket()method,disk_backed_precise_bucket_flag, and updated Add/Search/Serialize/Deserialize/CalDistanceById paths. - Parameter handling (
ivf_parameter.cpp,ivf_parameter.h): Newprecise_codes_layoutparameter ("flat"/"bucket"), validation rules (rejects pqfs quantizer, mmap_io, requires use_reorder). - Serialization (
serialization_tags.h): NewIVF_PRECISE_BUCKET = 17stream tag. - Factory (
factory.cpp): RejectsIndex::Loadfor disk-backed precise buckets. - IO (
noncontinuous_allocator.h): Changedlast_offset_tostd::atomic<uint64_t>withfetch_addfor thread-safe concurrent allocation. - Tests (
test_ivf.cpp): AddedGenerateBucketPreciseParameters(),CheckBucketPreciseIndex(), and multiple test cases covering build, search, serialize, merge, concurrent add, and CalDistanceById. - Documentation: Updated IVF docs and serialization docs in both EN/ZH.
- Example: New
326_feature_ivf_precise_bucket.cpprunnable example.
Overall Assessment
The design is sound and well-scoped. The bucket-aligned layout is a natural extension of the existing IVF architecture, and the implementation correctly mirrors the basic bucket's structure for precise codes. The PR includes thorough test coverage and handles edge cases like buckets_per_data > 1, disk-backed IO types, and compatibility checks for Merge/ExportModel/Clone/Fork.
Issues Already Identified (from existing comments)
Several issues have been raised by previous reviewers and are still present in this commit:
-
Fork()error message copy-paste (ivf.h:121): Says "Clone does not support..." instead of "Fork does not support...". [LHT129] -
cal_memory_usage()inflates memory for disk-backed IOs (ivf.cpp:2281):precise_bucket_->GetMemoryUsage()reports full on-disk size even for disk-backed IOs. [vsag-bot, LHT129] -
reorder_with_precise_bucketusesj==0posting only (ivf.cpp:1663):get_location()returns only the first posting's bucket/offset. Forbuckets_per_data > 1, the precise code read may not be colocated with the winning posting. [vsag-bot] -
fill_location_map()over-allocation (ivf.cpp:2050): Resizes tototal_elements_ * buckets_per_data_but indexed by origin id. Pre-existing on main. [Copilot] -
Flat path
FlattenInterface::MakeInstancenull check (ivf.cpp:404): No null check before passing toFlattenReorder. Pre-existing on main. [Copilot]
New Observations
Beyond the issues already identified, I reviewed the full diff and found the implementation to be consistent and correct. The NonContinuousAllocator thread-safety fix (atomic fetch_add) is a good improvement. The rejection of disk-backed precise buckets in Clone/Fork/ExportModel/Index::Load is appropriate and complete.
The test coverage is comprehensive, covering:
- Basic build and search with bucket precise
- Serialization round-trip
- Merge of bucket-precise indexes
- Concurrent add
- CalDistanceById with bucket precise
- Compatibility validation (rejecting incompatible merges)
- Various IO types (memory, buffer, async)
Recommendation
The PR is in good shape. The remaining issues are either pre-existing on main or have been acknowledged by the author. I recommend addressing the Fork() error message fix (trivial) and the cal_memory_usage() inflation before merging, but neither is a correctness blocker.
vsag-bot
left a comment
There was a problem hiding this comment.
Automated inline review completed.
Review effort: high (918 changed lines across 20 files).
No actionable inline findings were found.
Reviewed commit 7ed1b37.
|
/retest |
7ed1b37 to
dbc8fe6
Compare
LHT129
left a comment
There was a problem hiding this comment.
Review Summary (supplemental)
Reviewed commit dbc8fe6d (880 additions, 38 deletions across 20 files). This is a supplemental review on top of the 30+ existing inline comments and several prior review summaries that have already covered the PR thoroughly.
Issues already covered by existing comments
The following have been raised and addressed in prior review rounds:
- Fork() error message — Fixed (now says "Fork" instead of "Clone")
- NonContinuousAllocator thread safety — Fixed (atomic
fetch_add) - cal_memory_usage() inflation for disk IOs — Tracked separately in #2573 (pre-existing on main)
- buckets_per_data > 1 reorder posting — Resolved by requiring
buckets_per_data=1for bucket layout - Per-candidate QueryOneById in reorder/CalDistanceById — Acknowledged as baseline; grouped reads are follow-up work
- ExportModel/Load file sharing — Pre-existing on main for basic bucket; acknowledged
- Batch async writes — Acknowledged as existing behavior
- MMap truncation — Pre-existing on main
- fill_location_map over-allocation — Pre-existing on main
- Flat path null check — Pre-existing on main
Additional observations
After a thorough pass over the full implementation, I did not identify new correctness or security issues beyond what has already been raised. A few notes:
-
Deserialize/read_streaming_body symmetry: The serialization format correctly writes
"precise_bucket"or"reorder_codes"based on layout, and the deserialization paths correctly read the corresponding block. The parameter compatibility check inCheckCompatibility(checkingprecise_codes_layout) prevents cross-layout deserialization mismatches. -
Concurrent Add correctness: The
InsertVector→InsertVectorWithOffsetpattern in the concurrentadd_funclambda is sound. Each thread gets a unique offset from the precise bucket (protected by per-bucket mutexes), andInsertVectorWithOffsetusesstd::maxforbucket_sizes_update, correctly handling out-of-order inserts. -
Merge safety:
check_merge_illegalverifiesprecise_bucket_consistency between indexes and callsExportModelfor model comparison. SinceMergerejectsdisk_backed_precise_bucket_upfront, theExportModelcalls withincheck_merge_illegalare safe. -
Streaming tag handling: Both
IVF_PRECISE_BUCKETandHIGH_PRECISION_CODEStags are guarded by the appropriate null checks inread_streaming_body, and theloaded_precise_codesflag is correctly validated after the loop. Cross-layout mismatches are caught by parameter compatibility checks.
Conclusion
The PR is in good shape. The implementation is consistent, the serialization is symmetric, and the disk-backed safety guards (Clone/Fork/ExportModel/Merge/Load rejection) are complete. No new blocking issues found.
LHT129
left a comment
There was a problem hiding this comment.
Review Summary (supplemental)
Reviewed commit dbc8fe6d (880 additions, 38 deletions across 20 files). This is a supplemental review on top of the 30+ existing inline comments and several prior review summaries that have already covered the PR thoroughly.
Issues already covered by existing comments
The following have been raised and addressed in prior review rounds:
- Fork() error message — Fixed (now says "Fork" instead of "Clone")
- NonContinuousAllocator thread safety — Fixed (atomic
fetch_add) - cal_memory_usage() inflation for disk IOs — Tracked separately in #2573 (pre-existing on main)
- buckets_per_data > 1 reorder posting — Resolved by requiring
buckets_per_data=1for bucket layout - Per-candidate QueryOneById in reorder/CalDistanceById — Acknowledged as baseline; grouped reads are follow-up work
- ExportModel/Load file sharing — Pre-existing on main for basic bucket; acknowledged
- Batch async writes — Acknowledged as existing behavior
- MMap truncation — Pre-existing on main
- fill_location_map over-allocation — Pre-existing on main
- Flat path null check — Pre-existing on main
Additional observations
After a thorough pass over the full implementation, I did not identify new correctness or security issues beyond what has already been raised. A few notes:
-
Deserialize/read_streaming_body symmetry: The serialization format correctly writes
"precise_bucket"or"reorder_codes"based on layout, and the deserialization paths correctly read the corresponding block. The parameter compatibility check inCheckCompatibility(checkingprecise_codes_layout) prevents cross-layout deserialization mismatches. -
Concurrent Add correctness: The
InsertVector→InsertVectorWithOffsetpattern in the concurrentadd_funclambda is sound. Each thread gets a unique offset from the precise bucket (protected by per-bucket mutexes), andInsertVectorWithOffsetusesstd::maxforbucket_sizes_update, correctly handling out-of-order inserts. -
Merge safety:
check_merge_illegalverifiesprecise_bucket_consistency between indexes and callsExportModelfor model comparison. SinceMergerejectsdisk_backed_precise_bucket_upfront, theExportModelcalls withincheck_merge_illegalare safe. -
Streaming tag handling: Both
IVF_PRECISE_BUCKETandHIGH_PRECISION_CODEStags are guarded by the appropriate null checks inread_streaming_body, and theloaded_precise_codesflag is correctly validated after the loop. Cross-layout mismatches are caught by parameter compatibility checks.
Conclusion
The PR is in good shape. The implementation is consistent, the serialization is symmetric, and the disk-backed safety guards (Clone/Fork/ExportModel/Merge/Load rejection) are complete. No new blocking issues found.
LHT129
left a comment
There was a problem hiding this comment.
Review Summary (supplemental)
Reviewed commit dbc8fe6d (880 additions, 38 deletions across 20 files). This is a supplemental review on top of the 30+ existing inline comments and several prior review summaries that have already covered the PR thoroughly.
Issues already covered by existing comments
The following have been raised and addressed in prior review rounds:
- Fork() error message — Fixed (now says "Fork" instead of "Clone")
- NonContinuousAllocator thread safety — Fixed (atomic
fetch_add) - cal_memory_usage() inflation for disk IOs — Tracked separately in #2573 (pre-existing on main)
- buckets_per_data > 1 reorder posting — Resolved by requiring
buckets_per_data=1for bucket layout - Per-candidate QueryOneById in reorder/CalDistanceById — Acknowledged as baseline; grouped reads are follow-up work
- ExportModel/Load file sharing — Pre-existing on main for basic bucket; acknowledged
- Batch async writes — Acknowledged as existing behavior
- MMap truncation — Pre-existing on main
- fill_location_map over-allocation — Pre-existing on main
- Flat path null check — Pre-existing on main
Additional observations
After a thorough pass over the full implementation, I did not identify new correctness or security issues beyond what has already been raised. A few notes:
-
Deserialize/read_streaming_body symmetry: The serialization format correctly writes
"precise_bucket"or"reorder_codes"based on layout, and the deserialization paths correctly read the corresponding block. The parameter compatibility check inCheckCompatibility(checkingprecise_codes_layout) prevents cross-layout deserialization mismatches. -
Concurrent Add correctness: The
InsertVector→InsertVectorWithOffsetpattern in the concurrentadd_funclambda is sound. Each thread gets a unique offset from the precise bucket (protected by per-bucket mutexes), andInsertVectorWithOffsetusesstd::maxforbucket_sizes_update, correctly handling out-of-order inserts. -
Merge safety:
check_merge_illegalverifiesprecise_bucket_consistency between indexes and callsExportModelfor model comparison. SinceMergerejectsdisk_backed_precise_bucket_upfront, theExportModelcalls withincheck_merge_illegalare safe. -
Streaming tag handling: Both
IVF_PRECISE_BUCKETandHIGH_PRECISION_CODEStags are guarded by the appropriate null checks inread_streaming_body, and theloaded_precise_codesflag is correctly validated after the loop. Cross-layout mismatches are caught by parameter compatibility checks.
Conclusion
The PR is in good shape. The implementation is consistent, the serialization is symmetric, and the disk-backed safety guards (Clone/Fork/ExportModel/Merge/Load rejection) are complete. No new blocking issues found.
vsag-bot
left a comment
There was a problem hiding this comment.
Automated inline review completed.
Review effort: high (941 changed lines across 20 files).
Submitted 1 inline comment.
Reviewed commit dbc8fe6.
| precise_ids = precise_bucket_->GetInnerIds(i); | ||
| } | ||
| for (uint64_t j = 0; j < bucket_size; ++j) { | ||
| if (precise_ids != nullptr and precise_ids[j] != ids[j]) { |
There was a problem hiding this comment.
[P2] Validate the precise inner-ID vector length before indexing
BucketDataCell::Deserialize reads inner_ids_ and bucket_sizes_ as independent serialized vectors. A crafted IVF_PRECISE_BUCKET can therefore declare a bucket size equal to the valid basic bucket while supplying a shorter, even empty, precise inner-ID vector; this check then evaluates precise_ids[j] out of bounds, causing undefined behavior instead of rejecting the index. Validate that each precise inner-ID vector covers bucket_size before dereferencing it, preferably during datacell deserialization.
Signed-off-by: jac0626 <jac0626@users.noreply.github.com> Assisted-by: Codex:gpt-5
Signed-off-by: jac0626 <jac0626@users.noreply.github.com> Assisted-by: Codex:gpt-5
Signed-off-by: jac0626 <jac0626@users.noreply.github.com> Assisted-by: Codex:gpt-5
Signed-off-by: jac0626 <jac0626@users.noreply.github.com> Assisted-by: Codex:gpt-5
Signed-off-by: jac0626 <jac0626@users.noreply.github.com> Assisted-by: Codex:gpt-5
Signed-off-by: jac0626 <jac0626@users.noreply.github.com> Assisted-by: Codex:gpt-5
Signed-off-by: jac0626 <jac0626@users.noreply.github.com> Assisted-by: Codex:gpt-5
Signed-off-by: jac0626 <jac0626@users.noreply.github.com> Assisted-by: Codex:gpt-5
dbc8fe6 to
7e14d50
Compare
vsag-bot
left a comment
There was a problem hiding this comment.
Automated inline review completed.
Review effort: high (941 changed lines across 20 files).
Submitted 1 inline comment.
Reviewed commit 7e14d50.
| precise_ids = precise_bucket_->GetInnerIds(i); | ||
| } | ||
| for (uint64_t j = 0; j < bucket_size; ++j) { | ||
| if (precise_ids != nullptr and precise_ids[j] != ids[j]) { |
There was a problem hiding this comment.
[P2] [P2] Validate precise ID storage before indexing
BucketDataCell::Deserialize reads bucket_sizes_ and each inner_ids_ vector independently, so a malformed IVF_PRECISE_BUCKET can report the same size as the basic bucket while supplying fewer precise IDs. This line then indexes past precise_ids, causing undefined behavior instead of rejecting the serialized index. Validate that the precise inner-ID vector contains at least bucket_size entries before entering this loop.
Change Type
Linked Issue
What Changed
precise_codes_layout: "flat" | "bucket"and keepflatas the compatibility default.buckets_per_data: 1for the first bucket-layout version and reject multi-assignment explicitly.The first implementation intentionally keeps per-candidate
QueryOneByIdreads. Grouped batchreads and shared CacheIO remain independent follow-up optimizations.
The shared disk-backed
BucketDataCellmemory-accounting bug found during review is trackedseparately in #2573; it also affects basic IVF buckets on
main.Test Evidence
clang-format15 dry-runmake lintmake testmake cov, run tests, and collect coverageTest details:
Compatibility Impact
precise_codes_layoutdefaults toflat, so existing flat indexes remain readable. New bucket indexes persist their layout anduse a distinct streaming block.
Performance and Concurrency Impact
performance measurement is deferred to a dedicated POC. The first version rejects
buckets_per_data > 1, so reranking always reads the unique aligned posting.mirrors. The shared
NonContinuousIOallocation path is synchronized, and four-threadin-memory construction is covered.
Documentation Impact
README.mdDEVELOPMENT.mdCONTRIBUTING.mdexamples/cpp/README.mdRisk and Rollback
layout.
Checklist