1use crate::{OptimizerState, ParamGroupState};
7use std::collections::HashMap;
8use torsh_core::error::{Result, TorshError};
9use torsh_core::DeviceType;
10use torsh_tensor::Tensor;
11
12#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum CompressionMethod {
15 None,
16 Gzip,
17 Zstd,
18 Lz4,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq)]
23pub enum SerializationFormat {
24 Binary,
25 Json,
26 MessagePack,
27 Protobuf,
28}
29
30#[derive(Debug, Clone)]
32pub struct StateDictConfig {
33 pub compression: CompressionMethod,
34 pub format: SerializationFormat,
35 pub compress_threshold: usize,
36 pub use_memory_mapping: bool,
37 pub chunk_size: usize,
38 pub parallel_processing: bool,
39}
40
41impl Default for StateDictConfig {
42 fn default() -> Self {
43 Self {
44 compression: CompressionMethod::Zstd,
45 format: SerializationFormat::Binary,
46 compress_threshold: 1024 * 1024, use_memory_mapping: true,
48 chunk_size: 64 * 1024 * 1024, parallel_processing: true,
50 }
51 }
52}
53
54pub struct StateDictManager {
56 config: StateDictConfig,
57 cache: HashMap<String, CachedStateEntry>,
58 compression_stats: CompressionStats,
59}
60
61#[derive(Clone)]
63struct CachedStateEntry {
64 data: Vec<u8>,
65 checksum: u64,
66 last_accessed: std::time::Instant,
67 compression_ratio: f64,
68}
69
70#[derive(Debug, Clone, Default)]
72pub struct CompressionStats {
73 pub total_original_size: u64,
74 pub total_compressed_size: u64,
75 pub compression_time_ms: u64,
76 pub decompression_time_ms: u64,
77 pub cache_hits: u64,
78 pub cache_misses: u64,
79}
80
81impl CompressionStats {
82 pub fn compression_ratio(&self) -> f64 {
84 if self.total_original_size == 0 {
85 1.0
86 } else {
87 self.total_compressed_size as f64 / self.total_original_size as f64
88 }
89 }
90
91 pub fn cache_hit_rate(&self) -> f64 {
93 let total_accesses = self.cache_hits + self.cache_misses;
94 if total_accesses == 0 {
95 0.0
96 } else {
97 self.cache_hits as f64 / total_accesses as f64
98 }
99 }
100}
101
102impl StateDictManager {
103 pub fn new() -> Self {
105 Self::with_config(StateDictConfig::default())
106 }
107
108 pub fn with_config(config: StateDictConfig) -> Self {
110 Self {
111 config,
112 cache: HashMap::new(),
113 compression_stats: CompressionStats::default(),
114 }
115 }
116
117 pub fn serialize_state(&mut self, state: &OptimizerState) -> Result<Vec<u8>> {
119 let start_time = std::time::Instant::now();
120
121 let serializable_state = self.prepare_for_serialization(state)?;
123
124 let mut data = match self.config.format {
126 SerializationFormat::Binary => self.serialize_binary(&serializable_state)?,
127 SerializationFormat::Json => self.serialize_json(&serializable_state)?,
128 SerializationFormat::MessagePack => self.serialize_msgpack(&serializable_state)?,
129 SerializationFormat::Protobuf => self.serialize_protobuf(&serializable_state)?,
130 };
131
132 let original_size = data.len() as u64;
133
134 if data.len() > self.config.compress_threshold {
136 data = self.compress_data(data)?;
137 }
138
139 let compressed_size = data.len() as u64;
140
141 self.compression_stats.total_original_size += original_size;
143 self.compression_stats.total_compressed_size += compressed_size;
144 self.compression_stats.compression_time_ms += start_time.elapsed().as_millis() as u64;
145
146 Ok(data)
147 }
148
149 pub fn deserialize_state(&mut self, data: &[u8]) -> Result<OptimizerState> {
151 let start_time = std::time::Instant::now();
152
153 let decompressed_data = if self.is_compressed(data) {
155 self.decompress_data(data)?
156 } else {
157 data.to_vec()
158 };
159
160 let serializable_state = match self.config.format {
162 SerializationFormat::Binary => self.deserialize_binary(&decompressed_data)?,
163 SerializationFormat::Json => self.deserialize_json(&decompressed_data)?,
164 SerializationFormat::MessagePack => self.deserialize_msgpack(&decompressed_data)?,
165 SerializationFormat::Protobuf => self.deserialize_protobuf(&decompressed_data)?,
166 };
167
168 let state = self.restore_from_serialization(serializable_state)?;
170
171 self.compression_stats.decompression_time_ms += start_time.elapsed().as_millis() as u64;
172
173 Ok(state)
174 }
175
176 pub fn save_to_file(&mut self, state: &OptimizerState, path: &str) -> Result<()> {
178 let data = self.serialize_state(state)?;
179
180 if self.config.use_memory_mapping && data.len() > self.config.chunk_size {
181 self.save_with_memory_mapping(&data, path)
182 } else {
183 std::fs::write(path, data)
184 .map_err(|e| TorshError::IoError(format!("Failed to write file: {e}")))?;
185 Ok(())
186 }
187 }
188
189 pub fn load_from_file(&mut self, path: &str) -> Result<OptimizerState> {
191 let data = if self.config.use_memory_mapping {
192 self.load_with_memory_mapping(path)?
193 } else {
194 std::fs::read(path)
195 .map_err(|e| TorshError::IoError(format!("Failed to read file: {e}")))?
196 };
197
198 self.deserialize_state(&data)
199 }
200
201 pub fn get_stats(&self) -> &CompressionStats {
203 &self.compression_stats
204 }
205
206 pub fn clear_cache(&mut self) {
208 self.cache.clear();
209 self.compression_stats = CompressionStats::default();
210 }
211
212 fn prepare_for_serialization(&self, state: &OptimizerState) -> Result<SerializableState> {
214 let mut serializable_tensors = HashMap::new();
215
216 for (param_id, tensor_map) in &state.state {
217 let mut param_tensors = HashMap::new();
218 for (tensor_name, tensor) in tensor_map {
219 let tensor_data = TensorData {
221 data: tensor.to_vec()?,
222 shape: tensor.shape().dims().to_vec(),
223 dtype: format!("{:?}", tensor.dtype()),
224 device: format!("{:?}", tensor.device()),
225 };
226 param_tensors.insert(tensor_name.clone(), tensor_data);
227 }
228 serializable_tensors.insert(param_id.clone(), param_tensors);
229 }
230
231 Ok(SerializableState {
232 optimizer_type: state.optimizer_type.clone(),
233 version: state.version.clone(),
234 param_groups: state.param_groups.clone(),
235 state: serializable_tensors,
236 global_state: state.global_state.clone(),
237 })
238 }
239
240 fn restore_from_serialization(
242 &self,
243 serializable: SerializableState,
244 ) -> Result<OptimizerState> {
245 let mut state_tensors = HashMap::new();
246
247 for (param_id, tensor_map) in serializable.state {
248 let mut param_tensors = HashMap::new();
249 for (tensor_name, tensor_data) in tensor_map {
250 let tensor = self.recreate_tensor(tensor_data)?;
252 param_tensors.insert(tensor_name, tensor);
253 }
254 state_tensors.insert(param_id, param_tensors);
255 }
256
257 Ok(OptimizerState {
258 optimizer_type: serializable.optimizer_type,
259 version: serializable.version,
260 param_groups: serializable.param_groups,
261 state: state_tensors,
262 global_state: serializable.global_state,
263 })
264 }
265
266 fn recreate_tensor(&self, data: TensorData) -> Result<Tensor> {
268 let device = DeviceType::Cpu; Ok(torsh_tensor::creation::from_vec(
273 data.data,
274 &data.shape,
275 device,
276 )?)
277 }
278
279 fn compress_data(&self, data: Vec<u8>) -> Result<Vec<u8>> {
281 match self.config.compression {
282 CompressionMethod::None => Ok(data),
283 CompressionMethod::Gzip => {
284 Ok(data)
286 }
287 CompressionMethod::Zstd => {
288 Ok(data)
290 }
291 CompressionMethod::Lz4 => {
292 Ok(data)
294 }
295 }
296 }
297
298 fn decompress_data(&self, data: &[u8]) -> Result<Vec<u8>> {
300 Ok(data.to_vec())
302 }
303
304 fn is_compressed(&self, data: &[u8]) -> bool {
306 data.len() > 4 && &data[0..4] == b"COMP"
308 }
309
310 fn serialize_binary(&self, state: &SerializableState) -> Result<Vec<u8>> {
312 Ok(format!("{:?}", state).into_bytes())
314 }
315
316 fn deserialize_binary(&self, _data: &[u8]) -> Result<SerializableState> {
318 Err(TorshError::Other(
320 "Binary deserialization not fully implemented".to_string(),
321 ))
322 }
323
324 fn serialize_json(&self, state: &SerializableState) -> Result<Vec<u8>> {
326 Ok(format!("{:?}", state).into_bytes())
328 }
329
330 fn deserialize_json(&self, _data: &[u8]) -> Result<SerializableState> {
332 Err(TorshError::Other(
334 "JSON deserialization not fully implemented".to_string(),
335 ))
336 }
337
338 fn serialize_msgpack(&self, state: &SerializableState) -> Result<Vec<u8>> {
340 Ok(format!("{:?}", state).into_bytes())
342 }
343
344 fn deserialize_msgpack(&self, _data: &[u8]) -> Result<SerializableState> {
346 Err(TorshError::Other(
348 "MessagePack deserialization not fully implemented".to_string(),
349 ))
350 }
351
352 fn serialize_protobuf(&self, state: &SerializableState) -> Result<Vec<u8>> {
354 Ok(format!("{:?}", state).into_bytes())
356 }
357
358 fn deserialize_protobuf(&self, _data: &[u8]) -> Result<SerializableState> {
360 Err(TorshError::Other(
362 "Protobuf deserialization not fully implemented".to_string(),
363 ))
364 }
365
366 fn save_with_memory_mapping(&self, data: &[u8], path: &str) -> Result<()> {
368 std::fs::write(path, data)
370 .map_err(|e| TorshError::IoError(format!("Failed to write file: {e}")))?;
371 Ok(())
372 }
373
374 fn load_with_memory_mapping(&self, path: &str) -> Result<Vec<u8>> {
376 std::fs::read(path).map_err(|e| TorshError::IoError(format!("Failed to read file: {e}")))
378 }
379}
380
381impl Default for StateDictManager {
382 fn default() -> Self {
383 Self::new()
384 }
385}
386
387#[derive(Debug, Clone)]
389struct SerializableState {
390 optimizer_type: String,
391 version: String,
392 param_groups: Vec<ParamGroupState>,
393 state: HashMap<String, HashMap<String, TensorData>>,
394 global_state: HashMap<String, f32>,
395}
396
397#[derive(Debug, Clone)]
399struct TensorData {
400 data: Vec<f32>,
401 shape: Vec<usize>,
402 dtype: String,
403 device: String,
404}
405
406pub mod utils {
408 use super::*;
409
410 pub fn calculate_state_size(state: &OptimizerState) -> usize {
412 let mut total_size = 0;
413
414 for (_, tensor_map) in &state.state {
415 for (_, tensor) in tensor_map {
416 total_size += tensor.numel() * std::mem::size_of::<f32>();
417 }
418 }
419
420 total_size
421 }
422
423 pub fn estimate_memory_usage(
425 state: &OptimizerState,
426 config: &StateDictConfig,
427 ) -> MemoryEstimate {
428 let state_size = calculate_state_size(state);
429 let serialization_overhead = state_size / 4; let compression_working_memory = if matches!(config.compression, CompressionMethod::None) {
431 0
432 } else {
433 state_size / 2
434 };
435
436 MemoryEstimate {
437 state_size,
438 serialization_overhead,
439 compression_working_memory,
440 total_peak_usage: state_size + serialization_overhead + compression_working_memory,
441 }
442 }
443
444 pub fn optimize_config_for_size(state_size: usize) -> StateDictConfig {
446 let mut config = StateDictConfig::default();
447
448 if state_size > 1024 * 1024 * 1024 {
449 config.compression = CompressionMethod::Zstd;
451 config.use_memory_mapping = true;
452 config.chunk_size = 128 * 1024 * 1024; config.parallel_processing = true;
454 } else if state_size > 100 * 1024 * 1024 {
455 config.compression = CompressionMethod::Lz4; config.use_memory_mapping = false;
458 config.parallel_processing = true;
459 } else {
460 config.compression = CompressionMethod::None;
461 config.use_memory_mapping = false;
462 config.parallel_processing = false;
463 }
464
465 config
466 }
467}
468
469#[derive(Debug, Clone)]
471pub struct MemoryEstimate {
472 pub state_size: usize,
473 pub serialization_overhead: usize,
474 pub compression_working_memory: usize,
475 pub total_peak_usage: usize,
476}
477
478#[cfg(test)]
479mod tests {
480 use super::*;
481 use crate::OptimizerResult;
482 use torsh_core::device::Device;
483 use torsh_tensor::creation;
484
485 #[test]
486 fn test_state_dict_manager_creation() {
487 let manager = StateDictManager::new();
488 assert_eq!(manager.config.compression, CompressionMethod::Zstd);
489 assert_eq!(manager.config.format, SerializationFormat::Binary);
490 }
491
492 #[test]
493 fn test_compression_stats() {
494 let mut stats = CompressionStats::default();
495 stats.total_original_size = 1000;
496 stats.total_compressed_size = 500;
497 stats.cache_hits = 8;
498 stats.cache_misses = 2;
499
500 assert_eq!(stats.compression_ratio(), 0.5);
501 assert_eq!(stats.cache_hit_rate(), 0.8);
502 }
503
504 #[test]
505 fn test_config_optimization() {
506 let small_config = utils::optimize_config_for_size(1024); assert_eq!(small_config.compression, CompressionMethod::None);
508 assert!(!small_config.use_memory_mapping);
509
510 let large_config = utils::optimize_config_for_size(2 * 1024 * 1024 * 1024); assert_eq!(large_config.compression, CompressionMethod::Zstd);
512 assert!(large_config.use_memory_mapping);
513 }
514
515 #[test]
516 fn test_memory_estimation() -> OptimizerResult<()> {
517 let tensor = creation::randn::<f32>(&[100, 100])?;
519
520 let mut state_map = HashMap::new();
521 let mut tensor_map = HashMap::new();
522 tensor_map.insert("test_tensor".to_string(), tensor);
523 state_map.insert("param_1".to_string(), tensor_map);
524
525 let state = OptimizerState {
526 optimizer_type: "test".to_string(),
527 version: "0.1.0".to_string(),
528 param_groups: vec![],
529 state: state_map,
530 global_state: HashMap::new(),
531 };
532
533 let config = StateDictConfig::default();
534 let estimate = utils::estimate_memory_usage(&state, &config);
535
536 assert!(estimate.state_size > 0);
537 assert!(estimate.total_peak_usage >= estimate.state_size);
538 Ok(())
539 }
540
541 #[test]
542 fn test_calculate_state_size() -> OptimizerResult<()> {
543 let tensor1 = creation::randn::<f32>(&[10, 10])?; let tensor2 = creation::randn::<f32>(&[5, 5])?; let mut state_map = HashMap::new();
547 let mut tensor_map = HashMap::new();
548 tensor_map.insert("tensor1".to_string(), tensor1);
549 tensor_map.insert("tensor2".to_string(), tensor2);
550 state_map.insert("param_1".to_string(), tensor_map);
551
552 let state = OptimizerState {
553 optimizer_type: "test".to_string(),
554 version: "0.1.0".to_string(),
555 param_groups: vec![],
556 state: state_map,
557 global_state: HashMap::new(),
558 };
559
560 let size = utils::calculate_state_size(&state);
561 assert_eq!(size, 125 * 4);
563 Ok(())
564 }
565}