Repository navigation
perf: faster LUT and query preparation, QGKMeans cache and visited set - #188
Merged
Merged
Conversation
… quantizer Compute the LUT minimum and maximum while packing the table (generic, AVX2, AVX-512 and NEON) instead of scanning it again, and quantize finite float LUTs with a truncate-and-half-test kernel. LUT entries are at least their minimum, so this equals the existing round-half-away quantizer; it shares its body with the core AVX2/AVX-512 quantizer through a NonNegative template parameter. NaN, infinite and zero bounds fall back to the original reduction and quantization, and double LUTs are unchanged. Prepared LUT bytes, deltas and floating-point exception flags are identical.
Test the IEEE exponent bits of every value and reject once after the loop, which compilers vectorize without relaxed floating-point options. The rejected inputs and error message are unchanged.
Lower the QuerySumCache threshold from 1024 to 512 padded dimensions. Cached and uncached assignments are identical; tests cover 511, 512, 960 and 1024 dimensions.
Fewer collisions spill into the unordered_set during graph search, at twice the table memory and clear cost. Search results are unchanged.
The v1 contract requires the round-to-nearest floating-point environment. Clang inlines the scalar-tail std::round of the AVX2/AVX-512 quantizers with a mode-dependent addition, so directed modes rounded half-way tails down. Keep the byte and exception-flag comparisons with the reference quantizer in every mode.
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.
Summary
Four performance changes. Results are unchanged: identical LUT bytes, codes, assignments, objectives and search results. No public API or saved-format changes.
pack_lutnow also returns the table's minimum and maximum (generic, AVX2, AVX-512 and NEON kernels), instead of a seconddata_rangepass.NonNegativetemplate parameter (scalar_quantize_nonnegative_uint8_*).Lut<double>is unchanged.std::functions.check_inputtests the IEEE exponent bits with an integer reduction that compilers vectorize. It rejects the same inputs with the same message.unordered_set. The cost is twice the table memory and clear time.A larger
exact_assignpoint block (128 → 256) was also tried and dropped: at 960 dimensions it was 16% slower (0 of 6 pairs faster), and it showed no gain at 128 dimensions.Testing
Linux x86-64 host: Xeon Gold 6418H, 2 sockets; GCC 14.3;
RABITQ_ENABLE_NATIVE_OPTIMIZATION=OFF.--no-tests=error):RABITQ_USE_OPENMP=OFF: 173 passed.lut_prepare_testcompares fused tables, extrema and uint8 codes byte for byte against the original path on every available backend, together with floating-point exception flags, in all four rounding modes. It also checks half-way boundaries againststd::roundin the documented round-to-nearest mode, plus signed zeros, denormals, NaN and infinities, and the double LUT.std::rounduses a mode-dependent addition. This was already the case onmain, and those modes are outside the documented contract, so thestd::roundcomparison runs only in round-to-nearest.check-format.sh, fullcheck-tidy.sh build-tidy(clang-tidy 23) andgit diff --checkpass.--target=aarch64-linux-gnusyntax and semantic check passes. It was not built into object code or run.-O0, the AVX2/AVX-512 FastScan objects export the same set of weakstd::symbols asmain.scalar_quantize_uint8_avx512compiles to identical machine code; the AVX2 version differs only by a register swap.Benchmarks
The process was pinned to one idle core and its NUMA node (
numactl --physcpubind=20 --membind=0), single-threaded. Baseline (main) and candidate runs were alternated, 6–8 pairs per row. Synthetic clustered Gaussian data with fixed seeds.Ratio is candidate/baseline (median, with the range); "wins" counts the pairs in which the candidate was better. Quality was identical in every row.
SplitBatchQuery::reset, d=128, standard / HACCSplitBatchQuery::reset, d=960, standard / HACCrabitq_core_benchmarkprepare_query, 4 and 9 bitsencode_packedThe LUT gain is in query preparation. It is visible end to end where preparation is a large share of the work (QGKMeans assignment), but not in IVF search.
Limits:
Not run locally: NEON execution, Windows and macOS builds.