Skip to content

Benchmarks

Included directly from the repository README.md so it never goes stale relative to the single source of truth. See the Changelog for how these numbers evolved release to release.

Anti-OOM chunking vs. PennyLane, and general benchmarks

The distributed-dispatch section below covers run_chunk_distributed -- multi-device sharding, not a benchmark number itself, but kept here since it's part of the same performance story. General throughput/drift numbers (measured on Google Colab Free Tier) follow further down.

Dense Evolution maintains constant ~2 GB RAM at any qubit count via dynamic chunking. PennyLane allocates the full statevector — OOM beyond 26q.

Qubits Hilbert Space PennyLane PennyLane RAM Dense Evolution Dense RAM Chunk Geometry
24 16,777,216 307 MB 516 MB 1× (2²⁷)
26 67,108,864 1,074 MB 2,050 MB 1× (2²⁷)
28 268,435,456 ❌ OOM 2,050 MB 2× (2²⁷)
30 1,073,741,824 ❌ OOM 2,048 MB 8× (2²⁷)
32 4,294,967,296 ❌ OOM 2,048 MB 32× (2²⁷)
from dense_evolution import Chunk

sim = Chunk(27)
sim.run_chunk([['h', i] for i in range(27)], chunk_size_gates=500)

print(sim)
# Chunk(n_qubits=27, safe_qubits=27, num_chunks=1,
#       chunk_size_bits=27, mem_per_chunk=2048.0 MB, ram_free=42.3%, has_jax=True)

Distributed dispatch across a device mesh — run_chunk_distributed

run_chunk()'s multi-chunk path (num_chunks > 1) solves "RAM of one process" — every chunk lives in the same machine's memory, even though the kernel that moves them is JIT-fused. run_chunk_distributed() solves a different constraint: "more qubits than fit on one device," with one physical chunk pinned to its own JAX device (v1 scope: jax.device_count() must be >= num_chunks, exactly one chunk per device — a hybrid multi-chunk-per-device scheme is future work).

from dense_evolution import Chunk

sim = Chunk(30)  # num_chunks > 1 on most machines
sim.run_chunk_distributed([['h', i] for i in range(30)])

Cross-chunk gate mixing (a gate touching one of the top m = n_qubits - chunk_size_bits "chunk-select" qubits) becomes real point-to-point network communication — jax.lax.ppermute, the same pairwise-exchange pattern distributed statevector simulators use, exchanging edge data only with the fixed XOR-stride partner device, never gathering the full state onto any single device. A gate entirely within a chunk's local qubits, or a control-chunk/target-local gate (decidable from each device's own chunk index alone), needs no communication at all.

Requires jax.device_count() >= num_chunks; raises RuntimeError with the exact count needed otherwise. For local testing without real multi-GPU hardware, force extra simulated CPU devices via XLA_FLAGS=--xla_force_host_platform_device_count=N (set before the process starts — JAX's device count is fixed at first initialization, not changeable at runtime) — this validates the sharding/communication logic is correct, not real GPU-to-GPU network performance, which needs actual multi-GPU/multi-host hardware to measure honestly.


▍ Benchmarks

Measured on Google Colab Free Tier (CPU runtime)

Metric Value
Numerical drift (30-layer Ansatz, 1360 gates) Δ = 1.11 × 10⁻¹⁶
Memory footprint @ 20q 32 MB (float64) · 16 MB (float32)
JIT compile overhead (first run) < 400 ms
Gate throughput after warm-up > 10⁶ gates/s (CPU)
Maximum tested qubits (Colab Free) 24q stable · 33q high-RAM runtime
Anti-OOM latency reduction (static JIT cache) −86.47%