1use crate::infer::GraphExt as _;
24use crate::{DType, Graph, NodeId};
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum VqMetric {
29 L2,
31 Cosine,
33}
34
35impl Graph {
36 pub fn vector_quantize(
47 &mut self,
48 x: NodeId,
49 codebook: NodeId,
50 metric: VqMetric,
51 ) -> (NodeId, NodeId) {
52 let xs = self.shape(x).clone();
53 let cs = self.shape(codebook).clone();
54 assert_eq!(xs.rank(), 2, "vector_quantize: x must be rank-2 [N, D]");
55 assert_eq!(
56 cs.rank(),
57 2,
58 "vector_quantize: codebook must be rank-2 [K, D]"
59 );
60 let d = xs.dim(1).unwrap_static();
61 let k = cs.dim(0).unwrap_static();
62 assert_eq!(
63 cs.dim(1).unwrap_static(),
64 d,
65 "vector_quantize: x/codebook feature dim mismatch"
66 );
67
68 let idx = match metric {
69 VqMetric::L2 => {
70 let cb_t = self.transpose_(codebook, vec![1, 0]); let cross = self.mm(x, cb_t); let neg_two = self.constant(-2.0, DType::F32);
80 let neg_two_cross = self.mul(cross, neg_two);
81 let cb_sq = self.mul(codebook, codebook); let cb_norm = self.sum(cb_sq, vec![1], false); let cb_norm_row = self.reshape_(cb_norm, vec![1, k as i64]); let dist = self.add(cb_norm_row, neg_two_cross); let s = crate::shape::reduce_shape(self.shape(dist), &[1], false)
86 .expect("argmin shape");
87 self.argmin(dist, 1, false, s) }
89 VqMetric::Cosine => {
90 let xn = self.l2_normalize_rows(x);
91 let cn = self.l2_normalize_rows(codebook);
92 let cn_t = self.transpose_(cn, vec![1, 0]); let sim = self.mm(xn, cn_t); let s =
95 crate::shape::reduce_shape(self.shape(sim), &[1], false).expect("argmax shape");
96 self.argmax(sim, 1, false, s) }
98 };
99
100 let quantized = self.gather_(codebook, idx, 0); (idx, quantized)
102 }
103
104 pub fn residual_vq(
112 &mut self,
113 x: NodeId,
114 codebooks: &[NodeId],
115 metric: VqMetric,
116 ) -> (Vec<NodeId>, NodeId) {
117 assert!(!codebooks.is_empty(), "residual_vq: need ≥1 codebook");
118 let mut indices = Vec::with_capacity(codebooks.len());
119 let (idx0, mut recon) = self.vector_quantize(x, codebooks[0], metric);
120 indices.push(idx0);
121 let mut residual = self.sub(x, recon);
122 for &cb in &codebooks[1..] {
123 let (idx, q) = self.vector_quantize(residual, cb, metric);
124 indices.push(idx);
125 recon = self.add(recon, q);
126 residual = self.sub(residual, q);
127 }
128 (indices, recon)
129 }
130
131 fn l2_normalize_rows(&mut self, x: NodeId) -> NodeId {
133 let sq = self.mul(x, x);
134 let rank = self.shape(x).rank();
135 let sum = self.sum(sq, vec![rank - 1], true); let eps = self.constant(1e-12, DType::F32);
137 let sum_eps = self.add(sum, eps);
138 let norm = self.sqrt(sum_eps);
139 self.div(x, norm)
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use crate::{Op, Shape};
147
148 fn const_f32(g: &mut Graph, xs: &[f32], shape: &[usize]) -> NodeId {
149 let mut bytes = Vec::with_capacity(xs.len() * 4);
150 for x in xs {
151 bytes.extend_from_slice(&x.to_le_bytes());
152 }
153 g.add_node(
154 Op::Constant { data: bytes },
155 vec![],
156 Shape::new(shape, DType::F32),
157 )
158 }
159
160 fn dims(g: &Graph, id: NodeId) -> Vec<usize> {
161 g.shape(id)
162 .dims()
163 .iter()
164 .map(|d| d.unwrap_static())
165 .collect()
166 }
167
168 #[test]
169 fn vq_shapes_l2() {
170 let mut g = Graph::new("vq");
171 let x = const_f32(&mut g, &[0.0; 3 * 4], &[3, 4]);
172 let cb = const_f32(&mut g, &[0.0; 5 * 4], &[5, 4]);
173 let (idx, q) = g.vector_quantize(x, cb, VqMetric::L2);
174 assert_eq!(dims(&g, idx), vec![3]);
175 assert_eq!(dims(&g, q), vec![3, 4]);
176 }
177
178 #[test]
179 fn vq_shapes_cosine() {
180 let mut g = Graph::new("vq_cos");
181 let x = const_f32(&mut g, &[0.0; 2 * 6], &[2, 6]);
182 let cb = const_f32(&mut g, &[0.0; 8 * 6], &[8, 6]);
183 let (idx, q) = g.vector_quantize(x, cb, VqMetric::Cosine);
184 assert_eq!(dims(&g, idx), vec![2]);
185 assert_eq!(dims(&g, q), vec![2, 6]);
186 }
187
188 #[test]
189 fn rvq_shapes() {
190 let mut g = Graph::new("rvq");
191 let x = const_f32(&mut g, &[0.0; 4 * 4], &[4, 4]);
192 let cbs: Vec<_> = (0..3)
193 .map(|_| const_f32(&mut g, &[0.0; 16 * 4], &[16, 4]))
194 .collect();
195 let (indices, recon) = g.residual_vq(x, &cbs, VqMetric::L2);
196 assert_eq!(indices.len(), 3);
197 assert_eq!(dims(&g, recon), vec![4, 4]);
198 }
199}