diff --git a/versal_2ve/examples/README.md b/versal_2ve/examples/README.md
index ae04a2e..9b5fd0b 100755
--- a/versal_2ve/examples/README.md
+++ b/versal_2ve/examples/README.md
@@ -19,7 +19,6 @@ examples/
├── tutorials/ # Guided tutorials (see tutorials/)
│ ├── resnet18_bf16/ # ResNet-18 BF16 flow
│ ├── resnet50_quark/ # ResNet50 INT8 with AMD Quark
-│ ├── resnet50Cpp/ # ResNet50 FP32 (BF16); Python sources; run with C++ (ONNX-RT)
│ ├── yolov8m/ # YOLOv8m detection: Quark VINT8, compile, ORT on target
│ └── README.md
├── python_examples/ # Python ORT + VitisAI EP on the embedded target (see python_examples/)
@@ -58,7 +57,6 @@ The tables below are the reference overview:
|---------|----------|------|---------------|---------------|-------|--------------|
| [`resnet18_bf16`](tutorials/resnet18_bf16/) | Python | ResNet-18: export ONNX → Vitis AI compile → deploy; `runmodel.py` compares CPU vs NPU (e.g. RMSE) | Yes (`compile.py` in Docker) | Yes (`runmodel.py` on board) | BF16 (compiler from FP32 ONNX) | ImageNet-style validation; ONNX under `models/` |
| [`resnet50_quark`](tutorials/resnet50_quark/) | Python | ResNet50: Quark INT8 quant → compile → accuracy on CPU/NPU → on-target inference | Yes (`compile.py` in Docker) | Yes (`runmodel.py`; `runmodel_pre_cpu.py` for host checks) | INT8 (AMD Quark calibration) | ImageNet val / calibration JPEGs; ONNX under `models/` |
-| [`resnet50Cpp`](tutorials/resnet50Cpp/) | Python and C++ | ResNet50 FP32 (BF16); Python sources; run with C++ (ONNX-RT) | Yes (`compile.py` in Docker) | Yes (`runmodel.cpp` on board) | FP32 (BF16) | ImageNet val / calibration JPEGs; ONNX under `models/` |
| [`yolov8m`](tutorials/yolov8m/) | Python | YOLOv8m: Quark VINT8 (skip-nodes), compile, latency tuning, ORT EP on board | Yes (`compile.py` in Docker) | Yes (`run_inference.py` on board) | INT8 VINT8 + BF16 tail (per tutorial) | Calibration / val images; COCO-style labels |
### Python samples (`python_examples`)
diff --git a/versal_2ve/examples/tutorials/README.md b/versal_2ve/examples/tutorials/README.md
index b9021b2..7d4e154 100755
--- a/versal_2ve/examples/tutorials/README.md
+++ b/versal_2ve/examples/tutorials/README.md
@@ -20,16 +20,14 @@ tutorials/
├── README.md # This file
├── resnet18_bf16/ # ResNet-18 end-to-end (BF16); Python sources + README
├── resnet50_quark/ # ResNet50 INT8 with AMD Quark; Python sources + README
-├── resnet50Cpp/ # ResNet50 FP32 (BF16); Python sources (compile); run with C++ (ONNX-RT)
└── yolov8m/ # YOLOv8m object detection (Quark VINT8 → compile → ORT); sources + README
```
## Tutorials (summary)
+| Tutorial | Role | Quant | Main scripts |
+|----------|------|-------|----------------|
+| [**resnet18_bf16**](resnet18_bf16/) | Export ONNX, compile with Vitis AI, deploy and run ORT on the board; optional CPU vs NPU comparison | BF16 via compiler | `export_to_onnx.py`, `compile.py`, `runmodel.py` |
+| [**resnet50_quark**](resnet50_quark/) | Download ONNX, Quark INT8 quantization, compile, evaluate accuracy, on-target inference | INT8 (Quark) | `quantize.py`, `compile.py`, `evaluate.py`, `runmodel.py`, `runmodel_pre_cpu.py` |
+| [**yolov8m**](yolov8m/) | YOLOv8m detection: export, Quark VINT8 (with skip-nodes), compile, NPU timing / config tuning, on-target ORT inference | INT8 VINT8 (Quark); compiler BF16 tail per tutorial | `models/export_to_onnx.py`, `quantize.py`, `compile.py`, `evaluate.py`, `run_inference.py` |
-| Tutorial | description | Datatype |
-|----------|-------------|----------|
-| [**resnet18_bf16**](resnet18_bf16/) | Export ONNX, compile with Vitis AI, deploy and run ORT on the board; optional CPU vs NPU comparison | BF16 via compiler |
-| [**resnet50_quark**](resnet50_quark/) | Download ONNX, Quark INT8 quantization, compile, evaluate accuracy, on-target inference | INT8 (Quark) |
-| [**resnet50Cpp**](resnet50Cpp/) | Download ONNX, compile model, compile C++ ONNX-RT run program, on-target inference with C++ based executable | BF16 via compiler |
-| [**yolov8m**](yolov8m/) | YOLOv8m detection: export, Quark VINT8 (with skip-nodes), compile, NPU timing / config tuning, on-target ORT inference | INT8 VINT8 (Quark); compiler BF16 tail per tutorial |
diff --git a/versal_2ve/examples/tutorials/resnet18_bf16/README.md b/versal_2ve/examples/tutorials/resnet18_bf16/README.md
index 3cebcc4..c69181c 100644
--- a/versal_2ve/examples/tutorials/resnet18_bf16/README.md
+++ b/versal_2ve/examples/tutorials/resnet18_bf16/README.md
@@ -1,12 +1,10 @@
-  Vitis AI Development
+ |  Getting Started with Vitis AI: ResNet-18 End-to-End Flow
|
-# Getting Started with Vitis AI: ResNet-18 End-to-End Flow
-
## Introduction
The Vitis AI toolchain supports compiling and deploying AI models in the ONNX format for efficient execution on Versal AI Edge Series Gen 2 devices. By using the Vitis AI Execution Provider (EP) within ONNX Runtime, developers can seamlessly run ONNX models and leverage hardware acceleration provided by the NPU.
@@ -17,7 +15,7 @@ This tutorial shows how to compile an ONNX model with the Vitis AI flow and depl
To build the example and deploy it on board, the following software and hardware are required:
-* Latest Vitis AI Docker for Versal AI Edge Series Gen 2:
+* Vitis AI 6.2 Docker for Versal AI Edge Series Gen 2:
* Instructions for installation and startup are in the Vitis AI User Guide for Versal AI Edge Series Gen 2.
* VEK385 evaluation kit:
* Setup instructions are available in the Vitis AI User Guide for Versal AI Edge Series Gen 2.
@@ -35,15 +33,15 @@ Before starting Docker, get the tutorial repository and adjust the access permis
chmod -R a+w
```
-Load the docker image:
+Refer to Vitis AI User Guide for Versal AI Edge Series Gen 2 to load and start docker:
```
-docker load -i .tgz
+docker run -it --network host \
+ -v /path/to/your/license:/usr/licenses \
+ -v /:/ \
+ --rm : "bash"
```
-Load the latest docker image and launch it as explained in the Vitis AI User Guide for Versal AI Edge Series Gen 2.
-
-
## Vitis AI Compilation & Deployment Flow
1. Inside the docker, change directory to the tutorial folder, install python packages required by the example, and export the ResNet-18 ONNX model:
@@ -88,10 +86,10 @@ To get more details about compilation results you can display the content of the
```
--------- Final Summary of VAIML Pass ----------
OS: Linux X64
-VAIP commit: 744227ab2a0fddec1eccdfe04ca222afd339f53f
+VAIP commit: ......
Model: ....../models/resnet18.a1_in1k.onnx
Model signature: 41d764d4ef1d716a260bc7b2b4e07ff1
-Device: ve2-xc2ve3858
+Device: ve2
Model data type: float32
Device data type: bfloat16
Number of operators in the model: 49
@@ -110,17 +108,13 @@ Subgraph vaiml_par_0 stats:
Type: npu
Operators: 49 (100.000%)
GOPs : 3.644 (100.000%) OPs: 3,643,881,552
+ fp32 ops %: 99.731
```
-3. Refer to Vitis AI User Guide for Versal AI Edge Series Gen 2, boot up the AIE-ML_v2 board, and run following commands in the board to setup environment:
+3. Refer to Vitis AI User Guide for Versal AI Edge Series Gen 2, boot up the AIE-ML_v2 board, and setup environment:
```
-sudo su # To avoid permission issues while creating the hw context
-echo 1 > /sys/module/rcupdate/parameters/rcu_cpu_stall_suppress
-export XRT_AIARM=true
-export LD_LIBRARY_PATH=/usr/lib/python3.12/site-packages/voe/lib/:/usr/lib/python3.12/site-packages/flexmlrt/lib/
-export XLNX_ENABLE_CACHE=0
-export XRT_ELF_FLOW=1
+export LD_LIBRARY_PATH=/usr/lib/python3.12/site-packages/voe/lib:/usr/lib/python3.12/site-packages/flexmlrt/lib:/usr/lib/python3.12/site-packages/onnxruntime/capi
```
4. Run the inference on the board. The working directory can be mounted on the board or copied to the board by scp:
@@ -139,10 +133,10 @@ The script runs four inferences of the model and displays messages similar to th
```
Running 4 inferences, comparing CPU and NPU outputs
-Iteration 1: Max absolute difference = 0.198444, Root mean squared error = 0.081654
-Iteration 2: Max absolute difference = 0.154286, Root mean squared error = 0.068371
-Iteration 3: Max absolute difference = 0.210051, Root mean squared error = 0.081457
-Iteration 4: Max absolute difference = 0.196577, Root mean squared error = 0.063051
+Iteration 1: Max absolute difference = 0.228190, Root mean squared error = 0.085001
+Iteration 2: Max absolute difference = 0.231800, Root mean squared error = 0.086444
+Iteration 3: Max absolute difference = 0.248591, Root mean squared error = 0.092887
+Iteration 4: Max absolute difference = 0.177171, Root mean squared error = 0.069008
Inference Done!
```
@@ -156,20 +150,22 @@ python runmodel.py
The output includes the number of operators offloaded to the NPU and the number of NPU-executed subgraphs:
```
-I20250529 19:23:43.187124 1265 stat.cpp:193] [Vitis AI EP] No. of Operators :
-I20250529 19:23:43.187179 1265 stat.cpp:204] VAIML 49
-I20250529 19:23:43.187194 1265 stat.cpp:213]
-I20250529 19:23:43.187206 1265 stat.cpp:218] [Vitis AI EP] No. of Subgraphs :
-I20250529 19:23:43.187219 1265 stat.cpp:226] NPU 1
-I20250529 19:23:43.187227 1265 stat.cpp:229] Actually running on NPU 1
-I20250529 19:23:43.188418 1265 vitisai_compile_model.cpp:1477] AVG CPU Usage 95.4545%
-I20250529 19:23:43.188459 1265 vitisai_compile_model.cpp:1478] Peak Working Set size 213.195 MB
-[2025-05-29 19:23:43.261] [console] [info] [FLEXMLRT] FlexMLClient.cpp:1269 FlexMLRT Git Hash: 512d4e65
+I20260608 01:57:50.007778 1212 stat.cpp:198] [Vitis AI EP] No. of Operators :
+I20260608 01:57:50.007839 1212 stat.cpp:198] VAIML 49
+I20260608 01:57:50.007958 1212 stat.cpp:198]
+I20260608 01:57:50.007978 1212 stat.cpp:198] [Vitis AI EP] No. of Subgraphs :
+I20260608 01:57:50.007992 1212 stat.cpp:198] NPU 1
+I20260608 01:57:50.008001 1212 stat.cpp:198] Actually running on NPU 1
+......
Running 4 inferences, comparing CPU and NPU outputs
-Iteration 1: Max absolute difference = 0.193388, Root mean squared error = 0.083394
-Iteration 2: Max absolute difference = 0.241203, Root mean squared error = 0.090799
-Iteration 3: Max absolute difference = 0.190464, Root mean squared error = 0.080506
-Iteration 4: Max absolute difference = 0.217875, Root mean squared error = 0.087615
+......
+Iteration 1: Max absolute difference = 0.211893, Root mean squared error = 0.075283
+......
+Iteration 2: Max absolute difference = 0.220449, Root mean squared error = 0.082772
+......
+Iteration 3: Max absolute difference = 0.169577, Root mean squared error = 0.055290
+......
+Iteration 4: Max absolute difference = 0.223119, Root mean squared error = 0.077367
Inference Done!
```
@@ -186,128 +182,6 @@ And then run the inference. The output contains information as follows:
[xrt_xdna] DEBUG: Partition Created with start_col 0 num_columns 4 partition_id 1024
```
-## Vitis AI Flow Essential
-
-This section covers some essential concepts in Vitis AI model compilation and inference. By learning the concepts and example codes, the flow can be extended to other ONNX models.
-
-1. Onnx model is used as input to the model compilation, which tries to accelerate the operators in NPU. So, prepare the ONNX model in ML frameworks.
-
-2. Models are compiled for the NPU by creating an ONNX inference session using the Vitis AI Execution Provider (VAI EP). The example python code can be found in `compile.py`.
-
-```
-import onnxruntime
-
-provider_options_dict = {
- "config_file": 'vitisai_config.json',
- "cache_dir": 'my_cache_dir',
- "cache_key": 'resnet18.a1_in1k',
- "log_level": 'info',
- "target": 'VAIML'
-}
-
-print(f"Creating ORT inference session for model models/resnet18.a1_in1k.onnx")
-session = onnxruntime.InferenceSession(
- 'models/resnet18.a1_in1k.onnx',
- providers=["VitisAIExecutionProvider"],
- provider_options=[provider_options_dict]
-)
-```
-
-The example configuration file `vitisai_config.json` contains options for Vitis AI compiler:
-
-```
-{
- "passes": [
- {
- "name": "init",
- "plugin": "vaip-pass_init"
- },
- {
- "name": "vaiml_partition",
- "plugin": "vaip-pass_vaiml_partition",
- "vaiml_config":
- {
- "device": "ve2-xc2ve3858",
- "optimize_level": 2,
- "logging_level": "info",
- "keep_outputs": true,
- "threshold_gops_percent": 20
- }
- }
- ],
- "target": "VAIML",
- "targets": [
- {
- "name": "VAIML",
- "pass": [
- "init",
- "vaiml_partition"
- ]
- }
- ]
-}
-```
-
-The value `ve2-xc2ve3858` for the `device` option selects the VEK385 part on Versal AI Edge Series Gen 2 (AIE-ML_v2) for Vitis AI 6.2 compilation.
-
-3. To execute the compiled model on hardware, transfer the compiled model artifacts and the original ONNX model file to the target board. The compiled ONNX graph is automatically partitioned into multiple subgraphs by the VitisAI Execution Provider (EP). The subgraph(s) containing operators supported by the NPU are executed on the NPU. The remaining subgraph(s) are executed on the CPU. This graph partitioning and deployment technique across CPU and NPU is fully automated by the VAI EP and is totally transparent to the end-user.
-
-Model execution is performed using a Python script that establishes an ONNX Runtime (ORT) inference session. This session is initialized with the target ONNX model and configured to utilize the Vitis AI Execution Provider (EP). Upon execution, the ORT session leverages the Vitis AI EP, which utilizes the compiled model binaries in the specified directory and deploys the ONNX subgraph(s) on the NPU and the CPU.
-
-The example python code for deploying on the hardware can be found in `runmodel.py`. It creates `InferenceSession` for CPU and NPU and runs inferences. And then compute the RMSE (Root Mean Square Error) between the CPU and NPU results:
-
-```
-import numpy as np
-import onnxruntime as ort
-
-provider_options_dict = {
- "config_file": 'vitisai_config.json',
- "cache_dir": 'my_cache_dir',
- "cache_key": 'resnet18.a1_in1k',
- "log_level": 'info',
- "target": 'VAIML',
-}
-
-print(f"Creating ORT inference session for model models/resnet18.a1_in1k.onnx")
-
-onnx_model="models/resnet18.a1_in1k.onnx"
-# CPU session to compute reference values
-cpu_session = ort.InferenceSession(
- onnx_model,
-)
-# NPU session
-npu_session = ort.InferenceSession(
- onnx_model,
- providers=["VitisAIExecutionProvider"],
- provider_options=[provider_options_dict]
-)
-
-num_iter = 4
-print(f"Running {num_iter} inferences, comparing CPU and NPU outputs")
-for i in range(num_iter):
- # Generate random data
- input_data = {}
- for input in npu_session.get_inputs():
- fixed_shape = [1 if isinstance(dim, str) else dim for dim in input.shape]
- input_data[input.name] = np.random.rand(*fixed_shape).astype(np.float32)
-
- # Compute CPU results (reference values)
- cpu_outputs = cpu_session.run(None, input_data)
- # Compute NPU results
- try:
- npu_outputs = npu_session.run(None, input_data)
- except Exception as e:
- print(f"Failed to run on NPU: {e}")
- sys.exit(1)
-
- # Compare CPU and NPU results
- max_diff = np.max(np.abs(cpu_outputs[0] - npu_outputs[0]))
- rmse = np.sqrt(np.mean((cpu_outputs[0] - npu_outputs[0]) ** 2))
- print(f'Iteration {i+1:3d}: Max absolute difference = {max_diff:.6f}, Root mean squared error = {rmse:.6f}')
-
-print("Inference Done!")
-```
-
## Summary
By completing this tutorial, you learned:
diff --git a/versal_2ve/examples/tutorials/resnet18_bf16/vitisai_config.json b/versal_2ve/examples/tutorials/resnet18_bf16/vitisai_config.json
index 55c494c..98f32cf 100644
--- a/versal_2ve/examples/tutorials/resnet18_bf16/vitisai_config.json
+++ b/versal_2ve/examples/tutorials/resnet18_bf16/vitisai_config.json
@@ -11,9 +11,11 @@
{
"device": "ve2-xc2ve3858",
"optimize_level": 2,
- "logging_level": "info",
"keep_outputs": true,
- "threshold_gops_percent": 20
+ "logging_level": "info",
+ "threshold_gops_percent": 20,
+ "dp_size": 1,
+ "tp_size": 1
}
}
],
diff --git a/versal_2ve/examples/tutorials/resnet50Cpp/README.md b/versal_2ve/examples/tutorials/resnet50Cpp/README.md
index ba7abd7..51524b0 100644
--- a/versal_2ve/examples/tutorials/resnet50Cpp/README.md
+++ b/versal_2ve/examples/tutorials/resnet50Cpp/README.md
@@ -15,7 +15,7 @@ The process begins with getting the resnet50 model from HuggingFace ONNX Model Z
This tutorial requires:
-* Latest Vitis AI Docker for Versal AI Edge Series Gen 2:
+* Vitis AI 6.2 Docker for Versal AI Edge Series Gen 2:
* Instructions for installation and startup are in the Vitis AI User Guide for Versal AI Edge Series Gen 2.
* VEK385 evaluation kit:
* Setup instructions are available in the Vitis AI User Guide for Versal AI Edge Series Gen 2.
@@ -62,8 +62,26 @@ cd ..
chmod -R a+w resnet50Cpp
```
-Load the latest docker image and launch it as explained in the Vitis AI User Guide for Versal AI Edge Series Gen 2.
+Load the docker image:
+```bash
+docker load -i .tgz
+```
+
+Run `docker images` to verify docker REPOSITORY, IMAGEID and TAG information.
+
+|REPOSITORY | TAG | IMAGE ID | CREATED | SIZE |
+|--------------------|-------------------|-------------|---------------|--------|
+|vitis_ai_2ve_docker | release_v6.2 | ?????? | xx hours ago | 39.1GB |
+
+Start the docker:
+
+```bash
+docker run -it --network host \
+ -v /path/to/your/license:/usr/licenses \
+ -v $PWD/resnet50Cpp:/resnet50Cpp \
+ --rm vitis_ai_2ve_docker:release_v6.2 "bash"
+```
### Model Compilation
Compile the resnet50 ONNX model `models/resnet50-v1-12.onnx` for the NPU.
diff --git a/versal_2ve/examples/tutorials/resnet50_quark/README.md b/versal_2ve/examples/tutorials/resnet50_quark/README.md
index a0e376a..186b14c 100644
--- a/versal_2ve/examples/tutorials/resnet50_quark/README.md
+++ b/versal_2ve/examples/tutorials/resnet50_quark/README.md
@@ -21,8 +21,8 @@ In addition, this tutorial includes scripts for evaluating model accuracy on bot
To build the example and deploy it on board, the following software and hardware are required:
-* Latest Vitis AI Docker for Versal AI Edge Series Gen 2:
- * Instructions for installation and startup are in the Vitis AI User Guide for Versal AI Edge Series Gen 2.
+* Vitis AI 6.2 Docker for Versal AI Edge Series Gen 2:
+ * Instructions for installation and startup are in the Vitis AI User Guide for Versal AI Edge Series Gen 2.
* VEK385 evaluation kit:
* Setup instructions are available in the Vitis AI User Guide for Versal AI Edge Series Gen 2.
* Internet access:
@@ -46,8 +46,26 @@ Adjust the access permissions of the working directories on the host machine:
chmod -R a+w
```
-Load the latest docker image and launch it as explained in the Vitis AI User Guide for Versal AI Edge Series Gen 2.
+Load the docker image:
+```
+docker load -i .tgz
+```
+
+Run `docker images` to verify docker REPOSITORY, IMAGEID and TAG information.
+
+|REPOSITORY | TAG | IMAGE ID | CREATED | SIZE |
+|--------------------|-------------------|-------------|---------------|--------|
+|vitis_ai_2ve_docker | release_v6.2 | ?????? | xx hours ago | 39.1GB |
+
+Star the docker:
+
+```
+docker run -it --network host \
+ -v /path/to/your/license:/usr/licenses \
+ -v $PWD/resnet50_quark:/resnet50_quark \
+ --rm vitis_ai_2ve_docker:release_v6.2 "bash"
+```
## Evaluate the Float Model Accuracy with ImageNet Dataset
Evaluate the float model accuracy before quantization and deployment:
diff --git a/versal_2ve/examples/tutorials/yolov8m/README.md b/versal_2ve/examples/tutorials/yolov8m/README.md
index 211a28b..6dfebf5 100644
--- a/versal_2ve/examples/tutorials/yolov8m/README.md
+++ b/versal_2ve/examples/tutorials/yolov8m/README.md
@@ -1,9 +1,4 @@
-
-
-  YOLOv8m Object Detection: Quantization to Deployment
- |
-
-
+# YOLOv8m Object Detection: Quantization to Deployment
This tutorial outlines the essential steps for deploying the YOLOv8m model on AMD Versal AI Edge Series Gen 2 VEK385 Evaluation Kit
using Vitis AI 6.2, while leveraging the mixed-precision capabilities of the Vitis AI compiler.
@@ -19,7 +14,7 @@ for ONNX Runtime, ensuring robust and seamless operation on the VEK385 evaluatio
This tutorial requires:
-* Latest Vitis AI Docker for Versal AI Edge Series Gen 2:
+* Vitis AI 6.2 Docker for Versal AI Edge Series Gen 2:
* Instructions for installation and startup are in the Vitis AI User Guide for Versal AI Edge Series Gen 2.
* VEK385 evaluation kit (see above):
* Setup instructions are available in the Vitis AI User Guide for Versal AI Edge Series Gen 2.
@@ -52,8 +47,27 @@ Before starting Docker, adjust the access permissions of the working directories
chmod -R a+w
```
-Load the latest docker image and launch it as explained in the Vitis AI User Guide for Versal AI Edge Series Gen 2.
+Pull the docker image:
+```
+docker pull amdih/vitis-ai:versal-2ve-release_v6.2_0612
+```
+
+Run `docker images` to verify docker REPOSITORY, IMAGEID and TAG information.
+
+|REPOSITORY | TAG | IMAGE ID | CREATED | SIZE |
+|--------------------|--------------------------------|-------------------|---------------|--------|
+|amdih/vitis-ai |versal-2ve-release_v6.2_0612 | 8cd54102c274 | xx hours ago | 31.2GB |
+
+Start the docker:
+
+```
+docker run -it --network host \
+ -v /path/to/your/license:/usr/licenses \
+ -v $PWD/yolov8m:/yolov8m \
+ --rm amdih/vitis-ai:versal-2ve-release_v6.2_0612 \
+ "bash"
+```
### Install Required Python Packages
@@ -182,6 +196,12 @@ After quantization finishes, compile the model.
### Model Compilation
Compile the above VINT8 ONNX model ``models/yolov8m_VINT8_skipNodes.onnx`` for the NPU.
+```bash
+python3 compile.py
+```
+
+The compilation uses the following configuration:
+
```
provider_options_dict = {
"config_file": "vitisai_config.json",
@@ -308,7 +328,7 @@ Use ``evaluate.py`` script to run evaluation on 5k COCO dataset. The python scri
* ``cpu-int8-fp32``: To evaluate VINT8-FP32 quantized ONNX model on CPU using ``CPUExecutionProvider``
```bash
- python3 evaluate.py --model models/yolov8m_VINT8_skipNodes.onnx --coco_dataset datasets/coco --device cpu-int8
+ python3 evaluate.py --model models/yolov8m_VINT8_skipNodes.onnx --coco_dataset datasets/coco --device cpu-int8-fp32
```
* ``npu-bf16``: To evaluate Vitis AI compiled BF16 model on VEK385 NPU using ``VitisAIExecutionProvider`` provided appropriate ``--cache_dir`` and ``--cache_key``
@@ -344,30 +364,30 @@ Use ``evaluate.py`` script to run evaluation on 5k COCO dataset. The python scri
| FP32 CPU |
Original FP32 ONNX model, evaluation on CPU |
-48.0843 |
-65.1921 |
-51.6791 |
-
-
-| VINT8-FP32 CPU |
-FP32 model quantized to VINT8 with VINT8 head and FP32 tail, evaluation on CPU |
-46.7227 |
-64.4694 |
-50.5399 |
+49.95 |
+67.02 |
+53.97 |
| BF16 NPU |
FP32 ONNX model compiled to BF16, evaluation on VEK385 NPU |
-46.1114 |
-63.9787 |
-49.8143 |
+50.29 |
+67.66 |
+54.65 |
+
+
+| VINT8-FP32 CPU |
+FP32 model quantized to VINT8 with VINT8 head and FP32 tail, evaluation on CPU |
+48.75 |
+66.28 |
+53.12 |
| VINT8-BF16 NPU |
VINT8-FP32 quantized model compiled to VINT8-BF16, evaluation on VEK385 NPU |
-44.0326 |
-62.3832 |
-47.7017 |
+48.38 |
+66.12 |
+52.68 |
@@ -408,7 +428,7 @@ Once the JSON files are generated, launch AI Analyzer using steps below.
docker run -it -p 8011:8011 --network host \
-v /path/to/your/license:/usr/licenses \
-v $PWD/yolov8m:/yolov8m \
- --rm vitis_ai_2ve_docker:release_v6.2 "bash"
+ --rm amdih/vitis-ai:versal-2ve-release_v6.2_0612 "bash"
```
- Inside docker:
@@ -440,7 +460,7 @@ For details on launching AI Analyzer inside docker, see AI Analyzer section of V
### Using Vitis AI Runtime (VART)
-As seen above, the total inference time using the ONNX Runtime Execution Provider is **22.757ms**. By switching to VART, this inference time can be reduced to **16.545 ms**.
+As seen above, the total inference time using the ONNX Runtime Execution Provider is **24.441ms**. By switching to VART, this inference time can be reduced to **13.687 ms**.
VART offloads computation directly to the NPU with minimal CPU involvement, delivering more efficient end-to-end performance compared to ONNX Runtime.
VART is a runtime environment designed for high-efficiency AI model inference on NPU hardware with minimal CPU overhead, and is used for precise end-to-end performance measurement.
@@ -475,7 +495,7 @@ The JSON file contains an object that describes the configuration for a single m
},
"ifms-config": [
{
- "name": "ifm0",
+ "name": "images_QuantizeLinear_Output",
"file": "input_vart/test_image_int8.bin"
}
],
@@ -547,13 +567,13 @@ ml_vart --app-config ml_vart_config.json
For 100 runs:
```bash
-ml_vart --app-config ml_vart_config.json --runs 100
+ml_vart --app-config ml_vart_config.json --benchmark --runs 100
```
Expected console output:
```bash
-Wrote tensor 0 data for frames 0-0 to file: "output_vart/infer_out0-bf16_1x84x8400_output0.bin"
+Average inference time over 100 runs: 13.82 ms
Run completed successfully.
```
@@ -587,4 +607,4 @@ This tutorial demonstrated the complete workflow for deploying YOLOv8m object de
The MIT License (MIT)
-Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
\ No newline at end of file
+Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
diff --git a/versal_2ve/examples/tutorials/yolov8m/compile.py b/versal_2ve/examples/tutorials/yolov8m/compile.py
index 60841cd..a501925 100755
--- a/versal_2ve/examples/tutorials/yolov8m/compile.py
+++ b/versal_2ve/examples/tutorials/yolov8m/compile.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# ===========================================================
-# Copyright © 2025 Advanced Micro Devices, Inc. All rights reserved.
+# Copyright © 2026 Advanced Micro Devices, Inc. All rights reserved.
# MIT License
# ===========================================================
diff --git a/versal_2ve/examples/tutorials/yolov8m/evaluate.py b/versal_2ve/examples/tutorials/yolov8m/evaluate.py
index 33fad33..6919ef1 100755
--- a/versal_2ve/examples/tutorials/yolov8m/evaluate.py
+++ b/versal_2ve/examples/tutorials/yolov8m/evaluate.py
@@ -1,4 +1,10 @@
#!/usr/bin/env python3
+
+# ===========================================================
+# Copyright © 2026 Advanced Micro Devices, Inc. All rights reserved.
+# MIT License
+# ===========================================================
+
"""
YOLOv8 Detection: COCO Evaluation
"""
@@ -9,41 +15,32 @@
import json
import sys
+# Import required packages at top
+try:
+ import numpy as np
+ import torch
+ import cv2
+ import onnxruntime as ort
+ from tqdm import tqdm
+ from pycocotools.coco import COCO
+ from pycocotools.cocoeval import COCOeval
+ from ultralytics.utils.nms import non_max_suppression
+ from utils import load_coco_dataset, load_onnx_model, calc_yolo_id_to_coco_map
+ from utils import evaluate_model, save_detections, evaluate_coco
+ from utils import evaluate_model_ultralytics, evaluate_ultralytics
-def install_required_packages():
- """Install required Python packages using pip install --no-deps"""
- import subprocess
- import sys
-
- # List of packages required by this script
- # Note: onnxruntime-vitisai is expected to be already installed
- required_packages = [
- 'tqdm', # Progress bars
- 'pycocotools', # COCO evaluation tools (required by utils.py)
- 'opencv-python==4.8.0.76', # cv2 - required for image processing
- ]
-
- print("Installing required packages...")
- for package in required_packages:
- try:
- print(f"Installing {package}...")
- subprocess.check_call([
- sys.executable, '-m', 'pip', 'install', package, '--no-deps'
- ])
- print(f" ✓ {package} installed successfully")
- except subprocess.CalledProcessError as e:
- print(f" ✗ Failed to install {package}: {e}")
- print(f" Warning: Continuing anyway. Some features may not work.")
-
- print("Package installation complete!")
+except ImportError as e:
+ missing_package = str(e).split("'")[1] if "'" in str(e) else str(e)
+ print(f"\nError: Missing required package: {missing_package}")
+ print("\nPlease install required packages with:")
+ print(" python3 -m pip install -r requirements.txt --no-deps")
+ sys.exit(1)
def parse_args():
parser = argparse.ArgumentParser(description="Evaluate YOLO model directly on COCO dataset")
- parser.add_argument("--install-packages", action='store_true',
- help="Install required Python packages and exit")
- parser.add_argument("--model", type=str, required=False, help="Path to ONNX model")
- parser.add_argument("--coco_dataset", type=str, required=False,
+ parser.add_argument("--model", type=str, required=True, help="Path to ONNX model")
+ parser.add_argument("--coco_dataset", type=str, required=True,
help="Path to COCO dataset folder (e.g., datasets/coco)")
parser.add_argument("--device", type=str, default="npu-int8-bf16",
choices=["cpu-fp32", "cpu-int8-fp32", "npu-bf16", "npu-int8-bf16"],
@@ -53,8 +50,10 @@ def parse_args():
parser.add_argument("--config_file", type=str, default="vitisai_config.json", help="VitisAI config file")
parser.add_argument("--num_images", type=int, default=5000, help="Number of images to evaluate (default: 5000)")
parser.add_argument("--conf", type=float, default=0.001, help="Confidence threshold (min_score_thres)")
- parser.add_argument("--iou", type=float, default=0.5, help="NMS IoU threshold")
+ parser.add_argument("--iou", type=float, default=0.6, help="NMS IoU threshold (default: 0.6, ultralytics standard)")
parser.add_argument("--output_dir", type=str, default=None, help="Output directory for results")
+ parser.add_argument("--use_coco_metrics", action='store_true',
+ help="Use COCO metrics (pycocotools) instead of Ultralytics metrics (default: Ultralytics)")
return parser.parse_args()
@@ -62,6 +61,20 @@ def create_session(model_path, device, cache_dir, cache_key, config_file):
"""Create ONNX Runtime session based on device type"""
import onnxruntime as ort
import os
+ import hashlib
+
+ # Debug: Print model path and compute hash to verify correct model is loaded
+ abs_model_path = os.path.abspath(model_path)
+ print(f"\n{'='*80}")
+ print(f"Loading model: {abs_model_path}")
+
+ # Compute MD5 hash of the model file
+ md5_hash = hashlib.md5()
+ with open(abs_model_path, 'rb') as f:
+ for chunk in iter(lambda: f.read(4096), b""):
+ md5_hash.update(chunk)
+ print(f"Model MD5 hash: {md5_hash.hexdigest()}")
+ print(f"{'='*80}\n")
if device == "npu-bf16":
print("Running BF16 Model on NPU")
@@ -121,10 +134,10 @@ def create_session(model_path, device, cache_dir, cache_key, config_file):
provider_options=[provider_options_dict]
)
elif device == "cpu-int8-fp32":
- print("Running VINT8-FP32 Model on CPU")
+ print(f"Running VINT8-FP32 Model on CPU: {model_path}")
session = ort.InferenceSession(model_path)
else: # cpu-fp32
- print("Running FP32 Model on CPU")
+ print(f"Running FP32 Model on CPU: {model_path}")
session = ort.InferenceSession(model_path)
return session
@@ -133,67 +146,6 @@ def create_session(model_path, device, cache_dir, cache_key, config_file):
def main():
args = parse_args()
- # Handle package installation request
- if args.install_packages:
- install_required_packages()
- print("\n✓ Package installation complete!")
- return
-
- # Now import required packages
- missing_packages = []
-
- try:
- import onnxruntime as ort
- except ImportError:
- missing_packages.append('onnxruntime-vitisai')
-
- try:
- from tqdm import tqdm
- except ImportError:
- missing_packages.append('tqdm')
-
- try:
- import cv2
- except ImportError:
- missing_packages.append('opencv-python')
-
- try:
- from pycocotools.coco import COCO
- from pycocotools.cocoeval import COCOeval
- except ImportError:
- missing_packages.append('pycocotools')
-
- # Report missing packages BEFORE trying to import utils.py
- # (utils.py imports pycocotools at the top level, so it will fail if missing)
- if missing_packages:
- print(f"Error: Missing required packages: {', '.join(missing_packages)}")
- print("Please run the script with --install-packages first:")
- print(" python3 evaluate_coco_direct.py --install-packages")
- sys.exit(1)
-
- # Import utils.py functions (only after all dependencies are confirmed)
- try:
- from utils import (
- load_coco_dataset, load_onnx_model,
- evaluate_model, evaluate_coco, save_detections, calc_yolo_id_to_coco_map
- )
- except ImportError as e:
- print(f"Error: Failed to import utils.py: {e}")
- print("Please ensure utils.py is in the same directory as this script.")
- print("Also ensure all required packages are installed:")
- print(" python3 evaluate_coco_direct.py --install-packages")
- sys.exit(1)
-
- # Missing packages check is now done before importing utils.py
-
- # Validate required arguments
- if not args.model:
- print("Error: --model is required (unless using --install-packages)")
- sys.exit(1)
- if not args.coco_dataset:
- print("Error: --coco_dataset is required")
- sys.exit(1)
-
print("=" * 80)
print("YOLOv8 Detection: COCO Evaluation")
print("=" * 80)
@@ -260,82 +212,148 @@ def main():
output_root.mkdir(parents=True, exist_ok=True)
print(f" Output directory: {output_root}")
- # Run evaluation - using utils.py function
+ # Run evaluation
print(f"\n3. Running inference and evaluation on {len(img_ids)} images...")
print(f" Confidence threshold: {args.conf}")
print(f" NMS IoU threshold: {args.iou}")
-
- inference_times = []
-
- # Use utils.py evaluate_model function
- detections = evaluate_model(
- session,
- input_name,
- coco,
- images_folder,
- img_ids,
- yolo_id_to_coco_id_map,
- coco_id_to_cls_map,
- input_size=(img_w, img_h),
- min_score_thres=args.conf,
- nms_iou_thresh=args.iou,
- num_max_images=args.num_images if args.num_images else None,
- output_root=output_root,
- )
-
- if not detections:
- print('Error: Model did not generate any predictions. Unable to evaluate Model accuracy on COCO dataset')
- sys.exit(1)
-
- # Save detections - using utils.py function
- pred_json_save_path = output_root / "pred.json"
- print(f"\n4. Saving detections to {pred_json_save_path}")
- save_detections(detections, pred_json_save_path)
- print(f" ✓ Saved {len(detections)} detections")
-
- # Evaluate COCO metrics - using utils.py function
- print(f"\n5. Computing COCO evaluation metrics...")
- coco_eval_save_path = output_root / "coco-metrics.json"
- mAP, mAP50, mAP75 = evaluate_coco(coco, pred_json_save_path, coco_eval_save_path)
-
- # Save evaluation results summary
- eval_results = {
- 'map_metrics': {
- 'mAP': float(mAP),
- 'mAP50': float(mAP50),
- 'mAP75': float(mAP75)
- },
- 'conf_threshold': float(args.conf),
- 'iou_threshold': float(args.iou),
- 'num_images': int(len(img_ids)),
- 'total_detections': int(len(detections)),
- 'device': args.device,
- 'model': str(args.model)
- }
-
- eval_results_json = output_root / "evaluation_results.json"
- print(f"\n6. Saving evaluation results to {eval_results_json}")
- with open(eval_results_json, 'w') as f:
- json.dump(eval_results, f, indent=2)
-
- # Print summary
- print(f"\n{'='*80}")
- print("EVALUATION SUMMARY")
- print(f"{'='*80}\n")
-
- print(f"\nmAP Metrics:")
- print(f" {'Metric':<20} {'Value':<15}")
- print(f" {'-'*35}")
- print(f" {'mAP (AP@[IoU=0.50:0.95])':<20} {mAP:<15.4f}")
- print(f" {'mAP50 (AP@IoU=0.50)':<20} {mAP50:<15.4f}")
- print(f" {'mAP75 (AP@IoU=0.75)':<20} {mAP75:<15.4f}")
-
- print(f"\n{'='*80}")
- print("✓ Evaluation completed successfully!")
- print(f"\nResults saved to: {output_root}")
- print(f" - Detections: {pred_json_save_path}")
- print(f" - COCO metrics: {coco_eval_save_path}")
- print(f" - Evaluation results: {eval_results_json}")
+ print(f" Evaluation method: {'COCO (pycocotools)' if args.use_coco_metrics else 'Ultralytics'}")
+
+ if not args.use_coco_metrics:
+ # Use Ultralytics evaluation
+ stats, num_classes = evaluate_model_ultralytics(
+ session,
+ input_name,
+ coco,
+ images_folder,
+ img_ids,
+ yolo_id_to_cls_map,
+ coco_id_to_cls_map,
+ input_size=(img_w, img_h),
+ min_score_thres=args.conf,
+ nms_iou_thresh=args.iou,
+ num_max_images=args.num_images if args.num_images else None,
+ )
+
+ if not stats:
+ print('Error: Model did not generate any predictions. Unable to evaluate Model accuracy')
+ sys.exit(1)
+
+ # Compute Ultralytics metrics
+ print(f"\n4. Computing Ultralytics evaluation metrics...")
+ ultralytics_metrics_path = output_root / "ultralytics-metrics.json"
+ metrics = evaluate_ultralytics(stats, yolo_id_to_cls_map, ultralytics_metrics_path)
+
+ if metrics is None:
+ print('Error: Unable to compute metrics')
+ sys.exit(1)
+
+ # Save evaluation results summary
+ eval_results = {
+ 'map_metrics': metrics,
+ 'conf_threshold': float(args.conf),
+ 'iou_threshold': float(args.iou),
+ 'num_images': int(len(img_ids)),
+ 'device': args.device,
+ 'model': str(args.model),
+ 'evaluation_method': 'ultralytics'
+ }
+
+ eval_results_json = output_root / "evaluation_results.json"
+ print(f"\n5. Saving evaluation results to {eval_results_json}")
+ with open(eval_results_json, 'w') as f:
+ json.dump(eval_results, f, indent=2)
+
+ # Print summary
+ print(f"\n{'='*80}")
+ print("EVALUATION SUMMARY (Ultralytics Metrics)")
+ print(f"{'='*80}\n")
+
+ print(f"\nmAP Metrics:")
+ print(f" {'Metric':<20} {'Value':<15}")
+ print(f" {'-'*35}")
+ print(f" {'Precision':<20} {metrics['Precision']*100:<15.2f}%")
+ print(f" {'Recall':<20} {metrics['Recall']*100:<15.2f}%")
+ print(f" {'mAP50':<20} {metrics['mAP50']*100:<15.2f}%")
+ print(f" {'mAP75':<20} {metrics['mAP75']*100:<15.2f}%")
+ print(f" {'mAP50-95':<20} {metrics['mAP50-95']*100:<15.2f}%")
+
+ print(f"\n{'='*80}")
+ print("✓ Evaluation completed successfully!")
+ print(f"\nResults saved to: {output_root}")
+ print(f" - Ultralytics metrics: {ultralytics_metrics_path}")
+ print(f" - Evaluation results: {eval_results_json}")
+
+ else:
+ # Use COCO evaluation (pycocotools)
+ detections = evaluate_model(
+ session,
+ input_name,
+ coco,
+ images_folder,
+ img_ids,
+ yolo_id_to_coco_id_map,
+ coco_id_to_cls_map,
+ input_size=(img_w, img_h),
+ min_score_thres=args.conf,
+ nms_iou_thresh=args.iou,
+ num_max_images=args.num_images if args.num_images else None,
+ output_root=output_root,
+ )
+
+ if not detections:
+ print('Error: Model did not generate any predictions. Unable to evaluate Model accuracy on COCO dataset')
+ sys.exit(1)
+
+ # Save detections
+ pred_json_save_path = output_root / "pred.json"
+ print(f"\n4. Saving detections to {pred_json_save_path}")
+ save_detections(detections, pred_json_save_path)
+ print(f" ✓ Saved {len(detections)} detections")
+
+ # Evaluate COCO metrics
+ print(f"\n5. Computing COCO evaluation metrics...")
+ coco_eval_save_path = output_root / "coco-metrics.json"
+ mAP, mAP50, mAP75 = evaluate_coco(coco, pred_json_save_path, coco_eval_save_path)
+
+ # Save evaluation results summary
+ eval_results = {
+ 'map_metrics': {
+ 'mAP': float(mAP),
+ 'mAP50': float(mAP50),
+ 'mAP75': float(mAP75)
+ },
+ 'conf_threshold': float(args.conf),
+ 'iou_threshold': float(args.iou),
+ 'num_images': int(len(img_ids)),
+ 'total_detections': int(len(detections)),
+ 'device': args.device,
+ 'model': str(args.model),
+ 'evaluation_method': 'coco'
+ }
+
+ eval_results_json = output_root / "evaluation_results.json"
+ print(f"\n6. Saving evaluation results to {eval_results_json}")
+ with open(eval_results_json, 'w') as f:
+ json.dump(eval_results, f, indent=2)
+
+ # Print summary
+ print(f"\n{'='*80}")
+ print("EVALUATION SUMMARY (COCO Metrics)")
+ print(f"{'='*80}\n")
+
+ print(f"\nmAP Metrics:")
+ print(f" {'Metric':<20} {'Value':<15}")
+ print(f" {'-'*35}")
+ print(f" {'mAP (AP@[IoU=0.50:0.95])':<20} {mAP:<15.4f}")
+ print(f" {'mAP50 (AP@IoU=0.50)':<20} {mAP50:<15.4f}")
+ print(f" {'mAP75 (AP@IoU=0.75)':<20} {mAP75:<15.4f}")
+
+ print(f"\n{'='*80}")
+ print("✓ Evaluation completed successfully!")
+ print(f"\nResults saved to: {output_root}")
+ print(f" - Detections: {pred_json_save_path}")
+ print(f" - COCO metrics: {coco_eval_save_path}")
+ print(f" - Evaluation results: {eval_results_json}")
if __name__ == "__main__":
diff --git a/versal_2ve/examples/tutorials/yolov8m/images/performance_summary.png b/versal_2ve/examples/tutorials/yolov8m/images/performance_summary.png
index 4bc4594..03ce5c3 100755
Binary files a/versal_2ve/examples/tutorials/yolov8m/images/performance_summary.png and b/versal_2ve/examples/tutorials/yolov8m/images/performance_summary.png differ
diff --git a/versal_2ve/examples/tutorials/yolov8m/images/vart_perf_summary.png b/versal_2ve/examples/tutorials/yolov8m/images/vart_perf_summary.png
index 8b744ef..7e54d19 100755
Binary files a/versal_2ve/examples/tutorials/yolov8m/images/vart_perf_summary.png and b/versal_2ve/examples/tutorials/yolov8m/images/vart_perf_summary.png differ
diff --git a/versal_2ve/examples/tutorials/yolov8m/ml_vart_config.json b/versal_2ve/examples/tutorials/yolov8m/ml_vart_config.json
new file mode 100644
index 0000000..935e872
--- /dev/null
+++ b/versal_2ve/examples/tutorials/yolov8m/ml_vart_config.json
@@ -0,0 +1,17 @@
+{
+ "inference-config": {
+ "model-file": "yolov8m_VINT8_skipNodes",
+ "runner-options": {
+ "config-file": "vitisai_config.json",
+ "log-level": "WARNING",
+ "ai-analyzer-profiling": true
+ }
+ },
+ "ifms-config": [
+ {
+ "name": "images_QuantizeLinear_Output",
+ "file": "input_vart/test_image_int8.bin"
+ }
+ ],
+ "ofms-dir": "output_vart"
+}
diff --git a/versal_2ve/examples/tutorials/yolov8m/prepare_data.py b/versal_2ve/examples/tutorials/yolov8m/prepare_data.py
index b275687..6ab9e74 100755
--- a/versal_2ve/examples/tutorials/yolov8m/prepare_data.py
+++ b/versal_2ve/examples/tutorials/yolov8m/prepare_data.py
@@ -1,7 +1,7 @@
#!/bin/bash
# ===========================================================
-# Copyright © 2025 Advanced Micro Devices, Inc. All rights reserved.
+# Copyright © 2026 Advanced Micro Devices, Inc. All rights reserved.
# MIT License
# ===========================================================
diff --git a/versal_2ve/examples/tutorials/yolov8m/preprocess_to_int8_bin.py b/versal_2ve/examples/tutorials/yolov8m/preprocess_to_int8_bin.py
index 0c7c710..e9c70a9 100755
--- a/versal_2ve/examples/tutorials/yolov8m/preprocess_to_int8_bin.py
+++ b/versal_2ve/examples/tutorials/yolov8m/preprocess_to_int8_bin.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# ===========================================================
-# Copyright © 2025 Advanced Micro Devices, Inc. All rights reserved.
+# Copyright © 2026 Advanced Micro Devices, Inc. All rights reserved.
# MIT License
# ===========================================================
@@ -81,7 +81,7 @@ def main():
parser.add_argument("--img_size", type=int, default=640,
help="Resize images to this square size (default: 640)")
parser.add_argument("--scale", type=float, default= 0.0078125,
- help="Quantization scale factor (default: 2.0)")
+ help="Quantization scale factor (default:0.0078125)")
parser.add_argument("--zero_point", type=int, default=0,
help="Quantization zero point (default: 0)")
diff --git a/versal_2ve/examples/tutorials/yolov8m/quantize.py b/versal_2ve/examples/tutorials/yolov8m/quantize.py
index b15e552..79cc049 100755
--- a/versal_2ve/examples/tutorials/yolov8m/quantize.py
+++ b/versal_2ve/examples/tutorials/yolov8m/quantize.py
@@ -1,7 +1,7 @@
#!/bin/bash
# ===========================================================
-# Copyright © 2025 Advanced Micro Devices, Inc. All rights reserved.
+# Copyright © 2026 Advanced Micro Devices, Inc. All rights reserved.
# MIT License
# ===========================================================
diff --git a/versal_2ve/examples/tutorials/yolov8m/requirements.txt b/versal_2ve/examples/tutorials/yolov8m/requirements.txt
index dd5099b..b5197e2 100755
--- a/versal_2ve/examples/tutorials/yolov8m/requirements.txt
+++ b/versal_2ve/examples/tutorials/yolov8m/requirements.txt
@@ -1,3 +1,5 @@
-pycocotools
-wget
-ultralytics==8.3.225
\ No newline at end of file
+torch
+ultralytics==8.3.225
+tqdm
+pycocotools
+wget
diff --git a/versal_2ve/examples/tutorials/yolov8m/run_inference.py b/versal_2ve/examples/tutorials/yolov8m/run_inference.py
index 3f4270c..7f958b9 100755
--- a/versal_2ve/examples/tutorials/yolov8m/run_inference.py
+++ b/versal_2ve/examples/tutorials/yolov8m/run_inference.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# ===========================================================
-# Copyright © 2025 Advanced Micro Devices, Inc. All rights reserved.
+# Copyright © 2026 Advanced Micro Devices, Inc. All rights reserved.
# MIT License
# ===========================================================
@@ -27,14 +27,6 @@
print(" pip install opencv-python --no-deps")
sys.exit(1)
-# Try to import yolov8_utils if available (after cv2 import to avoid conflicts)
-try:
- from yolov8_utils import *
- # Re-import cv2 after yolov8_utils in case it was shadowed
- import cv2
-except ImportError:
- pass
-
# Load COCO class labels
COCO_CLASSES = [ # 80 classes
"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck",
@@ -566,4 +558,4 @@ def main():
print("="*70)
if __name__ == "__main__":
- main()
\ No newline at end of file
+ main()
diff --git a/versal_2ve/examples/tutorials/yolov8m/utils.py b/versal_2ve/examples/tutorials/yolov8m/utils.py
index 8fcc4ee..4c8b5e4 100755
--- a/versal_2ve/examples/tutorials/yolov8m/utils.py
+++ b/versal_2ve/examples/tutorials/yolov8m/utils.py
@@ -1,7 +1,7 @@
-#!/bin/bash
+#!/bin/bash
# ===========================================================
-# Copyright © 2025 Advanced Micro Devices, Inc. All rights reserved.
+# Copyright © 2026 Advanced Micro Devices, Inc. All rights reserved.
# MIT License
# ===========================================================
@@ -12,10 +12,12 @@
import os
import subprocess
import numpy as np
+import torch
import onnxruntime as ort
from pycocotools.coco import COCO
from pycocotools.cocoeval import COCOeval
from tqdm import tqdm
+from ultralytics.utils.nms import non_max_suppression
PROJECT_DIR = Path(__file__).parent
@@ -89,6 +91,46 @@ def preprocess_image(img: np.ndarray, input_size, bgr2rgb=False):
return img_resized, (top, left), scale
+def postprocess_output_model_space(
+ output: np.ndarray,
+ min_score_thres: float,
+ nms_iou_thres: float,
+):
+ """
+ Postprocess YOLOv8 output using Ultralytics non_max_suppression.
+ Returns predictions in MODEL INPUT SPACE (640x640)
+
+ Args:
+ output: Model output, shape (num-cls + 4, num-boxes)
+ min_score_thres: Confidence threshold
+ nms_iou_thres: NMS IoU threshold
+
+ Returns:
+ Tensor of detections in model input space (640x640), shape (N, 6)
+ where columns are [x1, y1, x2, y2, conf, class]
+ """
+ # Convert output to torch tensor for Ultralytics NMS
+ predictions_torch = torch.from_numpy(output)
+
+ # Add batch dimension if needed
+ if predictions_torch.dim() == 2:
+ predictions_torch = predictions_torch.unsqueeze(0) # (84, 8400) -> (1, 84, 8400)
+
+ # Apply Ultralytics NMS
+ detections = non_max_suppression(
+ predictions_torch,
+ conf_thres=min_score_thres,
+ iou_thres=nms_iou_thres,
+ nc=0, # For detect task
+ multi_label=True, # Multi-label NMS
+ agnostic=False, # Per-class NMS
+ max_det=300 # COCO standard
+ )
+
+ # Return predictions in model input space (640x640)
+ return detections[0] # Single image (batch size = 1)
+
+
def postprocess_output(
output: np.ndarray,
pad_top_left: tuple,
@@ -99,52 +141,113 @@ def postprocess_output(
img_width: int,
img_height: int,
):
- # output shape: (xyxy + num-cls, num-boxes)
-
- # shape: (num-boxes, cxcywh + num-cls)
- output = np.transpose(output, (1, 0))
-
- cxcywh_nx4 = output[:, :4] # shape: (num-boxes, 4)
- # restore boxes
- cxcywh_nx4[:, 0] -= pad_top_left[1] # minus pad left from cx
- cxcywh_nx4[:, 1] -= pad_top_left[0] # minus pad top from cy
- cxcywh_nx4 /= scale # restore to original image scale
- cx, cy, w, h = (
- cxcywh_nx4[:, 0],
- cxcywh_nx4[:, 1],
- cxcywh_nx4[:, 2],
- cxcywh_nx4[:, 3],
+ """
+ Postprocess YOLOv8 output using Ultralytics non_max_suppression.
+ Returns detections in ORIGINAL IMAGE SPACE for COCO evaluation.
+
+ Args:
+ output: Model output, shape (num-cls + 4, num-boxes)
+ pad_top_left: Letterbox padding (top, left)
+ scale: Letterbox scale factor
+ yolo_id_to_coco_id_map: Mapping from YOLO class IDs to COCO IDs
+ min_score_thres: Confidence threshold
+ nms_iou_thres: NMS IoU threshold
+ img_width: Original image width
+ img_height: Original image height
+
+ Returns:
+ List of detections in COCO format (original image space)
+ """
+ # Convert output to torch tensor for Ultralytics NMS
+ # Output shape from model: (84, 8400) for single image
+ # Need to add batch dimension: (1, 84, 8400)
+ # non_max_suppression expects (batch, 84, 8400) - DO NOT TRANSPOSE
+ predictions_torch = torch.from_numpy(output)
+
+ # Add batch dimension if needed
+ if predictions_torch.dim() == 2:
+ predictions_torch = predictions_torch.unsqueeze(0) # (84, 8400) -> (1, 84, 8400)
+
+ # Apply Ultralytics NMS
+ # IMPORTANT: non_max_suppression expects (batch, 84, 8400) format, NOT transposed!
+ # multi_label=True allows boxes to have multiple class labels
+ # agnostic=False applies NMS per-class (not across all classes)
+ # max_det=300 is COCO evaluation standard
+ detections = non_max_suppression(
+ predictions_torch,
+ conf_thres=min_score_thres,
+ iou_thres=nms_iou_thres,
+ nc=0, # For detect task (auto-inferred from prediction shape)
+ multi_label=True, # KEY: Multi-label NMS for better accuracy
+ agnostic=False, # KEY: Per-class NMS
+ max_det=300 # COCO standard
)
- x0 = cx - w / 2.0
- y0 = cy - h / 2.0
- class_scores_nxc = output[:, 4:] # shape: (num-boxes, num-cls)
- scores = np.amax(class_scores_nxc, axis=1) # shape: (num-boxes,)
- class_indices = np.argmax(class_scores_nxc, axis=1)
+ # Process detections - detections is a list with one element per batch
+ pred = detections[0] # Single image (batch size = 1)
- # Stack boxes into list of [left, top, width, height]
- boxes_xywh_nx4: np.ndarray = np.stack(arrays=[x0, y0, w, h], axis=1)
- indices = cv2.dnn.NMSBoxes(boxes_xywh_nx4, scores, min_score_thres, nms_iou_thres)
+ if pred is None or len(pred) == 0:
+ return []
- detections = []
- for i in indices:
- cls_id = class_indices[i]
- score = class_scores_nxc[i, cls_id]
- if score >= min_score_thres:
- detections.append(
- {
- "category_id": yolo_id_to_coco_id_map[int(cls_id)],
- "bbox": tuple(round(float(x), 4) for x in boxes_xywh_nx4[i]),
- "score": round(float(score), 4),
- }
- )
-
- # keep map 100
- detections = sorted(detections, key=lambda d: d["score"], reverse=True)
- if len(detections) > 100:
- detections = detections[:100]
+ # pred shape: (num_detections, 6) where columns are [x1, y1, x2, y2, conf, class]
+ # Boxes are in model input space (640x640), need to restore to original image space
- return detections
+ coco_detections = []
+
+ for detection in pred:
+ x1, y1, x2, y2, conf, cls = detection.tolist()
+
+ # Convert from xyxy to cxcywh (center format)
+ cx = (x1 + x2) / 2.0
+ cy = (y1 + y2) / 2.0
+ w = x2 - x1
+ h = y2 - y1
+
+ # Restore boxes from model input space to original image space
+ # 1. Remove letterbox padding
+ cx_unpadded = cx - pad_top_left[1] # minus pad left
+ cy_unpadded = cy - pad_top_left[0] # minus pad top
+
+ # 2. Scale back to original image size
+ cx_orig = cx_unpadded / scale
+ cy_orig = cy_unpadded / scale
+ w_orig = w / scale
+ h_orig = h / scale
+
+ # 3. Convert back to corner format (x, y, w, h) for COCO
+ x_orig = cx_orig - w_orig / 2.0
+ y_orig = cy_orig - h_orig / 2.0
+
+ # Clamp to image boundaries
+ x_orig = max(0, min(x_orig, img_width))
+ y_orig = max(0, min(y_orig, img_height))
+ w_orig = min(w_orig, img_width - x_orig)
+ h_orig = min(h_orig, img_height - y_orig)
+
+ # Skip invalid boxes
+ if w_orig <= 0 or h_orig <= 0:
+ continue
+
+ # Convert YOLO class ID to COCO class ID
+ coco_class_id = yolo_id_to_coco_id_map[int(cls)]
+
+ coco_detections.append({
+ "category_id": coco_class_id,
+ "bbox": (
+ round(float(x_orig), 4),
+ round(float(y_orig), 4),
+ round(float(w_orig), 4),
+ round(float(h_orig), 4)
+ ),
+ "score": round(float(conf), 4),
+ })
+
+ # Sort by confidence (highest first) and limit to top 300
+ coco_detections = sorted(coco_detections, key=lambda d: d["score"], reverse=True)
+ if len(coco_detections) > 300:
+ coco_detections = coco_detections[:300]
+
+ return coco_detections
def draw_detections(
@@ -181,6 +284,156 @@ def draw_detections(
cv2.imwrite(save_path, canvas)
+def evaluate_model_ultralytics(
+ session: ort.InferenceSession,
+ input_name: str,
+ coco: COCO,
+ images_folder: Path,
+ img_ids: list,
+ yolo_id_to_cls_map: dict,
+ coco_id_to_cls_map: dict,
+ input_size=(640, 640),
+ min_score_thres=0.001,
+ nms_iou_thresh=0.6,
+ num_max_images=None,
+):
+ """
+ Evaluate model using Ultralytics metrics
+ Predictions and labels stay in model input space (640x640).
+
+ Returns:
+ stats: List of (correct, conf, pcls, tcls) for ap_per_class
+ num_classes: Number of classes
+ """
+ from ultralytics.utils import ops
+ from ultralytics.utils.metrics import box_iou
+
+ images_folder = Path(images_folder)
+ assert images_folder.is_dir()
+
+ if num_max_images is not None:
+ img_ids = img_ids[:num_max_images]
+
+ # Create mapping from COCO class ID to YOLO class ID
+ # yolo_id_to_cls_map: {'0': 'person', '1': 'bicycle', ...}
+ # coco_id_to_cls_map: {1: 'person', 2: 'bicycle', ...}
+ # We need: {1: 0, 2: 1, ...} (COCO ID -> YOLO ID)
+ coco_id_to_yolo_id = {}
+ for yolo_id_str, class_name in yolo_id_to_cls_map.items():
+ # Find the COCO ID for this class name
+ for coco_id, coco_class_name in coco_id_to_cls_map.items():
+ if coco_class_name == class_name:
+ coco_id_to_yolo_id[coco_id] = int(yolo_id_str)
+ break
+
+ stats = []
+ iouv = torch.linspace(0.5, 0.95, 10) # IoU vector for mAP@0.5:0.95
+ imgsz = input_size[0] # Assuming square input
+
+ for img_id in tqdm(img_ids):
+ img_info = coco.loadImgs(img_id)[0]
+ img_path = images_folder / img_info["file_name"]
+ img: np.ndarray = cv2.imread(str(img_path), cv2.IMREAD_COLOR)
+ img_height, img_width = img.shape[:2]
+
+ # Preprocess image
+ img_resized, pad_top_left, scale = preprocess_image(
+ img, input_size, bgr2rgb=True
+ )
+
+ # Run inference
+ outputs = session.run(output_names=None, input_feed={input_name: img_resized})
+ outputs = outputs[0]
+
+ # Postprocess in model space (no coordinate transformation)
+ pred = postprocess_output_model_space(
+ outputs[0],
+ min_score_thres,
+ nms_iou_thresh,
+ )
+
+ # Get ground truth labels for this image
+ ann_ids = coco.getAnnIds(imgIds=img_id)
+ anns = coco.loadAnns(ann_ids)
+
+ # Convert COCO annotations to model input space
+ labels_list = []
+ for ann in anns:
+ if ann.get('iscrowd', 0):
+ continue
+
+ # Get COCO bbox in original image space (x, y, w, h)
+ x, y, w, h = ann['bbox']
+ coco_class_id = ann['category_id']
+
+ # Convert COCO class ID to YOLO class ID
+ if coco_class_id not in coco_id_to_yolo_id:
+ continue
+ yolo_class_id = coco_id_to_yolo_id[coco_class_id]
+
+ # Transform bbox from original image space to model input space
+ # 1. Scale to resized (before padding)
+ x_scaled = x * scale
+ y_scaled = y * scale
+ w_scaled = w * scale
+ h_scaled = h * scale
+
+ # 2. Add padding offset
+ x_model = x_scaled + pad_top_left[1] # add left padding
+ y_model = y_scaled + pad_top_left[0] # add top padding
+
+ # 3. Convert to normalized xywh format (normalized by model input size)
+ cx_norm = (x_model + w_scaled / 2) / imgsz
+ cy_norm = (y_model + h_scaled / 2) / imgsz
+ w_norm = w_scaled / imgsz
+ h_norm = h_scaled / imgsz
+
+ labels_list.append([yolo_class_id, cx_norm, cy_norm, w_norm, h_norm])
+
+ # Process stats for this image
+ if len(labels_list) > 0:
+ labels = torch.tensor(labels_list, dtype=torch.float32)
+ nl = labels.shape[0]
+
+ # Convert labels from normalized xywh to model input space xyxy
+ tbox = ops.xywh2xyxy(labels[:, 1:5])
+ tbox = tbox * torch.tensor([imgsz, imgsz, imgsz, imgsz], dtype=tbox.dtype)
+ labelsn = torch.cat((labels[:, 0:1], tbox), 1)
+
+ if pred is None or len(pred) == 0:
+ # No predictions but have ground truth
+ stats.append((
+ torch.zeros(0, iouv.numel(), dtype=torch.bool),
+ torch.Tensor(),
+ torch.Tensor(),
+ labels[:, 0]
+ ))
+ else:
+ # Compare predictions and labels (both in model input space)
+ correct = _process_batch_ultralytics(pred, labelsn, iouv)
+ stats.append((correct, pred[:, 4], pred[:, 5], labels[:, 0]))
+
+ return stats, len(yolo_id_to_cls_map)
+
+
+def _process_batch_ultralytics(detections, labels, iouv):
+ from ultralytics.utils.metrics import box_iou
+
+ iou = box_iou(labels[:, 1:], detections[:, :4])
+ correct = np.zeros((detections.shape[0], iouv.shape[0])).astype(bool)
+ correct_class = labels[:, 0:1] == detections[:, 5]
+ for i in range(len(iouv)):
+ x = torch.where((iou >= iouv[i]) & correct_class)
+ if x[0].shape[0]:
+ matches = torch.cat((torch.stack(x, 1), iou[x[0], x[1]][:, None]), 1).cpu().numpy()
+ if x[0].shape[0] > 1:
+ matches = matches[matches[:, 2].argsort()[::-1]]
+ matches = matches[np.unique(matches[:, 1], return_index=True)[1]]
+ matches = matches[np.unique(matches[:, 0], return_index=True)[1]]
+ correct[matches[:, 1].astype(int), i] = True
+ return torch.tensor(correct, dtype=torch.bool, device=detections.device)
+
+
def evaluate_model(
session: ort.InferenceSession,
input_name: str,
@@ -283,6 +536,67 @@ def save_coco_eval_results(
print(f"COCO evaluation results saved to: {save_path}")
+def evaluate_ultralytics(
+ stats: list,
+ names: dict,
+ save_path: str = None
+):
+ """
+ Args:
+ stats: List of (correct, conf, pcls, tcls) tuples from each image
+ names: Class names dict
+ save_path: Optional path to save results
+
+ Returns:
+ dict with Precision, Recall, mAP50, mAP50-95
+ """
+ from ultralytics.utils.metrics import ap_per_class
+ from pathlib import Path
+
+ if not stats:
+ return None
+
+ stats = [torch.cat(x, 0).cpu().numpy() for x in zip(*stats)]
+ if len(stats) and stats[0].any():
+ # Handle both old (7 values) and new (12 values) ap_per_class return formats
+ results = ap_per_class(
+ *stats,
+ plot=False,
+ save_dir=Path('.'),
+ names=names
+ )
+ # Extract first 7 values which are consistent across versions
+ tp, fp, p, r, f1, ap, ap_class = results[:7]
+ # ap shape: (num_classes, 10) where 10 IoU thresholds from 0.5 to 0.95
+ # ap[:, 0] is mAP@0.5
+ # ap[:, 5] is mAP@0.75 (0.5 + 5*0.05 = 0.75)
+ ap50 = ap[:, 0] # IoU=0.50
+ ap75 = ap[:, 5] # IoU=0.75
+ ap_mean = ap.mean(1) # Mean across all IoU thresholds
+
+ mp, mr = p.mean(), r.mean()
+ map50 = ap50.mean()
+ map75 = ap75.mean()
+ map = ap_mean.mean()
+
+ metrics = {
+ 'Precision': float(mp),
+ 'Recall': float(mr),
+ 'mAP50': float(map50),
+ 'mAP75': float(map75),
+ 'mAP50-95': float(map)
+ }
+
+ if save_path:
+ import json
+ with open(save_path, 'w') as f:
+ json.dump(metrics, f, indent=2)
+
+ return metrics
+
+ return None
+
+
def evaluate_coco(coco_gt: COCO, detections_path: str, results_save_path: str):
coco_dt = coco_gt.loadRes(str(detections_path))
coco_eval = COCOeval(coco_gt, coco_dt, "bbox")
diff --git a/versal_2ve/examples/tutorials/yolov8m/vitisai_config.json b/versal_2ve/examples/tutorials/yolov8m/vitisai_config.json
index a1963be..4fe4fc1 100644
--- a/versal_2ve/examples/tutorials/yolov8m/vitisai_config.json
+++ b/versal_2ve/examples/tutorials/yolov8m/vitisai_config.json
@@ -10,6 +10,7 @@
"plugin": "vaip-pass_vaiml_partition",
"vaiml_config": {
"keep_outputs": true,
+ "logging_level": "info",
"device": "ve2-xc2ve3858",
"optimize_level": 2,
"threshold_gops_percent": 20,
diff --git a/versal_2ve/examples/tutorials/yolov8m/yolov8_utils.py b/versal_2ve/examples/tutorials/yolov8m/yolov8_utils.py
index d43a3ae..e598cca 100644
--- a/versal_2ve/examples/tutorials/yolov8m/yolov8_utils.py
+++ b/versal_2ve/examples/tutorials/yolov8m/yolov8_utils.py
@@ -1,7 +1,7 @@
#!/bin/bash
# ===========================================================
-# Copyright © 2025 Advanced Micro Devices, Inc. All rights reserved.
+# Copyright © 2026 Advanced Micro Devices, Inc. All rights reserved.
# MIT License
# ===========================================================