diff --git a/docs/guides/serverless-manage-resources.ipynb b/docs/guides/serverless-manage-resources.ipynb index cb4927e70e..8b22f8306c 100644 --- a/docs/guides/serverless-manage-resources.ipynb +++ b/docs/guides/serverless-manage-resources.ipynb @@ -17,27 +17,60 @@ "id": "b2d40a63-3359-46e9-8f1b-4746b449b407", "metadata": {}, "source": [ - "For classical tasks that can be parallelized, use the `@distribute_task` decorater to define compute requirements needed to perform a task. Start by recalling the `transpile_parallel.py` example from the [Write your first Qiskit Serverless program](./serverless-first-program) topic:" + "For classical tasks that can be parallelized, use the `@distribute_task` decorater to define compute requirements needed to perform a task. Start by recalling the `transpile_remote.py` example from the [Write your first Qiskit Serverless program](./serverless-first-program) topic:" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, + "id": "2e8824d8-41cc-47fc-b42f-3aec8690d46b", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "# This cell is hidden from users\n", + "import os\n", + "from pathlib import Path\n", + "Path(\"./serverless-manage-resources-workspace/source_files\").mkdir(exist_ok=True, parents=True)\n", + "os.chdir(\"./serverless-manage-resources-workspace\")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, "id": "475d82f0-15cc-4db3-b3b0-54b07822b2a0", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Writing ./source_files/transpile_remote.py\n" + ] + } + ], "source": [ - "# /source_files/transpile_remote.py\n", + "%%writefile ./source_files/transpile_remote.py\n", "\n", "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n", + "from qiskit_ibm_runtime import QiskitRuntimeService\n", "from qiskit_serverless import distribute_task\n", "\n", + "service = QiskitRuntimeService()\n", + "\n", "@distribute_task(target={\"cpu\": 1})\n", "def transpile_remote(circuit, optimization_level, backend):\n", - " \"\"\"Transpiles an abstract circuit into an ISA circuit for a given backend.\"\"\"\n", + " \"\"\"Transpiles an abstract circuit (or list of circuits) into an ISA circuit for a given backend.\"\"\"\n", " pass_manager = generate_preset_pass_manager(\n", " optimization_level=optimization_level,\n", - "\t\tbackend=backend\n", + "\t\tbackend=service.backend(backend)\n", " )\n", " isa_circuit = pass_manager.run(circuit)\n", " return isa_circuit" @@ -48,20 +81,69 @@ "id": "a5914f1d-f898-4db4-8d1e-ccc8081883b9", "metadata": {}, "source": [ - "In this example, you decorated the `transpile_remote()` method with `@distribute_task(target={\"cpu\": 1})`. When run, this creates an asynchronous parallel worker task with a single CPU core, and returns with a reference to track the worker. To fetch the result, pass the reference to the `get()` method:" + "In this example, you decorated the `transpile_remote()` function with `@distribute_task(target={\"cpu\": 1})`. When run, this creates an asynchronous parallel worker task with a single CPU core, and returns with a reference to track the worker. To fetch the result, pass the reference to the `get()` function:" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 3, "id": "74fdcd4a-01cd-46ca-aa24-2a8a3605346f", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Appending to ./source_files/transpile_remote.py\n" + ] + } + ], "source": [ - "```python\n", - "from qiskit_serverless import get\n", + "%%writefile --append ./source_files/transpile_remote.py\n", + "\n", + "from qiskit_serverless import get, get_arguments, save_result\n", + "\n", + "arguments = get_arguments()\n", + "circuit = arguments.get(\"circuit\")\n", + "optimization_level = arguments.get(\"optimization_level\")\n", + "backend = arguments.get(\"backend\")\n", "\n", - "transpile_worker_reference = transpile_remote(circuit, optimization_level, backend)\n", + "transpile_worker_reference = transpile_remote(\n", + " circuit,\n", + " optimization_level,\n", + " backend\n", + ")\n", "result = get(transpile_worker_reference)\n", - "```" + "save_result(result)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "b54a47bb-9522-413e-b6a5-02859712b3e3", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Appending to ./source_files/transpile_remote.py\n" + ] + } + ], + "source": [ + "%%writefile --append ./source_files/transpile_remote.py\n", + "# This cell is hidden from users. It checks the transpilation ran correctly.\n", + "from qiskit import QuantumCircuit\n", + "assert isinstance(result, QuantumCircuit)" ] }, { @@ -73,114 +155,160 @@ ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 5, "id": "ac99b4a0-4a42-4c43-869d-265344b70359", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Appending to ./source_files/transpile_remote.py\n" + ] + } + ], "source": [ - "```python\n", + "%%writefile --append ./source_files/transpile_remote.py\n", + "\n", "transpile_worker_references = [\n", " transpile_remote(circuit, optimization_level, backend)\n", - " for circuit in circuit_list\n", + " for circuit in arguments.get(\"circuit_list\")\n", "]\n", "\n", "results = get(transpile_worker_references)\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "611fe030-4494-46b5-9ea1-9678ac513210", - "metadata": {}, - "source": [ - "### Explore different task configurations\n", - "\n", - "You can flexibly allocate CPU, GPU, and memory for your tasks via `@distribute_task()`. For Qiskit Serverless on IBM Quantum™ Platform, each program is equipped with 16 CPU cores and 32 GB RAM, which can be allocated dynamically as needed.\n", - "\n", - "CPU cores can be allocated as full CPU cores, or even fractional allocations, as shown in the following.\n", - "\n", - "Memory is allocated in number of bytes. Recall that there are 1024 bytes in a kilobyte, 1024 kilobytes in a megabyte, and 1024 megabytes in a gigabyte. To allocate 2 GB of memory for your worker, you need to allocate `\"mem\": 2 * 1024 * 1024 * 1024`." + "save_result(results) # Overwrites any previously saved results" ] }, { "cell_type": "code", - "execution_count": null, - "id": "cea90969-cfbf-4181-9ffa-524f3709dc69", - "metadata": {}, - "outputs": [], + "execution_count": 6, + "id": "b872a4b0-280a-41e8-8fa3-6a633ea64b2e", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Appending to ./source_files/transpile_remote.py\n" + ] + } + ], "source": [ - "@distribute_task(target={\n", - " \"cpu\": 16,\n", - " \"mem\": 32 * 1024 * 1024 * 1024\n", - "})\n", - "def transpile_remote(circuit, optimization_level, backend):\n", - " return None" + "%%writefile --append ./source_files/transpile_remote.py\n", + "# This cell is hidden from users. It checks the transpilation ran correctly.\n", + "assert all(isinstance(result, QuantumCircuit) for result in results)" ] }, { - "cell_type": "markdown", - "id": "f6fff174-301f-47ca-b7c6-69bb5298fd14", - "metadata": {}, + "cell_type": "code", + "execution_count": 7, + "id": "ad1914fa-bfd0-4aa0-936b-fa41bde6090e", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Job still running, waiting 5s then retrying (19)\n" + ] + } + ], "source": [ - "## Automatic QPU selection\n", - "\n", - "This example demonstrates how to use `IBMQPUSelector` to automate the process of selecting which qubits to use from a set of available QPUs. Instead of manually selecting a QPU, Qiskit Serverless automatically allocates a QPU according to desired criteria. `IBMQPUSelector`s are imported from server-side `qiskit_serverless_tools.selectors` modules, and must be invoked from your uploaded program.\n", + "# This cell is hidden from users.\n", + "# It uploads the serverless program and checks it runs.\n", "\n", - "Here, `IBMLeastNoisyQPUSelector` finds the QPU that yields the least-noisy qubit subgraph for the input circuit, from among the QPUs available to you through your IBM Quantum account.\n", + "def test_serverless_job(title, entrypoint):\n", + " # Import in function to stop them interfering with user-facing code\n", + " from qiskit.circuit.random import random_circuit\n", + " from qiskit_serverless import IBMServerlessClient, QiskitFunction\n", + " from IPython.display import clear_output\n", + " import time\n", + " import uuid\n", "\n", - "For each `IBMQPUSelector`, the context is set in the constructor. All `IBMQPUSelectors` require Qiskit Runtime credentials. The `IBMLeastNoisyQPUSelector` requires a circuit and transpile options specifying how the circuit should be optimized for each QPU, to determine the most optimal QPU and qubit layout.\n", + " title += \"_\" + uuid.uuid4().hex[:8]\n", + " serverless = IBMServerlessClient()\n", + " transpile_remote_demo = QiskitFunction(\n", + " title=title,\n", + " entrypoint=entrypoint,\n", + " working_dir=\"./source_files/\",\n", + " )\n", + " serverless.upload(transpile_remote_demo)\n", + " job = serverless.get(title).run(\n", + " circuit=random_circuit(3,3),\n", + " circuit_list=[random_circuit(3,3) for _ in range(3)],\n", + " backend=\"ibm_kyiv\",\n", + " optimization_level=1,\n", + " )\n", + " for retry in range(25):\n", + " status = job.status()\n", + " if status == \"DONE\":\n", + " break\n", + " if status == \"ERROR\":\n", + " raise Exception(\"Something went wrong. See following serverless logs:\\n\\n\" + job.logs())\n", + " clear_output()\n", + " print(f\"Job still running, waiting 5s then retrying ({retry})\")\n", + " time.sleep(5)\n", "\n", - "All `IBMQPUSelectors` implement a `get_backend` method, which retrieves the optimal QPU with respect to the given context. The `get_backend` method also allows for additional filtering of the QPUs. It is implemented using the same interface as the [QiskitRuntimeService.backends](/api/qiskit-ibm-runtime/qiskit_ibm_runtime.QiskitRuntimeService#backends) method." + "test_serverless_job(\n", + " title=\"transpile_remote_serverless_test\",\n", + " entrypoint=\"transpile_remote.py\"\n", + ")" ] }, { "cell_type": "markdown", - "id": "b9b38d02-1c4b-4598-abc8-f2ff2f3c1d23", + "id": "611fe030-4494-46b5-9ea1-9678ac513210", "metadata": {}, "source": [ - "```python\n", - "# source_files/transpile_parallel.py\n", - "\n", - "from qiskit_ibm_runtime import QiskitRuntimeService\n", - "from qiskit.circuit.random import random_circuit\n", - "from qiskit_serverless_tools.selectors import IBMLeastNoisyQPUSelector\n", + "### Explore different task configurations\n", "\n", - "service = QiskitRuntimeService(channel='ibm_quantum', token=API_TOKEN)\n", + "You can flexibly allocate CPU, GPU, and memory for your tasks via `@distribute_task()`. For Qiskit Serverless on IBM Quantum™ Platform, each program is equipped with 16 CPU cores and 32 GB RAM, which can be allocated dynamically as needed.\n", "\n", - "abstract_circuit = random_circuit(\n", - " num_qubits=100, depth=4, measure=True\n", - ")\n", + "CPU cores can be allocated as full CPU cores, or even fractional allocations, as shown in the following.\n", "\n", - "selector = IBMLeastNoisyQPUSelector(\n", - " service, circuit=abstract_circuit, transpile_options={\"optimization_level\": 3}\n", - ")\n", - "backend = selector.get_backend(min_num_qubits=127)\n", - "target_circuit = selector.optimized_circuit\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "8eda9b3e-31c3-4783-b5f6-a72bd31e41b6", - "metadata": {}, - "source": [ - "You can also use the `IBMLeastBusyQPUSelector` to find a QPU that can support the circuit width but with the shortest queue." + "Memory is allocated in number of bytes. Recall that there are 1024 bytes in a kilobyte, 1024 kilobytes in a megabyte, and 1024 megabytes in a gigabyte. To allocate 2 GB of memory for your worker, you need to allocate `\"mem\": 2 * 1024 * 1024 * 1024`." ] }, { "cell_type": "code", - "execution_count": null, - "id": "10bdb211-0d53-4269-aa92-51ecf494bd63", + "execution_count": 8, + "id": "cea90969-cfbf-4181-9ffa-524f3709dc69", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Overwriting ./source_files/transpile_remote.py\n" + ] + } + ], "source": [ - "# source_files/transpile_parallel.py\n", - "\n", - "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n", - "from qiskit_serverless_tools.selectors import IBMLeastBusyQPUSelector\n", + "%%writefile --append ./source_files/transpile_remote.py\n", "\n", - "backend = IBMLeastBusyQPUSelector(service).get_backend(min_num_qubits=127)\n", - "pm = generate_preset_pass_manager(optimization_level=3, backend=backend)\n", - "target_circuit = pm.run(abstract_circuit)" + "@distribute_task(target={\n", + " \"cpu\": 16,\n", + " \"mem\": 2 * 1024 * 1024 * 1024\n", + "})\n", + "def transpile_remote(circuit, optimization_level, backend):\n", + " return None" ] }, { @@ -200,17 +328,17 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 12, "id": "0183278f-8ce3-4466-9255-097b2d211052", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'{\"message\":\"/usr/src/app/media/5f37582aa306c50013fac285/transpile_demo.tar\"}'" + "'{\"message\":\"/usr/src/app/media/5e1f442128cdf60018496a04/transpile_demo.tar\"}'" ] }, - "execution_count": 8, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -221,10 +349,12 @@ "# Create a tar\n", "filename = \"transpile_demo.tar\"\n", "file = tarfile.open(filename,\"w\")\n", - "file.add(\"source_files/transpile_remote.py\")\n", + "file.add(\"./source_files/transpile_remote.py\")\n", "file.close()\n", "\n", "# Upload the tar to Serverless data directory\n", + "from qiskit_serverless import IBMServerlessClient\n", + "serverless = IBMServerlessClient()\n", "serverless.file_upload(filename)" ] }, @@ -238,7 +368,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 13, "id": "14241fc4-d0cb-4803-8752-a460e1f48708", "metadata": {}, "outputs": [ @@ -248,7 +378,7 @@ "['transpile_demo.tar']" ] }, - "execution_count": 9, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -267,46 +397,77 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 14, "id": "ef649b2a-ed95-4dd2-89d9-61438faa7c1e", "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "100%|██████████| 10.2k/10.2k [00:00<00:00, 10.0MiB/s]\n" - ] - }, { "name": "stdout", "output_type": "stream", "text": [ - "downloaded_3d3c4bf6_transpile_demo.tar\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\n" + "Appending to ./source_files/tarfile_example.py\n" ] } ], "source": [ - "# transpile_parallel.py\n", + "%%writefile ./source_files/extract_tarfile.py\n", "\n", "import tarfile\n", + "from qiskit_serverless import IBMServerlessClient\n", + "from qiskit_ibm_runtime import QiskitRuntimeService\n", "\n", + "service = QiskitRuntimeService()\n", + "serverless = IBMServerlessClient(\n", + " token=service.active_account()['token']\n", + ")\n", "files = serverless.files()\n", "demo_file = files[0]\n", "downloaded_tar = serverless.file_download(demo_file)\n", "\n", - "print(downloaded_tar)\n", "with tarfile.open(downloaded_tar, 'r') as tar:\n", " tar.extractall()" ] }, + { + "cell_type": "code", + "execution_count": 15, + "id": "252f0990-5643-46d4-9a58-c77fb50d17f4", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Job still running, waiting 5s then retrying (19)\n" + ] + } + ], + "source": [ + "# This cell is hidden from users.\n", + "\n", + "# Upload the serverless program and checks it runs.\n", + "test_serverless_job(\n", + " title=\"transpile_remote_serverless_test\",\n", + " entrypoint=\"extract_tarfile.py\"\n", + ")\n", + "\n", + "# Clean up\n", + "Path(\"./source_files/transpile_remote.py\").unlink()\n", + "Path(\"./source_files/extract_tarfile.py\").unlink()\n", + "Path(\"./transpile_demo.tar\").unlink()\n", + "os.chdir(\"../\")\n", + "Path(\"./serverless-manage-resources-workspace/source_files\").rimdir()\n", + "Path(\"./serverless-manage-resources-workspace\").rimdir()" + ] + }, { "cell_type": "markdown", "id": "5b93dbdb-2060-468b-8496-ba98142a780b", @@ -353,5 +514,5 @@ "title": "Manage Qiskit Serverless compute and data resources" }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/scripts/config/notebook-testing.toml b/scripts/config/notebook-testing.toml index 90c268bf75..15e89767a5 100644 --- a/scripts/config/notebook-testing.toml +++ b/scripts/config/notebook-testing.toml @@ -1,49 +1,50 @@ # For notebooks to be tested "normally" (no mocking) notebooks_normal_test = [ - "docs/guides/construct-circuits.ipynb", + "docs/guides/build-noise-models.ipynb", "docs/guides/circuit-library.ipynb", - "docs/guides/visualize-circuits.ipynb", "docs/guides/classical-feedforward-and-control-flow.ipynb", - "docs/guides/operators-overview.ipynb", - "docs/guides/pulse.ipynb", - "docs/guides/save-circuits.ipynb", - "docs/guides/dynamic-circuits-considerations.ipynb", - "docs/guides/get-qpu-information.ipynb", - "docs/guides/save-jobs.ipynb", - "docs/guides/visualize-results.ipynb", "docs/guides/common-parameters.ipynb", + "docs/guides/construct-circuits.ipynb", "docs/guides/create-transpiler-plugin.ipynb", "docs/guides/custom-backend.ipynb", "docs/guides/custom-transpiler-pass.ipynb", "docs/guides/defaults-and-configuration-options.ipynb", + "docs/guides/dynamic-circuits-considerations.ipynb", "docs/guides/dynamical-decoupling-pass-manager.ipynb", + "docs/guides/error-mitigation-and-suppression-techniques.ipynb", + "docs/guides/error-mitigation-and-suppression-techniques.ipynb", + "docs/guides/get-qpu-information.ipynb", + "docs/guides/local-testing-mode.ipynb", + "docs/guides/operator-class.ipynb", + "docs/guides/operators-overview.ipynb", + "docs/guides/plot-quantum-states.ipynb", + "docs/guides/pulse.ipynb", "docs/guides/represent-quantum-computers.ipynb", + "docs/guides/save-circuits.ipynb", + "docs/guides/save-jobs.ipynb", + "docs/guides/serverless-first-program.ipynb", + "docs/guides/serverless-manage-resources.ipynb", + "docs/guides/serverless-run-first-workload.ipynb", "docs/guides/set-optimization.ipynb", + "docs/guides/simulate-stabilizer-circuits.ipynb", + "docs/guides/simulate-with-qiskit-aer.ipynb", "docs/guides/transpile-with-pass-managers.ipynb", "docs/guides/transpiler-plugins.ipynb", "docs/guides/transpiler-stages.ipynb", - "docs/guides/build-noise-models.ipynb", - "docs/guides/local-testing-mode.ipynb", - "docs/guides/plot-quantum-states.ipynb", - "docs/guides/simulate-with-qiskit-aer.ipynb", - "docs/guides/simulate-stabilizer-circuits.ipynb", - "docs/guides/operator-class.ipynb", - "docs/guides/error-mitigation-and-suppression-techniques.ipynb", - "docs/guides/error-mitigation-and-suppression-techniques.ipynb", - "docs/guides/serverless-first-program.ipynb", - "docs/guides/serverless-run-first-workload.ipynb", + "docs/guides/visualize-circuits.ipynb", + "docs/guides/visualize-results.ipynb", ] # Don't test the following notebooks (this section can include glob patterns) notebooks_exclude = [ "**/.ipynb_checkpoints/**", - "docs/guides/qedma-qesem.ipynb", - "docs/guides/q-ctrl-optimization-solver.ipynb", - "docs/guides/q-ctrl-performance-management.ipynb", "docs/guides/algorithmiq-tem.ipynb", "docs/guides/functions.ipynb", - "docs/guides/qunasys-quri-chemistry.ipynb", "docs/guides/ibm-circuit-function.ipynb", + "docs/guides/q-ctrl-optimization-solver.ipynb", + "docs/guides/q-ctrl-performance-management.ipynb", + "docs/guides/qedma-qesem.ipynb", + "docs/guides/qunasys-quri-chemistry.ipynb", ] # The following notebooks submit jobs that can be mocked with a simulator @@ -56,6 +57,5 @@ notebooks_that_submit_jobs = [ # memory on a reasonable device. notebooks_no_mock = [ "docs/guides/hello-world.ipynb", - "docs/guides/serverless-manage-resources.ipynb", "docs/guides/noise-learning.ipynb" ]