use crate::index::ann::product::ProductQuantizer;
use crate::index::hnsw::{DenseHnsw, Hnsw};
use crate::query::AiExecutionContext;
use crate::rowid::RowId;
use crate::Result;
use std::collections::BTreeMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum BackendMetric {
Hamming,
Cosine,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub(crate) enum AnnBackendCheckpoint {
HnswBinarySign { bytes_per_vec: usize, graph: Hnsw },
HnswDense { graph: DenseHnsw },
DiskAnn {
dim: usize,
r: usize,
l: usize,
beam_width: usize,
alpha: u32,
graph: crate::index::ann::diskann::DiskAnnBackend,
},
Product {
dim: usize,
num_subvectors: usize,
bits: u8,
rerank_factor: usize,
quantizer: ProductQuantizer,
codes: BTreeMap<RowId, Vec<u8>>,
},
Ivf {
dim: usize,
nlist: usize,
nprobe: usize,
centroids: Vec<Vec<f32>>,
lists: BTreeMap<usize, Vec<(RowId, Vec<f32>)>>,
seed: u64,
},
}
pub(crate) trait AnnBackend: Send + Sync {
fn metric(&self) -> BackendMetric;
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn insert_validated(
&mut self,
vec: &[f32],
row_id: RowId,
checkpoint: &mut dyn FnMut() -> Result<()>,
) -> Result<()>;
fn finalize(&mut self, checkpoint: &mut dyn FnMut() -> Result<()>) -> Result<()> {
checkpoint()
}
fn search(
&self,
query: &[f32],
k: usize,
ef: usize,
context: Option<&AiExecutionContext>,
) -> Result<Vec<(RowId, f64)>>;
fn entries(&self) -> Vec<(Vec<u8>, RowId)>;
fn freeze(&self) -> AnnBackendCheckpoint;
fn empty_active(&self) -> Box<dyn AnnBackend>;
fn rebuild_from_entries(&self, entries: &[(Vec<u8>, RowId)]) -> Box<dyn AnnBackend>;
fn clone_box(&self) -> Box<dyn AnnBackend>;
}
impl AnnBackend for Hnsw {
fn metric(&self) -> BackendMetric {
BackendMetric::Hamming
}
fn len(&self) -> usize {
Hnsw::len(self)
}
fn insert_validated(
&mut self,
vec: &[f32],
row_id: RowId,
checkpoint: &mut dyn FnMut() -> Result<()>,
) -> Result<()> {
checkpoint()?;
let bits = super::quantize_f32_to_binary_sign(vec, self.bytes_per_vec());
self.insert(bits, row_id);
Ok(())
}
fn search(
&self,
query: &[f32],
k: usize,
ef: usize,
context: Option<&AiExecutionContext>,
) -> Result<Vec<(RowId, f64)>> {
let bits = super::quantize_f32_to_binary_sign(query, self.bytes_per_vec());
Ok(self
.search_with_context(&bits, k, ef, context)?
.into_iter()
.map(|(row_id, distance)| (row_id, f64::from(distance)))
.collect())
}
fn entries(&self) -> Vec<(Vec<u8>, RowId)> {
Hnsw::entries(self).collect()
}
fn freeze(&self) -> AnnBackendCheckpoint {
AnnBackendCheckpoint::HnswBinarySign {
bytes_per_vec: self.bytes_per_vec(),
graph: self.clone(),
}
}
fn empty_active(&self) -> Box<dyn AnnBackend> {
let (m, ef_construction) = self.options();
Box::new(Hnsw::new(self.bytes_per_vec(), m, ef_construction))
}
fn rebuild_from_entries(&self, entries: &[(Vec<u8>, RowId)]) -> Box<dyn AnnBackend> {
let (m, ef_construction) = self.options();
let mut graph = Hnsw::new(self.bytes_per_vec(), m, ef_construction);
for (bits, row_id) in entries {
graph.insert(bits.clone(), *row_id);
}
Box::new(graph)
}
fn clone_box(&self) -> Box<dyn AnnBackend> {
Box::new(self.clone())
}
}
impl AnnBackend for DenseHnsw {
fn metric(&self) -> BackendMetric {
BackendMetric::Cosine
}
fn len(&self) -> usize {
DenseHnsw::len(self)
}
fn insert_validated(
&mut self,
vec: &[f32],
row_id: RowId,
checkpoint: &mut dyn FnMut() -> Result<()>,
) -> Result<()> {
let mut on_checkpoint = || -> Result<()> { checkpoint() };
self.insert_with_checkpoint(vec.to_vec(), row_id, &mut on_checkpoint)
}
fn search(
&self,
query: &[f32],
k: usize,
ef: usize,
context: Option<&AiExecutionContext>,
) -> Result<Vec<(RowId, f64)>> {
Ok(self
.search_with_context(query, k, ef, context)?
.into_iter()
.map(|(row_id, distance)| (row_id, f64::from(distance)))
.collect())
}
fn entries(&self) -> Vec<(Vec<u8>, RowId)> {
DenseHnsw::entries(self)
.map(|(vec, row_id)| (vec_to_le_bytes(&vec), row_id))
.collect()
}
fn freeze(&self) -> AnnBackendCheckpoint {
AnnBackendCheckpoint::HnswDense {
graph: self.clone(),
}
}
fn empty_active(&self) -> Box<dyn AnnBackend> {
let (m, ef_construction) = self.options();
Box::new(DenseHnsw::new(self.dim(), m, ef_construction))
}
fn rebuild_from_entries(&self, entries: &[(Vec<u8>, RowId)]) -> Box<dyn AnnBackend> {
let (m, ef_construction) = self.options();
let mut graph = DenseHnsw::new(self.dim(), m, ef_construction);
for (bytes, row_id) in entries {
let vec = le_bytes_to_vec(bytes, self.dim());
graph.insert(vec, *row_id);
}
Box::new(graph)
}
fn clone_box(&self) -> Box<dyn AnnBackend> {
Box::new(self.clone())
}
}
fn vec_to_le_bytes(vec: &[f32]) -> Vec<u8> {
let mut bytes = Vec::with_capacity(vec.len() * 4);
for value in vec {
bytes.extend_from_slice(&value.to_le_bytes());
}
bytes
}
fn le_bytes_to_vec(bytes: &[u8], dim: usize) -> Vec<f32> {
debug_assert_eq!(bytes.len(), dim * 4, "dense checkpoint entry size mismatch");
(0..dim)
.map(|i| {
let offset = i * 4;
f32::from_le_bytes([
bytes[offset],
bytes[offset + 1],
bytes[offset + 2],
bytes[offset + 3],
])
})
.collect()
}