Skip to content

Commit

Permalink
Improved documentation for custom images (#1410)
Browse files Browse the repository at this point in the history
* Improved documentation for custom images

* Add new tutorial to the index
  • Loading branch information
Tansito authored Jul 16, 2024
1 parent af7f60e commit 6c3487a
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 187 deletions.
224 changes: 37 additions & 187 deletions docs/deployment/deploying_custom_image_function.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,29 @@
Building custom image for function
==================================

In this tutorial we will describe how you can build your custom docker image and execute it as
a Qiskit Function.

In this tutorial we will describe how to build custom docker image for function.

In this tutorial we will be following 3 steps to deploy our function with custom docker image:
You will be following 3 steps to deploy it:

* implement function template
* define dockerfile, build it and push to registry
* define dockerfile, build it
* upload

All of our custom image files will be located in a folder `custom_function`, which will 2 files: `Dockerfile` and `runner.py`.
You can find the example in `docs/deployment/custom_function`, which will have 2 files: `Sample-Dockerfile` and `runner.py`.

.. code-block::
:caption: Custom image folder source files
:caption: Custom image folder source files for the example
/custom_function
/runner.py
/Dockerfile
/Sample-Dockerfile
First we will implement function entrypoint by following template. All functions with custom docker images must follow same template structure.
First, you will implement your function entrypoint following the template. All functions with custom docker images must follow same template structure.

We need to create class `Runner` and implement `run` method that will be called during invocation of the function and results of the run method will be returned as result of the function.
We need to create class `Runner` and implement `run` method that will be called during invocation of the function and the results of the run method will be returned as result of the function.

Let's create `runner.py` file with following content
Let's create `runner.py` file with the following content:

.. code-block::
:caption: `runner.py` - Runner class implementation. This is an entrypoint to you custom image function.
Expand All @@ -45,7 +45,7 @@ As a next step let's define and build our custom docker image.

Dockerfile will be extending base serverless node image and adding required packages and structure to it.

In our simple case it will look something like this
In our simple case it will look something like this:

.. code-block::
:caption: Dockerfile for custom image function.
Expand All @@ -54,7 +54,7 @@ In our simple case it will look something like this
# install all necessary dependencies for your custom image
# copy our function implementation in `/runner.py` of the docker image
# copy our function implementation in `/runner/runner.py` of the docker image
USER 0
WORKDIR /runner
Expand All @@ -63,17 +63,35 @@ In our simple case it will look something like this
USER $RAY_UID
and then we need to build it
and after that we need to build it:

.. code-block::
:caption: Build and push image.
:caption: Build image
docker build -t icr.io/quantum-public/my-custom-function-image:1.0.0 ./custom_function
docker push icr.io/quantum-public/my-custom-function-image:1.0.0
docker build -t local-provider-function -f Sample-Dockerfile .
We got to our final step of function development - uploading to serverless.

Let define `QiskitFunction` with image we just build, give it a name and upload it.
For a local development you can modify `docker-compose.yaml` ray image with the image that it was generated in the previous step:

.. code-block::
:caption: Modify docker compose definition
services:
ray-head:
container_name: ray-head
image: local-provider-function:latest
Run it:

.. code-block::
:caption: Run docker compose
docker-compose up
Once time the local environment is running, it only remains to run the code! For that you just need to define `QiskitFunction`

with the image that you just built, give it a name and upload it:

.. code-block::
:caption: Uploading and using function with custom image.
Expand All @@ -87,9 +105,9 @@ Let define `QiskitFunction` with image we just build, give it a name and upload
)
serverless
function_with_custom_image = QiskitFunction(
function = QiskitFunction(
title="custom-image-function",
image="icr.io/quantum-public/my-custom-function-image:1.0.0"
image="local-provider-function:latest",
provider="mockprovider"
)
function_with_custom_image
Expand All @@ -104,171 +122,3 @@ Let define `QiskitFunction` with image we just build, give it a name and upload
job
job.result()
=============================
Example custom image function
=============================

Function example (runner.py)

.. code-block::
:caption: runner.py
from qiskit_aer import AerSimulator
from qiskit_serverless import get_arguments, save_result
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import Session
def custom_function(arguments):
service = arguments.get("service")
circuit = arguments.get("circuit")
observable = arguments.get("observable")
if service:
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
session = Session(backend=backend)
else:
backend = AerSimulator()
target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
target_circuit = pm.run(circuit)
target_observable = observable.apply_layout(target_circuit.layout)
from qiskit_ibm_runtime import EstimatorV2 as Estimator
if service:
estimator = Estimator(session=session)
else:
estimator = Estimator(backend=backend)
job = estimator.run([(target_circuit, target_observable)])
if service:
session.close()
return job.result()[0].data.evs
class Runner:
def run(self, arguments: dict) -> dict:
return custom_function(arguments)
Dockerfile

.. code-block::
:caption: Dockerfile
FROM icr.io/quantum-public/qiskit-serverless/ray-node:0.13.0-py310
# install all necessary dependencies for your custom image
# copy our function implementation in `/runner.py` of the docker image
USER 0
RUN pip install qiskit_aer
WORKDIR /runner
COPY ./runner.py /runner
WORKDIR /
USER $RAY_UID
Build container image

.. code-block::
:caption: Docker build
Docker build -t function .
Docker image tag function:latest "<image retistory/image name:image tag>"
Docker image push "<image retistory/image name:image tag>"
The build container image need to be tagged and uploaded to the image registory that can be accessible from the gateway

Upload and register function

Set GATEWAY_TOKEN, GATEWAY_HOST, PROVIDER_ID and YOUR_TOKEN (YOUR_TOKEN is necessary when use_session=True only) environment variables.

.. code-block::
:caption: upload.py
import os
from qiskit_serverless import QiskitFunction, ServerlessClient
serverless = ServerlessClient(
token=os.environ.get("GATEWAY_TOKEN", "awesome_token"),
host=os.environ.get("GATEWAY_HOST", "http://localhost:8000"),
)
help = """
title: custom-image-function
description: sample function implemented in a custom image
arguments:
service: service created with the accunt information
circuit: circuit
observable: observabl
"""
function_with_custom_image = QiskitFunction(
title="custom-image-function",
image="<image retistory/image name:image tag>",
provider=os.environ.get("PROVIDER_ID", "mockprovider"),
description=help
)
serverless.upload(function_with_custom_image)
For the User

List all available functions

.. code-block::
:caption: list.py
import os
from qiskit_serverless import ServerlessClient
serverless = ServerlessClient(
token=os.environ.get("GATEWAY_TOKEN", "awesome_token"),
host=os.environ.get("GATEWAY_HOST", "http://localhost:8000"),
)
my_functions = serverless.list()
for function in my_functions:
print("Name: " + function.title)
print(function.description)
print()
Execute Function

.. code-block::
:caption: usage.py
import os
from qiskit_serverless import ServerlessClient
from qiskit import QuantumCircuit
from qiskit.circuit.random import random_circuit
from qiskit.quantum_info import SparsePauliOp
from qiskit_ibm_runtime import QiskitRuntimeService
# set this True for the real Quantum system use
use_service=False
service = None
if use_service:
service = QiskitRuntimeService(
token=os.environ.get("YOUR_TOKEN", ""),
channel='ibm_quantum',
instance='ibm-q/open/main',
verify=False,
)
circuit = random_circuit(2, 2, seed=1234)
observable = SparsePauliOp("IY")
serverless = ServerlessClient(
token=os.environ.get("GATEWAY_TOKEN", "awesome_token"),
host=os.environ.get("GATEWAY_HOST", "http://localhost:8000"),
)
my_function = serverless.get("custom-image-function")
job = my_function.run(service=service, circuit=circuit, observable=observable)
print(job.result())
print(job.logs())
Loading

0 comments on commit 6c3487a

Please sign in to comment.