PyTorch-Modelle auf verschiedenen Hardwarezielen mit ONNX Runtime inferieren

Als Entwickler, der ein PyTorch- oder ONNX-Modell bereitstellen und die Leistung und Hardwareflexibilität maximieren möchte, können Sie ONNX Runtime nutzen, um Ihr Modell optimal auf Ihrer Hardwareplattform auszuführen.

In diesem Tutorial lernen Sie

  1. wie Sie das PyTorch ResNet-50-Modell für die Bildklassifizierung verwenden
  2. in ONNX konvertieren und
  3. mit ONNX Runtime auf der Standard-CPU, NVIDIA CUDA (GPU) und Intel OpenVINO bereitstellen – unter Verwendung des gleichen Anwendungscodes zum Laden und Ausführen der Inferenz über verschiedene Hardwareplattformen hinweg.

ONNX wurde als Open-Source-ML-Modellformat von Microsoft, Meta, Amazon und anderen Technologieunternehmen entwickelt, um Machine-Learning-Modelle zu standardisieren und einfach auf verschiedenen Hardwaretypen bereitzustellen. ONNX Runtime wurde von Microsoft beigesteuert und wird von Microsoft gepflegt, um die Leistung von ONNX-Modellen über Frameworks wie PyTorch, Tensorflow und mehr zu optimieren. Wenn das ResNet-50-Modell mit dem ImageNet-Datensatz trainiert wird, wird es üblicherweise für die Bildklassifizierung verwendet.

Dieses Tutorial zeigt, wie ein ONNX-Modell auf CPU, GPU und Intel-Hardware mit OpenVINO und ONNX Runtime ausgeführt wird, unter Verwendung von Microsoft Azure Machine Learning.

Setup

Betriebssystem-Voraussetzungen

Ihre Umgebung sollte über installiertes curl verfügen.

Geräte-Voraussetzungen

Die onnxruntime-gpu-Bibliothek benötigt Zugriff auf einen NVIDIA CUDA-Beschleuniger auf Ihrem Gerät oder Compute-Cluster. Die Ausführung nur auf der CPU funktioniert jedoch für die CPU- und OpenVINO-CPU-Demos.

Inferenz-Voraussetzungen

Stellen Sie sicher, dass Sie ein Bild für die Inferenz haben. Für dieses Tutorial haben wir ein „cat.jpg“-Bild, das sich im selben Verzeichnis wie die Notebook-Dateien befindet.

Umgebungs-Voraussetzungen

Führen Sie in Azure Notebook Terminal oder einem AnaConda-Eingabeauffenster die folgenden Befehle aus, um Ihre 3 Umgebungen für CPU, GPU und/oder OpenVINO zu erstellen (Unterschiede sind fett gedruckt).

CPU

conda create -n cpu_env_demo python=3.8
conda activate cpu_env_demo
conda install -c anaconda ipykernel
conda install -c conda-forge ipywidgets
python -m ipykernel install --user --name=cpu_env_demo
jupyter notebook

GPU

conda create -n gpu_env_demo python=3.8
conda activate gpu_env_demo 
conda install -c anaconda ipykernel
conda install -c conda-forge ipywidgets
python -m ipykernel install --user --name=gpu_env_demo 
jupyter notebook

OpenVINO

conda create -n openvino_env_demo python=3.8
conda activate openvino_env_demo 
conda install -c anaconda ipykernel
conda install -c conda-forge ipywidgets
python -m ipykernel install --user --name=openvino_env_demo
python -m pip install --upgrade pip
pip install openvino

Bibliotheksanforderungen

Installieren Sie in der ersten Codezelle die erforderlichen Bibliotheken mit den folgenden Code-Snippets (Unterschiede sind fett gedruckt).

CPU + GPU

import sys

if sys.platform in ['linux', 'win32']: # Linux or Windows
    !{sys.executable} -m pip install torch==1.9.0+cu111 torchvision==0.10.0+cu111 torchaudio===0.9.0 -f https://download.pytorch.org/whl/torch_stable.html
else: # Mac
    print("PyTorch 1.9 MacOS Binaries do not support CUDA, install from source instead")

!{sys.executable} -m pip install onnxruntime-gpu onnx onnxconverter_common==1.8.1 pillow

OpenVINO

import sys

if sys.platform in ['linux', 'win32']: # Linux or Windows
    !{sys.executable} -m pip install torch==1.9.0+cu111 torchvision==0.10.0+cu111 torchaudio===0.9.0 -f https://download.pytorch.org/whl/torch_stable.html
