fix(vector): resolve 7 correctness, memory-safety, and quantization bugs (#50)#65
Merged
farhan-syah merged 7 commits intomainfrom Apr 16, 2026
Merged
Conversation
Move simd.rs into a simd/ directory with dedicated files for each target (avx2, avx512, neon, scalar, hamming, runtime). Add a length-mismatch assertion in the top-level distance() entry point so mismatched vector dimensions panic immediately with a clear message rather than producing silent wrong results.
random_layer() could theoretically produce very large values for unlucky RNG draws, promoting max_layer to an unbounded height and making every subsequent search's Phase-1 greedy descent O(max_layer). Apply a hard cap of 16 layers — standard practice for production HNSW deployments. Also refactor compact() to expose compact_with_map() returning the old→new id remapping needed by doc_id_map maintenance.
The previous initialization selected the farthest point deterministically rather than sampling proportionally to squared distance. Replace with proper weighted d² sampling using a deterministic xorshift RNG so centroid seeding is stable across runs and converges reliably for skewed distributions. Also derive MessagePack serialization for PqCodec so trained codecs survive checkpointing.
…ent search Bitmap filters carry global vector ids, but each segment's HNSW nodes and FlatIndex entries are numbered starting at zero (local ids). Without an offset the filter tests the wrong bit for every segment after the first, producing incorrect results for filtered searches over collections with more than one sealed segment. Add search_filtered_offset / search_with_bitmap_bytes_offset to HnswIndex and search_filtered_offset to FlatIndex. Thread id_offset through the internal search_layer function so bitmap membership is checked against the correct global id. Update collection/search.rs to pass the per-segment base_id and the growing segment's growing_base_id when dispatching filtered searches.
…d_map Checkpoint serialization previously skipped deleted vectors entirely, so on restore those local ids were missing and all subsequent ids shifted by one — corrupting the HNSW graph's neighbor adjacency. Fix by capturing the deleted flag for every vector (growing and building segments) and replaying tombstones via insert_tombstoned / index.delete() on restore. Separately, compact() previously discarded doc_id_map and multi_doc_map entries for the segment being compacted. Use compact_with_map() to obtain the old→new local id remapping and rewrite both maps so that global ids continue to resolve to the correct document strings after compaction.
…lection Add IndexConfig / IndexType to VectorCollection so PQ-configured collections train and store PQ codes when sealing a segment rather than always falling back to SQ8. Split quantizer training helpers into collection/quantize.rs to keep lifecycle.rs under the 500-line file cap. Extend the sealed-segment search path to use a unified quantized_search() function that handles both PQ and SQ8 with proper asymmetric scoring, widened candidate generation, and exact FP32 reranking. Report PQ quantization and index type correctly in collection stats.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #50.
Summary
Seven independent bugs in
nodedb-vector— three silent-wrong-result bugs, one memory-safety UB, one silently no-op feature, and two performance pathologies. Each has dedicated regression coverage innodedb-vector/tests/.Fixes
distance::distance()dispatcher now assertsa.len() == b.len()before invoking any AVX2/AVX-512/NEON kernel, with defense-in-depth asserts inside each kernel.HnswIndex/FlatIndexgainedsearch_filtered_offset/search_with_bitmap_bytes_offset;VectorCollection::search_with_bitmap_bytespassesseg.base_idper segment so global-id bitmaps resolve correctly across sealed segments.index_type='hnsw_pq'wiring —VectorCollectioncarriesIndexConfig; newwith_index_config/with_pq_configconstructors;complete_buildtrainsPqCodecwhen configured;SealedSegment.pqstored;stats()reportsPq/HnswPq. Quantized candidates are now actually scored viaPqCodec::asymmetric_distance(andSq8Codecasymmetric variants) during search with FP32 rerank.FlatIndex::get_vectorreturnsNonefor tombstoned slots; addedis_deleted,insert_tombstoned;BuildingSnapshotgaineddeleted: Vec<bool>;from_checkpointreplays tombstones for growing and building segments.SealedSnapshotnow serializespq_bytes+pq_codesso PQ survives restore.HnswIndex::compact_with_map()returns(removed, id_map);VectorCollection::compactrewritesdoc_id_mapandmulti_doc_mapto new globals.pub const MAX_LAYER_CAP: usize = 16, applied inrandom_layer.Xorshift64) in bothquantize/pq.rsandivf.rs;min_distsinit fixed.Structural
distance/simd.rs(504 lines, over hard cap) intodistance/simd/{mod,runtime,scalar,hamming,avx2,avx512,neon}.rs.collection/lifecycle.rs(555 → 494 lines) intocollection/quantize.rs.Test plan
cargo nextest run -p nodedb-vector --all-features— 103 passed, 0 failedcargo nextest run --all-features(workspace) — 5228 passed, 0 failedcargo clippy --all-targets --all-features -- -D warnings— cleancargo fmt --all— applied