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% |