1#![allow(dead_code)]
10
11use super::anomaly_detection::*;
12use super::conflict_analysis::*;
13use super::enhanced_analysis::*;
14use super::monitoring::*;
15use super::performance_tracking::*;
16use super::types::*;
17use super::visualization::*;
18use crate::DebugConfig;
19use anyhow::Result;
20use serde::{Deserialize, Serialize};
21use std::collections::HashMap;
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct FlowAnalysis {
26 pub layer_analyses: HashMap<String, LayerFlowAnalysis>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct LayerFlowAnalysis {
32 pub layer_name: String,
33 pub is_vanishing: bool,
34 pub is_exploding: bool,
35 pub gradient_norm: f64,
36 pub flow_consistency: f64,
37}
38
39#[derive(Debug)]
41pub struct GradientDebugger {
42 config: DebugConfig,
43 gradient_config: GradientDebugConfig,
44 gradient_histories: HashMap<String, GradientHistory>,
45 current_step: usize,
46 alerts: Vec<GradientAlert>,
47 layer_no_gradient_count: HashMap<String, usize>,
48
49 adaptive_thresholds: HashMap<String, AdaptiveThresholds>,
51 real_time_monitors: HashMap<String, RealTimeGradientMonitor>,
52 anomaly_detector: GradientAnomalyDetector,
53 performance_tracker: GradientPerformanceTracker,
54 conflict_analyzer: GradientConflictAnalyzer,
55 flow_visualizer: GradientFlowVisualizer,
56 enhanced_analyzer: EnhancedGradientAnalyzer,
57}
58
59impl GradientDebugger {
60 pub fn new(config: DebugConfig) -> Self {
62 let gradient_config = GradientDebugConfig::default();
63
64 Self {
65 config,
66 gradient_config: gradient_config.clone(),
67 gradient_histories: HashMap::new(),
68 current_step: 0,
69 alerts: Vec::new(),
70 layer_no_gradient_count: HashMap::new(),
71 adaptive_thresholds: HashMap::new(),
72 real_time_monitors: HashMap::new(),
73 anomaly_detector: GradientAnomalyDetector::default(),
74 performance_tracker: GradientPerformanceTracker::default(),
75 conflict_analyzer: GradientConflictAnalyzer::default(),
76 flow_visualizer: GradientFlowVisualizer::default(),
77 enhanced_analyzer: EnhancedGradientAnalyzer::default(),
78 }
79 }
80
81 pub fn with_gradient_config(config: DebugConfig, gradient_config: GradientDebugConfig) -> Self {
83 Self {
84 config,
85 gradient_config: gradient_config.clone(),
86 gradient_histories: HashMap::new(),
87 current_step: 0,
88 alerts: Vec::new(),
89 layer_no_gradient_count: HashMap::new(),
90 adaptive_thresholds: HashMap::new(),
91 real_time_monitors: HashMap::new(),
92 anomaly_detector: GradientAnomalyDetector::default(),
93 performance_tracker: GradientPerformanceTracker::default(),
94 conflict_analyzer: GradientConflictAnalyzer::default(),
95 flow_visualizer: GradientFlowVisualizer::default(),
96 enhanced_analyzer: EnhancedGradientAnalyzer::default(),
97 }
98 }
99
100 pub fn set_layer_parameter_count(&mut self, layer_name: &str, count: usize) {
109 self.gradient_histories
110 .entry(layer_name.to_string())
111 .or_insert_with(|| GradientHistory::new(layer_name.to_string(), 1000))
112 .parameter_count = Some(count);
113 }
114
115 pub fn record_gradient_flow(
123 &mut self,
124 layer_name: &str,
125 gradient_norm: f64,
126 gradient_mean: f64,
127 gradient_std: f64,
128 ) -> Result<()> {
129 self.record_flow(GradientFlow {
130 layer_name: layer_name.to_string(),
131 step: self.current_step,
132 gradient_norm,
133 gradient_mean,
134 gradient_std,
135 gradient_max: None,
136 gradient_min: None,
137 dead_neurons_ratio: None,
138 active_neurons_ratio: None,
139 timestamp: chrono::Utc::now(),
140 })
141 }
142
143 pub fn record_gradient_values(&mut self, layer_name: &str, gradients: &[f64]) -> Result<()> {
155 if gradients.is_empty() {
156 anyhow::bail!(
157 "record_gradient_values({layer_name:?}): empty gradient slice -- there is \
158 nothing to measure"
159 );
160 }
161 let n = gradients.len() as f64;
162 let gradient_mean = gradients.iter().sum::<f64>() / n;
163 let variance = gradients.iter().map(|g| (g - gradient_mean).powi(2)).sum::<f64>() / n;
164 let gradient_norm = gradients.iter().map(|g| g * g).sum::<f64>().sqrt();
165 let gradient_max = gradients.iter().copied().fold(f64::NEG_INFINITY, f64::max);
166 let gradient_min = gradients.iter().copied().fold(f64::INFINITY, f64::min);
167 let dead = gradients
168 .iter()
169 .filter(|g| g.abs() <= self.gradient_config.dead_gradient_magnitude)
170 .count();
171 let dead_neurons_ratio = dead as f64 / n;
172
173 self.set_layer_parameter_count(layer_name, gradients.len());
174 self.record_flow(GradientFlow {
175 layer_name: layer_name.to_string(),
176 step: self.current_step,
177 gradient_norm,
178 gradient_mean,
179 gradient_std: variance.sqrt(),
180 gradient_max: Some(gradient_max),
181 gradient_min: Some(gradient_min),
182 dead_neurons_ratio: Some(dead_neurons_ratio),
183 active_neurons_ratio: Some(1.0 - dead_neurons_ratio),
184 timestamp: chrono::Utc::now(),
185 })
186 }
187
188 fn record_flow(&mut self, flow: GradientFlow) -> Result<()> {
190 let timer = self.performance_tracker.start_timing(&flow.layer_name);
194 let layer_name = flow.layer_name.clone();
195 let layer_name = layer_name.as_str();
196 let gradient_norm = flow.gradient_norm;
197
198 {
200 let history = self
201 .gradient_histories
202 .entry(layer_name.to_string())
203 .or_insert_with(|| GradientHistory::new(layer_name.to_string(), 1000));
204 history.add_gradient_flow(&flow);
205 }
206
207 let thresholds =
209 self.adaptive_thresholds.entry(layer_name.to_string()).or_insert_with(|| {
210 AdaptiveThresholds::new(
211 layer_name.to_string(),
212 self.gradient_config.vanishing_threshold,
213 self.gradient_config.exploding_threshold,
214 )
215 });
216 thresholds.update_thresholds(gradient_norm);
217
218 let monitor = self
220 .real_time_monitors
221 .entry(layer_name.to_string())
222 .or_insert_with(|| RealTimeGradientMonitor::new(layer_name.to_string()));
223 monitor.update(gradient_norm);
224
225 self.check_gradient_alerts(layer_name, &flow)?;
227
228 let (_, bookkeeping_time) = timer.finish();
236 self.performance_tracker
237 .record_layer_performance(layer_name, bookkeeping_time, None);
238
239 let anomalies =
241 self.anomaly_detector
242 .detect_anomalies(layer_name, gradient_norm, self.current_step);
243 for anomaly in anomalies {
244 self.alerts.push(GradientAlert::GradientOscillation {
245 layer_name: anomaly.layer_name,
246 variance: anomaly.severity,
247 });
248 }
249
250 if let Some(history) = self.gradient_histories.get(layer_name) {
252 if history.gradient_norms.len() == 50 {
253 let gradient_values: Vec<f64> = history.gradient_norms.iter().cloned().collect();
254 self.anomaly_detector.establish_baseline(layer_name, &gradient_values);
255 }
256 }
257
258 Ok(())
259 }
260
261 pub fn get_status(&self) -> GradientDebugStatus {
263 let layer_statuses: HashMap<String, LayerGradientStatus> = self
264 .gradient_histories
265 .iter()
266 .map(|(layer_name, history)| {
267 let status = self.compute_layer_status(layer_name, history);
268 (layer_name.clone(), status)
269 })
270 .collect();
271
272 let overall_health = self.compute_overall_health(&layer_statuses);
273 let recent_alerts: Vec<GradientAlert> =
274 self.alerts.iter().rev().take(10).cloned().collect();
275
276 GradientDebugStatus {
277 current_step: self.current_step,
278 overall_health,
279 layer_statuses,
280 recent_alerts,
281 total_alerts: self.alerts.len(),
282 active_layers: self.gradient_histories.len(),
283 }
284 }
285
286 fn generate_flow_analysis(&self) -> FlowAnalysis {
288 let mut layer_analyses = HashMap::new();
289
290 for (layer_name, history) in &self.gradient_histories {
291 let latest_gradient = history.gradient_norms.back().cloned().unwrap_or(0.0);
292
293 let is_vanishing = latest_gradient < 1e-8
295 || (history.gradient_norms.len() > 5
296 && history.gradient_norms.iter().rev().take(5).all(|&g| g < 1e-6));
297
298 let is_exploding = latest_gradient > 100.0
299 || (history.gradient_norms.len() > 3
300 && history.gradient_norms.iter().rev().take(3).any(|&g| g > 50.0));
301
302 let flow_consistency = if history.gradient_norms.len() > 1 {
304 let mean = history.gradient_norms.iter().sum::<f64>()
305 / history.gradient_norms.len() as f64;
306 let variance =
307 history.gradient_norms.iter().map(|&x| (x - mean).powi(2)).sum::<f64>()
308 / history.gradient_norms.len() as f64;
309 1.0 / (1.0 + variance) } else {
311 1.0
312 };
313
314 layer_analyses.insert(
315 layer_name.clone(),
316 LayerFlowAnalysis {
317 layer_name: layer_name.clone(),
318 is_vanishing,
319 is_exploding,
320 gradient_norm: latest_gradient,
321 flow_consistency,
322 },
323 );
324 }
325
326 FlowAnalysis { layer_analyses }
327 }
328
329 pub fn generate_comprehensive_report(&self) -> Result<ComprehensiveGradientReport> {
331 let status = self.get_status();
332 let conflict_analysis = self.conflict_analyzer.analyze_conflicts(&self.gradient_histories);
333 let visualization = self
334 .flow_visualizer
335 .generate_visualization(&self.gradient_histories, self.current_step);
336 let enhanced_analysis =
337 self.enhanced_analyzer.generate_enhanced_analysis(&self.gradient_histories);
338 let performance_snapshot = self.performance_tracker.take_performance_snapshot();
339 let anomaly_summary = self.anomaly_detector.get_anomaly_summary(None);
340
341 let flow_analysis = self.generate_flow_analysis();
342
343 Ok(ComprehensiveGradientReport {
344 timestamp: chrono::Utc::now(),
345 status,
346 conflict_analysis,
347 visualization,
348 enhanced_analysis,
349 flow_analysis,
350 performance_snapshot,
351 anomaly_summary,
352 recommendations: self.generate_comprehensive_recommendations()?,
353 })
354 }
355
356 pub fn analyze_gradient_conflicts(&self) -> GradientConflictAnalysis {
358 self.conflict_analyzer.analyze_conflicts(&self.gradient_histories)
359 }
360
361 pub fn generate_gradient_flow_visualization(&self) -> GradientFlowVisualization {
363 self.flow_visualizer
364 .generate_visualization(&self.gradient_histories, self.current_step)
365 }
366
367 pub fn generate_enhanced_layer_analysis(&self) -> EnhancedLayerGradientAnalysis {
369 self.enhanced_analyzer.generate_enhanced_analysis(&self.gradient_histories)
370 }
371
372 pub fn get_performance_insights(&self) -> PerformanceInsights {
374 let trends = self.performance_tracker.get_performance_trends();
375 let recommendations = self.performance_tracker.generate_optimization_recommendations();
376 let bottlenecks = self.performance_tracker.bottleneck_layers.clone();
377
378 PerformanceInsights {
379 trends,
380 recommendations,
381 bottlenecks,
382 current_throughput: self.performance_tracker.throughput_gradients_per_second,
383 memory_usage: self.performance_tracker.memory_usage_bytes,
384 }
385 }
386
387 pub fn next_step(&mut self) {
389 self.current_step += 1;
390
391 if self.alerts.len() > 100 {
393 self.alerts.drain(0..self.alerts.len() - 100);
394 }
395
396 for (layer_name, history) in &self.gradient_histories {
398 if let Some(latest_norm) = history.gradient_norms.back() {
399 if *latest_norm < 1e-8 {
400 *self.layer_no_gradient_count.entry(layer_name.clone()).or_insert(0) += 1;
401 } else {
402 self.layer_no_gradient_count.insert(layer_name.clone(), 0);
403 }
404 }
405 }
406
407 for (layer_name, &count) in &self.layer_no_gradient_count {
409 if count >= self.gradient_config.no_gradient_steps_threshold {
410 self.alerts.push(GradientAlert::NoGradientFlow {
411 layer_name: layer_name.clone(),
412 steps_without_gradient: count,
413 });
414 }
415 }
416 }
417
418 pub fn reset(&mut self) {
420 self.gradient_histories.clear();
421 self.current_step = 0;
422 self.alerts.clear();
423 self.layer_no_gradient_count.clear();
424 self.adaptive_thresholds.clear();
425 self.real_time_monitors.clear();
426 self.anomaly_detector = GradientAnomalyDetector::default();
427 self.performance_tracker = GradientPerformanceTracker::default();
428 }
429
430 pub fn get_layer_alerts(&self, layer_name: &str) -> Vec<&GradientAlert> {
432 self.alerts
433 .iter()
434 .filter(|alert| match alert {
435 GradientAlert::VanishingGradients {
436 layer_name: name, ..
437 } => name == layer_name,
438 GradientAlert::ExplodingGradients {
439 layer_name: name, ..
440 } => name == layer_name,
441 GradientAlert::DeadNeurons {
442 layer_name: name, ..
443 } => name == layer_name,
444 GradientAlert::GradientOscillation {
445 layer_name: name, ..
446 } => name == layer_name,
447 GradientAlert::NoGradientFlow {
448 layer_name: name, ..
449 } => name == layer_name,
450 })
451 .collect()
452 }
453
454 pub fn get_layer_history(&self, layer_name: &str) -> Option<&GradientHistory> {
456 self.gradient_histories.get(layer_name)
457 }
458
459 pub fn get_monitored_layers(&self) -> Vec<&String> {
461 self.gradient_histories.keys().collect()
462 }
463
464 fn check_gradient_alerts(&mut self, layer_name: &str, flow: &GradientFlow) -> Result<()> {
467 if let Some(thresholds) = self.adaptive_thresholds.get(layer_name) {
469 let threshold_alerts = thresholds.check_thresholds(flow.gradient_norm);
470 self.alerts.extend(threshold_alerts);
471 } else {
472 if flow.gradient_norm < self.gradient_config.vanishing_threshold {
474 self.alerts.push(GradientAlert::VanishingGradients {
475 layer_name: layer_name.to_string(),
476 norm: flow.gradient_norm,
477 threshold: self.gradient_config.vanishing_threshold,
478 });
479 }
480
481 if flow.gradient_norm > self.gradient_config.exploding_threshold {
482 self.alerts.push(GradientAlert::ExplodingGradients {
483 layer_name: layer_name.to_string(),
484 norm: flow.gradient_norm,
485 threshold: self.gradient_config.exploding_threshold,
486 });
487 }
488 }
489
490 if let Some(ratio) = flow.dead_neurons_ratio {
492 if ratio > self.gradient_config.dead_neuron_threshold {
493 self.alerts.push(GradientAlert::DeadNeurons {
494 layer_name: layer_name.to_string(),
495 ratio,
496 threshold: self.gradient_config.dead_neuron_threshold,
497 });
498 }
499 }
500
501 if let Some(monitor) = self.real_time_monitors.get(layer_name) {
503 if monitor.is_oscillating() {
504 self.alerts.push(GradientAlert::GradientOscillation {
505 layer_name: layer_name.to_string(),
506 variance: monitor.get_stability_score(),
507 });
508 }
509 }
510
511 Ok(())
512 }
513
514 fn compute_layer_status(
515 &self,
516 layer_name: &str,
517 history: &GradientHistory,
518 ) -> LayerGradientStatus {
519 let latest_norm = history.gradient_norms.back().cloned().unwrap_or(0.0);
520 let health = self.classify_layer_health(layer_name, history);
521 let alerts = self.get_layer_alerts(layer_name).len();
522 let trend = history.get_trend_slope().unwrap_or(0.0);
523
524 LayerGradientStatus {
525 layer_name: layer_name.to_string(),
526 health,
527 latest_gradient_norm: latest_norm,
528 gradient_trend: trend,
529 alert_count: alerts,
530 steps_recorded: history.gradient_norms.len(),
531 }
532 }
533
534 fn classify_layer_health(&self, layer_name: &str, history: &GradientHistory) -> LayerHealth {
535 let latest_norm = history.gradient_norms.back().cloned().unwrap_or(0.0);
536 let alert_count = self.get_layer_alerts(layer_name).len();
537
538 if !(1e-7..=100.0).contains(&latest_norm) || alert_count > 3 {
539 LayerHealth::Critical
540 } else if !(1e-5..=10.0).contains(&latest_norm) || alert_count > 0 {
541 LayerHealth::Warning
542 } else {
543 LayerHealth::Healthy
544 }
545 }
546
547 fn compute_overall_health(
548 &self,
549 layer_statuses: &HashMap<String, LayerGradientStatus>,
550 ) -> LayerHealth {
551 if layer_statuses.is_empty() {
552 return LayerHealth::Healthy;
553 }
554
555 let critical_count =
556 layer_statuses.values().filter(|s| s.health == LayerHealth::Critical).count();
557 let warning_count =
558 layer_statuses.values().filter(|s| s.health == LayerHealth::Warning).count();
559 let total = layer_statuses.len();
560
561 if critical_count > 0 || warning_count as f64 / total as f64 > 0.5 {
562 LayerHealth::Critical
563 } else if warning_count > 0 {
564 LayerHealth::Warning
565 } else {
566 LayerHealth::Healthy
567 }
568 }
569
570 fn generate_comprehensive_recommendations(&self) -> Result<Vec<GradientRecommendation>> {
571 let mut recommendations = Vec::new();
572
573 let perf_recs = self.performance_tracker.generate_optimization_recommendations();
575 for rec in perf_recs {
576 recommendations.push(GradientRecommendation {
577 recommendation_type: RecommendationType::Performance,
578 title: rec.layer_name,
579 description: format!("{:?}: {}", rec.issue_type, rec.recommendations.join(", ")),
580 priority: match rec.severity {
581 OptimizationSeverity::Critical => GradientRecommendationPriority::High,
582 OptimizationSeverity::High => GradientRecommendationPriority::High,
583 OptimizationSeverity::Medium => GradientRecommendationPriority::Medium,
584 OptimizationSeverity::Low => GradientRecommendationPriority::Low,
585 },
586 expected_impact: rec.expected_improvement,
587 });
588 }
589
590 let conflict_analysis = self.conflict_analyzer.analyze_conflicts(&self.gradient_histories);
592 for strategy in conflict_analysis.mitigation_strategies {
593 recommendations.push(GradientRecommendation {
594 recommendation_type: RecommendationType::Conflict,
595 title: strategy.strategy_name,
596 description: strategy.description,
597 priority: match strategy.implementation_complexity {
598 MitigationComplexity::Simple => GradientRecommendationPriority::High,
599 MitigationComplexity::Moderate => GradientRecommendationPriority::Medium,
600 MitigationComplexity::Complex => GradientRecommendationPriority::Medium,
601 MitigationComplexity::RequiresArchitectureChange => {
602 GradientRecommendationPriority::Low
603 },
604 },
605 expected_impact: strategy.effectiveness,
606 });
607 }
608
609 let anomaly_summary = self.anomaly_detector.get_anomaly_summary(None);
611 for rec_text in anomaly_summary.recommendations {
612 recommendations.push(GradientRecommendation {
613 recommendation_type: RecommendationType::Anomaly,
614 title: "Anomaly Mitigation".to_string(),
615 description: rec_text,
616 priority: if anomaly_summary.average_severity > 0.7 {
617 GradientRecommendationPriority::High
618 } else {
619 GradientRecommendationPriority::Medium
620 },
621 expected_impact: 1.0 - anomaly_summary.average_severity,
622 });
623 }
624
625 recommendations.sort_by(|a, b| {
627 let priority_cmp = b.priority.cmp(&a.priority);
628 if priority_cmp == std::cmp::Ordering::Equal {
629 b.expected_impact
630 .partial_cmp(&a.expected_impact)
631 .unwrap_or(std::cmp::Ordering::Equal)
632 } else {
633 priority_cmp
634 }
635 });
636
637 Ok(recommendations)
638 }
639
640 pub fn generate_recommendations(&self) -> Result<Vec<GradientRecommendation>> {
642 self.generate_comprehensive_recommendations()
643 }
644
645 pub async fn start(&mut self) -> Result<()> {
647 self.performance_tracker.start_monitoring();
649
650 self.current_step = 0;
652 self.alerts.clear();
653
654 for (layer_name, history) in &self.gradient_histories {
656 if !history.gradient_norms.is_empty() {
657 let thresholds = AdaptiveThresholds::from_history(history);
658 self.adaptive_thresholds.insert(layer_name.clone(), thresholds);
659 }
660 }
661
662 Ok(())
663 }
664
665 pub async fn generate_report(&self) -> Result<ComprehensiveGradientReport> {
667 let status = GradientDebugStatus {
668 current_step: self.current_step,
669 overall_health: self.evaluate_overall_health(),
670 layer_statuses: self.get_layer_statuses(),
671 recent_alerts: self.alerts.iter().rev().take(10).cloned().collect(),
672 total_alerts: self.alerts.len(),
673 active_layers: self.gradient_histories.len(),
674 };
675
676 let conflict_analysis = self.conflict_analyzer.analyze_conflicts(&self.gradient_histories);
677 let visualization = self.flow_visualizer.create_visualization(&self.gradient_histories);
678 let enhanced_analysis = self.enhanced_analyzer.analyze_gradients(&self.gradient_histories);
679 let performance_snapshot = self.performance_tracker.take_performance_snapshot();
680 let anomaly_summary = self.anomaly_detector.get_anomaly_summary(None);
681 let recommendations = self.generate_recommendations().unwrap_or_default();
682
683 let flow_analysis = self.generate_flow_analysis();
684
685 Ok(ComprehensiveGradientReport {
686 timestamp: chrono::Utc::now(),
687 status,
688 conflict_analysis,
689 visualization,
690 enhanced_analysis,
691 flow_analysis,
692 performance_snapshot,
693 anomaly_summary,
694 recommendations,
695 })
696 }
697
698 pub async fn quick_analysis(&self) -> Result<GradientQuickAnalysis> {
700 let mut problematic_layers = Vec::new();
701 let mut total_gradients = 0f64;
702 let mut active_layers = 0;
703
704 for (layer_name, history) in &self.gradient_histories {
705 if let Some(latest_norm) = history.gradient_norms.back() {
706 active_layers += 1;
707 total_gradients += latest_norm;
708
709 if *latest_norm < 1e-8 {
711 problematic_layers.push(format!("{}: Vanishing gradients", layer_name));
712 } else if *latest_norm > 100.0 {
713 problematic_layers.push(format!("{}: Exploding gradients", layer_name));
714 }
715 }
716 }
717
718 let average_gradient =
719 if active_layers > 0 { total_gradients / active_layers as f64 } else { 0.0 };
720
721 let health_score = self.calculate_quick_health_score();
722
723 Ok(GradientQuickAnalysis {
724 overall_health: if health_score > 0.8 {
725 LayerHealth::Healthy
726 } else if health_score > 0.5 {
727 LayerHealth::Warning
728 } else {
729 LayerHealth::Critical
730 },
731 active_layers,
732 problematic_layers,
733 average_gradient_norm: average_gradient,
734 recent_alerts_count: self.alerts.len(),
735 timestamp: chrono::Utc::now(),
736 })
737 }
738
739 fn evaluate_overall_health(&self) -> LayerHealth {
741 if self.gradient_histories.is_empty() {
742 return LayerHealth::Unknown;
743 }
744
745 let mut healthy_count = 0;
746 let mut warning_count = 0;
747 let mut critical_count = 0;
748
749 for history in self.gradient_histories.values() {
750 if let Some(latest_norm) = history.gradient_norms.back() {
751 if *latest_norm < 1e-8 || *latest_norm > 100.0 {
752 critical_count += 1;
753 } else if *latest_norm < 1e-6 || *latest_norm > 10.0 {
754 warning_count += 1;
755 } else {
756 healthy_count += 1;
757 }
758 }
759 }
760
761 let total = healthy_count + warning_count + critical_count;
762 let critical_ratio = critical_count as f64 / total as f64;
763 let warning_ratio = (warning_count + critical_count) as f64 / total as f64;
764
765 if critical_ratio > 0.3 {
766 LayerHealth::Critical
767 } else if warning_ratio > 0.5 {
768 LayerHealth::Warning
769 } else {
770 LayerHealth::Healthy
771 }
772 }
773
774 fn get_layer_statuses(&self) -> HashMap<String, LayerGradientStatus> {
776 let mut statuses = HashMap::new();
777
778 for (layer_name, history) in &self.gradient_histories {
779 let status = if let Some(latest_norm) = history.gradient_norms.back() {
780 LayerGradientStatus {
781 layer_name: layer_name.clone(),
782 latest_gradient_norm: *latest_norm,
783 gradient_trend: self.calculate_trend_value(history),
784 health: if *latest_norm < 1e-8 {
785 LayerHealth::Critical
786 } else if *latest_norm > 100.0 {
787 LayerHealth::Critical
788 } else if *latest_norm < 1e-6 || *latest_norm > 10.0 {
789 LayerHealth::Warning
790 } else {
791 LayerHealth::Healthy
792 },
793 alert_count: self.get_layer_alerts(layer_name).len(),
794 steps_recorded: history.gradient_norms.len(),
795 }
796 } else {
797 LayerGradientStatus {
798 layer_name: layer_name.clone(),
799 latest_gradient_norm: 0.0,
800 gradient_trend: 0.0,
801 health: LayerHealth::Unknown,
802 alert_count: 0,
803 steps_recorded: 0,
804 }
805 };
806
807 statuses.insert(layer_name.clone(), status);
808 }
809
810 statuses
811 }
812
813 fn calculate_trend(&self, history: &GradientHistory) -> GradientTrend {
815 if history.gradient_norms.len() < 3 {
816 return GradientTrend::Unknown;
817 }
818
819 let recent: Vec<f64> = history.gradient_norms.iter().rev().take(3).cloned().collect();
820
821 if recent[0] > recent[1] && recent[1] > recent[2] {
822 GradientTrend::Increasing
823 } else if recent[0] < recent[1] && recent[1] < recent[2] {
824 GradientTrend::Decreasing
825 } else {
826 GradientTrend::Stable
827 }
828 }
829
830 fn calculate_trend_value(&self, history: &GradientHistory) -> f64 {
832 if history.gradient_norms.len() < 2 {
833 return 0.0;
834 }
835
836 let recent: Vec<f64> = history.gradient_norms.iter().rev().take(10).cloned().collect();
837 if recent.len() < 2 {
838 return 0.0;
839 }
840
841 let n = recent.len() as f64;
843 let sum_x = (0..recent.len()).sum::<usize>() as f64;
844 let sum_y = recent.iter().sum::<f64>();
845 let sum_xy = recent.iter().enumerate().map(|(i, &y)| i as f64 * y).sum::<f64>();
846 let sum_x2 = (0..recent.len()).map(|i| (i * i) as f64).sum::<f64>();
847
848 (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x)
849 }
850
851 fn calculate_quick_health_score(&self) -> f64 {
853 if self.gradient_histories.is_empty() {
854 return 0.0;
855 }
856
857 let mut score = 0.0;
858 let mut count = 0;
859
860 for history in self.gradient_histories.values() {
861 if let Some(latest_norm) = history.gradient_norms.back() {
862 let norm_score = if *latest_norm >= 1e-4 && *latest_norm <= 1.0 {
864 1.0
865 } else if *latest_norm >= 1e-6 && *latest_norm <= 10.0 {
866 0.7
867 } else if *latest_norm >= 1e-8 && *latest_norm <= 100.0 {
868 0.3
869 } else {
870 0.0
871 };
872
873 score += norm_score;
874 count += 1;
875 }
876 }
877
878 if count == 0 {
879 0.0
880 } else {
881 score / count as f64
882 }
883 }
884}
885
886#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
888pub struct GradientDebugStatus {
889 pub current_step: usize,
890 pub overall_health: LayerHealth,
891 pub layer_statuses: HashMap<String, LayerGradientStatus>,
892 pub recent_alerts: Vec<GradientAlert>,
893 pub total_alerts: usize,
894 pub active_layers: usize,
895}
896
897#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
899pub struct ComprehensiveGradientReport {
900 pub timestamp: chrono::DateTime<chrono::Utc>,
901 pub status: GradientDebugStatus,
902 pub conflict_analysis: GradientConflictAnalysis,
903 pub visualization: GradientFlowVisualization,
904 pub enhanced_analysis: EnhancedLayerGradientAnalysis,
905 pub flow_analysis: FlowAnalysis,
906 pub performance_snapshot: PerformanceSnapshot,
907 pub anomaly_summary: AnomalySummary,
908 pub recommendations: Vec<GradientRecommendation>,
909}
910
911impl ComprehensiveGradientReport {
912 pub fn has_vanishing_gradients(&self) -> bool {
914 for layer_status in self.status.layer_statuses.values() {
916 if layer_status.latest_gradient_norm < 1e-8 {
917 return true;
918 }
919 }
920
921 for anomaly in &self.anomaly_summary.anomalies {
923 if matches!(
924 anomaly.anomaly_type,
925 crate::anomaly_detector::AnomalyType::GradientVanishing
926 ) {
927 return true;
928 }
929 }
930
931 false
932 }
933
934 pub fn has_exploding_gradients(&self) -> bool {
936 for layer_status in self.status.layer_statuses.values() {
938 if layer_status.latest_gradient_norm > 100.0 {
939 return true;
940 }
941 }
942
943 for anomaly in &self.anomaly_summary.anomalies {
945 if matches!(
946 anomaly.anomaly_type,
947 crate::anomaly_detector::AnomalyType::GradientExplosion
948 | crate::anomaly_detector::AnomalyType::NumericalInstability
949 ) {
950 return true;
951 }
952 }
953
954 false
955 }
956}
957
958#[derive(Debug, Clone)]
960pub struct PerformanceInsights {
961 pub trends: PerformanceTrends,
962 pub recommendations: Vec<OptimizationRecommendation>,
963 pub bottlenecks: Vec<String>,
964 pub current_throughput: f64,
965 pub memory_usage: Option<usize>,
969}
970
971#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
973pub struct GradientRecommendation {
974 pub recommendation_type: RecommendationType,
975 pub title: String,
976 pub description: String,
977 pub priority: GradientRecommendationPriority,
978 pub expected_impact: f64,
979}
980
981#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
982pub enum RecommendationType {
983 Performance,
984 Conflict,
985 Anomaly,
986 Architecture,
987 Optimization,
988}
989
990#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
991pub enum GradientRecommendationPriority {
992 Low,
993 Medium,
994 High,
995}
996
997#[derive(Debug, Clone)]
999pub struct GradientQuickAnalysis {
1000 pub overall_health: LayerHealth,
1001 pub active_layers: usize,
1002 pub problematic_layers: Vec<String>,
1003 pub average_gradient_norm: f64,
1004 pub recent_alerts_count: usize,
1005 pub timestamp: chrono::DateTime<chrono::Utc>,
1006}
1007
1008#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1010pub struct LayerGradientStatus {
1011 pub layer_name: String,
1012 pub health: LayerHealth,
1013 pub latest_gradient_norm: f64,
1014 pub gradient_trend: f64,
1015 pub alert_count: usize,
1016 pub steps_recorded: usize,
1017}
1018
1019#[derive(Debug, Clone, PartialEq, Eq)]
1021pub enum GradientTrend {
1022 Unknown,
1023 Increasing,
1024 Decreasing,
1025 Stable,
1026}
1027
1028#[cfg(test)]
1029mod tests {
1030 use super::*;
1031
1032 fn debugger() -> GradientDebugger {
1035 GradientDebugger::new(crate::DebugConfig::default())
1036 }
1037
1038 #[test]
1039 fn reduced_entry_point_reports_absence_for_per_element_fields() {
1040 let mut dbg = debugger();
1041 dbg.record_gradient_flow("layer", 1e-9, 0.0, 0.0).expect("record");
1042 let history = dbg.get_layer_history("layer").expect("history");
1043 assert_eq!(history.gradient_norms.len(), 1);
1044 assert!(
1047 dbg.get_layer_alerts("layer")
1048 .into_iter()
1049 .all(|a| !matches!(a, GradientAlert::DeadNeurons { .. })),
1050 "a dead-neuron alert must never fire from reduced statistics alone"
1051 );
1052 }
1053
1054 #[test]
1055 fn record_gradient_values_computes_the_real_statistics() {
1056 let mut dbg = debugger();
1057 let gradients = vec![0.0, 0.0, 3.0, -4.0];
1058 dbg.record_gradient_values("layer", &gradients).expect("record");
1059 let history = dbg.get_layer_history("layer").expect("history");
1060 let norm = history.gradient_norms.back().copied().expect("a norm");
1061 assert!(
1062 (norm - 5.0).abs() < 1e-12,
1063 "L2 norm of [0,0,3,-4] is 5, got {norm}"
1064 );
1065 let mean = history.gradient_means.back().copied().expect("a mean");
1066 assert!((mean + 0.25).abs() < 1e-12, "mean is -0.25, got {mean}");
1067 assert_eq!(
1068 history.parameter_count,
1069 Some(4),
1070 "real element count is recorded"
1071 );
1072 }
1073
1074 #[test]
1075 fn dead_neuron_alert_fires_only_from_real_per_element_data() {
1076 let mut dbg = debugger();
1077 dbg.record_gradient_values("layer", &[0.0, 0.0, 1.0, 2.0]).expect("record");
1080 let dead: Vec<f64> = dbg
1081 .get_layer_alerts("layer")
1082 .into_iter()
1083 .filter_map(|alert| match alert {
1084 GradientAlert::DeadNeurons { ratio, .. } => Some(*ratio),
1085 _ => None,
1086 })
1087 .collect();
1088 assert_eq!(
1089 dead.len(),
1090 1,
1091 "one real dead-neuron alert expected, got {dead:?}"
1092 );
1093 assert!(
1094 (dead[0] - 0.5).abs() < 1e-12,
1095 "the reported ratio must be the real 0.5"
1096 );
1097 }
1098
1099 #[test]
1100 fn no_dead_neuron_alert_when_no_element_is_dead() {
1101 let mut dbg = debugger();
1102 dbg.record_gradient_values("layer", &[1e-7, 2e-7, 3e-7, 4e-7]).expect("record");
1105 assert!(
1106 dbg.get_layer_alerts("layer")
1107 .into_iter()
1108 .all(|a| !matches!(a, GradientAlert::DeadNeurons { .. })),
1109 "no element is below the 1e-8 dead magnitude, so nothing is dead"
1110 );
1111 }
1112
1113 #[test]
1114 fn record_gradient_values_refuses_an_empty_slice() {
1115 let mut dbg = debugger();
1116 let err = dbg.record_gradient_values("layer", &[]).expect_err("must refuse");
1117 assert!(err.to_string().contains("nothing to measure"), "{err}");
1118 }
1119
1120 #[test]
1121 fn tracked_memory_is_absent_until_a_caller_reports_a_real_figure() {
1122 let mut dbg = debugger();
1123 dbg.record_gradient_flow("layer", 1.0, 0.0, 1.0).expect("record");
1124 let insights = dbg.get_performance_insights();
1125 assert_eq!(
1126 insights.memory_usage, None,
1127 "no memory sample was ever supplied, so the aggregate must be absent, not 0"
1128 );
1129 }
1130
1131 #[test]
1132 fn test_set_layer_parameter_count_creates_history_if_absent() {
1133 let mut debugger = GradientDebugger::new(DebugConfig::default());
1134 assert!(!debugger.gradient_histories.contains_key("new_layer"));
1135
1136 debugger.set_layer_parameter_count("new_layer", 42);
1137
1138 assert_eq!(
1139 debugger.gradient_histories.get("new_layer").and_then(|h| h.parameter_count),
1140 Some(42)
1141 );
1142 }
1143
1144 #[test]
1145 fn test_set_layer_parameter_count_updates_existing_history() {
1146 let mut debugger = GradientDebugger::new(DebugConfig::default());
1147 debugger.record_gradient_flow("layer0", 1.0, 0.5, 0.1).expect("record ok");
1148 assert_eq!(
1149 debugger.gradient_histories.get("layer0").and_then(|h| h.parameter_count),
1150 None,
1151 "record_gradient_flow only ever receives reduced scalars, never a shape"
1152 );
1153
1154 debugger.set_layer_parameter_count("layer0", 123_456);
1155
1156 assert_eq!(
1157 debugger.gradient_histories.get("layer0").and_then(|h| h.parameter_count),
1158 Some(123_456)
1159 );
1160 assert_eq!(
1163 debugger.gradient_histories.get("layer0").unwrap().gradient_norms.len(),
1164 1
1165 );
1166 }
1167}