1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
//! gemv-e4m3-bench — THE Q2 MEASUREMENT of the FP8-ST v3 gate (lane/fp8-v3-gate, 2026-08-05).
//!
//! QUESTION. The ship path (ARM B') dequants FP8 checkpoints to a Q8_0 slab at load, so decode pays
//! Q8_0's 1.0625 B/weight (34 B per 32 weights: 32 int8 + one fp16 scale). Native e4m3 is exactly
//! 1.0 B/weight, i.e. a 5.88% smaller weight stream, and m=1 decode is weight-stream-bound. Is that
//! arithmetic realized as decode time?
//!
//! WHAT MADE THIS A BOUNDED WRITE. The native-e4m3 m=1 GEMV ALREADY EXISTS and already ships behind
//! MEMRA_ST_E4M3=1: `qmatvec_e4m3_mmvq` (cu/qmatvec.cu, body `e4m3_row_dot`) reads the raw checkpoint
//! e4m3 bytes as its weight stream — no dequant, row_bytes == in_f — against the same q8_1 activation
//! every fast decode path produces. Its correctness is already gated at m=1 by kernel-check (f64 CPU
//! e4m3 reference, plus grid.y=m and _b2/_b4/_b8 bit-parity arms). So NO new kernel was needed for
//! this question: what was missing, and all this bin adds, is the A/B PERF measurement. The kernel
//! shipped without one — its only prior evidence was end-to-end.
//!
//! THE COMPARISON. Same in_f/out_f, same m=1, same activation (both arms ride `qmatvec_mmvq_raw`,
//! which quantizes the SAME f32 x to q8_1 and launches the warp-per-row MMVQ for the given qtype):
//! arm E4M3 : QT_F8_E4M3, row_bytes = in_f -> out_f * in_f bytes
//! arm Q8_0 : QT_Q8_0, row_bytes = in_f/32 * 34 -> out_f * in_f/32 * 34 bytes
//! `ratio = t_q8_0 / t_e4m3` (>1 means e4m3 is faster). The byte ratio is fixed at 1.0625, so a
//! bandwidth-bound pair should land near +6.25pp and an arithmetic-bound one below it — that gap is
//! the finding, because the two arms are NOT the same arithmetic: Q8_0 does 8 dp4a into s32 per
//! 32-block, e4m3 does 8 cvt + 16 fmaf in f32. This bin measures TIME; it makes no exactness claim
//! (kernel-check owns the e4m3 GEMV's numeric gate, and model-level equivalence between the two
//! CONTAINERS is v2's teacher-forced + NLL protocol, not a bit-identity question).
//!
//! DRAM-COLD DISCIPLINE. Decode re-reads the whole weight from HBM every tick, so an L2-resident
//! measurement would be a fiction. Each shape allocates `copies` independent weight buffers sized so
//! the rotation set is past L2 and rotates through them, so consecutive launches never re-read the
//! same bytes. Both arms rotate identically.
//!
//! PROTOCOL: warm up both arms, then iters x (E4M3 timed, Q8_0 timed) INTERLEAVED inside the loop so
//! both share one clock/thermal regime; median of per-iteration times. Run under
//! flock /tmp/memra-5090.lock. GPU temp is printed at entry and exit for the thermal-regime record.
//!
//! THE BLOCK-128 ARM (lane/fp8-blk128-decode, 2026-08-05). A third arm was added, and it is the
//! VERDICT INSTRUMENT for that lane: `qmatvec_e4m3_blk_mmvq` over the same raw e4m3 plane plus a
//! resident f32 `[ceil(out_f/128), ceil(in_f/128)]` scale grid — the Qwen-official FP8 class
//! (`weight_block_size [128,128]`, the shape Qwen3.6-FP8 ships and Qwen3.8-FP8 is expected to).
//! WHY THIS BIN CARRIES THE VERDICT rather than an end-to-end run: no 27B-class block-128 artifact
//! is staged on this box (the official Qwen3.6-27B-FP8 is 29 GB and lives on the remote 2x5090),
//! and per the owner rule verdicts anchor on 27B shapes only — the 1.7B synth checkpoint is a
//! bring-up instrument, never a verdict. So the verdict is the per-shape m=1 A/B on the 27B
//! projection shapes with synthetic block-128 weights, which is exactly what these rows are.
//!
//! Arms, all three interleaved inside one iteration so they share one clock/thermal regime:
//! arm E4M3 : QT_F8_E4M3, row_bytes = in_f -> 1.0 B/weight (per-tensor scale)
//! arm BLK : qmatvec_e4m3_blk_mmvq, row_bytes = in_f -> 1.0 B/weight + the grid
//! arm Q8_0 : QT_Q8_0, row_bytes = in_f/32 * 34 -> 1.0625 B/weight (the ARM B' floor)
//! `ratio_blk = t_q8_0 / t_blk` is the lane's number; `ratio` (per-tensor) is re-measured in the
//! same hold as a CONTROL ANCHOR — it has a published value from lane/fp8-v3-gate, so agreement
//! validates the harness and `blk_vs_e4m3` isolates what the per-k128 scale load actually costs.
//! The grid is ~21 KB even for the widest 27B projection (0.02% of the weight bytes) and is
//! allocated ONE PER WEIGHT COPY, rotated with it — which is exactly a real model's geometry (each
//! projection owns its own grid and re-reads it every decode tick).
//!
//! usage: gemv-e4m3-bench [iters] [27b|1p7b] (default iters=200, 27b)
use memra_engine::Engine;
use std::time::Instant;
/// Raw e4m3 weight rows, [out_f, in_f] row-major, row stride == in_f. Magnitude 0x7F is the e4m3 NaN
/// code (hardware NaN, host convention 0.0), so it is excluded — a NaN would make the accumulator
/// path data-dependent and is refused by the real dispatch anyway.
fn synth_e4m3(out_f: usize, in_f: usize) -> Vec<u8> {
let mut w = vec![0u8; out_f * in_f];
let mut s: u32 = 0x1234_5678;
for b in w.iter_mut() {
s = s.wrapping_mul(1664525).wrapping_add(1013904223);
let mag = ((s >> 16) & 0x7F) as u8;
// never 0x7F (NaN); 0x30 is a benign mid-range substitute.
let mag = if mag == 0x7F { 0x30 } else { mag };
*b = mag | ((((s >> 8) & 1) as u8) << 7);
}
w
}
/// Block-128 scale grid, [ceil(out_f/128), ceil(in_f/128)] f32 in F8BlockGrid order. Magnitudes in
/// the range a real Qwen `weight_scale_inv` shows (~2^-6..2^-2), so the accumulator stays finite.
fn synth_blk_grid(out_f: usize, in_f: usize) -> Vec<f32> {
let (rows, cols) = (out_f.div_ceil(128), in_f.div_ceil(128));
let mut g = vec![0f32; rows * cols];
let mut s: u32 = 0x5BF0_3635;
for v in g.iter_mut() {
s = s.wrapping_mul(1664525).wrapping_add(1013904223);
*v = (2f32).powi(-6 + ((s >> 20) % 5) as i32);
}
g
}
/// Raw ggml block_q8_0 weight rows: in_f/32 blocks per row, 34 B each (fp16 scale + 32 int8).
fn synth_q8_0(out_f: usize, in_f: usize) -> Vec<u8> {
let nblk = in_f / 32;
let mut w = vec![0u8; out_f * nblk * 34];
let mut s: u32 = 0x9E37_79B9;
for blk in w.chunks_exact_mut(34) {
// d = f16 0x1400 = 2^-10 (fixed, small, valid — keeps acc finite; same trick as q5issue).
blk[0] = 0x00;
blk[1] = 0x14;
for q in blk[2..].iter_mut() {
s = s.wrapping_mul(1664525).wrapping_add(1013904223);
*q = (s >> 24) as u8;
}
}
w
}
fn gpu_temp() -> String {
std::process::Command::new("nvidia-smi")
.args([
"--query-gpu=temperature.gpu,clocks.sm",
"--format=csv,noheader",
])
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().replace('\n', " | "))
.unwrap_or_else(|| "n/a".into())
}
fn median(v: &mut [f64]) -> f64 {
v.sort_by(f64::total_cmp);
v[v.len() / 2]
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let iters: usize = std::env::args()
.nth(1)
.and_then(|s| s.parse().ok())
.unwrap_or(200);
let set = std::env::args().nth(2).unwrap_or_else(|| "27b".to_string());
let e = Engine::new(0)?;
// The warp-per-row MMVQ dispatch reads this per call (house style); single-threaded here.
unsafe {
std::env::set_var("MEMRA_MMVQ", "1");
}
println!(
"GPU: {} iters={iters} shapes={set} temp_in: {}",
e.ctx().name()?,
gpu_temp()
);
println!("m=1 GEMV, THREE arms: native per-tensor e4m3 (qmatvec_e4m3_mmvq, 1.0 B/w) | native");
println!(
" BLOCK-128 e4m3 (qmatvec_e4m3_blk_mmvq, 1.0 B/w + per-k128 f32 grid) | Q8_0 MMVQ floor"
);
println!(" (ARM B', 1.0625 B/w). DRAM-cold (rotated copies, grid rotates with its weight);");
println!(
" all three interleaved per iter; median. ratio_blk = t_q8_0/t_blk is the lane verdict;"
);
println!(" ratio (per-tensor) is the control anchor vs lane/fp8-v3-gate's published value.");
println!(
"{:<26} {:>3} {:>9} {:>9} {:>9} {:>9} {:>9} {:>9} {:>9}",
"shape in->out",
"cp",
"e4m3_us",
"blk_us",
"q8_0_us",
"ratio",
"ratio_blk",
"blk/e4m3",
"blk_GB/s"
);
// The v2 shape sheet, verbatim.
let shapes_27b: [(usize, usize, &str); 6] = [
(5120, 12288, "q_proj"),
(5120, 1024, "k/v_proj"),
(6144, 5120, "o_proj"),
(5120, 17408, "gate/up_proj"),
(17408, 5120, "down_proj"),
(5120, 5120, "square-ref"),
];
let shapes_1p7b: [(usize, usize, &str); 5] = [
(2048, 2048, "q_proj"),
(2048, 1024, "k/v_proj"),
(2048, 2048, "o_proj"),
(2048, 6144, "gate/up_proj"),
(6144, 2048, "down_proj"),
];
let shapes: Vec<(usize, usize, &str)> = if set == "1p7b" {
shapes_1p7b.to_vec()
} else {
shapes_27b.to_vec()
};
let mut sum_ln = 0.0f64;
let mut sum_ln_blk = 0.0f64;
let mut n = 0usize;
// JSONL receipt: raw per-shape rows next to the summary (evidence discipline — a claim whose
// raw runs exist nowhere in the repo is not evidence).
let mut rows: Vec<String> = Vec::new();
for (in_f, out_f, label) in shapes {
let rb_e4m3 = in_f;
let rb_q8_0 = (in_f / 32) * 34;
let wb_e4m3 = out_f * rb_e4m3;
let wb_q8_0 = out_f * rb_q8_0;
let scols = in_f.div_ceil(128);
// Enough copies that the rotation set is past L2 by a wide margin, capped so the set stays
// well inside VRAM alongside the sibling lane's allocation. Three arms now share the
// budget, so the divisor counts all three planes per copy.
let copies = (768_000_000usize / (wb_q8_0 + 2 * wb_e4m3)).clamp(1, 64);
let h_e4m3 = synth_e4m3(out_f, in_f);
let h_q8_0 = synth_q8_0(out_f, in_f);
let h_grid = synth_blk_grid(out_f, in_f);
let d_e4m3: Vec<_> = (0..copies)
.map(|_| e.htod_bytes(&h_e4m3))
.collect::<Result<_, _>>()?;
// The BLK arm gets its OWN weight copies, not a share of the per-tensor arm's — otherwise
// the second arm of each iteration would read bytes the first just pulled into L2 and the
// DRAM-cold discipline would be broken for exactly the arm under test.
let d_blk: Vec<_> = (0..copies)
.map(|_| e.htod_bytes(&h_e4m3))
.collect::<Result<_, _>>()?;
let d_grid: Vec<_> = (0..copies)
.map(|_| e.htod(&h_grid))
.collect::<Result<_, _>>()?;
let d_q8_0: Vec<_> = (0..copies)
.map(|_| e.htod_bytes(&h_q8_0))
.collect::<Result<_, _>>()?;
drop(h_e4m3);
drop(h_q8_0);
let x: Vec<f32> = (0..in_f).map(|i| ((i % 17) as f32 - 8.0) * 0.1).collect();
let xd = e.htod(&x)?;
// warmup all three arms
for c in 0..copies.min(4) {
let _ = e.qmatvec_mmvq_raw(
&d_e4m3[c],
&xd,
1,
in_f,
out_f,
memra_engine::QT_F8_E4M3,
rb_e4m3,
false,
)?;
let _ = e.qmatvec_e4m3_blk_mmvq_raw(
&d_blk[c], &xd, &d_grid[c], 1, in_f, out_f, rb_e4m3, scols,
)?;
let _ = e.qmatvec_mmvq_raw(
&d_q8_0[c],
&xd,
1,
in_f,
out_f,
memra_engine::QT_Q8_0,
rb_q8_0,
false,
)?;
}
e.stream().synchronize()?;
let mut t_f8: Vec<f64> = Vec::with_capacity(iters);
let mut t_bk: Vec<f64> = Vec::with_capacity(iters);
let mut t_q8: Vec<f64> = Vec::with_capacity(iters);
for i in 0..iters {
let c = i % copies;
// INTERLEAVED: all three arms share one clock/thermal regime per iteration.
let t0 = Instant::now();
let _ = e.qmatvec_mmvq_raw(
&d_e4m3[c],
&xd,
1,
in_f,
out_f,
memra_engine::QT_F8_E4M3,
rb_e4m3,
false,
)?;
e.stream().synchronize()?;
t_f8.push(t0.elapsed().as_secs_f64());
let t1 = Instant::now();
let _ = e.qmatvec_e4m3_blk_mmvq_raw(
&d_blk[c], &xd, &d_grid[c], 1, in_f, out_f, rb_e4m3, scols,
)?;
e.stream().synchronize()?;
t_bk.push(t1.elapsed().as_secs_f64());
let t2 = Instant::now();
let _ = e.qmatvec_mmvq_raw(
&d_q8_0[c],
&xd,
1,
in_f,
out_f,
memra_engine::QT_Q8_0,
rb_q8_0,
false,
)?;
e.stream().synchronize()?;
t_q8.push(t2.elapsed().as_secs_f64());
}
let a = median(&mut t_f8);
let k = median(&mut t_bk);
let b = median(&mut t_q8);
let ratio = b / a;
let ratio_blk = b / k;
println!(
"{:<26} {:>3} {:>9.2} {:>9.2} {:>9.2} {:>8.4}x {:>8.4}x {:>8.4}x {:>9.1}",
format!("{label} {in_f}->{out_f}"),
copies,
a * 1e6,
k * 1e6,
b * 1e6,
ratio,
ratio_blk,
a / k,
wb_e4m3 as f64 / k / 1e9
);
rows.push(format!(
"{{\"shape\":\"{label}\",\"in_f\":{in_f},\"out_f\":{out_f},\"copies\":{copies},\
\"iters\":{iters},\"e4m3_us\":{:.3},\"blk_us\":{:.3},\"q8_0_us\":{:.3},\
\"ratio_e4m3\":{ratio:.5},\"ratio_blk\":{ratio_blk:.5},\"blk_over_e4m3\":{:.5},\
\"blk_GBs\":{:.2},\"grid_bytes\":{}}}",
a * 1e6,
k * 1e6,
b * 1e6,
a / k,
wb_e4m3 as f64 / k / 1e9,
out_f.div_ceil(128) * scols * 4
));
sum_ln += ratio.ln();
sum_ln_blk += ratio_blk.ln();
n += 1;
}
let geo = (sum_ln / n as f64).exp();
let geo_blk = (sum_ln_blk / n as f64).exp();
println!(
"GEOMEAN ratio (q8_0/e4m3, CONTROL ANCHOR) over {n} shapes: {geo:.4}x => delta_pp {:+.2}",
100.0 * (geo - 1.0)
);
println!(
"GEOMEAN ratio_blk (q8_0/blk128, THE LANE VERDICT) over {n} shapes: {geo_blk:.4}x => delta_pp {:+.2}",
100.0 * (geo_blk - 1.0)
);
println!("byte-stream ceiling: 34/32 = 1.0625x => +6.25pp if perfectly bandwidth-bound");
println!("temp_out: {}", gpu_temp());
if let Ok(p) = std::env::var("MEMRA_GEMV_JSONL") {
use std::io::Write;
let mut f = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&p)?;
for r in &rows {
writeln!(f, "{r}")?;
}
println!("jsonl rows appended: {} -> {p}", rows.len());
}
Ok(())
}