use fuel_ir::Result;
use fuel_tensor::Device;
use fuel_tensor::lazy::Tensor;
#[derive(Debug, Clone)]
pub struct NormTweakingConfig {
pub calibration_samples: usize,
pub target_layers: Vec<String>,
}
impl Default for NormTweakingConfig {
fn default() -> Self {
Self {
calibration_samples: 50,
target_layers: vec![],
}
}
}
#[derive(Debug, Clone)]
pub struct LayerStats {
pub name: String,
pub pre_quant_mean: f64,
pub pre_quant_std: f64,
pub post_quant_mean: f64,
pub post_quant_std: f64,
pub gamma: Vec<f32>,
pub beta: Vec<f32>,
}
impl LayerStats {
pub fn shift_magnitude(&self) -> f64 {
let mean_shift = (self.post_quant_mean - self.pre_quant_mean).abs();
let std_shift = (self.post_quant_std - self.pre_quant_std).abs();
mean_shift + std_shift
}
}
#[derive(Debug, Clone)]
pub struct LayerAdjustments {
pub layer_name: String,
pub gamma_scale: f64,
pub beta_shift: f64,
pub adjusted_gamma: Vec<f32>,
pub adjusted_beta: Vec<f32>,
}
pub struct NormTweaker {
config: NormTweakingConfig,
device: Device,
}
impl NormTweaker {
pub fn new(config: NormTweakingConfig, device: Device) -> Self {
Self { config, device }
}
pub fn device(&self) -> &Device {
&self.device
}
pub fn calibrate(&self, layer_stats: &[LayerStats]) -> Result<Vec<LayerAdjustments>> {
let mut adjustments = Vec::new();
for stats in layer_stats {
if !self.config.target_layers.is_empty()
&& !self
.config
.target_layers
.iter()
.any(|t| stats.name.contains(t))
{
continue;
}
adjustments.push(self.compute_adjustment(stats));
}
Ok(adjustments)
}
fn compute_adjustment(&self, stats: &LayerStats) -> LayerAdjustments {
let gamma_scale = if stats.post_quant_std > 1e-6 {
stats.pre_quant_std / stats.post_quant_std
} else {
1.0
};
let beta_shift = stats.pre_quant_mean - (stats.post_quant_mean * gamma_scale);
let adjusted_gamma: Vec<f32> = stats
.gamma
.iter()
.map(|&g| (g as f64 * gamma_scale) as f32)
.collect();
let adjusted_beta: Vec<f32> = stats
.beta
.iter()
.zip(stats.gamma.iter())
.map(|(&b, &g)| (b as f64 + g as f64 * beta_shift) as f32)
.collect();
LayerAdjustments {
layer_name: stats.name.clone(),
gamma_scale,
beta_shift,
adjusted_gamma,
adjusted_beta,
}
}
pub fn collect_statistics(
&self,
activations: &Tensor,
layer_name: &str,
gamma: &[f32],
beta: &[f32],
) -> Result<LayerStats> {
let flat = activations.flatten_all()?;
let mean = flat.mean_all().realize_f32()[0] as f64;
let variance = flat.var(0, 0.0)?.mean_all().realize_f32()[0] as f64;
let std = variance.sqrt();
Ok(LayerStats {
name: layer_name.to_string(),
pre_quant_mean: mean,
pre_quant_std: std,
post_quant_mean: 0.0,
post_quant_std: 0.0,
gamma: gamma.to_vec(),
beta: beta.to_vec(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn norm_tweaking_config_default() {
let config = NormTweakingConfig::default();
assert_eq!(config.calibration_samples, 50);
assert!(config.target_layers.is_empty());
}
#[test]
fn layer_stats_shift_magnitude() {
let stats = LayerStats {
name: "layer.0".to_string(),
pre_quant_mean: 0.0,
pre_quant_std: 1.0,
post_quant_mean: 0.1,
post_quant_std: 0.9,
gamma: vec![1.0; 128],
beta: vec![0.0; 128],
};
let shift = stats.shift_magnitude();
assert!(
(shift - 0.2).abs() < 1e-6,
"|0.1-0| + |0.9-1.0| = 0.2, got {shift}"
);
}
#[test]
fn compute_adjustment_scales_gamma_and_shifts_beta() {
let config = NormTweakingConfig::default();
let tweaker = NormTweaker::new(config, Device::cpu());
let stats = LayerStats {
name: "layer.0".to_string(),
pre_quant_mean: 0.0,
pre_quant_std: 1.0,
post_quant_mean: 0.1,
post_quant_std: 0.8,
gamma: vec![1.0; 4],
beta: vec![0.0; 4],
};
let adjustment = tweaker.compute_adjustment(&stats);
assert!(
(adjustment.gamma_scale - 1.25).abs() < 0.01,
"1.0 / 0.8 = 1.25, got {}",
adjustment.gamma_scale
);
assert!(
(adjustment.beta_shift - (-0.125)).abs() < 0.01,
"expected -0.125, got {}",
adjustment.beta_shift
);
}
#[test]
fn compute_adjustment_beta_shift_scales_by_each_channels_gamma() {
let config = NormTweakingConfig::default();
let tweaker = NormTweaker::new(config, Device::cpu());
let stats = LayerStats {
name: "layer.0".to_string(),
pre_quant_mean: 0.0,
pre_quant_std: 1.0,
post_quant_mean: 0.1,
post_quant_std: 0.8,
gamma: vec![1.0, 3.0],
beta: vec![0.0, 0.0],
};
let adjustment = tweaker.compute_adjustment(&stats);
let shift = adjustment.beta_shift as f32;
assert!(
(adjustment.adjusted_beta[0] - 1.0 * shift).abs() < 1e-4,
"channel 0 (gamma=1.0): expected beta + 1.0*shift = {}, got {}",
1.0 * shift,
adjustment.adjusted_beta[0]
);
assert!(
(adjustment.adjusted_beta[1] - 3.0 * shift).abs() < 1e-4,
"channel 1 (gamma=3.0): expected beta + 3.0*shift = {}, got {} -- \
a flat scalar add would give the SAME value as channel 0, which is wrong",
3.0 * shift,
adjustment.adjusted_beta[1]
);
}
#[test]
fn compute_adjustment_degenerate_post_quant_std_does_not_explode() {
let config = NormTweakingConfig::default();
let tweaker = NormTweaker::new(config, Device::cpu());
let stats = LayerStats {
name: "layer.0".to_string(),
pre_quant_mean: 1.0,
pre_quant_std: 1.0,
post_quant_mean: 0.5,
post_quant_std: 0.0,
gamma: vec![2.0],
beta: vec![0.0],
};
let adjustment = tweaker.compute_adjustment(&stats);
assert_eq!(
adjustment.gamma_scale, 1.0,
"near-zero post_quant_std must not scale gamma"
);
assert!(adjustment.gamma_scale.is_finite());
assert!(adjustment.beta_shift.is_finite());
}
#[test]
fn calibrate_filters_by_target_layers() {
let config = NormTweakingConfig {
calibration_samples: 50,
target_layers: vec!["attn".to_string()],
};
let tweaker = NormTweaker::new(config, Device::cpu());
let make = |name: &str| LayerStats {
name: name.to_string(),
pre_quant_mean: 0.0,
pre_quant_std: 1.0,
post_quant_mean: 0.0,
post_quant_std: 1.0,
gamma: vec![1.0],
beta: vec![0.0],
};
let stats = vec![
make("layer.0.attn_norm"),
make("layer.0.mlp_norm"),
make("layer.1.attn_norm"),
];
let adjustments = tweaker.calibrate(&stats).unwrap();
assert_eq!(
adjustments.len(),
2,
"only the 2 'attn' layers must pass the filter"
);
assert!(adjustments.iter().all(|a| a.layer_name.contains("attn")));
}
#[test]
fn collect_statistics_uses_population_variance() {
let device = Device::cpu();
let config = NormTweakingConfig::default();
let tweaker = NormTweaker::new(config, device.clone());
let data =
Tensor::from_f32(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3], &device).unwrap();
let stats = tweaker
.collect_statistics(&data, "test_layer", &[1.0], &[0.0])
.unwrap();
let want_var = 17.5_f64 / 6.0;
let want_std = want_var.sqrt();
assert!(
(stats.pre_quant_std - want_std).abs() < 1e-4,
"population std = sqrt(17.5/6) = {want_std}, got {}",
stats.pre_quant_std
);
}
#[test]
fn collect_statistics_variance_includes_between_position_spread() {
let device = Device::cpu();
let config = NormTweakingConfig::default();
let tweaker = NormTweaker::new(config, device.clone());
let rows = 4;
let data: Vec<f32> = (0..rows).flat_map(|_| [0.0_f32, 100.0_f32]).collect();
let tensor = Tensor::from_f32(data, vec![rows, 2], &device).unwrap();
let stats = tweaker
.collect_statistics(&tensor, "test_layer", &[1.0], &[0.0])
.unwrap();
assert!(
(stats.pre_quant_mean - 50.0).abs() < 1e-4,
"got {}",
stats.pre_quant_mean
);
assert!(
(stats.pre_quant_std - 50.0).abs() < 1.0,
"a within-column-only variance would report ~0 here; the true \
population std over the flattened distribution is 50, got {}",
stats.pre_quant_std
);
}
}