Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
4a4171b
docs(server): refresh dense batch benchmark
GuyPaddock May 24, 2026
b0705d9
docs(server): record release batch timings
GuyPaddock May 24, 2026
bd00d9c
docs(server): add longer decode batch evidence
GuyPaddock May 24, 2026
4131ea3
test(server): assert scheduler batch events
GuyPaddock May 24, 2026
00c79ce
feat(server): support head128 dense batch decode
GuyPaddock May 24, 2026
56ed5b4
fix(server): bound qwen dense batch validation
GuyPaddock May 24, 2026
2720698
test(server): cover qwen head128 packed prefill
GuyPaddock May 24, 2026
515d56f
test(server): gate head128 packed prefill diagnostics
GuyPaddock May 24, 2026
1948f6f
fix(server): admit qwen packed-prefix prefill
GuyPaddock May 24, 2026
a08dbff
test(server): validate staggered scheduler overlap
GuyPaddock May 24, 2026
59af747
feat(server): admit preferred compressed kv batching
GuyPaddock May 24, 2026
eb2aa46
feat(server): add compressed packed-prefix prefill
GuyPaddock May 24, 2026
72382f7
docs(perf): record compressed batch scheduler smoke
GuyPaddock May 24, 2026
7f3bcfd
feat(cuda): batch direct compressed kv attention
GuyPaddock May 24, 2026
a2501c0
feat(server): admit head128 compressed prefill
GuyPaddock May 25, 2026
cb5b239
feat(server): batch same-length qwen compressed prefill
GuyPaddock May 25, 2026
6363af7
test(cuda): strengthen qwen prefill fixture
GuyPaddock May 25, 2026
0578ce8
test(cuda): diagnose qwen mixed prefill
GuyPaddock May 25, 2026
dcf585c
feat(server): admit mixed qwen compressed prefill
GuyPaddock May 25, 2026
c1acb81
feat(server): promote dense head128 prefill batching
GuyPaddock May 25, 2026
5c6f19f
docs(perf): record qwen head128 scheduler timing
GuyPaddock May 25, 2026
b0fb851
feat(infer): select projection backend by memory budget
GuyPaddock May 25, 2026
6f9e573
feat(cuda): add fused q8 projection primitive
GuyPaddock May 25, 2026
b8b51c5
feat(cuda): add q8 projection benchmark
GuyPaddock May 25, 2026
755dcab
feat(cuda): optimize q8 decode projection
GuyPaddock May 25, 2026
1c18239
feat(cuda): optimize q8 prefill projection
GuyPaddock May 25, 2026
cc2ac21
feat(cuda): add shared q8 prefill projection
GuyPaddock May 25, 2026
2f7b89a
test(cuda): add q8 generation canary
GuyPaddock May 25, 2026
616d628
fix(infer): support q8 tied output embeddings
GuyPaddock May 25, 2026
b5e6216
test(cuda): compare qwen projection backends
GuyPaddock May 25, 2026
5f85257
test(cuda): measure q8 packed prefill
GuyPaddock May 25, 2026
46a34b6
feat(infer): auto-pack compressed q8 prefill
GuyPaddock May 25, 2026
22ca31f
feat(cuda): add qwen throughput probe
GuyPaddock May 25, 2026
adbccd6
feat(cuda): add f16 decode projection kernel
GuyPaddock May 25, 2026
d38c12c
perf(cuda): reduce qwen f16 decode stream waits
GuyPaddock May 25, 2026
6629f75
perf(cuda): fuse qwen decode projections
GuyPaddock May 25, 2026
b2f1cf5
perf(cuda): record fused residual norm experiment
GuyPaddock May 25, 2026
386ce76
chore(cuda): remove neutral residual norm fusion
GuyPaddock May 25, 2026
c71254c
perf(cuda): fuse q8 decode projections
GuyPaddock May 25, 2026
d188243
docs(perf): record residual norm fusion outcome
GuyPaddock May 25, 2026
39a3c98
perf(cuda): tune q8 large-k decode scheduling
GuyPaddock May 25, 2026
b8eb20a
perf(cuda): add q8 decode event diagnostics
GuyPaddock May 25, 2026
d178045
perf(cuda): add opt-in tiled q8 decode projection
GuyPaddock May 25, 2026
11cff45
perf(cuda): add q8 lm head argmax diagnostic
GuyPaddock May 25, 2026
54226d7
feat(cuda): add q4 gguf projection support
GuyPaddock May 26, 2026
6b5ebdb
perf(cuda): optimize qwen head128 decode attention
GuyPaddock May 26, 2026
faf3209
perf(cuda): add qwen decode diagnostics
GuyPaddock May 26, 2026
4a4fb28
perf(cuda): add shared q8 lm-head argmax diagnostic
GuyPaddock May 26, 2026
c10b544
perf(cuda): preserve q8 tiled gate-up experiment
GuyPaddock May 26, 2026
dcbbf49
perf(cuda): remove slower q8 gate-up tile
GuyPaddock May 26, 2026
fdc506b
perf(cuda): split q8 decode block work
GuyPaddock May 26, 2026
82c1a0c
perf(cuda): preserve head128 grouped attention experiment
GuyPaddock May 26, 2026
5e21b54
perf(cuda): remove slower head128 grouped attention path
GuyPaddock May 26, 2026
b8505a4
docs(perf): record grouped attention removal commit
GuyPaddock May 26, 2026
1138b93
docs(perf): refresh qwen q8 attention benchmark
GuyPaddock May 26, 2026
e361b74
perf(cuda): split q8 qkv decode blocks
GuyPaddock May 26, 2026
1929182
perf(cuda): preserve head128 group4 attention experiment
GuyPaddock May 26, 2026
b9c2194
perf(cuda): remove slower head128 group4 attention path
GuyPaddock May 26, 2026
e810b82
docs(perf): record group4 attention removal commit
GuyPaddock May 26, 2026
c436aeb
perf(cuda): add dense sink recent attention diagnostic
GuyPaddock May 26, 2026
c14c0d8
perf(cuda): tile q8 decode projection columns
GuyPaddock May 26, 2026
c6c2588
perf(cuda): preserve q8 mlp column tile experiment
GuyPaddock May 26, 2026
15b2f9d
perf(cuda): remove slower q8 mlp column tile
GuyPaddock May 26, 2026
0da8f39
docs(perf): clarify qwen q8 benchmark flags
GuyPaddock May 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 197 additions & 8 deletions AGENTS.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ harness = false
name = "gemm_fallback"
harness = false

