{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Parallel workflows with QiskitFunction\n", "\n", "In this document, we will learn how to run distributed workflows inside a function. In this case, we will compute the quasi-probability distribution in parallel for a list of quantum circuits.\n", "\n", "Let's take a look at the function file [./source_files/function_with_parallel_workflow.py](./source_files/function_with_parallel_workflow.py). \n", "\n", "```python\n", "from qiskit_serverless import get_arguments, save_result, distribute_task, get\n", "\n", "from qiskit import QuantumCircuit\n", "from qiskit.primitives import Sampler\n", "\n", "\n", "@distribute_task()\n", "def distributed_sample(circuit: QuantumCircuit):\n", " \"\"\"Distributed task that returns quasi distribution for given circuit.\"\"\"\n", " return Sampler().run(circuit).result().quasi_dists[0]\n", "\n", "\n", "arguments = get_arguments()\n", "circuits = arguments.get(\"circuits\")\n", "\n", "\n", "# run distributed tasks as async function\n", "# we get task references as a return type\n", "sample_task_references = [\n", " distributed_sample(circuit)\n", " for circuit in circuits\n", "]\n", "\n", "# now we need to collect results from task references\n", "results = get(sample_task_references)\n", "\n", "save_result({\n", " \"results\": results\n", "})\n", "```\n", "\n", "There are a lot of new concepts introduced in this Qiskit Function, so let's go over them in more detail:\n", "\n", "The [distribute_task](https://qiskit.github.io/qiskit-serverless/stubs/qiskit_serverless.core.distribute_task.html#qiskit_serverless.core.distribute_task) decorator converts a function into a distributed task. This means that the function will be executed on compute resources asynchronously and in parallel to the main context of the Qiskit Function.\n", "\n", "When you call a converted function, it will return a reference to the function execution instead of the result. In order to get the result back, you need to call the [get](https://qiskit.github.io/qiskit-serverless/stubs/qiskit_serverless.core.get.html#qiskit_serverless.core.get) function on the function reference. `get` will wait until the function is finished and return the result of the function execution.\n", "\n", "In the Qiskit Function above, we have applied the `distribute_task` decorator to the `distributed_sample` function. This function takes a `QuantumCircuit` as input and returns the quasi distribution for that circuit.\n", "\n", "After we have defined the `distributed_sample` function, we read the circuits from the Qiskit Function arguments using the [get_arguments](https://qiskit.github.io/qiskit-serverless/stubs/qiskit_serverless.serializers.get_arguments.html#qiskit_serverless.serializers.get_arguments) function. We then call the `distributed_sample` function for each of the circuits, which creates a reference to each of the function executions.\n", "\n", "These function executions will run in parallel on compute resources, and we get task references as the return type. We store these task references in the `sample_task_references` list.\n", "\n", "After we have created the task references for each of the function executions, we need to collect the results from these tasks. We do this by calling the `get` function on the list of task references, which waits until all the tasks have completed and returns the results.\n", "\n", "Once we have the results, we can save them using the [save_result](https://qiskit.github.io/qiskit-serverless/stubs/qiskit_serverless.core.save_result.html#qiskit_serverless.core.save_result) function.\n", "\n", "Essentially, this Qiskit Function reads the circuits from the Qiskit Function arguments, executes the `distributed_sample` function on each circuit in parallel, collects the results from the function executions, and saves the results." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> ⚠ This provider is set up with default credentials to a test cluster intended to run on your machine. For information on setting up infrastructure on your local machine, check out the guide on [local infrastructure setup](https://qiskit.github.io/qiskit-serverless/deployment/local.html)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from qiskit_serverless import ServerlessClient\n", "import os\n", "\n", "client = ServerlessClient(\n", " token=os.environ.get(\"GATEWAY_TOKEN\", \"awesome_token\"),\n", " host=os.environ.get(\"GATEWAY_HOST\", \"http://localhost:8000\"),\n", " # If you are using the kubernetes approach the URL must be http://localhost\n", ")\n", "\n", "client" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's create a list of random circuits which we will be passed as arguments to the function." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[,\n", " ,\n", " ]" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from qiskit.circuit.random import random_circuit\n", "\n", "circuits = [random_circuit(2, 2) for _ in range(3)]\n", "[circuit.measure_all() for circuit in circuits]\n", "circuits" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Run the function as usual, but pass the circuits in as a keyword argument, `circuits`." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "QiskitFunction(function-with-parallel-workflow)" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from qiskit_serverless import QiskitFunction\n", "\n", "function = QiskitFunction(\n", " title=\"function-with-parallel-workflow\",\n", " entrypoint=\"function_with_parallel_workflow.py\",\n", " working_dir=\"./source_files/\",\n", ")\n", "\n", "client.upload(function)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "parallel_function = client.get(\"function-with-parallel-workflow\")\n", "job = parallel_function.run(circuits=circuits)\n", "job" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'DONE'" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "job.status()" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'results': [{'00': 1024}, {'00': 1024}, {'01': 1002, '00': 22}]}" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "job.result()" ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.9" } }, "nbformat": 4, "nbformat_minor": 4 }