use crate::data_model::GraphDataType;
use diskann::{ANNError, ANNResult};
use diskann_providers::storage::{StorageReadProvider, StorageWriteProvider};
use diskann_providers::{
index::diskann_async::train_pq,
model::{
graph::provider::async_::{common::NoStore, inmem::WithBits},
FixedChunkPQTable, IndexConfiguration, MAX_PQ_TRAINING_SET_SIZE,
},
storage::{PQStorage, SQStorage},
utils::{BridgeErr, PQPathNames},
};
use diskann_quantization::scalar::train::ScalarQuantizationParameters;
use diskann_utils::views::MatrixView;
use tracing::info;
use crate::QuantizationType;
#[derive(Clone)]
pub enum BuildQuantizer {
NoQuant(NoStore),
Scalar1Bit(WithBits<1>),
PQ(FixedChunkPQTable),
}
impl BuildQuantizer {
pub fn train<Data, StorageProvider>(
build_quantization_type: &QuantizationType,
index_path_prefix: &str,
index_configuration: &IndexConfiguration,
pq_storage: &PQStorage,
storage_provider: &StorageProvider,
) -> ANNResult<Self>
where
Data: GraphDataType<VectorIdType = u32>,
StorageProvider: StorageReadProvider + StorageWriteProvider,
{
let num_points = index_configuration.max_points;
let p_val = MAX_PQ_TRAINING_SET_SIZE / (num_points as f64);
match *build_quantization_type {
QuantizationType::FP => Ok(Self::NoQuant(NoStore)),
QuantizationType::PQ { num_chunks } => {
let table = {
let seed = index_configuration.random_seed;
let mut rnd =
diskann_providers::utils::create_rnd_provider_from_optional_seed(seed)
.create_rnd();
let (train_data, train_size, train_dim) = pq_storage
.get_random_train_data_slice::<Data::VectorDataType, _>(
p_val,
storage_provider,
&mut rnd,
)?;
train_pq(
MatrixView::try_from(&train_data, train_size, train_dim).bridge_err()?,
num_chunks,
&mut rnd,
index_configuration.num_threads,
)?
};
let pq_paths = PQPathNames::new(index_path_prefix);
let pq_build_storage =
PQStorage::new(&pq_paths.pivots, &pq_paths.compressed_data, None);
pq_build_storage.write_pivot_data(
table.get_pq_table(),
table.get_centroids(),
table.get_chunk_offsets(),
table.get_num_centers(),
table.get_dim(),
storage_provider,
)?;
Ok(Self::PQ(table))
}
QuantizationType::SQ {
nbits,
standard_deviation,
} => {
if nbits != 1 {
return Err(ANNError::log_index_config_error(
"build_quantization_type".to_string(),
"SQ quantization is only supported for 1 bit".to_string(),
));
}
let rng = diskann_providers::utils::create_rnd_provider_from_optional_seed(
index_configuration.random_seed,
);
let (train_data_vector, train_size, train_dim) = pq_storage
.get_random_train_data_slice::<Data::VectorDataType, _>(
p_val,
storage_provider,
&mut rng.create_rnd(),
)?;
let quantizer_params = if let Some(std_dev) = standard_deviation {
ScalarQuantizationParameters::new(std_dev)
} else {
ScalarQuantizationParameters::default()
};
let quantizer = quantizer_params.train(
MatrixView::try_from(&train_data_vector, train_size, train_dim).bridge_err()?,
);
info!("Now quantizer is trained and saving to file");
let sq_storage = SQStorage::new(index_path_prefix);
sq_storage.save_quantizer(&quantizer, storage_provider)?;
Ok(Self::Scalar1Bit(WithBits::<1>::new(quantizer)))
}
}
}
pub fn load<StorageProvider>(
build_quantization_type: &QuantizationType,
index_path_prefix: &str,
storage_provider: &StorageProvider,
) -> ANNResult<Self>
where
StorageProvider: StorageReadProvider,
{
match build_quantization_type {
QuantizationType::FP => Ok(Self::NoQuant(NoStore)),
QuantizationType::PQ { num_chunks } => {
let pq_pivots_paths = PQPathNames::new(index_path_prefix);
let pq_build_storage = PQStorage::new(
&pq_pivots_paths.pivots,
&pq_pivots_paths.compressed_data,
None,
);
let table = pq_build_storage.load_pq_pivots_bin::<StorageProvider>(
&pq_pivots_paths.pivots,
*num_chunks,
storage_provider,
)?;
Ok(Self::PQ(table))
}
QuantizationType::SQ { .. } => {
let sq_storage = SQStorage::new(index_path_prefix);
let sq_quantizer = sq_storage.load_quantizer(storage_provider)?;
Ok(Self::Scalar1Bit(WithBits::<1>::new(sq_quantizer)))
}
}
}
}