[[bench]]
name = "q8_projection"
harness = false

[[bench]]
name = "attention"
harness = false
Expand Down
360 changes: 360 additions & 0 deletions benches/q8_projection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,360 @@
// Benchmark GGUF Q8_0 fused projection against existing F16 and materialized
// FP32 projection paths. Run with:
// M40LLM_ENABLE_NVCC=1 M40LLM_ENABLE_CUBLAS=1 cargo bench --features cuda --bench q8_projection

#[cfg(all(feature = "cuda", nvcc))]
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
#[cfg(not(all(feature = "cuda", nvcc)))]
use criterion::{criterion_group, criterion_main, Criterion};
#[cfg(all(feature = "cuda", nvcc))]
use m40_llm::cuda::CudaStream;
#[cfg(all(feature = "cuda", nvcc))]
use std::ffi::c_void;

#[cfg(all(feature = "cuda", nvcc))]
#[path = "../tests/cuda_env.rs"]
mod cuda_env;

#[cfg(all(feature = "cuda", nvcc))]
fn f32s_to_bytes(vals: &[f32]) -> Vec<u8> {
let mut out = Vec::with_capacity(vals.len() * 4);
for &v in vals {
out.extend_from_slice(&v.to_le_bytes());
}
out
}

#[cfg(all(feature = "cuda", nvcc))]
fn f32s_to_halves_bytes(vals: &[f32]) -> Vec<u8> {
let mut out = Vec::with_capacity(vals.len() * 2);
for &v in vals {
let bits = half::f16::from_f32(v).to_bits();
out.push((bits & 0xff) as u8);
out.push((bits >> 8) as u8);
}
out
}

#[cfg(all(feature = "cuda", nvcc))]
fn bytes_to_f32s(bytes: &[u8]) -> Vec<f32> {
bytes
.chunks_exact(4)
.map(|ch| f32::from_le_bytes([ch[0], ch[1], ch[2], ch[3]]))
.collect()
}

#[cfg(all(feature = "cuda", nvcc))]
fn cpu_gguf_gemm_f32(a: &[f32], b: &[f32], m: usize, n: usize, k: usize) -> Vec<f32> {
let mut c = vec![0f32; m * n];
for row in 0..m {
for col in 0..n {
let mut acc = 0.0f32;
for kk in 0..k {
acc += a[row * k + kk] * b[col * k + kk];
}
c[row * n + col] = acc;
}
}
c
}

