Converting from Qiskit Runtime Programs¶
This tutorial will be a demonstation of converting your custom Qiskit Runtime Program into a Qiskit Serverless QiskitPattern
.
If you were using Qiskit Runtime Programs before, your code probably looks similar to the following example:
"""A sample runtime program that submits random circuits for user-specified iterations."""
import random
from qiskit import transpile
from qiskit.circuit.random import random_circuit
def prepare_circuits(backend):
circuit = random_circuit(
num_qubits=5, depth=4, measure=True, seed=random.randint(0, 1000)
)
return transpile(circuit, backend)
def main(backend, user_messenger, **kwargs):
"""Main entry point of the program.
Args:
backend: Backend to submit the circuits to.
user_messenger: Used to communicate with the program consumer.
kwargs: User inputs.
"""
iterations = kwargs.pop("iterations", 5)
for it in range(iterations):
qc = prepare_circuits(backend)
result = backend.run(qc).result()
user_messenger.publish({"iteration": it, "counts": result.get_counts()})
return "Hello, World!"
All Qiskit Runtime Programs have a main
method which accepts backend
, user_messenger
and **kwargs
. This method is not required for Qiskit Serverless patterns.
Qiskit Serverless handles backends, logging, and input arguments a bit differently than Qiskit Runtime:
backend
. For Qiskit Serverless programs you are not limited to single backend for a program. You can call any number of backends from single program. SinceBackend.run
is deprecated, we will be using Qiskit Primitives to do our calculation.user_messenger
were used in Qiskit Runtime Programs to facilitate retrieving logs from the program. Qiskit Serverless does not require passing such an object. Instead, all contents ofstdout
(e.g. print statements, logging messages) will be provided to the user via the Qiskit Serverless job handler.**kwargs
was a variable used to capture program inputs from the user. Users should now input their arguments to theServerlessProvider.run
method, and the arguments should be retrieved within the pattern using theget_arguments
function from Qiskit Serverless.To save the results of a pattern, the
save_result
function should be used. It accepts a python dictionary and can be accessed via the job handler.
Let’s use the guidelines above to transform the above Qiskit Runtime Program into a Qiskit Serverless QiskitPattern.
# migrated_pattern.py
"""A sample runtime pattern that submits random circuits for user-specified iterations."""
import random
from qiskit import transpile
from qiskit.circuit.random import random_circuit
from qiskit.primitives import Sampler
from qiskit_serverless import get_arguments, save_result
def prepare_circuits():
circuit = random_circuit(
num_qubits=5, depth=4, measure=True, seed=random.randint(0, 1000)
)
return transpile(circuit)
arguments = get_arguments()
iterations = arguments.get("iterations", 5)
for it in range(iterations):
qc = prepare_circuits()
result = Sampler.run(qc).result()
print({"iteration": it, "dists": result.quasi_dists})
save_result({"result": "Hello, World!"})
Let’s save this code as ./src/migrated_pattern.py
and execute it using the QiskitPattern
class from the qiskit_serverless
package.
[1]:
from qiskit_serverless import QiskitPattern
pattern = QiskitPattern(
title="migrated-pattern", entrypoint="migrated_pattern.py", working_dir="./src/"
)
[2]:
from qiskit_serverless import ServerlessProvider
import os
serverless = ServerlessProvider(
token=os.environ.get("GATEWAY_TOKEN", "awesome_token"),
host=os.environ.get("GATEWAY_HOST", "http://localhost:8000"),
)
serverless
[2]:
<ServerlessProvider: gateway-provider>
[3]:
serverless.upload(pattern)
[3]:
'migrated-pattern'
While Qiskit Runtime programs required users to upload their program and call it in two separate steps, the QiskitPattern
class allows users to send a job for remote execution in a single step.
[4]:
job = serverless.run("migrated-pattern", arguments={"iterations": 3})
job
[4]:
<Job | 0ae02e24-859a-43c1-a9e9-038dcb7dc296>
[5]:
job.result()
[5]:
{'result': 'Hello, World!'}
[6]:
print(job.logs())
OpenBLAS WARNING - could not determine the L2 cache size on this system, assuming 256k
OpenBLAS WARNING - could not determine the L2 cache size on this system, assuming 256k
{'iteration': 0, 'dists': [{0: 0.01953568379784, 1: 0.003434588839427, 2: 0.000962772320948, 3: 0.022007500316319, 4: 0.018613812117662, 5: 0.003272513622768, 6: 0.000917339944671, 7: 0.020968985795759, 8: 0.089342856396779, 9: 0.100647254570651, 10: 0.004403062114611, 11: 0.015707460288483, 12: 0.085126845839344, 13: 0.095897799438366, 14: 0.004195285498675, 15: 0.014966239097696, 16: 0.018613812117662, 17: 0.003272513622768, 18: 0.007093750939097, 19: 0.014792574801333, 20: 0.01953568379784, 21: 0.003434588839427, 22: 0.007445077580607, 23: 0.01552519505666, 24: 0.085126845839344, 25: 0.067651119863038, 26: 0.032441965074003, 27: 0.014966239097696, 28: 0.089342856396779, 29: 0.071001623840398, 30: 0.034048692844864, 31: 0.015707460288483}]}
{'iteration': 1, 'dists': [{0: 0.179254126288168, 1: 0.382666923582299, 4: 0.031208803971925, 5: 0.066623721595232, 16: 0.092444479011949, 17: 0.197348005974499, 20: 0.016094924471265, 21: 0.034359015104663}]}
{'iteration': 2, 'dists': [{0: 0.003593062771524, 2: 0.037032118667734, 4: 0.032043767985561, 6: 0.330261031899324, 8: 0.005805731220397, 10: 0.059837119799466, 12: 0.002779551551986, 14: 0.028647616104007, 16: 0.003593062771524, 18: 0.037032118667734, 20: 0.032043767985561, 22: 0.330261031899324, 24: 0.005805731220397, 26: 0.059837119799466, 28: 0.002779551551986, 30: 0.028647616104007}]}