fastqml 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastqml-0.2.1/LICENSE +21 -0
- fastqml-0.2.1/PKG-INFO +428 -0
- fastqml-0.2.1/README.md +378 -0
- fastqml-0.2.1/fastqml/__init__.py +6 -0
- fastqml-0.2.1/fastqml/circulant.py +50 -0
- fastqml-0.2.1/fastqml/gate_fusion.py +547 -0
- fastqml-0.2.1/fastqml/register_ops.py +232 -0
- fastqml-0.2.1/fastqml/torch_sim_real.py +479 -0
- fastqml-0.2.1/fastqml.egg-info/PKG-INFO +428 -0
- fastqml-0.2.1/fastqml.egg-info/SOURCES.txt +13 -0
- fastqml-0.2.1/fastqml.egg-info/dependency_links.txt +1 -0
- fastqml-0.2.1/fastqml.egg-info/requires.txt +6 -0
- fastqml-0.2.1/fastqml.egg-info/top_level.txt +1 -0
- fastqml-0.2.1/pyproject.toml +45 -0
- fastqml-0.2.1/setup.cfg +4 -0
fastqml-0.2.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Eric A. F. Reinhardt
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
fastqml-0.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastqml
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Fast PyTorch quantum-circuit primitives via Kronecker-product layer fusion and edge coloring
|
|
5
|
+
Author: Eric A. F. Reinhardt
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Eric A. F. Reinhardt
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/ereinha/fastQML
|
|
29
|
+
Project-URL: Repository, https://github.com/ereinha/fastQML
|
|
30
|
+
Project-URL: Issues, https://github.com/ereinha/fastQML/issues
|
|
31
|
+
Keywords: quantum-computing,quantum-machine-learning,pytorch,gate-fusion,cuda-graphs
|
|
32
|
+
Classifier: Development Status :: 4 - Beta
|
|
33
|
+
Classifier: Intended Audience :: Science/Research
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
40
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
41
|
+
Requires-Python: >=3.10
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
License-File: LICENSE
|
|
44
|
+
Requires-Dist: torch<3.0,>=2.6
|
|
45
|
+
Requires-Dist: numpy<3.0,>=1.24
|
|
46
|
+
Provides-Extra: test
|
|
47
|
+
Requires-Dist: pennylane<1.0,>=0.40; extra == "test"
|
|
48
|
+
Requires-Dist: pennylane-lightning<1.0,>=0.40; extra == "test"
|
|
49
|
+
Dynamic: license-file
|
|
50
|
+
|
|
51
|
+
# fastQML
|
|
52
|
+
|
|
53
|
+
A small PyTorch quantum-circuit simulation toolkit built around two ideas:
|
|
54
|
+
|
|
55
|
+
1. **Layer-fusion via Kronecker product + matmul.** When a circuit applies a single-qubit gate to many qubits in parallel, the combined operator is the Kronecker product of those gates. Build the product once, apply via a single matmul on the flat state — replaces N strided per-qubit kernels with one Tensor-Core matmul.
|
|
56
|
+
|
|
57
|
+
2. **Disjoint multi-qubit-layer fusion via edge coloring.** A layer that applies 2- or 3-qubit gates to many tuples is partitioned into "colors" — each color is a set of tuples that act on pairwise-disjoint qubits AND commute with the gates in later colors. Each color becomes one matmul.
|
|
58
|
+
|
|
59
|
+
Both shapes collapse to the same kernel: `(B, 2^nq) @ (2^nq, 2^nq)`. On a modern GPU this saturates Tensor Cores, and the whole forward+backward fits cleanly into a CUDA graph.
|
|
60
|
+
|
|
61
|
+
The result: a per-sample throughput **~36× faster than PennyLane lightning.qubit** (C++ backend) for the same circuit at `n_qubits=8, n_layers=3` — see benchmark below.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## What's in here
|
|
66
|
+
|
|
67
|
+
| File | Purpose |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `fastqml/gate_fusion.py` | Gate matrix builders (RX/RY/RZ/Rot/PhaseShift, X/Y/Z/H/S/T, CRX/CRY/CRZ/CPhase/IsingXX/YY/ZZ, CNOT/CY/CZ/SWAP/ISWAP, Toffoli, CSWAP) + fused layer-apply functions + edge coloring with commutation predicates |
|
|
70
|
+
| `fastqml/torch_sim_real.py` | Raw per-gate kernels in `(B, 2, 2, ..., 2)` state layout, plus state-utility helpers, block-encoding subroutines, and the Hadamard-test overlap primitive |
|
|
71
|
+
| `examples/minimal_cuda_graph_model.py` | A small variational-quantum-circuit-style `nn.Module`, plus benchmarks for CPU eager / CPU `torch.compile` / CUDA eager / CUDA graph / PennyLane `lightning.qubit` |
|
|
72
|
+
| `fastqml/register_ops.py` | Multiplexed (address-controlled) rotations for single large registers — angle indexed by computational-basis address bits — with optional Triton kernels (autograd included), pure-torch fallback, and dense composition helpers (`compose_dense` / `apply_dense`) |
|
|
73
|
+
| `fastqml/circulant.py` | Translation-equivariant circulant layers for lattice registers: momentum-block operators `M = F' (sum_k U(k)) F` as dense matrices or FFT-style applies |
|
|
74
|
+
| `examples/register_ops_bench.py` | Benchmarks for the multiplexer (Triton / eager / per-batch chain), circulant applies, and dense composition |
|
|
75
|
+
| `test_register_ops.py` | 15 numerical tests: multiplexers vs explicit dense references at fp64, Triton forward/backward vs eager autograd, circulant unitarity / dense-vs-k-space equality / translation equivariance |
|
|
76
|
+
| `test_pennylane_equivalence.py` | 32 numerical equivalence tests against PennyLane `default.qubit` covering every gate, plus multi-pair colors, chain ordering, and per-batch chains. Every test must pass at fp64 noise floor (max diff = 0 or ~1e-17) |
|
|
77
|
+
|
|
78
|
+
### Which module should I use?
|
|
79
|
+
|
|
80
|
+
In almost all training / inference workloads, **use `fastqml.gate_fusion` first**. It's the fast path — every common gate is exposed as a layer-fusing builder + apply pair, and the kron-product + matmul pattern is what makes the CUDA-graph + per-sample throughput numbers in the benchmark below work. If you're building an `nn.Module`, building a VQC, or anything that runs many gates on many qubits per forward, this is the right tool.
|
|
81
|
+
|
|
82
|
+
**Reach for `fastqml.torch_sim_real`** when one of these applies:
|
|
83
|
+
|
|
84
|
+
- **One-off single-gate ops.** Building a full `(nq, 2, 2)` gate stack to use `apply_su2_layer` for a single non-identity gate is wasteful. `apply_ry(re, im, theta, qubit, nq)` etc. apply one gate directly via `movedim` + stack ops — fewer launches when the work isn't layer-shaped.
|
|
85
|
+
- **State / amplitude / probability conversion.** `_amp_to_state_real(amp, nq)` initializes the `(B, 2, 2, ..., 2)` state from an amplitude vector. `_state_to_probs_real(re, im)` flattens `|ψ|²` to a probability vector. `_renormalize_real(re, im, nq)` re-normalizes after non-unitary operations. `gate_fusion` deliberately doesn't ship these — they aren't gates.
|
|
86
|
+
- **Block-encoded operators.** `apply_block_encoding_real(amp, ry_layers, cry_anchor, cry_data, ry_final, nq)` and `apply_block_encoding_complex(...)` post-select an ancilla qubit to encode a learnable sub-norm-1 linear operator. Useful for quantum LCU-style circuits or learned non-unitary maps.
|
|
87
|
+
- **Hadamard test for overlaps.** `hadamard_test_overlap(re_Q, im_Q, re_K, im_K, nq)` returns `Re<ψ_Q | ψ_K>` via the standard 1-ancilla Hadamard-test circuit — useful whenever you need a differentiable estimate of an inner product between two state vectors.
|
|
88
|
+
|
|
89
|
+
If you're just doing standard parametric circuits with rotations and entanglers, you only need `fastqml.gate_fusion`. For more exotic QML primitives (LCU, overlap-based losses, hybrid amplitude/probability workflows), you'll want both.
|
|
90
|
+
|
|
91
|
+
**Reach for `fastqml.register_ops` / `fastqml.circulant`** when your model is a *single large register* rather than many small ones — e.g. a lattice register where sites are basis states. The regime rule: many small registers are launch-bound (gate_fusion + CUDA graphs is the fast path); one big register is bandwidth-bound, and the fast path is minimizing passes over the state. `register_ops` provides the multiplexed rotation (angle indexed by basis-address bits — the uniformly controlled rotation of circuit synthesis), which per-batch gate primitives cannot express without a batch-fold that costs ~150x (see benchmark). `circulant` provides translation-equivariant lattice layers; combined with `compose_dense` for parameters that are static across many evaluations, whole gate cascades collapse to one matmul per block.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Installation
|
|
96
|
+
|
|
97
|
+
### Python packages
|
|
98
|
+
|
|
99
|
+
For NVIDIA GPUs (Windows/Linux), pin a CUDA-matched PyTorch wheel first:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pip install torch --index-url https://download.pytorch.org/whl/cu128
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
(Use `cu124` / `cu121` / etc. for older CUDA toolkits, or skip this and let `pip install fastqml` pull the default wheel for CPU / Apple silicon.)
|
|
106
|
+
|
|
107
|
+
Then install fastqml:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pip install fastqml
|
|
111
|
+
# or directly from GitHub for the latest:
|
|
112
|
+
pip install git+https://github.com/ereinha/fastQML.git
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
For editable / contributor install:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
git clone https://github.com/ereinha/fastQML.git && cd fastQML
|
|
119
|
+
pip install -e . # core
|
|
120
|
+
pip install -e .[test] # also install pennylane + pennylane-lightning for the equivalence tests
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The core toolkit (`fastqml.gate_fusion` + `fastqml.torch_sim_real`) needs only torch + numpy. PennyLane is an optional dep used for `test_pennylane_equivalence.py` and the PennyLane row in the example benchmark.
|
|
124
|
+
|
|
125
|
+
> **Note:** Triton is *not* a dependency. The gate_fusion fast path uses CUDA graphs (`torch.cuda.make_graphed_callables`), which capture raw CUDA kernel launches and do no codegen. `fastqml.register_ops` will use Triton kernels for its multiplexed rotations when Triton is importable (~4x over its pure-torch fallback) and falls back to pure torch otherwise. On Linux/macOS Triton ships transitively with `torch`; on Windows `pip install triton-windows`.
|
|
126
|
+
|
|
127
|
+
### C++ compiler (only needed for `torch.compile` on CPU)
|
|
128
|
+
|
|
129
|
+
CUDA users don't need any compiler — `torch.cuda.make_graphed_callables` works out of the box.
|
|
130
|
+
|
|
131
|
+
For CPU `torch.compile`, Inductor calls a C++ compiler to emit fused kernels.
|
|
132
|
+
|
|
133
|
+
**Linux** — install GCC if you don't have it:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
sudo apt install build-essential # Debian/Ubuntu
|
|
137
|
+
sudo dnf groupinstall "Development Tools" # Fedora/RHEL
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**macOS** — install the Xcode Command Line Tools:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
xcode-select --install
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
This puts `clang`/`clang++` on PATH; PyTorch picks them up automatically.
|
|
147
|
+
|
|
148
|
+
**Windows** — install Visual Studio 2022 Build Tools' C++ workload:
|
|
149
|
+
|
|
150
|
+
```powershell
|
|
151
|
+
winget install Microsoft.VisualStudio.2022.BuildTools --override `
|
|
152
|
+
"--quiet --wait --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.Windows11SDK.22621 --includeRecommended"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
(~3 GB download.) After install, launch your shell from a **"Developer Command Prompt for VS 2022"** so `cl.exe` is on PATH, OR call `vcvars64.bat` first:
|
|
156
|
+
|
|
157
|
+
```powershell
|
|
158
|
+
cmd /c '"%ProgramFiles(x86)%\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvars64.bat" && python examples\minimal_cuda_graph_model.py'
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
If `cl.exe` isn't found at run-time the example's CPU compile path gracefully skips with the error and continues with the eager + CUDA paths.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Quick example
|
|
166
|
+
|
|
167
|
+
### Apply a single layer of mixed single-qubit gates
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
import torch
|
|
171
|
+
from fastqml import gate_fusion as gf
|
|
172
|
+
|
|
173
|
+
device, dtype = torch.device("cuda"), torch.float32
|
|
174
|
+
nq, B = 6, 32
|
|
175
|
+
|
|
176
|
+
state_re = torch.rand(B, *([2] * nq), device=device, dtype=dtype)
|
|
177
|
+
state_im = torch.zeros_like(state_re)
|
|
178
|
+
|
|
179
|
+
ry_angles = torch.randn(nq, device=device, dtype=dtype)
|
|
180
|
+
rz_angles = torch.randn(nq, device=device, dtype=dtype)
|
|
181
|
+
|
|
182
|
+
G_re, G_im = gf.build_ry_rz_gates(ry_angles, rz_angles)
|
|
183
|
+
state_re, state_im = gf.apply_su2_layer(state_re, state_im, G_re, G_im)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
That's one (B, 64) @ (64, 64) matmul replacing 12 strided per-qubit ops (6 RY + 6 RZ).
|
|
187
|
+
|
|
188
|
+
### Apply a layer of CRY gates with edge coloring
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
edges = [(0, 1), (2, 3), (4, 5), (1, 2)]
|
|
192
|
+
angles = torch.randn(len(edges), device=device, dtype=dtype)
|
|
193
|
+
|
|
194
|
+
plans = gf.compute_two_qubit_plans(edges, nq, device, dtype, commute_fn=gf.cry_commute)
|
|
195
|
+
for plan in plans:
|
|
196
|
+
color_angles = angles.index_select(0, plan['k_idx'])
|
|
197
|
+
G_re, G_im = gf.build_cry_4x4(color_angles)
|
|
198
|
+
state_re, state_im = gf.apply_disjoint_two_qubit_layer(state_re, state_im, plan, G_re, G_im)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
For these 4 edges the coloring produces 2 colors: `[(0,1), (2,3), (4,5)]` (3 disjoint pairs in one matmul) and `[(1,2)]` (must be its own color — non-commuting with the first via target/control overlap).
|
|
202
|
+
|
|
203
|
+
### Apply a Toffoli + a CSWAP
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
plans = gf.compute_three_qubit_plans([(0, 1, 2)], nq, device, dtype)
|
|
207
|
+
G_re, G_im = gf.build_toffoli_8x8(1, device, dtype)
|
|
208
|
+
state_re, state_im = gf.apply_disjoint_three_qubit_layer(state_re, state_im, plans[0], G_re, G_im)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The full list of builders and apply functions is in `fastqml.gate_fusion`. They all return `(re, im)` real/imaginary pairs and operate on state tensors with shape `(B, 2, 2, ..., 2)` where axis 1 is qubit 0 (MSB of the flat basis index).
|
|
212
|
+
|
|
213
|
+
### Use it inside an `nn.Module` for training
|
|
214
|
+
|
|
215
|
+
See `examples/minimal_cuda_graph_model.py` for the full pattern. The key rules:
|
|
216
|
+
|
|
217
|
+
1. **Build constant gates in `__init__`** and stash via `register_buffer`. Never build constants in `forward()` — Python-list → tensor allocates on the host and breaks CUDA graph capture.
|
|
218
|
+
2. **Compute plans in `__init__`** (`compute_two_qubit_plans`, `compute_three_qubit_plans`) so the per-color `k_idx` and permutations are precomputed.
|
|
219
|
+
3. **Parametric builders go in `forward()`** — `build_ry_rz_gates(...)` etc. are pure GPU-tensor ops, autograd flows through them, and they're safe under capture.
|
|
220
|
+
4. **Wrap the model once with `torch.cuda.make_graphed_callables`** (CUDA) or `torch.compile` (CPU). The toolkit is graph-safe; the wrapping is one line in your training script.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Benchmarks
|
|
225
|
+
|
|
226
|
+
The example file runs four variants of the same `TinyVQC(n_qubits=8, n_layers=3)` model on a batch of 32 states, against PennyLane `lightning.qubit` running the same circuit topology.
|
|
227
|
+
|
|
228
|
+
System: Windows 11, NVIDIA RTX 5090, CPU compile via MSVC 14.44.
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
=== CPU (16 threads) ===
|
|
232
|
+
forward (no_grad):
|
|
233
|
+
eager: 28.95 ms/call
|
|
234
|
+
torch.compile: 6.79 ms/call speedup: 4.26x (compile cost: 58s)
|
|
235
|
+
training step (fwd + bwd + opt):
|
|
236
|
+
eager: 73.58 ms/step
|
|
237
|
+
torch.compile: 15.37 ms/step speedup: 4.79x (compile cost: 107s)
|
|
238
|
+
|
|
239
|
+
=== CUDA (RTX 5090) ===
|
|
240
|
+
forward (no_grad):
|
|
241
|
+
eager: 18.43 ms/call
|
|
242
|
+
cuda-graph: 1.46 ms/call speedup: 12.64x
|
|
243
|
+
training step (fwd + bwd + opt):
|
|
244
|
+
eager: 51.55 ms/step
|
|
245
|
+
cuda-graph: 4.54 ms/step speedup: 11.35x
|
|
246
|
+
|
|
247
|
+
=== PennyLane lightning.qubit (C++ backend) ===
|
|
248
|
+
per-circuit eval (B=1): 1.63 ms/call
|
|
249
|
+
batch of 32 sequential: 52.33 ms (1.64 ms/sample)
|
|
250
|
+
|
|
251
|
+
=== TorchQuantum 0.3.0 (CUDA eager, batched) ===
|
|
252
|
+
forward (no_grad): 13.22 ms/call (0.41 ms/sample)
|
|
253
|
+
training step (fwd+bwd+opt): 37.44 ms/step
|
|
254
|
+
torch.compile: fails (InductorError: complex-dtype states)
|
|
255
|
+
cuda-graph capture: fails (gate matrices built dynamically per call)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
The TorchQuantum run is the identical circuit (cross-checked to 2.5e-10 on output probabilities, `examples/torchquantum_bench.py`). Its batched eager forward beats our eager path; the gap opens at the fast paths, neither of which TorchQuantum can enter — CUDA-graph capture fails on its dynamic gate construction, and `torch.compile` fails on its complex-dtype states (the two constraints the compile-safe `(re, im)` design of this toolkit exists to satisfy).
|
|
259
|
+
|
|
260
|
+
### Per-sample throughput
|
|
261
|
+
|
|
262
|
+
Our forward times divided by `B=32` for direct comparison with PennyLane's per-sample number:
|
|
263
|
+
|
|
264
|
+
| Backend | ms/sample | vs PennyLane |
|
|
265
|
+
|---|---:|---:|
|
|
266
|
+
| PennyLane `lightning.qubit` (C++) | 1.64 | 1.0× |
|
|
267
|
+
| toolkit CPU eager | 0.91 | 1.8× faster |
|
|
268
|
+
| toolkit CPU `torch.compile` | 0.21 | **7.7× faster** |
|
|
269
|
+
| TorchQuantum (CUDA eager, batched) | 0.41 | 4.0× faster |
|
|
270
|
+
| toolkit CUDA eager | 0.58 | 2.8× faster |
|
|
271
|
+
| toolkit CUDA graphs | 0.046 | **35.6× faster** |
|
|
272
|
+
|
|
273
|
+
The gap comes from gate_fusion processing `B` samples as one batched matmul; PennyLane's lightning evaluates one circuit per sample.
|
|
274
|
+
|
|
275
|
+
### Register-ops benchmark
|
|
276
|
+
|
|
277
|
+
`examples/register_ops_bench.py`, RTX 5090, fp32, TF32 off.
|
|
278
|
+
|
|
279
|
+
Multiplexed RyRz (`B=8192`, `nq=8`, 64 select values), three routes to the identical op:
|
|
280
|
+
|
|
281
|
+
| Route | ms/call |
|
|
282
|
+
|---|---:|
|
|
283
|
+
| pure-torch fallback + `torch.compile` | 0.024 |
|
|
284
|
+
| `register_ops` Triton (in-place) | 0.109 |
|
|
285
|
+
| `register_ops` pure-torch fallback | 0.375 |
|
|
286
|
+
| batch-fold + `apply_per_batch_su2_chain` | 10.80 |
|
|
287
|
+
|
|
288
|
+
In an eager loop, the compiled fallback is fastest (Inductor fuses trig + rotation + stacking into fewer kernels than the hand-written Triton path launches). Inside an outer CUDA graph the ordering flips: capture removes launch overhead for every route, and the in-place Triton kernel's single zero-copy pass wins. Compile and graphs are mutually exclusive here (see Limitations), so pick per context.
|
|
289
|
+
|
|
290
|
+
The per-batch-chain route is the only way to express address-indexed angles with gate_fusion primitives; the ~160x gap is permute/copy traffic plus half-a-million tiny batched matmuls, and is the reason the multiplexer exists as its own kernel.
|
|
291
|
+
|
|
292
|
+
Circulant block apply (`A=4` ancilla channels):
|
|
293
|
+
|
|
294
|
+
| Lattice | dim | B | dense (4 GEMMs) | k-space | k-space + `torch.compile` |
|
|
295
|
+
|---|---:|---:|---:|---:|---:|
|
|
296
|
+
| 8x8 | 256 | 8192 | 0.142 ms | 0.831 ms | 0.460 ms |
|
|
297
|
+
| 16x16 | 1024 | 1024 | 0.238 ms | 0.858 ms | 0.358 ms |
|
|
298
|
+
|
|
299
|
+
Dense wins at both sizes on this hardware — the k-space route has ~10x fewer FLOPs but loses to launch/bandwidth overhead at these dims; it is kept for larger registers.
|
|
300
|
+
|
|
301
|
+
`compose_dense` (8 fixed layers, `nq=8`, `B=8192`): sequential applies 0.874 ms vs composed-once single apply 0.140 ms (**6.2x**) — when parameters are static across many evaluations, compose once and amortize.
|
|
302
|
+
|
|
303
|
+
### Reproducing
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
.venv/Scripts/python.exe examples/minimal_cuda_graph_model.py
|
|
307
|
+
.venv/Scripts/python.exe examples/register_ops_bench.py
|
|
308
|
+
.venv/Scripts/python.exe examples/torchquantum_bench.py
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
The TorchQuantum comparison needs `pip install git+https://github.com/mit-han-lab/torchquantum.git --no-deps` plus `opt_einsum qiskit qiskit-ibm-runtime qiskit-aer torchpack torchdiffeq pathos` (the PyPI torchquantum release is pinned to removed qiskit APIs).
|
|
312
|
+
|
|
313
|
+
On Linux/macOS, `torch.compile`'s CPU backend picks up `gcc`/`clang` automatically. On Windows, run from a Visual Studio Developer Command Prompt (or call `vcvars64.bat`) so `cl.exe` is on `PATH`; otherwise the CPU compile path gracefully skips with the error.
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Equivalence tests
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
.venv/Scripts/python.exe test_pennylane_equivalence.py
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Output (32 tests, all at fp64 noise floor):
|
|
324
|
+
|
|
325
|
+
```
|
|
326
|
+
=== Single-qubit gates ===
|
|
327
|
+
OK single-qubit RY/RZ/RX/PhaseShift/H/X/Y/Z/S/T max |diff| = 0 or ~1e-17
|
|
328
|
+
OK Rot(phi,theta,omega) per qubit layer max |diff| = 1.5e-16
|
|
329
|
+
OK apply_su2_layer (RY then RZ per qubit) max |diff| = 1.2e-16
|
|
330
|
+
|
|
331
|
+
=== 2-qubit gates (single) ===
|
|
332
|
+
OK CRY / CRX / CRZ / CPhase max |diff| = 0
|
|
333
|
+
OK IsingZZ / IsingXX / IsingYY max |diff| = 0
|
|
334
|
+
OK CNOT / CY / CZ / SWAP / ISWAP max |diff| = 0
|
|
335
|
+
|
|
336
|
+
=== 2-qubit gates (multi-pair colors) ===
|
|
337
|
+
OK 3 disjoint CRYs in one color max |diff| = 2.8e-17
|
|
338
|
+
OK chain CRY (3 edges, 3 colors) max |diff| = 0
|
|
339
|
+
OK 5 overlapping CPhase (3 colors via diag commute) max |diff| = 6.2e-17
|
|
340
|
+
|
|
341
|
+
=== 3-qubit gates ===
|
|
342
|
+
OK Toffoli, CSWAP, 2 disjoint Toffolis, chain Toffoli max |diff| = 0
|
|
343
|
+
|
|
344
|
+
=== Per-batch ===
|
|
345
|
+
OK apply_per_batch_su2_chain (B=4, nq=5): all batches match
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Register-ops tests
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
.venv/Scripts/python.exe test_register_ops.py
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Output (15 tests; multiplexers checked against explicit fp64 dense references, Triton backward against eager autograd):
|
|
355
|
+
|
|
356
|
+
```
|
|
357
|
+
=== register_ops: multiplexed rotations ===
|
|
358
|
+
OK mux_ry_rz vs dense reference (nq=5, t=1, S=4) max |diff| = 2.4e-07
|
|
359
|
+
OK mux_ry_rz in-place == out-of-place max |diff| = 2.4e-07
|
|
360
|
+
OK mux_ry vs dense reference (nq=5, t=1, S=4) max |diff| = 1.6e-07
|
|
361
|
+
OK mux_ry in-place == out-of-place max |diff| = 2.4e-07
|
|
362
|
+
OK mux_ry_rz vs dense reference (nq=8, t=0, S=64) max |diff| = 3.3e-07
|
|
363
|
+
OK mux_ry_rz in-place == out-of-place max |diff| = 3.6e-07
|
|
364
|
+
OK triton fwd == eager fwd (nq=8, S=64) max |diff| = 4.8e-07
|
|
365
|
+
OK mux_ry_rz autograd d_re/d_im/d_ty/d_tz (triton bwd vs eager autograd) max |diff| = 7.2e-07
|
|
366
|
+
=== register_ops: dense composition ===
|
|
367
|
+
OK compose_dense == sequential apply_dense max |diff| = 7.2e-07
|
|
368
|
+
=== circulant ===
|
|
369
|
+
OK circulant_dense unitary for unitary U(k) (L=4x4, A=4) max |diff| = 3.2e-08
|
|
370
|
+
OK circulant dense apply == k-space apply max |diff| = 7.2e-07
|
|
371
|
+
OK circulant commutes with lattice translations max |diff| = 7.2e-07
|
|
372
|
+
|
|
373
|
+
15/15 passed
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## Conventions
|
|
379
|
+
|
|
380
|
+
**State layout.** A state on `nq` qubits is a pair `(re, im)` of float tensors with shape `(B, 2, 2, ..., 2)` — one batch axis and `nq` qubit axes. Axis 1 is qubit 0 (the MSB of the flat basis index), matching PennyLane's default `qml.state()` ordering.
|
|
381
|
+
|
|
382
|
+
**Gate matrices.** All gate builders return `(G_re, G_im)`. Single-qubit shape: `(nq, 2, 2)` for layer apply, `(B, nq, 2, 2)` for per-batch chains. Two-qubit: `(n_pairs, 4, 4)`. Three-qubit: `(n_triples, 8, 8)`.
|
|
383
|
+
|
|
384
|
+
**Two-qubit basis.** `(c, t)` with `c` as MSB, so the 4-state basis order is `|c=0,t=0>, |c=0,t=1>, |c=1,t=0>, |c=1,t=1>`. For CRY/CRX/CRZ the matrix is identity on `|c=0>...`, the rotation on `|c=1>...`.
|
|
385
|
+
|
|
386
|
+
**Three-qubit basis.** `(q1, q2, q3)` with `q1` as MSB; the 8-state basis order matches.
|
|
387
|
+
|
|
388
|
+
**register_ops layout.** Multiplexer state is flat `(B, 2^nq)` with qubit 0 as MSB. `apply_mux_ry(re, im, angles, target, nq, select_bits)`: angles have shape `(B, 2^select_bits)` and are indexed by the lowest `select_bits` bits of the basis address; the select bits must lie below the target qubit (other layouts reduce to this by a basis permutation). In-place variants (`apply_mux_ry_`, `apply_mux_ry_rz_`) skip autograd for sampling-style loops. `circulant` register index is `a * N + site` with sites row-major on the `L1 x L2` torus; momentum blocks `Uk` have shape `(L1, L2, A, A)`.
|
|
389
|
+
|
|
390
|
+
**Commutation predicates.** Used by the edge-coloring algorithms to decide when reordering across colors is safe.
|
|
391
|
+
- `cry_commute(e1, e2)` — CRX/CRY/CRZ family: don't commute when one's target is the other's control
|
|
392
|
+
- `diagonal_commute(e1, e2)` — CZ/CPhase/IsingZZ: always commute (just need disjoint qubits within a color)
|
|
393
|
+
- `symmetric_commute(e1, e2)` — SWAP/ISWAP/IsingXX/YY: don't commute when sharing any qubit
|
|
394
|
+
- `toffoli_commute(t1, t2)` — Toffoli: target of one ↔ any control of the other
|
|
395
|
+
- `cswap_commute(t1, t2)` — share any qubit ⇒ don't commute
|
|
396
|
+
|
|
397
|
+
Pick the right predicate when calling `compute_two_qubit_plans(...)` / `compute_three_qubit_plans(...)` for your gate.
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Citation
|
|
402
|
+
|
|
403
|
+
If you use fastQML in published research, please cite it:
|
|
404
|
+
|
|
405
|
+
```bibtex
|
|
406
|
+
@software{Reinhardt_fastQML_2026,
|
|
407
|
+
author = {Reinhardt, Eric A. F.},
|
|
408
|
+
title = {{fastQML}: Fast {PyTorch} quantum-circuit primitives via {K}ronecker-product layer fusion},
|
|
409
|
+
year = {2026},
|
|
410
|
+
version = {0.2.1},
|
|
411
|
+
url = {https://github.com/ereinha/fastQML}
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
The multiplexed rotation is the uniformly controlled rotation of quantum-circuit synthesis (Mottonen, Vartiainen, Bergholm & Salomaa, PRL 93, 130502, 2004; Shende, Bullock & Markov, IEEE TCAD 25, 1000, 2006). The optional Triton kernels use Triton (Tillet, Kung & Cox, MAPL 2019). Gate fusion in statevector simulation follows the lineage of qHiPSTER (Smelyanskiy et al., arXiv:1601.07195) and Haner & Steiger (SC'17, arXiv:1704.01127); the equivalence tests and benchmarks use PennyLane (Bergholm et al., arXiv:1811.04968) and TorchQuantum (Wang et al., HPCA 2022, arXiv:2107.10845).
|
|
416
|
+
|
|
417
|
+
A machine-readable [`CITATION.cff`](CITATION.cff) is also included — GitHub renders a "Cite this repository" button on the repo page that copies the citation in BibTeX, APA, or other formats.
|
|
418
|
+
|
|
419
|
+
## License
|
|
420
|
+
|
|
421
|
+
MIT — see [`LICENSE`](LICENSE).
|
|
422
|
+
|
|
423
|
+
## Limitations
|
|
424
|
+
|
|
425
|
+
- **Per-batch unitaries don't fuse.** When a layer's gate angles depend on per-batch quantities (e.g. data re-uploading), the full `(B, 2^nq, 2^nq)` per-batch unitary is too large to materialize for `nq=10` (256 MB at B=64, fp32). `apply_per_batch_su2_chain` falls back to a sequential per-qubit batched matmul — still fast in CUDA graph mode but not as fast as the static-unitary path. When the data-dependent angle is indexed by basis-address bits rather than free per batch element, use `register_ops.apply_mux_ry` / `apply_mux_ry_rz` instead (see the register-ops benchmark).
|
|
426
|
+
- **CUDA graphs and torch.compile are mutually exclusive in this code.** Both want to own graph capture; trying to combine breaks. Use one or the other.
|
|
427
|
+
- **Windows CPU `torch.compile` needs MSVC.** Install Visual Studio Build Tools' C++ workload, then run from a Developer Command Prompt (or `vcvars64.bat`-load the env). Linux/macOS work with their default `gcc`/`clang`.
|
|
428
|
+
- **Inductor's auto-CUDA-graph (`reduce-overhead` mode) is disabled** in `fastqml.gate_fusion` (`torch._inductor.config.triton.cudagraphs = False`) to avoid fighting an outer `make_graphed_callables`. If you use Inductor's auto-graphs instead, comment that out.
|