1use crate::{OptimizerError, OptimizerResult};
2use std::collections::{BTreeMap, HashMap, HashSet};
3use std::sync::{Arc, RwLock};
4use torsh_core::error::TorshError;
5
6#[derive(Debug, Clone)]
8pub struct SparseUpdateConfig {
9 pub sparsity_threshold: f32,
11 pub coordinate_wise_sparsity: bool,
13 pub track_sparse_patterns: bool,
15 pub min_sparsity_ratio: f32,
17 pub use_compression: bool,
19 pub block_size: usize,
21 pub adaptive_threshold: bool,
23}
24
25impl Default for SparseUpdateConfig {
26 fn default() -> Self {
27 Self {
28 sparsity_threshold: 1e-8,
29 coordinate_wise_sparsity: true,
30 track_sparse_patterns: true,
31 min_sparsity_ratio: 0.1, use_compression: true,
33 block_size: 32,
34 adaptive_threshold: true,
35 }
36 }
37}
38
39#[derive(Debug, Clone)]
41pub struct SparseGradient {
42 pub values: Vec<f32>,
44 pub indices: Vec<usize>,
46 pub row_ptr: Vec<usize>,
48 pub shape: Vec<usize>,
50 pub total_elements: usize,
52 pub sparsity_ratio: f32,
54}
55
56impl SparseGradient {
57 pub fn from_dense(gradient: &[f32], shape: Vec<usize>, threshold: f32) -> Self {
59 let mut values = Vec::new();
60 let mut indices = Vec::new();
61
62 for (i, &val) in gradient.iter().enumerate() {
63 if val.abs() > threshold {
64 values.push(val);
65 indices.push(i);
66 }
67 }
68
69 let total_elements = gradient.len();
70 let sparsity_ratio = 1.0 - (values.len() as f32 / total_elements as f32);
71
72 let row_ptr = if shape.len() == 1 {
74 vec![0, values.len()]
75 } else {
76 Self::compute_row_pointers(&indices, &shape)
78 };
79
80 Self {
81 values,
82 indices,
83 row_ptr,
84 shape,
85 total_elements,
86 sparsity_ratio,
87 }
88 }
89
90 pub fn to_dense(&self) -> Vec<f32> {
92 let mut dense = vec![0.0; self.total_elements];
93
94 for (&idx, &val) in self.indices.iter().zip(self.values.iter()) {
95 dense[idx] = val;
96 }
97
98 dense
99 }
100
101 pub fn is_worth_sparse(&self, min_sparsity_ratio: f32) -> bool {
103 self.sparsity_ratio >= min_sparsity_ratio
104 }
105
106 pub fn memory_footprint(&self) -> usize {
108 self.values.len() * 4 + self.indices.len() * 8 + self.row_ptr.len() * 8 + self.shape.len() * 8 + 24 }
114
115 pub fn compression_ratio(&self) -> f32 {
117 let dense_size = self.total_elements * 4; let sparse_size = self.memory_footprint();
119 dense_size as f32 / sparse_size as f32
120 }
121
122 pub fn add(&mut self, other: &SparseGradient) -> Result<(), OptimizerError> {
124 if self.shape != other.shape {
125 return Err(OptimizerError::InvalidParameter(
126 "Cannot add sparse gradients with different shapes".to_string(),
127 ));
128 }
129
130 let mut dense_self = self.to_dense();
132 let dense_other = other.to_dense();
133
134 for (a, b) in dense_self.iter_mut().zip(dense_other.iter()) {
135 *a += b;
136 }
137
138 let threshold = self
140 .values
141 .iter()
142 .chain(other.values.iter())
143 .map(|&x| x.abs())
144 .fold(0.0f32, |acc, x| acc.max(x))
145 * 1e-6;
146
147 let result = Self::from_dense(&dense_self, self.shape.clone(), threshold);
148
149 self.values = result.values;
150 self.indices = result.indices;
151 self.row_ptr = result.row_ptr;
152 self.sparsity_ratio = result.sparsity_ratio;
153
154 Ok(())
155 }
156
157 pub fn scale(&mut self, factor: f32) {
159 for val in &mut self.values {
160 *val *= factor;
161 }
162 }
163
164 pub fn norm(&self) -> f32 {
166 self.values.iter().map(|&x| x * x).sum::<f32>().sqrt()
167 }
168
169 fn compute_row_pointers(indices: &[usize], shape: &[usize]) -> Vec<usize> {
172 if shape.len() != 2 {
173 return vec![0, indices.len()];
175 }
176
177 let rows = shape[0];
178 let cols = shape[1];
179 let mut row_ptr = vec![0; rows + 1];
180
181 for &idx in indices {
182 let row = idx / cols;
183 if row < rows {
184 row_ptr[row + 1] += 1;
185 }
186 }
187
188 for i in 1..row_ptr.len() {
190 row_ptr[i] += row_ptr[i - 1];
191 }
192
193 row_ptr
194 }
195}
196
197#[derive(Debug, Clone)]
199pub struct BlockSparseGradient {
200 pub blocks: Vec<Vec<f32>>,
202 pub block_indices: Vec<(usize, usize)>,
204 pub block_size: usize,
206 pub shape: Vec<usize>,
208 pub block_shape: Vec<usize>,
210 pub block_sparsity_ratio: f32,
212}
213
214impl BlockSparseGradient {
215 pub fn from_dense(
225 gradient: &[f32],
226 shape: Vec<usize>,
227 block_size: usize,
228 threshold: f32,
229 ) -> torsh_core::error::Result<Self> {
230 match shape.len() {
231 1 => Ok(Self::from_dense_1d(gradient, shape, block_size, threshold)),
232 2 => Ok(Self::from_dense_2d(gradient, shape, block_size, threshold)),
233 rank => Err(TorshError::UnsupportedOperation {
234 op: "block-sparse gradient representation".to_string(),
235 dtype: format!("rank-{rank} gradient (only rank 1 and 2 are supported)"),
236 }),
237 }
238 }
239
240 pub fn to_dense(&self) -> Vec<f32> {
242 let total_elements: usize = self.shape.iter().product();
243 let mut dense = vec![0.0; total_elements];
244
245 for (block, &(block_row, block_col)) in self.blocks.iter().zip(self.block_indices.iter()) {
246 if self.shape.len() == 1 {
247 let start_idx = block_row * self.block_size;
248 for (i, &val) in block.iter().enumerate() {
249 if start_idx + i < dense.len() {
250 dense[start_idx + i] = val;
251 }
252 }
253 } else if self.shape.len() == 2 {
254 let rows = self.shape[0];
255 let cols = self.shape[1];
256 let start_row = block_row * self.block_size;
257 let start_col = block_col * self.block_size;
258
259 for (i, &val) in block.iter().enumerate() {
260 let row = start_row + i / self.block_size;
261 let col = start_col + i % self.block_size;
262 if row < rows && col < cols {
263 dense[row * cols + col] = val;
264 }
265 }
266 }
267 }
268
269 dense
270 }
271
272 pub fn is_worth_block_sparse(&self, min_sparsity_ratio: f32) -> bool {
274 self.block_sparsity_ratio >= min_sparsity_ratio
275 }
276
277 fn from_dense_1d(
280 gradient: &[f32],
281 shape: Vec<usize>,
282 block_size: usize,
283 threshold: f32,
284 ) -> Self {
285 let total_elements = shape[0];
286 let num_blocks = (total_elements + block_size - 1) / block_size;
287
288 let mut blocks = Vec::new();
289 let mut block_indices = Vec::new();
290
291 for block_idx in 0..num_blocks {
292 let start_idx = block_idx * block_size;
293 let end_idx = (start_idx + block_size).min(total_elements);
294
295 let block_data: Vec<f32> = gradient[start_idx..end_idx].to_vec();
296
297 let block_norm = block_data.iter().map(|&x| x * x).sum::<f32>().sqrt();
299
300 if block_norm > threshold {
301 blocks.push(block_data);
302 block_indices.push((block_idx, 0));
303 }
304 }
305
306 let block_sparsity_ratio = 1.0 - (blocks.len() as f32 / num_blocks as f32);
307
308 Self {
309 blocks,
310 block_indices,
311 block_size,
312 shape,
313 block_shape: vec![num_blocks],
314 block_sparsity_ratio,
315 }
316 }
317
318 fn from_dense_2d(
319 gradient: &[f32],
320 shape: Vec<usize>,
321 block_size: usize,
322 threshold: f32,
323 ) -> Self {
324 let rows = shape[0];
325 let cols = shape[1];
326 let block_rows = (rows + block_size - 1) / block_size;
327 let block_cols = (cols + block_size - 1) / block_size;
328
329 let mut blocks = Vec::new();
330 let mut block_indices = Vec::new();
331
332 for block_row in 0..block_rows {
333 for block_col in 0..block_cols {
334 let mut block_data = Vec::new();
335
336 let start_row = block_row * block_size;
337 let end_row = (start_row + block_size).min(rows);
338 let start_col = block_col * block_size;
339 let end_col = (start_col + block_size).min(cols);
340
341 for row in start_row..end_row {
342 for col in start_col..end_col {
343 let idx = row * cols + col;
344 block_data.push(gradient[idx]);
345 }
346 }
347
348 let block_norm = block_data.iter().map(|&x| x * x).sum::<f32>().sqrt();
350
351 if block_norm > threshold {
352 blocks.push(block_data);
353 block_indices.push((block_row, block_col));
354 }
355 }
356 }
357
358 let total_blocks = block_rows * block_cols;
359 let block_sparsity_ratio = 1.0 - (blocks.len() as f32 / total_blocks as f32);
360
361 Self {
362 blocks,
363 block_indices,
364 block_size,
365 shape,
366 block_shape: vec![block_rows, block_cols],
367 block_sparsity_ratio,
368 }
369 }
370}
371
372#[derive(Debug, Clone)]
374pub struct SparsePatternTracker {
375 pub parameter_id: String,
377 pub pattern_history: VecDeque<HashSet<usize>>,
379 pub max_history: usize,
381 pub stable_sparse_indices: HashSet<usize>,
383 pub stable_dense_indices: HashSet<usize>,
385 pub stability_threshold: f32,
387}
388
389use std::collections::VecDeque;
390
391impl SparsePatternTracker {
392 pub fn new(parameter_id: String, max_history: usize, stability_threshold: f32) -> Self {
394 Self {
395 parameter_id,
396 pattern_history: VecDeque::with_capacity(max_history),
397 max_history,
398 stable_sparse_indices: HashSet::new(),
399 stable_dense_indices: HashSet::new(),
400 stability_threshold,
401 }
402 }
403
404 pub fn update(&mut self, sparse_gradient: &SparseGradient) {
406 let non_zero_indices: HashSet<usize> = sparse_gradient.indices.iter().cloned().collect();
407
408 self.pattern_history.push_back(non_zero_indices);
410
411 if self.pattern_history.len() > self.max_history {
413 self.pattern_history.pop_front();
414 }
415
416 if self.pattern_history.len() >= (self.max_history as f32 * 0.5) as usize {
418 self.update_stable_patterns(sparse_gradient.total_elements);
419 }
420 }
421
422 pub fn get_statistics(&self) -> SparsePatternStatistics {
424 let total_patterns = self.pattern_history.len();
425
426 let average_sparsity = if total_patterns > 0 {
427 let total_sparse: usize = self
428 .pattern_history
429 .iter()
430 .map(|pattern| pattern.len())
431 .sum();
432 total_sparse as f32 / total_patterns as f32
433 } else {
434 0.0
435 };
436
437 let pattern_stability = if total_patterns > 1 {
438 let mut stability_sum = 0.0;
440 for i in 1..total_patterns {
441 let prev = &self.pattern_history[i - 1];
442 let curr = &self.pattern_history[i];
443 let intersection = prev.intersection(curr).count();
444 let union = prev.union(curr).count();
445 stability_sum += intersection as f32 / union.max(1) as f32;
446 }
447 stability_sum / (total_patterns - 1) as f32
448 } else {
449 1.0
450 };
451
452 SparsePatternStatistics {
453 parameter_id: self.parameter_id.clone(),
454 average_sparsity,
455 pattern_stability,
456 stable_sparse_count: self.stable_sparse_indices.len(),
457 stable_dense_count: self.stable_dense_indices.len(),
458 total_patterns: total_patterns,
459 }
460 }
461
462 pub fn is_consistently_sparse(&self, index: usize) -> bool {
464 self.stable_sparse_indices.contains(&index)
465 }
466
467 pub fn is_consistently_dense(&self, index: usize) -> bool {
469 self.stable_dense_indices.contains(&index)
470 }
471
472 fn update_stable_patterns(&mut self, total_elements: usize) {
475 let history_len = self.pattern_history.len();
476 let required_count = (history_len as f32 * self.stability_threshold) as usize;
477
478 let mut index_counts: HashMap<usize, usize> = HashMap::new();
480 for pattern in &self.pattern_history {
481 for &idx in pattern {
482 *index_counts.entry(idx).or_insert(0) += 1;
483 }
484 }
485
486 self.stable_dense_indices.clear();
488 self.stable_sparse_indices.clear();
489
490 for idx in 0..total_elements {
491 let count = index_counts.get(&idx).copied().unwrap_or(0);
492
493 if count >= required_count {
494 self.stable_dense_indices.insert(idx);
495 } else if count == 0 && history_len >= required_count {
496 self.stable_sparse_indices.insert(idx);
497 }
498 }
499 }
500}
501
502#[derive(Debug, Clone)]
504pub struct SparsePatternStatistics {
505 pub parameter_id: String,
506 pub average_sparsity: f32,
507 pub pattern_stability: f32,
508 pub stable_sparse_count: usize,
509 pub stable_dense_count: usize,
510 pub total_patterns: usize,
511}
512
513impl std::fmt::Display for SparsePatternStatistics {
514 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
515 writeln!(f, "Sparse Pattern Statistics for {}:", self.parameter_id)?;
516 writeln!(
517 f,
518 " Average Sparsity: {:.1}%",
519 (1.0 - self.average_sparsity) * 100.0
520 )?;
521 writeln!(f, " Pattern Stability: {:.3}", self.pattern_stability)?;
522 writeln!(f, " Stable Sparse: {}", self.stable_sparse_count)?;
523 writeln!(f, " Stable Dense: {}", self.stable_dense_count)?;
524 writeln!(f, " Total Patterns: {}", self.total_patterns)?;
525 Ok(())
526 }
527}
528
529pub struct SparseUpdateManager {
531 config: SparseUpdateConfig,
532 pattern_trackers: HashMap<String, SparsePatternTracker>,
533 adaptive_thresholds: HashMap<String, f32>,
534 compression_stats: HashMap<String, CompressionStats>,
535}
536
537#[derive(Debug, Clone)]
538pub struct CompressionStats {
539 pub total_updates: usize,
540 pub sparse_updates: usize,
541 pub average_compression_ratio: f32,
542 pub memory_saved_bytes: usize,
543}
544
545impl SparseUpdateManager {
546 pub fn new(config: SparseUpdateConfig) -> Self {
548 Self {
549 config,
550 pattern_trackers: HashMap::new(),
551 adaptive_thresholds: HashMap::new(),
552 compression_stats: HashMap::new(),
553 }
554 }
555
556 pub fn process_gradient(
558 &mut self,
559 parameter_id: String,
560 gradient: Vec<f32>,
561 shape: Vec<usize>,
562 ) -> SparseUpdateResult {
563 let threshold = self.get_threshold(¶meter_id, &gradient);
564
565 let sparse_gradient = SparseGradient::from_dense(&gradient, shape.clone(), threshold);
567
568 if self.config.track_sparse_patterns {
570 let tracker = self
571 .pattern_trackers
572 .entry(parameter_id.clone())
573 .or_insert_with(|| SparsePatternTracker::new(parameter_id.clone(), 100, 0.8));
574 tracker.update(&sparse_gradient);
575 }
576
577 self.update_compression_stats(¶meter_id, &sparse_gradient, gradient.len());
579
580 if sparse_gradient.is_worth_sparse(self.config.min_sparsity_ratio) {
582 if self.config.use_compression {
583 SparseUpdateResult::Sparse(sparse_gradient)
584 } else {
585 SparseUpdateResult::Dense(gradient)
586 }
587 } else {
588 SparseUpdateResult::Dense(gradient)
589 }
590 }
591
592 pub fn process_gradient_block_sparse(
594 &mut self,
595 parameter_id: String,
596 gradient: Vec<f32>,
597 shape: Vec<usize>,
598 ) -> SparseUpdateResult {
599 let threshold = self.get_threshold(¶meter_id, &gradient);
600
601 let block_sparse = match BlockSparseGradient::from_dense(
604 &gradient,
605 shape,
606 self.config.block_size,
607 threshold,
608 ) {
609 Ok(block_sparse) => block_sparse,
610 Err(_) => return SparseUpdateResult::Dense(gradient),
611 };
612
613 if block_sparse.is_worth_block_sparse(self.config.min_sparsity_ratio) {
614 SparseUpdateResult::BlockSparse(block_sparse)
615 } else {
616 SparseUpdateResult::Dense(gradient)
617 }
618 }
619
620 pub fn get_parameter_statistics(&self, parameter_id: &str) -> Option<SparsePatternStatistics> {
622 self.pattern_trackers
623 .get(parameter_id)
624 .map(|tracker| tracker.get_statistics())
625 }
626
627 pub fn get_compression_statistics(&self) -> &HashMap<String, CompressionStats> {
629 &self.compression_stats
630 }
631
632 pub fn set_parameter_threshold(&mut self, parameter_id: String, threshold: f32) {
634 self.adaptive_thresholds.insert(parameter_id, threshold);
635 }
636
637 pub fn get_overall_statistics(&self) -> OverallSparseStatistics {
639 let total_parameters = self.compression_stats.len();
640
641 let total_updates: usize = self
642 .compression_stats
643 .values()
644 .map(|stats| stats.total_updates)
645 .sum();
646
647 let total_sparse_updates: usize = self
648 .compression_stats
649 .values()
650 .map(|stats| stats.sparse_updates)
651 .sum();
652
653 let average_compression_ratio = if total_parameters > 0 {
654 self.compression_stats
655 .values()
656 .map(|stats| stats.average_compression_ratio)
657 .sum::<f32>()
658 / total_parameters as f32
659 } else {
660 1.0
661 };
662
663 let total_memory_saved: usize = self
664 .compression_stats
665 .values()
666 .map(|stats| stats.memory_saved_bytes)
667 .sum();
668
669 let sparse_ratio = if total_updates > 0 {
670 total_sparse_updates as f32 / total_updates as f32
671 } else {
672 0.0
673 };
674
675 OverallSparseStatistics {
676 total_parameters,
677 total_updates,
678 total_sparse_updates,
679 sparse_ratio,
680 average_compression_ratio,
681 total_memory_saved,
682 }
683 }
684
685 fn get_threshold(&self, parameter_id: &str, gradient: &[f32]) -> f32 {
688 if let Some(&custom_threshold) = self.adaptive_thresholds.get(parameter_id) {
689 return custom_threshold;
690 }
691
692 if !self.config.adaptive_threshold {
693 return self.config.sparsity_threshold;
694 }
695
696 let gradient_magnitude = gradient.iter().map(|&x| x * x).sum::<f32>().sqrt();
698 let gradient_std = {
699 let mean = gradient.iter().sum::<f32>() / gradient.len() as f32;
700 let variance =
701 gradient.iter().map(|&x| (x - mean).powi(2)).sum::<f32>() / gradient.len() as f32;
702 variance.sqrt()
703 };
704
705 let adaptive_threshold = gradient_std * 0.1;
707 adaptive_threshold
708 .max(self.config.sparsity_threshold)
709 .min(gradient_magnitude * 0.01)
710 }
711
712 fn update_compression_stats(
713 &mut self,
714 parameter_id: &str,
715 sparse_gradient: &SparseGradient,
716 original_size: usize,
717 ) {
718 let stats = self
719 .compression_stats
720 .entry(parameter_id.to_string())
721 .or_insert_with(|| CompressionStats {
722 total_updates: 0,
723 sparse_updates: 0,
724 average_compression_ratio: 1.0,
725 memory_saved_bytes: 0,
726 });
727
728 stats.total_updates += 1;
729
730 if sparse_gradient.is_worth_sparse(self.config.min_sparsity_ratio) {
731 stats.sparse_updates += 1;
732
733 let compression_ratio = sparse_gradient.compression_ratio();
734 stats.average_compression_ratio = (stats.average_compression_ratio
735 * (stats.sparse_updates - 1) as f32
736 + compression_ratio)
737 / stats.sparse_updates as f32;
738
739 let original_bytes = original_size * 4; let compressed_bytes = sparse_gradient.memory_footprint();
741 stats.memory_saved_bytes += original_bytes.saturating_sub(compressed_bytes);
742 }
743 }
744}
745
746#[derive(Debug)]
748pub enum SparseUpdateResult {
749 Dense(Vec<f32>),
751 Sparse(SparseGradient),
753 BlockSparse(BlockSparseGradient),
755}
756
757#[derive(Debug, Clone)]
759pub struct OverallSparseStatistics {
760 pub total_parameters: usize,
761 pub total_updates: usize,
762 pub total_sparse_updates: usize,
763 pub sparse_ratio: f32,
764 pub average_compression_ratio: f32,
765 pub total_memory_saved: usize,
766}
767
768impl std::fmt::Display for OverallSparseStatistics {
769 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
770 writeln!(f, "Overall Sparse Update Statistics:")?;
771 writeln!(f, " Total Parameters: {}", self.total_parameters)?;
772 writeln!(f, " Total Updates: {}", self.total_updates)?;
773 writeln!(f, " Sparse Updates: {}", self.total_sparse_updates)?;
774 writeln!(f, " Sparse Ratio: {:.1}%", self.sparse_ratio * 100.0)?;
775 writeln!(
776 f,
777 " Average Compression: {:.2}x",
778 self.average_compression_ratio
779 )?;
780 writeln!(
781 f,
782 " Memory Saved: {:.2} MB",
783 self.total_memory_saved as f64 / 1024.0 / 1024.0
784 )?;
785 Ok(())
786 }
787}
788
789pub trait SparseUpdateSupport {
791 fn apply_sparse_update(
793 &mut self,
794 parameter_id: &str,
795 sparse_gradient: &SparseGradient,
796 ) -> Result<(), OptimizerError>;
797
798 fn apply_block_sparse_update(
800 &mut self,
801 parameter_id: &str,
802 block_sparse: &BlockSparseGradient,
803 ) -> Result<(), OptimizerError>;
804
805 fn apply_dense_update(
807 &mut self,
808 parameter_id: &str,
809 gradient: &[f32],
810 ) -> Result<(), OptimizerError>;
811
812 fn get_parameter_shape(&self, parameter_id: &str) -> Option<Vec<usize>>;
814}
815
816pub struct SparseUpdateOptimizer<T> {
818 inner: T,
819 sparse_manager: SparseUpdateManager,
820 enabled: bool,
821}
822
823impl<T> SparseUpdateOptimizer<T>
824where
825 T: SparseUpdateSupport,
826{
827 pub fn new(inner: T, config: SparseUpdateConfig) -> Self {
829 Self {
830 inner,
831 sparse_manager: SparseUpdateManager::new(config),
832 enabled: true,
833 }
834 }
835
836 pub fn set_enabled(&mut self, enabled: bool) {
838 self.enabled = enabled;
839 }
840
841 pub fn inner(&self) -> &T {
843 &self.inner
844 }
845
846 pub fn inner_mut(&mut self) -> &mut T {
848 &mut self.inner
849 }
850
851 pub fn sparse_manager(&self) -> &SparseUpdateManager {
853 &self.sparse_manager
854 }
855
856 pub fn submit_gradients(
858 &mut self,
859 gradients: HashMap<String, Vec<f32>>,
860 ) -> Result<(), OptimizerError> {
861 for (parameter_id, gradient) in gradients {
862 if !self.enabled {
863 self.inner.apply_dense_update(¶meter_id, &gradient)?;
864 continue;
865 }
866
867 let shape = self
869 .inner
870 .get_parameter_shape(¶meter_id)
871 .unwrap_or_else(|| vec![gradient.len()]);
872
873 match self
875 .sparse_manager
876 .process_gradient(parameter_id.clone(), gradient, shape)
877 {
878 SparseUpdateResult::Dense(dense_gradient) => {
879 self.inner
880 .apply_dense_update(¶meter_id, &dense_gradient)?;
881 }
882 SparseUpdateResult::Sparse(sparse_gradient) => {
883 self.inner
884 .apply_sparse_update(¶meter_id, &sparse_gradient)?;
885 }
886 SparseUpdateResult::BlockSparse(block_sparse) => {
887 self.inner
888 .apply_block_sparse_update(¶meter_id, &block_sparse)?;
889 }
890 }
891 }
892
893 Ok(())
894 }
895
896 pub fn get_parameter_statistics(&self, parameter_id: &str) -> Option<SparsePatternStatistics> {
898 self.sparse_manager.get_parameter_statistics(parameter_id)
899 }
900
901 pub fn get_statistics(&self) -> OverallSparseStatistics {
903 self.sparse_manager.get_overall_statistics()
904 }
905}
906
907#[cfg(test)]
908mod tests {
909 use super::*;
910
911 #[test]
912 fn test_sparse_gradient() {
913 let dense = vec![0.0, 1.5, 0.0, 0.0, 2.3, 0.0, -1.1, 0.0];
914 let shape = vec![8];
915 let threshold = 0.1;
916
917 let sparse = SparseGradient::from_dense(&dense, shape, threshold);
918
919 assert_eq!(sparse.values, vec![1.5, 2.3, -1.1]);
920 assert_eq!(sparse.indices, vec![1, 4, 6]);
921 assert!(sparse.sparsity_ratio > 0.5);
922
923 let recovered = sparse.to_dense();
924 assert_eq!(recovered, dense);
925 }
926
927 #[test]
928 fn test_block_sparse_gradient() {
929 let dense = vec![
930 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.5, 1.5, 0.0, 0.0, 0.5, 0.8,
931 ];
932 let shape = vec![4, 4];
933 let block_size = 2;
934 let threshold = 0.1;
935
936 let block_sparse = BlockSparseGradient::from_dense(&dense, shape, block_size, threshold)
937 .expect("rank-2 gradients are supported");
938
939 assert_eq!(block_sparse.blocks.len(), 2);
941 assert!(block_sparse.block_sparsity_ratio > 0.0);
942
943 let recovered = block_sparse.to_dense();
944 assert!((recovered[0] - 1.0).abs() < 1e-6);
946 assert!((recovered[10] - 2.5).abs() < 1e-6);
947 }
948
949 #[test]
950 fn test_sparse_pattern_tracker() {
951 let mut tracker = SparsePatternTracker::new("test_param".to_string(), 5, 0.8);
952
953 for _ in 0..10 {
955 let dense = vec![1.0, 0.0, 2.0, 0.0, 0.0];
956 let sparse = SparseGradient::from_dense(&dense, vec![5], 0.1);
957 tracker.update(&sparse);
958 }
959
960 let stats = tracker.get_statistics();
961 assert!(stats.pattern_stability > 0.5);
962 assert_eq!(stats.stable_dense_count, 2); assert_eq!(stats.stable_sparse_count, 3); }
965
966 #[derive(Debug)]
967 struct MockOptimizer {
968 updates_received: HashMap<String, usize>,
969 }
970
971 impl MockOptimizer {
972 fn new() -> Self {
973 Self {
974 updates_received: HashMap::new(),
975 }
976 }
977 }
978
979 impl SparseUpdateSupport for MockOptimizer {
980 fn apply_sparse_update(
981 &mut self,
982 parameter_id: &str,
983 _sparse_gradient: &SparseGradient,
984 ) -> Result<(), OptimizerError> {
985 *self
986 .updates_received
987 .entry(parameter_id.to_string())
988 .or_insert(0) += 1;
989 Ok(())
990 }
991
992 fn apply_block_sparse_update(
993 &mut self,
994 parameter_id: &str,
995 _block_sparse: &BlockSparseGradient,
996 ) -> Result<(), OptimizerError> {
997 *self
998 .updates_received
999 .entry(parameter_id.to_string())
1000 .or_insert(0) += 1;
1001 Ok(())
1002 }
1003
1004 fn apply_dense_update(
1005 &mut self,
1006 parameter_id: &str,
1007 _gradient: &[f32],
1008 ) -> Result<(), OptimizerError> {
1009 *self
1010 .updates_received
1011 .entry(parameter_id.to_string())
1012 .or_insert(0) += 1;
1013 Ok(())
1014 }
1015
1016 fn get_parameter_shape(&self, _parameter_id: &str) -> Option<Vec<usize>> {
1017 Some(vec![8])
1018 }
1019 }
1020
1021 #[test]
1022 fn test_sparse_update_optimizer() {
1023 let config = SparseUpdateConfig {
1024 sparsity_threshold: 0.1,
1025 min_sparsity_ratio: 0.3,
1026 ..Default::default()
1027 };
1028
1029 let optimizer = MockOptimizer::new();
1030 let mut sparse_optimizer = SparseUpdateOptimizer::new(optimizer, config);
1031
1032 let mut gradients = HashMap::new();
1034 gradients.insert(
1035 "param1".to_string(),
1036 vec![0.0, 1.5, 0.0, 0.0, 2.3, 0.0, -1.1, 0.0],
1037 ); gradients.insert(
1039 "param2".to_string(),
1040 vec![1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7],
1041 ); sparse_optimizer.submit_gradients(gradients).unwrap();
1044
1045 assert_eq!(
1047 sparse_optimizer.inner().updates_received.get("param1"),
1048 Some(&1)
1049 );
1050 assert_eq!(
1051 sparse_optimizer.inner().updates_received.get("param2"),
1052 Some(&1)
1053 );
1054
1055 let stats = sparse_optimizer.get_statistics();
1057 assert_eq!(stats.total_parameters, 2);
1058 assert!(stats.total_updates > 0);
1059 }
1060}