Skip to main contentIBM Quantum Documentation Preview
This is a preview build of IBM Quantum® documentation. Refer to quantum.cloud.ibm.com/docs for the official documentation.

Dry-run jobs

A dry-run job runs your workload through IBM Quantum® Compute Service without using any QPU time. Use a dry-run job to do the following before you commit to a real run:

  • **Get the usage that the job would incur on the real QPU.
  • Validate the job. Quantum Compute checks the job's parameters, the same way it does for a real job.
  • Get the circuit timing schedule, if you request it, to debug the job before you run it for real.

Unlike dry-run jobs, local testing (fake backends and Qiskit Aer) runs on your own machine and therefore cannot provide usage numbers, service-side validation, or service timing information. To compare the two approaches, see Mock devices compared to fake backends.

Note
  • qiskit-ibm-runtime v0.51.0 or later is required for dry-run jobs.
  • Dry-run jobs are available in all regions, and are only available on the Flex, Premium, and On-Prem plans. They are not available on the Open or Pay-As-You-Go plans.

How it works

To run a dry-run job, pass dry_run=True to the primitive's run() method:

backend = service.backend("ibm_phoenix")
isa_circuit = pm.run(circuit)  # transpiled for ibm_phoenix
sampler = Sampler(mode=backend)
job = sampler.run([isa_circuit], dry_run=True)
job.result()  # wait for completion
print(job.usage())

dry_run is an argument of the run() method (the default is False), not a runtime option. It is available to all primitives.

A dry-run job has the following characteristics:

  • It runs in IBM Quantum Compute Service in the cloud, not on your machine.
  • It uses the calibration data from the real QPU, not just a snapshot.
  • Its results keep the expected schema and follow the expected shape of results from the real QPU, but contain randomized mock data. They are not actual or simulated measurement results, so do not use them to check physical correctness.
  • Processing happens on the server, so the job might not finish immediately.

Circuit size, shots, and other limits are the same as for any job of that program.

dry_run=True also works when the backend is already a mock device.


Read the usage

The usage of a completed dry-run job is the usage the same job would incur on the real QPU, except in rare situations where calibration data changes significantly after the dry-run job and before the job on a real QPU.

  • job.usage() returns the usage in seconds.
  • job.metrics()["usage"] returns the same data together with the usage status.

Dry-run usage is not charged, does not consume your plan allocation, and does not count against any limits.

Do not use job.usage_estimation with dry-run jobs

Do not use job.usage_estimation on a dry-run job. Use job.usage().

job.usage() returns 0 until the usage calculation is final, so wait for the job to finish before you read it.


Circuit timing schedule

To request the circuit timing schedule, use the same option as for a real job. For full details, see Visualize circuit timing.


Limitations

  • Job and batch modes only. Dry-run jobs can only run in job mode and batch mode.
  • Flex, Premium, and On-Prem plans only. Dry-run jobs are not available on the Open or Pay-As-You-Go plans.

Mock devices

Mock devices are the implementation behind dry-run jobs. Dry-run jobs hide mock devices by default, but you encounter them in these cases:

  • Instances configured to access only mock devices. Example: if you use service.backend("ibm_phoenix") to submit a dry-run job on ibm_phoenix, but your instance doesn't have access to ibm_phoenix, you will get a QiskitBackendNotFoundError. Instead, select the mock device directly with service.backend("mock_phoenix").
  • Compute resources. Mock devices are hidden by default on the Compute resources page. The table on the page has a filter option to include them, but only the Flex, Premium, and On-Prem plans have access to mock devices.
  • Workloads table. Jobs from mock devices are hidden by default on the Workloads page. The table has a filter option (the funnel icon) to display them.
  • Python API. service.backends() and service.jobs() exclude mock devices unless you call them with include_mocks=True. IBMBackend.is_mock indicates whether a backend is a mock device. service.least_busy() never returns a mock device.

Mock devices compared to fake backends

The names are similar, but a mock device and a fake backend are different things. A fake backend is a local object in your Python environment. A mock device is a cloud resource in IBM Quantum Compute Service.

Column 1
Mock device (for example mock_phoenix)
Fake backend (for example FakeManilaV2)
What it isA device in IBM Quantum Compute ServiceA Python class in qiskit_ibm_runtime.fake_provider
Where it runsCloud, in the serviceLocally, on your machine
How you get itservice.backend("mock_phoenix"), or automatically with run(..., dry_run=True)from qiskit_ibm_runtime.fake_provider import ...
Account and instanceSpecific plans only (Flex, Premium, or On-Prem)All instances and plans
CalibrationLive, matches the real device at any timeFixed snapshot shipped with the installed package
ResultsRandomized data with the expected schema. Not a simulationNoisy simulation that uses the snapshot's noise model
Physically meaningful resultsNo. Never use them to check correctnessApproximately, within the limits of the noise model
Usage reportedYesNone
Charged or counted against plan allocation or limitsNoNo
Job validationDone by the service, same as a real jobLimited to what the client checks
Timing scheduleAvailable on requestNot available
Execution modesJob and batch mode only. Sessions are blockedSession syntax accepted but ignored (local mode has no real sessions or batches)
CompletionAsynchronous, on the serverRuns locally and returns when finished
Shown in platform interface and API listsHidden by default (Compute resources, Workloads table, backends(), jobs()). Opt in to showNever, because nothing is sent to the service

How to choose

  • To find out how much usage the job will consume, or whether the service will accept it, use a dry-run job.
  • To find out what the output might look like, or to work offline, use a fake backend or Aer.
  • To do both, iterate on circuits and results with a fake backend, then dry-run as the last check before the real QPU.

Common points of confusion

  • A mock device is not a simulator. Its output is random, so sampling a Bell circuit on mock_phoenix does not return a Bell distribution. If you want realistic noisy output, use a fake backend.
  • A fake backend is not the live device. Its calibration is a snapshot and might be out of date. A mock device tracks the real calibration, so transpiled circuits and timing match what the QPU sees today.
  • Neither runs on real hardware. Neither consumes QPU time, but only a dry-run job goes through the service.
  • usage_estimation is not the dry-run estimate. For a dry-run job, job.usage() returns the seconds of usage your job would be charged on a real QPU.

Use cases

Students and courses

An administrator restricts an instance to mock devices only, using the QPU customization toggle when creating or editing the instance. Students then validate their payloads end-to-end without consuming real QPU time or RUs. Because the instance has access only to mock devices, students select the backend by its mock name:

backend = service.backend("mock_phoenix")
sampler = Sampler(mode=backend)
job = sampler.run([isa_circuit], dry_run=True)

Project planning

A team about to start a project dry-runs representative jobs to see the RUs the project needs, before choosing a plan or allocation:

backend = service.backend("ibm_phoenix")
sampler = Sampler(mode=backend)
job = sampler.run([isa_circuit], dry_run=True)
job.result()
print(job.usage()) 

Final check before running on a real QPU

Validate parameters and inspect the timing schedule with dry_run=True, then drop the argument and run on the real QPU. Nothing else in your code changes:

sampler = Sampler(mode=backend)

# Pre-flight check
job = sampler.run([isa_circuit], dry_run=True)
job.result()

# Real run - same code, without dry_run
job = sampler.run([isa_circuit])

Next steps

Recommendations