1use crate::{Codebook, LayerCodebook, LayerType, NeuralError, Result};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct EncoderConfig {
9 pub target_ratio: f32,
11 pub max_quality_loss: f32,
13 pub use_residual: bool,
15 pub residual_bits: u8,
17 pub batch_size: usize,
19}
20
21impl Default for EncoderConfig {
22 fn default() -> Self {
23 Self {
24 target_ratio: 10.0,
25 max_quality_loss: 0.01,
26 use_residual: true,
27 residual_bits: 4,
28 batch_size: 1024,
29 }
30 }
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct EncodedTensor {
36 pub name: String,
38 pub shape: Vec<usize>,
40 pub dtype: String,
42 pub layer_type: LayerType,
44 pub indices: Vec<u16>,
46 pub residuals: Option<Vec<i8>>,
48 pub residual_scale: f32,
50 pub stats: EncodingStats,
52}
53
54#[derive(Debug, Clone, Default, Serialize, Deserialize)]
56pub struct EncodingStats {
57 pub mse: f32,
59 pub psnr: f32,
61 pub compression_ratio: f32,
63 pub original_size: usize,
65 pub compressed_size: usize,
67}
68
69#[derive(Debug)]
71pub struct NeuralEncoder {
72 config: EncoderConfig,
73 codebooks: LayerCodebook,
74}
75
76impl NeuralEncoder {
77 pub fn new(config: EncoderConfig, codebooks: LayerCodebook) -> Self {
79 Self { config, codebooks }
80 }
81
82 pub fn encode_tensor(
84 &self,
85 name: &str,
86 data: &[f32],
87 shape: &[usize],
88 layer_type: LayerType,
89 ) -> Result<EncodedTensor> {
90 let codebook = self
91 .codebooks
92 .get(layer_type)
93 .ok_or_else(|| NeuralError::CodebookNotFound(format!("{:?}", layer_type)))?;
94
95 let dim = codebook.config.centroid_dim;
96 let num_vectors = data.len() / dim;
97
98 if !data.len().is_multiple_of(dim) {
99 return Err(NeuralError::DimensionMismatch {
100 expected: (num_vectors + 1) * dim,
101 actual: data.len(),
102 });
103 }
104
105 let indices = codebook.encode_batch(data, dim)?;
107
108 let (residuals, residual_scale) = if self.config.use_residual {
110 self.compute_residuals(data, &indices, codebook)
111 } else {
112 (None, 0.0)
113 };
114
115 let reconstructed = codebook.decode_batch(&indices);
117 let mse = self.compute_mse(data, &reconstructed);
118 let psnr = self.compute_psnr(mse);
119
120 let original_size = data.len() * 4; let index_size = indices.len() * 2; let residual_size = residuals.as_ref().map(|r| r.len()).unwrap_or(0);
123 let compressed_size = index_size + residual_size;
124
125 let stats = EncodingStats {
126 mse,
127 psnr,
128 compression_ratio: original_size as f32 / compressed_size as f32,
129 original_size,
130 compressed_size,
131 };
132
133 Ok(EncodedTensor {
134 name: name.to_string(),
135 shape: shape.to_vec(),
136 dtype: "float32".to_string(),
137 layer_type,
138 indices,
139 residuals,
140 residual_scale,
141 stats,
142 })
143 }
144
145 fn compute_residuals(
147 &self,
148 original: &[f32],
149 indices: &[u16],
150 codebook: &Codebook,
151 ) -> (Option<Vec<i8>>, f32) {
152 let reconstructed = codebook.decode_batch(indices);
153
154 let residuals: Vec<f32> = original
156 .iter()
157 .zip(reconstructed.iter())
158 .map(|(o, r)| o - r)
159 .collect();
160
161 let max_abs = residuals.iter().map(|r| r.abs()).fold(0.0f32, f32::max);
163
164 if max_abs < 1e-6 {
165 return (None, 0.0);
166 }
167
168 let scale = max_abs / 127.0;
169
170 let quantized: Vec<i8> = residuals
172 .iter()
173 .map(|r| (r / scale).clamp(-127.0, 127.0) as i8)
174 .collect();
175
176 (Some(quantized), scale)
177 }
178
179 fn compute_mse(&self, original: &[f32], reconstructed: &[f32]) -> f32 {
181 if original.len() != reconstructed.len() || original.is_empty() {
182 return f32::INFINITY;
183 }
184
185 let sum: f32 = original
186 .iter()
187 .zip(reconstructed.iter())
188 .map(|(o, r)| (o - r).powi(2))
189 .sum();
190
191 sum / original.len() as f32
192 }
193
194 fn compute_psnr(&self, mse: f32) -> f32 {
196 if mse < 1e-10 {
197 return 100.0; }
199 10.0 * (1.0 / mse).log10()
200 }
201
202 pub fn encode_tensors(
204 &self,
205 tensors: &[(String, Vec<f32>, Vec<usize>, LayerType)],
206 ) -> Result<Vec<EncodedTensor>> {
207 tensors
208 .iter()
209 .map(|(name, data, shape, layer_type)| {
210 self.encode_tensor(name, data, shape, *layer_type)
211 })
212 .collect()
213 }
214
215 pub fn config(&self) -> &EncoderConfig {
217 &self.config
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn test_encode_tensor() {
227 let codebooks = LayerCodebook::with_defaults("test");
228 let encoder = NeuralEncoder::new(EncoderConfig::default(), codebooks);
229
230 let data: Vec<f32> = (0..640).map(|i| (i as f32 / 640.0) - 0.5).collect();
232 let shape = vec![10, 64];
233
234 let encoded = encoder
235 .encode_tensor("test.weight", &data, &shape, LayerType::AttentionQK)
236 .unwrap();
237
238 assert_eq!(encoded.name, "test.weight");
239 assert_eq!(encoded.indices.len(), 10);
240 assert!(encoded.stats.compression_ratio > 1.0);
241 }
242
243 #[test]
244 fn test_residual_encoding() {
245 let config = EncoderConfig {
246 use_residual: true,
247 ..Default::default()
248 };
249
250 let codebooks = LayerCodebook::with_defaults("test");
251 let encoder = NeuralEncoder::new(config, codebooks);
252
253 let data: Vec<f32> = (0..640).map(|i| (i as f32 / 640.0) - 0.5).collect();
254 let shape = vec![10, 64];
255
256 let encoded = encoder
257 .encode_tensor("test.weight", &data, &shape, LayerType::AttentionQK)
258 .unwrap();
259
260 assert!(encoded.residuals.is_some());
261 assert!(encoded.residual_scale > 0.0);
262 }
263
264 #[test]
265 fn test_psnr_calculation() {
266 let codebooks = LayerCodebook::with_defaults("test");
267 let encoder = NeuralEncoder::new(EncoderConfig::default(), codebooks);
268
269 assert!(encoder.compute_psnr(0.001) >= 30.0);
271 assert!(encoder.compute_psnr(0.1) < 20.0);
273 }
274}