open-standards/wai/wai-quantum/examples/sv_bench.rs
2026-10-17 11:21:27 -04:00

176 lines
7 KiB
Rust

//! Time the dense state vector: single gates by position and kind, then whole
//! circuits (brickwork, QFT, QAOA) at each fusion width.
//!
//! ```text
//! cargo run --release --example sv_bench --features quantum_sv -- N [gates|circuits|all]
//! ```
//!
//! A gate pass reads and writes every amplitude once, 32 bytes each, so the
//! `GB/s` column is that traffic over the time.
use std::time::Instant;
use wai_quantum::gates::Gate;
use wai_quantum::quantum_sv::{Schedule, StateVector};
fn rng(seed: u64) -> impl FnMut() -> f64 {
let mut s = seed;
move || {
s = s.wrapping_add(0x9e37_79b9_7f4a_7c15);
let mut z = s;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
(((z ^ (z >> 31)) >> 11) as f64 + 0.5) / 9_007_199_254_740_992.0
}
}
fn brickwork(n: u32, depth: usize, seed: u64) -> Vec<Gate> {
let mut u = rng(seed);
let mut g = Vec::new();
for layer in 0..depth {
for q in 0..n {
g.push(Gate::rx(q, u() * 6.0));
g.push(Gate::rz(q, u() * 6.0));
}
let mut q = (layer % 2) as u32;
while q + 1 < n {
g.push(Gate::fsim(q, q + 1, u() * 3.0, u() * 3.0));
q += 2;
}
}
g
}
fn qft(n: u32) -> Vec<Gate> {
let mut g = Vec::new();
for i in 0..n {
g.push(Gate::h(i));
for j in i + 1..n {
g.push(Gate::cphase(j, i, core::f64::consts::PI / f64::from(1u32 << (j - i).min(30))));
}
}
g
}
fn qaoa(n: u32, p: usize, seed: u64) -> Vec<Gate> {
let mut u = rng(seed);
let mut g: Vec<Gate> = (0..n).map(Gate::h).collect();
for _ in 0..p {
let gamma = u();
for q in 0..n {
g.push(Gate::rzz(q, (q + 1) % n, gamma));
g.push(Gate::rzz(q, (q + 3) % n, gamma));
}
let beta = u();
for q in 0..n {
g.push(Gate::rx(q, beta));
}
}
g
}
fn time<F: FnMut()>(reps: usize, mut f: F) -> f64 {
f();
let t0 = Instant::now();
for _ in 0..reps {
f();
}
t0.elapsed().as_secs_f64() / reps as f64
}
fn main() {
let a: Vec<String> = std::env::args().collect();
let n: u32 = a.get(1).map_or(24, |s| s.parse().unwrap());
let what = a.get(2).map_or("all", |s| s.as_str());
let bytes = 32.0 * (1u64 << n) as f64;
let mut sv = StateVector::zero(n).unwrap();
sv.run(&(0..n).map(Gate::h).collect::<Vec<_>>(), 0).unwrap();
let threads = std::thread::available_parallelism().map_or(1, |t| t.get());
println!("{n} qubits, {threads} threads, {:.2} GiB of state", 16.0 * (1u64 << n) as f64 / (1u64 << 30) as f64);
if what == "gates" || what == "all" {
let reps = if n >= 28 { 3 } else { 10 };
let cases: Vec<(&str, Gate)> = vec![
("rx, top qubit", Gate::rx(0, 0.3)),
("rx, middle qubit", Gate::rx(n / 2, 0.3)),
("rx, bottom qubit", Gate::rx(n - 1, 0.3)),
("rz (diagonal), middle", Gate::rz(n / 2, 0.3)),
("fsim, top pair", Gate::fsim(0, 1, 0.4, 0.2)),
("fsim, middle pair", Gate::fsim(n / 2, n / 2 + 1, 0.4, 0.2)),
("fsim, bottom pair", Gate::fsim(n - 2, n - 1, 0.4, 0.2)),
("fsim, far apart", Gate::fsim(1, n - 2, 0.4, 0.2)),
("rzz (diagonal), middle", Gate::rzz(n / 2, n / 2 + 1, 0.3)),
];
for (name, g) in cases {
let t = time(reps, || sv.apply(&g).unwrap());
println!(" {name:26} {:9.2} ms {:7.1} GB/s", t * 1e3, bytes / t / 1e9);
}
for k in 3..=5u32 {
// A dense k-qubit gate: a fused brickwork block on the bottom k qubits.
let block = brickwork(k, 3, 7).into_iter().map(|mut g| {
g.qubits.iter_mut().for_each(|q| *q += n - k);
g
});
let fused = wai_quantum::quantum_sv::fuse(&block.collect::<Vec<_>>(), k as usize);
if fused.len() == 1 {
let g = &fused[0];
let t = time(reps, || sv.apply(g).unwrap());
println!(" dense {k}-qubit, bottom {:9.2} ms {:7.1} GB/s", t * 1e3, bytes / t / 1e9);
}
}
}
if what == "cache" {
// Below 2^14 amplitudes every kernel runs on one thread: the in-cache
// rate of one core, in amplitudes per second.
let reps = (1usize << 26) >> n;
let cases: Vec<(&str, Gate)> = vec![
("rx, top qubit", Gate::rx(0, 0.3)),
("rx, bottom qubit", Gate::rx(n - 1, 0.3)),
("rz (diagonal)", Gate::rz(n / 2, 0.3)),
("fsim, top pair", Gate::fsim(0, 1, 0.4, 0.2)),
("fsim, bottom pair", Gate::fsim(n - 2, n - 1, 0.4, 0.2)),
("fsim, far apart", Gate::fsim(1, n - 2, 0.4, 0.2)),
];
for (name, g) in cases {
let t = time(reps, || sv.apply(&g).unwrap());
println!(" {name:26} {:9.1} M amplitudes/s per core", (1u64 << n) as f64 / t / 1e6);
}
for k in 3..=5u32 {
let block = brickwork(k, 3, 7).into_iter().map(|mut g| {
g.qubits.iter_mut().for_each(|q| *q += n - k);
g
});
let fused = wai_quantum::quantum_sv::fuse(&block.collect::<Vec<_>>(), k as usize);
if fused.len() == 1 {
let g = &fused[0];
let t = time(reps, || sv.apply(g).unwrap());
println!(" dense {k}-qubit {:9.1} M amplitudes/s per core", (1u64 << n) as f64 / t / 1e6);
}
}
}
if what == "circuits" || what == "all" {
let circuits: Vec<(&str, Vec<Gate>)> = vec![("brickwork depth 20", brickwork(n, 20, 1)), ("qft", qft(n)), ("qaoa p=2", qaoa(n, 2, 3))];
let tile: u32 = std::env::var("TILE").ok().map_or(15, |s| s.parse().unwrap());
let low: u32 = std::env::var("RUN").ok().map_or(5, |s| s.parse().unwrap());
let tiled = Schedule { tile_bits: tile, run_bits: low };
for (name, gates) in &circuits {
for width in [2usize, 3, 4, 5] {
let passes = wai_quantum::quantum_sv::fuse(gates, width).len();
let mut times = Vec::new();
let mut states = Vec::new();
for schedule in [Schedule::UNTILED, tiled] {
let mut s = StateVector::zero(n).unwrap();
let t0 = Instant::now();
s.run_scheduled(gates, width, schedule).unwrap();
times.push(t0.elapsed().as_secs_f64());
states.push(s);
}
let same = states[0].re.iter().chain(&states[0].im).zip(states[1].re.iter().chain(&states[1].im)).all(|(a, b)| a.to_bits() == b.to_bits());
println!(
" {name:20} {} gates, width {width}: {passes:5} gates fused; untiled {:8.3} s, tiled {:8.3} s ({:4.1}x), same bits: {same}",
gates.len(),
times[0],
times[1],
times[0] / times[1]
);
}
}
}
}