Local-first, honestly-benchmarked hybrid quantum-classical ML. Every quantum model ships
beside a mandatory classical baseline in the same report (D2); claiming quantum advantage is
permanently out of scope (D1). Built against a private estate spec; the honest-benchmarking
rules it enforces (D1/D2/D7, §18D) are documented in docs/decisions.md.
Status: Phases 1–6 built (v1 complete). Phase 6 — the publishing pipeline — turns each
merged experiment folder into a blog-ready, VISUAL, honest post (qlab publish): front-matter,
plain-English intro, results table with the mandatory classical baseline (D2), embedded figures
(reused matplotlib PNG/GIF + newly-generated on-brand SVG), an auto-included honest-caveats
section (D1), and a reproducible-source link (D7). Published: the posts and the
Honest QML Lab case study are live on theinvalid.me.
| Phase | task | quantum | classical baseline | honest verdict |
|---|---|---|---|---|
| 1 | two-moons (CPU) | re-uploading VQC 0.978 | logreg 0.853 · MLP 0.978 | ties MLP — framework trains |
| 2 | MNIST 0/1 (hybrid) | hybrid 0.9954 (3-seed) | logreg 0.9962 · MLP 0.9954 | quantum loses (§18D) |
| 3 | real HW (ibm_fez) | ⟨Z₀⟩ sim +0.724 → raw +0.486 → mitigated +0.870 | — | noise real, ZNE lifts it (3× shots) |
| 4a | barren plateau | global Var[∂C/∂θ] collapses 1436× (α≈0.638) | local α≈0.089 (7× shallower) | trainability fails at scale |
| 4b | quantum kernel | two-moons 0.787 · MNIST 0.900 | RBF-SVM 0.973 · 1.000 | quantum kernel loses both |
| 5 | agentic loop | best VQC 0.9822 (10 sandboxed cycles) | baseline 0.9778 | +0.0044 ≈ 1σ, no advantage |
| 6 | publish | 6 blog-ready posts, 6 on-brand SVGs generated | — | drafts only, honesty auto-included |
Phase-2 GPU truth-check (RTX 5090, verified 2026-07-09): torch 2.11.0+cu128 lists sm_120
(real GPU autodiff, not silent CPU); lightning.gpu runs a 20-qubit circuit on the card;
statevector benchmark sweeps 4→28 qubits on GPU and hits the 32 GB wall at 30q (≈16 GiB
statevector, over-commit refused). The hybrid at 0.9954 loses to logreg 0.9962 — the
honest, spec-sanctioned outcome (D1/§18D), not a bug.
Per-phase release tags (cut by the owner at merge): v0.1-phase1 … v0.6-phase6.
uv sync --extra dev
uv run qlab train --config configs/two_moons.yaml # the gated 3-seed two-moons benchmark
make test # pytest + coverage (CPU-only, CI-green)make gpu-env # build .venv-gpu (torch cu128 + lightning.gpu)
make gpucheck # print the sm_120 / lightning.gpu truth-check
make mnist # MNIST-0/1 hybrid + GPU benchmark sweep
make test-gpu # run the torch-lane tests (CI skips these)Each run writes a full proof lineage to experiments/<timestamp>_<name>/ (D7 — every number
traces to a CSV): Phase 1 → config · metrics · seed_summary · report · summary + boundary/
curve PNGs + boundary_evolution.gif; Phase 2 → config · seed_summary · epochs · benchmark
CSVs + benchmark.png · training_curve.png · report.md · summary.json · capability.json.
An LLM proposes VQC variants; each runs sandboxed on the CPU simulator (network-denied,
write-scoped, resource-capped, wall-clock-killed — the NUI nui-verify isolation pattern,
DP-A6), is scored against the Phase-1 human baseline (0.978, identical splits, D2), and gets a
written rationale. The agent can never reach a real QPU — the sandbox has no network
(simulator-only, D3/D4).
make sandbox-selftest # prove the sandbox denies network + a file-escape + a hang (DP-A6)
make agent # 10 autonomous cycles: propose → run SANDBOXED → score → rationaleIf LiteLLM :4000 is up (D5) the proposer/report-writer use it; if it is down the proposer falls
back to a deterministic heuristic and the report to a template — stated honestly, no fabricated
reasoning (D1). Proof lineage: cycles.csv · rationales.md · sandbox_denials.json · cycleNN_score.json · report.md · summary.json.
The flagship SOTA module: the Phase-5 loop turned into a two-objective search for the central
open question of variational QML — a circuit that is BOTH trainable (gradient variance high, no
barren plateau) AND classically hard (a Pauli-propagation surrogate can't cheaply reproduce it).
The 2024-25 Cerezo result says that corner is essentially empty; S-1 lets the agent hunt it. Each
LLM-proposed variant is scored inside the reused bwrap sandbox on trainability (reusing Phase-4
run_bp_cell) and hardness (reusing CW-1 pauli_prop.audit_circuit), and the loop maps the
trainable×hard Pareto frontier.
make s1 # 16 agent-proposed variants → score both axes SANDBOXED → frontier + conclusion
make test-s1 # the S-1 unit suite (proposer + in-sandbox scorer + search runner)Honest finding (D1): the trainable-AND-hard corner stays (near-)empty — the surviving hits are
WEAK single-surrogate candidates (Pauli-hard ≠ genuinely hard; a circuit can be Pauli-hard yet
MPS-cheap, the CW-1 disagreement), reported as candidates pending an MPS re-audit, not as a solved
grail. Simulator-only, zero hardware budget (the agent has no device field → no QPU path, D3/D4).
Proof lineage: variants.csv · rationales.md · cerezo_plane.png · sandbox_denials.json · varNN_score.json · report.md · summary.json.
The SOTA answer to the fidelity-kernel concentration Phase 4b measured (off-diagonal Gram
variance collapsed ~64× as qubits grew 2→10, and the kernel lost to classical RBF). The
projected quantum kernel (Huang et al., Power of data in QML, 2021) encodes x with the SAME
IQP feature map, then reads out the local Pauli expectations {⟨X_i⟩,⟨Y_i⟩,⟨Z_i⟩} (the
single-qubit RDMs) and runs a classical Gaussian kernel on that fixed 3n-dim vector. Living in a
low-dim classical space, it resists the concentration the exponentially-thin state overlap suffers.
make s2 # 4-way compare (projected-QK vs fidelity-QK vs RBF vs VQC, D2) + concentration study
make test-s2 # the S-2 unit suite (projected kernel + runner)Honest finding (D1/D2): the projected kernel resists concentration (its off-diagonal
variance stays alive across widths while the fidelity kernel's collapses) but still does NOT beat
the classical RBF on this data — the SOTA fix cures the pathology without delivering advantage.
Resisting concentration is necessary, not sufficient, for quantum advantage. Simulator-only, zero
hardware budget. Proof lineage: four_way.csv · concentration.csv · concentration_verdict.json · concentration_compare.svg · report.md.
Phase 4 MEASURED the barren plateau (global-cost gradient variance collapses 1436×, α≈0.638). S-3 asks the honest follow-up: does any known escape technique keep gradients alive on our setup — and at what cost? It re-runs the SAME gradient-variance sweep (reusing Phase 4's ansatz + efficient single-parameter-shift gradient UNCHANGED) under four techniques and fits α for each vs the baseline: small-angle init, identity-block init, layerwise (shallow) training, and the local-cost anchor.
make s3 # α per technique vs the Phase-4 baseline + on-brand comparison plot
make test-s3 # the S-3 unit suite (escape core + runner)Honest finding (D1): small-angle (α≈+0.01) and identity-block (α≈−0.14) keep gradients alive —
but only at initialisation (the circuit starts near identity; training drifts away). Layerwise
(α≈+0.66) does not beat the width-driven global-cost plateau. The one technique that reliably
keeps gradients alive across widths is the local cost (α≈+0.089) — but per S-1 shallow+local is
the classically-simulable regime. No init-only technique escapes the plateau for the deep
global-cost circuit that would be non-simulable — escaping ties you back to simulability. Simulator-
only, zero hardware budget. Proof lineage: escape_metrics.csv · escape_fits.csv · escape_logvar.png · escape_compare.svg · report.md.
The SAME sim-trained VQC (Phase 3, native-Qiskit reconstruction) run on a second, physically-
distinct real backend via Open Quantum ($50/90-day tier fronting IonQ/AQT trapped-ion +
IQM/Rigetti superconducting), behind the SAME HardwareBackend interface. Yields an honest
superconducting (IBM) vs trapped-ion (IonQ) sim/raw/mitigated cross-hardware table. Open
Quantum bills credits (not QPU-seconds), guarded by a sibling out-of-repo CreditBudgetGuard
with the same inviolable D4 contract (no override, survives git clean).
make oq-env # build .venv-oq (openquantum-sdk[qiskit] + qiskit + aer), separate lane
make oq-backends # list Open Quantum backends + credit balance (ZERO spend)
make crosshw-dryrun # sim/raw/mitigated on noisy Aer (ZERO credits) — verify the path first
make crosshw-oq # REAL: 1 sample × 3 ZNE scales × 100 shots on ionq:forte-1 (~15 credits)Proof lineage: summary.json · cross_hardware.json · equivalence.json · report.md · weights.npy;
every credit is logged in the out-of-repo credit ledger. Creds env-only (0600 SDK-key), never
committed.
Turns an experiment folder into a theinvalid.me content-as-files post. Idempotent + config-driven; reuses the existing plots and generates on-brand SVGs (dark canvas, one gold accent — the estate diagram rule, not mermaid): a pipeline diagram, the sim→raw→mitigated hardware bar chart, the "where the 5090 dies" memory-wall chart, the barren-plateau decay curve, the kernel-concentration curve, and the agentic-loop trajectory.
make publish # all experiment folders → publish/output/<slug>/{post.md,preview.html,assets/}
uv run qlab publish --experiment two-moons # build ONE folder
uv run qlab publish --stage-drafts <drafts-dir> # also copy into the site's gitignored drafts/Each post carries, by construction: front-matter (matches the theinvalid-site blog schema), an
ELI10 intro, a results table with the classical baseline (D2), embedded figures (reused PNG/GIF +
≥1 generated SVG), an honest-caveats section (D1), and a reproducible-source link (D7). A
self-contained preview.html renders every figure so the visuals are provably present. Nothing
is deployed live — publish/output/ is the phase deliverable; go-live is the owner's call
("publish ≠ reachable").
src/qlab/
config.py typed, validated experiment config (device is a config field, D8)
data/ seeded, standardized two-moons split (identical for VQC + baselines)
circuits/ embeddings (angle/IQP) + ansatze (StronglyEntangling/BasicEntangler)
device.py GPU capability detection + automatic CPU fallback (D8, Phase 2)
models/vqc.py configurable, data-RE-UPLOADING variational quantum classifier
models/hybrid_torch.py nn.Module: encoder → quantum TorchLayer → head (GPU lane)
data/mnist_binary.py MNIST 0/1 → PCA → N qubits (Phase 2)
baselines/ logistic regression + MLP (mandatory mirror, D2)
bench/ statevector wall-time + VRAM vs qubits, to the memory wall (Phase 2)
train/ Adam VQC trainer + hybrid torch trainer + runners
viz/ decision-boundary + training-curve + benchmark PNGs + GIF
report.py report.md renderer (always carries honest caveats, D1)
agent/ Phase 5: sandbox.py (bwrap/docker, DP-A6) · scorer.py (untrusted, in-jail) ·
llm.py (LiteLLM :4000 D5) · proposer.py · experiment_agent.py · report_writer.py ·
tools.py (typed propose/run/read) · cerezo_scorer.py + cerezo_proposer.py (S-1,
in-jail two-axis scorer + plane proposer)
surrogates/ D9 simulability audit — mps_surrogate.py (lightning.tensor, χ) +
pauli_prop.py (CW-1, pure-numpy Pauli-propagation/LOWESA, weight) +
compare_runner.py (`qlab surrogate-compare` — two-surrogate agree/disagree table)
hardware/ research/ real-hardware (IBM + Open Quantum) · Phase-4 research modules ·
research/cerezo_search.py (S-1 two-objective trainable×hard search + frontier)
publish/ Phase 6: palette.py (brand) · svggen.py (on-brand SVG) · posts.py (per-phase
builders, data-driven from summary.json) · pipeline.py · render_html.py (preview)
- Two lanes. CPU lane (
.venv,pyproject.toml) =default.qubit/lightning.qubit, no torch — this is what CI runs. GPU lane (.venv-gpu,requirements-gpu.txt) = torch cu128 +lightning.gpufor Phase 2, a separate pinned env (D8, §18A-2). CI stays green CPU-only: torch-lane testsimportorskip('torch'). - No port claim. v1 is a CLI/library.
:9310–:9329is reserved only if a future phase becomes a service. - No secrets in repo or logs; gitleaks in pre-commit + CI.
data/(MNIST cache) and.venv-gpu/are gitignored.
See docs/decisions.md and PROBLEMS-AND-SOLUTIONS.md.