Skip to Content
DocsTutorialsRunning on Hardware

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 MarqovDevice handles backend-specific circuit conversion automatically
  • How to configure the backend when submitting jobs

Prerequisites

Backend Options

Marqov supports several backends through the MarqovDevice abstraction. Each backend has different speed, cost, and fidelity characteristics.

Simulators

BackendDescriptionCostTypical Speed
localLocal Braket simulator, runs on the Marqov workerFreeMilliseconds
marqov-simAlias for localFreeMilliseconds
sv1AWS Braket StateVector simulator (SV1)Per-task pricing1-5 seconds
dm1AWS Braket DensityMatrix simulatorPer-task pricing1-10 seconds
tn1AWS Braket TensorNetwork simulatorPer-task pricingVaries

Quantum Processors (QPUs)

BackendProviderQubit CountNotes
IonQ devicesIonQ (trapped ion)11-32 qubitsHigh fidelity, slower gate times
Rigetti devicesRigetti (superconducting)80+ qubitsFaster gates, higher error rates
IQM devicesIQM (superconducting)20 qubitsEuropean 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 a MarqovDevice configured for the specified backend. The backend_params dict 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:

  1. Normalize the input circuitMarqovDevice accepts any supported circuit type: marqov.Circuit, Braket Circuit, Qiskit QuantumCircuit, Cirq Circuit, PennyLane QuantumScript, or a raw OpenQASM string. It normalizes everything to marqov.Circuit first.

  2. 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 Circuit via .to_braket()
    • Azure backends (IonQ via Azure, etc.): converts to Qiskit QuantumCircuit via .to_qiskit()

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 QuantumCircuit

Step 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:

BackendBell 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

Last updated on