use anyhow::Result;
use inferencelayer::encoder::{enc_gemm3_src, enc_gemm4_f16w_src};
use inferencelayer::GpuCtx;
use wgpu::util::DeviceExt;
fn pipeline(ctx: &GpuCtx, label: &str, src: &str) -> wgpu::ComputePipeline {
let m = ctx.device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some(label),
source: wgpu::ShaderSource::Wgsl(src.into()),
});
ctx.device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some(label),
layout: None,
module: &m,
entry_point: Some("main"),
compilation_options: Default::default(),
cache: None,
})
}
#[allow(clippy::too_many_arguments)]
fn bench(ctx: &GpuCtx, label: &str, src: &str, m: usize, n: usize, k: usize,
bm: usize, bn: usize, bias_len: usize, reps: usize) -> f64 {
let pl = pipeline(ctx, label, src);
let xb = ctx.storage(&vec![0.5f32; m * k]);
let wb = ctx.storage(&vec![0.25f32; k * n]); let bb = ctx.storage(&vec![0.0f32; bias_len]);
let yb = ctx.storage(&vec![0f32; m * n]);
let meta = ctx.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("meta"),
contents: bytemuck::cast_slice(&[m as u32, n as u32, k as u32, 0u32]),
usage: wgpu::BufferUsages::UNIFORM,
});
let bg = ctx.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &pl.get_bind_group_layout(0),
entries: &[
wgpu::BindGroupEntry { binding: 0, resource: xb.as_entire_binding() },
wgpu::BindGroupEntry { binding: 1, resource: wb.as_entire_binding() },
wgpu::BindGroupEntry { binding: 2, resource: bb.as_entire_binding() },
wgpu::BindGroupEntry { binding: 3, resource: yb.as_entire_binding() },
wgpu::BindGroupEntry { binding: 4, resource: meta.as_entire_binding() },
],
});
let (gx, gy) = ((n as u32).div_ceil(bn as u32), (m as u32).div_ceil(bm as u32));
let run = |reps: usize| -> f64 {
let mut enc = ctx.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
{
let mut pass = enc.begin_compute_pass(&wgpu::ComputePassDescriptor::default());
pass.set_pipeline(&pl);
pass.set_bind_group(0, &bg, &[]);
for _ in 0..reps { pass.dispatch_workgroups(gx, gy, 1); }
}
let t0 = std::time::Instant::now();
ctx.queue.submit([enc.finish()]);
let _ = ctx.device.poll(wgpu::PollType::wait_indefinitely());
t0.elapsed().as_secs_f64()
};
run(2); let mut best = f64::MAX;
for _ in 0..5 { best = best.min(run(reps)); }
2.0 * m as f64 * n as f64 * k as f64 * reps as f64 / best / 1e9
}
fn main() -> Result<()> {
let ctx = GpuCtx::new()?;
println!("caps: coop_matrix={} f16={} subgroups={}", ctx.coop_matrix, ctx.f16, ctx.subgroups);
if !ctx.coop_matrix {
println!("!! coop_matrix NOT available on this adapter — coop numbers will be absent");
}
let shapes = [
(2048usize, 2048usize, 2048usize),
(2624, 2048, 2048), (4096, 4096, 4096), ];
let reps = 20;
println!("{:>18} | {:>12} | {:>12} | {:>7}", "shape (m,n,k)", "ALU GFLOP/s", "coop GFLOP/s", "ratio");
for (m, n, k) in shapes {
let alu = bench(&ctx, "gemm3_alu", &enc_gemm3_src(false, 64, 64, 16), m, n, k, 64, 64, 1, reps);
let coop = if ctx.coop_matrix {
bench(&ctx, "gemm4_coop", &enc_gemm4_f16w_src(128, 128, 8), m, n, k, 128, 128, 8 * n, reps)
} else { f64::NAN };
println!("{:>6},{:>5},{:>5} | {:>12.0} | {:>12.0} | {:>6.1}x", m, n, k, alu, coop, coop / alu);
}
Ok(())
}