1use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::time::{Duration, Instant};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct GradientPerformanceTracker {
14 pub total_gradient_computations: usize,
15 pub average_computation_time: Duration,
16 pub memory_usage_bytes: Option<usize>,
24 pub throughput_gradients_per_second: f64,
25 pub bottleneck_layers: Vec<String>,
26 pub layer_performance_map: HashMap<String, LayerPerformanceMetrics>,
27 pub resource_utilization: ResourceUtilization,
28 pub performance_history: Vec<PerformanceSnapshot>,
29}
30
31impl Default for GradientPerformanceTracker {
32 fn default() -> Self {
33 Self {
34 total_gradient_computations: 0,
35 average_computation_time: Duration::from_millis(0),
36 memory_usage_bytes: None,
37 throughput_gradients_per_second: 0.0,
38 bottleneck_layers: Vec::new(),
39 layer_performance_map: HashMap::new(),
40 resource_utilization: ResourceUtilization::default(),
41 performance_history: Vec::new(),
42 }
43 }
44}
45
46impl GradientPerformanceTracker {
47 pub fn new() -> Self {
48 Self::default()
49 }
50
51 pub fn start_timing(&mut self, layer_name: &str) -> PerformanceTimer {
52 PerformanceTimer::new(layer_name.to_string())
53 }
54
55 pub fn record_layer_performance(
61 &mut self,
62 layer_name: &str,
63 computation_time: Duration,
64 memory_used: Option<usize>,
65 ) {
66 let metrics = self
67 .layer_performance_map
68 .entry(layer_name.to_string())
69 .or_insert_with(|| LayerPerformanceMetrics::new(layer_name.to_string()));
70
71 metrics.update(computation_time, memory_used);
72 self.total_gradient_computations += 1;
73
74 self.update_overall_metrics();
76 self.identify_bottlenecks();
77 }
78
79 fn update_overall_metrics(&mut self) {
80 if self.layer_performance_map.is_empty() {
81 return;
82 }
83
84 let total_time: Duration =
85 self.layer_performance_map.values().map(|m| m.average_computation_time).sum();
86
87 let total_layers = self.layer_performance_map.len();
88 self.average_computation_time = total_time / total_layers as u32;
89
90 let mut memory_total = None;
93 for average in self.layer_performance_map.values().filter_map(|m| m.average_memory_usage) {
94 memory_total = Some(memory_total.unwrap_or(0) + average);
95 }
96 self.memory_usage_bytes = memory_total;
97
98 if self.average_computation_time.as_secs_f64() > 0.0 {
100 self.throughput_gradients_per_second =
101 1.0 / self.average_computation_time.as_secs_f64();
102 }
103 }
104
105 fn identify_bottlenecks(&mut self) {
106 self.bottleneck_layers.clear();
107
108 if self.layer_performance_map.len() < 2 {
109 return;
110 }
111
112 let times: Vec<f64> = self
114 .layer_performance_map
115 .values()
116 .map(|m| m.average_computation_time.as_secs_f64())
117 .collect();
118
119 let mean = times.iter().sum::<f64>() / times.len() as f64;
120 let variance = times.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / times.len() as f64;
121 let std_dev = variance.sqrt();
122
123 let threshold = mean + 1.5 * std_dev;
125
126 for (layer_name, metrics) in &self.layer_performance_map {
127 if metrics.average_computation_time.as_secs_f64() > threshold {
128 self.bottleneck_layers.push(layer_name.clone());
129 }
130 }
131 }
132
133 pub fn get_performance_trends(&self) -> PerformanceTrends {
134 if self.performance_history.len() < 2 {
135 return PerformanceTrends::default();
136 }
137
138 let recent_snapshots: Vec<&PerformanceSnapshot> =
139 self.performance_history.iter().rev().take(10).collect();
140
141 let older_snapshots: Vec<&PerformanceSnapshot> =
142 self.performance_history.iter().rev().skip(10).take(10).collect();
143
144 if older_snapshots.is_empty() {
145 return PerformanceTrends::default();
146 }
147
148 let recent_avg_throughput = recent_snapshots.iter().map(|s| s.throughput).sum::<f64>()
149 / recent_snapshots.len() as f64;
150
151 let older_avg_throughput = older_snapshots.iter().map(|s| s.throughput).sum::<f64>()
152 / older_snapshots.len() as f64;
153
154 let mean_memory = |window: &[&PerformanceSnapshot]| -> Option<f64> {
157 let samples: Vec<usize> = window.iter().filter_map(|s| s.memory_usage).collect();
158 if samples.is_empty() {
159 None
160 } else {
161 Some(samples.iter().sum::<usize>() as f64 / samples.len() as f64)
162 }
163 };
164 let memory_trend = match (
165 mean_memory(&recent_snapshots),
166 mean_memory(&older_snapshots),
167 ) {
168 (Some(recent), Some(older)) => Some(Self::classify_trend(recent, older)),
169 _ => None,
170 };
171
172 PerformanceTrends {
173 throughput_trend: Self::classify_trend(recent_avg_throughput, older_avg_throughput),
174 memory_trend,
175 bottleneck_stability: self
176 .analyze_bottleneck_stability(&recent_snapshots, &older_snapshots),
177 overall_performance_direction: self
178 .analyze_overall_direction(&recent_snapshots, &older_snapshots),
179 }
180 }
181
182 fn classify_trend(recent: f64, older: f64) -> TrendDirection {
183 let change_ratio = (recent - older) / older.max(1e-10);
184 let threshold = 0.05; if change_ratio > threshold {
187 TrendDirection::Improving
188 } else if change_ratio < -threshold {
189 TrendDirection::Degrading
190 } else {
191 TrendDirection::Stable
192 }
193 }
194
195 fn analyze_bottleneck_stability(
196 &self,
197 recent: &[&PerformanceSnapshot],
198 older: &[&PerformanceSnapshot],
199 ) -> BottleneckStability {
200 let recent_bottlenecks: std::collections::HashSet<&String> =
201 recent.iter().flat_map(|s| &s.active_bottlenecks).collect();
202
203 let older_bottlenecks: std::collections::HashSet<&String> =
204 older.iter().flat_map(|s| &s.active_bottlenecks).collect();
205
206 let intersection_size = recent_bottlenecks.intersection(&older_bottlenecks).count();
207 let union_size = recent_bottlenecks.union(&older_bottlenecks).count();
208
209 if union_size == 0 {
210 return BottleneckStability::Stable;
211 }
212
213 let stability_ratio = intersection_size as f64 / union_size as f64;
214
215 if stability_ratio > 0.8 {
216 BottleneckStability::Stable
217 } else if stability_ratio > 0.5 {
218 BottleneckStability::Moderate
219 } else {
220 BottleneckStability::Unstable
221 }
222 }
223
224 fn analyze_overall_direction(
225 &self,
226 recent: &[&PerformanceSnapshot],
227 older: &[&PerformanceSnapshot],
228 ) -> PerformanceDirection {
229 let recent_avg_time =
230 recent.iter().map(|s| s.average_time.as_secs_f64()).sum::<f64>() / recent.len() as f64;
231
232 let older_avg_time =
233 older.iter().map(|s| s.average_time.as_secs_f64()).sum::<f64>() / older.len() as f64;
234
235 if recent_avg_time < older_avg_time * 0.95 {
236 PerformanceDirection::Improving
237 } else if recent_avg_time > older_avg_time * 1.05 {
238 PerformanceDirection::Degrading
239 } else {
240 PerformanceDirection::Stable
241 }
242 }
243
244 pub fn generate_optimization_recommendations(&self) -> Vec<OptimizationRecommendation> {
245 let mut recommendations = Vec::new();
246
247 for layer_name in &self.bottleneck_layers {
249 if let Some(metrics) = self.layer_performance_map.get(layer_name) {
250 recommendations.push(OptimizationRecommendation {
251 layer_name: layer_name.clone(),
252 issue_type: OptimizationIssue::ComputationalBottleneck,
253 severity: self.calculate_bottleneck_severity(metrics),
254 recommendations: vec![
255 format!("Consider optimizing {} layer computation", layer_name),
256 "Check for inefficient operations or memory access patterns".to_string(),
257 "Consider layer-specific optimizations or hardware acceleration"
258 .to_string(),
259 ],
260 expected_improvement: self.estimate_improvement_potential(metrics),
261 });
262 }
263 }
264
265 if self.memory_usage_bytes.is_some_and(|bytes| bytes > 1_000_000_000) {
267 recommendations.push(OptimizationRecommendation {
269 layer_name: "Global".to_string(),
270 issue_type: OptimizationIssue::HighMemoryUsage,
271 severity: OptimizationSeverity::High,
272 recommendations: vec![
273 "Consider gradient checkpointing to reduce memory usage".to_string(),
274 "Optimize batch size and sequence length".to_string(),
275 "Use memory-efficient attention mechanisms".to_string(),
276 ],
277 expected_improvement: 0.3,
278 });
279 }
280
281 if self.throughput_gradients_per_second < 1.0 {
283 recommendations.push(OptimizationRecommendation {
284 layer_name: "Global".to_string(),
285 issue_type: OptimizationIssue::LowThroughput,
286 severity: OptimizationSeverity::Medium,
287 recommendations: vec![
288 "Consider mixed precision training".to_string(),
289 "Optimize data loading and preprocessing pipelines".to_string(),
290 "Use gradient accumulation for larger effective batch sizes".to_string(),
291 ],
292 expected_improvement: 0.4,
293 });
294 }
295
296 recommendations
297 }
298
299 fn calculate_bottleneck_severity(
300 &self,
301 metrics: &LayerPerformanceMetrics,
302 ) -> OptimizationSeverity {
303 let relative_slowness = metrics.average_computation_time.as_secs_f64()
304 / self.average_computation_time.as_secs_f64();
305
306 if relative_slowness > 3.0 {
307 OptimizationSeverity::Critical
308 } else if relative_slowness > 2.0 {
309 OptimizationSeverity::High
310 } else if relative_slowness > 1.5 {
311 OptimizationSeverity::Medium
312 } else {
313 OptimizationSeverity::Low
314 }
315 }
316
317 fn estimate_improvement_potential(&self, metrics: &LayerPerformanceMetrics) -> f64 {
318 let relative_slowness = metrics.average_computation_time.as_secs_f64()
319 / self.average_computation_time.as_secs_f64();
320
321 (relative_slowness - 1.0).min(0.8).max(0.1)
323 }
324
325 pub fn start_monitoring(&mut self) {
327 self.total_gradient_computations = 0;
329 self.average_computation_time = Duration::from_millis(0);
330 self.memory_usage_bytes = None;
331 self.throughput_gradients_per_second = 0.0;
332 self.bottleneck_layers.clear();
333 self.layer_performance_map.clear();
334
335 self.resource_utilization = ResourceUtilization {
337 cpu_usage_percent: 0.0,
338 memory_usage_percent: 0.0,
339 gpu_usage_percent: 0.0,
340 io_wait_percent: 0.0,
341 };
342 }
343
344 pub fn take_performance_snapshot(&self) -> PerformanceSnapshot {
346 PerformanceSnapshot {
347 timestamp: std::time::SystemTime::now(),
348 total_computations: self.total_gradient_computations,
349 average_time: self.average_computation_time,
350 memory_usage: self.memory_usage_bytes,
351 throughput: self.throughput_gradients_per_second,
352 active_bottlenecks: self.bottleneck_layers.clone(),
353 layer_count: self.layer_performance_map.len(),
354 }
355 }
356}
357
358#[derive(Debug, Clone, Serialize, Deserialize)]
360pub struct LayerPerformanceMetrics {
361 pub layer_name: String,
362 pub computation_count: usize,
363 pub total_computation_time: Duration,
364 pub average_computation_time: Duration,
365 pub total_memory_usage: Option<usize>,
368 pub average_memory_usage: Option<usize>,
371 pub memory_sample_count: usize,
375 pub min_computation_time: Duration,
376 pub max_computation_time: Duration,
377 pub performance_variance: f64,
378}
379
380impl LayerPerformanceMetrics {
381 pub fn new(layer_name: String) -> Self {
382 Self {
383 layer_name,
384 computation_count: 0,
385 total_computation_time: Duration::from_millis(0),
386 average_computation_time: Duration::from_millis(0),
387 total_memory_usage: None,
388 average_memory_usage: None,
389 memory_sample_count: 0,
390 min_computation_time: Duration::from_secs(u64::MAX),
391 max_computation_time: Duration::from_millis(0),
392 performance_variance: 0.0,
393 }
394 }
395
396 pub fn update(&mut self, computation_time: Duration, memory_used: Option<usize>) {
397 self.computation_count += 1;
398 self.total_computation_time += computation_time;
399 if let Some(bytes) = memory_used {
400 self.memory_sample_count += 1;
401 let total = self.total_memory_usage.unwrap_or(0) + bytes;
402 self.total_memory_usage = Some(total);
403 self.average_memory_usage = Some(total / self.memory_sample_count);
404 }
405
406 self.average_computation_time = self.total_computation_time / self.computation_count as u32;
407
408 if computation_time < self.min_computation_time {
409 self.min_computation_time = computation_time;
410 }
411 if computation_time > self.max_computation_time {
412 self.max_computation_time = computation_time;
413 }
414
415 self.update_variance(computation_time);
416 }
417
418 fn update_variance(&mut self, new_time: Duration) {
419 if self.computation_count < 2 {
420 self.performance_variance = 0.0;
421 return;
422 }
423
424 let mean = self.average_computation_time.as_secs_f64();
425 let new_value = new_time.as_secs_f64();
426
427 let old_variance = self.performance_variance;
429 let delta = new_value - mean;
430 self.performance_variance = ((self.computation_count - 1) as f64 * old_variance
431 + delta * delta)
432 / self.computation_count as f64;
433 }
434}
435
436#[derive(Debug)]
438pub struct PerformanceTimer {
439 layer_name: String,
440 start_time: Instant,
441}
442
443impl PerformanceTimer {
444 pub fn new(layer_name: String) -> Self {
445 Self {
446 layer_name,
447 start_time: Instant::now(),
448 }
449 }
450
451 pub fn finish(self) -> (String, Duration) {
452 (self.layer_name, self.start_time.elapsed())
453 }
454}
455
456#[derive(Debug, Clone, Serialize, Deserialize)]
458pub struct ResourceUtilization {
459 pub cpu_usage_percent: f64,
460 pub gpu_usage_percent: f64,
461 pub memory_usage_percent: f64,
462 pub io_wait_percent: f64,
463}
464
465impl Default for ResourceUtilization {
466 fn default() -> Self {
467 Self {
468 cpu_usage_percent: 0.0,
469 gpu_usage_percent: 0.0,
470 memory_usage_percent: 0.0,
471 io_wait_percent: 0.0,
472 }
473 }
474}
475
476#[derive(Debug, Clone, Serialize, Deserialize)]
478pub struct PerformanceSnapshot {
479 pub timestamp: std::time::SystemTime,
480 pub total_computations: usize,
481 pub average_time: Duration,
482 pub memory_usage: Option<usize>,
485 pub throughput: f64,
486 pub active_bottlenecks: Vec<String>,
487 pub layer_count: usize,
488}
489
490#[derive(Debug, Clone, Serialize, Deserialize)]
492pub struct PerformanceTrends {
493 pub throughput_trend: TrendDirection,
494 pub memory_trend: Option<TrendDirection>,
497 pub bottleneck_stability: BottleneckStability,
498 pub overall_performance_direction: PerformanceDirection,
499}
500
501impl Default for PerformanceTrends {
502 fn default() -> Self {
503 Self {
504 throughput_trend: TrendDirection::Stable,
505 memory_trend: None,
506 bottleneck_stability: BottleneckStability::Stable,
507 overall_performance_direction: PerformanceDirection::Stable,
508 }
509 }
510}
511
512#[derive(Debug, Clone, Serialize, Deserialize)]
513pub enum TrendDirection {
514 Improving,
515 Stable,
516 Degrading,
517}
518
519#[derive(Debug, Clone, Serialize, Deserialize)]
520pub enum BottleneckStability {
521 Stable,
522 Moderate,
523 Unstable,
524}
525
526#[derive(Debug, Clone, Serialize, Deserialize)]
527pub enum PerformanceDirection {
528 Improving,
529 Stable,
530 Degrading,
531}
532
533#[derive(Debug, Clone, Serialize, Deserialize)]
535pub struct OptimizationRecommendation {
536 pub layer_name: String,
537 pub issue_type: OptimizationIssue,
538 pub severity: OptimizationSeverity,
539 pub recommendations: Vec<String>,
540 pub expected_improvement: f64,
541}
542
543#[derive(Debug, Clone, Serialize, Deserialize)]
544pub enum OptimizationIssue {
545 ComputationalBottleneck,
546 HighMemoryUsage,
547 LowThroughput,
548 ResourceUnderutilization,
549}
550
551#[derive(Debug, Clone, Serialize, Deserialize)]
552pub enum OptimizationSeverity {
553 Low,
554 Medium,
555 High,
556 Critical,
557}