Skip to main content

trustformers_debug/gradient_debugger/
debugger.rs

1//! Main Gradient Debugger Implementation
2//!
3//! This module provides the main GradientDebugger that orchestrates all gradient
4//! debugging capabilities including monitoring, anomaly detection, performance tracking,
5//! conflict analysis, visualization, and enhanced analysis.
6// reason: debug/profiling scaffolding — structs are constructed and their fields/methods
7// are retained for the data model, serialization completeness, and future consumers that
8// do not yet read every member. Consolidated from many item-level #[allow(dead_code)].
9#![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/// Flow analysis for gradient flow patterns
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct FlowAnalysis {
26    pub layer_analyses: HashMap<String, LayerFlowAnalysis>,
27}
28
29/// Analysis of gradient flow for a specific layer
30#[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/// Main gradient debugger
40#[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    // Advanced features
50    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    /// Create a new gradient debugger
61    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    /// Create with custom gradient configuration
82    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    /// Report the real number of elements in `layer_name`'s gradient
101    /// tensor, for callers that have access to the actual tensor (and not
102    /// just the reduced norm/mean/std [`Self::record_gradient_flow`]
103    /// takes). Once set, vanishing/exploding-region reports for this layer
104    /// (see [`super::visualization::RegionExtent::affected_parameters`])
105    /// carry this real count instead of an honest `None`. Creates the
106    /// layer's history entry if `record_gradient_flow` has not been called
107    /// for it yet.
108    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    /// Record gradient flow for a layer from REDUCED statistics.
116    ///
117    /// The per-element quantities on [`GradientFlow`] (`gradient_max`,
118    /// `gradient_min`, `dead_neurons_ratio`, `active_neurons_ratio`) are
119    /// honestly `None` here: norm, mean and std do not determine them. Call
120    /// [`Self::record_gradient_values`] instead when the real gradient tensor
121    /// is at hand and those fields are wanted.
122    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    /// Record gradient flow for a layer from the REAL per-element gradients.
144    ///
145    /// Computes every [`GradientFlow`] field from the supplied values: the L2
146    /// norm, the mean, the population standard deviation, the true max/min, and
147    /// the real dead fraction (elements whose magnitude is at or below
148    /// [`GradientDebugConfig::dead_gradient_magnitude`]). Also records the real
149    /// element count so region reports can name a real
150    /// `affected_parameters`.
151    ///
152    /// Returns an error for an empty slice rather than reporting a
153    /// zero-gradient layer that was never measured.
154    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    /// Shared bookkeeping for both `record_gradient_flow` entry points.
189    fn record_flow(&mut self, flow: GradientFlow) -> Result<()> {
190        // Timed across the whole bookkeeping body -- see the
191        // `record_layer_performance` call at the end of this function for what
192        // the measurement actually covers.
193        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        // Update gradient history
199        {
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        // Update adaptive thresholds
208        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        // Update real-time monitor
219        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        // Check for alerts
226        self.check_gradient_alerts(layer_name, &flow)?;
227
228        // Record how long this debugger's own bookkeeping took. This is
229        // explicitly NOT the layer's backward-pass time -- the debugger never
230        // executes the layer -- and the timer therefore spans exactly the body
231        // of `record_flow`. (It used to be started and finished on adjacent
232        // lines at the very end, so it timed nothing at all.) No per-layer
233        // memory figure is measurable from here, hence `None` rather than the
234        // previous fabricated `0`.
235        let (_, bookkeeping_time) = timer.finish();
236        self.performance_tracker
237            .record_layer_performance(layer_name, bookkeeping_time, None);
238
239        // Detect anomalies
240        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        // Establish baseline if needed
251        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    /// Get current gradient debugging status
262    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    /// Generate flow analysis for report generation
287    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            // Determine if gradients are vanishing or exploding
294            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            // Calculate flow consistency (variance in gradient norms)
303            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) // Higher consistency = lower variance
310            } 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    /// Generate comprehensive debugging report
330    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    /// Analyze gradient conflicts between layers
357    pub fn analyze_gradient_conflicts(&self) -> GradientConflictAnalysis {
358        self.conflict_analyzer.analyze_conflicts(&self.gradient_histories)
359    }
360
361    /// Generate gradient flow visualization
362    pub fn generate_gradient_flow_visualization(&self) -> GradientFlowVisualization {
363        self.flow_visualizer
364            .generate_visualization(&self.gradient_histories, self.current_step)
365    }
366
367    /// Generate enhanced layer analysis
368    pub fn generate_enhanced_layer_analysis(&self) -> EnhancedLayerGradientAnalysis {
369        self.enhanced_analyzer.generate_enhanced_analysis(&self.gradient_histories)
370    }
371
372    /// Get performance insights
373    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    /// Advance to next step
388    pub fn next_step(&mut self) {
389        self.current_step += 1;
390
391        // Clear old alerts (keep last 100)
392        if self.alerts.len() > 100 {
393            self.alerts.drain(0..self.alerts.len() - 100);
394        }
395
396        // Update no-gradient counters
397        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        // Check for no-gradient alerts
408        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    /// Reset debugger state
419    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    /// Get alerts for a specific layer
431    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    /// Get gradient history for a layer
455    pub fn get_layer_history(&self, layer_name: &str) -> Option<&GradientHistory> {
456        self.gradient_histories.get(layer_name)
457    }
458
459    /// Get all monitored layers
460    pub fn get_monitored_layers(&self) -> Vec<&String> {
461        self.gradient_histories.keys().collect()
462    }
463
464    // Private helper methods
465
466    fn check_gradient_alerts(&mut self, layer_name: &str, flow: &GradientFlow) -> Result<()> {
467        // Check adaptive thresholds first
468        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            // Fallback to static thresholds
473            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        // Check dead neurons -- only when a real per-element ratio was measured.
491        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        // Check oscillation
502        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        // Performance recommendations
574        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        // Conflict recommendations
591        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        // Anomaly recommendations
610        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        // Sort by priority and expected impact
626        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    /// Generate recommendations based on current analysis
641    pub fn generate_recommendations(&self) -> Result<Vec<GradientRecommendation>> {
642        self.generate_comprehensive_recommendations()
643    }
644
645    /// Start the gradient debugger
646    pub async fn start(&mut self) -> Result<()> {
647        // Initialize monitoring systems
648        self.performance_tracker.start_monitoring();
649
650        // Reset state for a new debugging session
651        self.current_step = 0;
652        self.alerts.clear();
653
654        // Initialize adaptive thresholds for existing histories
655        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    /// Generate comprehensive gradient report
666    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    /// Quick analysis for immediate insights
699    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                // Check for basic problems
710                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    /// Evaluate overall gradient health
740    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    /// Get status for each layer
775    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    /// Calculate gradient trend for a layer
814    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    /// Calculate gradient trend as numeric value for a layer
831    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        // Calculate linear trend slope
842        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    /// Calculate quick health score
852    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                // Score based on gradient magnitude (ideal range: 1e-4 to 1.0)
863                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/// Current gradient debugging status
887#[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/// Comprehensive gradient debugging report
898#[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    /// Check if there are vanishing gradient issues
913    pub fn has_vanishing_gradients(&self) -> bool {
914        // Check if any layers have very small gradients
915        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        // Check anomaly summary for vanishing gradient patterns
922        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    /// Check if there are exploding gradient issues
935    pub fn has_exploding_gradients(&self) -> bool {
936        // Check if any layers have very large gradients
937        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        // Check anomaly summary for exploding gradient patterns
944        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/// Performance insights summary
959#[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    /// Aggregate tracked memory usage; `None` when no caller has reported a
966    /// real per-layer memory sample (see
967    /// [`super::performance_tracking::GradientPerformanceTracker::record_layer_performance`]).
968    pub memory_usage: Option<usize>,
969}
970
971/// Gradient debugging recommendation
972#[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/// Quick analysis results for immediate insights
998#[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/// Status for individual layer gradients
1009#[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/// Gradient trend indicators
1020#[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    // ---- Wave 6c debug-sweep2 honesty regressions ------------------------
1033
1034    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        // The old code filled these from norm/mean/std and from a 0.9/0.3/0.05
1045        // ladder over the norm.
1046        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        // Half the elements are exactly zero => real dead ratio 0.5, over the
1078        // 0.1 default threshold.
1079        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        // Tiny but non-zero gradients: the OLD ladder reported 90% dead here
1103        // because the norm was below 1e-6.
1104        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        // The real gradient history recorded before the count was set must
1161        // survive untouched.
1162        assert_eq!(
1163            debugger.gradient_histories.get("layer0").unwrap().gradient_norms.len(),
1164            1
1165        );
1166    }
1167}