Merge pull request #181 from rohitg00/feat/rust-coverage-pass-1

Rust coverage pass 1: dev env, edge inference, audio, INT8 quant
This commit is contained in:
Rohit Ghumare
2026-05-25 21:03:39 +01:00
committed by GitHub
5 changed files with 670 additions and 3 deletions
+7 -3
View File
@@ -25,6 +25,7 @@
"has_quiz": true,
"has_notebook": false,
"code_files": [
"main.rs",
"verify.py"
],
"outputs": [
@@ -2134,7 +2135,8 @@
"has_quiz": true,
"has_notebook": false,
"code_files": [
"main.py"
"main.py",
"main.rs"
],
"outputs": [
{
@@ -3690,7 +3692,8 @@
"has_quiz": false,
"has_notebook": true,
"code_files": [
"main.py"
"main.py",
"main.rs"
],
"outputs": [
{
@@ -5438,7 +5441,8 @@
"has_quiz": true,
"has_notebook": false,
"code_files": [
"main.py"
"main.py",
"main.rs"
],
"outputs": [
{
@@ -0,0 +1,137 @@
// Lesson: Dev Environment (phase 00 / lesson 01)
// Topic: verify that the four-layer toolchain (system, package managers, runtimes, libs)
// is reachable from a Rust binary. Spawns each tool with `--version`, captures stdout,
// reports PASS/FAIL plus the parsed version string. Stdlib only.
// Refs:
// https://doc.rust-lang.org/std/process/struct.Command.html
// https://doc.rust-lang.org/std/process/struct.Output.html
// https://doc.rust-lang.org/book/ch12-00-an-io-project.html
// Build: rustc --edition 2021 code/main.rs -o /tmp/lesson_dev_env && /tmp/lesson_dev_env
use std::process::{Command, ExitCode};
struct Check {
name: &'static str,
program: &'static str,
args: &'static [&'static str],
optional: bool,
}
const CHECKS: &[Check] = &[
Check { name: "Git", program: "git", args: &["--version"], optional: false },
Check { name: "Python 3.10+", program: "python3", args: &["--version"], optional: false },
Check { name: "Node.js", program: "node", args: &["--version"], optional: false },
Check { name: "Rust (rustc)", program: "rustc", args: &["--version"], optional: false },
Check { name: "Cargo", program: "cargo", args: &["--version"], optional: false },
Check { name: "uv (Python)", program: "uv", args: &["--version"], optional: true },
Check { name: "pnpm", program: "pnpm", args: &["--version"], optional: true },
Check { name: "Julia", program: "julia", args: &["--version"], optional: true },
];
fn run_check(check: &Check) -> Result<String, String> {
let output = Command::new(check.program)
.args(check.args)
.output()
.map_err(|e| format!("{}: {}", check.program, e))?;
if !output.status.success() {
return Err(format!("exit code {:?}", output.status.code()));
}
let combined = if !output.stdout.is_empty() {
&output.stdout
} else {
&output.stderr
};
let raw = String::from_utf8_lossy(combined);
let line = raw.lines().next().unwrap_or("").trim().to_string();
if line.is_empty() {
Err("empty version output".to_string())
} else {
Ok(line)
}
}
fn parse_minor_python(version_line: &str) -> Option<(u32, u32)> {
let trimmed = version_line.trim_start_matches("Python").trim();
let mut parts = trimmed.split('.');
let major: u32 = parts.next()?.parse().ok()?;
let minor: u32 = parts.next()?.parse().ok()?;
Some((major, minor))
}
fn print_header() {
println!();
println!("=== AI Engineering from Scratch — Environment Check (Rust) ===");
println!();
println!("Layer 1 (system) -> Layer 2 (package managers) -> Layer 3 (runtimes) -> Layer 4 (libs)");
println!();
}
fn main() -> ExitCode {
print_header();
let mut required_pass = 0u32;
let mut required_total = 0u32;
let mut optional_pass = 0u32;
let mut optional_total = 0u32;
let mut python_ok = true;
println!("Required tools:");
for check in CHECKS.iter().filter(|c| !c.optional) {
required_total += 1;
match run_check(check) {
Ok(version) => {
if check.name.starts_with("Python") {
match parse_minor_python(&version) {
Some((major, minor)) if (major, minor) >= (3, 10) => {}
_ => {
println!(" [FAIL] {:<14} {} (need parseable Python 3.10+)", check.name, version);
python_ok = false;
continue;
}
}
}
required_pass += 1;
println!(" [PASS] {:<14} {}", check.name, version);
}
Err(why) => {
println!(" [FAIL] {:<14} {}", check.name, why);
if check.name.starts_with("Python") {
python_ok = false;
}
}
}
}
println!();
println!("Optional tools:");
for check in CHECKS.iter().filter(|c| c.optional) {
optional_total += 1;
match run_check(check) {
Ok(version) => {
optional_pass += 1;
println!(" [PASS] {:<14} {}", check.name, version);
}
Err(_) => {
println!(" [skip] {:<14} not installed", check.name);
}
}
}
println!();
println!("Summary: {}/{} required, {}/{} optional",
required_pass, required_total, optional_pass, optional_total);
if required_pass == required_total && python_ok {
println!();
println!("Environment is ready. Start with Phase 1.");
ExitCode::SUCCESS
} else {
println!();
println!("Fix the failed checks above, then run this again.");
ExitCode::from(1)
}
}
@@ -0,0 +1,187 @@
// Lesson: Real-Time Vision Edge Deployment (phase 04 / lesson 15)
// Topic: edge inference loop in Rust. Builds a tiny depthwise-separable conv block
// (the MobileNet primitive), runs it over a 160x160x3 input tensor, and reports
// p50/p95/p99 latency the way an on-device profiler would. Stdlib only.
// Refs:
// https://doc.rust-lang.org/std/time/struct.Instant.html
// https://arxiv.org/abs/1704.04861 (MobileNetV1: depthwise separable convolutions)
// https://pytorch.org/docs/stable/quantization.html (edge measurement discipline)
// Build: rustc --edition 2021 -O code/main.rs -o /tmp/lesson_edge && /tmp/lesson_edge
use std::time::Instant;
const H: usize = 160;
const W: usize = 160;
const C_IN: usize = 3;
const C_OUT: usize = 16;
const K: usize = 3;
const WARMUP: usize = 3;
const ITERS: usize = 20;
#[derive(Clone)]
struct Tensor {
data: Vec<f32>,
h: usize,
w: usize,
c: usize,
}
impl Tensor {
fn zeros(h: usize, w: usize, c: usize) -> Self {
Self { data: vec![0.0; h * w * c], h, w, c }
}
fn idx(&self, y: usize, x: usize, c: usize) -> usize {
(y * self.w + x) * self.c + c
}
}
// Cheap deterministic PRNG. Avoids pulling in rand for a stdlib-only lesson.
fn lcg(seed: &mut u64) -> f32 {
*seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
let bits = (*seed >> 33) as u32;
(bits as f32 / u32::MAX as f32) * 2.0 - 1.0
}
fn fill_random(t: &mut Tensor, seed: &mut u64) {
for v in t.data.iter_mut() {
*v = lcg(seed) * 0.5;
}
}
// Depthwise conv: one 3x3 kernel per input channel, no cross-channel mixing.
// This is the part MobileNet uses to cut FLOPs by ~9x vs a dense conv.
fn depthwise_conv(input: &Tensor, weights: &[f32]) -> Tensor {
let mut out = Tensor::zeros(input.h, input.w, input.c);
let pad = K / 2;
for y in 0..input.h {
for x in 0..input.w {
for c in 0..input.c {
let mut acc = 0.0;
for ky in 0..K {
for kx in 0..K {
let iy = y as isize + ky as isize - pad as isize;
let ix = x as isize + kx as isize - pad as isize;
if iy < 0 || ix < 0 || iy >= input.h as isize || ix >= input.w as isize {
continue;
}
let pixel = input.data[input.idx(iy as usize, ix as usize, c)];
let w_idx = c * K * K + ky * K + kx;
acc += pixel * weights[w_idx];
}
}
let oi = out.idx(y, x, c);
out.data[oi] = acc.max(0.0);
}
}
}
out
}
// Pointwise 1x1 conv: mixes channels. Together with the depthwise above this is
// one MobileNet block: ~8-9x cheaper than a full HxWxC_in x C_out 3x3 dense conv.
fn pointwise_conv(input: &Tensor, weights: &[f32], c_out: usize) -> Tensor {
let mut out = Tensor::zeros(input.h, input.w, c_out);
for y in 0..input.h {
for x in 0..input.w {
for co in 0..c_out {
let mut acc = 0.0;
for ci in 0..input.c {
let pixel = input.data[input.idx(y, x, ci)];
let w_idx = co * input.c + ci;
acc += pixel * weights[w_idx];
}
let oi = out.idx(y, x, co);
out.data[oi] = acc.max(0.0);
}
}
}
out
}
fn forward(input: &Tensor, dw_w: &[f32], pw_w: &[f32]) -> Tensor {
let dw = depthwise_conv(input, dw_w);
pointwise_conv(&dw, pw_w, C_OUT)
}
fn flops_per_pass() -> u64 {
let dw = (H * W * C_IN * K * K * 2) as u64;
let pw = (H * W * C_IN * C_OUT * 2) as u64;
dw + pw
}
fn percentile(sorted_ms: &[f64], pct: f64) -> f64 {
if sorted_ms.is_empty() {
return 0.0;
}
let idx = ((sorted_ms.len() as f64 - 1.0) * pct).round() as usize;
sorted_ms[idx]
}
fn main() {
let mut seed: u64 = 0xa1b2_c3d4_e5f6_0708;
let mut input = Tensor::zeros(H, W, C_IN);
fill_random(&mut input, &mut seed);
let mut dw_weights = vec![0.0f32; C_IN * K * K];
let mut pw_weights = vec![0.0f32; C_OUT * C_IN];
for w in dw_weights.iter_mut() { *w = lcg(&mut seed) * 0.1; }
for w in pw_weights.iter_mut() { *w = lcg(&mut seed) * 0.1; }
println!();
println!("=== Edge inference benchmark (Rust, single thread) ===");
println!();
println!("Model : depthwise 3x3 + pointwise 1x1 (one MobileNet block)");
println!("Input shape: {}x{}x{}", H, W, C_IN);
println!("Output ch : {}", C_OUT);
let flops = flops_per_pass();
println!("FLOPs/pass : {:.2} M", flops as f64 / 1e6);
println!();
println!("Warming up ({} iters, ignored)...", WARMUP);
for _ in 0..WARMUP {
let _ = forward(&input, &dw_weights, &pw_weights);
}
println!("Measuring ({} iters)...", ITERS);
let mut times_ms = Vec::with_capacity(ITERS);
for _ in 0..ITERS {
let t0 = Instant::now();
let out = forward(&input, &dw_weights, &pw_weights);
let dt = t0.elapsed().as_secs_f64() * 1000.0;
times_ms.push(dt);
std::hint::black_box(out);
}
let mut sorted = times_ms.clone();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
let p50 = percentile(&sorted, 0.50);
let p95 = percentile(&sorted, 0.95);
let p99 = percentile(&sorted, 0.99);
let mean: f64 = times_ms.iter().sum::<f64>() / times_ms.len() as f64;
let min = sorted[0];
let max = *sorted.last().unwrap();
println!();
println!("Latency (ms):");
println!(" p50 {:>8.2}", p50);
println!(" p95 {:>8.2}", p95);
println!(" p99 {:>8.2}", p99);
println!(" mean {:>8.2}", mean);
println!(" min {:>8.2}", min);
println!(" max {:>8.2}", max);
let throughput_fps = 1000.0 / p50;
let gflops_s = (flops as f64) / (p50 / 1000.0) / 1e9;
println!();
println!("Throughput (from p50):");
println!(" {:>5.1} fps {:>5.2} GFLOPs/s", throughput_fps, gflops_s);
println!();
println!("Edge measurement discipline (also enforced here):");
println!(" - {} warmup passes ignored to avoid cold-cache bias", WARMUP);
println!(" - fixed input resolution (production resolution must match)");
println!(" - p50 reported alongside p99 so tail latency is visible");
println!();
}
@@ -0,0 +1,157 @@
// Lesson: Real-Time Audio Processing (phase 06 / lesson 11)
// Topic: stream a 16 kHz mono sine wave through 20 ms frames, apply a gain stage
// and a 9-tap low-pass FIR filter, measure per-frame and aggregate throughput.
// This is the inner loop every voice agent runs under VAD/ASR/TTS.
// Refs:
// https://doc.rust-lang.org/std/time/struct.Instant.html
// https://en.wikipedia.org/wiki/Finite_impulse_response
// https://webrtc.googlesource.com/src/+/refs/heads/main/modules/audio_processing (20 ms frame convention)
// Build: rustc --edition 2021 -O code/main.rs -o /tmp/lesson_audio && /tmp/lesson_audio
use std::f32::consts::PI;
use std::time::Instant;
const SAMPLE_RATE: u32 = 16_000;
const FRAME_MS: u32 = 20;
const FRAME_LEN: usize = (SAMPLE_RATE / 1000 * FRAME_MS) as usize; // 320 samples
const TONE_HZ: f32 = 440.0;
const TOTAL_SECONDS: f32 = 2.0;
const GAIN_DB: f32 = -3.0;
// 9-tap symmetric low-pass FIR. Hand-tuned, sum ~= 1.0 so DC is preserved.
const FIR_TAPS: [f32; 9] = [
0.02, 0.06, 0.12, 0.18, 0.24, 0.18, 0.12, 0.06, 0.02,
];
fn db_to_linear(db: f32) -> f32 {
10f32.powf(db / 20.0)
}
fn synth_sine_frame(start_sample: u64, freq_hz: f32, sr: u32) -> Vec<f32> {
let mut frame = Vec::with_capacity(FRAME_LEN);
let two_pi_f_over_sr = 2.0 * PI * freq_hz / sr as f32;
for n in 0..FRAME_LEN {
let t = (start_sample + n as u64) as f32;
frame.push((two_pi_f_over_sr * t).sin());
}
frame
}
fn apply_gain(frame: &mut [f32], gain_lin: f32) {
for s in frame.iter_mut() {
*s *= gain_lin;
}
}
// Streaming FIR. `state` carries the last (taps-1) samples across frame boundaries
// so the filter sees a continuous signal, not 20 ms islands with edge artefacts.
fn fir_streaming(frame: &mut [f32], taps: &[f32], state: &mut Vec<f32>) {
let order = taps.len();
let mut buf = Vec::with_capacity(state.len() + frame.len());
buf.extend_from_slice(state);
buf.extend_from_slice(frame);
for n in 0..frame.len() {
let mut acc = 0.0;
for k in 0..order {
acc += taps[k] * buf[n + order - 1 - k];
}
frame[n] = acc;
}
let keep = order - 1;
state.clear();
state.extend_from_slice(&buf[buf.len() - keep..]);
}
fn rms(frame: &[f32]) -> f32 {
let sum_sq: f32 = frame.iter().map(|x| x * x).sum();
(sum_sq / frame.len() as f32).sqrt()
}
fn rms_dbfs(frame: &[f32]) -> f32 {
let r = rms(frame).max(1e-10);
20.0 * r.log10()
}
fn percentile(sorted_us: &[f64], pct: f64) -> f64 {
if sorted_us.is_empty() {
return 0.0;
}
let idx = ((sorted_us.len() as f64 - 1.0) * pct).round() as usize;
sorted_us[idx]
}
fn main() {
let total_samples = (SAMPLE_RATE as f32 * TOTAL_SECONDS) as u64;
let total_frames = (total_samples as usize) / FRAME_LEN;
let gain_lin = db_to_linear(GAIN_DB);
println!();
println!("=== Real-time audio benchmark (Rust, single thread) ===");
println!();
println!("Sample rate : {} Hz", SAMPLE_RATE);
println!("Frame size : {} ms ({} samples)", FRAME_MS, FRAME_LEN);
println!("Stream length: {:.1} s ({} frames)", TOTAL_SECONDS, total_frames);
println!("Tone : {} Hz sine", TONE_HZ);
println!("Gain stage : {:+.1} dB", GAIN_DB);
println!("FIR : {}-tap symmetric low-pass", FIR_TAPS.len());
println!();
let mut fir_state = vec![0.0f32; FIR_TAPS.len() - 1];
let mut per_frame_us: Vec<f64> = Vec::with_capacity(total_frames);
let mut rms_in_db = 0.0f32;
let mut rms_out_db = 0.0f32;
let wall = Instant::now();
for f in 0..total_frames {
let start_sample = (f * FRAME_LEN) as u64;
let mut frame = synth_sine_frame(start_sample, TONE_HZ, SAMPLE_RATE);
let t_frame = Instant::now();
if f == 0 { rms_in_db = rms_dbfs(&frame); }
apply_gain(&mut frame, gain_lin);
fir_streaming(&mut frame, &FIR_TAPS, &mut fir_state);
if f == 0 { rms_out_db = rms_dbfs(&frame); }
per_frame_us.push(t_frame.elapsed().as_secs_f64() * 1e6);
}
let wall_ms = wall.elapsed().as_secs_f64() * 1000.0;
let mut sorted = per_frame_us.clone();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
let p50 = percentile(&sorted, 0.50);
let p95 = percentile(&sorted, 0.95);
let p99 = percentile(&sorted, 0.99);
let mean = per_frame_us.iter().sum::<f64>() / per_frame_us.len() as f64;
let budget_us = (FRAME_MS as f64) * 1000.0;
let headroom = budget_us / p99.max(1e-9);
println!("Per-frame latency (us):");
println!(" p50 {:>9.2}", p50);
println!(" p95 {:>9.2}", p95);
println!(" p99 {:>9.2}", p99);
println!(" mean {:>9.2}", mean);
println!();
println!("Aggregate:");
println!(" wall time {:>8.2} ms", wall_ms);
println!(" realtime budget {:>8.2} ms ({} frames * {} ms)", total_frames as f64 * FRAME_MS as f64, total_frames, FRAME_MS);
println!(" realtime factor {:>8.1}x (wall/budget; lower is faster)", wall_ms / (total_frames as f64 * FRAME_MS as f64));
println!(" headroom per p99 {:>8.1}x (budget / p99)", headroom);
println!();
println!("Signal levels (frame 0):");
println!(" RMS in {:>7.2} dBFS", rms_in_db);
println!(" RMS out {:>7.2} dBFS (after {:+.1} dB gain + FIR)", rms_out_db, GAIN_DB);
println!();
if headroom >= 50.0 {
println!("Verdict: huge headroom. VAD + STT + LLM + TTS all fit in the 20 ms slot.");
} else if headroom >= 5.0 {
println!("Verdict: comfortable headroom. Streaming pipeline will fit.");
} else {
println!("Verdict: too slow. Pipeline will drop frames at this DSP cost.");
}
println!();
}
@@ -0,0 +1,182 @@
// Lesson: Quantization — INT8 / GPTQ / AWQ / GGUF (phase 10 / lesson 11)
// Topic: symmetric INT8 quantization of an FP32 weight vector. Computes scale
// from abs-max, rounds + clips to [-127, 127], dequantizes, reports MSE,
// max abs error, SNR, cosine similarity, and a bit-width sweep (8 / 4 / 2 bit).
// Refs:
// https://pytorch.org/docs/stable/quantization.html
// https://leimao.github.io/article/Neural-Networks-Quantization/
// https://arxiv.org/abs/2210.17323 (GPTQ)
// https://arxiv.org/abs/2306.00978 (AWQ)
// Build: rustc --edition 2021 -O code/main.rs -o /tmp/lesson_quant && /tmp/lesson_quant
use std::f64;
fn lcg(seed: &mut u64) -> f64 {
*seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
let bits = (*seed >> 11) as u64;
let unit = bits as f64 / (1u64 << 53) as f64;
unit * 2.0 - 1.0
}
// Box-Muller via the LCG, so we generate normal-ish floats without external crates.
fn randn(seed: &mut u64) -> f64 {
let u1 = (lcg(seed) + 1.0) / 2.0;
let u2 = (lcg(seed) + 1.0) / 2.0;
let u1 = u1.max(1e-12);
let r = (-2.0 * u1.ln()).sqrt();
r * (2.0 * std::f64::consts::PI * u2).cos()
}
struct QuantResult {
qmin: i32,
qmax: i32,
scale: f64,
quantized: Vec<i32>,
reconstructed: Vec<f64>,
}
fn quantize_symmetric(weights: &[f64], num_bits: u32) -> QuantResult {
let qmax = (1i32 << (num_bits - 1)) - 1;
let qmin = -qmax;
let abs_max = weights.iter().fold(0.0f64, |acc, &x| acc.max(x.abs()));
let scale = if abs_max == 0.0 { 1.0 } else { abs_max / qmax as f64 };
let mut quantized = Vec::with_capacity(weights.len());
let mut reconstructed = Vec::with_capacity(weights.len());
for &w in weights {
let q = (w / scale).round() as i32;
let q = q.max(qmin).min(qmax);
quantized.push(q);
reconstructed.push(q as f64 * scale);
}
QuantResult { qmin, qmax, scale, quantized, reconstructed }
}
struct ErrorReport {
mse: f64,
rmse: f64,
max_abs_error: f64,
snr_db: f64,
cosine: f64,
}
fn error_report(original: &[f64], reconstructed: &[f64]) -> ErrorReport {
let n = original.len() as f64;
let mut sum_sq_err = 0.0f64;
let mut max_abs = 0.0f64;
let mut signal_power = 0.0f64;
let mut dot = 0.0f64;
let mut norm_a = 0.0f64;
let mut norm_b = 0.0f64;
for (a, b) in original.iter().zip(reconstructed.iter()) {
let diff = a - b;
sum_sq_err += diff * diff;
max_abs = max_abs.max(diff.abs());
signal_power += a * a;
dot += a * b;
norm_a += a * a;
norm_b += b * b;
}
let mse = sum_sq_err / n;
let rmse = mse.sqrt();
let snr_db = if mse > 0.0 {
10.0 * (signal_power / n / mse).log10()
} else {
f64::INFINITY
};
let cosine = if norm_a > 0.0 && norm_b > 0.0 {
dot / (norm_a.sqrt() * norm_b.sqrt())
} else {
0.0
};
ErrorReport { mse, rmse, max_abs_error: max_abs, snr_db, cosine }
}
fn print_quant_summary(label: &str, weights: &[f64], r: &QuantResult, err: &ErrorReport) {
println!("[{}]", label);
println!(" range [qmin, qmax] {} .. {}", r.qmin, r.qmax);
println!(" scale (FP32 step) {:.8}", r.scale);
println!(" sample weights (10) {:?}", &weights[..10.min(weights.len())]
.iter().map(|w| format!("{:+.4}", w)).collect::<Vec<_>>());
println!(" quantized codes (10) {:?}", &r.quantized[..10.min(r.quantized.len())]);
println!(" dequantized (10) {:?}", &r.reconstructed[..10.min(r.reconstructed.len())]
.iter().map(|w| format!("{:+.4}", w)).collect::<Vec<_>>());
println!();
println!(" mse {:.10}", err.mse);
println!(" rmse {:.10}", err.rmse);
println!(" max |error| {:.10}", err.max_abs_error);
println!(" snr {:.2} dB", err.snr_db);
println!(" cosine similarity {:.10}", err.cosine);
println!();
}
fn fmt_bytes(b: u64) -> String {
let kb = b as f64 / 1024.0;
if kb < 1024.0 { format!("{:.2} KB", kb) } else { format!("{:.2} MB", kb / 1024.0) }
}
fn main() {
let mut seed: u64 = 42;
let n = 8192;
let mut weights: Vec<f64> = (0..n).map(|_| randn(&mut seed) * 0.02).collect();
weights[0] *= 25.0;
weights[123] *= 15.0;
weights[2048] *= 10.0;
let stats = {
let abs_vals: Vec<f64> = weights.iter().map(|x| x.abs()).collect();
let max = abs_vals.iter().fold(0.0f64, |a, &b| a.max(b));
let mean: f64 = abs_vals.iter().sum::<f64>() / abs_vals.len() as f64;
let var: f64 = abs_vals.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / abs_vals.len() as f64;
(max, mean, var.sqrt())
};
println!();
println!("=== INT8 quantization (Rust, stdlib only) ===");
println!();
println!("Tensor : 1D weight vector, n = {}", n);
println!("Distribution : Normal(0, 0.02) with 3 outlier weights");
println!(" max |w| {:.6}", stats.0);
println!(" mean |w| {:.6}", stats.1);
println!(" std |w| {:.6}", stats.2);
println!();
let r8 = quantize_symmetric(&weights, 8);
let err8 = error_report(&weights, &r8.reconstructed);
print_quant_summary("INT8 symmetric per-tensor", &weights, &r8, &err8);
println!("--- Bit-width sweep (symmetric per-tensor) ---");
println!(" {:>5} {:>10} {:>14} {:>10} {:>12} {:>10}",
"bits", "levels", "mse", "snr_db", "max |err|", "ratio_vs_fp32");
for bits in [16u32, 8, 4, 2] {
let r = quantize_symmetric(&weights, bits);
let er = error_report(&weights, &r.reconstructed);
let ratio = 32.0 / bits as f64;
let levels = (r.qmax - r.qmin + 1) as u64;
println!(" {:>5} {:>10} {:>14.10} {:>10.2} {:>12.6} {:>9.1}x",
bits, levels, er.mse, er.snr_db, er.max_abs_error, ratio);
}
println!();
let fp32_bytes = (n * 4) as u64;
let int8_bytes = (n * 1) as u64 + 8;
let int4_bytes = ((n + 1) / 2) as u64 + 8;
println!("--- Memory footprint ---");
println!(" FP32 weights {}", fmt_bytes(fp32_bytes));
println!(" INT8 + scale {} ({:.1}x smaller)", fmt_bytes(int8_bytes), fp32_bytes as f64 / int8_bytes as f64);
println!(" INT4 + scale {} ({:.1}x smaller)", fmt_bytes(int4_bytes), fp32_bytes as f64 / int4_bytes as f64);
println!();
println!("Takeaway:");
println!(" - INT8 keeps SNR well above 30 dB for normal weight distributions.");
println!(" - Outliers dominate scale: 3 outliers in {} weights inflate scale and ", n);
println!(" waste precision on the rest. Per-channel (or GPTQ/AWQ) helps.");
println!();
}