hermes_core/structures/vector/index/
rabitq.rs1use std::io;
7
8use serde::{Deserialize, Serialize};
9
10use crate::structures::vector::ivf::QuantizedCode;
11use crate::structures::vector::quantization::{
12 QuantizedQuery, QuantizedVector, RaBitQCodebook, RaBitQConfig,
13};
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct RaBitQIndex {
21 pub codebook: RaBitQCodebook,
23 pub centroid: Vec<f32>,
25 pub doc_ids: Vec<u32>,
27 pub ordinals: Vec<u16>,
29 pub vectors: Vec<QuantizedVector>,
31}
32
33impl RaBitQIndex {
34 pub(crate) fn validate(&self) -> Result<(), String> {
35 self.codebook.validate()?;
36 let dim = self.codebook.config.dim;
37 if self.centroid.len() != dim || self.centroid.iter().any(|value| !value.is_finite()) {
38 return Err(format!("RaBitQ centroid is invalid for dimension {dim}"));
39 }
40 if self.doc_ids.len() != self.vectors.len() || self.ordinals.len() != self.vectors.len() {
41 return Err(format!(
42 "RaBitQ column lengths differ: docs={}, ordinals={}, vectors={}",
43 self.doc_ids.len(),
44 self.ordinals.len(),
45 self.vectors.len()
46 ));
47 }
48 for vector in &self.vectors {
49 self.codebook.validate_vector(vector)?;
50 }
51 Ok(())
52 }
53
54 pub fn new(config: RaBitQConfig) -> Self {
56 let dim = config.dim;
57 let codebook = RaBitQCodebook::new(config);
58
59 Self {
60 codebook,
61 centroid: vec![0.0; dim],
62 doc_ids: Vec::new(),
63 ordinals: Vec::new(),
64 vectors: Vec::new(),
65 }
66 }
67
68 pub fn build_with_ids(
70 config: RaBitQConfig,
71 vectors: &[(u32, u16, Vec<f32>)], ) -> Self {
73 let n = vectors.len();
74 let dim = config.dim;
75
76 assert!(n > 0, "Cannot build index from empty vector set");
77 assert!(vectors[0].2.len() == dim, "Vector dimension mismatch");
78
79 let mut index = Self::new(config);
80
81 index.centroid = vec![0.0; dim];
83 for (_, _, v) in vectors {
84 for (i, &val) in v.iter().enumerate() {
85 index.centroid[i] += val;
86 }
87 }
88 for c in &mut index.centroid {
89 *c /= n as f32;
90 }
91
92 index.doc_ids = vectors.iter().map(|(doc_id, _, _)| *doc_id).collect();
94 index.ordinals = vectors.iter().map(|(_, ordinal, _)| *ordinal).collect();
95 index.vectors = vectors
96 .iter()
97 .map(|(_, _, v)| index.codebook.encode(v, Some(&index.centroid)))
98 .collect();
99
100 index
101 }
102
103 pub fn build(config: RaBitQConfig, vectors: &[Vec<f32>]) -> Self {
105 let with_ids: Vec<(u32, u16, Vec<f32>)> = vectors
106 .iter()
107 .enumerate()
108 .map(|(i, v)| (i as u32, 0u16, v.clone()))
109 .collect();
110 Self::build_with_ids(config, &with_ids)
111 }
112
113 pub fn add_vector(&mut self, doc_id: u32, ordinal: u16, vector: &[f32]) {
115 self.doc_ids.push(doc_id);
116 self.ordinals.push(ordinal);
117 self.vectors
118 .push(self.codebook.encode(vector, Some(&self.centroid)));
119 }
120
121 pub fn prepare_query(&self, query: &[f32]) -> QuantizedQuery {
123 self.codebook.prepare_query(query, Some(&self.centroid))
124 }
125
126 pub fn estimate_distance(&self, query: &QuantizedQuery, vec_idx: usize) -> f32 {
128 self.codebook
129 .estimate_distance(query, &self.vectors[vec_idx])
130 }
131
132 pub fn search(&self, query: &[f32], k: usize) -> Vec<(u32, u16, f32)> {
134 let prepared = self.prepare_query(query);
135
136 let mut candidates = super::BoundedDistanceCollector::new(k);
137 for idx in 0..self.vectors.len() {
138 candidates.insert(
139 self.doc_ids[idx],
140 self.ordinals[idx],
141 self.estimate_distance(&prepared, idx),
142 );
143 }
144 candidates.into_sorted_results()
145 }
146
147 pub fn len(&self) -> usize {
149 self.vectors.len()
150 }
151
152 pub fn is_empty(&self) -> bool {
153 self.vectors.is_empty()
154 }
155
156 pub fn size_bytes(&self) -> usize {
158 use std::mem::size_of;
159
160 let vectors_size: usize = self.vectors.iter().map(|v| v.size_bytes()).sum();
161 let centroid_size = self.centroid.len() * size_of::<f32>();
162 let doc_ids_size = self.doc_ids.len() * size_of::<u32>();
163 let ordinals_size = self.ordinals.len() * size_of::<u16>();
164 let codebook_size = self.codebook.size_bytes();
165 vectors_size + centroid_size + doc_ids_size + ordinals_size + codebook_size
166 }
167
168 pub fn estimated_memory_bytes(&self) -> usize {
170 self.size_bytes()
171 }
172
173 pub fn compression_ratio(&self) -> f32 {
175 if self.vectors.is_empty() {
176 return 1.0;
177 }
178
179 let dim = self.codebook.config.dim;
180 let raw_size = self.vectors.len() * dim * 4;
181 let compressed_size: usize = self.vectors.iter().map(|v| v.size_bytes()).sum();
182
183 raw_size as f32 / compressed_size as f32
184 }
185
186 pub fn to_bytes(&self) -> io::Result<Vec<u8>> {
188 serde_json::to_vec(self).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
189 }
190
191 pub fn from_bytes(data: &[u8]) -> io::Result<Self> {
193 let index: Self = serde_json::from_slice(data)
194 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
195 index
196 .validate()
197 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
198 Ok(index)
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use rand::prelude::*;
206
207 #[test]
208 fn test_rabitq_basic() {
209 let dim = 128;
210 let n = 100;
211
212 let mut rng = rand::rngs::StdRng::seed_from_u64(12345);
213 let vectors: Vec<Vec<f32>> = (0..n)
214 .map(|_| (0..dim).map(|_| rng.random::<f32>() - 0.5).collect())
215 .collect();
216
217 let config = RaBitQConfig::new(dim);
218 let index = RaBitQIndex::build(config, &vectors);
219
220 assert_eq!(index.len(), n);
221 println!("Compression ratio: {:.1}x", index.compression_ratio());
222 }
223
224 #[test]
225 fn test_rabitq_search() {
226 let dim = 64;
227 let n = 1000;
228 let k = 10;
229
230 let mut rng = rand::rngs::StdRng::seed_from_u64(42);
231 let vectors: Vec<Vec<f32>> = (0..n)
232 .map(|_| (0..dim).map(|_| rng.random::<f32>() - 0.5).collect())
233 .collect();
234
235 let config = RaBitQConfig::new(dim);
236 let index = RaBitQIndex::build(config, &vectors);
237
238 let query: Vec<f32> = (0..dim).map(|_| rng.random::<f32>() - 0.5).collect();
239 let results = index.search(&query, k);
240
241 assert_eq!(results.len(), k);
242
243 for i in 1..results.len() {
245 assert!(results[i].2 >= results[i - 1].2);
246 }
247 }
248}