use crate::collection::types::Collection;
use crate::error::Result;
use crate::quantization::{PQVector, ProductQuantizer, RaBitQIndex, StorageMode};
use crate::storage::VectorStorage;
use std::collections::HashMap;
use std::sync::Arc;
impl Collection {
pub(crate) fn restore_persisted_quantizers(&self) -> Result<()> {
let mode = self.config.read().storage_mode;
match mode {
StorageMode::ProductQuantization => self.restore_persisted_pq(),
StorageMode::RaBitQ => self.restore_persisted_rabitq(),
StorageMode::Full | StorageMode::SQ8 | StorageMode::Binary => Ok(()),
}
}
fn restore_persisted_pq(&self) -> Result<()> {
if self.pq_quantizer.read().is_some() {
return Ok(());
}
let Some(mut pq) = ProductQuantizer::load_codebook(&self.path)? else {
return Ok(());
};
let dimension = self.config.read().dimension;
if pq.codebook.dimension != dimension {
tracing::warn!(
codebook_dim = pq.codebook.dimension,
collection_dim = dimension,
"codebook.pq dimension does not match the collection; quantizer not installed"
);
return Ok(());
}
if pq.rotation.is_none() {
pq.rotation = ProductQuantizer::load_rotation(&self.path)?;
}
let cache = self.encode_pq_cache(&pq);
tracing::debug!(
entries = cache.len(),
"restored PQ quantizer from codebook.pq; cache rebuilt on open"
);
*self.pq_cache.write() = cache;
*self.pq_quantizer.write() = Some(pq);
Ok(())
}
fn encode_pq_cache(&self, pq: &ProductQuantizer) -> HashMap<u64, PQVector> {
let storage = self.vector_storage.read();
let ids = storage.ids();
let mut cache = HashMap::with_capacity(ids.len());
for id in ids {
let Ok(Some(vector)) = storage.retrieve(id) else {
continue;
};
match pq.quantize(&vector) {
Ok(code) => {
cache.insert(id, code);
}
Err(err) => {
tracing::warn!(id, %err, "PQ re-encode failed on open; keeping HNSW-only scoring");
}
}
}
cache
}
fn restore_persisted_rabitq(&self) -> Result<()> {
preinstall_persisted_rabitq(&self.path, self.config.read().dimension, &self.index)
}
pub(crate) fn install_rabitq_quantizer(&self, rabitq: Arc<RaBitQIndex>) -> Result<bool> {
self.index.install_trained_rabitq(rabitq)
}
#[cfg(test)]
pub(crate) fn is_rabitq_quantizer_trained(&self) -> bool {
self.index.is_rabitq_quantizer_trained()
}
#[cfg(test)]
pub(crate) fn pq_cache_len(&self) -> usize {
self.pq_cache.read().len()
}
}
#[cfg(feature = "persistence")]
pub(super) fn preinstall_persisted_rabitq(
path: &std::path::Path,
dimension: usize,
index: &crate::index::HnswIndex,
) -> Result<()> {
if index.is_rabitq_quantizer_trained() {
return Ok(());
}
let Some(rabitq) = RaBitQIndex::load(path)? else {
return Ok(());
};
if rabitq.dimension != dimension {
tracing::warn!(
rabitq_dim = rabitq.dimension,
"rabitq.idx dimension does not match the collection; quantizer not installed"
);
return Ok(());
}
let installed = index.install_trained_rabitq(Arc::new(rabitq))?;
if installed {
tracing::debug!("restored RaBitQ quantizer from rabitq.idx; vectors re-encoded");
} else {
tracing::warn!(
"rabitq.idx present but the HNSW backend is not RaBitQ; quantizer not installed"
);
}
Ok(())
}