Migrate to using Qiskit Runtime primitives
This guide describes key patterns of behavior and use cases with code
examples to help you migrate code from the legacy qiskit-ibmq-provider
package to use the Qiskit Runtime primitives.
Overview
There are two methods for accessing IBM Quantum systems. First, the qiskit-ibm-provider
package provides the backend.run()
interface, allowing direct access to IBM Quantum systems with no pre- or post-processing involved. This level of access is suitable for those users who want precise control over circuit execution and result processing. This level of access is needed for those at the level of kernel developer who are looking to develop, for example, circuit optimization routines or error mitigation techniques, or who want to characterize quantum systems.
In contrast, Qiskit Runtime is designed to streamline algorithm and application construction by removing the need for users to understand technical hardware and low-level software details. Advanced processing techniques for error suppression and mitigation are automatically applied, giving users high-fidelity results without the burden of having to code these routines themselves. Sessions within Qiskit Runtime allow users to run iterative algorithm circuits back to back, or batch collections of circuits without having to re-queue each job. This results in more efficient quantum processor use and reduces the time users spend running complex computations.
Primitives are the recommended tool to write quantum algorithms, as they encapsulate common device queries seen in application packages and allow for managed performance through the Qiskit Runtime service. However, if your algorithm requires more granular information, such as pre-shot measurements, the primitives might not provide the desired abstraction level.
The Qiskit Runtime primitives implement the reference Sampler
and
Estimator
interfaces found in
qiskit.primitives.
These interfaces let you switch between primitive implementations with
minimal code changes. Different primitive implementations can be found
in the qiskit
, qiskit_aer
, and qiskit_ibm_runtime
libraries. Each
implementation serves a specific purpose:
- The primitives in
qiskit
can perform local state vector simulations - useful for quickly prototyping algorithms. - The primitives in
qiskit_aer
give access to the local Aer simulators for tasks such as noisy simulation. - The primitives in
qiskit_ibm_runtime
provide access to cloud simulators and real hardware through the Qiskit Runtime service. They include exclusive features such as built-in circuit optimization and error mitigation support.
The only primitives that provide access to the Qiskit Runtime service are those imported from qiskit_ibm_runtime
(Qiskit Runtime Primitives).
When migrating, the key to writing an equivalent algorithm using primitives is to first identify what is the minimal unit of information your algorithm is based on:
- If it uses an expectation value, you will need an
Estimator
. - If it uses a probability distribution (from sampling the device), you will need a
Sampler
.
After determining which primitive to use, identify where the algorithm
accesses the backend. Look for the call to backend.run()
. Next, you
will replace this call with the respective primitive call, as shown in
the following examples.
This guide is for algorithm developers who need to refactor algorithms to use primitives instead of backend.run()
. See examples here:
The following topics are use cases with code migration examples:
- Update parameter values while running
- Algorithm tuning options (shots, transpilation, error mitigation)
FAQs
Users might have the following questions when planning to migrate their code to Qiskit Runtime:
How do the Qiskit Runtime primitives differ from backend.run?
There are two methods for accessing IBM Quantum systems. First, the qiskit-ibm-provider package provides the backend.run() interface, allowing direct access to IBM Quantum systems with no pre- or post-processing involved. This level of access is suitable for those users who want precise control over circuit execution and result processing. This level of access is needed for those looking to work at the level Kernel developer developing, for example, circuit optimization routines, error mitigation techniques, or characterizing quantum systems.
In contrast, Qiskit Runtime is designed to streamline algorithm and application construction by removing the need for users to understand technical hardware and low-level software details. Advanced processing techniques for error suppression and mitigation are automatically applied, giving users high-fidelity results without the burden of having to code these routines themselves. The inclusion of sessions within Qiskit Runtime allows users to run iterative algorithm circuits back to back, or batch collections of circuits without having to re-queue each job. This results in more efficient quantum processor utilization and reduces the total amount of time users spend running complex computations.
Which channel should I use?
After deciding to use Qiskit Runtime primitives, the user must determine whether to access Qiskit Runtime through IBM Cloud or IBM Quantum Platform. Some information that might help you decide includes:
- The available plans:
- Qiskit Runtime is available in both the Open (free access) or Premium (contract-based paid access) plan on IBM Quantum Platform. See IBM Quantum access plans (opens in a new tab) for details.
- Qiskit Runtime is accessible through the Lite (free access) or Standard (pay-as-you-go access) plan in IBM Cloud. See Qiskit Runtime plans (opens in a new tab) on IBM Cloud for details.
- The use case requirements:
- IBM Quantum Platform offers a visual circuit composer (Quantum Composer) and a Jupyter Notebook environment (Quantum Lab).
- IBM Cloud offers a cloud native service that is ideal if users need to integrate quantum capabilities with other cloud services.
How do I set up my channel?
After deciding which channel to use to interact with Qiskit Runtime, you can get set up on either platform by following the steps in Install and set up.
Should I modify the Qiskit Terra algorithms?
As of v0.22, Qiskit Terra algorithms (opens in a new tab) use Qiskit Runtime primitives. Thus, there is no need for users to modify amplitude estimators or any other Qiskit Terra algorithms.
Which primitive should I use?
When choosing which primitive to use, you first need to understand whether the algorithm uses a quasi-probability distribution sampled from a quantum state (a list of quasi-probabilities), or an expectation value of a certain observable with respect to a quantum state (a real number).
A probability distribution is often of interest in optimization problems that return a classical bit string, encoding a certain solution to a problem at hand. In these cases, you might be interested in finding a bit string that corresponds to a ket value with the largest probability of being measured from a quantum state, for example.
An expectation value of an observable could be the target quantity in scenarios where knowing a quantum state is not relevant. This often occurs in optimization problems or chemistry applications. For example, when trying to discover the extremal energy of a system.
Migrate setup from qiskit-ibmq-provider
This guide describes how to migrate code from the legacy IBMQ provider
qiskit-ibmq-provider
package to use Qiskit Runtime
qiskit-ibm-runtime
. This guide includes instructions to
migrate legacy runtime programs to the new syntax. However, the ability
to use custom uploaded programs is pending deprecation, so these should
be migrated to use primitives instead.
Changes in Class name and location
The classes related to Qiskit Runtime that used to be included in
qiskit-ibmq-provider
are now part of qiskit-ibm-runtime
. Before, the
provider used to populate the qiskit.providers.ibmq.runtime
namespace
with objects for Qiskit Runtime. These now live in the
qiskit_ibm_runtime
module.
The module from which the classes are imported has changed. The
following table contains example access patterns in
qiskit.providers.ibmq.runtime
and their new form in
qiskit_ibm_runtime
:
class in qiskit-ibmq-provider | class in qiskit-ibm-runtime | Notes |
---|---|---|
qiskit.providers.ibmq.runtime.IBMRuntimeService | qiskit_ibm_runtime.QiskitRuntimeService | IBMRuntimeService class was removed from qiskit-ibm-runtime 0.6 and replaced by the new class in qiskit-ibm-runtime. |
qiskit.providers.ibmq.runtime.RuntimeJob | qiskit_ibm_runtime.RuntimeJob | |
qiskit.providers.ibmq.runtime.RuntimeProgram | qiskit_ibm_runtime.RuntimeProgram | |
qiskit.providers.ibmq.runtime.UserMessenger | qiskit_ibm_runtime.program.UserMessenger | New location: qiskit_ibm_runtime.program |
qiskit.providers.ibmq.runtime.ProgramBackend | qiskit_ibm_runtime.program.ProgramBackend | New location: qiskit_ibm_runtime.program |
qiskit.providers.ibmq.runtime.ResultDecoder | qiskit_ibm_runtime.program.ResultDecoder | New location: qiskit_ibm_runtime.program |
qiskit.providers.ibmq.runtime.RuntimeEncoder | qiskit_ibm_runtime.RuntimeEncoder | |
qiskit.providers.ibmq.runtime.RuntimeDecoder | qiskit_ibm_runtime.RuntimeDecoder | |
qiskit.providers.ibmq.runtime.ParameterNamespace | qiskit_ibm_runtime.ParameterNamespace | |
qiskit.providers.ibmq.runtime.RuntimeOptions | qiskit_ibm_runtime.RuntimeOptions |
Import path
The import path has changed as follows:
Legacy
from qiskit import IBMQ
Updated
from qiskit_ibm_runtime import QiskitRuntimeService
Save and load accounts
Use the updated code to work with accounts.
Legacy: Save accounts
IBMQ.save_account("<IQX_TOKEN>", overwrite=True)
Updated: Save accounts The new syntax accepts credentials for Qiskit Runtime on IBM Cloud or IBM Quantum Platform. For more information on retrieving account credentials, see Install and set up.
# IBM Cloud channel
QiskitRuntimeService.save_account(channel="ibm_cloud", token="<IBM Cloud API key>", instance="<IBM Cloud CRN>", overwrite=True)
# IBM Quantum channel; set to default
QiskitRuntimeService.save_account(channel="ibm_quantum", token="<IQP_TOKEN>", overwrite=True, default=true)
Updated: Name saved credentials You can now name your saved credentials and load the credentials by name.
Example:
# Save different accounts for open and premium access
QiskitRuntimeService.save_account(channel="ibm_quantum", token="<IQX_TOKEN>", instance="h1/g1/p1", name="premium")
QiskitRuntimeService.save_account(channel="ibm_quantum", token="<IQX_TOKEN>", instance="h2/g2/p2", name="open")
# Load the "open" credentials
service = QiskitRuntimeService(name="open")
Legacy: Load accounts
IBMQ.load_account()
Updated: Load accounts
The new syntax combines the functionality from load_account()
and
get_provider()
in one statement. The channel
input parameter is
optional. If multiple accounts have been saved in one device and no
channel
is provided, the default is "ibm_cloud"
.
# To access saved credentials for the IBM cloud channel
service = QiskitRuntimeService(channel="ibm_cloud")
# To access saved credentials for the IBM quantum channel
service = QiskitRuntimeService(channel="ibm_quantum")
Channel selection (get a provider)
Use the updated code to select a channel.
Legacy
provider = IBMQ.get_provider(project="my_project", group="my_group", hub="my_hub")
Updated
The new syntax combines the functionality from load_account()
and
get_provider()
in one statement. When using the ibm_quantum
channel,
the hub
, group
, and project
are specified through the new
instance
keyword.
# To access saved credentials for the IBM quantum channel and select an instance
service = QiskitRuntimeService(channel="ibm_quantum", instance="my_hub/my_group/my_project")
Get the backend
Use the updated code to view backends.
Legacy
provider = IBMQ.get_provider(hub="h1", group="g1", project="p1")
backend = provider.get_backend("ibm_backend")
Updated
# You can specify the instance in service.backend() instead of initializing a new service
backend = service.backend("ibm_backend", instance="h1/g1/p1")
Upload, view, or delete custom prototype programs
To work with custom programs, replace provider.runtime
with service
.
This function is pending deprecation.
Legacy
# Printing existing programs
provider.runtime.pprint_programs()
# Deleting custom program
provider.runtime.delete_program("my_program") # Substitute "my_program" with your program ID
# Uploading custom program
program_id = provider.runtime.upload_program(
data=program_data,
metadata=program_json
)
Updated
# Printing existing programs
service.pprint_programs()
# Deleting custom program
service.delete_program("my_program") # Substitute "my_program" with your program ID
# Uploading custom program
program_id = service.upload_program(
data=program_data,
metadata=program_json
)
Run prototype programs
To run prototype programs, replace provider.runtime
with service
.
This function is pending deprecation.
Legacy
program_inputs = {"iterations": 3}
options = {"backend_name": backend.name()}
job = provider.runtime.run(program_id="hello-world",
options=options,
inputs=program_inputs
)
print(f"job id: {job.job_id()}")
result = job.result()
print(result)
Updated
program_inputs = {"iterations": 3}
options = {"backend": ""}
job = service.run(program_id="hello-world",
options=options,
inputs=program_inputs
)
print(f"job id: {job.job_id()}")
result = job.result()
print(result)
Parametrized circuits with primitives
Parametrized circuits are a commonly used tool for quantum algorithm
design. Because backend.run()
did not accept parametrized
circuits, the parameter binding step had to be integrated in the
algorithm workflow. The primitives can perform the parameter binding
step internally, which results in a simplification of the algorithm-side
logic.
The following example summarizes the new workflow for managing parametrized circuits.
Example
Let's define a parametrized circuit:
from qiskit.circuit import QuantumCircuit, ParameterVector
n = 3
thetas = ParameterVector('θ',n)
qc = QuantumCircuit(n, 1)
qc.h(0)
for i in range(n-1):
qc.cx(i, i+1)
for i,t in enumerate(thetas):
qc.rz(t, i)
for i in reversed(range(n-1)):
qc.cx(i, i+1)
qc.h(0)
qc.measure(0, 0)
qc.draw()
We want to assign the following parameter values to the circuit:
import numpy as np
theta_values = [np.pi/2, np.pi/2, np.pi/2]
Legacy
Previously, the parameter values had to be bound to their respective
circuit parameters prior to calling backend.run()
.
from qiskit import Aer
bound_circuit = qc.bind_parameters(theta_values)
bound_circuit.draw()
backend = Aer.get_backend('aer_simulator')
job = backend.run(bound_circuit)
counts = job.result().get_counts()
print(counts)
Primitives
Now, the primitives take in parametrized circuits directly, together with the parameter values, and the parameter assignment operation can be performed more efficiently on the server side of the primitive.
This feature is particularly interesting when working with iterative algorithms because the parametrized circuit remains unchanged between calls while the parameter values change. The primitives can transpile once and then cache the unbound circuit, using classical resources more efficiently. Moreover, only the updated parameters are transferred to the cloud, saving additional bandwidth.
from qiskit.primitives import Sampler
sampler = Sampler()
job = sampler.run(qc, theta_values)
result = job.result().quasi_dists
print(result)
Algorithm tuning
One of the advantages of the primitives is that they abstract away the circuit execution setup so that algorithm developers can focus on the pure algorithmic components. However, sometimes, to get the most out of an algorithm, you might want to tune certain primitive options. For details, see Advanced runtime options.