use pictor_core::quant_fp8::{BlockFP8E4M3, BlockFP8E5M2, QK_FP8};
use pictor_model::{
quantize_fp8_e4m3_smooth, quantize_fp8_e5m2_smooth, SmoothQuantCalibrator, SmoothQuantConfig,
SmoothQuantError,
};
fn dequant_e4m3(blocks: &[BlockFP8E4M3], n: usize) -> Vec<f32> {
let mut out = vec![0.0f32; n];
BlockFP8E4M3::dequant(blocks, &mut out).expect("dequant E4M3 failed");
out
}
fn dequant_e5m2(blocks: &[BlockFP8E5M2], n: usize) -> Vec<f32> {
let mut out = vec![0.0f32; n];
BlockFP8E5M2::dequant(blocks, &mut out).expect("dequant E5M2 failed");
out
}
#[test]
fn calibrator_records_per_channel_max_abs() {
let in_features = 4usize;
#[rustfmt::skip]
let activations = vec![
0.1_f32, 1.2, 0.0, -3.0,
0.5, 0.0, 0.0, 1.0,
0.3, 0.8, 0.0, 2.0,
];
let mut calib = SmoothQuantCalibrator::new(SmoothQuantConfig::default_alpha());
calib.record_activation("layer0", &activations, in_features);
let out_features = 4usize;
let weights = vec![1.0_f32; out_features * in_features];
let factors = calib
.smooth_factors("layer0", &weights, out_features)
.expect("smooth_factors failed");
assert_eq!(factors.len(), in_features, "wrong number of smooth factors");
assert!(
factors[2].is_finite() && factors[2] > 0.0,
"col 2 factor: {}",
factors[2]
);
assert!(
factors[3] > factors[0],
"factor[3]={} should be > factor[0]={} (col 3 has larger act_max)",
factors[3],
factors[0]
);
}
#[test]
fn calibrator_accumulates_across_batches() {
let in_features = 2usize;
let batch1 = vec![1.0_f32, 0.5, -0.3, 0.2];
let batch2 = vec![0.2_f32, -2.0, 0.1, 0.9];
let mut calib = SmoothQuantCalibrator::new(SmoothQuantConfig::default_alpha());
calib.record_activation("layer_acc", &batch1, in_features);
calib.record_activation("layer_acc", &batch2, in_features);
let out_features = 2usize;
let weights = vec![1.0_f32; out_features * in_features];
let factors = calib
.smooth_factors("layer_acc", &weights, out_features)
.expect("smooth_factors failed");
assert_eq!(factors.len(), in_features);
assert!(
factors[1] > factors[0],
"factor[1]={} should be > factor[0]={} after accumulation",
factors[1],
factors[0]
);
}
#[test]
fn calibrator_different_layers() {
let in_features = 2usize;
let acts_a = vec![10.0_f32, 1.0];
let acts_b = vec![1.0_f32, 10.0];
let mut calib = SmoothQuantCalibrator::new(SmoothQuantConfig::default_alpha());
calib.record_activation("layer_a", &acts_a, in_features);
calib.record_activation("layer_b", &acts_b, in_features);
let out_features = 2usize;
let weights = vec![1.0_f32; out_features * in_features];
let factors_a = calib
.smooth_factors("layer_a", &weights, out_features)
.expect("layer_a factors failed");
let factors_b = calib
.smooth_factors("layer_b", &weights, out_features)
.expect("layer_b factors failed");
assert!(
factors_a[0] > factors_a[1],
"layer_a: factor[0]={} should > factor[1]={}",
factors_a[0],
factors_a[1]
);
assert!(
factors_b[1] > factors_b[0],
"layer_b: factor[1]={} should > factor[0]={}",
factors_b[1],
factors_b[0]
);
}
#[test]
fn smooth_factors_returns_finite_nonzero() {
let in_features = 8usize;
let out_features = 4usize;
let mut activations = Vec::with_capacity(3 * in_features);
for t in 0..3usize {
for j in 0..in_features {
activations.push(((t + 1) as f32) * (j as f32 + 1.0) * 0.3);
}
}
let mut calib = SmoothQuantCalibrator::new(SmoothQuantConfig::new(0.5));
calib.record_activation("fc1", &activations, in_features);
let weights: Vec<f32> = (0..(out_features * in_features))
.map(|i| (i as f32 + 1.0) * 0.1)
.collect();
let factors = calib
.smooth_factors("fc1", &weights, out_features)
.expect("smooth_factors failed");
assert_eq!(factors.len(), in_features);
for (j, &f) in factors.iter().enumerate() {
assert!(f.is_finite(), "factor[{j}] is not finite: {f}");
assert!(f > 0.0, "factor[{j}] is not positive: {f}");
}
}
#[test]
fn smooth_factors_layer_not_found_error() {
let calib = SmoothQuantCalibrator::new(SmoothQuantConfig::default_alpha());
let weights = vec![1.0_f32; 16];
let result = calib.smooth_factors("nonexistent_layer", &weights, 2);
match result {
Err(SmoothQuantError::LayerNotFound(name)) => {
assert!(
name.contains("nonexistent_layer"),
"error should mention the layer name, got: {name}"
);
}
other => panic!("expected LayerNotFound, got: {:?}", other),
}
}
#[test]
fn quantize_fp8_e4m3_smooth_reduces_outlier_error() {
let out_features = 4usize;
let in_features = 8usize;
let n = out_features * in_features;
let mut weights = vec![1.0_f32; n];
for row in 0..out_features {
weights[row * in_features + 3] = 0.01;
}
let num_tokens = 4usize;
let mut activations = vec![1.0_f32; num_tokens * in_features];
for t in 0..num_tokens {
activations[t * in_features + 3] = 1000.0;
}
let blocks_raw = BlockFP8E4M3::quantize(&weights).expect("raw E4M3 quantize failed");
let recon_raw = dequant_e4m3(&blocks_raw, n);
let err_raw_col3: f32 = weights
.iter()
.zip(recon_raw.iter())
.enumerate()
.filter(|(idx, _)| idx % in_features == 3)
.map(|(_, (&w, &r))| (w - r).abs() / w.abs().max(1e-6))
.fold(0.0_f32, f32::max);
let mut calib = SmoothQuantCalibrator::new(SmoothQuantConfig::new(0.5));
calib.record_activation("linear_e4m3", &activations, in_features);
let smooth_factors = calib
.smooth_factors("linear_e4m3", &weights, out_features)
.expect("smooth_factors failed");
assert!(
smooth_factors[3] > 10.0,
"smooth_factors[3]={} should be > 10.0 (amplifying for tiny-weight col with large activation)",
smooth_factors[3]
);
let blocks_smooth =
quantize_fp8_e4m3_smooth(&weights, out_features, in_features, &smooth_factors)
.expect("smooth E4M3 quantize failed");
let deq_smooth_raw = dequant_e4m3(&blocks_smooth, n);
let recon_smooth: Vec<f32> = deq_smooth_raw
.iter()
.enumerate()
.map(|(idx, &v)| {
let col = idx % in_features;
let s = smooth_factors[col];
if s == 0.0 {
v
} else {
v / s
}
})
.collect();
let err_smooth_col3: f32 = weights
.iter()
.zip(recon_smooth.iter())
.enumerate()
.filter(|(idx, _)| idx % in_features == 3)
.map(|(_, (&w, &r))| (w - r).abs() / w.abs().max(1e-6))
.fold(0.0_f32, f32::max);
assert!(
err_smooth_col3 < err_raw_col3,
"smoothed E4M3 col-3 relative error ({err_smooth_col3:.4}) should be < unsmoothed ({err_raw_col3:.4})"
);
}
#[test]
fn quantize_fp8_e5m2_smooth_reduces_outlier_error() {
let out_features = 4usize;
let in_features = 8usize;
let n = out_features * in_features;
let mut weights = vec![1.0_f32; n];
for row in 0..out_features {
weights[row * in_features + 5] = 80.0;
}
let num_tokens = 4usize;
let mut activations = vec![1.0_f32; num_tokens * in_features];
for t in 0..num_tokens {
activations[t * in_features + 5] = 0.1;
}
let blocks_raw = BlockFP8E5M2::quantize(&weights).expect("raw E5M2 quantize failed");
let recon_raw = dequant_e5m2(&blocks_raw, n);
let err_raw_other: f32 = weights
.iter()
.zip(recon_raw.iter())
.enumerate()
.filter(|(idx, _)| idx % in_features != 5)
.map(|(_, (&w, &r))| (w - r).abs())
.fold(0.0_f32, f32::max);
let mut calib = SmoothQuantCalibrator::new(SmoothQuantConfig::new(0.5));
calib.record_activation("linear5m2", &activations, in_features);
let smooth_factors = calib
.smooth_factors("linear5m2", &weights, out_features)
.expect("smooth_factors failed");
assert!(
smooth_factors[5] < 0.5,
"smooth_factors[5]={} should be < 0.5",
smooth_factors[5]
);
let blocks_smooth =
quantize_fp8_e5m2_smooth(&weights, out_features, in_features, &smooth_factors)
.expect("smooth E5M2 quantize failed");
let deq_smooth_raw = dequant_e5m2(&blocks_smooth, n);
let recon_smooth: Vec<f32> = deq_smooth_raw
.iter()
.enumerate()
.map(|(idx, &v)| {
let col = idx % in_features;
let s = smooth_factors[col];
if s == 0.0 {
v
} else {
v / s
}
})
.collect();
let err_smooth_other: f32 = weights
.iter()
.zip(recon_smooth.iter())
.enumerate()
.filter(|(idx, _)| idx % in_features != 5)
.map(|(_, (&w, &r))| (w - r).abs())
.fold(0.0_f32, f32::max);
assert!(
err_smooth_other < err_raw_other,
"smoothed E5M2 non-outlier error ({err_smooth_other:.6}) should be < unsmoothed ({err_raw_other:.6})"
);
}
#[test]
#[should_panic(expected = "in_features mismatch")]
fn calibrator_in_features_mismatch() {
let mut calib = SmoothQuantCalibrator::new(SmoothQuantConfig::default_alpha());
calib.record_activation("layer_mismatch", &[1.0_f32; 4], 4);
calib.record_activation("layer_mismatch", &[1.0_f32; 6], 6);
}
#[test]
fn layer_count_tracks_unique_layers() {
let mut calib = SmoothQuantCalibrator::new(SmoothQuantConfig::default_alpha());
assert_eq!(calib.layer_count(), 0);
calib.record_activation("layer_x", &[1.0_f32, 2.0], 2);
assert_eq!(calib.layer_count(), 1);
calib.record_activation("layer_y", &[0.5_f32, 0.3], 2);
assert_eq!(calib.layer_count(), 2);
calib.record_activation("layer_z", &[0.1_f32, 0.9], 2);
assert_eq!(calib.layer_count(), 3);
calib.record_activation("layer_x", &[0.2_f32, 0.4], 2);
assert_eq!(
calib.layer_count(),
3,
"re-recording existing layer should not increase count"
);
}
#[test]
fn has_layer_returns_true_after_recording() {
let mut calib = SmoothQuantCalibrator::new(SmoothQuantConfig::default_alpha());
calib.record_activation("my_layer", &[1.0_f32, -1.0], 2);
assert!(
calib.has_layer("my_layer"),
"has_layer should be true after recording"
);
}
#[test]
fn has_layer_returns_false_before_recording() {
let calib = SmoothQuantCalibrator::new(SmoothQuantConfig::default_alpha());
assert!(
!calib.has_layer("not_recorded"),
"has_layer should be false for unrecorded layer"
);
}
#[test]
fn quantize_fp8_e4m3_smooth_output_size() {
let out_features = 8usize;
let in_features = 8usize;
let n = out_features * in_features; let expected_blocks = n / QK_FP8;
let weights: Vec<f32> = (0..n).map(|i| (i as f32) * 0.01 + 0.1).collect();
let activations: Vec<f32> = (0..(2 * in_features))
.map(|i| (i as f32) * 0.05 + 0.1)
.collect();
let mut calib = SmoothQuantCalibrator::new(SmoothQuantConfig::default_alpha());
calib.record_activation("size_test", &activations, in_features);
let factors = calib
.smooth_factors("size_test", &weights, out_features)
.expect("smooth_factors failed");
let blocks = quantize_fp8_e4m3_smooth(&weights, out_features, in_features, &factors)
.expect("smooth E4M3 quantize failed");
assert_eq!(
blocks.len(),
expected_blocks,
"expected {expected_blocks} blocks for {n} weights, got {}",
blocks.len()
);
}