work / engineering and ai
GPU Backend for FatQat
A fork of FatQat, an open-source quantum-circuit simulator, inspired by the course CENG5280. It adds CUDA execution for all four simulation methods, keeping the interfaces and double precision. Archived warm complex128 calls on 24–28-qubit observables ran 17–21× faster than the project's own optimized 32-thread CPU engine, using default error-compensated GPU products.
the engineering
A state-vector simulator updates every amplitude for every gate, and the state doubles with each qubit. At 24–28 qubits the project's own optimized 32-thread CPU engine is the bottleneck.
- Keep the simulator's interfaces: a circuit runs unchanged on either engine.
- Keep double precision; check numerical error against a 60-digit reference on small circuits.
- Every faster path must give the same results, checked by randomized differential tests.
The result, computed
BUILD-COMPUTED by the Lab's own code when this page was built · one nearly cancelling case, then 2,000-case sweeps
a·b − c·d with a, b, c, d = 0.61822776263579726, 0.68463533686008304, 0.61822776263636192, 0.68463533685945777
| inputs | plain, median | plain, worst | compensated, median | compensated, worst |
|---|---|---|---|---|
| nearly cancelling | 6.3e+15 | 2.8e+17 | 0.00 | 0.00 |
| typical amplitudes | 0.36 | 7.1e+2 | 0.28 | 0.98 |
What it shows
The browser experiment isolates cancellation in one difference of products. Separately, the archived GPU accuracy experiment covered 110 small circuits, up to 5 qubit/qutrit subsystems: default-mode maximum component error stayed within 2.9 machine epsilons of a 60-digit oracle evolving the stored complex128 coefficients.
The tradeoff
- chose
- error-compensated GPU products, Kahan's algorithm for each part of a complex product, as the default
- over
- plain products, which round as the CPU engines do
- because
- compensation recovers product-rounding error before subtraction; it reduces cancellation error in each complex product, while the circuit's sums still round
- cost
- extra arithmetic: archived warm complex128 observable calls on 24–28 qubits ran 17–21× over the 32-thread CPU baseline, versus 23–26× in optional plain mode. The small-circuit accuracy study is a separate experiment.
What I built
The GPU fork: CUDA execution for all four simulation methods, the compensated arithmetic, exact shot branching and circuit simplification, the randomized differential checks, and a fix merged upstream.
- 17–21×
- archived warm complex128 observable calls, 24–28 qubits, vs the project's own optimized 32-thread CPU; default compensated products, simplification off
- ≤ 2.9 ε
- separate accuracy experiment: 110 small circuits, up to 5 qubit/qutrit subsystems; maximum component error vs a 60-digit oracle of stored complex128 coefficients, default compensated mode
The problem
A state-vector quantum simulatorstate vectorThe full description of a quantum register: one complex amplitude per basis state, so its size doubles with every qubit. keeps every amplitude of the quantum state in memory and updates all of them for every gate. The state doubles with each added qubit, so larger circuits are limited by how fast the machine can move memory. FatQat, an open-source simulator by spaceqat, runs on the CPU. I wanted a GPU engine that existing FatQat code could use without changes and without losing accuracy.
What I built
- CUDA (GPU) execution for all four simulation methods behind the simulator’s existing interfaces, so a circuit runs unchanged on either engine.
- Double precision throughout, matching the CPU engine rather than trading accuracy for speed.
- Exact shot branching: shots that share a history are simulated once and split only where they differ, with identical counts.
- Exact circuit simplification, and randomized differential checks that compare every “same result” claim against the reference.
- An Apple-GPU runtime (
runtime="metal") that computes binary64 in software, bit-identical to the CPU engine, andruntime="auto", which picks the faster runtime for each run. - A fork kept in sync with upstream FatQat, inspired by the course CENG5280, and a fix merged upstream (spaceqat/fatqat#52: process shot workers sized by the CPU affinity mask), which makes me a contributor to upstream FatQat.
Result
Explore the recorded timings in the Lab: change the requested output, state size and product mode, then inspect the source run behind each CPU/GPU comparison.
Speed, on the archived timing workloads. Warm complex128 observable calls on 24–28 qubits ran 17–21× faster than the project’s own optimized 32-thread CPU engine with default error-compensated GPU products and simplification off (6–7× with the full state copied back). Synchronisation and requested host output are included; imports, construction and first-call compilation are excluded. Optional plain mode reached 23–26×. The r9 engines recorded 35–42×, before compensated products became the default and the CPU engine got faster.
Accuracy, on a separate small-circuit study. On 110 small circuits, up to 5 qubit/qutrit subsystems, the default mode’s maximum component error was within 2.9 machine epsilons of a 60-digit oracle evolving stored complex128 coefficients. That is a measured result for these cases, not an error bound for the larger timing workloads or ideal gates.
Other archived checks. Exact shot branching was up to 89× faster than running each shot alone on noiseless circuits with mid-circuit measurements, with identical counts. Randomized differential runs checked results against the reference. On an Apple-silicon Mac, the adaptive CPU-plus-Metal runtime recorded 1.18–1.52× over the CPU reference at 24–26 qubits, with bit-identical output in the measured cases; this is an adaptive-runtime result, not GPU-only acceleration.
How it works
- 01CircuitFatQat's interface
- 02Engine choiceCPU, CUDA or Metal
- 03GPU state vectordouble precision
- 04Gate kernelsCUDA via CuPy
- 05Parity checkvs CPU results
designed and verified by Oscar, implemented with AI coding agents (Claude Code, Codex); the simulator itself is spaceqat’s FatQat (Apache-2.0)
Limits
- The 17–21× figure measures warm complex128 observable calls on 24–28 qubits against a fixed 32-thread CPU baseline; copying the full state back cuts it to 6–7×. It is not whole-program acceleration or a fresh benchmark of today’s version.
- The precision experiment uses small circuits and the stored coefficients as its oracle input. It does not measure the numerical error of the larger speed-test workloads.
- The branching speed-up applies to noiseless circuits with mid-circuit measurements; noisy circuits gain far less.