本文以 YOLOv8-L 模型为例,系统讲解其从 ONNX 模型到 NPU 支持的cix格式的完整编译与推理流程。
YOLOv8 简介
YOLOv8 是 Ultralytics 公司推出的最新一代单阶段目标检测模型,延续了 YOLO 系列“实时性”与“高精度”的设计哲学。相较于前代版本,YOLOv8 在网络结构、损失函数和训练策略上进行了多项优化,支持分类、检测、分割、姿态估计等多种计算机视觉任务。其开源实现提供了 ONNX 格式的导出能力,为跨平台部署奠定了基础。
在边缘计算与专用 AI 芯片快速发展的背景下,将 YOLOv8 部署到神经网络处理单元(NPU)上,已成为提升推理效率、降低功耗的关键路径。
安装NOE Compiler
首先需要申请早鸟计划,获取NeuralOne AI SDK,其中包含了NPU驱动以及用于模型编译转换的CixBuilder。
CixBuilder用于模型的编译,能够将 ONNX 格式的模型转换为可以使用NPU推理的cix格式,我们申请时的版本为q1,其中的CixBuilder的版本为6.1.3119。
随后安装基础依赖项及 NPU 编译工具链:
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install CixBuilder-6.1.3119.3-py3-none-linux_x86_64.whl安装完成后如下图所示:
模型编译
YOLOv8 的 ONNX 模型可从 ModelScope 模型库获取:
wget https://modelscope.cn/models/cix/ai_model_hub_25_Q1_2/resolve/master/models/ComputeVision/Object_Detection/onnx_yolov8_l/model/yolov8l.onnx此外,原始 ONNX 模型可能包含动态维度或冗余算子,不利于 NPU 编译。使用 onnxsim 工具进行常量折叠与结构简化,并显式指定输入尺寸:
pip3 install onnxsim onnxruntime -i https://pypi.tuna.tsinghua.edu.cn/simple
onnxsim yolov8l_q1_2.onnx yolov8l_q1_2-sim.onnx --overwrite-input-shape 1,3,640,640执行结果如下:
之后我们配置.cfg 配置文件,编译过程通过 .cfg 配置文件驱动,具体内容如下:
[Common]
mode = build
[Parser]
model_type = ONNX
model_name = yolov8_l
detection_postprocess =
model_domain = OBJECT_DETECTION
input_data_format = NCHW
input_model = ./yolov8l_q1_2-sim.onnx
input = images
input_shape = [1, 3, 640, 640]
output_dir = ./
[Optimizer]
dataset = numpydataset
calibration_data = ./calibration_data.npy
calibration_batch_size = 1
output_dir = ./
dump_dir = ./
quantize_method_for_activation = per_tensor_asymmetric
quantize_method_for_weight = per_channel_symmetric_restricted_range
save_statistic_info = True
trigger_float_op = disable & <[(258, 272)]:float16_preferred!>
weight_bits = 8& <[(273,274)]:16>
activation_bits = 8& <[(273,274)]:16>
bias_bits = 32& <[(273,274)]:48>
[GBuilder]
target = X2_1204MP3
outputs = yolov8l_q1_2.cix
tiling = fps
profile = True其中主要的是输入模型和输出模型的路径和名称,以及校准集的路径。
完成修改后,我们就能够执行如下命令进行模型的编译了。
cixbuild ./yolov8_l_q1_2_build.cfg在编译过程中可能会报如下错误:
这是因为系统提示找不到 NPU 模拟器动态库,需我们手动查找并设置环境变量:
find / -name libaipu_simulator_x2.so
export LD_LIBRARY_PATH=/opt/conda/lib/python3.8/site-packages/AIPUBuilder/simulator-lib:$LD_LIBRARY_PATH
之后重新执行模型编译:
cixbuild ./yolov8_l_q1_2_build.cfg
编译成功后,就能够得到cix格式的模型文件了。
模型部署
首先将推理代码仓库克隆下来
git cloen https://github.com/cixtech/ai_model_hubyolov8l的推理代码和相关位于如下目录中:
ai_model_hub/models/ComputeVision/Object_Detection/onnx_yolov8_l
我们从ai_model_hub_25_Q1_2中把text_data目录也复制过来,其中包含了用于测试的两张图片。并且把模型文件放到model目录下
之后我们新建一个虚拟环境并安装相应的依赖
python3 -m venv --system-site-packages yolo_env
source yolo_env/bin/activate
pip install tqdm opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install -r ../../../../requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
此外,我们对推理脚本进行了适当修改,增加了推理耗时显示
import cv2
import numpy as np
import argparse
import os
import sys
from tqdm import tqdm
import time # 新增:导入 time 模块用于计时
_abs_path = os.path.join(os.path.dirname(__file__), "../../../../")
sys.path.append(_abs_path)
from utils.tools import get_file_list
from utils.image_process import preprocess_object_detect_method1
from utils.object_detect_postprocess import postprocess_yolo, xywh2xyxy
from utils.draw import draw_coco as draw
from utils.NOE_Engine import EngineInfer
def get_args():
parser = argparse.ArgumentParser()
parser.add_argument(
"--image_path",
default="test_data",
help="path to the image file",
)
parser.add_argument(
"--model_path",
default="yolov8_l.cix",
help="path to the quant model file",
)
parser.add_argument(
"--output_dir", default="./output_npu", help="path to the result output"
)
parser.add_argument(
"--conf_thr",
type=float,
default=0.3,
help="Score threshould to filter the result.",
)
parser.add_argument(
"--nms_thr", type=float, default=0.45, help="NMS threshould to the object."
)
parser.add_argument(
"--benchmark",
action='store_true', # 建议修改为 action,更标准
help="benchmark on COCO val dataset."
)
args = parser.parse_args()
return args
if __name__ == "__main__":
args = get_args()
benchmark = args.benchmark
os.makedirs(args.output_dir, exist_ok=True)
# Get list of images from the specified path
image_list = get_file_list(args.image_path)
model = EngineInfer(args.model_path)
# 初始化计时变量
total_inference_time = 0.0
total_images = 0
if benchmark:
from utils.evaluate.coco_metric import COCO_Metric
save_pred_json = "pred_yolov8_l_npu.json"
coco_metric = COCO_Metric(saved_json_path=save_pred_json)
image_list = coco_metric.get_image_ids()
else:
image_list = get_file_list(args.image_path)
# 使用 tqdm 显示进度条
for img_name in tqdm(image_list):
if benchmark:
img_id = img_name
img_name = coco_metric.get_image_path(img_id)
# ------------------- 开始计时 -------------------
start_time = time.perf_counter() # 使用 perf_counter 更精确
# Preprocess the image for inference
src_shape, new_shape, show_image, data = preprocess_object_detect_method1(
img_name, target_size=(640, 640), mode="BGR"
)
# Run inference and get predictions
pred = model.forward(data.astype(np.float32))[0]
pred = np.reshape(pred, (84, 8400))
pred = np.transpose(pred, (1, 0))
# Postprocess
results = postprocess_yolo(pred, args.conf_thr, args.nms_thr)
if len(results) > 0:
bbox_xywh = results[:, :4]
bbox_xyxy = xywh2xyxy(bbox_xywh)
x_scale = src_shape[1] / new_shape[1]
y_scale = src_shape[0] / new_shape[0]
bbox_xyxy *= (x_scale, y_scale, x_scale, y_scale)
if benchmark:
coco_metric.append_bboxes(img_id, bbox_xyxy, results[:, 5], results[:, 4])
else:
ret_img = draw(show_image, bbox_xyxy, results[:, 5], results[:, 4])
output_path = os.path.join(args.output_dir, os.path.basename(img_name))
cv2.imwrite(output_path, ret_img)
else:
if not benchmark:
output_path = os.path.join(args.output_dir, os.path.basename(img_name))
cv2.imwrite(output_path, show_image)
# ------------------- 结束计时 -------------------
end_time = time.perf_counter()
inference_time = end_time - start_time # 单张图片总耗时(秒)
total_inference_time += inference_time
total_images += 1
# 打印每张图片的耗时
print(f"{os.path.basename(img_name)}: {inference_time*1000:.2f} ms")
# 计算并打印平均推理耗时
if total_images > 0:
avg_time_ms = (total_inference_time / total_images) * 1000 # 转换为毫秒
print(f"\n[INFO] Total images: {total_images}")
print(f"[INFO] Average inference time: {avg_time_ms:.2f} ms per image")
print(f"[INFO] Average FPS: {1000 / avg_time_ms:.2f} FPS")
# Benchmark evaluation
if benchmark:
coco_metric.saved_json()
coco_metric.evaluate()
# Clean up model
model.clean()模型测试
之后,我们分别执行inference_npu.py和inference_onnx.py分别测试模型在npu和cpu上的运行效果。
python3 inference_npu.py --image_path ./test_data/ --model_path ./model/yolov8l_q1_2.cix
推理输出在output_npu目录下
python3 inference_onnx.py --image_path ./test_data/ --onnx_path ./model/yolov8l_q1_2-sim.onnx
推理输出在output_onnx目录下
从推理结果可以看到,使用NPU推理比CPU推理速度能够快大约5.6倍,说明NPU的性能还是很不错的。