int8_shape_bench/
int8_shape_bench.rs1use ftts_kernels::f32ref;
17use ftts_kernels::int8::{Int8Tier, QuantizedMatrix, linear_q8, quantize_row_q8};
18use std::hint::black_box;
19use std::time::Instant;
20
21const SHAPES: &[(&str, usize, usize)] = &[
23 ("q_proj/head 2048x1024", 2048, 1024),
24 ("k/v_proj 1024x1024", 1024, 1024),
25 ("o_proj 1024x2048", 1024, 2048),
26 ("gate/up 3072x1024", 3072, 1024),
27 ("down_proj 1024x3072", 1024, 3072),
28];
29
30const ROUNDS: usize = 12;
31const WARMUP_ROUNDS: usize = 2;
32
33fn pseudo_random_f32(len: usize, seed: u64) -> Vec<f32> {
34 let mut state = seed;
35 (0..len)
36 .map(|_| {
37 state = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
38 let mut z = state;
39 z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
40 z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
41 ((z >> 40) as f32 / (1u64 << 23) as f32) - 1.0
42 })
43 .collect()
44}
45
46struct Stats {
47 mean_us: f64,
48 cv_percent: f64,
49}
50
51fn stats(samples: &[f64]) -> Stats {
52 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
53 let variance =
54 samples.iter().map(|s| (s - mean) * (s - mean)).sum::<f64>() / samples.len() as f64;
55 Stats {
56 mean_us: mean,
57 cv_percent: 100.0 * variance.sqrt() / mean,
58 }
59}
60
61#[allow(clippy::too_many_lines)]
62fn main() {
63 let tiers: Vec<Int8Tier> = Int8Tier::available();
64 println!("int8 shape bench — tiers available: {:?}", {
65 tiers.iter().map(|t| t.as_str()).collect::<Vec<_>>()
66 });
67 println!(
68 "interleaved rounds={ROUNDS} (+{WARMUP_ROUNDS} warmup); per-sample = mean over calls in one round; cv% over rounds"
69 );
70
71 for &m in &[1_usize, 16] {
72 println!(
73 "\n== m = {m} {} ==",
74 if m == 1 {
75 "(decode GEMV)"
76 } else {
77 "(seq-16 verify GEMM)"
78 }
79 );
80 for &(label, n, k) in SHAPES {
81 let calls: usize = (32 / m).max(2);
82 let weight = pseudo_random_f32(n * k, 0xbe0_0001 ^ (n as u64) << 20 ^ k as u64);
83 let x = pseudo_random_f32(m * k, 0xbe0_0002 ^ (m as u64) << 32 ^ k as u64);
84 let quantized = QuantizedMatrix::quantize(&weight, n, k);
85 let mut out = vec![0.0_f32; m * n];
86 let mut x_q = vec![0_i8; m * k];
87 let mut x_scales = vec![0.0_f32; m];
88
89 let mut f32_samples = Vec::with_capacity(ROUNDS);
91 let mut tier_samples: Vec<Vec<f64>> =
92 tiers.iter().map(|_| Vec::with_capacity(ROUNDS)).collect();
93
94 for round in 0..ROUNDS + WARMUP_ROUNDS {
95 let start = Instant::now();
97 for _ in 0..calls {
98 f32ref::linear(
99 black_box(&x),
100 black_box(&weight),
101 None,
102 m,
103 k,
104 n,
105 black_box(&mut out),
106 );
107 }
108 let f32_us = start.elapsed().as_secs_f64() * 1e6 / calls as f64;
109
110 let mut this_round = Vec::with_capacity(tiers.len());
112 for &tier in &tiers {
113 let start = Instant::now();
114 for _ in 0..calls {
115 for ((x_row, q_row), scale) in x
116 .chunks_exact(k)
117 .zip(x_q.chunks_exact_mut(k))
118 .zip(x_scales.iter_mut())
119 {
120 *scale = quantize_row_q8(black_box(x_row), q_row);
121 }
122 linear_q8(
123 black_box(&x_q),
124 black_box(&x_scales),
125 black_box(&quantized),
126 None,
127 m,
128 black_box(&mut out),
129 tier,
130 );
131 }
132 this_round.push(start.elapsed().as_secs_f64() * 1e6 / calls as f64);
133 }
134
135 if round >= WARMUP_ROUNDS {
136 f32_samples.push(f32_us);
137 for (samples, sample) in tier_samples.iter_mut().zip(&this_round) {
138 samples.push(*sample);
139 }
140 }
141 }
142
143 let f32_stats = stats(&f32_samples);
144 let f32_bytes = (n * k * 4 * m) as f64;
148 println!(
149 "{label} f32 {:9.1} us cv {:4.1}% ({:5.1} GB/s weight-stream)",
150 f32_stats.mean_us,
151 f32_stats.cv_percent,
152 f32_bytes / (f32_stats.mean_us * 1e-6) / 1e9,
153 );
154 for (tier, samples) in tiers.iter().zip(&tier_samples) {
155 let tier_stats = stats(samples);
156 let q8_bytes = (n * k) as f64;
157 let verdict = if tier_stats.cv_percent > 5.0 || f32_stats.cv_percent > 5.0 {
158 "REFUSED (cv>5%)"
159 } else {
160 ""
161 };
162 println!(
163 "{label} q8 {:9} {:9.1} us cv {:4.1}% ({:5.1} GB/s weight-stream) x{:.2} vs f32 {verdict}",
164 tier.as_str(),
165 tier_stats.mean_us,
166 tier_stats.cv_percent,
167 q8_bytes / (tier_stats.mean_us * 1e-6) / 1e9,
168 f32_stats.mean_us / tier_stats.mean_us,
169 );
170 }
171 }
172 }
173 println!(
174 "\nNOTE: ratios above compare routes inside this tree (self-comparison = maintenance),\nnever a pinned incumbent. cv%>5 rows are refused, not averaged."
175 );
176}