Running on Real Hardware
This tutorial explains how to move from local simulation to cloud simulators and real quantum processors (QPUs). You will learn the differences between backends, their costs, and when to use each one.
By the end, you will understand:
- The available backend options and how to select them
- Cost and timing differences between simulators and QPUs
- How
MarqovDevicehandles backend-specific circuit conversion automatically - How to configure the backend when submitting jobs
Prerequisites
- Completed the Your First Quantum Script tutorial
- A Marqov account (QPU access requires an active plan)
Backend Options
Marqov supports several backends through the MarqovDevice abstraction. Each backend has different speed, cost, and fidelity characteristics.
Simulators
| Backend | Description | Cost | Typical Speed |
|---|---|---|---|
local | Local Braket simulator, runs on the Marqov worker | Free | Milliseconds |
marqov-sim | Alias for local | Free | Milliseconds |
sv1 | AWS Braket StateVector simulator (SV1) | Per-task pricing | 1-5 seconds |
dm1 | AWS Braket DensityMatrix simulator | Per-task pricing | 1-10 seconds |
tn1 | AWS Braket TensorNetwork simulator | Per-task pricing | Varies |
Quantum Processors (QPUs)
| Backend | Provider | Qubit Count | Notes |
|---|---|---|---|
| IonQ devices | IonQ (trapped ion) | 11-32 qubits | High fidelity, slower gate times |
| Rigetti devices | Rigetti (superconducting) | 80+ qubits | Faster gates, higher error rates |
| IQM devices | IQM (superconducting) | 20 qubits | European provider |
QPU jobs are subject to queue wait times, which can range from seconds to hours depending on demand.
Step 1: Start with Local Simulation
Always develop and test with the local backend first. It is free, fast, and gives you immediate results:
from marqov import task, workflow, Circuit
@task
def run_circuit():
circuit = Circuit().h(0).cnot(0, 1)
from marqov import LocalExecutor
import asyncio
executor = LocalExecutor()
result = asyncio.run(executor.execute(circuit, shots=1000))
return {"counts": result.counts}
@workflow
def my_experiment():
return run_circuit()Submit this with the local backend. Results return in under a second.
Step 2: Move to a Cloud Simulator (SV1)
When your circuit is ready, switch to SV1 for a more realistic simulation environment. SV1 runs on AWS infrastructure and supports up to 34 qubits.
To run on SV1, use the MarqovDevice API inside your task:
from marqov import task, workflow, Circuit, get_device
@task(retries=3, timeout=3600)
def run_on_sv1(backend_params):
"""Execute a Bell State on SV1."""
device = get_device(backend_params)
circuit = Circuit().h(0).cnot(0, 1)
counts = device.run(circuit, shots=1000)
return {
"counts": counts,
"backend": device.backend_name,
"is_simulator": device.is_simulator,
}
@workflow
def sv1_experiment(backend_params):
return run_on_sv1(backend_params)Key differences from local execution:
@task(retries=3, timeout=3600)— Cloud services can fail (network issues, throttling). Retries with exponential backoff protect against transient failures. The 1-hour timeout accounts for QPU queue times.get_device(backend_params)— Factory function that creates aMarqovDeviceconfigured for the specified backend. Thebackend_paramsdict comes from the job submission configuration.device.run(circuit, shots=1000)— Executes the circuit on the configured backend and returns measurement counts as a dictionary.
How MarqovDevice Works
MarqovDevice provides a uniform interface across all backends. Under the hood, it handles two conversions:
-
Normalize the input circuit —
MarqovDeviceaccepts any supported circuit type:marqov.Circuit, BraketCircuit, QiskitQuantumCircuit, CirqCircuit, PennyLaneQuantumScript, or a raw OpenQASM string. It normalizes everything tomarqov.Circuitfirst. -
Convert to backend-native format — Depending on the target backend, it converts to the appropriate SDK format:
- AWS backends (local, SV1, DM1, QPUs): converts to Braket
Circuitvia.to_braket() - Azure backends (IonQ via Azure, etc.): converts to Qiskit
QuantumCircuitvia.to_qiskit()
- AWS backends (local, SV1, DM1, QPUs): converts to Braket
This means you can write your circuit once and run it on any backend:
# All of these work with device.run():
circuit = Circuit().h(0).cnot(0, 1) # marqov.Circuit
circuit = "OPENQASM 2.0;\nqreg q[2];\nh q[0];" # QASM string
circuit = BraketCircuit().h(0).cnot(0, 1) # Braket Circuit
circuit = QuantumCircuit(2).h(0).cx(0, 1) # Qiskit QuantumCircuitStep 3: Run on a QPU
Running on real quantum hardware follows the same pattern. The only change is the backend configuration:
@task(retries=3, timeout=3600)
def run_on_qpu(backend_params):
"""Execute on a real quantum processor."""
device = get_device(backend_params)
circuit = Circuit().h(0).cnot(0, 1)
counts = device.run(circuit, shots=1000)
return {
"counts": counts,
"backend": device.backend_name,
"is_simulator": device.is_simulator,
"_summary": {
"Backend": device.backend_name,
"Type": "Simulator" if device.is_simulator else "QPU",
"Shots": "1000",
},
}The code is identical to the SV1 version. The difference is in the backend selection at job submission time.
Configuring the Backend at Submission
When you click “Run as Job” in the playground, a dialog appears where you select the backend. The available options are:
- local — free local simulator
- sv1 — AWS Braket SV1 simulator
- dm1 — AWS Braket DensityMatrix simulator
- ionq — IonQ trapped-ion QPU (when available)
The platform passes the backend configuration to your workflow as part of the execution parameters. The worker constructs the backend_params dictionary with the necessary credentials, S3 paths, and device ARNs.
When to Use Each Backend
Use local when:
- Developing and debugging your circuit
- Running unit tests
- Circuits have fewer than 20 qubits
- You want instant feedback at zero cost
Use sv1 when:
- You need a cloud-grade statevector simulation
- Testing circuits with up to 34 qubits
- Validating results before QPU submission
- Running benchmarks with realistic execution times
Use a QPU when:
- You need results from real quantum hardware
- Studying noise effects on your algorithm
- Your circuit is optimized and debugged
- You have verified correctness on a simulator first
Cost Considerations
Simulators (SV1, DM1) are billed per task by AWS. A typical simulation costs fractions of a cent.
QPUs are billed per shot and per task. Costs vary significantly by provider:
- IonQ: billed per shot and per gate. A 1000-shot, 2-qubit circuit might cost a few dollars.
- Rigetti: billed per task. Pricing is per-second of QPU access.
Always validate on a simulator before submitting to a QPU. A bug in your circuit that runs 1000 iterations on a QPU can get expensive quickly.
Timing Differences
Expect significant variation in execution time across backends:
| Backend | Bell State (1000 shots) | VQE Step (5 Pauli terms) |
|---|---|---|
local | ~10ms | ~50ms |
sv1 | ~2s | ~10s (parallel) / ~18s (sequential) |
| IonQ QPU | ~30s-5min (includes queue) | ~3-30min |
QPU execution times include queue wait time, which varies by demand. During peak hours, you might wait minutes to hours.
Retries and Timeouts
Cloud backends can fail for various reasons: network timeouts, API throttling, service outages. Marqov’s @task decorator integrates with Temporal’s retry mechanism:
@task(retries=3, timeout=3600)
async def measure_pauli(circuit_dict, pauli, executor_config):
# If this fails, Temporal retries up to 3 times
# with exponential backoff
...retries=3— Maximum 3 retry attempts on failure. Temporal handles exponential backoff automatically.timeout=3600— 1-hour timeout. QPUs can have long queue times, so set this generously.
For pure compute tasks (like combining measurement results), use shorter timeouts:
@task(retries=2, timeout=60)
def compute_energy(zi, iz, zz, xx, yy):
# Pure computation, no external calls
...Next Steps
- Parallel Task Execution — learn how Marqov parallelizes independent tasks across backends
- Viewing Results — interpret QPU results on the execution dashboard
- Building a VQE Optimization — build a real VQE with parallel Pauli measurements