1use super::config::{CacheEvictionStrategy, MemoryConfig, TransformerBasedOptimizerConfig};
4use crate::error::Result;
5use scirs2_core::ndarray::Array2;
6use scirs2_core::numeric::Float;
7use std::collections::{HashMap, VecDeque};
8use std::fmt::Debug;
9use std::time::{Duration, Instant};
10
11#[derive(Debug, Clone, Copy, PartialEq)]
13pub enum MemoryManagementStrategy {
14 FIFO,
16 LRU,
18 LFU,
20 ARC,
22 Compressed,
24 Hierarchical,
26}
27
28pub struct TransformerMemoryManager<
30 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
31> {
32 strategy: MemoryManagementStrategy,
34
35 config: MemoryConfig,
37
38 primary_cache: MemoryCache<T>,
40
41 secondary_cache: Option<MemoryCache<T>>,
43
44 compression_manager: Option<CompressionManager<T>>,
46
47 statistics: MemoryStatistics,
49
50 access_tracker: AccessTracker,
52
53 pressure_monitor: MemoryPressureMonitor,
55
56 model_dimension: usize,
58}
59
60impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
61 TransformerMemoryManager<T>
62{
63 pub fn new(config: &TransformerBasedOptimizerConfig<T>) -> Result<Self> {
65 let memory_config = config.memory_config.clone();
66 let strategy = match memory_config.eviction_strategy {
67 CacheEvictionStrategy::LRU => MemoryManagementStrategy::LRU,
68 CacheEvictionStrategy::LFU => MemoryManagementStrategy::LFU,
69 CacheEvictionStrategy::FIFO => MemoryManagementStrategy::FIFO,
70 CacheEvictionStrategy::Random => MemoryManagementStrategy::LRU, };
72
73 let primary_cache = MemoryCache::new(
74 memory_config.max_cache_size / 2,
75 memory_config.eviction_strategy,
76 )?;
77
78 let secondary_cache = if memory_config.max_cache_size > 1024 * 1024 * 100 {
79 Some(MemoryCache::new(
81 memory_config.max_cache_size / 2,
82 CacheEvictionStrategy::FIFO,
83 )?)
84 } else {
85 None
86 };
87
88 let compression_manager = if memory_config.enable_compression {
89 Some(CompressionManager::new()?)
90 } else {
91 None
92 };
93
94 let statistics = MemoryStatistics::new();
95 let access_tracker = AccessTracker::new(1000);
96 let pressure_monitor = MemoryPressureMonitor::new();
97
98 Ok(Self {
99 strategy,
100 config: memory_config,
101 primary_cache,
102 secondary_cache,
103 compression_manager,
104 statistics,
105 access_tracker,
106 pressure_monitor,
107 model_dimension: config.model_dimension,
108 })
109 }
110
111 pub fn store(&mut self, key: String, tensor: Array2<T>) -> Result<()> {
121 if tensor.ncols() != self.model_dimension {
122 return Err(crate::error::OptimError::InvalidConfig(format!(
123 "TransformerMemoryManager holds {}-wide tensors but was given {} columns",
124 self.model_dimension,
125 tensor.ncols()
126 )));
127 }
128 let start_time = Instant::now();
129
130 self.pressure_monitor.update(self.get_memory_usage());
132 if self.pressure_monitor.is_high_pressure() {
133 self.evict_memory()?;
134 }
135
136 let storage_result = match self.strategy {
138 MemoryManagementStrategy::LRU => self.store_lru(key.clone(), tensor.clone()),
139 MemoryManagementStrategy::LFU => self.store_lfu(key.clone(), tensor.clone()),
140 MemoryManagementStrategy::FIFO => self.store_fifo(key.clone(), tensor.clone()),
141 MemoryManagementStrategy::ARC => self.store_arc(key.clone(), tensor.clone()),
142 MemoryManagementStrategy::Compressed => {
143 self.store_compressed(key.clone(), tensor.clone())
144 }
145 MemoryManagementStrategy::Hierarchical => {
146 self.store_hierarchical(key.clone(), tensor.clone())
147 }
148 };
149
150 let tensor_len = tensor.len();
152
153 if storage_result.is_err() && self.secondary_cache.is_some() {
155 if let Some(ref mut secondary) = self.secondary_cache {
156 secondary.store(key.clone(), tensor)?;
157 }
158 }
159
160 let storage_time = start_time.elapsed();
162 self.statistics.record_storage(tensor_len, storage_time);
163 self.access_tracker.record_write(key);
164
165 storage_result
166 }
167
168 pub fn retrieve(&mut self, key: &str) -> Result<Option<Array2<T>>> {
170 let start_time = Instant::now();
171
172 let result = self.primary_cache.retrieve(key)?;
174
175 if result.is_some() {
176 self.access_tracker.record_read(key.to_string());
177 let retrieval_time = start_time.elapsed();
178 self.statistics.record_retrieval(retrieval_time, true);
179 return Ok(result);
180 }
181
182 if let Some(ref mut secondary) = self.secondary_cache {
184 let result = secondary.retrieve(key)?;
185 if result.is_some() {
186 self.access_tracker.record_read(key.to_string());
187 let retrieval_time = start_time.elapsed();
188 self.statistics.record_retrieval(retrieval_time, true);
189 return Ok(result);
190 }
191 }
192
193 if let Some(ref mut compression) = self.compression_manager {
195 if let Some(compressed_data) = compression.retrieve(key)? {
196 let decompressed = compression.decompress(&compressed_data)?;
197 self.access_tracker.record_read(key.to_string());
198 let retrieval_time = start_time.elapsed();
199 self.statistics.record_retrieval(retrieval_time, true);
200 return Ok(Some(decompressed));
201 }
202 }
203
204 let retrieval_time = start_time.elapsed();
205 self.statistics.record_retrieval(retrieval_time, false);
206 Ok(None)
207 }
208
209 pub fn remove(&mut self, key: &str) -> Result<bool> {
211 let mut removed = false;
212
213 if self.primary_cache.remove(key)? {
214 removed = true;
215 }
216
217 if let Some(ref mut secondary) = self.secondary_cache {
218 if secondary.remove(key)? {
219 removed = true;
220 }
221 }
222
223 if let Some(ref mut compression) = self.compression_manager {
224 if compression.remove(key)? {
225 removed = true;
226 }
227 }
228
229 self.access_tracker.record_removal(key.to_string());
230 Ok(removed)
231 }
232
233 pub fn clear(&mut self) -> Result<()> {
235 self.primary_cache.clear()?;
236
237 if let Some(ref mut secondary) = self.secondary_cache {
238 secondary.clear()?;
239 }
240
241 if let Some(ref mut compression) = self.compression_manager {
242 compression.clear()?;
243 }
244
245 self.statistics.reset();
246 self.access_tracker.clear();
247 self.pressure_monitor.reset();
248
249 Ok(())
250 }
251
252 pub fn get_memory_usage(&self) -> usize {
254 let primary_usage = self.primary_cache.get_memory_usage();
255 let secondary_usage = self
256 .secondary_cache
257 .as_ref()
258 .map(|cache| cache.get_memory_usage())
259 .unwrap_or(0);
260 let compression_usage = self
261 .compression_manager
262 .as_ref()
263 .map(|comp| comp.get_memory_usage())
264 .unwrap_or(0);
265
266 primary_usage + secondary_usage + compression_usage
267 }
268
269 pub fn optimize_memory(&mut self) -> Result<OptimizationReport> {
271 let start_time = Instant::now();
272 let initial_usage = self.get_memory_usage();
273
274 let access_patterns = self.access_tracker.analyze_patterns();
276
277 self.reorganize_by_frequency(&access_patterns)?;
279
280 if let Some(ref mut compression) = self.compression_manager {
282 compression.optimize_compression_ratios(&access_patterns)?;
283 }
284
285 self.defragment_memory()?;
287
288 let final_usage = self.get_memory_usage();
289 let optimization_time = start_time.elapsed();
290
291 Ok(OptimizationReport {
292 initial_memory_usage: initial_usage,
293 final_memory_usage: final_usage,
294 memory_saved: initial_usage.saturating_sub(final_usage),
295 optimization_time,
296 operations_performed: access_patterns.total_accesses,
297 })
298 }
299
300 pub fn prefetch(&mut self, keys: Vec<String>) -> Result<usize> {
302 let mut prefetched_count = 0;
303
304 for key in keys {
305 if !self.primary_cache.contains(&key) {
306 if let Some(ref mut secondary) = self.secondary_cache {
308 if let Some(tensor) = secondary.retrieve(&key)? {
309 if self.primary_cache.store(key.clone(), tensor).is_ok() {
310 secondary.remove(&key)?;
311 prefetched_count += 1;
312 }
313 }
314 }
315
316 if let Some(ref mut compression) = self.compression_manager {
318 if let Some(compressed_data) = compression.retrieve(&key)? {
319 let decompressed = compression.decompress(&compressed_data)?;
320 if self.primary_cache.store(key.clone(), decompressed).is_ok() {
321 compression.remove(&key)?;
322 prefetched_count += 1;
323 }
324 }
325 }
326 }
327 }
328
329 Ok(prefetched_count)
330 }
331
332 fn store_lru(&mut self, key: String, tensor: Array2<T>) -> Result<()> {
334 self.primary_cache.store(key, tensor)
335 }
336
337 fn store_lfu(&mut self, key: String, tensor: Array2<T>) -> Result<()> {
338 self.primary_cache.store(key, tensor)
340 }
341
342 fn store_fifo(&mut self, key: String, tensor: Array2<T>) -> Result<()> {
343 self.primary_cache.store(key, tensor)
344 }
345
346 fn store_arc(&mut self, key: String, tensor: Array2<T>) -> Result<()> {
347 self.primary_cache.store(key, tensor)
349 }
350
351 fn store_compressed(&mut self, key: String, tensor: Array2<T>) -> Result<()> {
352 if let Some(ref mut compression) = self.compression_manager {
353 let compressed_data = compression.compress(&tensor)?;
354 compression.store(key, compressed_data)?;
355 Ok(())
356 } else {
357 self.primary_cache.store(key, tensor)
358 }
359 }
360
361 fn store_hierarchical(&mut self, key: String, tensor: Array2<T>) -> Result<()> {
362 let tensor_size = tensor.len() * std::mem::size_of::<T>();
363
364 if tensor_size < self.config.allocation_block_size {
365 self.primary_cache.store(key, tensor)
367 } else if let Some(ref mut secondary) = self.secondary_cache {
368 secondary.store(key, tensor)
370 } else {
371 self.primary_cache.store(key, tensor)
373 }
374 }
375
376 fn evict_memory(&mut self) -> Result<()> {
377 self.primary_cache.evict_lru()?;
379
380 if self.pressure_monitor.is_high_pressure() {
382 if let Some(ref mut secondary) = self.secondary_cache {
383 secondary.evict_lru()?;
384 }
385 }
386
387 Ok(())
388 }
389
390 fn reorganize_by_frequency(&mut self, patterns: &AccessPatterns) -> Result<()> {
391 let frequent_keys: Vec<String> = patterns
393 .frequency_map
394 .iter()
395 .filter(|(_, &count)| count as f64 > patterns.average_frequency)
396 .map(|(key, _)| key.clone())
397 .collect();
398
399 self.prefetch(frequent_keys)?;
400 Ok(())
401 }
402
403 fn defragment_memory(&mut self) -> Result<()> {
404 let primary_items = self.primary_cache.get_all_items()?;
406 self.primary_cache.clear()?;
407
408 for (key, tensor) in primary_items {
409 self.primary_cache.store(key, tensor)?;
410 }
411
412 Ok(())
413 }
414
415 pub fn get_statistics(&self) -> &MemoryStatistics {
417 &self.statistics
418 }
419
420 pub fn get_access_patterns(&self) -> AccessPatterns {
422 self.access_tracker.analyze_patterns()
423 }
424
425 pub fn set_strategy(&mut self, strategy: MemoryManagementStrategy) {
427 self.strategy = strategy;
428 }
429
430 pub fn get_memory_pressure(&self) -> f64 {
432 self.pressure_monitor.get_pressure_ratio()
433 }
434}
435
436pub struct MemoryCache<
438 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
439> {
440 storage: HashMap<String, CacheEntry<T>>,
442
443 access_order: VecDeque<String>,
445
446 access_frequency: HashMap<String, usize>,
448
449 max_size: usize,
451
452 current_size: usize,
454
455 eviction_strategy: CacheEvictionStrategy,
457
458 insertion_order: VecDeque<String>,
461}
462
463impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
464 MemoryCache<T>
465{
466 pub fn new(max_size: usize, eviction_strategy: CacheEvictionStrategy) -> Result<Self> {
467 Ok(Self {
468 storage: HashMap::new(),
469 access_order: VecDeque::new(),
470 access_frequency: HashMap::new(),
471 max_size,
472 current_size: 0,
473 eviction_strategy,
474 insertion_order: VecDeque::new(),
475 })
476 }
477
478 pub fn store(&mut self, key: String, tensor: Array2<T>) -> Result<()> {
489 let tensor_size = tensor.len() * std::mem::size_of::<T>();
490
491 if tensor_size > self.max_size {
492 return Err(crate::error::OptimError::Other(format!(
493 "Tensor of {tensor_size} bytes is too large for a cache of {} bytes",
494 self.max_size
495 )));
496 }
497
498 if let Some(old_entry) = self.storage.remove(&key) {
501 self.current_size -= old_entry.size;
502 self.remove_from_access_order(&key);
503 self.insertion_order.retain(|k| k != &key);
504 self.access_frequency.remove(&key);
505 }
506
507 while self.current_size + tensor_size > self.max_size && !self.storage.is_empty() {
509 self.evict_one()?;
510 }
511
512 let entry = CacheEntry {
514 tensor,
515 size: tensor_size,
516 access_time: Instant::now(),
517 access_count: 1,
518 };
519
520 self.storage.insert(key.clone(), entry);
521 self.current_size += tensor_size;
522 self.update_access_tracking(&key);
523
524 Ok(())
525 }
526
527 pub fn retrieve(&mut self, key: &str) -> Result<Option<Array2<T>>> {
528 let tensor_result = if let Some(entry) = self.storage.get_mut(key) {
529 entry.access_time = Instant::now();
530 entry.access_count += 1;
531 Some(entry.tensor.clone())
532 } else {
533 None
534 };
535
536 if tensor_result.is_some() {
538 self.update_access_tracking(key);
539 }
540
541 Ok(tensor_result)
542 }
543
544 pub fn remove(&mut self, key: &str) -> Result<bool> {
545 if let Some(entry) = self.storage.remove(key) {
546 self.current_size -= entry.size;
547 self.remove_from_access_order(key);
548 self.insertion_order.retain(|k| k != key);
549 self.access_frequency.remove(key);
550 Ok(true)
551 } else {
552 Ok(false)
553 }
554 }
555
556 pub fn contains(&self, key: &str) -> bool {
557 self.storage.contains_key(key)
558 }
559
560 pub fn clear(&mut self) -> Result<()> {
561 self.storage.clear();
562 self.access_order.clear();
563 self.insertion_order.clear();
564 self.access_frequency.clear();
565 self.current_size = 0;
566 Ok(())
567 }
568
569 pub fn get_memory_usage(&self) -> usize {
570 self.current_size
571 }
572
573 pub fn evict_lru(&mut self) -> Result<()> {
574 if let Some(oldest_key) = self.access_order.front().cloned() {
575 self.remove(&oldest_key)?;
576 }
577 Ok(())
578 }
579
580 fn evict_one(&mut self) -> Result<()> {
581 match self.eviction_strategy {
582 CacheEvictionStrategy::LRU => self.evict_lru(),
583 CacheEvictionStrategy::LFU => self.evict_lfu(),
584 CacheEvictionStrategy::FIFO => self.evict_fifo(),
585 CacheEvictionStrategy::Random => self.evict_random(),
586 }
587 }
588
589 fn evict_lfu(&mut self) -> Result<()> {
590 if let Some(lfu_key) = self
591 .access_frequency
592 .iter()
593 .min_by_key(|(_, &freq)| freq)
594 .map(|(key, _)| key.clone())
595 {
596 self.remove(&lfu_key)?;
597 }
598 Ok(())
599 }
600
601 fn evict_fifo(&mut self) -> Result<()> {
608 if let Some(first_key) = self.insertion_order.front().cloned() {
609 self.remove(&first_key)?;
610 }
611 Ok(())
612 }
613
614 fn evict_random(&mut self) -> Result<()> {
620 if self.storage.is_empty() {
621 return Ok(());
622 }
623 let keys: Vec<String> = self.storage.keys().cloned().collect();
624 let index = scirs2_core::random::thread_rng().gen_range(0..keys.len());
625 let victim = keys[index].clone();
626 self.remove(&victim)?;
627 Ok(())
628 }
629
630 fn update_access_tracking(&mut self, key: &str) {
631 self.remove_from_access_order(key);
633 self.access_order.push_back(key.to_string());
634
635 if !self.insertion_order.iter().any(|k| k == key) {
638 self.insertion_order.push_back(key.to_string());
639 }
640
641 *self.access_frequency.entry(key.to_string()).or_insert(0) += 1;
643 }
644
645 fn remove_from_access_order(&mut self, key: &str) {
646 self.access_order.retain(|k| k != key);
647 }
648
649 pub fn get_all_items(&self) -> Result<Vec<(String, Array2<T>)>> {
650 let items = self
651 .storage
652 .iter()
653 .map(|(key, entry)| (key.clone(), entry.tensor.clone()))
654 .collect();
655 Ok(items)
656 }
657}
658
659#[derive(Debug, Clone)]
661pub struct CacheEntry<
662 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
663> {
664 pub tensor: Array2<T>,
665 pub size: usize,
666 pub access_time: Instant,
667 pub access_count: usize,
668}
669
670pub struct CompressionManager<
672 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
673> {
674 compressed_storage: HashMap<String, CompressedData<T>>,
676
677 memory_usage: usize,
679
680 _phantom: std::marker::PhantomData<T>,
682}
683
684impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
685 CompressionManager<T>
686{
687 pub fn new() -> Result<Self> {
696 Ok(Self {
697 compressed_storage: HashMap::new(),
698 memory_usage: 0,
699 _phantom: std::marker::PhantomData,
700 })
701 }
702
703 pub fn compress(&self, tensor: &Array2<T>) -> Result<CompressedData<T>> {
723 if tensor.is_empty() {
724 return Err(crate::error::OptimError::InsufficientData(
725 "cannot compress an empty tensor".to_string(),
726 ));
727 }
728 let mut min = f64::INFINITY;
729 let mut max = f64::NEG_INFINITY;
730 for value in tensor.iter() {
731 let v = value.to_f64().unwrap_or(f64::NAN);
732 if !v.is_finite() {
733 return Err(crate::error::OptimError::ComputationError(
734 "cannot quantize a tensor containing a non-finite value".to_string(),
735 ));
736 }
737 if v < min {
738 min = v;
739 }
740 if v > max {
741 max = v;
742 }
743 }
744
745 let levels = 255.0_f64;
746 let step = if max > min { (max - min) / levels } else { 0.0 };
747 let codes: Vec<u8> = tensor
748 .iter()
749 .map(|value| {
750 let v = value.to_f64().unwrap_or(0.0);
751 if step <= 0.0 {
752 0u8
753 } else {
754 (((v - min) / step).round()).clamp(0.0, levels) as u8
755 }
756 })
757 .collect();
758
759 let shape = tensor.shape().to_vec();
760 let metadata_bytes =
761 2 * std::mem::size_of::<f64>() + shape.len() * std::mem::size_of::<usize>();
762 Ok(CompressedData::<T> {
763 shape,
764 codes,
765 quantization_min: min,
766 quantization_step: step,
767 original_size: tensor.len() * std::mem::size_of::<T>(),
768 compressed_size: codes_len_bytes(tensor.len()) + metadata_bytes,
769 _phantom: std::marker::PhantomData,
770 })
771 }
772
773 pub fn decompress(&self, compressed: &CompressedData<T>) -> Result<Array2<T>> {
779 if compressed.shape.len() != 2 {
780 return Err(crate::error::OptimError::InvalidConfig(format!(
781 "compressed payload has a {}-dimensional shape, expected 2",
782 compressed.shape.len()
783 )));
784 }
785 let expected = compressed.shape[0] * compressed.shape[1];
786 if compressed.codes.len() != expected {
787 return Err(crate::error::OptimError::ComputationError(format!(
788 "compressed payload holds {} codes but its shape implies {expected}",
789 compressed.codes.len()
790 )));
791 }
792 let values: Vec<T> = compressed
793 .codes
794 .iter()
795 .map(|&code| {
796 let v = compressed.quantization_min + code as f64 * compressed.quantization_step;
797 scirs2_core::numeric::NumCast::from(v).unwrap_or_else(|| T::zero())
798 })
799 .collect();
800 Array2::from_shape_vec((compressed.shape[0], compressed.shape[1]), values)
801 .map_err(|_| crate::error::OptimError::Other("Decompression failed".to_string()))
802 }
803
804 pub fn store(&mut self, key: String, compressed: CompressedData<T>) -> Result<()> {
805 self.memory_usage += compressed.compressed_size;
806 self.compressed_storage.insert(key, compressed);
807 Ok(())
808 }
809
810 pub fn retrieve(&self, key: &str) -> Result<Option<CompressedData<T>>> {
811 Ok(self.compressed_storage.get(key).cloned())
812 }
813
814 pub fn remove(&mut self, key: &str) -> Result<bool> {
815 if let Some(compressed) = self.compressed_storage.remove(key) {
816 self.memory_usage -= compressed.compressed_size;
817 Ok(true)
818 } else {
819 Ok(false)
820 }
821 }
822
823 pub fn clear(&mut self) -> Result<()> {
824 self.compressed_storage.clear();
825 self.memory_usage = 0;
826 Ok(())
827 }
828
829 pub fn get_memory_usage(&self) -> usize {
830 self.memory_usage
831 }
832
833 pub fn optimize_compression_ratios(&mut self, _patterns: &AccessPatterns) -> Result<()> {
834 Ok(())
837 }
838}
839
840fn codes_len_bytes(count: usize) -> usize {
842 count }
844
845#[derive(Debug, Clone)]
851pub struct CompressedData<
852 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
853> {
854 pub shape: Vec<usize>,
856 pub codes: Vec<u8>,
858 pub quantization_min: f64,
860 pub quantization_step: f64,
862 pub original_size: usize,
864 pub compressed_size: usize,
866 _phantom: std::marker::PhantomData<T>,
867}
868
869impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
870 CompressedData<T>
871{
872 pub fn compression_ratio(&self) -> f64 {
877 if self.original_size == 0 {
878 return 1.0;
879 }
880 self.compressed_size as f64 / self.original_size as f64
881 }
882
883 pub fn max_reconstruction_error(&self) -> f64 {
885 self.quantization_step / 2.0
886 }
887
888 pub fn element_count(&self) -> usize {
890 self.codes.len()
891 }
892}
893
894#[derive(Debug, Clone)]
896pub struct MemoryStatistics {
897 pub total_stores: usize,
899
900 pub total_retrievals: usize,
902
903 pub cache_hits: usize,
905
906 pub cache_misses: usize,
908
909 pub total_bytes_stored: usize,
911
912 pub average_storage_time: Duration,
914
915 pub average_retrieval_time: Duration,
917
918 pub pressure_events: usize,
920}
921
922impl Default for MemoryStatistics {
923 fn default() -> Self {
924 Self::new()
925 }
926}
927
928impl MemoryStatistics {
929 pub fn new() -> Self {
930 Self {
931 total_stores: 0,
932 total_retrievals: 0,
933 cache_hits: 0,
934 cache_misses: 0,
935 total_bytes_stored: 0,
936 average_storage_time: Duration::new(0, 0),
937 average_retrieval_time: Duration::new(0, 0),
938 pressure_events: 0,
939 }
940 }
941
942 pub fn record_storage(&mut self, bytes: usize, time: Duration) {
943 self.total_stores += 1;
944 self.total_bytes_stored += bytes;
945 self.average_storage_time = (self.average_storage_time * (self.total_stores - 1) as u32
946 + time)
947 / self.total_stores as u32;
948 }
949
950 pub fn record_retrieval(&mut self, time: Duration, hit: bool) {
951 self.total_retrievals += 1;
952 if hit {
953 self.cache_hits += 1;
954 } else {
955 self.cache_misses += 1;
956 }
957 self.average_retrieval_time =
958 (self.average_retrieval_time * (self.total_retrievals - 1) as u32 + time)
959 / self.total_retrievals as u32;
960 }
961
962 pub fn record_pressure_event(&mut self) {
963 self.pressure_events += 1;
964 }
965
966 pub fn get_hit_ratio(&self) -> f64 {
967 if self.total_retrievals > 0 {
968 self.cache_hits as f64 / self.total_retrievals as f64
969 } else {
970 0.0
971 }
972 }
973
974 pub fn reset(&mut self) {
975 *self = Self::new();
976 }
977}
978
979pub struct AccessTracker {
981 read_log: VecDeque<AccessEvent>,
983
984 write_log: VecDeque<AccessEvent>,
986
987 max_log_size: usize,
989}
990
991impl AccessTracker {
992 pub fn new(max_log_size: usize) -> Self {
993 Self {
994 read_log: VecDeque::new(),
995 write_log: VecDeque::new(),
996 max_log_size,
997 }
998 }
999
1000 pub fn record_read(&mut self, key: String) {
1001 self.read_log.push_back(AccessEvent {
1002 key,
1003 timestamp: Instant::now(),
1004 });
1005
1006 if self.read_log.len() > self.max_log_size {
1007 self.read_log.pop_front();
1008 }
1009 }
1010
1011 pub fn record_write(&mut self, key: String) {
1012 self.write_log.push_back(AccessEvent {
1013 key,
1014 timestamp: Instant::now(),
1015 });
1016
1017 if self.write_log.len() > self.max_log_size {
1018 self.write_log.pop_front();
1019 }
1020 }
1021
1022 pub fn record_removal(&mut self, _key: String) {
1023 }
1025
1026 pub fn analyze_patterns(&self) -> AccessPatterns {
1027 let mut frequency_map = HashMap::new();
1028
1029 for event in self.read_log.iter().chain(self.write_log.iter()) {
1031 *frequency_map.entry(event.key.clone()).or_insert(0) += 1;
1032 }
1033
1034 let total_accesses: usize = frequency_map.values().sum();
1035 let average_frequency = if frequency_map.is_empty() {
1036 0.0
1037 } else {
1038 total_accesses as f64 / frequency_map.len() as f64
1039 };
1040
1041 AccessPatterns {
1042 frequency_map,
1043 average_frequency,
1044 total_accesses,
1045 }
1046 }
1047
1048 pub fn clear(&mut self) {
1049 self.read_log.clear();
1050 self.write_log.clear();
1051 }
1052}
1053
1054#[derive(Debug, Clone)]
1056pub struct AccessEvent {
1057 pub key: String,
1058 pub timestamp: Instant,
1059}
1060
1061#[derive(Debug, Clone)]
1063pub struct AccessPatterns {
1064 pub frequency_map: HashMap<String, usize>,
1065 pub average_frequency: f64,
1066 pub total_accesses: usize,
1067}
1068
1069pub struct MemoryPressureMonitor {
1071 current_usage: usize,
1073
1074 max_memory: usize,
1076
1077 warning_threshold: f64,
1079 critical_threshold: f64,
1080
1081 pressure_history: VecDeque<f64>,
1083}
1084
1085impl Default for MemoryPressureMonitor {
1086 fn default() -> Self {
1087 Self::new()
1088 }
1089}
1090
1091impl MemoryPressureMonitor {
1092 pub fn new() -> Self {
1093 Self {
1094 current_usage: 0,
1095 max_memory: 1024 * 1024 * 1024, warning_threshold: 0.7,
1097 critical_threshold: 0.9,
1098 pressure_history: VecDeque::new(),
1099 }
1100 }
1101
1102 pub fn update(&mut self, current_usage: usize) {
1103 self.current_usage = current_usage;
1104 let pressure_ratio = self.get_pressure_ratio();
1105
1106 self.pressure_history.push_back(pressure_ratio);
1107 if self.pressure_history.len() > 100 {
1108 self.pressure_history.pop_front();
1109 }
1110 }
1111
1112 pub fn get_pressure_ratio(&self) -> f64 {
1113 if self.max_memory > 0 {
1114 self.current_usage as f64 / self.max_memory as f64
1115 } else {
1116 0.0
1117 }
1118 }
1119
1120 pub fn is_high_pressure(&self) -> bool {
1121 self.get_pressure_ratio() > self.critical_threshold
1122 }
1123
1124 pub fn is_warning_pressure(&self) -> bool {
1125 self.get_pressure_ratio() > self.warning_threshold
1126 }
1127
1128 pub fn reset(&mut self) {
1129 self.current_usage = 0;
1130 self.pressure_history.clear();
1131 }
1132}
1133
1134#[derive(Debug, Clone)]
1136pub struct OptimizationReport {
1137 pub initial_memory_usage: usize,
1138 pub final_memory_usage: usize,
1139 pub memory_saved: usize,
1140 pub optimization_time: Duration,
1141 pub operations_performed: usize,
1142}
1143
1144#[cfg(test)]
1145mod tests {
1146 use super::*;
1147
1148 #[test]
1149 fn test_memory_manager_creation() {
1150 let config = super::super::config::TransformerBasedOptimizerConfig::<f32>::default();
1151 let manager = TransformerMemoryManager::new(&config);
1152 assert!(manager.is_ok());
1153 }
1154
1155 #[test]
1156 fn test_memory_cache() {
1157 let cache = MemoryCache::<f32>::new(1024 * 1024, CacheEvictionStrategy::LRU);
1158 assert!(cache.is_ok());
1159
1160 let mut c = cache.expect("MemoryCache::new should succeed");
1161 let tensor = Array2::<f32>::ones((10, 10));
1162 assert!(c.store("test".to_string(), tensor).is_ok());
1163 assert!(c.contains("test"));
1164 }
1165
1166 #[test]
1169 fn an_oversized_tensor_is_rejected_without_evicting_anything() {
1170 let mut cache = MemoryCache::<f64>::new(4096, CacheEvictionStrategy::LRU).expect("cache");
1171 cache
1172 .store("keep_a".to_string(), Array2::<f64>::ones((4, 4)))
1173 .expect("store a");
1174 cache
1175 .store("keep_b".to_string(), Array2::<f64>::ones((4, 4)))
1176 .expect("store b");
1177 let before = cache.get_memory_usage();
1178 assert!(before > 0);
1179
1180 let huge = Array2::<f64>::ones((100, 100));
1182 assert!(cache.store("huge".to_string(), huge).is_err());
1183
1184 assert!(cache.contains("keep_a"), "keep_a was evicted for nothing");
1185 assert!(cache.contains("keep_b"), "keep_b was evicted for nothing");
1186 assert_eq!(
1187 cache.get_memory_usage(),
1188 before,
1189 "the failed store changed the cache size"
1190 );
1191 }
1192
1193 #[test]
1196 fn fifo_eviction_ignores_accesses_unlike_lru() {
1197 let build = |strategy: CacheEvictionStrategy| {
1199 let mut cache = MemoryCache::<f64>::new(300, strategy).expect("cache");
1200 cache
1201 .store("first".to_string(), Array2::<f64>::ones((4, 4)))
1202 .expect("store first");
1203 cache
1204 .store("second".to_string(), Array2::<f64>::ones((4, 4)))
1205 .expect("store second");
1206 assert!(cache.retrieve("first").expect("retrieve").is_some());
1208 cache
1209 .store("third".to_string(), Array2::<f64>::ones((4, 4)))
1210 .expect("store third");
1211 cache
1212 };
1213
1214 let lru = build(CacheEvictionStrategy::LRU);
1215 assert!(
1216 lru.contains("first"),
1217 "LRU must keep the recently accessed entry"
1218 );
1219 assert!(!lru.contains("second"), "LRU should drop 'second'");
1220
1221 let fifo = build(CacheEvictionStrategy::FIFO);
1222 assert!(
1223 !fifo.contains("first"),
1224 "FIFO must drop the first-inserted entry regardless of access"
1225 );
1226 assert!(fifo.contains("second"), "FIFO should keep 'second'");
1227 assert!(fifo.contains("third"));
1228 }
1229
1230 #[test]
1233 fn random_eviction_actually_varies() {
1234 let mut victims = std::collections::HashSet::new();
1235 for _ in 0..200 {
1236 let mut cache =
1237 MemoryCache::<f64>::new(300, CacheEvictionStrategy::Random).expect("cache");
1238 for key in ["a", "b"] {
1239 cache
1240 .store(key.to_string(), Array2::<f64>::ones((4, 4)))
1241 .expect("store");
1242 }
1243 cache
1244 .store("c".to_string(), Array2::<f64>::ones((4, 4)))
1245 .expect("store c");
1246 for key in ["a", "b"] {
1247 if !cache.contains(key) {
1248 victims.insert(key.to_string());
1249 }
1250 }
1251 }
1252 assert_eq!(
1253 victims.len(),
1254 2,
1255 "random eviction only ever chose {victims:?}"
1256 );
1257 }
1258
1259 #[test]
1263 fn compression_reports_a_real_measured_ratio() {
1264 let comp = CompressionManager::<f64>::new().expect("manager");
1265 let tensor = Array2::from_shape_fn((8, 8), |(i, j)| (i as f64) - 0.5 * (j as f64));
1266 let compressed = comp.compress(&tensor).expect("compress");
1267
1268 assert_eq!(compressed.element_count(), 64);
1269 assert_eq!(compressed.original_size, 64 * std::mem::size_of::<f64>());
1270 let expected = 64 + 2 * std::mem::size_of::<f64>() + 2 * std::mem::size_of::<usize>();
1272 assert_eq!(
1273 compressed.compressed_size, expected,
1274 "compressed_size is not the real payload size"
1275 );
1276 assert!(
1277 compressed.compressed_size < compressed.original_size,
1278 "the payload did not shrink: {} vs {}",
1279 compressed.compressed_size,
1280 compressed.original_size
1281 );
1282 let half = compressed.original_size / 2;
1284 assert_ne!(
1285 compressed.compressed_size, half,
1286 "compressed_size is still the fabricated original/2"
1287 );
1288
1289 let restored = comp.decompress(&compressed).expect("decompress");
1290 assert_eq!(restored.dim(), tensor.dim());
1291 let tolerance = compressed.max_reconstruction_error() + 1e-12;
1292 for (a, b) in tensor.iter().zip(restored.iter()) {
1293 assert!(
1294 (a - b).abs() <= tolerance,
1295 "reconstruction error {} exceeds the quantization bound {tolerance}",
1296 (a - b).abs()
1297 );
1298 }
1299 }
1300
1301 #[test]
1302 fn a_constant_tensor_round_trips_exactly() {
1303 let comp = CompressionManager::<f64>::new().expect("manager");
1304 let tensor = Array2::<f64>::from_elem((3, 4), 2.5);
1305 let compressed = comp.compress(&tensor).expect("compress");
1306 assert_eq!(compressed.quantization_step, 0.0);
1307 assert_eq!(compressed.max_reconstruction_error(), 0.0);
1308 let restored = comp.decompress(&compressed).expect("decompress");
1309 assert_eq!(restored, tensor);
1310 }
1311
1312 #[test]
1313 fn compression_rejects_degenerate_input() {
1314 let comp = CompressionManager::<f64>::new().expect("manager");
1315 assert!(comp.compress(&Array2::<f64>::zeros((0, 3))).is_err());
1316 let mut nan = Array2::<f64>::zeros((2, 2));
1317 nan[[0, 0]] = f64::NAN;
1318 assert!(comp.compress(&nan).is_err());
1319 }
1320
1321 #[test]
1322 fn test_compression_manager() {
1323 let compression = CompressionManager::<f32>::new();
1324 assert!(compression.is_ok());
1325
1326 let comp = compression.expect("CompressionManager::new should succeed");
1327 let tensor = Array2::<f32>::ones((5, 5));
1328 let compressed = comp.compress(&tensor);
1329 assert!(compressed.is_ok());
1330
1331 let decompressed = comp.decompress(&compressed.expect("decompress should succeed"));
1332 assert!(decompressed.is_ok());
1333 }
1334
1335 #[test]
1336 fn test_access_tracker() {
1337 let mut tracker = AccessTracker::new(100);
1338
1339 tracker.record_read("key1".to_string());
1340 tracker.record_write("key2".to_string());
1341
1342 let patterns = tracker.analyze_patterns();
1343 assert!(patterns.total_accesses > 0);
1344 }
1345
1346 #[test]
1347 fn test_memory_pressure_monitor() {
1348 let mut monitor = MemoryPressureMonitor::new();
1349
1350 monitor.update(500 * 1024 * 1024); assert!(!monitor.is_high_pressure());
1352
1353 monitor.update(950 * 1024 * 1024); assert!(monitor.is_high_pressure());
1355 }
1356}