Chapter 12 — Key Takeaways (Running on Real Hardware)
The hardware page. §12.7's decision procedure is the durable skill; everything else feeds it.
Choosing a backend — not by qubit count
backend qubits 2q gate median 2q err median RO T1 (μs) T2 (μs)
fake_sherbrooke 127 ecr 0.00779 0.0198 278.4 170.0
fake_torino 133 cz 0.00419 0.0229 185.0 140.9
fake_kyiv 127 ecr 0.01172 0.0127 287.1 118.1
fake_osaka 127 ecr 0.00693 0.0211 287.3 140.0
2.8× spread in gate error. Gate sets differ (ecr vs cz) — transpile against the real target.
No single best device. Best gate error has the worst readout, and vice versa.
| Your circuit | Optimize for |
|---|---|
| Shallow, few 2q gates | readout error |
| Deep, many 2q gates | two-qubit gate error |
| Long, idle qubits | $T_2$ |
★★ The spread WITHIN a device dwarfs the spread between them
min median max max/min
readout error 0.0029 0.0198 0.5000 170.7x
2q gate error 0.0035 0.0078 1.0000 288.2x
T1 (μs) 73.2 278.4 514.9 7.0x
T2 (μs) 2.6 170.0 488.8 185.4x
Between devices: 2.8×. Within one device: 288×.
Choosing good qubits matters ~100× more than choosing a good device.
readout error > 10%: [6, 9, 13, 16, 34, 52, 56, 57, 64, 70, 84, 92] (12 of 127)
ecr pairs error ≥ .99: [(6,5),(7,6),(8,9),(16,8),(56,52),(56,57),
(83,84),(85,84),(92,102)] (9 dead)
Chapter 4 found qubit 6 by accident. A five-line query finds all twelve in a second.
"A 127-qubit processor" is a statement about fabrication, not usable resource. Treat a device as a population you sample from, not a machine you run on.
★ readout_error is an AVERAGE, and the averaging hides things
$$\texttt{readout\_error} = \frac{P(1|0) + P(0|1)}{2}$$
| qubit | readout_error |
$P(1\mid 0)$ | $P(0\mid 1)$ | measured $P(1)$ |
|---|---|---|---|---|
| 6 | 0.2573 | 0.5044 | 0.0103 | 0.5044 |
| 70 | 0.1626 | 0.3188 | 0.0063 | 0.3262 |
| 84 | 0.5000 | 1.0000 | 0.0000 | 1.0000 |
| 92 | 0.3406 | 0.0127 | 0.6685 | 0.0142 |
Qubit 84 is not a coin flip — it is STUCK AT 1. Returns 1 from either input, all 4096 shots. $(1+0)/2 = 0.5$ is precisely what a stuck qubit averages to. Qubit 92 is the mirror image.
Measured behavior tracks $P(1|0)$, not the average. Threshold on both directions.
🗝️ Two paths disagree.
NoiseModel.from_backendsymmetrizes →[[0.5,0.5],[0.5,0.5]]→AerSimulator.from_backendgives{'0':2054,'1':2042}.SamplerV2(mode=backend)honors the asymmetry →{'1': 4096}, seed-independent. Same backend, same circuit, different physics."I simulated it with the device noise model" is ambiguous. Say which path.
★ Which direction dominates? It depends on the chip.
| device | $P(1\lvert 0) > P(0\lvert 1)$ | reverse | mean $P(1\lvert 0)$ | mean $P(0\lvert 1)$ |
|---|---|---|---|---|
fake_sherbrooke |
85 | 38 | 0.0585 | 0.0245 |
fake_torino |
69 | 61 | 0.0512 | 0.0422 |
fake_kyiv |
56 | 68 | 0.0281 | 0.0341 |
fake_osaka |
51 | 76 | 0.0410 | 0.0428 |
Textbook expectation: $1 \to 0$ should dominate ($|1\rangle$ decays during readout). Sherbrooke contradicts it 85-to-38; Kyiv and Osaka agree with it; Torino is a coin toss.
The direction flips between devices — there is no rule to memorize, and that is the result. Chapter 2 drew this from 2 qubits; 513 qubits across 4 devices confirm it. Median asymmetry is 1.3–1.9×; Sherbrooke has 11 qubits over 10×.
Measure the asymmetry, don't inherit it.
★ Scoring layouts — and it works
def score_chain(path, target, two_q): # LOWER IS BETTER
total = sum(target["measure"][(q,)].error for q in path)
for a, b in zip(path, path[1:]):
p = target[two_q].get((a, b)) or target[two_q].get((b, a))
total += 3 * (p.error if p and p.error is not None else 1.0)
return total
n=3: 394 connected paths best (122,123,124) 0.0501 worst (83,84,85) 6.6016 131.9x
n=5: 684 connected paths best 0.1104 worst 10.0220 90.7x
Measured, 3-qubit GHZ:
BEST [122,123,124] correct fraction 0.9727
median [ 67, 66, 65] 0.9207
WORST [ 83, 84, 85] 0.2844
97.3% vs 28.4% — a factor of 3.4, same circuit, same chip, same second.
The score is crude (errors combine multiplicatively, not additively; the ×3 is borrowed from SWAP cost). It does not need to be accurate — it needs to RANK, and it does.
Preflight check — refuse to submit onto readout > 10% (both directions) or gate error > 5%. Ten lines. Catches Chapter 4's dead qubit instantly.
Jobs
job = sampler.run([isa], shots=4096)
job.job_id() # SAVE THIS, at submit time
job.status() # QUEUED / RUNNING / DONE / ERROR / CANCELLED
job = service.job("cx1a2b...") # retrieve tomorrow
- Do not watch the queue. Largest time sink there is.
- Never cancel and resubmit — you lose your position.
least_busy()for iteration; a chosen backend for results.- Record backend, layout, score, job id, and date — calibration drifts.
| Mode | Behavior | Use for |
|---|---|---|
| Job | queues independently | one-offs |
| Batch | submitted as a group | independent circuits |
| Session | keeps queue position between iterations | VQE/QAOA loops |
Sessions are metered while open, including while your optimizer thinks.
★★★ Noise or bug? The decision procedure
1. DID IT RUN WHERE YOU THINK? backend.name, final_index_layout(), preflight
2. DOES IT FAIL IN SIMULATION TOO?
fails there too -> IT IS A BUG. STOP.
3. HOW FAR FROM THE NOISELESS REFERENCE?
ratio ~1.0 -> noise ratio ~0.5 or ~0 -> structural
4. IS THE DEVIATION THE RIGHT SIZE? (error budget from calibration data)
within 2x -> noise, EXPLAINED 10x larger -> routing/layout
5. WHAT SHAPE IS THE DEVIATION? (Ch. 11 two axes)
impossible outcomes + balanced -> depolarizing/readout
no impossible outcomes + tilted -> T1
neither, still wrong -> MEASURE ANOTHER BASIS
Steps 1 and 2 are free and resolve most cases. They are the two people skip.
A failure that survives the removal of noise is not a noise failure. One line, airtight inference. Four bugs so far in this book (Ch. 7 layout trap, Ch. 8 parameter ordering, Ch. 8 CS1's four physics hypotheses, Ch. 10 routing) would each have been caught in seconds by it.
Check the pipeline before the physics.
Three failures that looked alike (CS2)
| correct | diagnosed by | verdict | |
|---|---|---|---|
| A | 0.9727 | step 4 — budget predicted 0.9706 | noise, explained |
| B | 0.2844 | step 1 — preflight | bad layout |
| C | 0.4897 | step 2 — noiseless sim | BUG |
The worst-looking run was easiest; the mildest-looking one was the only bug.
- Run B: only 4 of 8 outcomes appeared, all with the middle bit set — that bit reads stuck qubit 84.
- Run C: two clean peaks,
000and011— not111.111appeared 5 times in 4096.
A tall peak on a single WRONG outcome is never noise. Noise spreads. One tall wrong peak is deterministic — it lives in your circuit, layout, or bit ordering.
Read bit positions, not just totals. Both histograms named their own fault; both scalar summaries discarded it.
Common pitfalls
- Choosing a backend by qubit count.
- Thresholding on the averaged readout error (passes a stuck qubit at 0.5).
- Assuming "the device noise model" is one thing.
- Watching the queue; cancelling and resubmitting.
- Reaching for physics before running the noiseless simulation.
- Continuing down the procedure after an early step gave a clean answer (Run B's error budget would have misled).
- Reporting a result without the layout and the date.
Project piece added this chapter
vqelab/backends.py v3 — device_health(), best_layout(), preflight() /
require_preflight(), readout_directions(), and RunRecord extended with job id, layout, score,
and date. 8 tests, all passing, including one asserting that best_layout() always passes its own
preflight.