Remove dead TurboQuant projection state (#5618)

Summary:
Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5618

Remove the unused projection buffers and eager `init_projection` work from `ScalarQuantizer::TurboQuantRefine`. Quantizers construct the required projection from `trained` when used, so rebuilding it during training and deserialization only added an unbounded quadratic allocation and cubic QR operation.

Keep `seed` and `qjl_type` as the serialized configuration, enforce that the helper remains a small config-only struct, and cover both projection modes in deserialization tests.

___

Differential Revision: D119243693

fbshipit-source-id: 53f982b00dd849cbe88983ffd27809c2fa738a4c
This commit is contained in:
Trang Nguyen
2026-09-09 11:05:57 -07:00
committed by meta-codesync[bot]
parent 545550f6a2
commit 5702cafcb7
4 changed files with 84 additions and 32 deletions
+77
View File
@@ -4924,3 +4924,80 @@ TEST(IndexFlatPanoramaSafety, ReconstructRejectsOutOfRangeKey) {
EXPECT_NO_THROW(index.reconstruct_n(0, 2, recons.data()));
EXPECT_THROW(index.reconstruct_n(1, 2, recons.data()), FaissException);
}
namespace {
constexpr size_t kTurboQFullDimension = 16;
constexpr uint64_t kTurboQFullSeed = 0x0123456789abcdefULL;
constexpr auto kTurboQFullQtype = ScalarQuantizer::QT_3bit_tq;
// Total TurboQ bits/component for a QT_*_tq qtype (matches the mapping in
// ScalarQuantizer::set_derived_sizes). Full TurboQ splits these into
// (bits - 1) MSE bits + 1 QJL bit, so the reader-side trained-vector
// centroid count is k = 1 << (bits - 1).
constexpr size_t turboq_full_nb_bits(ScalarQuantizer::QuantizerType qt) {
return qt == ScalarQuantizer::QT_2bit_tq ? 2
: qt == ScalarQuantizer::QT_3bit_tq ? 3
: qt == ScalarQuantizer::QT_4bit_tq ? 4
: qt == ScalarQuantizer::QT_5bit_tq ? 5
: 0;
}
// Derived rather than hardcoded so that changing kTurboQFullQtype (e.g. to
// QT_4bit_tq) automatically resizes the payload; hardcoding kept the two
// constants coupled and a mismatch would fail the read for an unrelated
// reason (trained.size() != k + (k-1) + 3).
//
// `turboq_full_nb_bits` returns 0 for qtypes it does not recognise; guard
// against that sentinel here so a future change to `kTurboQFullQtype` that
// forgets to extend the mapping fails loudly at compile time instead of
// tripping UB via `size_t{1} << (0 - 1)`.
static_assert(
turboq_full_nb_bits(kTurboQFullQtype) != 0,
"kTurboQFullQtype must be a QT_*_tq qtype recognised by "
"turboq_full_nb_bits; extend the mapping when adding new tq qtypes.");
constexpr size_t kTurboQFullCentroids = size_t{1}
<< (turboq_full_nb_bits(kTurboQFullQtype) - 1);
std::vector<uint8_t> make_turboq_full_sq_payload(
uint8_t qjl_type,
size_t d = kTurboQFullDimension) {
float seed[2];
ScalarQuantizer::TurboQuantRefine::pack_seed(kTurboQFullSeed, seed);
std::vector<float> trained(
kTurboQFullCentroids + (kTurboQFullCentroids - 1) + 3, 0.0f);
trained[trained.size() - 3] = seed[0];
trained[trained.size() - 2] = seed[1];
trained[trained.size() - 1] = static_cast<float>(qjl_type);
std::vector<uint8_t> buf;
push_fourcc(buf, "IxSQ");
push_index_header(buf, static_cast<int>(d), /*ntotal=*/0);
push_val<int>(buf, kTurboQFullQtype);
push_val<int>(buf, 0); // rangestat
push_val<float>(buf, 0.0f); // rangestat_arg
push_val<size_t>(buf, d);
push_val<size_t>(buf, 0); // code_size is recomputed on read
push_vector<float>(buf, trained);
push_vector<uint8_t>(buf, {}); // codes
return buf;
}
} // namespace
TEST(ReadIndexDeserialize, SQTurboQuantFullRecoversConfigFromTrained) {
for (const uint8_t qjl_type : {uint8_t{0}, uint8_t{2}}) {
SCOPED_TRACE(testing::Message() << "qjl_type = " << int(qjl_type));
VectorIOReader reader;
reader.data = make_turboq_full_sq_payload(qjl_type);
std::unique_ptr<Index> index;
ASSERT_NO_THROW(index = read_index_up(&reader));
const auto* sq_index = dynamic_cast<IndexScalarQuantizer*>(index.get());
ASSERT_NE(sq_index, nullptr);
EXPECT_EQ(sq_index->sq.d, kTurboQFullDimension);
EXPECT_EQ(sq_index->sq.turboq_refine.qjl_type, qjl_type);
EXPECT_EQ(sq_index->sq.turboq_refine.seed, kTurboQFullSeed);
}
}