Waterman · 11月13日 · 辽宁

【“星睿O6”AI PC开发套件评测】YOLOv8l模型编译与推理部署

本文以 YOLOv8-L 模型为例,系统讲解其从 ONNX 模型到 NPU 支持的cix格式的完整编译与推理流程。

YOLOv8 简介

YOLOv8 是 Ultralytics 公司推出的最新一代单阶段目标检测模型,延续了 YOLO 系列“实时性”与“高精度”的设计哲学。相较于前代版本,YOLOv8 在网络结构、损失函数和训练策略上进行了多项优化,支持分类、检测、分割、姿态估计等多种计算机视觉任务。其开源实现提供了 ONNX 格式的导出能力,为跨平台部署奠定了基础。
在边缘计算与专用 AI 芯片快速发展的背景下,将 YOLOv8 部署到神经网络处理单元(NPU)上,已成为提升推理效率、降低功耗的关键路径。
1.png

安装NOE Compiler

首先需要申请早鸟计划,获取NeuralOne AI SDK,其中包含了NPU驱动以及用于模型编译转换的CixBuilder。
CixBuilder用于模型的编译,能够将 ONNX 格式的模型转换为可以使用NPU推理的cix格式,我们申请时的版本为q1,其中的CixBuilder的版本为6.1.3119。
2.png
随后安装基础依赖项及 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

安装完成后如下图所示:
3.png

模型编译

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

执行结果如下:
4.png
之后我们配置.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

其中主要的是输入模型和输出模型的路径和名称,以及校准集的路径。
5.png
完成修改后,我们就能够执行如下命令进行模型的编译了。

cixbuild ./yolov8_l_q1_2_build.cfg

在编译过程中可能会报如下错误:
6.png
这是因为系统提示找不到 NPU 模拟器动态库,需我们手动查找并设置环境变量:

find / -name libaipu_simulator_x2.so
export LD_LIBRARY_PATH=/opt/conda/lib/python3.8/site-packages/AIPUBuilder/simulator-lib:$LD_LIBRARY_PATH

7.png
之后重新执行模型编译:

cixbuild ./yolov8_l_q1_2_build.cfg

8.png
编译成功后,就能够得到cix格式的模型文件了。
9.png

模型部署

首先将推理代码仓库克隆下来

git cloen https://github.com/cixtech/ai_model_hub

yolov8l的推理代码和相关位于如下目录中:

ai_model_hub/models/ComputeVision/Object_Detection/onnx_yolov8_l

10.png
我们从ai_model_hub_25_Q1_2中把text_data目录也复制过来,其中包含了用于测试的两张图片。并且把模型文件放到model目录下
11.png
之后我们新建一个虚拟环境并安装相应的依赖

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

12.png
此外,我们对推理脚本进行了适当修改,增加了推理耗时显示

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

13.png
推理输出在output_npu目录下
14.jpg
15.jpg

python3 inference_onnx.py --image_path ./test_data/ --onnx_path ./model/yolov8l_q1_2-sim.onnx

16.png
推理输出在output_onnx目录下
17.jpg
18.jpg
从推理结果可以看到,使用NPU推理比CPU推理速度能够快大约5.6倍,说明NPU的性能还是很不错的。

推荐阅读
关注数
0
文章数
3
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息