perf: vectorize raw vector search - #734
Conversation
| .sum::<f32>(); | ||
| 1.0 / (1.0 + squared_distance) | ||
| } else { | ||
| let squared_distance = |
There was a problem hiding this comment.
The ||a||² + ||b||² - 2·a·b reconstruction is not numerically equivalent to the previous scalar L2 loop for large finite components. I reproduced this with four queries and 128-D vectors: for an exact [1e10; 128] match, this path returns about 4.44e-16 instead of 1.0; a vector differing by 1024 in one component receives the same matrix score, while the scalar path returns about 9.54e-7. This can change Top-K results, not just add a small error. Please use a numerically stable path or fallback when cancellation can dominate, and add large-finite-value coverage.
| norm_b += stored * stored; | ||
| } | ||
| let denominator = query_l2_norm * norm_b.sqrt(); | ||
| let denominator = (query_l2_squared_norm * norm_b).sqrt(); |
There was a problem hiding this comment.
Taking sqrt(query_norm_squared * stored_norm_squared) introduces an overflow that the previous scalar implementation avoided. For an identical [1e15, 0] query and stored vector, both squared norms are finite (1e30), but their product becomes infinity, so this code returns cosine 0 instead of 1. This affects the scalar path too, not only matrix scoring. Please compute the two square roots before multiplying (as before), or otherwise avoid the intermediate product overflow, and add a regression test.
* main: perf: vectorize raw vector search (apache#734) feat(file_index): add predicate evaluation foundation (apache#721) feat(go): add postpone fixed-bucket write bindings (apache#722) perf(vindex): split build timing logs by phase (apache#723) fix(avro): read TIME, BLOB, MULTISET and non-string-key map columns (apache#724) fix(datafusion): surface tag create-time and retention in $tags (apache#728) [core] Support multivalue global index (apache#731) feat: add Java-compatible array predicate pushdown (apache#732) fix: serialize unbounded varchar as string (apache#730) perf(vindex): decouple vector read threads and remove chunk barrier (apache#720) feat(vindex): add DiskANN and IVF-SQ/RQ support (apache#726) # Conflicts: # crates/paimon/src/table/data_file_reader.rs # crates/paimon/src/table/vindex_index_build_builder.rs
What changed
include_row_idssearches on the scalar path to avoid matrix setup and unnecessary scoring.O(n log K)raw Top-K maintenance with buffered partial selection, giving amortizedO(n)candidate selection and sorting only the final K rows.FixedSizeListArrayoffsets and preserve scalar L2 semantics for non-finite values.Why
Raw vector search previously computed every row/query distance with scalar loops and maintained a K-sized heap for every query. This left batch queries unable to reuse optimized matrix kernels and paid
log Kwork for each candidate.Performance
Local release microbenchmark: 8,192 rows, 128 dimensions, L2, K=10. The benchmark compared the previous scalar-distance + heap path with the optimized hybrid path.
Validation
cargo test -p paimon table::vector_search_builder::tests --lib(79 passed)cargo clippy -p paimon --lib -- -D warningscargo fmt --all -- --check