use serde_json::Value;
use super::statistics::{estimate_gradient_norm_from_weights, estimate_max_gradient_from_weights};
use super::types::GradientFlowInfo;
pub(super) fn analyze_gradient_flow(
old_obj: &serde_json::Map<String, Value>,
new_obj: &serde_json::Map<String, Value>,
) -> Option<(String, String)> {
let old_flow = extract_gradient_flow_info(old_obj)?;
let new_flow = extract_gradient_flow_info(new_obj)?;
let mut flow_analysis = Vec::new();
if old_flow.vanishing_layers != new_flow.vanishing_layers {
let change = new_flow.vanishing_layers as i32 - old_flow.vanishing_layers as i32;
let trend = if change == 0 {
"stable"
} else if change > 0 {
"more_vanishing"
} else {
"less_vanishing"
};
flow_analysis.push(format!(
"vanishing_layers: {} ({:+}, {})",
new_flow.vanishing_layers, change, trend
));
}
if old_flow.exploding_layers != new_flow.exploding_layers {
let change = new_flow.exploding_layers as i32 - old_flow.exploding_layers as i32;
flow_analysis.push(format!(
"exploding_layers: {} ({:+})",
new_flow.exploding_layers, change
));
}
if let (Some(old_balance), Some(new_balance)) = (old_flow.flow_balance, new_flow.flow_balance) {
let balance_change = new_balance - old_balance;
let balance_status = if balance_change.abs() < 0.1 {
"balanced"
} else if balance_change > 0.0 {
"forward_dominant"
} else {
"backward_dominant"
};
flow_analysis.push(format!(
"flow_balance: {new_balance:.3} ({balance_change:+.3}, {balance_status})"
));
}
if flow_analysis.is_empty() {
return None;
}
let old_info = format!(
"vanishing: {}, exploding: {}, balance: {:.3}",
old_flow.vanishing_layers,
old_flow.exploding_layers,
old_flow.flow_balance.unwrap_or(0.0)
);
let new_info = flow_analysis.join(", ");
Some((old_info, new_info))
}
pub(super) fn extract_gradient_flow_info(
obj: &serde_json::Map<String, Value>,
) -> Option<GradientFlowInfo> {
let mut vanishing_layers = 0;
let mut exploding_layers = 0;
let _estimated_norm = estimate_gradient_norm_from_weights(obj);
let _estimated_max = estimate_max_gradient_from_weights(obj);
#[allow(unused_assignments)]
let mut flow_balance = None;
let vanishing_threshold = 1e-7; let exploding_threshold = 5.0; let _moderate_exploding_threshold = 1.0;
let mut layer_gradients = Vec::new();
for (key, value) in obj {
let is_gradient_related = key.contains("grad")
|| key.contains("gradient")
|| key.contains("weight")
|| key.contains("bias")
|| key.contains("param")
|| key.contains("layer");
if is_gradient_related {
match value {
Value::Number(num) => {
if let Some(val) = num.as_f64() {
let abs_val = val.abs();
layer_gradients.push(abs_val);
if abs_val < vanishing_threshold {
vanishing_layers += 1;
} else if abs_val > exploding_threshold {
exploding_layers += 1;
}
}
}
Value::Array(arr) => {
let mut layer_sum = 0.0;
let mut layer_count = 0;
for item in arr {
if let Value::Number(num) = item {
if let Some(val) = num.as_f64() {
let abs_val = val.abs();
layer_sum += abs_val;
layer_count += 1;
}
}
}
if layer_count > 0 {
let layer_mean = layer_sum / layer_count as f64;
layer_gradients.push(layer_mean);
if layer_mean < vanishing_threshold {
vanishing_layers += 1;
} else if layer_mean > exploding_threshold {
exploding_layers += 1;
}
}
}
Value::Object(nested) => {
if let Some(nested_flow) = extract_gradient_flow_info(nested) {
vanishing_layers += nested_flow.vanishing_layers;
exploding_layers += nested_flow.exploding_layers;
}
}
_ => {}
}
}
}
flow_balance = estimate_gradient_flow_balance(obj);
if !layer_gradients.is_empty() {
layer_gradients.sort_by(|a, b| a.partial_cmp(b).unwrap());
let median_idx = layer_gradients.len() / 2;
let median_grad = layer_gradients[median_idx];
if median_grad < vanishing_threshold * 10.0 {
vanishing_layers = vanishing_layers.max(layer_gradients.len() / 3);
}
}
Some(GradientFlowInfo {
vanishing_layers,
exploding_layers,
flow_balance,
})
}
pub(super) fn estimate_gradient_flow_balance(obj: &serde_json::Map<String, Value>) -> Option<f64> {
let mut layer_gradients = Vec::new();
let mut total_forward_flow = 0.0;
let mut total_backward_flow = 0.0;
let mut layer_count = 0;
for (key, value) in obj {
let is_layer_weight = key.contains("layer")
|| key.contains("block")
|| key.contains("weight")
|| key.contains("attention")
|| key.contains("ffn")
|| key.contains("transformer");
if is_layer_weight {
match value {
Value::Number(num) => {
if let Some(val) = num.as_f64() {
layer_gradients.push((key.clone(), val.abs()));
}
}
Value::Array(arr) => {
let mut sum = 0.0;
let mut count = 0;
for item in arr {
if let Value::Number(num) = item {
if let Some(val) = num.as_f64() {
sum += val.abs();
count += 1;
}
}
}
if count > 0 {
layer_gradients.push((key.clone(), sum / count as f64));
}
}
Value::Object(nested) => {
if let Some(nested_flow) = estimate_gradient_flow_balance(nested) {
layer_gradients.push((key.clone(), nested_flow));
}
}
_ => {}
}
}
}
if layer_gradients.is_empty() {
return None;
}
layer_gradients.sort_by(|a, b| a.0.cmp(&b.0));
let total_layers = layer_gradients.len();
for (i, (_layer_name, gradient_mag)) in layer_gradients.iter().enumerate() {
let layer_position = i as f64 / total_layers as f64;
if layer_position < 0.5 {
total_forward_flow += gradient_mag * (1.0 - layer_position);
} else {
total_backward_flow += gradient_mag * layer_position;
}
layer_count += 1;
}
if layer_count > 0 && total_backward_flow > 1e-12 {
let flow_ratio = total_forward_flow / total_backward_flow;
let normalized_balance = 1.0 / (1.0 + (flow_ratio - 1.0).abs());
Some(normalized_balance)
} else {
None
}
}