perf: improve IVF-SQ training, encoding, and reader reuse - #91
Conversation
leaves12138
left a comment
There was a problem hiding this comment.
Reviewed 6603e4e73edc51575f7859e73492f90613e1e3c0 against main at 8dcabf208c99707eab3938af0bcc40530fdb4cad. No blocking findings.
I checked the pooled residual-bound training, fused SQ8 encoding and SIMD packing/tails, conservative L2 cutoff propagation, query-heap reuse, decoded-partition FIFO and memory accounting, cache misses/error recovery, and the unchanged IVSQ v1 serialization layout.
Validation on native x86_64 with AVX2/FMA available, using Rust 1.94.1:
cargo test --workspace --locked: 510 passed, 2 intentionally ignored, including v1 golden-format fixtures and the reader-cache/filter/probe-range coverage.cargo fmt --all -- --check: passed.cargo clippy --workspace --all-targets --locked -- -D warnings: passed.- Two additional local randomized differential tests passed: fused residual encoding matches the existing encoder across non-unit per-dimension ranges, clipping, reordered/repeated rows, and SIMD/tail dimensions; finite-cutoff native blocked scans preserve every competitive distance compared with the infinite-cutoff scan, including partial blocks and equality boundaries.
The additional tests exercise native AVX2 rather than only compiling that path. All current PR CI checks are also successful. I did not independently rerun the Apple M4 benchmark measurements, native NEON execution, or cold/object-store workloads; the documented qualification of those results is appropriate. LGTM.
* main: perf: improve IVF-SQ training, encoding, and reader reuse (apache#91) # Conflicts: # docs/releases.html
Sparse IVF training partitions can learn narrow or constant scalar bounds that clip unseen residuals. IVF-SQ also repeats residual allocations, list transposition work, ID decoding, and query heap setup. This change pools residual bounds across the training sample and reduces build and repeated-query overhead while preserving the IVSQ v1 file layout.
IVFSQIndexReader::openremains uncached.The version, required flags, blocked-code layout, and delta-varint IDs are unchanged. Existing files keep their recorded quantizers. Newly built files store pooled bounds in the existing per-list metadata; the pre-change reader at
8dcabf2successfully reads them. Rebuilding obtains the new training bounds; reader caching and scan optimizations require the new reader.Measured results
Three-run native
ann_benchmedians on Apple M4 Pro (12 CPU cores, 48 GiB RAM), Rust 1.95 release builds, eight workers, 1,024 partitions, 64 probes, Top-10, 65,536 training rows, and 1,000 held-out queries. Values show baseline8dcabf2→ current, using the same input files and settings. GloVe is L2-normalized.Build time includes training, encoding, and serialization. The native benchmark uses a 4 GiB reader budget: sequential queries can reuse partitions loaded earlier in the sweep; batch timing uses a separate fresh reader and includes payload reads and cache insertion. The baseline reader does not cache SQ partitions. File sizes are unchanged.
These measurements cover a warm local filesystem cache. Cold storage, object stores, other architectures, and other distributions need separate measurement. Pooled bounds can lose resolution on extreme-outlier data.
See the IVF-SQ benchmark results and reproduction guide.
Validation
cargo test --workspace: 510 passed, 2 intentionally ignored, including v1 golden fixtures.