Files
faiss/tests/test_scalar_quantizer.cpp
T
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

696 lines
23 KiB
C++

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <gtest/gtest.h>
#include <array>
#include <cmath>
#include <cstring>
#include <memory>
#include <random>
#include <vector>
#include <faiss/IndexFlat.h>
#include <faiss/IndexIVFFlat.h>
#include <faiss/IndexScalarQuantizer.h>
#include <faiss/impl/ScalarQuantizer.h>
#include <faiss/impl/scalar_quantizer/distance_computers.h>
#include <faiss/impl/scalar_quantizer/quantizers.h>
#include <faiss/impl/scalar_quantizer/scanners.h>
#include <faiss/impl/scalar_quantizer/similarities.h>
#include <faiss/index_factory.h>
#include <faiss/utils/distances.h>
#include <faiss/utils/random.h>
#include <faiss/utils/simd_levels.h>
namespace {
std::vector<float> make_normalized_vectors(size_t n, size_t d) {
std::vector<float> x(n * d);
faiss::float_randn(x.data(), x.size(), 1234);
faiss::fvec_renorm_L2(d, n, x.data());
return x;
}
float mean_squared_error(
const std::vector<float>& x,
const std::vector<float>& y) {
EXPECT_EQ(x.size(), y.size());
return faiss::fvec_L2sqr(x.data(), y.data(), x.size()) / x.size();
}
template <int NBits>
using ScalarLloydMaxQuantizer = faiss::scalar_quantizer::
QuantizerLloydMax<NBits, faiss::SIMDLevel::NONE>;
template <int NBits>
using ScalarLloydMaxL2DistanceComputer = faiss::scalar_quantizer::DCTemplate<
ScalarLloydMaxQuantizer<NBits>,
faiss::scalar_quantizer::SimilarityL2<faiss::SIMDLevel::NONE>,
faiss::SIMDLevel::NONE>;
template <int NBits>
using ScalarLloydMaxL2Scanner = faiss::scalar_quantizer::IVFSQScannerL2<
ScalarLloydMaxL2DistanceComputer<NBits>>;
faiss::ScalarQuantizer make_trained_lloyd_max_sq(
size_t d,
faiss::ScalarQuantizer::QuantizerType qtype) {
const size_t n = 128;
std::vector<float> x = make_normalized_vectors(n, d);
faiss::ScalarQuantizer sq(d, qtype);
sq.train(n, x.data());
return sq;
}
struct ScopedSIMDLevel {
faiss::SIMDLevel original_level;
explicit ScopedSIMDLevel(faiss::SIMDLevel level)
: original_level(faiss::SIMDConfig::get_level()) {
faiss::SIMDConfig::set_level(level);
}
~ScopedSIMDLevel() {
faiss::SIMDConfig::set_level(original_level);
}
};
std::vector<faiss::SIMDLevel> available_lloyd_max_simd_levels() {
std::vector<faiss::SIMDLevel> levels;
for (faiss::SIMDLevel level :
{faiss::SIMDLevel::AVX512,
faiss::SIMDLevel::AVX2,
faiss::SIMDLevel::ARM_NEON}) {
if (faiss::SIMDConfig::is_simd_level_available(level)) {
levels.push_back(level);
}
}
return levels;
}
template <int NBits>
void expect_lloyd_max_simd_dispatch_for_compatible_dim(
faiss::SIMDLevel level,
faiss::ScalarQuantizer::QuantizerType qtype) {
ScopedSIMDLevel scoped(level);
const size_t d = 32;
faiss::ScalarQuantizer sq = make_trained_lloyd_max_sq(d, qtype);
std::unique_ptr<faiss::ScalarQuantizer::SQuantizer> quantizer(
sq.select_quantizer());
ASSERT_NE(quantizer, nullptr);
std::unique_ptr<faiss::ScalarQuantizer::SQDistanceComputer> dc(
sq.get_distance_computer(faiss::METRIC_L2));
ASSERT_NE(dc, nullptr);
std::unique_ptr<faiss::InvertedListScanner> scanner(
sq.select_InvertedListScanner(
faiss::METRIC_L2, nullptr, false, nullptr, false));
ASSERT_NE(scanner, nullptr);
auto* quantizer_raw = quantizer.get();
auto* dc_raw = dc.get();
auto* scanner_raw = scanner.get();
EXPECT_NE(typeid(*quantizer_raw), typeid(ScalarLloydMaxQuantizer<NBits>));
EXPECT_NE(typeid(*dc_raw), typeid(ScalarLloydMaxL2DistanceComputer<NBits>));
EXPECT_NE(typeid(*scanner_raw), typeid(ScalarLloydMaxL2Scanner<NBits>));
}
template <int NBits>
void expect_lloyd_max_simd_dispatch_fallback_for_incompatible_dim(
faiss::SIMDLevel level,
size_t d,
faiss::ScalarQuantizer::QuantizerType qtype) {
ScopedSIMDLevel scoped(level);
faiss::ScalarQuantizer sq = make_trained_lloyd_max_sq(d, qtype);
std::unique_ptr<faiss::ScalarQuantizer::SQuantizer> quantizer(
sq.select_quantizer());
ASSERT_NE(quantizer, nullptr);
std::unique_ptr<faiss::ScalarQuantizer::SQDistanceComputer> dc(
sq.get_distance_computer(faiss::METRIC_L2));
ASSERT_NE(dc, nullptr);
std::unique_ptr<faiss::InvertedListScanner> scanner(
sq.select_InvertedListScanner(
faiss::METRIC_L2, nullptr, false, nullptr, false));
ASSERT_NE(scanner, nullptr);
auto* quantizer_raw = quantizer.get();
auto* dc_raw = dc.get();
auto* scanner_raw = scanner.get();
EXPECT_EQ(typeid(*quantizer_raw), typeid(ScalarLloydMaxQuantizer<NBits>));
EXPECT_EQ(typeid(*dc_raw), typeid(ScalarLloydMaxL2DistanceComputer<NBits>));
EXPECT_EQ(typeid(*scanner_raw), typeid(ScalarLloydMaxL2Scanner<NBits>));
}
template <int NBits>
void check_lloyd_max_distance_path_parity(
faiss::SIMDLevel level,
faiss::ScalarQuantizer::QuantizerType qtype) {
ScopedSIMDLevel scoped(level);
const size_t d = 32;
const size_t n = 128;
std::vector<float> xb = make_normalized_vectors(n, d);
std::vector<float> xq = make_normalized_vectors(1, d);
faiss::ScalarQuantizer sq(d, qtype);
sq.train(n, xb.data());
std::vector<uint8_t> codes(sq.code_size * n, 0);
sq.compute_codes(xb.data(), codes.data(), n);
std::unique_ptr<faiss::ScalarQuantizer::SQDistanceComputer> scalar_dc(
faiss::scalar_quantizer::sq_select_distance_computer<
faiss::SIMDLevel::NONE>(
faiss::METRIC_L2, qtype, d, sq.trained));
std::unique_ptr<faiss::ScalarQuantizer::SQDistanceComputer> simd_dc(
sq.get_distance_computer(faiss::METRIC_L2));
ASSERT_NE(scalar_dc, nullptr);
ASSERT_NE(simd_dc, nullptr);
scalar_dc->set_query(xq.data());
simd_dc->set_query(xq.data());
std::vector<uint8_t> zero_code(sq.code_size, 0);
std::vector<uint8_t> max_code(sq.code_size, 0xff);
const std::array<const uint8_t*, 6> query_codes = {
codes.data(),
codes.data() + sq.code_size,
codes.data() + 2 * sq.code_size,
codes.data() + 3 * sq.code_size,
zero_code.data(),
max_code.data()};
for (const uint8_t* code : query_codes) {
EXPECT_NEAR(
scalar_dc->query_to_code(code),
simd_dc->query_to_code(code),
1e-3);
}
std::vector<uint8_t> bundle(4 * sq.code_size, 0);
std::memcpy(bundle.data(), zero_code.data(), sq.code_size);
std::memcpy(bundle.data() + sq.code_size, max_code.data(), sq.code_size);
std::memcpy(bundle.data() + 2 * sq.code_size, codes.data(), sq.code_size);
std::memcpy(
bundle.data() + 3 * sq.code_size,
codes.data() + sq.code_size,
sq.code_size);
scalar_dc->codes = bundle.data();
scalar_dc->code_size = sq.code_size;
simd_dc->codes = bundle.data();
simd_dc->code_size = sq.code_size;
const std::array<std::pair<faiss::idx_t, faiss::idx_t>, 4> pairs = {{
{0, 1},
{0, 2},
{1, 3},
{2, 3},
}};
for (const auto& [lhs, rhs] : pairs) {
EXPECT_NEAR(
scalar_dc->symmetric_dis(lhs, rhs),
simd_dc->symmetric_dis(lhs, rhs),
1e-3);
}
std::unique_ptr<faiss::InvertedListScanner> scalar_scanner(
faiss::scalar_quantizer::sq_select_InvertedListScanner<
faiss::SIMDLevel::NONE>(
qtype,
faiss::METRIC_L2,
d,
sq.code_size,
sq.trained,
nullptr,
false,
nullptr,
false));
std::unique_ptr<faiss::InvertedListScanner> simd_scanner(
sq.select_InvertedListScanner(
faiss::METRIC_L2, nullptr, false, nullptr, false));
ASSERT_NE(scalar_scanner, nullptr);
ASSERT_NE(simd_scanner, nullptr);
scalar_scanner->set_query(xq.data());
simd_scanner->set_query(xq.data());
scalar_scanner->set_list(0, 0.0f);
simd_scanner->set_list(0, 0.0f);
for (const uint8_t* code : query_codes) {
EXPECT_NEAR(
scalar_scanner->distance_to_code(code),
simd_scanner->distance_to_code(code),
1e-3);
}
}
void check_tqmse_roundtrip(
size_t d,
faiss::ScalarQuantizer::QuantizerType qtype) {
const size_t n = 128;
std::vector<float> x = make_normalized_vectors(n, d);
faiss::ScalarQuantizer sq(d, qtype);
sq.train(n, x.data());
std::vector<uint8_t> codes(sq.code_size * n, 0);
sq.compute_codes(x.data(), codes.data(), n);
std::vector<float> decoded(n * d);
sq.decode(codes.data(), decoded.data(), n);
for (float v : decoded) {
EXPECT_TRUE(std::isfinite(v));
EXPECT_LE(v, 1.0f);
EXPECT_GE(v, -1.0f);
}
}
void check_eden_roundtrip(
size_t d,
size_t nb_bits,
faiss::ScalarQuantizer::QuantizerType qtype) {
const size_t n = 128;
std::vector<float> x = make_normalized_vectors(n, d);
faiss::ScalarQuantizer sq(d, qtype);
sq.train(0, nullptr);
const size_t k = size_t(1) << nb_bits;
EXPECT_EQ(sq.bits, nb_bits);
EXPECT_EQ(sq.trained.size(), 2 * k - 1);
std::vector<uint8_t> codes(sq.code_size * n, 0);
sq.compute_codes(x.data(), codes.data(), n);
std::vector<float> decoded(n * d);
sq.decode(codes.data(), decoded.data(), n);
for (float v : decoded) {
EXPECT_TRUE(std::isfinite(v));
}
}
} // namespace
TEST(ScalarQuantizer, RSQuantilesClamping) {
int d = 8;
int n = 100;
std::vector<float> x(d * n);
for (size_t i = 0; i < x.size(); i++) {
x[i] = static_cast<float>(i % 100);
}
faiss::ScalarQuantizer sq(d, faiss::ScalarQuantizer::QT_8bit);
sq.rangestat = faiss::ScalarQuantizer::RS_quantiles;
sq.rangestat_arg = 0.05f;
ASSERT_NO_THROW(sq.train(n, x.data()));
sq.rangestat_arg = 0.0f;
ASSERT_NO_THROW(sq.train(n, x.data()));
sq.rangestat_arg = -0.1f;
ASSERT_NO_THROW(sq.train(n, x.data()));
sq.rangestat_arg = 0.8f;
ASSERT_NO_THROW(sq.train(n, x.data()));
sq.rangestat_arg = 0.5f;
ASSERT_NO_THROW(sq.train(n, x.data()));
}
TEST(ScalarQuantizer, RSQuantilesOddSize) {
int d = 4;
int n = 5;
std::vector<float> x(d * n);
for (size_t i = 0; i < x.size(); i++) {
x[i] = static_cast<float>(i);
}
faiss::ScalarQuantizer sq(d, faiss::ScalarQuantizer::QT_8bit);
sq.rangestat = faiss::ScalarQuantizer::RS_quantiles;
sq.rangestat_arg = 0.4f;
ASSERT_NO_THROW(sq.train(n, x.data()));
sq.rangestat_arg = 0.5f;
ASSERT_NO_THROW(sq.train(n, x.data()));
sq.rangestat_arg = 0.6f;
ASSERT_NO_THROW(sq.train(n, x.data()));
}
TEST(ScalarQuantizer, RSQuantilesValidRange) {
int d = 8;
int n = 100;
std::vector<float> x(d * n);
for (size_t i = 0; i < x.size(); i++) {
x[i] = static_cast<float>(i);
}
faiss::ScalarQuantizer sq(d, faiss::ScalarQuantizer::QT_8bit);
sq.rangestat = faiss::ScalarQuantizer::RS_quantiles;
sq.rangestat_arg = 0.1f;
sq.train(n, x.data());
std::vector<uint8_t> codes(sq.code_size * n);
ASSERT_NO_THROW(sq.compute_codes(x.data(), codes.data(), n));
std::vector<float> decoded(d * n);
ASSERT_NO_THROW(sq.decode(codes.data(), decoded.data(), n));
}
TEST(ScalarQuantizer, RSQuantilesSmallDataset) {
int d = 2;
int n = 2;
std::vector<float> x = {1.0f, 2.0f, 3.0f, 4.0f};
faiss::ScalarQuantizer sq(d, faiss::ScalarQuantizer::QT_8bit);
sq.rangestat = faiss::ScalarQuantizer::RS_quantiles;
sq.rangestat_arg = 0.1f;
ASSERT_NO_THROW(sq.train(n, x.data()));
}
TEST(ScalarQuantizer, MinimalTraining) {
std::vector<float> x = {1.0f, 2.0f, 3.0f, 4.0f};
using SQ = faiss::ScalarQuantizer;
for (auto qt : {
SQ::QT_4bit,
SQ::QT_4bit_uniform,
SQ::QT_6bit,
SQ::QT_8bit,
SQ::QT_8bit_uniform,
}) {
SCOPED_TRACE(qt);
for (size_t d : {1, 2}) {
faiss::IndexScalarQuantizer index(d, qt);
index.sq.rangestat = SQ::RS_minmax;
// n=0 is insufficient.
EXPECT_THROW(index.train(0, x.data()), faiss::FaissException);
// nullptr throws, not crashes.
EXPECT_THROW(index.train(1, nullptr), faiss::FaissException);
// 1x1 values is allowed.
EXPECT_NO_THROW(index.train(1, x.data()));
}
}
}
TEST(TestSQ0bit, CoarseOnlySearch) {
// Test QT_0bit: centroid-only distance
int d = 64;
int nlist = 8;
int nb = 1000;
int nq = 10;
int k = 5;
std::mt19937 rng(42);
std::uniform_real_distribution<float> distrib(0.0f, 1.0f);
std::vector<float> xb(nb * d), xq(nq * d);
for (int i = 0; i < nb * d; i++) {
xb[i] = distrib(rng);
}
for (int i = 0; i < nq * d; i++) {
xq[i] = distrib(rng);
}
faiss::IndexFlatL2 quantizer(d);
faiss::IndexIVFScalarQuantizer index(
&quantizer,
d,
nlist,
faiss::ScalarQuantizer::QT_0bit,
faiss::METRIC_L2,
false);
EXPECT_EQ(index.code_size, 0);
EXPECT_FALSE(index.by_residual);
index.train(nb, xb.data());
index.add(nb, xb.data());
EXPECT_EQ(index.ntotal, nb);
index.nprobe = nlist;
std::vector<float> distances(nq * k);
std::vector<faiss::idx_t> labels(nq * k);
index.search(nq, xq.data(), k, distances.data(), labels.data());
// Verify we got results
for (int q = 0; q < nq; q++) {
EXPECT_GE(labels[q * k], 0);
}
// Compare with direct quantizer search - distances should match
std::vector<float> coarse_dis(nq * nlist);
std::vector<faiss::idx_t> coarse_ids(nq * nlist);
quantizer.search(
nq, xq.data(), nlist, coarse_dis.data(), coarse_ids.data());
for (int q = 0; q < nq; q++) {
float ivf_dis = distances[q * k];
bool found = false;
for (int j = 0; j < nlist; j++) {
if (std::abs(ivf_dis - coarse_dis[q * nlist + j]) < 1e-5) {
found = true;
break;
}
}
EXPECT_TRUE(found) << "IVF distance " << ivf_dis
<< " not found in coarse distances for query " << q;
}
}
TEST(TestSQ0bit, IndexFactory) {
int d = 32;
std::unique_ptr<faiss::Index> index(faiss::index_factory(d, "IVF8,SQ0"));
EXPECT_NE(index, nullptr);
auto* ivfsq = dynamic_cast<faiss::IndexIVFScalarQuantizer*>(index.get());
EXPECT_NE(ivfsq, nullptr);
EXPECT_EQ(ivfsq->sq.qtype, faiss::ScalarQuantizer::QT_0bit);
EXPECT_EQ(ivfsq->code_size, 0);
}
TEST(TestSQ0bit, InnerProduct) {
int d = 64;
int nlist = 4;
int nb = 500;
int nq = 5;
int k = 3;
std::mt19937 rng2(43);
std::uniform_real_distribution<float> distrib2(0.0f, 1.0f);
std::vector<float> xb(nb * d), xq(nq * d);
for (int i = 0; i < nb * d; i++) {
xb[i] = distrib2(rng2);
}
for (int i = 0; i < nq * d; i++) {
xq[i] = distrib2(rng2);
}
faiss::IndexFlatIP quantizer(d);
faiss::IndexIVFScalarQuantizer index(
&quantizer,
d,
nlist,
faiss::ScalarQuantizer::QT_0bit,
faiss::METRIC_INNER_PRODUCT,
false);
index.train(nb, xb.data());
index.add(nb, xb.data());
index.nprobe = nlist;
std::vector<float> distances(nq * k);
std::vector<faiss::idx_t> labels(nq * k);
index.search(nq, xq.data(), k, distances.data(), labels.data());
for (int q = 0; q < nq; q++) {
EXPECT_GE(labels[q * k], 0);
}
}
TEST(ScalarQuantizer, TQMSEEncodeDecode) {
check_tqmse_roundtrip(32, faiss::ScalarQuantizer::QT_1bit_tqmse);
check_tqmse_roundtrip(32, faiss::ScalarQuantizer::QT_2bit_tqmse);
check_tqmse_roundtrip(32, faiss::ScalarQuantizer::QT_3bit_tqmse);
check_tqmse_roundtrip(32, faiss::ScalarQuantizer::QT_4bit_tqmse);
check_tqmse_roundtrip(32, faiss::ScalarQuantizer::QT_8bit_tqmse);
}
TEST(ScalarQuantizer, EDENEncodeDecode) {
check_eden_roundtrip(32, 1, faiss::ScalarQuantizer::QT_1bit_eden);
check_eden_roundtrip(32, 2, faiss::ScalarQuantizer::QT_2bit_eden);
check_eden_roundtrip(32, 3, faiss::ScalarQuantizer::QT_3bit_eden);
check_eden_roundtrip(32, 4, faiss::ScalarQuantizer::QT_4bit_eden);
check_eden_roundtrip(32, 5, faiss::ScalarQuantizer::QT_5bit_eden);
check_eden_roundtrip(32, 6, faiss::ScalarQuantizer::QT_6bit_eden);
check_eden_roundtrip(32, 7, faiss::ScalarQuantizer::QT_7bit_eden);
check_eden_roundtrip(32, 8, faiss::ScalarQuantizer::QT_8bit_eden);
}
TEST(ScalarQuantizer, TQMSEAccuracyOrdering) {
const size_t d = 32;
const size_t n = 256;
std::vector<float> x = make_normalized_vectors(n, d);
const std::array<faiss::ScalarQuantizer::QuantizerType, 5> qtypes = {
faiss::ScalarQuantizer::QT_1bit_tqmse,
faiss::ScalarQuantizer::QT_2bit_tqmse,
faiss::ScalarQuantizer::QT_3bit_tqmse,
faiss::ScalarQuantizer::QT_4bit_tqmse,
faiss::ScalarQuantizer::QT_8bit_tqmse};
std::vector<float> mses;
mses.reserve(qtypes.size());
for (auto qtype : qtypes) {
faiss::ScalarQuantizer sq(d, qtype);
sq.train(n, x.data());
std::vector<uint8_t> codes(sq.code_size * n, 0);
sq.compute_codes(x.data(), codes.data(), n);
std::vector<float> decoded(n * d);
sq.decode(codes.data(), decoded.data(), n);
mses.push_back(mean_squared_error(x, decoded));
}
for (size_t i = 0; i + 1 < mses.size(); ++i) {
EXPECT_GE(mses[i], mses[i + 1]);
}
}
TEST(ScalarQuantizer, TQMSENonSimdDims) {
check_tqmse_roundtrip(7, faiss::ScalarQuantizer::QT_1bit_tqmse);
check_tqmse_roundtrip(9, faiss::ScalarQuantizer::QT_2bit_tqmse);
check_tqmse_roundtrip(11, faiss::ScalarQuantizer::QT_3bit_tqmse);
check_tqmse_roundtrip(13, faiss::ScalarQuantizer::QT_4bit_tqmse);
check_tqmse_roundtrip(33, faiss::ScalarQuantizer::QT_8bit_tqmse);
}
TEST(ScalarQuantizer, TQMSESimdDispatchSelection) {
const std::vector<faiss::SIMDLevel> levels =
available_lloyd_max_simd_levels();
if (levels.empty()) {
GTEST_SKIP() << "No SIMD level available for TurboQuant dispatch tests";
}
for (faiss::SIMDLevel level : levels) {
SCOPED_TRACE(faiss::to_string(level));
expect_lloyd_max_simd_dispatch_for_compatible_dim<1>(
level, faiss::ScalarQuantizer::QT_1bit_tqmse);
expect_lloyd_max_simd_dispatch_for_compatible_dim<2>(
level, faiss::ScalarQuantizer::QT_2bit_tqmse);
expect_lloyd_max_simd_dispatch_for_compatible_dim<3>(
level, faiss::ScalarQuantizer::QT_3bit_tqmse);
expect_lloyd_max_simd_dispatch_for_compatible_dim<4>(
level, faiss::ScalarQuantizer::QT_4bit_tqmse);
expect_lloyd_max_simd_dispatch_for_compatible_dim<8>(
level, faiss::ScalarQuantizer::QT_8bit_tqmse);
expect_lloyd_max_simd_dispatch_fallback_for_incompatible_dim<1>(
level, 7, faiss::ScalarQuantizer::QT_1bit_tqmse);
expect_lloyd_max_simd_dispatch_fallback_for_incompatible_dim<2>(
level, 9, faiss::ScalarQuantizer::QT_2bit_tqmse);
expect_lloyd_max_simd_dispatch_fallback_for_incompatible_dim<3>(
level, 11, faiss::ScalarQuantizer::QT_3bit_tqmse);
expect_lloyd_max_simd_dispatch_fallback_for_incompatible_dim<4>(
level, 13, faiss::ScalarQuantizer::QT_4bit_tqmse);
expect_lloyd_max_simd_dispatch_fallback_for_incompatible_dim<8>(
level, 33, faiss::ScalarQuantizer::QT_8bit_tqmse);
}
}
TEST(ScalarQuantizer, EDENSimdDispatchSelection) {
const std::vector<faiss::SIMDLevel> levels =
available_lloyd_max_simd_levels();
if (levels.empty()) {
GTEST_SKIP() << "No SIMD level available for EDEN dispatch tests";
}
for (faiss::SIMDLevel level : levels) {
SCOPED_TRACE(faiss::to_string(level));
expect_lloyd_max_simd_dispatch_for_compatible_dim<1>(
level, faiss::ScalarQuantizer::QT_1bit_eden);
expect_lloyd_max_simd_dispatch_for_compatible_dim<2>(
level, faiss::ScalarQuantizer::QT_2bit_eden);
expect_lloyd_max_simd_dispatch_for_compatible_dim<3>(
level, faiss::ScalarQuantizer::QT_3bit_eden);
expect_lloyd_max_simd_dispatch_for_compatible_dim<4>(
level, faiss::ScalarQuantizer::QT_4bit_eden);
expect_lloyd_max_simd_dispatch_for_compatible_dim<8>(
level, faiss::ScalarQuantizer::QT_8bit_eden);
expect_lloyd_max_simd_dispatch_fallback_for_incompatible_dim<1>(
level, 7, faiss::ScalarQuantizer::QT_1bit_eden);
expect_lloyd_max_simd_dispatch_fallback_for_incompatible_dim<2>(
level, 9, faiss::ScalarQuantizer::QT_2bit_eden);
expect_lloyd_max_simd_dispatch_fallback_for_incompatible_dim<3>(
level, 11, faiss::ScalarQuantizer::QT_3bit_eden);
expect_lloyd_max_simd_dispatch_fallback_for_incompatible_dim<4>(
level, 13, faiss::ScalarQuantizer::QT_4bit_eden);
expect_lloyd_max_simd_dispatch_fallback_for_incompatible_dim<8>(
level, 33, faiss::ScalarQuantizer::QT_8bit_eden);
}
}
TEST(ScalarQuantizer, TQMSESimdDistancePathParity) {
const std::vector<faiss::SIMDLevel> levels =
available_lloyd_max_simd_levels();
if (levels.empty()) {
GTEST_SKIP() << "No SIMD level available for TurboQuant parity tests";
}
for (faiss::SIMDLevel level : levels) {
SCOPED_TRACE(faiss::to_string(level));
check_lloyd_max_distance_path_parity<1>(
level, faiss::ScalarQuantizer::QT_1bit_tqmse);
check_lloyd_max_distance_path_parity<2>(
level, faiss::ScalarQuantizer::QT_2bit_tqmse);
check_lloyd_max_distance_path_parity<3>(
level, faiss::ScalarQuantizer::QT_3bit_tqmse);
check_lloyd_max_distance_path_parity<4>(
level, faiss::ScalarQuantizer::QT_4bit_tqmse);
check_lloyd_max_distance_path_parity<8>(
level, faiss::ScalarQuantizer::QT_8bit_tqmse);
}
}
TEST(ScalarQuantizer, EDENSimdDistancePathParity) {
const std::vector<faiss::SIMDLevel> levels =
available_lloyd_max_simd_levels();
if (levels.empty()) {
GTEST_SKIP() << "No SIMD level available for EDEN parity tests";
}
for (faiss::SIMDLevel level : levels) {
SCOPED_TRACE(faiss::to_string(level));
check_lloyd_max_distance_path_parity<1>(
level, faiss::ScalarQuantizer::QT_1bit_eden);
check_lloyd_max_distance_path_parity<2>(
level, faiss::ScalarQuantizer::QT_2bit_eden);
check_lloyd_max_distance_path_parity<3>(
level, faiss::ScalarQuantizer::QT_3bit_eden);
check_lloyd_max_distance_path_parity<4>(
level, faiss::ScalarQuantizer::QT_4bit_eden);
check_lloyd_max_distance_path_parity<8>(
level, faiss::ScalarQuantizer::QT_8bit_eden);
}
}