1use std::mem::size_of;
12
13use roaring::RoaringBitmap;
14use rustc_hash::FxHashMap;
15#[path = "vector_index/build.rs"]
16mod build;
17#[path = "vector_index/config.rs"]
18mod config;
19#[path = "vector_index/hnsw.rs"]
20mod hnsw;
21#[path = "vector_index/ivf.rs"]
22mod ivf;
23#[path = "vector_index/ivf_adapter.rs"]
24mod ivf_adapter;
25#[path = "vector_index/maintenance.rs"]
26mod maintenance;
27#[path = "vector_index/memory.rs"]
28mod memory;
29#[path = "vector_index/rebuild.rs"]
30mod rebuild;
31#[path = "vector_index/search_hit.rs"]
32mod search_hit;
33#[path = "vector_index/turbo_quant.rs"]
34mod turbo_quant;
35#[path = "vector_index/turbo_quant_adapter.rs"]
36mod turbo_quant_adapter;
37
38use selene_core::{DbString, HnswIndexConfig, IvfIndexConfig, VectorMetric, VectorValue};
39use serde::{Deserialize, Serialize};
40
41use crate::error::{GraphError, GraphResult};
42use crate::graph::VectorIndexEntry;
43pub(crate) use build::{
44 build_vector_index_lenient_with_configs, build_vector_index_with_configs,
45 maintain_vector_indexes_strict, rebuild_vector_indexes, rebuild_vector_indexes_strict,
46};
47pub(crate) use config::MAX_IVF_TARGET_CENTROIDS;
48use config::{hnsw_config_for_kind, ivf_config_for_kind};
49pub(crate) use hnsw::HnswSearchScratch;
50use hnsw::HnswVectorIndex;
51use ivf::IvfVectorIndex;
52pub use maintenance::VectorIndexValueError;
53pub(crate) use maintenance::{apply_node_create, apply_node_delete, apply_node_update};
54pub use memory::{
55 IVF_REBUILD_MIN_PENDING_RETRAIN_ENTRIES, IVF_REBUILD_PENDING_RETRAIN_BASIS_POINTS,
56 VectorIndexMemoryUsage,
57};
58pub use rebuild::{
59 VectorIndexMaintenancePolicy, VectorIndexRebuildEntry, VectorIndexRebuildReport,
60};
61pub(crate) use search_hit::VectorIndexSearchHit;
62use search_hit::{hnsw_hits, ivf_hits};
63use turbo_quant::TurboQuantVectorIndex;
64
65type VectorIndexMap = FxHashMap<(DbString, DbString), VectorIndexEntry>;
66
67#[derive(
69 Clone,
70 Copy,
71 Debug,
72 Deserialize,
73 Eq,
74 Hash,
75 PartialEq,
76 rkyv::Archive,
77 rkyv::Deserialize,
78 rkyv::Serialize,
79 Serialize,
80)]
81pub enum VectorIndexKind {
82 Flat,
84 HnswSquaredEuclidean,
86 HnswCosine,
88 HnswNegativeInnerProduct,
90 IvfSquaredEuclidean,
92 IvfCosine,
94 IvfNegativeInnerProduct,
96 TurboQuantCosine,
98}
99
100#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
102pub struct VectorIndexConfig {
103 pub hnsw: Option<HnswIndexConfig>,
105 pub ivf: Option<IvfIndexConfig>,
107}
108
109impl VectorIndexConfig {
110 #[must_use]
112 pub const fn new(hnsw: Option<HnswIndexConfig>, ivf: Option<IvfIndexConfig>) -> Self {
113 Self { hnsw, ivf }
114 }
115
116 #[must_use]
118 pub const fn hnsw(config: HnswIndexConfig) -> Self {
119 Self {
120 hnsw: Some(config),
121 ivf: None,
122 }
123 }
124
125 #[must_use]
127 pub const fn ivf(config: IvfIndexConfig) -> Self {
128 Self {
129 hnsw: None,
130 ivf: Some(config),
131 }
132 }
133}
134
135impl VectorIndexKind {
136 #[must_use]
138 pub const fn hnsw_metric(self) -> Option<VectorMetric> {
139 match self {
140 Self::Flat => None,
141 Self::HnswSquaredEuclidean => Some(VectorMetric::SquaredEuclidean),
142 Self::HnswCosine => Some(VectorMetric::Cosine),
143 Self::HnswNegativeInnerProduct => Some(VectorMetric::NegativeInnerProduct),
144 Self::IvfSquaredEuclidean
145 | Self::IvfCosine
146 | Self::IvfNegativeInnerProduct
147 | Self::TurboQuantCosine => None,
148 }
149 }
150
151 #[must_use]
153 pub const fn ivf_metric(self) -> Option<VectorMetric> {
154 match self {
155 Self::Flat
156 | Self::HnswSquaredEuclidean
157 | Self::HnswCosine
158 | Self::HnswNegativeInnerProduct
159 | Self::TurboQuantCosine => None,
160 Self::IvfSquaredEuclidean => Some(VectorMetric::SquaredEuclidean),
161 Self::IvfCosine => Some(VectorMetric::Cosine),
162 Self::IvfNegativeInnerProduct => Some(VectorMetric::NegativeInnerProduct),
163 }
164 }
165
166 #[must_use]
168 pub const fn ann_metric(self) -> Option<VectorMetric> {
169 match self {
170 Self::Flat => None,
171 Self::HnswSquaredEuclidean | Self::IvfSquaredEuclidean => {
172 Some(VectorMetric::SquaredEuclidean)
173 }
174 Self::HnswCosine | Self::IvfCosine => Some(VectorMetric::Cosine),
175 Self::HnswNegativeInnerProduct | Self::IvfNegativeInnerProduct => {
176 Some(VectorMetric::NegativeInnerProduct)
177 }
178 Self::TurboQuantCosine => Some(VectorMetric::Cosine),
179 }
180 }
181}
182
183#[derive(Clone, Debug)]
185pub struct VectorIndex {
186 kind: VectorIndexKind,
187 dimension: u32,
188 hnsw_config: Option<HnswIndexConfig>,
189 ivf_config: Option<IvfIndexConfig>,
190 rows: RoaringBitmap,
191 hnsw: Option<HnswVectorIndex>,
192 ivf: Option<IvfVectorIndex>,
193 turbo_quant: Option<TurboQuantVectorIndex>,
194}
195
196impl VectorIndex {
197 pub fn new(kind: VectorIndexKind, dimension: u32) -> GraphResult<Self> {
204 Self::new_with_configs(kind, dimension, None, None)
205 }
206
207 pub fn new_with_hnsw_config(
215 kind: VectorIndexKind,
216 dimension: u32,
217 hnsw_config: Option<HnswIndexConfig>,
218 ) -> GraphResult<Self> {
219 Self::new_with_configs(kind, dimension, hnsw_config, None)
220 }
221
222 pub fn new_with_configs(
232 kind: VectorIndexKind,
233 dimension: u32,
234 hnsw_config: Option<HnswIndexConfig>,
235 ivf_config: Option<IvfIndexConfig>,
236 ) -> GraphResult<Self> {
237 ensure_dimension(dimension)?;
238 let hnsw_config = hnsw_config_for_kind(kind, hnsw_config)?;
239 let ivf_config = ivf_config_for_kind(kind, ivf_config)?;
240 let hnsw = kind.hnsw_metric().map(|metric| {
241 HnswVectorIndex::with_config(metric, hnsw_config.expect("HNSW kind stores config"))
242 });
243 let ivf = kind
244 .ivf_metric()
245 .map(|metric| IvfVectorIndex::with_config(metric, ivf_config));
246 let turbo_quant = match kind {
247 VectorIndexKind::TurboQuantCosine => Some(TurboQuantVectorIndex::new(dimension)?),
248 _ => None,
249 };
250 Ok(Self {
251 kind,
252 dimension,
253 hnsw_config,
254 ivf_config,
255 rows: RoaringBitmap::new(),
256 hnsw,
257 ivf,
258 turbo_quant,
259 })
260 }
261
262 #[must_use]
264 pub const fn kind(&self) -> VectorIndexKind {
265 self.kind
266 }
267
268 #[must_use]
270 pub const fn dimension(&self) -> u32 {
271 self.dimension
272 }
273
274 #[must_use]
276 pub const fn hnsw_config(&self) -> Option<HnswIndexConfig> {
277 self.hnsw_config
278 }
279
280 #[must_use]
282 pub const fn ivf_config(&self) -> Option<IvfIndexConfig> {
283 self.ivf_config
284 }
285
286 #[must_use]
288 pub fn cardinality(&self) -> u64 {
289 self.rows.len()
290 }
291
292 #[must_use]
294 pub const fn rows(&self) -> &RoaringBitmap {
295 &self.rows
296 }
297
298 #[must_use]
300 pub const fn is_hnsw(&self) -> bool {
301 self.kind.hnsw_metric().is_some()
302 }
303
304 #[must_use]
306 pub const fn is_ivf(&self) -> bool {
307 self.kind.ivf_metric().is_some()
308 }
309
310 #[must_use]
312 pub const fn is_turbo_quant(&self) -> bool {
313 matches!(self.kind, VectorIndexKind::TurboQuantCosine)
314 }
315
316 #[must_use]
318 pub const fn hnsw_metric(&self) -> Option<VectorMetric> {
319 self.kind.hnsw_metric()
320 }
321
322 #[must_use]
324 pub const fn ann_metric(&self) -> Option<VectorMetric> {
325 self.kind.ann_metric()
326 }
327
328 #[must_use]
330 pub fn memory_usage(&self) -> VectorIndexMemoryUsage {
331 let row_bitmap_bytes = roaring_heap_bytes(&self.rows);
332 let row_bitmap_serialized_bytes = self.rows.serialized_size();
333 let hnsw = self
334 .hnsw
335 .as_ref()
336 .map(HnswVectorIndex::memory_usage)
337 .unwrap_or_default();
338 let ivf = self
339 .ivf
340 .as_ref()
341 .map(IvfVectorIndex::memory_usage)
342 .unwrap_or_default();
343 let turbo_quant = self
344 .turbo_quant
345 .as_ref()
346 .map(TurboQuantVectorIndex::memory_usage)
347 .unwrap_or_default();
348 let estimated_index_bytes = size_of::<Self>()
349 .saturating_add(row_bitmap_bytes)
350 .saturating_add(hnsw.estimated_heap_bytes)
351 .saturating_add(ivf.estimated_heap_bytes)
352 .saturating_add(turbo_quant.estimated_heap_bytes);
353 VectorIndexMemoryUsage {
354 indexed_rows: self.cardinality(),
355 row_bitmap_bytes,
356 row_bitmap_serialized_bytes,
357 hnsw_index_bytes: hnsw.estimated_heap_bytes,
358 hnsw_referenced_vector_bytes: hnsw.referenced_vector_bytes,
359 hnsw_entries: hnsw.entries,
360 hnsw_live_entries: hnsw.live_entries,
361 hnsw_deleted_entries: hnsw.deleted_entries,
362 hnsw_link_count: hnsw.link_count,
363 hnsw_level_zero_link_count: hnsw.level_zero_link_count,
364 hnsw_upper_layer_link_count: hnsw.upper_layer_link_count,
365 hnsw_max_layer_count: hnsw.max_layer_count,
366 hnsw_max_links_per_layer: hnsw.max_links_per_layer,
367 hnsw_average_links_per_entry_basis_points: hnsw.average_links_per_entry_basis_points,
368 ivf_index_bytes: ivf.estimated_heap_bytes,
369 ivf_referenced_vector_bytes: ivf.referenced_vector_bytes,
370 ivf_entries: ivf.entries,
371 ivf_live_entries: ivf.live_entries,
372 ivf_deleted_entries: ivf.deleted_entries,
373 ivf_centroids: ivf.centroids,
374 ivf_list_count: ivf.list_count,
375 ivf_non_empty_list_count: ivf.non_empty_list_count,
376 ivf_max_list_len: ivf.max_list_len,
377 ivf_average_list_len_basis_points: ivf.average_list_len_basis_points,
378 ivf_assigned_entries: ivf.assigned_entries,
379 ivf_pending_retrain_entries: ivf.pending_retrain_entries,
380 turbo_quant_index_bytes: turbo_quant.estimated_heap_bytes,
381 turbo_quant_referenced_vector_bytes: turbo_quant.referenced_vector_bytes,
382 turbo_quant_entries: turbo_quant.entries,
383 turbo_quant_live_entries: turbo_quant.live_entries,
384 turbo_quant_deleted_entries: turbo_quant.deleted_entries,
385 turbo_quant_code_bytes: turbo_quant.code_bytes,
386 turbo_quant_codebook_bytes: turbo_quant.codebook_bytes,
387 turbo_quant_calibration_bytes: turbo_quant.calibration_bytes,
388 estimated_index_bytes,
389 estimated_reachable_bytes: estimated_index_bytes
390 .saturating_add(hnsw.referenced_vector_bytes)
391 .saturating_add(ivf.referenced_vector_bytes)
392 .saturating_add(turbo_quant.referenced_vector_bytes),
393 }
394 }
395
396 pub(crate) fn insert_value(&mut self, row: u32, vector: &VectorValue) -> GraphResult<()> {
397 let mut scratch = HnswSearchScratch::default();
398 self.insert_value_with_scratch(row, vector, &mut scratch)
399 }
400
401 pub(crate) fn insert_value_with_scratch(
402 &mut self,
403 row: u32,
404 vector: &VectorValue,
405 scratch: &mut HnswSearchScratch,
406 ) -> GraphResult<()> {
407 self.rows.insert(row);
408 if let Some(hnsw) = &mut self.hnsw {
409 hnsw.insert_with_scratch(row, vector.clone(), scratch)?;
410 }
411 if let Some(ivf) = &mut self.ivf {
412 ivf.insert(row, vector.clone())?;
413 }
414 if let Some(turbo_quant) = &mut self.turbo_quant {
415 turbo_quant.insert(row, vector)?;
416 }
417 Ok(())
418 }
419
420 pub(crate) fn finish_bulk_load(&mut self) -> GraphResult<()> {
421 if let Some(hnsw) = &mut self.hnsw {
422 hnsw.finish_bulk_load();
423 }
424 if let Some(ivf) = &mut self.ivf {
425 ivf.finish_bulk_load()?;
426 }
427 if let Some(turbo_quant) = &mut self.turbo_quant {
428 turbo_quant.finish_bulk_load()?;
429 }
430 Ok(())
431 }
432
433 pub(crate) fn remove_row(&mut self, row: u32) {
434 self.rows.remove(row);
435 if let Some(hnsw) = &mut self.hnsw {
436 hnsw.remove(row);
437 }
438 if let Some(ivf) = &mut self.ivf {
439 ivf.remove(row);
440 }
441 if let Some(turbo_quant) = &mut self.turbo_quant {
442 turbo_quant.remove(row);
443 }
444 }
445
446 pub(crate) fn rows_eq(&self, reference: &Self) -> bool {
447 self.kind == reference.kind
448 && self.dimension == reference.dimension
449 && self.hnsw_config == reference.hnsw_config
450 && self.ivf_config == reference.ivf_config
451 && self.rows == reference.rows
452 }
453
454 pub(crate) fn ann_search(
455 &self,
456 query: &VectorValue,
457 k: usize,
458 search_width: usize,
459 ) -> Option<selene_core::CoreResult<Vec<VectorIndexSearchHit>>> {
460 if let Some(hnsw) = &self.hnsw {
461 return Some(hnsw.search(query, k, search_width).map(hnsw_hits));
462 }
463 if let Some(ivf) = &self.ivf {
464 return Some(ivf.search(query, k, search_width).map(ivf_hits));
465 }
466 None
467 }
468
469 pub(crate) fn ann_search_with_scratch(
470 &self,
471 query: &VectorValue,
472 k: usize,
473 search_width: usize,
474 scratch: &mut HnswSearchScratch,
475 ) -> Option<selene_core::CoreResult<Vec<VectorIndexSearchHit>>> {
476 if let Some(hnsw) = &self.hnsw {
477 return Some(
478 hnsw.search_with_scratch(query, k, search_width, scratch)
479 .map(hnsw_hits),
480 );
481 }
482 if let Some(ivf) = &self.ivf {
483 return Some(ivf.search(query, k, search_width).map(ivf_hits));
484 }
485 None
486 }
487
488 pub(crate) fn ann_search_in_rows_with_scratch(
489 &self,
490 query: &VectorValue,
491 k: usize,
492 search_width: usize,
493 allowed_rows: &RoaringBitmap,
494 scratch: &mut HnswSearchScratch,
495 ) -> Option<selene_core::CoreResult<Vec<VectorIndexSearchHit>>> {
496 if let Some(hnsw) = &self.hnsw {
497 return Some(
498 hnsw.search_in_rows_with_scratch(query, k, search_width, allowed_rows, scratch)
499 .map(hnsw_hits),
500 );
501 }
502 if let Some(ivf) = &self.ivf {
503 return Some(
504 ivf.search_in_rows(query, k, search_width, allowed_rows)
505 .map(ivf_hits),
506 );
507 }
508 None
509 }
510}
511
512fn roaring_heap_bytes(rows: &RoaringBitmap) -> usize {
513 let statistics = rows.statistics();
514 u64_to_usize_saturating(
515 statistics
516 .n_bytes_array_containers
517 .saturating_add(statistics.n_bytes_run_containers)
518 .saturating_add(statistics.n_bytes_bitset_containers),
519 )
520}
521
522fn ensure_dimension(dimension: u32) -> GraphResult<()> {
523 if dimension == 0 || dimension as usize > selene_core::MAX_VECTOR_DIMENSION {
524 Err(GraphError::VectorIndexInvalidDimension { dimension })
525 } else {
526 Ok(())
527 }
528}
529
530fn u64_to_usize_saturating(value: u64) -> usize {
531 usize::try_from(value).unwrap_or(usize::MAX)
532}
533
534#[cfg(test)]
535#[path = "vector_index/config_tests.rs"]
536mod config_tests;
537
538#[cfg(test)]
539#[path = "vector_index/tests.rs"]
540mod tests;