else: # Mac
    print("PyTorch 1.9 MacOS Binaries do not support CUDA, install from source instead")

!{sys.executable} -m pip install onnxruntime-openvino onnx onnxconverter_common==1.8.1 pillow

import openvino.utils as utils
utils.add_openvino_libs_to_path()

ResNet-50 Demo

Umgebungs-Setup

Importieren Sie die notwendigen Bibliotheken, um Modelle zu laden und Inferenz durchzuführen.

from torchvision import models, datasets, transforms as T
import torch
from PIL import Image
import numpy as np

Vor-trainiertes ResNet-50-Modell laden und nach ONNX exportieren

Laden Sie ein vor-trainiertes ResNet-50-Modell von PyTorch herunter und exportieren Sie es in das ONNX-Format.

resnet50 = models.resnet50(pretrained=True)

# Download ImageNet labels
!curl -o imagenet_classes.txt https://raw.githubusercontent.com/pytorch/hub/master/imagenet_classes.txt

# Read the categories
with open("imagenet_classes.txt", "r") as f:
    categories = [s.strip() for s in f.readlines()]

# Export the model to ONNX
image_height = 224
image_width = 224
x = torch.randn(1, 3, image_height, image_width, requires_grad=True)
torch_out = resnet50(x)
torch.onnx.export(resnet50,                     # model being run
                  x,                            # model input (or a tuple for multiple inputs)
                  "resnet50.onnx",              # where to save the model (can be a file or file-like object)
                  export_params=True,           # store the trained parameter weights inside the model file
                  opset_version=12,             # the ONNX version to export the model to
                  do_constant_folding=True,     # whether to execute constant folding for optimization
                  input_names = ['input'],      # the model's input names
                  output_names = ['output'])    # the model's output names

Beispiel-Ausgabe

% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 10472 100 10472 0 0 50581 0 --:--:-- --:--:-- --:--:-- 50834

Vorverarbeitung für die Inferenz einrichten

Erstellen Sie die Vorverarbeitung für das Bild (z. B. cat.jpg), auf dem Sie das Modell für die Inferenz verwenden möchten.

# Pre-processing for ResNet-50 Inferencing, from https://pytorch.org/hub/pytorch_vision_resnet/
resnet50.eval()  
filename = 'cat.jpg' # change to your filename

