Commit Graph
8 Commits
Author SHA1 Message Date
ihb2032 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
2026-09-23 12:00:59 -07:00
ranbenbasat 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
2026-07-10 08:38:21 -07:00
Michael Norris 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
2026-06-04 07:59:24 -07:00
Tom Jackson 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
2026-04-29 16:33:15 -07:00
Fabrizio (Misto) Milo 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
2026-04-24 16:32:53 -07:00
Matthijs Douze 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
2026-04-23 13:31:11 -07:00
Michael Norris 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
2026-04-15 16:16:09 -07:00
Subhadeep Karan 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
2025-11-21 21:34:30 -08:00