587 lines
20 KiB
Rust
587 lines
20 KiB
Rust
//! Scaling benchmarks for ruQu Coherence Gate.
|
|
//!
|
|
//! Measures how performance scales with:
|
|
//! - Code distance (5, 9, 13, 17, 21)
|
|
//! - Qubit count (50, 100, 500, 1000)
|
|
//! - Tile count (10, 50, 100, 255)
|
|
//! - Graph density
|
|
//!
|
|
//! Run with: `cargo bench -p ruqu --bench scaling_bench`
|
|
|
|
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
|
use std::hint::black_box as hint_black_box;
|
|
|
|
use ruqu::filters::{FilterConfig, FilterPipeline, SystemState};
|
|
use ruqu::syndrome::{DetectorBitmap, SyndromeBuffer, SyndromeRound};
|
|
use ruqu::tile::{GateThresholds, PatchGraph, SyndromeDelta, TileReport, TileZero, WorkerTile};
|
|
|
|
// ============================================================================
|
|
// HELPER FUNCTIONS
|
|
// ============================================================================
|
|
|
|
/// Calculate approximate detector count for a surface code distance
|
|
fn detectors_for_distance(distance: usize) -> usize {
|
|
// For surface code, detector count is roughly d^2
|
|
distance * distance
|
|
}
|
|
|
|
/// Calculate approximate qubit count for a surface code distance
|
|
fn qubits_for_distance(distance: usize) -> usize {
|
|
// For surface code, data qubits = 2*d^2 - 2*d + 1
|
|
2 * distance * distance - 2 * distance + 1
|
|
}
|
|
|
|
/// Create a worker tile sized for a given qubit count
|
|
fn create_scaled_worker_tile(tile_id: u8, qubit_count: usize) -> WorkerTile {
|
|
let mut tile = WorkerTile::new(tile_id);
|
|
|
|
let vertices = (qubit_count / 4).min(255) as u16; // Tile handles a fraction of qubits
|
|
let edges_per_vertex = 4; // Surface code connectivity
|
|
|
|
for i in 0..vertices {
|
|
tile.patch_graph.ensure_vertex(i);
|
|
}
|
|
|
|
let mut edges_added = 0u16;
|
|
let max_edges = (vertices as usize * edges_per_vertex / 2).min(1000) as u16;
|
|
|
|
'outer: for i in 0..vertices.saturating_sub(1) {
|
|
// Lattice-like connectivity
|
|
let neighbors = [i + 1, i.wrapping_add(vertices / 10)];
|
|
for &neighbor in &neighbors {
|
|
if neighbor < vertices && neighbor != i && edges_added < max_edges {
|
|
if tile.patch_graph.add_edge(i, neighbor, 1000).is_some() {
|
|
edges_added += 1;
|
|
}
|
|
}
|
|
if edges_added >= max_edges {
|
|
break 'outer;
|
|
}
|
|
}
|
|
}
|
|
|
|
tile.patch_graph.recompute_components();
|
|
tile
|
|
}
|
|
|
|
/// Create a filter pipeline sized for a given qubit count
|
|
fn create_scaled_filter_pipeline(qubit_count: usize) -> FilterPipeline {
|
|
let config = FilterConfig::default();
|
|
let mut pipeline = FilterPipeline::new(config);
|
|
|
|
let vertices = qubit_count.min(500) as u64;
|
|
|
|
// Add graph structure proportional to qubit count
|
|
for i in 0..vertices.saturating_sub(1) {
|
|
let _ = pipeline.structural_mut().insert_edge(i, i + 1, 1.0);
|
|
if i % 10 == 0 && i + 10 < vertices {
|
|
let _ = pipeline.structural_mut().insert_edge(i, i + 10, 0.5);
|
|
}
|
|
}
|
|
pipeline.structural_mut().build();
|
|
|
|
// Warm up shift filter
|
|
let num_regions = (qubit_count / 16).min(64);
|
|
for region in 0..num_regions {
|
|
for _ in 0..50 {
|
|
pipeline.shift_mut().update(region, 0.5);
|
|
}
|
|
}
|
|
|
|
// Warm up evidence filter
|
|
for _ in 0..20 {
|
|
pipeline.evidence_mut().update(1.5);
|
|
}
|
|
|
|
pipeline
|
|
}
|
|
|
|
// ============================================================================
|
|
// LATENCY VS CODE DISTANCE
|
|
// ============================================================================
|
|
|
|
/// Benchmark latency scaling with code distance
|
|
fn bench_latency_vs_distance(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("latency_vs_distance");
|
|
group.sample_size(100);
|
|
|
|
let distances = [5, 9, 13, 17, 21];
|
|
|
|
for distance in distances.iter() {
|
|
let qubit_count = qubits_for_distance(*distance);
|
|
let detector_count = detectors_for_distance(*distance);
|
|
|
|
// Worker tile tick latency
|
|
group.bench_with_input(
|
|
BenchmarkId::new("worker_tick", format!("d{}", distance)),
|
|
&qubit_count,
|
|
|b, &qubits| {
|
|
let mut tile = create_scaled_worker_tile(1, qubits);
|
|
let delta = SyndromeDelta::new(0, 1, 100);
|
|
|
|
b.iter(|| {
|
|
let report = tile.tick(black_box(&delta));
|
|
black_box(report)
|
|
});
|
|
},
|
|
);
|
|
|
|
// Filter pipeline evaluation latency
|
|
group.bench_with_input(
|
|
BenchmarkId::new("filter_pipeline", format!("d{}", distance)),
|
|
&qubit_count,
|
|
|b, &qubits| {
|
|
let pipeline = create_scaled_filter_pipeline(qubits);
|
|
let state = SystemState::new(qubits);
|
|
|
|
b.iter(|| {
|
|
let result = pipeline.evaluate(black_box(&state));
|
|
black_box(result)
|
|
});
|
|
},
|
|
);
|
|
|
|
// Full decision cycle latency
|
|
group.bench_with_input(
|
|
BenchmarkId::new("full_decision", format!("d{}", distance)),
|
|
&qubit_count,
|
|
|b, &qubits| {
|
|
let mut tile = create_scaled_worker_tile(1, qubits);
|
|
let thresholds = GateThresholds::default();
|
|
let mut tilezero = TileZero::new(thresholds);
|
|
|
|
b.iter(|| {
|
|
let delta = SyndromeDelta::new(0, 1, 100);
|
|
let report = tile.tick(&delta);
|
|
let reports = vec![report; 10];
|
|
let decision = tilezero.merge_reports(reports);
|
|
black_box(decision)
|
|
});
|
|
},
|
|
);
|
|
|
|
// Syndrome buffer push latency
|
|
group.bench_with_input(
|
|
BenchmarkId::new("syndrome_push", format!("d{}", distance)),
|
|
&detector_count,
|
|
|b, &detectors| {
|
|
let mut buffer = SyndromeBuffer::new(1024);
|
|
let mut round_id = 0u64;
|
|
|
|
b.iter(|| {
|
|
let mut bitmap = DetectorBitmap::new(detectors.min(1024));
|
|
for i in 0..detectors.min(1024) / 10 {
|
|
bitmap.set(i * 10, true);
|
|
}
|
|
let round = SyndromeRound::new(round_id, round_id, round_id * 1000, bitmap, 0);
|
|
buffer.push(round);
|
|
round_id += 1;
|
|
black_box(buffer.len())
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
// ============================================================================
|
|
// LATENCY VS QUBIT COUNT
|
|
// ============================================================================
|
|
|
|
/// Benchmark latency scaling with qubit count
|
|
fn bench_latency_vs_qubit_count(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("latency_vs_qubits");
|
|
group.sample_size(100);
|
|
|
|
let qubit_counts = [50, 100, 500, 1000];
|
|
|
|
for qubit_count in qubit_counts.iter() {
|
|
// Worker tile tick latency
|
|
group.bench_with_input(
|
|
BenchmarkId::new("worker_tick", format!("q{}", qubit_count)),
|
|
qubit_count,
|
|
|b, &qubits| {
|
|
let mut tile = create_scaled_worker_tile(1, qubits);
|
|
let delta = SyndromeDelta::new(0, 1, 100);
|
|
|
|
b.iter(|| {
|
|
let report = tile.tick(black_box(&delta));
|
|
black_box(report)
|
|
});
|
|
},
|
|
);
|
|
|
|
// Filter pipeline evaluation
|
|
group.bench_with_input(
|
|
BenchmarkId::new("filter_pipeline", format!("q{}", qubit_count)),
|
|
qubit_count,
|
|
|b, &qubits| {
|
|
let pipeline = create_scaled_filter_pipeline(qubits);
|
|
let state = SystemState::new(qubits);
|
|
|
|
b.iter(|| {
|
|
let result = pipeline.evaluate(black_box(&state));
|
|
black_box(result)
|
|
});
|
|
},
|
|
);
|
|
|
|
// Patch graph operations
|
|
group.bench_with_input(
|
|
BenchmarkId::new("patch_graph_estimate_cut", format!("q{}", qubit_count)),
|
|
qubit_count,
|
|
|b, &qubits| {
|
|
let tile = create_scaled_worker_tile(1, qubits);
|
|
|
|
b.iter(|| {
|
|
let cut = tile.patch_graph.estimate_local_cut();
|
|
black_box(cut)
|
|
});
|
|
},
|
|
);
|
|
|
|
// Component recomputation
|
|
group.bench_with_input(
|
|
BenchmarkId::new("recompute_components", format!("q{}", qubit_count)),
|
|
qubit_count,
|
|
|b, &qubits| {
|
|
let mut tile = create_scaled_worker_tile(1, qubits);
|
|
|
|
b.iter(|| {
|
|
tile.patch_graph.status |= PatchGraph::STATUS_DIRTY;
|
|
let count = tile.patch_graph.recompute_components();
|
|
black_box(count)
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
// ============================================================================
|
|
// LATENCY VS TILE COUNT
|
|
// ============================================================================
|
|
|
|
/// Benchmark latency scaling with tile count (TileZero merge)
|
|
fn bench_latency_vs_tile_count(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("latency_vs_tiles");
|
|
group.sample_size(100);
|
|
|
|
let tile_counts = [10, 50, 100, 150, 200, 255];
|
|
|
|
for tile_count in tile_counts.iter() {
|
|
// TileZero merge latency
|
|
group.bench_with_input(
|
|
BenchmarkId::new("tilezero_merge", format!("t{}", tile_count)),
|
|
tile_count,
|
|
|b, &count| {
|
|
let thresholds = GateThresholds::default();
|
|
let mut tilezero = TileZero::new(thresholds);
|
|
|
|
let reports: Vec<TileReport> = (1..=count)
|
|
.map(|i| {
|
|
let mut report = TileReport::new(i as u8);
|
|
report.local_cut = 10.0 + (i as f64 * 0.1);
|
|
report.shift_score = 0.1;
|
|
report.e_value = 200.0;
|
|
report.num_vertices = 100;
|
|
report.num_edges = 200;
|
|
report
|
|
})
|
|
.collect();
|
|
|
|
b.iter(|| {
|
|
let decision = tilezero.merge_reports(black_box(reports.clone()));
|
|
black_box(decision)
|
|
});
|
|
},
|
|
);
|
|
|
|
// Full decision cycle with scaled tiles
|
|
group.bench_with_input(
|
|
BenchmarkId::new("full_decision", format!("t{}", tile_count)),
|
|
tile_count,
|
|
|b, &count| {
|
|
let mut tile = create_scaled_worker_tile(1, 100);
|
|
let thresholds = GateThresholds::default();
|
|
let mut tilezero = TileZero::new(thresholds);
|
|
|
|
b.iter(|| {
|
|
let delta = SyndromeDelta::new(0, 1, 100);
|
|
let report = tile.tick(&delta);
|
|
let reports = vec![report; count];
|
|
let decision = tilezero.merge_reports(reports);
|
|
black_box(decision)
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
// ============================================================================
|
|
// THROUGHPUT VS SYSTEM SIZE
|
|
// ============================================================================
|
|
|
|
/// Benchmark throughput scaling with system size
|
|
fn bench_throughput_vs_size(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("throughput_vs_size");
|
|
|
|
let qubit_counts = [50, 100, 500, 1000];
|
|
|
|
for qubit_count in qubit_counts.iter() {
|
|
// Syndrome ingestion throughput
|
|
group.throughput(Throughput::Elements(1000));
|
|
group.bench_with_input(
|
|
BenchmarkId::new("syndrome_ingestion", format!("q{}", qubit_count)),
|
|
qubit_count,
|
|
|b, &qubits| {
|
|
let mut buffer = SyndromeBuffer::new(4096);
|
|
let detector_count = (qubits / 2).min(1024);
|
|
let mut round_id = 0u64;
|
|
|
|
b.iter(|| {
|
|
for _ in 0..1000 {
|
|
let mut bitmap = DetectorBitmap::new(detector_count);
|
|
for i in 0..detector_count / 10 {
|
|
bitmap.set(i * 10, true);
|
|
}
|
|
let round =
|
|
SyndromeRound::new(round_id, round_id, round_id * 1000, bitmap, 0);
|
|
buffer.push(round);
|
|
round_id += 1;
|
|
}
|
|
black_box(buffer.len())
|
|
});
|
|
},
|
|
);
|
|
|
|
// Decision throughput
|
|
group.throughput(Throughput::Elements(100));
|
|
group.bench_with_input(
|
|
BenchmarkId::new("decision_throughput", format!("q{}", qubit_count)),
|
|
qubit_count,
|
|
|b, &qubits| {
|
|
let mut tile = create_scaled_worker_tile(1, qubits);
|
|
let thresholds = GateThresholds::default();
|
|
let mut tilezero = TileZero::new(thresholds);
|
|
|
|
b.iter(|| {
|
|
for i in 0..100 {
|
|
let delta = SyndromeDelta::new(0, 1, (i % 256) as u16);
|
|
let report = tile.tick(&delta);
|
|
let reports = vec![report; 10];
|
|
let decision = tilezero.merge_reports(reports);
|
|
hint_black_box(decision);
|
|
}
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
// ============================================================================
|
|
// GRAPH DENSITY SCALING
|
|
// ============================================================================
|
|
|
|
/// Benchmark latency scaling with graph density
|
|
fn bench_latency_vs_density(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("latency_vs_density");
|
|
group.sample_size(100);
|
|
|
|
let base_vertices = 100u16;
|
|
let densities = [
|
|
("sparse", base_vertices / 2), // 0.5 edges per vertex
|
|
("linear", base_vertices), // 1 edge per vertex
|
|
("lattice", base_vertices * 2), // 2 edges per vertex
|
|
("dense", base_vertices * 4), // 4 edges per vertex
|
|
("very_dense", base_vertices * 8), // 8 edges per vertex
|
|
];
|
|
|
|
for (name, edge_count) in densities.iter() {
|
|
// Worker tile tick
|
|
group.bench_with_input(
|
|
BenchmarkId::new("worker_tick", *name),
|
|
edge_count,
|
|
|b, &edges| {
|
|
let mut tile = WorkerTile::new(1);
|
|
|
|
for i in 0..base_vertices {
|
|
tile.patch_graph.ensure_vertex(i);
|
|
}
|
|
|
|
let mut added = 0u16;
|
|
'outer: for i in 0..base_vertices {
|
|
for j in (i + 1)..base_vertices.min(i + 10) {
|
|
if added >= edges {
|
|
break 'outer;
|
|
}
|
|
if tile.patch_graph.add_edge(i, j, 1000).is_some() {
|
|
added += 1;
|
|
}
|
|
}
|
|
}
|
|
tile.patch_graph.recompute_components();
|
|
|
|
let delta = SyndromeDelta::new(0, 1, 100);
|
|
|
|
b.iter(|| {
|
|
let report = tile.tick(black_box(&delta));
|
|
black_box(report)
|
|
});
|
|
},
|
|
);
|
|
|
|
// Local cut estimation
|
|
group.bench_with_input(
|
|
BenchmarkId::new("estimate_local_cut", *name),
|
|
edge_count,
|
|
|b, &edges| {
|
|
let mut graph = PatchGraph::new();
|
|
|
|
for i in 0..base_vertices {
|
|
graph.ensure_vertex(i);
|
|
}
|
|
|
|
let mut added = 0u16;
|
|
'outer: for i in 0..base_vertices {
|
|
for j in (i + 1)..base_vertices.min(i + 10) {
|
|
if added >= edges {
|
|
break 'outer;
|
|
}
|
|
if graph.add_edge(i, j, 1000).is_some() {
|
|
added += 1;
|
|
}
|
|
}
|
|
}
|
|
graph.recompute_components();
|
|
|
|
b.iter(|| {
|
|
let cut = graph.estimate_local_cut();
|
|
black_box(cut)
|
|
});
|
|
},
|
|
);
|
|
|
|
// Component recomputation
|
|
group.bench_with_input(
|
|
BenchmarkId::new("recompute_components", *name),
|
|
edge_count,
|
|
|b, &edges| {
|
|
let mut graph = PatchGraph::new();
|
|
|
|
for i in 0..base_vertices {
|
|
graph.ensure_vertex(i);
|
|
}
|
|
|
|
let mut added = 0u16;
|
|
'outer: for i in 0..base_vertices {
|
|
for j in (i + 1)..base_vertices.min(i + 10) {
|
|
if added >= edges {
|
|
break 'outer;
|
|
}
|
|
if graph.add_edge(i, j, 1000).is_some() {
|
|
added += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
b.iter(|| {
|
|
graph.status |= PatchGraph::STATUS_DIRTY;
|
|
let count = graph.recompute_components();
|
|
black_box(count)
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
// ============================================================================
|
|
// MEMORY PRESSURE SCALING
|
|
// ============================================================================
|
|
|
|
/// Benchmark under memory pressure (large buffers)
|
|
fn bench_memory_pressure(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("memory_pressure");
|
|
group.sample_size(50);
|
|
|
|
let buffer_sizes = [1024, 4096, 16384, 65536];
|
|
|
|
for buffer_size in buffer_sizes.iter() {
|
|
// Syndrome buffer under pressure
|
|
group.throughput(Throughput::Elements(1000));
|
|
group.bench_with_input(
|
|
BenchmarkId::new("syndrome_buffer", format!("cap{}", buffer_size)),
|
|
buffer_size,
|
|
|b, &size| {
|
|
let mut buffer = SyndromeBuffer::new(size);
|
|
// Pre-fill to capacity
|
|
for i in 0..(size as u64) {
|
|
let round = SyndromeRound::new(i, i, i * 1000, DetectorBitmap::new(64), 0);
|
|
buffer.push(round);
|
|
}
|
|
|
|
let mut round_id = size as u64;
|
|
b.iter(|| {
|
|
for _ in 0..1000 {
|
|
let round = SyndromeRound::new(
|
|
round_id,
|
|
round_id,
|
|
round_id * 1000,
|
|
DetectorBitmap::new(64),
|
|
0,
|
|
);
|
|
buffer.push(round);
|
|
round_id += 1;
|
|
}
|
|
black_box(buffer.len())
|
|
});
|
|
},
|
|
);
|
|
|
|
// Window extraction under pressure
|
|
group.bench_with_input(
|
|
BenchmarkId::new("window_extraction", format!("cap{}", buffer_size)),
|
|
buffer_size,
|
|
|b, &size| {
|
|
let mut buffer = SyndromeBuffer::new(size);
|
|
for i in 0..(size as u64) {
|
|
let round = SyndromeRound::new(i, i, i * 1000, DetectorBitmap::new(64), 0);
|
|
buffer.push(round);
|
|
}
|
|
|
|
let window_size = (size / 10).max(10);
|
|
b.iter(|| {
|
|
let window = buffer.window(window_size);
|
|
black_box(window)
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
// ============================================================================
|
|
// CRITERION GROUPS
|
|
// ============================================================================
|
|
|
|
criterion_group!(
|
|
scaling_benches,
|
|
bench_latency_vs_distance,
|
|
bench_latency_vs_qubit_count,
|
|
bench_latency_vs_tile_count,
|
|
bench_throughput_vs_size,
|
|
bench_latency_vs_density,
|
|
bench_memory_pressure,
|
|
);
|
|
|
|
criterion_main!(scaling_benches);
|