Reference Classification Application
In order to build an application on the MLSoC, we will first write a python application that will serve as a reference, and then begin to build our GStreamer application. In this first section, we have the code and resources for the python based application.
Resources and setup
Please download and place this archive in your Palette docker container.
In your environment, uncompress the application, go into the uncompressed folder, make sure to install the requirements:
sima-user@docker-image-id$ tar xvf building_applications_mlsoc_1.4.0.tar.xz
building_applications_mlsoc_1.4.0/
building_applications_mlsoc_1.4.0/requirements.txt
building_applications_mlsoc_1.4.0/data/
building_applications_mlsoc_1.4.0/data/imagenet_labels.txt
building_applications_mlsoc_1.4.0/data/openimages_v7_images_and_labels.pkl
building_applications_mlsoc_1.4.0/data/golden_retriever_207.jpg
building_applications_mlsoc_1.4.0/README.md
building_applications_mlsoc_1.4.0/models/
building_applications_mlsoc_1.4.0/models/download_resnet50.py
building_applications_mlsoc_1.4.0/src/
building_applications_mlsoc_1.4.0/src/x86_reference_app/
building_applications_mlsoc_1.4.0/src/x86_reference_app/resnet50_reference_classification_app.py
building_applications_mlsoc_1.4.0/src/modelsdk_quantize_model/
building_applications_mlsoc_1.4.0/src/modelsdk_quantize_model/resnet50_quant.py
sima-user@docker-image-id$ cd building_applications_mlsoc_1.4.0/
sima-user@docker-image-id$ tree -L 3
.
├── data
│ ├── golden_retriever_207.jpg
│ ├── imagenet_labels.txt
│ └── openimages_v7_images_and_labels.pkl
├── models
│ └── download_resnet50.py
├── README.md
├── requirements.txt
└── src
├── modelsdk_quantize_model
│ └── resnet50_quant.py
└── x86_reference_app
└── resnet50_reference_classification_app.py
sima-user@docker-image-id$ pip3 install -r requirements.txt
Note
data
: Any data we will use in the reference application or the quantization scriptgolden_retriever_207.jpg
: A test image we will use in our scripts.imagenet_labels.txt
: The list of labels from ImageNet we will use to perform inference and retrieve what class was predicted.openimages_v7_images_and_labels.pkl
: The labels and images from Open Images that we will use to calibrate our model during quantization.
models
: Where we will store our modeldownload_resnet50.py
: Downloads a pre-trained ResNet50 model, add asoftmax
node at the end, and saves the model.The output of this script is the core model we will be using throughout the guide.
src
: Application and quantization scriptsmodelsdk_quantize_model/resnet50_quant.py
:Script used to quantize our
resnet50_model.onnx
model using SiMa.ai’s Palette ModelSDK APIs (output fromdownload_resnet50.py
).
x86_reference_app/resnet50_reference_classification_app.py
:Our sample python application that can run on any machine and uses
onnxruntime
to run theresnet50_model.onnx
along with all pre and post processing.his will be our reference application, and we will be developing this exact application on the MLSoC.
Python example for x86/Mac
Download and save the model
Throughout this guide, we will be using a ResNet50 model from TorchVision and we will add a softmax
to the output.
To download the model and add the softmax
operator, the output will be stored in the directory models/resnet50_model.onnx
:
sima-user@docker-image-id$ python models/download_resnet50.py
Downloading: "https://download.pytorch.org/models/resnet50-11ad3fa6.pth" to /root/.cache/torch/hub/checkpoints/resnet50-11ad3fa6.pth
100%|███████████████████████████████████████████████████████████████████████████████████████████| 97.8M/97.8M [00:00<00:00, 114MB/s]
============== Diagnostic Run torch.onnx.export version 2.0.1+cpu ==============
verbose: False, log level: Level.ERROR
======================= 0 NONE 0 NOTE 0 WARNING 0 ERROR ========================
Model exported successfully to /home/docker/sima-cli/building_applications_mlsoc_1.4.0/models/resnet50_export.onnx
Simplified model saved to /home/docker/sima-cli/building_applications_mlsoc_1.4.0/models/resnet50_model.onnx
console$ tree -L3
.
├── data
│ ├── golden_retriever_207.jpg
│ ├── imagenet_labels.txt
│ └── openimages_v7_images_and_labels.pkl
├── models
│ ├── download_resnet50.py
│ └── resnet50_model.onnx
├── README.md
├── requirements.txt
└── src
├── modelsdk_quantize_model
│ └── resnet50_quant.py
└── x86_reference_app
└── resnet50_reference_classification_app.py
Run the reference application
The following Python script demonstrates how to classify images using an ONNX ResNet50 model and onnxruntime
.
This script can be run on any x86/Mac machine with the necessary dependencies installed.
The reference application itself:
1#**************************************************************************
2#|| SiMa.ai CONFIDENTIAL ||
3#|| Unpublished Copyright (c) 2023-2024 SiMa.ai, All Rights Reserved. ||
4#**************************************************************************
5# NOTICE: All information contained herein is, and remains the property of
6# SiMa.ai. The intellectual and technical concepts contained herein are
7# proprietary to SiMa and may be covered by U.S. and Foreign Patents,
8# patents in process, and are protected by trade secret or copyright law.
9#
10# Dissemination of this information or reproduction of this material is
11# strictly forbidden unless prior written permission is obtained from
12# SiMa.ai. Access to the source code contained herein is hereby forbidden
13# to anyone except current SiMa.ai employees, managers or contractors who
14# have executed Confidentiality and Non-disclosure agreements explicitly
15# covering such access.
16#
17# The copyright notice above does not evidence any actual or intended
18# publication or disclosure of this source code, which includes information
19# that is confidential and/or proprietary, and is a trade secret, of SiMa.ai.
20#
21# ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, OR PUBLIC
22# DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT THE EXPRESS WRITTEN
23# CONSENT OF SiMa.ai IS STRICTLY PROHIBITED, AND IN VIOLATION OF APPLICABLE
24# LAWS AND INTERNATIONAL TREATIES. THE RECEIPT OR POSSESSION OF THIS SOURCE
25# CODE AND/OR RELATED INFORMATION DOES NOT CONVEY OR IMPLY ANY RIGHTS TO
26# REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, OR TO MANUFACTURE, USE, OR
27# SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART.
28#
29#**************************************************************************
30
31import cv2
32import shutil
33import argparse
34import numpy as np
35import onnxruntime as ort
36
37from pathlib import Path
38from typing import Union
39
40# Constants
41ROOT_PATH = Path(__file__).parent.resolve()
42INPUT_IMAGES_PATH = str(Path(ROOT_PATH, "../../data/"))
43IMAGENET_LABELS_PATH = str(Path(ROOT_PATH, "../../data/imagenet_labels.txt"))
44MODEL_PATH = str(Path(ROOT_PATH, "../../models/resnet50_model.onnx"))
45SAVE_OUTPUTS_FOR_DEBUG = True
46OUTPUT_DEBUG_PATH = ROOT_PATH/"debug"
47
48####################
49# Helper Functions #
50####################
51
52def recreate_directory(dir_path: Union[Path, str]):
53 # Ensure dir_path is a Path object if given a string
54 dir_path = Path(dir_path)
55
56 # Check if the directory exists
57 if dir_path.exists():
58 # Remove the directory and all its contents
59 shutil.rmtree(dir_path)
60 print(f"Deleted directory: {dir_path}")
61
62 # Recreate the directory
63 dir_path.mkdir(parents=True, exist_ok=True)
64 print(f"Recreated directory: {dir_path}")
65
66###################
67# Setup Functions #
68###################
69
70def setup(model_path: str, labels_path: str):
71 # Load labels and ONNX model
72 with open(labels_path, "r") as f:
73 labels = [line.strip() for line in f.readlines()]
74
75 # Load the ONNX model
76 session = ort.InferenceSession(model_path)
77 input_name = session.get_inputs()[0].name
78
79 return labels, session, input_name
80
81######################
82# Pipeline Functions #
83######################
84
85def read_image(image_path: str, dump_output: bool = False):
86 image = cv2.imread(str(image_path), cv2.IMREAD_UNCHANGED)
87 rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
88
89 image_name = Path(image_path).stem
90 if dump_output:
91 debug_image_name = image_name + "_rgb.bin"
92 rgb_image.tofile(str(OUTPUT_DEBUG_PATH/debug_image_name))
93
94 return rgb_image, image_name
95
96
97# Function to preprocess the image
98def preprocess_image(image: np.ndarray, input_shape: tuple = (224, 224), scale_factor: tuple = 255.0,
99 dump_output_image_name: str = None, dump_output: bool = False):
100 mean = [0.485, 0.456, 0.406]
101 stddv = [0.229, 0.224, 0.225]
102
103 resized_image = cv2.resize(image, input_shape)
104 image_data = resized_image / scale_factor
105 image_data = (image_data - mean) / stddv
106
107 if dump_output:
108 # Dump reference output before transposing because MLA uses NHWC format not NCHW
109 assert dump_output_image_name is not None
110
111 debug_image_path = OUTPUT_DEBUG_PATH/f"{dump_output_image_name}_preprocessed_rgb_nhwc_fp32.bin"
112 image_data.tofile(debug_image_path)
113
114 image_data = np.transpose(image_data.astype('float32'), (2, 0, 1)) # Change to (C, H, W)
115 image_data = np.expand_dims(image_data, axis=0) # Add batch dimension
116
117 return image_data
118
119# Function to post-process the output
120def postprocess_output(output: np.ndarray, labels: dict,
121 dump_output_image_name: str = None, dump_output: bool = False):
122 probabilities = output[0][0]
123 max_idx = np.argmax(probabilities)
124
125 if dump_output:
126 # Dump reference output before transposing because MLA uses NHWC format not NCHW
127 assert dump_output_image_name is not None
128
129 debug_image_path = OUTPUT_DEBUG_PATH/f"{dump_output_image_name}_inference_output_probabilities.bin"
130 probabilities.tofile(debug_image_path)
131
132 return labels[max_idx], probabilities[max_idx]
133
134def main(images_path: str, model_path: str, labels_path: str = IMAGENET_LABELS_PATH):
135 """ Runs an application pipeline composed of the following 5 stages:
136
137 Load image -> Preprocess image -> Run ResNet50 (inference) -> Postprocess outputs -> Display results
138 """
139
140 # Initialization #
141 labels, inference_session, input_name = setup(model_path=model_path, labels_path=labels_path)
142 image_paths = list(Path(images_path).glob("*.jpg"))
143
144 # 5 Stage application pipeline #
145 print(f"\nProcessing all images in: {images_path}\n")
146
147 for idx, image_path in enumerate(image_paths):
148 print(f"Processing image [{idx}] --> ", end="")
149
150 # Load image #
151 image, image_name = read_image(str(image_path), dump_output=SAVE_OUTPUTS_FOR_DEBUG)
152
153 # Preprocess #
154 input_data = preprocess_image(image, dump_output_image_name=image_name, dump_output=SAVE_OUTPUTS_FOR_DEBUG)
155
156 # Run inference #
157 output = inference_session.run(None, {input_name: input_data})
158
159 # Postprocess #
160 class_name, confidence = postprocess_output(output, labels, dump_output_image_name=image_name, dump_output=SAVE_OUTPUTS_FOR_DEBUG)
161
162 # Display results #
163 print(f"Class: {class_name} Confidence: {confidence * 100.0:.2f}%")
164
165
166if __name__ == "__main__":
167 parser = argparse.ArgumentParser(description="Classify an image using an ONNX ResNet50 model.")
168
169 parser.add_argument("-i", "--images_path", type=str, required=False, default=INPUT_IMAGES_PATH, help="Path to the image file.")
170 parser.add_argument("-m", "--model_path", type=str, required=False, default=MODEL_PATH, help="Path to the image file.")
171 args = parser.parse_args()
172
173 # Setup output directory
174 if SAVE_OUTPUTS_FOR_DEBUG:
175 recreate_directory(OUTPUT_DEBUG_PATH)
176
177 main(images_path=args.images_path, model_path=args.model_path)
Note
Notice how the 5 stages of our application pipeline are described in lines 150-163
# Load image #
image, image_name = read_image(str(image_path), dump_output=SAVE_OUTPUTS_FOR_DEBUG)
# Preprocess #
input_data = preprocess_image(image, dump_output_image_name=image_name, dump_output=SAVE_OUTPUTS_FOR_DEBUG)
# Run inference #
output = inference_session.run(None, {input_name: input_data})
# Postprocess #
class_name, confidence = postprocess_output(output, labels, dump_output_image_name=image_name, dump_output=SAVE_OUTPUTS_FOR_DEBUG)
# Display results #
print(f"Class: {class_name} Confidence: {confidence * 100.0:.2f}%")
Note
Notice how key functions have the parameter dump_output: bool
. This dumps binary files with the results of each function.
These outputs will become our reference outputs as we build our GStreamer pipeline to validate if we have the correct expected
values out of each GStreamer plugin.
To run the script:
sima-user@docker-image-id$ python src/x86_reference_app/resnet50_reference_classification_app.py
Recreated directory: /home/docker/sima-cli/building_applications_mlsoc_1.4.0/src/x86_reference_app/debug
Processing all images in: /home/docker/sima-cli/building_applications_mlsoc_1.4.0/src/x86_reference_app/../../data
Processing image [0] --> Class: 207: 'golden retriever', Confidence: 98.38%
The resulting directory structure should look like:
sima-user@docker-image-id$ tree -L 4
.
├── data
│ ├── golden_retriever_207.jpg
│ ├── imagenet_labels.txt
│ └── openimages_v7_images_and_labels.pkl
├── models
│ ├── download_resnet50.py
│ └── resnet50_model.onnx
├── README.md
├── requirements.txt
└── src
├── modelsdk_quantize_model
│ └── resnet50_quant.py
└── x86_reference_app
├── debug
│ ├── golden_retriever_207_inference_output_probabilities.bin
│ ├── golden_retriever_207_preprocessed_rgb_nhwc_fp32.bin
│ └── golden_retriever_207_rgb.bin
└── resnet50_reference_classification_app.py
Note
Notice how a newly created debug
directory appears that has intermediate dumps of data we will use later in this guide.
Run on the MLSoC CPU (Arm A65)
To run the same resnet50_reference_classification_app.py
application on the A65 of the MLSoC, simply:
Replicate the same directory structure in a directory on the MLSoC devkit
scp the application,
data
andmodels
directories to the board
ssh
into the boardpip3 install onnxruntime
if you have not already done soRun the application
Conclusion and next steps
In this section, we:
Developed a reference application that will serve as a guideline of what we need to develop in the MLSoC.
The reference application could be executed on a host machine or on the MSLoC.
The application also dumps each of the intermediate outputs which will be used during development to ensure we have the correct functional output out of our GStreamer plugins.
Next we will take our first step into MLSoC development: taking our trained model, and using the ModelSDK to compile and optimize it for running on the MLA.