nodedb_vector/collection/
quantize.rs1use nodedb_mem::ScopedMemory;
9
10use crate::hnsw::{HnswIndex, HnswParams};
11use crate::index_config::{IndexConfig, IndexType};
12use crate::quantize::pq::PqCodec;
13use crate::quantize::sq8::Sq8Codec;
14
15use super::lifecycle::VectorCollection;
16use super::segment::DEFAULT_SEAL_THRESHOLD;
17
18impl VectorCollection {
19 pub fn with_pq_config(dim: usize, hnsw: HnswParams, pq_m: usize) -> Self {
24 let config = IndexConfig {
25 hnsw,
26 index_type: IndexType::HnswPq,
27 pq_m,
28 ..IndexConfig::default()
29 };
30 Self::with_index_config(dim, config)
31 }
32
33 pub fn with_seal_threshold_and_pq_config(
36 dim: usize,
37 hnsw: HnswParams,
38 pq_m: usize,
39 seal_threshold: usize,
40 ) -> Self {
41 let config = IndexConfig {
42 hnsw,
43 index_type: IndexType::HnswPq,
44 pq_m,
45 ..IndexConfig::default()
46 };
47 Self::with_seal_threshold_and_config(dim, config, seal_threshold)
48 }
49
50 pub fn build_sq8_for_index(index: &HnswIndex) -> Option<(Sq8Codec, Vec<u8>)> {
55 if index.live_count() < 1000 {
56 return None;
57 }
58 let dim = index.dim();
59 let n = index.len();
60
61 let mut refs: Vec<&[f32]> = Vec::with_capacity(n);
62 for i in 0..n {
63 if !index.is_deleted(i as u32)
64 && let Some(v) = index.get_vector(i as u32)
65 {
66 refs.push(v);
67 }
68 }
69 if refs.is_empty() {
70 return None;
71 }
72
73 let codec = Sq8Codec::calibrate(&refs, dim).ok()?;
76
77 let mut data = Vec::with_capacity(dim * n);
78 for i in 0..n {
79 if let Some(v) = index.get_vector(i as u32) {
80 data.extend(codec.quantize(v));
81 } else {
82 data.extend(vec![0u8; dim]);
83 }
84 }
85
86 Some((codec, data))
87 }
88
89 pub fn build_pq_for_index(
92 index: &HnswIndex,
93 pq_m: usize,
94 memory: ScopedMemory,
95 ) -> Option<(PqCodec, Vec<u8>)> {
96 let dim = index.dim();
97 if pq_m == 0 || !dim.is_multiple_of(pq_m) {
98 return None;
99 }
100 let n = index.len();
101 let mut refs: Vec<Vec<f32>> = Vec::with_capacity(n);
102 for i in 0..n {
103 if !index.is_deleted(i as u32)
104 && let Some(v) = index.get_vector(i as u32)
105 {
106 refs.push(v.to_vec());
107 }
108 }
109 if refs.is_empty() {
110 return None;
111 }
112 let refs_slices: Vec<&[f32]> = refs.iter().map(|v| v.as_slice()).collect();
113 let k = 256usize.min(refs.len());
114 let codec = match PqCodec::train(&refs_slices, dim, pq_m, k, 20, memory) {
118 Ok(codec) => codec,
119 Err(e) => {
120 tracing::warn!(error = %e, dim, pq_m, "PQ training refused; segment stays unquantized");
121 return None;
122 }
123 };
124 let zero = vec![0.0f32; dim];
127 let all: Vec<&[f32]> = (0..n as u32)
128 .map(|i| index.get_vector(i).unwrap_or(zero.as_slice()))
129 .collect();
130 let codes = match codec.encode_batch(&all) {
131 Ok(codes) => codes,
132 Err(e) => {
133 tracing::warn!(error = %e, dim, pq_m, "PQ encoding refused; segment stays unquantized");
134 return None;
135 }
136 };
137 Some((codec, codes))
138 }
139}
140
141const _: usize = DEFAULT_SEAL_THRESHOLD;