#[cfg(all(feature = "cuda", nvcc))]
fn q8_0_gguf_bytes_from_dequantized(vals: &[f32], n: usize, k: usize) -> Vec<u8> {
const QK: usize = 32;
const BLOCK_BYTES: usize = 34;
let blocks_per_col = k.div_ceil(QK);
let mut out = vec![0u8; n * blocks_per_col * BLOCK_BYTES];
for col in 0..n {
for block in 0..blocks_per_col {
let start = block * QK;
let end = (start + QK).min(k);
let scale = vals[col * k + start..col * k + end]
.iter()
.fold(0.0f32, |acc, v| acc.max(v.abs()))
/ 127.0;
let scale = if scale == 0.0 { 1.0 } else { scale };
let base = (col * blocks_per_col + block) * BLOCK_BYTES;
let scale_bits = half::f16::from_f32(scale).to_bits();
out[base] = (scale_bits & 0xff) as u8;
out[base + 1] = (scale_bits >> 8) as u8;
for idx in 0..QK {
let k_idx = start + idx;
let q = if k_idx < k {
(vals[col * k + k_idx] / scale).round().clamp(-128.0, 127.0) as i8
} else {
0
};
out[base + 2 + idx] = q as u8;
}
}
}
out
}

#[cfg(all(feature = "cuda", nvcc))]
fn q8_0_gguf_dequantize(bytes: &[u8], n: usize, k: usize) -> Vec<f32> {
const QK: usize = 32;
const BLOCK_BYTES: usize = 34;
let blocks_per_col = k.div_ceil(QK);
let mut out = vec![0f32; n * k];
for col in 0..n {
for block in 0..blocks_per_col {
let base = (col * blocks_per_col + block) * BLOCK_BYTES;
let scale_bits = u16::from_le_bytes([bytes[base], bytes[base + 1]]);
let scale = half::f16::from_bits(scale_bits).to_f32();
for idx in 0..QK {
let k_idx = block * QK + idx;
if k_idx < k {
out[col * k + k_idx] = f32::from(bytes[base + 2 + idx] as i8) * scale;
}
}
}
}
out
}

#[cfg(all(feature = "cuda", nvcc))]
fn max_mean_abs_diff(a: &[f32], b: &[f32]) -> (f32, f32) {
let mut max_diff = 0.0f32;
let mut sum = 0.0f32;
for (&x, &y) in a.iter().zip(b.iter()) {
let diff = (x - y).abs();
max_diff = max_diff.max(diff);
sum += diff;
}
(max_diff, sum / a.len().max(1) as f32)
}

