所属合集 RK3588 端侧 AI 部署 第 6 / 6 篇
RK3588 端侧 AI 部署(六):MobileNet、YOLO 与 RKLLM 实战
最后一篇把前面的知识组合成项目路线:先用 MobileNet 验证基础 C API,再完成一个自定义 YOLO 安全帽检测项目,最后认识 RKLLM 大模型部署。
一、为什么先做 MobileNet
MobileNet 分类模型通常只有一个图像输入和一个分类输出,能把问题限制在:
读取 RKNN→ 查询 Tensor→ BGR/RGB、resize 和 layout→ NPU 推理→ 反量化/Softmax/Top-K先通过官方 MobileNet Model Zoo 示例 验证环境和通用 C API,再进入 YOLO。分类模型都没跑对时,直接调多尺度检测后处理只会增加变量。
MobileNet 的验收标准:
- Python、Lite2 和 C++ 的 Top-5 基本一致;
- 输入 shape、RGB/BGR 和均值/标准差已明确;
- C++ 能正确释放输出和 Context;
- 已分别统计预处理、推理和后处理时间。
二、YOLO 部署比分类多了什么
YOLO 的 RKNN 调用本身没有本质变化,难点集中在输出契约和后处理:
- 多尺度输出;
- 回归分支和分类分支;
- INT8 输出反量化;
- 置信度筛选;
- 网格/stride 解码;
- letterbox 坐标还原;
- 按类别执行 NMS;
- 类别数量和标签映射。
不同 YOLO 版本甚至同一版本的不同导出参数,输出数量、顺序和 shape 都可能不同。C++ 后处理必须针对实际 ONNX/RKNN 查询结果编写。
三、项目:安全帽检测从数据到板端
第 1 步:定义业务指标
安全帽项目不能只写“mAP 越高越好”。建议提前定义:
helmet和no_helmet两类;- 对
no_helmet漏检的容忍度; - 摄像头分辨率和距离范围;
- 目标最小像素尺寸;
- 期望 FPS 和最大告警延迟;
- 白天/夜间、室内/室外和遮挡场景。
业务上未戴安全帽的漏检代价通常高于普通误报,因此要单独看该类别 Recall 和真实漏检案例。
第 2 步:采集和划分数据
视频抽帧示例:
import cv2from pathlib import Path
video = cv2.VideoCapture("source.mp4")output = Path("frames")output.mkdir(exist_ok=True)
index = 0saved = 0interval = 15
while True: ok, frame = video.read() if not ok: break if index % interval == 0: cv2.imwrite(str(output / f"{saved:06d}.jpg"), frame) saved += 1 index += 1
video.release()print("saved:", saved)连续视频相邻帧非常相似。训练集和验证集应按视频、机位或时间段划分,不能把同一段连续帧随机打散到两边,否则验证指标会虚高。
YOLO 数据结构:
helmet_dataset/├─ images/│ ├─ train/│ └─ val/└─ labels/ ├─ train/ └─ val/单个标签行:
class_id x_center y_center width height坐标归一化到 0~1。class_id 必须与 YAML 中的类别顺序一致。
helmet.yaml:
path: /absolute/path/to/helmet_datasettrain: images/trainval: images/valnames: 0: helmet 1: no_helmet第 3 步:建立训练环境
conda create -n helmet-yolo python=3.10 -yconda activate helmet-yolopip install ultralytics
nvidia-smipython - <<'PY'import torchprint("torch:", torch.__version__)print("cuda available:", torch.cuda.is_available())print("cuda version:", torch.version.cuda)PYPyTorch、CUDA 和驱动组合应参考当前 PyTorch/Ultralytics 官方安装说明,不要固定照抄课程中的旧版本。
第 4 步:训练和验证
命令行示例:
yolo detect train \ model=yolo26n.pt \ data=helmet.yaml \ epochs=300 \ imgsz=640 \ batch=-1 \ workers=4 \ patience=50 \ device=0Python 示例:
from ultralytics import YOLO
if __name__ == "__main__": model = YOLO("yolo26n.pt") model.train( data="helmet.yaml", epochs=300, imgsz=640, batch=-1, workers=4, patience=50, device=0, )Windows 多进程训练要保留 if __name__ == "__main__":。
训练结束优先使用 best.pt,并检查:
- train/val loss;
- 每类别 Precision、Recall、mAP;
- 混淆矩阵;
- PR 和 F1 曲线;
- 验证集真实框与预测框;
- 漏检、误检和小目标案例。
Ultralytics 当前训练参数和用法参考官方 Train 文档。
四、把 YOLO 模型导出到 RKNN
这里有两条路线。
路线 A:Ultralytics 直接导出 RKNN
截至本文写作时,Ultralytics 已提供 RKNN 集成,要求在 x86 Linux 上导出,当前主要支持检测模型:
from ultralytics import YOLO
model = YOLO("runs/detect/train/weights/best.pt")
# RK3588 浮点构建model.export(format="rknn", name="rk3588")
# INT8 构建,data 用于量化校准model.export( format="rknn", name="rk3588", quantize=8, data="helmet.yaml",)官方说明见 Ultralytics RKNN 集成文档。导出后可用 Ultralytics 在 RK3588 上执行预测和验证,但正式 C++ 项目仍要确认输出 Tensor 契约。
路线 B:ONNX → Toolkit2 → 自定义 C++
如果需要复用 Model Zoo 的 C++ 后处理、修改检测头输出或做深度定制:
best.pt→ 导出 ONNX→ onnx.checker + Netron + ONNX Runtime→ Toolkit2 转 RKNN→ Python 模拟器/连板验证→ C++ 后处理课程中的 YOLO26 示例通过修改检测头导出多路回归/分类特征。源码路径会随 Ultralytics 版本变化,不能机械按旧行号修改。无论怎样导出,必须固定并记录:
- 输出数量和顺序;
- 每个输出 shape;
- 三个 stride/尺度;
- 类别数;
- DFL/回归解码方式;
- 是否已经包含 NMS。
五、C++ 检测后处理的关键顺序
读取各输出 Tensor→ 按 scale/zero point 反量化→ 解码每个尺度的候选框→ 计算 objectness/类别分数→ 置信度过滤→ 合并所有尺度→ 按类别 NMS→ 去除 letterbox padding→ 映射回原图坐标Letterbox 参数必须随每帧保存:
scale = min(input_w / original_w, input_h / original_h)pad_x = (input_w - original_w × scale) / 2pad_y = (input_h - original_h × scale) / 2还原坐标时先减 padding,再除以 scale,并裁剪到原图边界。
逐级调试方法
- 固定一张测试图;
- 保存 PyTorch 输出和最终框;
- 保存 ONNX 对应输出;
- 保存 RKNN Python 输出;
- 保存 C++ 反量化后的部分 Tensor;
- 对比同一位置、同一类别和同一尺度;
- 最后才加入视频和多线程。
如果单图都不一致,不要通过反复调置信度阈值掩盖问题。
六、用官方 YOLO 示例建立参考
RKNN Model Zoo 提供 YOLOv5、YOLOv8 等示例。以 YOLOv5 为例:
cd rknn_model_zoo/examples/yolov5/model./download_model.sh
cd ../pythonpython convert.py ../model/yolov5s_relu.onnx rk3588
cd ../../..export GCC_COMPILER=<AARCH64_TOOLCHAIN_PREFIX>./build-linux.sh -t rk3588 -a aarch64 -d yolov5示例模型可能专门删除了不利于量化的子图,并把它移到 C++ 后处理。因此官方示例的后处理不能无条件套到任意原始 YOLO ONNX。
七、YOLO 性能优化顺序
1. INT8
先用代表性校准集转换,再跑完整验证集。不要只比较某一张图片。
2. Letterbox 和 RGA
Letterbox首先保证几何正确。确认 OpenCV 版本结果正确后,再用 RGA 替换 resize/颜色转换,并逐像素/逐框比较结果。
3. 多 Context 线程池
解码 → 有界队列 → 3 个左右独立 Context worker→ 结果重排 → 后处理/显示线程数增加到一定程度会被 DDR、CPU 前后处理或 NPU 饱和限制。报告总吞吐时同时报告单帧 P95 延迟和队列深度。
4. 零拷贝
通用 API 正确后,再把 RGA/DMA-BUF 输出绑定给 RKNN Tensor Memory,减少 CPU 拷贝。必须处理 stride 和 Cache 一致性。
5. 硬件友好的模型结构
课程演示了将 SiLU 改为 ReLU 后重新训练。修改激活函数会改变模型,不能沿用原权重后直接宣称加速成功。正确流程是重新训练、导出、量化,并同时比较精度和性能。
八、视频流项目的完整指标
最终报告至少包含:
| 类别 | 指标 |
|---|---|
| 模型 | 版本、输入尺寸、FP16/INT8、模型大小 |
| 精度 | 每类 Precision/Recall、mAP、困难样本 |
| 性能 | 纯推理、端到端 P50/P95、持续 FPS |
| 资源 | CPU/NPU 利用率、峰值 RSS、温度、功耗 |
| 流水线 | 解码/预处理/推理/后处理/编码耗时 |
| 稳定性 | 运行时长、丢帧、队列峰值、错误恢复 |
“100 FPS”如果不说明线程数、是否包含前后处理、是否丢帧和输入尺寸,几乎没有复现价值。
九、RKLLM:在 RK3588 上部署 Qwen/DeepSeek
RKLLM 与 RKNN 的整体思路相似:
Hugging Face 模型→ RKLLM-Toolkit 转换和 W4A16/W8A8 等量化→ .rkllm→ RKLLM Runtime→ RK3588 NPU当前官方入口是 airockchip/rknn-llm。截至 2026-07-21,该仓库发布版本已到 v1.3.0,并列出 Python 3.9~3.12 支持;实际模型、量化类型和 API 仍以当前 README/Release 为准。
1. 先评估内存
总内存不只包括模型文件:
量化权重+ KV Cache(随上下文增长)+ Runtime+ Tokenizer/embedding+ 系统和其他应用8 GiB 板卡应从较小模型和较短上下文开始,先确认能稳定加载,再逐步增加。能放入存储不等于能在内存中运行。
2. 优先跑官方预转换模型
第一次建议按仓库 Quickstart 下载预转换模型和 Demo,先验证 Runtime/驱动。官方多模态示例的基本步骤是:
adb push ./demo_Linux_aarch64 /data/adb push ./model.rkllm /data/demo_Linux_aarch64/adb push ./vision_model.rknn /data/demo_Linux_aarch64/
adb shellcd /data/demo_Linux_aarch64export LD_LIBRARY_PATH=./lib具体命令参数包含图片、视觉 RKNN、语言 RKLLM、最大生成长度、上下文长度、NPU 核数和平台。参数在新版本中可能变化,运行前直接查看该 release 的 README 和 Demo usage。
3. 大模型性能要测什么
- 首 Token 延迟;
- 生成速度 Token/s;
- 最大 RSS 和 KV Cache;
- 不同上下文长度的变化;
- CPU/NPU 利用率;
- 输出质量和量化影响;
- 连续多轮对话的稳定性。
官方仓库建议使用频率脚本,并可设置:
export RKLLM_LOG_LEVEL=1再配合仓库的 CPU/NPU 监控脚本记录性能和内存。
十、完整学习路线验收
完成六篇后,我应该能独立完成:
确认硬件和版本→ 搭建 Toolkit2/Runtime 环境→ 检查并转换 ONNX→ 对齐预处理和输出契约→ 评估 INT8 精度、性能和内存→ 用 Lite2/C API 在 RK3588 运行→ 用零拷贝和多 Context 优化→ 完成分类、检测或小型 LLM 项目真正完成项目的标准,不是终端打印 success,而是有一套可复现的代码、版本、模型、指标和问题定位方法。
Some information may be outdated