input_image = Image.open(filename)
preprocess = T.Compose([
    T.Resize(256),
    T.CenterCrop(224),
    T.ToTensor(),
    T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
input_tensor = preprocess(input_image)
input_batch = input_tensor.unsqueeze(0) # create a mini-batch as expected by the model

# move the input and model to GPU for speed if available
print("GPU Availability: ", torch.cuda.is_available())
if torch.cuda.is_available():
    input_batch = input_batch.to('cuda')
    resnet50.to('cuda')

Beispiel-Ausgabe

GPU Availability: False

ResNet-50 ONNX-Modell mit ONNX Runtime inferieren

Inferieren Sie das Modell mit ONNX Runtime, indem Sie den entsprechenden Execution Provider für die Umgebung auswählen. Wenn Ihre Umgebung CPU verwendet, kommentieren Sie CPUExecutionProvider aus; wenn die Umgebung NVIDIA CUDA verwendet, kommentieren Sie CUDAExecutionProvider aus; und wenn die Umgebung OpenVINOExecutionProvider verwendet, kommentieren Sie OpenVINOExecutionProvider aus – kommentieren Sie die anderen onnxruntime.InferenceSession-Codezeilen aus.

# Inference with ONNX Runtime
import onnxruntime
from onnx import numpy_helper
import time

session_fp32 = onnxruntime.InferenceSession("resnet50.onnx", providers=['CPUExecutionProvider'])
# session_fp32 = onnxruntime.InferenceSession("resnet50.onnx", providers=['CUDAExecutionProvider'])
# session_fp32 = onnxruntime.InferenceSession("resnet50.onnx", providers=['OpenVINOExecutionProvider'])

def softmax(x):
    """Compute softmax values for each sets of scores in x."""
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum()

latency = []
def run_sample(session, image_file, categories, inputs):
    start = time.time()
    input_arr = inputs.cpu().detach().numpy()
    ort_outputs = session.run([], {'input':input_arr})[0]
    latency.append(time.time() - start)
    output = ort_outputs.flatten()
    output = softmax(output) # this is optional
    top5_catid = np.argsort(-output)[:5]
    for catid in top5_catid:
        print(categories[catid], output[catid])
    return ort_outputs

ort_output = run_sample(session_fp32, 'cat.jpg', categories, input_batch)
print("ONNX Runtime CPU/GPU/OpenVINO Inference time = {} ms".format(format(sum(latency) * 1000 / len(latency), '.2f')))

Beispiel-Ausgabe

Egyptian cat 0.78605634
tabby 0.117310025
tiger cat 0.020089425
Siamese cat 0.011728076
plastic bag 0.0052174763
ONNX Runtime CPU Inference time = 32.34 ms

Vergleich mit PyTorch

Verwenden Sie PyTorch, um die Genauigkeit und Latenz von ONNX Runtime (CPU und GPU) zu benchmarken.

# Inference with OpenVINO
from openvino.runtime import Core

ie = Core()
onnx_model_path = "./resnet50.onnx"
model_onnx = ie.read_model(model=onnx_model_path)
compiled_model_onnx = ie.compile_model(model=model_onnx, device_name="CPU")

# inference
output_layer = next(iter(compiled_model_onnx.outputs))

latency = []
input_arr = input_batch.detach().numpy()
inputs = {'input':input_arr}
start = time.time()
request = compiled_model_onnx.create_infer_request()
output = request.infer(inputs=inputs)

outputs = request.get_output_tensor(output_layer.index).data
latency.append(time.time() - start)

print("OpenVINO CPU Inference time = {} ms".format(format(sum(latency) * 1000 / len(latency), '.2f')))

print("***** Verifying correctness *****")
for i in range(2):
    print('OpenVINO and ONNX Runtime output {} are close:'.format(i), np.allclose(ort_output, outputs, rtol=1e-05, atol=1e-04))

Beispiel-Ausgabe

Egyptian cat 0.7820879
tabby 0.113261245
tiger cat 0.020114701
Siamese cat 0.012514038
plastic bag 0.0056432663
OpenVINO CPU Inference time = 31.83 ms
***** Verifying correctness *****
PyTorch and ONNX Runtime output 0 are close: True
PyTorch and ONNX Runtime output 1 are close: True

Vergleich mit OpenVINO

Verwenden Sie OpenVINO, um die Genauigkeit und Latenz von ONNX Runtime (OpenVINO) zu benchmarken.

# Inference with OpenVINO
from openvino.runtime import Core

ie = Core()
onnx_model_path = "./resnet50.onnx"
model_onnx = ie.read_model(model=onnx_model_path)
compiled_model_onnx = ie.compile_model(model=model_onnx, device_name="CPU")

# inference
output_layer = next(iter(compiled_model_onnx.outputs))

latency = []
input_arr = input_batch.detach().numpy()
inputs = {'input':input_arr}
start = time.time()
request = compiled_model_onnx.create_infer_request()
output = request.infer(inputs=inputs)

outputs = request.get_output_tensor(output_layer.index).data
latency.append(time.time() - start)

print("OpenVINO CPU Inference time = {} ms".format(format(sum(latency) * 1000 / len(latency), '.2f')))

print("***** Verifying correctness *****")
for i in range(2):
    print('OpenVINO and ONNX Runtime output {} are close:'.format(i), np.allclose(ort_output, outputs, rtol=1e-05, atol=1e-04))

Beispiel-Ausgabe

Egyptian cat 0.7820879
tabby 0.113261245
tiger cat 0.020114701
Siamese cat 0.012514038
plastic bag 0.0056432663
OpenVINO CPU Inference time = 31.83 ms
***** Verifying correctness *****
PyTorch and ONNX Runtime output 0 are close: True
PyTorch and ONNX Runtime output 1 are close: True

Fazit

Wir haben gezeigt, dass ONNX Runtime eine effektive Möglichkeit ist, Ihr PyTorch- oder ONNX-Modell auf CPU, NVIDIA CUDA (GPU) und Intel OpenVINO (Mobil) auszuführen. ONNX Runtime ermöglicht die Bereitstellung auf mehr Hardwaretypen, die unter Execution Providers zu finden sind. Wir würden uns freuen, Ihr Feedback zu erhalten, indem Sie an unserem ONNX Runtime Github-Repo teilnehmen.

Video-Demonstration

Sehen Sie sich das Video hier an, um weitere Erklärungen zur Bereitstellung von ResNet-50 und flexiblen Inferenz mit der Schritt-für-Schritt-Anleitung zu erhalten.