fn bench_q8_projection(c: &mut Criterion) {
let mut group = c.benchmark_group("q8_projection");

#[cfg(all(feature = "cuda", nvcc))]
{
let ctx = cuda_env::ctx_m40().expect("cuda context");
let cases = vec![
("tiny", 2, 3, 35),
("llama_decode_q", 1, 2048, 2048),
("llama_decode_mlp", 1, 2048, 8192),
("qwen_decode_q", 1, 2048, 2048),
("qwen_decode_mlp", 1, 2048, 11008),
("qwen_prefill64_q", 64, 2048, 2048),
];

for (label, m, k, n) in cases {
let a: Vec<f32> = (0..m * k)
.map(|idx| ((idx % 17) as f32 - 8.0) * 0.03125)
.collect();
let b: Vec<f32> = (0..n * k)
.map(|idx| ((idx % 23) as f32 - 11.0) * 0.015625)
.collect();
let a_bytes = f32s_to_bytes(&a);
let b_f16 = f32s_to_halves_bytes(&b);
let b_q8 = q8_0_gguf_bytes_from_dequantized(&b, n, k);
let b_deq = q8_0_gguf_dequantize(&b_q8, n, k);

let bytes_a = m * k * std::mem::size_of::<f32>();
let bytes_c = m * n * std::mem::size_of::<f32>();
let bytes_bt = n * k * std::mem::size_of::<f32>();

let da = ctx.device_malloc(bytes_a).unwrap();
let db_f16 = ctx.device_malloc(b_f16.len()).unwrap();
let db_q8 = ctx.device_malloc(b_q8.len()).unwrap();
let db_f32 = ctx.device_malloc(bytes_bt).unwrap();
let dc = ctx.device_malloc(bytes_c).unwrap();

unsafe {
ctx.memcpy_h2d(da, a_bytes.as_ptr() as *const c_void, bytes_a)
.unwrap();
ctx.memcpy_h2d(db_f16, b_f16.as_ptr() as *const c_void, b_f16.len())
.unwrap();
ctx.memcpy_h2d(db_q8, b_q8.as_ptr() as *const c_void, b_q8.len())
.unwrap();
ctx.materialize_gguf_f16_to_f32_colmajor_nt(
db_f16 as *const c_void,
db_f32,
n as i32,
k as i32,
)
.unwrap();
}

if m * n <= 8192 {
let expect = cpu_gguf_gemm_f32(&a, &b_deq, m, n, k);
unsafe {
ctx.gemm_f32xq8_0_gguf_f32(
da as *const c_void,
db_q8 as *const c_void,
dc,
m as i32,
n as i32,
k as i32,
)
.unwrap();
}
let mut got_bytes = vec![0u8; bytes_c];
unsafe {
ctx.memcpy_d2h(
got_bytes.as_mut_ptr() as *mut c_void,
dc as *const c_void,
got_bytes.len(),
)
.unwrap();
}
let got = bytes_to_f32s(&got_bytes);
let (max_diff, mean_diff) = max_mean_abs_diff(&got, &expect);
eprintln!(
"[q8_projection] correctness label={label} max_abs_diff={max_diff:.6} mean_abs_diff={mean_diff:.6}"
);
}

let moved_q8 = bytes_a + b_q8.len() + bytes_c;
let moved_f16 = bytes_a + b_f16.len() + bytes_c;
let moved_f32 = bytes_a + bytes_bt + bytes_c;
let ops = 2u64 * m as u64 * n as u64 * k as u64;

group.throughput(Throughput::Bytes(moved_q8 as u64));
group.bench_with_input(
BenchmarkId::new("q8_scalar_generic", format!("{label}_{m}x{k}x{n}")),
&(m, n, k),
|bch, &(m, n, k)| {
bch.iter(|| unsafe {
ctx.gemm_f32xq8_0_gguf_f32_generic_async(
da as *const c_void,
db_q8 as *const c_void,
dc,
m as i32,
n as i32,
k as i32,
)
.unwrap();
ctx.synchronize_stream(CudaStream::Prefill).unwrap();
})
},
);

group.throughput(Throughput::Bytes(moved_q8 as u64));
group.bench_with_input(
BenchmarkId::new("q8_blockloop", format!("{label}_{m}x{k}x{n}")),
&(m, n, k),
|bch, &(m, n, k)| {
bch.iter(|| unsafe {
ctx.gemm_f32xq8_0_gguf_f32_blockloop_async(
da as *const c_void,
db_q8 as *const c_void,
dc,
m as i32,
n as i32,
k as i32,
)
.unwrap();
ctx.synchronize_stream(CudaStream::Prefill).unwrap();
})
},
);

if m >= 4 && k >= 64 && n >= 16 {
group.throughput(Throughput::Bytes(moved_q8 as u64));
group.bench_with_input(
BenchmarkId::new("q8_shared_activation", format!("{label}_{m}x{k}x{n}")),
&(m, n, k),
|bch, &(m, n, k)| {
bch.iter(|| unsafe {
ctx.gemm_f32xq8_0_gguf_f32_shared_activation_async(
da as *const c_void,
db_q8 as *const c_void,
dc,
m as i32,
n as i32,
k as i32,
)
.unwrap();
ctx.synchronize_stream(CudaStream::Prefill).unwrap();
})
},
);
}

if m == 1 && k % 32 == 0 {
group.throughput(Throughput::Bytes(moved_q8 as u64));
group.bench_with_input(
BenchmarkId::new("q8_decode_tiled", format!("{label}_{m}x{k}x{n}")),
&(m, n, k),
|bch, &(m, n, k)| {
bch.iter(|| unsafe {
ctx.gemm_f32xq8_0_gguf_f32_decode_async(
da as *const c_void,
db_q8 as *const c_void,
dc,
m as i32,
n as i32,
k as i32,
)
.unwrap();
ctx.synchronize_stream(CudaStream::Prefill).unwrap();
})
},
);
}

group.throughput(Throughput::Bytes(moved_f16 as u64));
group.bench_with_input(
BenchmarkId::new("f16_gguf_kernel", format!("{label}_{m}x{k}x{n}")),
&(m, n, k),
|bch, &(m, n, k)| {
bch.iter(|| unsafe {
ctx.gemm_f32xf16_gguf_f32(
da as *const c_void,
db_f16 as *const c_void,
dc,
m as i32,
n as i32,
k as i32,
)
.unwrap()
})
},
);

group.throughput(Throughput::Bytes(moved_f32 as u64));
group.bench_with_input(
BenchmarkId::new("materialized_f32_cublas", format!("{label}_{m}x{k}x{n}")),
&(m, n, k),
|bch, &(m, n, k)| {
bch.iter(|| unsafe {
ctx.gemm_f32xf32_f32(
da as *const c_void,
db_f32 as *const c_void,
dc,
m as i32,
n as i32,
k as i32,
)
.unwrap()
})
},
);

eprintln!(
"[q8_projection] shape={label} M={m} K={k} N={n} ops={ops} bytes_q8={moved_q8} bytes_f16={moved_f16} bytes_f32={moved_f32}"
);

unsafe {
ctx.device_free(da).unwrap();
ctx.device_free(db_f16).unwrap();
ctx.device_free(db_q8).unwrap();
ctx.device_free(db_f32).unwrap();
ctx.device_free(dc).unwrap();
}
}
}

#[cfg(not(all(feature = "cuda", nvcc)))]
{
group.bench_function("noop", |b| b.iter(|| {}));
}

group.finish();
}

criterion_group!(benches, bench_q8_projection);
criterion_main!(benches);
Loading
Loading