Open Neural Network Exchange (ONNX)
This notebook provides an example of serving a model built in PyTorch with ONNX Runtime, a cross-platform, high performance scoring engine for machine learning models.
The Open Neural Network Exchange (ONNX) format is supported by a number of deep learning frameworks, including PyTorch, CNTK and MXNet.
import os
from urllib.request import urlretrieve
import sys
import numpy as np
from PIL import Image
import onnx
from onnx import optimizer
from konduit.utils import default_python_path
This page documents two ways to create Konduit Serving configurations with the Python SDK:
Using Python to create a configuration, and
Writing the configuration as a YAML file, then serving it using the Python SDK.
These approaches are documented in separate tabs throughout this page. For example, the following code block shows the imports for each approach in separate tabs:
from konduit import PythonConfig, ServingConfig, InferenceConfiguration, \
PythonStep
from konduit.server import Server
from konduit.client import Client
Download file
For the purposes of this example, we use ONNX model files from Ultra-Light-Fast-Generic-Face-Detector-1MB by Linzaer, a lightweight facedetection model designed for edge computing devices.
dl_path = os.path.abspath("../data/facedetector/facedetector.onnx")
DOWNLOAD_URL = "https://raw.githubusercontent.com/Linzaer/Ultra-Light-Fast-Generic-Face-Detector-1MB/master/models/onnx/version-RFB-320.onnx"
if not os.path.isfile(dl_path):
urlretrieve(DOWNLOAD_URL, filename=dl_path)
The following content is based on the PyTorch tutorial Exporting a Model from PyTorch to ONNX and Running it using ONNX Runtime, with modifications.
We start by loading the model and running onnx.checker.check_model
to check whether the model has a valid schema.
# Load the ONNX model
model = onnx.load(dl_path)
# model is a onnx.ModelProto object
onnx.checker.check_model(model)
Optimize
When loading some models, ONNX may return warnings that the model can be further optimized by removing some unused nodes.
Use ONNX's optimizer to optimize your ONNX file. The code below is adapted from this GitHub comment.
Note that the API for optimizing models in ONNX Runtime is experimental, and may change.
onnx_model = onnx.load(dl_path)
passes = ["extract_constant_to_initializer", "eliminate_unused_initializer"]
optimized_model = optimizer.optimize(onnx_model, passes)
onnx.save(optimized_model, dl_path)
Python script with PyTorch and ONNX Runtime
Now that we have an optimized ONNX file, we can serve our model.
The following code:
transforms a PIL image into a 240 x 320 image,
casts it into a PyTorch Tensor,
adds an extra dimension with
unsqueeze
,casts the Tensor into a NumPy array, then
returns the model's output with ONNX Runtime.
python_code = """
from PIL import Image
import torchvision.transforms as transforms
import onnxruntime
import os
dl_path = os.path.abspath("../data/facedetector/facedetector.onnx")
image = Image.fromarray(image.astype('uint8'), 'RGB')
resize = transforms.Resize([240, 320])
img_y = resize(image)
to_tensor = transforms.ToTensor()
img_y = to_tensor(img_y)
img_y.unsqueeze_(0)
def to_numpy(tensor):
return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy()
ort_session = onnxruntime.InferenceSession(dl_path)
ort_inputs = {ort_session.get_inputs()[0].name: to_numpy(img_y)}
ort_outs = ort_session.run(None, ort_inputs)
_, boxes = ort_outs
"""
Configure the step
Defining a PythonConfig
PythonConfig
Here we use the
python_code
argument instead ofpython_code_path
, since the code is defined as a string.Define the inputs and outputs as dictionaries, where the keys represent objects in the server's Python environment, and the values represent data types (Python data structures), defined as strings. See https://serving.oss.konduit.ai/python for supported data types.
work_dir = os.path.abspath('.')
python_config = PythonConfig(
python_code=python_code,
python_inputs={"image": "NDARRAY"},
python_outputs={"boxes": "NDARRAY"},
python_path=default_python_path(work_dir)
)
Define a pipeline step with the PythonStep
class.
PythonStep
class.In the .step()
method, define a name for this step (input1
) and the respective configuration (python_config
).
onnx_step = (PythonStep()
.step(input_name="input1",
python_config=python_config))
Configure the server
port = np.random.randint(1000, 65535)
server = Server(
steps=onnx_step,
serving_config=ServingConfig(http_port=port)
)
Start the server
server.start()
Starting server.........
Server has started successfully.
Configure the client
Make sure to configure the client after starting the server, so that the Client object can inherit the Server's attributes.
Since the image is passed to the Server as a NumPy array, specify the input and output data format as NUMPY
.
client = Client(
input_data_format='NUMPY',
return_output_data_format='NUMPY',
output_data_format="RAW",
port=port
)
Inference
Load a sample image using PIL/Pillow and send the image to the server for prediction using the predict()
method of the Client
class.
im = Image.open("../data/facedetector/1.jpg")
im = np.array(im).astype("int")
output = client.predict(
{"input1": im}
)
print(output)
[[[ 0.00601701 0.00688479 0.02177745 0.03408115]
[-0.0018133 -0.00657785 0.03698186 0.05206966]
[-0.01035942 -0.01786287 0.04902049 0.06799769]
...
[ 0.7294515 0.6165271 1.0584102 1.1059598 ]
[ 0.65046376 0.48442802 1.141786 1.2248938 ]
[ 0.5633501 0.37209463 1.2047783 1.2747201 ]]]
Finally, we stop the server:
server.stop()
Last updated
Was this helpful?