1use serde::{Deserialize, Serialize};
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
94pub enum PruningStrategy {
95 Magnitude,
97 Gradient,
99 Random,
101 Structured,
103 L1Norm,
105 L2Norm,
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
111pub enum PruningSchedule {
112 OneShot,
114 Gradual {
116 steps: usize,
118 },
119 Iterative {
121 iterations: usize,
123 train_steps: usize,
125 },
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct PruningConfig {
131 pub strategy: PruningStrategy,
133 pub sparsity: f32,
135 pub schedule: PruningSchedule,
137 pub layers_to_prune: Vec<String>,
139 pub layers_to_skip: Vec<String>,
141 pub use_magnitude: bool,
143 pub update_masks: bool,
145}
146
147impl PruningConfig {
148 pub fn new() -> Self {
150 Self {
151 strategy: PruningStrategy::Magnitude,
152 sparsity: 0.5,
153 schedule: PruningSchedule::OneShot,
154 layers_to_prune: Vec::new(),
155 layers_to_skip: vec!["output".to_string()], use_magnitude: true,
157 update_masks: false,
158 }
159 }
160
161 pub fn with_strategy(mut self, strategy: PruningStrategy) -> Self {
163 self.strategy = strategy;
164 self
165 }
166
167 pub fn with_sparsity(mut self, sparsity: f32) -> Self {
169 self.sparsity = sparsity.clamp(0.0, 1.0);
170 self
171 }
172
173 pub fn with_schedule(mut self, schedule: PruningSchedule) -> Self {
175 self.schedule = schedule;
176 self
177 }
178
179 pub fn prune_layer(mut self, layer: String) -> Self {
181 self.layers_to_prune.push(layer);
182 self
183 }
184
185 pub fn skip_layer(mut self, layer: String) -> Self {
187 self.layers_to_skip.push(layer);
188 self
189 }
190}
191
192impl Default for PruningConfig {
193 fn default() -> Self {
194 Self::new()
195 }
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct PruningMask {
201 pub mask: Vec<bool>,
203 pub sparsity: f32,
205 pub layer_name: String,
207}
208
209impl PruningMask {
210 pub fn new(size: usize, layer_name: String) -> Self {
212 Self {
213 mask: vec![true; size], sparsity: 0.0,
215 layer_name,
216 }
217 }
218
219 pub fn apply_magnitude_pruning(&mut self, weights: &[f32], target_sparsity: f32) {
225 let mut magnitudes: Vec<(usize, f32)> = weights
227 .iter()
228 .enumerate()
229 .map(|(i, &w)| (i, w.abs()))
230 .collect();
231
232 magnitudes.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
234
235 let num_to_prune = (weights.len() as f32 * target_sparsity) as usize;
237
238 for i in 0..num_to_prune.min(magnitudes.len()) {
240 let idx = magnitudes[i].0;
241 self.mask[idx] = false;
242 }
243
244 self.update_sparsity();
246 }
247
248 pub fn apply_random_pruning(&mut self, target_sparsity: f32) {
250 let num_to_prune = (self.mask.len() as f32 * target_sparsity) as usize;
251
252 for _ in 0..num_to_prune {
253 let idx = fastrand::usize(..self.mask.len());
254 self.mask[idx] = false;
255 }
256
257 self.update_sparsity();
258 }
259
260 pub fn apply_to_weights(&self, weights: &mut [f32]) {
262 for (i, &keep) in self.mask.iter().enumerate() {
263 if !keep && i < weights.len() {
264 weights[i] = 0.0;
265 }
266 }
267 }
268
269 pub fn update_sparsity(&mut self) {
271 let pruned = self.mask.iter().filter(|&&x| !x).count();
272 self.sparsity = pruned as f32 / self.mask.len() as f32;
273 }
274
275 pub fn num_pruned(&self) -> usize {
277 self.mask.iter().filter(|&&x| !x).count()
278 }
279
280 pub fn num_kept(&self) -> usize {
282 self.mask.iter().filter(|&&x| x).count()
283 }
284}
285
286#[derive(Debug, Clone)]
288pub struct Pruner {
289 config: PruningConfig,
290}
291
292impl Pruner {
293 pub fn new(config: PruningConfig) -> Self {
295 Self { config }
296 }
297
298 pub fn generate_mask(&self, weights: &[f32], layer_name: String) -> PruningMask {
300 let mut mask = PruningMask::new(weights.len(), layer_name.clone());
301
302 if self.config.layers_to_skip.contains(&layer_name) {
304 return mask; }
306
307 match self.config.strategy {
309 PruningStrategy::Magnitude | PruningStrategy::L1Norm => {
310 mask.apply_magnitude_pruning(weights, self.config.sparsity);
311 }
312 PruningStrategy::Random => {
313 mask.apply_random_pruning(self.config.sparsity);
314 }
315 _ => {
316 mask.apply_magnitude_pruning(weights, self.config.sparsity);
318 }
319 }
320
321 mask
322 }
323
324 pub fn config(&self) -> &PruningConfig {
326 &self.config
327 }
328}
329
330#[derive(Debug, Clone, Serialize, Deserialize)]
332pub struct DistillationConfig {
333 pub temperature: f32,
335 pub alpha: f32,
338 pub use_soft_targets: bool,
340 pub use_feature_matching: bool,
342 pub feature_match_layers: Vec<(String, String)>,
344}
345
346impl DistillationConfig {
347 pub fn new() -> Self {
349 Self {
350 temperature: 3.0,
351 alpha: 0.7,
352 use_soft_targets: true,
353 use_feature_matching: false,
354 feature_match_layers: Vec::new(),
355 }
356 }
357
358 pub fn with_temperature(mut self, temperature: f32) -> Self {
360 self.temperature = temperature.max(1.0);
361 self
362 }
363
364 pub fn with_alpha(mut self, alpha: f32) -> Self {
366 self.alpha = alpha.clamp(0.0, 1.0);
367 self
368 }
369
370 pub fn with_feature_matching(mut self) -> Self {
372 self.use_feature_matching = true;
373 self
374 }
375
376 pub fn add_feature_match(mut self, teacher_layer: String, student_layer: String) -> Self {
378 self.feature_match_layers
379 .push((teacher_layer, student_layer));
380 self
381 }
382}
383
384impl Default for DistillationConfig {
385 fn default() -> Self {
386 Self::new()
387 }
388}
389
390#[derive(Debug, Clone, Serialize, Deserialize)]
392pub struct DistillationLoss {
393 pub distillation_loss: f32,
395 pub student_loss: f32,
397 pub feature_loss: Option<f32>,
399 pub total_loss: f32,
401}
402
403impl DistillationLoss {
404 pub fn new(distillation_loss: f32, student_loss: f32, alpha: f32) -> Self {
406 let total_loss = alpha * distillation_loss + (1.0 - alpha) * student_loss;
407
408 Self {
409 distillation_loss,
410 student_loss,
411 feature_loss: None,
412 total_loss,
413 }
414 }
415
416 pub fn with_feature_loss(mut self, feature_loss: f32, beta: f32) -> Self {
418 self.feature_loss = Some(feature_loss);
419 self.total_loss += beta * feature_loss;
420 self
421 }
422}
423
424#[derive(Debug, Clone, Serialize, Deserialize)]
426pub struct OptimizationStats {
427 pub original_params: usize,
429 pub optimized_params: usize,
431 pub sparsity: f32,
433 pub compression_ratio: f32,
435 pub speedup_estimate: f32,
437}
438
439impl OptimizationStats {
440 pub fn new(original_params: usize, optimized_params: usize) -> Self {
442 let sparsity = 1.0 - (optimized_params as f32 / original_params as f32);
443 let compression_ratio = original_params as f32 / optimized_params as f32;
444
445 let speedup_estimate = 1.0 + sparsity;
447
448 Self {
449 original_params,
450 optimized_params,
451 sparsity,
452 compression_ratio,
453 speedup_estimate,
454 }
455 }
456
457 pub fn param_reduction_percent(&self) -> f32 {
459 self.sparsity * 100.0
460 }
461}
462
463#[derive(Debug, Clone, Serialize, Deserialize)]
465pub struct FusionConfig {
466 pub fuse_conv_bn: bool,
468 pub fuse_conv_relu: bool,
470 pub fuse_linear_bias: bool,
472 pub fuse_consecutive: bool,
474}
475
476impl FusionConfig {
477 pub fn new() -> Self {
479 Self {
480 fuse_conv_bn: true,
481 fuse_conv_relu: true,
482 fuse_linear_bias: true,
483 fuse_consecutive: true,
484 }
485 }
486
487 pub fn all() -> Self {
489 Self::new()
490 }
491}
492
493impl Default for FusionConfig {
494 fn default() -> Self {
495 Self::new()
496 }
497}
498
499#[derive(Debug, Clone, Serialize, Deserialize)]
501pub struct FusedOperation {
502 pub name: String,
504 pub operations: Vec<String>,
506 pub speedup: f32,
508}
509
510impl FusedOperation {
511 pub fn new(name: String, operations: Vec<String>, speedup: f32) -> Self {
513 Self {
514 name,
515 operations,
516 speedup,
517 }
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524
525 #[test]
526 fn test_pruning_config() {
527 let config = PruningConfig::new()
528 .with_strategy(PruningStrategy::Magnitude)
529 .with_sparsity(0.7)
530 .skip_layer("output".to_string());
531
532 assert_eq!(config.strategy, PruningStrategy::Magnitude);
533 assert_eq!(config.sparsity, 0.7);
534 assert!(config.layers_to_skip.contains(&"output".to_string()));
535 }
536
537 #[test]
538 fn test_pruning_mask_creation() {
539 let mask = PruningMask::new(100, "layer1".to_string());
540
541 assert_eq!(mask.mask.len(), 100);
542 assert_eq!(mask.sparsity, 0.0); assert_eq!(mask.num_kept(), 100);
544 assert_eq!(mask.num_pruned(), 0);
545 }
546
547 #[test]
548 fn test_magnitude_pruning() {
549 let weights = vec![0.1, 0.5, 0.2, 0.8, 0.3];
550 let mut mask = PruningMask::new(weights.len(), "test".to_string());
551
552 mask.apply_magnitude_pruning(&weights, 0.4); assert_eq!(mask.num_pruned(), 2); assert!((mask.sparsity - 0.4).abs() < 0.1);
556 }
557
558 #[test]
559 fn test_random_pruning() {
560 let mut mask = PruningMask::new(100, "test".to_string());
561
562 mask.apply_random_pruning(0.5);
563
564 let pruned = mask.num_pruned();
567 assert!(
568 pruned >= 30 && pruned <= 60,
569 "Expected ~50 pruned, got {}",
570 pruned
571 );
572 assert!(mask.sparsity > 0.2 && mask.sparsity < 0.7);
573 }
574
575 #[test]
576 fn test_mask_application() {
577 let mut weights = vec![1.0, 2.0, 3.0, 4.0, 5.0];
578 let mut mask = PruningMask::new(weights.len(), "test".to_string());
579
580 mask.mask[0] = false;
582 mask.mask[1] = false;
583 mask.update_sparsity();
584
585 mask.apply_to_weights(&mut weights);
586
587 assert_eq!(weights[0], 0.0);
588 assert_eq!(weights[1], 0.0);
589 assert_eq!(weights[2], 3.0);
590 assert_eq!(weights[3], 4.0);
591 assert_eq!(weights[4], 5.0);
592 }
593
594 #[test]
595 fn test_pruner_generation() {
596 let config = PruningConfig::new().with_sparsity(0.5);
597 let pruner = Pruner::new(config);
598
599 let weights = vec![0.1, 0.5, 0.2, 0.8, 0.3, 0.9, 0.1, 0.4];
600 let mask = pruner.generate_mask(&weights, "layer1".to_string());
601
602 assert_eq!(mask.num_pruned(), 4); assert!((mask.sparsity - 0.5).abs() < 0.01);
604 }
605
606 #[test]
607 fn test_pruner_skip_layer() {
608 let config = PruningConfig::new()
609 .with_sparsity(0.5)
610 .skip_layer("output".to_string());
611
612 let pruner = Pruner::new(config);
613
614 let weights = vec![0.1, 0.5, 0.2, 0.8];
615 let mask = pruner.generate_mask(&weights, "output".to_string());
616
617 assert_eq!(mask.num_pruned(), 0); assert_eq!(mask.sparsity, 0.0);
619 }
620
621 #[test]
622 fn test_distillation_config() {
623 let config = DistillationConfig::new()
624 .with_temperature(4.0)
625 .with_alpha(0.8)
626 .with_feature_matching();
627
628 assert_eq!(config.temperature, 4.0);
629 assert_eq!(config.alpha, 0.8);
630 assert!(config.use_feature_matching);
631 }
632
633 #[test]
634 fn test_distillation_loss() {
635 let loss = DistillationLoss::new(0.5, 0.3, 0.7);
636
637 assert_eq!(loss.distillation_loss, 0.5);
638 assert_eq!(loss.student_loss, 0.3);
639 assert!((loss.total_loss - (0.7 * 0.5 + 0.3 * 0.3)).abs() < 0.001);
640 }
641
642 #[test]
643 fn test_distillation_loss_with_feature() {
644 let loss = DistillationLoss::new(0.5, 0.3, 0.7).with_feature_loss(0.2, 0.1);
645
646 assert!(loss.feature_loss.is_some());
647 assert_eq!(loss.feature_loss.unwrap(), 0.2);
648 assert!(loss.total_loss > 0.4); }
650
651 #[test]
652 fn test_optimization_stats() {
653 let stats = OptimizationStats::new(1000, 500);
654
655 assert_eq!(stats.original_params, 1000);
656 assert_eq!(stats.optimized_params, 500);
657 assert_eq!(stats.sparsity, 0.5);
658 assert_eq!(stats.compression_ratio, 2.0);
659 assert_eq!(stats.param_reduction_percent(), 50.0);
660 }
661
662 #[test]
663 fn test_fusion_config() {
664 let config = FusionConfig::new();
665
666 assert!(config.fuse_conv_bn);
667 assert!(config.fuse_conv_relu);
668 assert!(config.fuse_linear_bias);
669 }
670
671 #[test]
672 fn test_fused_operation() {
673 let fused = FusedOperation::new(
674 "conv_bn_relu".to_string(),
675 vec!["conv".to_string(), "bn".to_string(), "relu".to_string()],
676 1.5,
677 );
678
679 assert_eq!(fused.name, "conv_bn_relu");
680 assert_eq!(fused.operations.len(), 3);
681 assert_eq!(fused.speedup, 1.5);
682 }
683
684 #[test]
685 fn test_pruning_schedule() {
686 let one_shot = PruningSchedule::OneShot;
687 let gradual = PruningSchedule::Gradual { steps: 1000 };
688 let iterative = PruningSchedule::Iterative {
689 iterations: 5,
690 train_steps: 100,
691 };
692
693 assert_eq!(one_shot, PruningSchedule::OneShot);
694 assert_ne!(gradual, one_shot);
695 assert_ne!(iterative, gradual);
696 }
697}