1use crate::{NeuralError, Result};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct CodebookConfig {
10 pub num_centroids: usize,
12 pub centroid_dim: usize,
14 pub index_bits: u8,
16 pub product_quantization: bool,
18 pub pq_subspaces: usize,
20}
21
22impl CodebookConfig {
23 pub fn attention_qk() -> Self {
25 Self {
26 num_centroids: 4096,
27 centroid_dim: 64,
28 index_bits: 12,
29 product_quantization: false,
30 pq_subspaces: 1,
31 }
32 }
33
34 pub fn attention_vo() -> Self {
36 Self {
37 num_centroids: 8192,
38 centroid_dim: 64,
39 index_bits: 13,
40 product_quantization: false,
41 pq_subspaces: 1,
42 }
43 }
44
45 pub fn ffn() -> Self {
47 Self {
48 num_centroids: 2048,
49 centroid_dim: 128,
50 index_bits: 11,
51 product_quantization: true,
52 pq_subspaces: 2,
53 }
54 }
55
56 pub fn normalization() -> Self {
58 Self {
59 num_centroids: 256,
60 centroid_dim: 32,
61 index_bits: 8,
62 product_quantization: false,
63 pq_subspaces: 1,
64 }
65 }
66
67 pub fn memory_size(&self) -> usize {
69 self.num_centroids * self.centroid_dim * 4 }
71
72 pub fn bits_per_weight(&self) -> f32 {
74 if self.product_quantization {
75 self.index_bits as f32 * self.pq_subspaces as f32 / self.centroid_dim as f32
76 } else {
77 self.index_bits as f32 / self.centroid_dim as f32
78 }
79 }
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct Codebook {
85 pub config: CodebookConfig,
87 pub centroids: Vec<f32>,
89 pub id: String,
91 pub stats: CodebookStats,
93}
94
95#[derive(Debug, Clone, Default, Serialize, Deserialize)]
97pub struct CodebookStats {
98 pub mse: f32,
100 pub iterations: usize,
102 pub usage_distribution: (f32, f32, f32),
104 pub samples_seen: usize,
106}
107
108impl Codebook {
109 pub fn new(config: CodebookConfig, id: impl Into<String>) -> Self {
111 use rand::Rng;
112 let mut rng = rand::thread_rng();
113
114 let centroids: Vec<f32> = (0..config.num_centroids * config.centroid_dim)
115 .map(|_| rng.gen_range(-0.1..0.1))
116 .collect();
117
118 Self {
119 config,
120 centroids,
121 id: id.into(),
122 stats: CodebookStats::default(),
123 }
124 }
125
126 pub fn from_centroids(
128 config: CodebookConfig,
129 centroids: Vec<f32>,
130 id: impl Into<String>,
131 ) -> Result<Self> {
132 let expected = config.num_centroids * config.centroid_dim;
133 if centroids.len() != expected {
134 return Err(NeuralError::DimensionMismatch {
135 expected,
136 actual: centroids.len(),
137 });
138 }
139
140 Ok(Self {
141 config,
142 centroids,
143 id: id.into(),
144 stats: CodebookStats::default(),
145 })
146 }
147
148 pub fn find_nearest(&self, vector: &[f32]) -> Result<(usize, f32)> {
150 if vector.len() != self.config.centroid_dim {
151 return Err(NeuralError::DimensionMismatch {
152 expected: self.config.centroid_dim,
153 actual: vector.len(),
154 });
155 }
156
157 let mut best_idx = 0;
158 let mut best_dist = f32::INFINITY;
159
160 for i in 0..self.config.num_centroids {
161 let offset = i * self.config.centroid_dim;
162 let dist =
163 self.squared_distance(vector, &self.centroids[offset..offset + vector.len()]);
164
165 if dist < best_dist {
166 best_dist = dist;
167 best_idx = i;
168 }
169 }
170
171 Ok((best_idx, best_dist.sqrt()))
172 }
173
174 pub fn find_k_nearest(&self, vector: &[f32], k: usize) -> Result<Vec<(usize, f32)>> {
176 if vector.len() != self.config.centroid_dim {
177 return Err(NeuralError::DimensionMismatch {
178 expected: self.config.centroid_dim,
179 actual: vector.len(),
180 });
181 }
182
183 let mut distances: Vec<(usize, f32)> = (0..self.config.num_centroids)
184 .map(|i| {
185 let offset = i * self.config.centroid_dim;
186 let dist =
187 self.squared_distance(vector, &self.centroids[offset..offset + vector.len()]);
188 (i, dist.sqrt())
189 })
190 .collect();
191
192 distances.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
193 distances.truncate(k);
194
195 Ok(distances)
196 }
197
198 pub fn get_centroid(&self, index: usize) -> Option<&[f32]> {
200 if index >= self.config.num_centroids {
201 return None;
202 }
203 let offset = index * self.config.centroid_dim;
204 Some(&self.centroids[offset..offset + self.config.centroid_dim])
205 }
206
207 fn squared_distance(&self, a: &[f32], b: &[f32]) -> f32 {
209 a.iter().zip(b.iter()).map(|(x, y)| (x - y).powi(2)).sum()
210 }
211
212 pub fn encode_batch(&self, vectors: &[f32], vector_dim: usize) -> Result<Vec<u16>> {
214 if vector_dim != self.config.centroid_dim {
215 return Err(NeuralError::DimensionMismatch {
216 expected: self.config.centroid_dim,
217 actual: vector_dim,
218 });
219 }
220
221 let num_vectors = vectors.len() / vector_dim;
222 let mut indices = Vec::with_capacity(num_vectors);
223
224 for i in 0..num_vectors {
225 let offset = i * vector_dim;
226 let vector = &vectors[offset..offset + vector_dim];
227 let (idx, _) = self.find_nearest(vector)?;
228 indices.push(idx as u16);
229 }
230
231 Ok(indices)
232 }
233
234 pub fn decode_batch(&self, indices: &[u16]) -> Vec<f32> {
236 let dim = self.config.centroid_dim;
237 let mut vectors = Vec::with_capacity(indices.len() * dim);
238
239 for &idx in indices {
240 if let Some(centroid) = self.get_centroid(idx as usize) {
241 vectors.extend_from_slice(centroid);
242 } else {
243 vectors.extend(std::iter::repeat_n(0.0, dim));
245 }
246 }
247
248 vectors
249 }
250
251 pub fn to_bytes(&self) -> Vec<u8> {
253 bincode::serialize(self).unwrap_or_default()
254 }
255
256 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
258 bincode::deserialize(bytes).map_err(|e| NeuralError::InvalidFormat(e.to_string()))
259 }
260}
261
262#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
264pub enum LayerType {
265 AttentionQK,
267 AttentionVO,
269 FFN,
271 Normalization,
273 Embedding,
275 OutputHead,
277}
278
279impl LayerType {
280 pub fn default_config(&self) -> CodebookConfig {
282 match self {
283 LayerType::AttentionQK => CodebookConfig::attention_qk(),
284 LayerType::AttentionVO => CodebookConfig::attention_vo(),
285 LayerType::FFN => CodebookConfig::ffn(),
286 LayerType::Normalization => CodebookConfig::normalization(),
287 LayerType::Embedding => CodebookConfig::ffn(), LayerType::OutputHead => CodebookConfig::attention_vo(), }
290 }
291}
292
293#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct LayerCodebook {
296 codebooks: HashMap<LayerType, Codebook>,
298 pub model_id: String,
300 pub total_memory: usize,
302}
303
304impl LayerCodebook {
305 pub fn new(model_id: impl Into<String>) -> Self {
307 Self {
308 codebooks: HashMap::new(),
309 model_id: model_id.into(),
310 total_memory: 0,
311 }
312 }
313
314 pub fn add(&mut self, layer_type: LayerType, codebook: Codebook) {
316 self.total_memory += codebook.config.memory_size();
317 self.codebooks.insert(layer_type, codebook);
318 }
319
320 pub fn get(&self, layer_type: LayerType) -> Option<&Codebook> {
322 self.codebooks.get(&layer_type)
323 }
324
325 pub fn with_defaults(model_id: impl Into<String>) -> Self {
327 let model_id = model_id.into();
328 let mut lc = Self::new(&model_id);
329
330 for layer_type in &[
331 LayerType::AttentionQK,
332 LayerType::AttentionVO,
333 LayerType::FFN,
334 LayerType::Normalization,
335 ] {
336 let config = layer_type.default_config();
337 let id = format!("{}_{:?}", model_id, layer_type);
338 lc.add(*layer_type, Codebook::new(config, id));
339 }
340
341 lc
342 }
343
344 pub fn to_bytes(&self) -> Vec<u8> {
346 bincode::serialize(self).unwrap_or_default()
347 }
348
349 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
351 bincode::deserialize(bytes).map_err(|e| NeuralError::InvalidFormat(e.to_string()))
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use super::*;
358
359 #[test]
360 fn test_codebook_creation() {
361 let config = CodebookConfig::attention_qk();
362 let codebook = Codebook::new(config.clone(), "test");
363
364 assert_eq!(codebook.config.num_centroids, 4096);
365 assert_eq!(codebook.centroids.len(), 4096 * 64);
366 }
367
368 #[test]
369 fn test_find_nearest() {
370 let config = CodebookConfig {
371 num_centroids: 4,
372 centroid_dim: 3,
373 index_bits: 2,
374 product_quantization: false,
375 pq_subspaces: 1,
376 };
377
378 let centroids = vec![
379 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, ];
384
385 let codebook = Codebook::from_centroids(config, centroids, "test").unwrap();
386
387 let (idx, _) = codebook.find_nearest(&[0.1, 0.9, 0.1]).unwrap();
389 assert_eq!(idx, 1);
390
391 let (idx, _) = codebook.find_nearest(&[0.9, 0.9, 0.9]).unwrap();
393 assert_eq!(idx, 3);
394 }
395
396 #[test]
397 fn test_encode_decode() {
398 let config = CodebookConfig {
399 num_centroids: 256,
400 centroid_dim: 4,
401 index_bits: 8,
402 product_quantization: false,
403 pq_subspaces: 1,
404 };
405
406 let codebook = Codebook::new(config, "test");
407
408 let vectors: Vec<f32> = (0..40).map(|i| i as f32 * 0.01).collect();
410
411 let indices = codebook.encode_batch(&vectors, 4).unwrap();
413 assert_eq!(indices.len(), 10);
414
415 let decoded = codebook.decode_batch(&indices);
417 assert_eq!(decoded.len(), 40);
418 }
419
420 #[test]
421 fn test_layer_codebook() {
422 let lc = LayerCodebook::with_defaults("sdxl");
423
424 assert!(lc.get(LayerType::AttentionQK).is_some());
425 assert!(lc.get(LayerType::FFN).is_some());
426 assert!(lc.total_memory > 0);
427 }
428
429 #[test]
430 fn test_memory_size() {
431 let config = CodebookConfig::attention_qk();
432 assert_eq!(config.memory_size(), 4096 * 64 * 4); }
434}