mirror of
https://github.com/facebookresearch/faiss.git
synced 2026-10-12 02:13:36 +00:00
5e74b1bddeb5e554023a044007a4ce22fd19647e
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5e74b1bdde |
feat: implement RVV optimized scalar quantizer codecs and distance computation (#5535)
Summary:
This PR adds RISC-V Vector Extension SIMD support for Faiss scalar quantizer.
The implementation introduces runtime vector-length aware RVV kernels using the m8 vector configuration, instead of assuming a fixed vector width. This matches the variable-length nature of RVV hardware and allows the implementation to scale across different RISC-V vector implementations.
## Changes
- Add RVV implementations for scalar quantizer codecs:
- 8-bit codec decoding
- 4-bit codec decoding
- (6-bit stays scalar: the RVV gather decoder measured 12-22% slower
than the scalar path, so QT_6bit falls back until a faster decoder
exists)
- Add RVV quantizer reconstruction support for:
- Uniform quantizers
- Non-uniform quantizers
- FP16 (Zvfhmin is the enforced minimum ISA of the RISCV_RVV level; a
build without it fails compilation rather than silently falling back)
- BF16
- Direct 8-bit quantization
- Signed 8-bit quantization
- LloydMax quantizers (1/2/3/4/8-bit)
- Add RVV optimized similarity implementations:
- L2 distance
- Inner product
Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5535
Reviewed By: mnorris11
Differential Revision: D116996553
Pulled By: juancarpio27
fbshipit-source-id: dc726948fa602a04a757d8f9cab8eea41ac1979a
|
||
|
|
41133fde42 |
Add EDEN quantizer indexes (#5278)
Summary: Adds EDEN and IVF-EDEN index types, using Lloyd-Max centroids from the EDEN paper with the original unbiased scale as the default. The implementation also supports the biased MSE-minimizing scale as an option. References: - EDEN: Vargaftik et al., ICML 2022, https://proceedings.mlr.press/v162/vargaftik22a.html - Biased scale: DRIVE, NeurIPS 2021, https://papers.nips.cc/paper/2021/hash/0397758f8990c1b41b81b43ac389ab9f-Abstract.html This PR includes: - `IndexEDEN` and `IndexIVFEDEN` - factory strings such as `EDEN`, `EDEN4`, `IVF16,EDEN4`, and `EDEN4BIASED` - serialization/deserialization - focused tests - `benchs/bench_eden.py` for EDEN vs RaBitQ recall, quantization MSE, and speed comparisons ## Benchmark Summary Benchmarks compare EDEN against RaBitQ using `recall@10` and reconstruction MSE. Parameters for the accuracy/MSE sweep: `nt=4000`, `nb=4000`, `nq=200`, `k=10`, `qb=0`, one thread, 300 bootstrap samples. Across all HR/RR rotated 2-bit and 4-bit rows in the sweep, EDEN reduced quantization MSE by 14.7% on average and improved recall by +0.0106 absolute on average. For rotated 4-bit rows, EDEN reduced MSE by 23.4% on average and improved recall by +0.0150 absolute on average. Representative rows: | d | distribution | rotation | bits | RaBitQ recall | EDEN recall | recall delta | MSE reduction | |---:|---|---|---:|---:|---:|---:|---:| | 64 | gaussian | RR | 4 | 0.8630 | 0.8835 | +0.0205 | 30.64% | | 256 | laplace | RR | 4 | 0.8750 | 0.9035 | +0.0285 | 25.02% | | 1024 | laplace | RR | 4 | 0.8865 | 0.9070 | +0.0205 | 20.00% | | 1536 | gaussian | RR | 4 | 0.8700 | 0.8895 | +0.0195 | 19.17% | | 64 | laplace | RR | 2 | 0.6125 | 0.6480 | +0.0355 | 10.49% | | 256 | gaussian | HR | 2 | 0.6185 | 0.6400 | +0.0215 | 6.83% | Search-time benchmark parameters: `nt=5000`, `nb=10000`, `nq=50`, `k=1`, one thread, 7 repeats. | d | 1-bit EDEN/RaBitQ | 2-bit EDEN/RaBitQ | 4-bit EDEN/RaBitQ | |---:|---:|---:|---:| | 64 | 0.74x | 0.57x | 0.92x | | 256 | 0.68x | 0.75x | 1.34x | | 1024 | 0.78x | 1.02x | 1.87x | | 1536 | 0.81x | 1.00x | 1.71x | | 4096 | 0.87x | 1.05x | 1.71x | EDEN is faster than or near RaBitQ for 1-bit and 2-bit in these flat-search measurements. 4-bit EDEN improves MSE/recall but is slower at high dimensions because it uses Lloyd-Max centroid lookup and per-vector scaling rather than RaBitQ’s bitwise/popcount-style kernels. ## Verification - `tests/test_eden.py`: 7 passed, 16 subtests passed - rebased on latest `facebookresearch/faiss:main` - branch is one commit over upstream main Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5278 Test Plan: buck test //faiss/tests/:test_eden Reviewed By: mnorris11 Differential Revision: D110218915 Pulled By: mdouze fbshipit-source-id: 556772dfe8c8e5b61e4e37d454e97ee317a7b665 |
||
|
|
74d36198fa |
TurboQuant in ScalarQuantizer: full Algorithm 2 (QJL stage) with SIMD and optimizations (#5170)
Summary: Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5170 Adds the complete TurboQuant implementation (paper Algorithm 2, "TurboQuant_prod") as a standalone TurboQuantizer class alongside the MSE-only ScalarQuantizer integration from D102195204. Benchmark results - TLDR: TQ has a similar operating point to RaBitQ in terms of memory savings, but it never hit RaBitQ levels of recall. We do not really recommend TurboQuant for most ranking scenarios. We implemented and compared TurboQuant to our existing implementation of RaBitQ across 20+ datasets. Some were internal, 18 were open source (from https://vector-index-bench.github.io/datasets.html, a nice collection) - RaBitQ had 4% to 53% higher recall than TurboQuant at equivalent bit widths (i.e. TurboQuant2 vs RaBitQ2), for every single parameter swept. 2 bit is one of the most interesting operating points. TurboQuant recall was notably coupled with dataset characteristics: recall correlates heavily with dataset effective dimensions. - TurboQuant had ~1.7x faster scoring than non-FastScan RaBitQ at equivalent bit widths for practical qb values. But RaBitQFS is faster and has the same recall as non-FS RaBitQ for reasonable datasets. - Fast Walsh Hadamard Transform rather than Gaussian in the QJL step improved TurboQuant recall 7-15%. Random rotation was also better than Gaussian, and even better than FWHT on 5/18 datasets. So we just dropped Gaussian here and kept RR and FWHT. - TurboQuant may have some recall benefit on datasets with very high effective dimensions, e.g. after applying heavy PCA, but in these cases recall is pretty degenerate anyway (50% ish). - TurboQuant adaptive bit lengths (2.5, 3.5) raised recall only marginally over 2 and 3 respectively, 2-4%. However there is no reason this can't also be applied to RaBitQ for additional operating points. We plan to investigate this sometime soon. Description - **Core algorithm:** - TurboQuantizer: two-stage quantizer (Lloyd-Max MSE + QJL sign bits) - IndexIVFTurboQ: IVF index with by_residual=false - Bit-plane encoding layout for MSE codes - Per-vector factors (norm, gamma) for distance computation - FWHT and Random Rotation QJL projection options - Custom distance computer with direct MSE+QJL scoring **SIMD and pre-screening:** - turboq::masked_sum SIMD kernel (AVX2/AVX512) for 1-bit MSE and QJL dot products - Public TurboQDistanceComputer with pre-computed query state - MSE-first pre-screening with worst-case L1 QJL error bound - Adaptive bit width (TurboQ2.5, TurboQ3.5 factory strings) **Integer popcount paths:** - Integer popcount MSE path (qb parameter) reusing rabitq::bitwise_and_dot_product — 1.2-2.2x QPS with <1% recall loss - Integer QJL path (int_qjl) — additional 1.3x QPS on top of qb **Random Rotation QJL:** - QJLProjectionType enum (FWHT, RANDOM_ROTATION) - QR-orthogonalized Gaussian matrix for exact orthogonality - TurboQ{n}r factory string **Removed after benchmarking (not useful):** - Gaussian QJL (TurboQ{n}g): strictly worse recall AND QPS than FWHT on all 26 datasets - Statistical bound (stat_bound): catastrophic recall loss on real data (mean -0.20 R@10) due to CLT assumption failing on real embedding distributions - Two-pass batch screening (two_pass_c): no benefit with practical IVF settings (candidate count > list size makes it a no-op) Factory strings: TurboQ, TurboQ{1-5}, TurboQ{n}r, TurboQ2.5, TurboQ3.5 All work standalone or with IVF prefix (e.g., IVF100,TurboQ2). Reviewed By: mdouze Differential Revision: D102408184 fbshipit-source-id: b985e9c4327169a95c5bb9042a5ca3a093bcc405 |
||
|
|
c41405d525 |
Enforce minimum training set size for ScalarQuantizer, check nullptr (#5141)
Summary: Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5141 Reviewed By: mnorris11 Differential Revision: D102078734 fbshipit-source-id: f2c9899b5a34b620a359e01dc1e530bfdd5aa960 |
||
|
|
a7116ed4ec |
Add TurboQuant (CPU) (#5049)
Summary: This PR adds initial TurboQuant (see https://github.com/facebookresearch/faiss/issues/4990) support to Faiss and integrates it into the main codepaths needed for local evaluation. Changes in this PR: - add `IndexTurboQuantMSE` and the underlying `TurboQuantizer` implementation - add cloning and serialization support for TurboQuant indexes - expose TurboQuant through the Python SWIG bindings - extend quantizer benchmarking to cover TurboQuant and additional datasets - add unit tests for TurboQuant reconstruction behavior ## Preliminary Benchmarks These are preliminary local results from a macOS CPU-only run. Command: ```bash python bench_quantizer.py glove 100x4 turboquant pq rq eval on glove 100x4 maxtrain=100000 No training set: training on database ===== PQ training time: 0.594 s encode time: 0.317 reconstruction error: 0.010 recall@1: 0.7036 recons_err_compat 0.100 code_size: 50 B/vector ===== RQ training time: 208.554 s max_beam_size=1 encode time: 2.977 reconstruction error: 0.027 recall@1: 0.6034 recons_err_compat 0.162 code_size: 50 B/vector max_beam_size=2 encode time: 5.774 reconstruction error: 0.023 recall@1: 0.6280 recons_err_compat 0.151 code_size: 50 B/vector max_beam_size=4 encode time: 12.271 reconstruction error: 0.020 recall@1: 0.6512 recons_err_compat 0.140 code_size: 50 B/vector max_beam_size=8 encode time: 25.617 reconstruction error: 0.018 recall@1: 0.6751 recons_err_compat 0.131 code_size: 50 B/vector max_beam_size=16 encode time: 50.257 reconstruction error: 0.016 recall@1: 0.6890 recons_err_compat 0.123 code_size: 50 B/vector max_beam_size=32 encode time: 105.272 reconstruction error: 0.014 recall@1: 0.7067 recons_err_compat 0.116 code_size: 50 B/vector ===== TurboQuant training time: 0.002 s encode time: 0.080 reconstruction error: 0.009 recall@1: 0.7189 recons_err_compat 0.095 code_size: 50 B/vector ``` ### Initial takeaway On this macOS CPU-only benchmark, TurboQuant shows: - better `recall@1` than PQ at the same code size - lower reconstruction error than PQ - higher `recall@1` than the best RQ setting tested here - much lower training and encoding cost than RQ ### TODO - [x] add JQL support (only MSE at the moment) - reproduce the paper benchmarks with same datasets (need a beefy machine but script is ready) - ~add CUDA support~ (will be in another PR if this is accepted) Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5049 Test Plan: buck build fbcode//faiss:faiss buck test fbcode//faiss/tests:test_factory buck test fbcode//faiss/tests:test_scalar_quantizer buck test fbcode//faiss/tests:test_scalar_quantizer_correctness buck build fbcode//faiss/tests/fb:faiss_fuzzer_helpers Reviewed By: mdouze Differential Revision: D102195204 Pulled By: mnorris11 fbshipit-source-id: a302541a1fc5c229e697f28e639cba633babd072 |
||
|
|
8d1b964c11 |
Enable dynamic dispatch build on Windows (#5127)
Summary: Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5127 Attempt to enable FAISS dynamic dispatch (DD) mode on Windows/MSVC. Changes: - CMakeLists.txt: Remove if(NOT WIN32) guard from DD section, add MSVC per-file SIMD flags (/arch:AVX2, /arch:AVX512, /bigobj) alongside existing GCC/Clang flags - build-pull-request.yml: Add windows-x86_64-DD-cmake job that runs immediately (no dependency on linux-x86_64-cmake), builds with MSVC and FAISS_OPT_LEVEL=dd, runs C++ tests and Python tests This diff is expected to fail on Windows due to MSVC requiring explicit template specialization declarations (C++ §17.8.3) which GCC/Clang don't enforce. The CI failure will surface the exact errors to guide the fix. UPDATE: in fact, MSVC does not seem to require this. After fixing build scripts and a few classical Windows error, the C++ and python tests pass. Reviewed By: algoriddle Differential Revision: D101649751 fbshipit-source-id: 765ef8483d02652ce58625cd22baab8870acf718 |
||
|
|
6299bff1f6 |
Add QT_0bit to ScalarQuantizer for centroid-only IVF distance (#5079)
Summary: Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5079 Adds a new ScalarQuantizer::QT_0bit type that encodes 0 bits per component, enabling IndexIVFScalarQuantizer to operate in centroid-only distance mode (code_size=0). In this mode, distance_to_code() returns the coarse distance from the quantizer, no per-vector data is stored, and reconstruction returns the centroid vector. This is useful for IVF configurations where only query-to-centroid distances are needed (e.g., Unicorn's SQ0 use case). Changes: - Add QT_0bit enum to ScalarQuantizer::QuantizerType - Add IVFCoarseDistanceScanner that returns coarse_dis from set_list() - Handle code_size=0 in encode/decode/reconstruct/add paths - Add 'SQ0' to index_factory - Force by_residual=false for QT_0bit - Guard memcpy in ArrayInvertedLists for code_size=0 - Handle QT_0bit in index_read.cpp validation switch - Add tests for L2, IP, and index_factory --- Reviewed By: ALittleYam Differential Revision: D100348052 fbshipit-source-id: 9d57aa71d154e604bda71c131757f993a9e04f39 |
||
|
|
7721a01e62 |
clamping variable used for computing percentile (#4687)
Summary: Pull Request resolved: https://github.com/facebookresearch/faiss/pull/4687 # Bug: vmin > vmax When o > n/2 ### How the Code Works - You compute `o = int(rs_arg * n)`, then clamp it so that `o <= n/2`. - You use `std::nth_element` to find: - `vmin = x_copy[o]` (the o-th smallest element) - `vmax = x_copy[n-1-o]` (the (n-1-o)-th smallest element) ### What Happens When o > n/2? - The code clamps `o` to `n/2` if `o > n/2`. - For even `n`, `o = n/2` means: - `vmin = x_copy[n/2]` - `vmax = x_copy[n-1-n/2] = x_copy[n/2 - 1]` - For odd `n`, integer division means `o = floor(n/2)`. ### Can vmin > vmax? - Yes, if `o > n/2` (or is clamped to `n/2`), then `o > n-1-o`. - This means the index for `vmin` is greater than the index for `vmax`. - In a sorted array, this would mean `vmin >= vmax`. - In practice, since `nth_element` only guarantees the element at the index is in the correct position, but does not fully sort the array, you can end up with `vmin > vmax`. ## Example Suppose `n = 10`, so `o = 5` after clamping. - `vmin = x_copy[5]` (6th smallest) - `vmax = x_copy[4]` (5th smallest) If the array is not sorted, and you only use `nth_element`, you could have `vmin > vmax`. Reviewed By: mnorris11 Differential Revision: D86571612 fbshipit-source-id: 1fb8225fd021a2984b525a2c508062e2284213b7 |