RK3588 部署 Qwen3-VL 多模态大模型:RKNN-LLM 项目部分实践日志
RK3588 部署 Qwen3-VL 多模态大模型:RKNN-LLM 项目部分实践日志
本文整理自
RKNN-LLM项目的实际目录、构建脚本和 RK3588 运行链路,作为项目的公开使用说明。本文重点介绍当前项目中已经验证的Qwen3-VL-2B 局域网 Web Demo,同时说明仓库内的 RKLLM API 示例、服务端示例、模型转换脚本、运行时库和性能测试模块。
一、文章摘要
本文介绍如何在 Rockchip RK3588 开发板上使用 RKNN、RKLLM Runtime 和 RKLLM Toolkit 部署 Qwen3-VL 多模态大模型,实现以下功能:
- 在 RK3588 NPU 上运行 Qwen3-VL-2B。
- 通过局域网网页完成图片问答和连续文字对话。
- 使用 Python Web 服务、SSE 和 Unix Socket 连接 C++ 推理进程。
- 通过 Sherpa-Onnx + RKNN 接入 Type-C 耳机麦克风,实现中文实时语音识别。
- 通过受限 Function Calling 控制本地音乐播放。
- 保留 RKLLM 原生 C++ API 示例、OpenAI 兼容服务示例和模型转换流程。
关键词:RK3588、RKNN、RKLLM、Qwen3-VL、边缘计算、NPU、RKNN-Toolkit2、Sherpa-Onnx、语音识别、局域网 Web
二、项目最终效果
项目的核心运行形态如下:
电脑浏览器 │ HTTP / SSE ▼ Python Web 网关 │ Unix Socket 长度帧协议 ▼ C++ Qwen3-VL Worker ├── RKNN 视觉编码器 └── RKLLM Qwen3-VL-2B Type-C 耳机麦克风 │ ALSA ▼ 独立 ASR 进程 │ Sherpa-Onnx + RKNN Zipformer ▼ ASR 最终文本自动提交到 Web 网关打开浏览器访问开发板地址后,可以完成以下操作:
- 输入文字,让本地大模型回答。
- 上传一张 JPG、PNG 或 WebP 图片并提问。
- 在上一轮对话的基础上继续追问。
- 点击麦克风按钮,通过 Type-C 耳机输入中文语音。
- 发送“播放某首歌”等指令,让模型调用白名单音乐工具。
- 点击“新建对话”或调用接口清理当前上下文。
三、RKNN、RKLLM 和 Toolkit 的关系
很多初次接触 Rockchip NPU 的开发者容易把几个组件混在一起。它们的职责并不相同。
| 组件 | 主要职责 | 运行位置 |
|---|---|---|
| RKNN-Toolkit2 | 将 ONNX 等视觉模型转换为 RKNN 模型 | PC 或服务器 |
| RKNN Runtime | 在 Rockchip 芯片上执行 RKNN 模型 | 开发板 |
| RKLLM-Toolkit | 将 HuggingFace 语言模型量化并转换为 RKLLM 模型 | PC 或服务器 |
| RKLLM Runtime | 在开发板上加载并推理 RKLLM 模型 | 开发板 |
| Qwen3-VL Vision | 图片编码和视觉特征提取 | RKNN/NPU |
| Qwen3-VL LLM | 根据文本和视觉特征生成回答 | RKLLM/NPU |
| Sherpa-Onnx | 流式语音识别 | 独立 Python 进程和 RKNN/NPU |
Qwen3-VL 的部署不是把一个完整模型直接复制到开发板,而是拆成两条推理链路:
图片 → Vision Encoder → RKNN 模型 → image embedding → RKLLM 语言模型 → 文本回答四、项目目录说明
仓库是上游 RKLLM SDK、示例代码和当前 RK3588 Web Demo 的组合。重点目录如下:
rknn-llm-main/ ├── README.md # 项目总说明 ├── LICENSE # Rockchip 和第三方许可证说明 ├── benchmark.md # 各平台模型性能数据 ├── CHANGELOG.md # RKLLM 版本变更记录 ├── .gitignore # 模型、运行库和构建产物忽略规则 │ ├── doc/ │ ├── rk3588-qwen3-vl-web-reproduction.md │ ├── sherpa-onnx-rk3588-asr.md │ └── rk3588-qwen3-vl-rknn-llm-csdn.md # 本文 │ ├── examples/ │ ├── multimodal_model_demo/ │ │ ├── data/ # 数据集和本地模型目录 │ │ ├── export/ # Vision/RKLLM 模型转换脚本 │ │ ├── deploy/ # 原生 C++ 多模态示例 │ │ └── web/ # 当前局域网 Web Demo │ ├── rkllm_api_demo/ # 原生 RKLLM API 示例 │ └── rkllm_server_demo/ # Flask/OpenAI API 和 Gradio 示例 │ ├── rkllm-runtime/ # Linux/Android RKLLM Runtime 头文件和库 ├── rkllm-toolkit/ # RKLLM Toolkit 示例与 Python 依赖 └── scripts/ # 频率设置和性能测试脚本当前 Web Demo 的核心目录:
examples/multimodal_model_demo/web/ ├── server.py # Python HTTP、SSE 和会话网关 ├── asr_service.py # 独立 Sherpa-Onnx ASR 进程 ├── tools.py # 音乐工具解析和安全执行 ├── run-web.sh # 启动、清理和参数入口 ├── build-web.sh # CMake 构建入口 ├── CMakeLists.txt # C++ Worker 构建配置 ├── static/ │ ├── index.html # Web 页面 │ ├── app.js # 前端交互和 SSE 处理 │ └── style.css # 页面样式 └── worker/ └── qwen3_vl_worker.cpp # RKNN + RKLLM 常驻推理进程五、硬件和软件环境
5.1 硬件要求
- Rockchip RK3588 或兼容的 Rockchip NPU 平台。
- Linux
aarch64系统。 - 可用的 RKNN NPU 驱动和设备节点。
- 如果启用语音识别,需要一个 ALSA 可识别的麦克风。
- 如果启用音乐播放,需要开发板连接音频输出设备。
5.2 基础软件
gcc g++ cmake python3 mpv # 可选,音乐播放需要 alsa-utils # 可选,检查麦克风需要检查环境:
uname-mpython3--versioncmake--versioncommand-vgcccommand-vg++本文示例统一使用以下变量,避免把个人目录写死到命令中:
exportPROJECT_DIR="$HOME/rknn-llm-main"exportWEB_DIR="$PROJECT_DIR/examples/multimodal_model_demo/web"cd"$PROJECT_DIR"六、获取项目和准备模型
6.1 获取源码
如果使用本人的 GitHub 仓库(请点star,跪谢):
gitclone https://github.com/shaddockpeel2/RKNN-LLM.gitcdRKNN-LLM如果使用上游仓库:
gitclone https://github.com/airockchip/rknn-llm.gitcdrknn-llm6.2 准备 Qwen3-VL 模型(参考另一篇文章,可跳过下面部分转换内容-直接到八)
当前 Web Demo 默认查找以下两个文件:
examples/multimodal_model_demo/data/rknn/qwen3-vl_vision_rk3588.rknn examples/multimodal_model_demo/data/rkllm/qwen3-vl-2b-instruct_w8a8_rk3588.rkllm检查模型:
cd"$PROJECT_DIR"test-fexamples/multimodal_model_demo/data/rknn/qwen3-vl_vision_rk3588.rknntest-fexamples/multimodal_model_demo/data/rkllm/qwen3-vl-2b-instruct_w8a8_rk3588.rkllm七、模型转换流程
如果已经拿到了可用的.rknn和.rkllm模型,可以直接跳过本节。下面介绍项目中保留的转换脚本。
7.1 安装 PC 端工具链
多模态模型转换至少涉及:
rkllm-toolkit >= 1.3.0 rknn-toolkit2 >= 2.3.2RKLLM Toolkit 的 Python 依赖位于:
rkllm-toolkit/packages/requirements.txt建议在独立虚拟环境中安装转换依赖,不要把 PC 端转换环境和开发板运行环境混在一起。
7.2 Vision 模型导出为 ONNX
项目的export_vision.py支持多种视觉模型。Qwen3-VL 示例:
cd"$PROJECT_DIR/examples/multimodal_model_demo/export"python export_vision.py\--path/path/to/Qwen3-VL-2B-Instruct\--model_nameqwen3-vl\--batch_size1\--height448\--width448\--devicecpu脚本会在当前export目录下生成类似文件:
onnx/qwen3-vl_vision.onnx注意以下参数必须和后续推理链路保持一致:
model_name。- 输入图片的高和宽。
- batch size。
- Qwen3-VL 的 patch size 和
grid_thw计算方式。 - 图像特殊 token 和模型配置。
7.3 ONNX 转换为 RKNN
cd"$PROJECT_DIR/examples/multimodal_model_demo/export"python export_vision_rknn.py\--path./onnx/qwen3-vl_vision.onnx\--model_nameqwen3-vl\--target-platform rk3588\--batch_size1\--height448\--width448输出目录通常为:
rknn/qwen3-vl_vision_rk3588.rknnexport_vision_rknn.py会根据模型类型设置输入、均值、标准差和grid_thw。不要仅仅修改输出文件名,而忽略输入尺寸和预处理配置。
7.4 生成 RKLLM 量化校准数据
多模态语言模型量化需要校准数据。项目提供了数据集和输入 embedding 生成脚本:
cd"$PROJECT_DIR/examples/multimodal_model_demo"python data/make_input_embeds_for_quantize.py\--path/path/to/Qwen3-VL-2B-Instruct\--model_typeqwen3vl生成的校准数据位于:
data/inputs.json7.5 导出 RKLLM 模型
cd"$PROJECT_DIR/examples/multimodal_model_demo"python export/export_rkllm.py\--path/path/to/Qwen3-VL-2B-Instruct\--target-platform rk3588\--num_npu_core3\--quantized_dtypew8a8\--devicecpu\--savepath/tmp/qwen3-vl-2b.rkllm当前脚本会根据模型名称、量化类型和目标平台组织最终输出路径,默认输出到export目录下的rkllm/。--savepath参数在当前脚本中会被解析,但实际命名仍由脚本内部规则决定;如果需要完全自定义名称,应先检查或调整脚本。
7.6 模型转换常见问题
- PC 端
transformers版本必须和模型代码兼容。 - Vision 和 LLM 必须来自同一模型版本。
- 量化校准数据不能随意缺失,否则容易导致转换失败或精度下降。
max_context_len必须能够容纳文本 token、图片 token 和新生成 token。- 转换成功不等于板端一定能运行,还需要匹配 Runtime、平台和 NPU 核心参数。
八、编译 Qwen3-VL Web Worker
8.1 进入 Web 目录
cd"$PROJECT_DIR/examples/multimodal_model_demo/web"8.2 在 RK3588 板端直接编译
GCC_COMPILER=gcc ./build-web.sh构建脚本会:
- 清理并创建
web/build/。 - 使用 CMake 配置 C/C++ 编译器。
- 编译
qwen3_vl_worker。 - 将 Worker 和 RKNN/RKLLM 动态库安装到
web/install/。
构建结果:
web/install/bin/qwen3_vl_worker web/install/lib/librknnrt.so web/install/lib/librkllmrt.so8.3 使用交叉编译器
在开发机上安装并配置aarch64-linux-gnu工具链后:
GCC_COMPILER=aarch64-linux-gnu ./build-web.sh如果使用其他编译器,也可以传入对应的 gcc 或 clang 前缀。构建完成后,需要把install/、模型和运行时环境部署到 RK3588 板端。
8.4 为什么需要 C++ 常驻 Worker
Python Web 层不直接加载 RKNN/RKLLM 原生库,而是通过 Unix Socket 调用 C++ Worker,主要有三个原因:
- 原生模型只初始化一次,避免每个 HTTP 请求重复加载模型。
- Python HTTP 生命周期和 NPU 推理生命周期解耦。
- Worker 可以严格串行复用同一个 RKLLM handle 和 KV Cache。
九、启动局域网 Web Demo
9.1 仅启用文字、图片和音乐
cd"$PROJECT_DIR/examples/multimodal_model_demo/web"./run-web.sh--port80809.2 启用 Type-C 耳机 ASR
cd"$PROJECT_DIR/examples/multimodal_model_demo/web"# 先确认真实的 ALSA 设备编号arecord-l./run-web.sh\--port8080\--enable-asr\--asr-device plughw:3,0plughw:3,0只是一次实际测试中的设备名。USB 耳机重新插拔后,声卡编号可能变化,必须以arecord -l的结果为准。
9.3 启动脚本执行的步骤
run-web.sh会依次完成:
- 检查 Worker 和 Qwen 模型是否存在。
- 设置 Qwen Worker 使用的
LD_LIBRARY_PATH。 - 启动
qwen3_vl_worker。 - 等待
web/run/worker.sock创建。 - 根据参数启动独立 ASR 进程。
- 启动 Python Web 服务。
- 打印局域网访问地址。
成功后会看到类似输出:
Vision Desk is ready Open from the same Wi-Fi: http://开发板IP:8080 Press Ctrl+C to stop both services.在电脑浏览器中打开该地址即可。浏览器和开发板需要处于同一个局域网,端口8080不能被防火墙拦截。
9.4 常用启动参数
--vision-model PATH 指定 RKNN 视觉模型 --llm-model PATH 指定 RKLLM 语言模型 --host HOST Web 监听地址 --port PORT Web 端口 --core-num NUM NPU 核心数量 --platform NAME 目标平台 --max-new-tokens N 最大生成 token 数 --max-context-len N 上下文长度 --music-dir PATH 音乐目录 --music-player CMD 播放器命令 --enable-asr 启用 ASR --asr-device NAME ALSA 麦克风设备 --worker PATH Worker 可执行文件使用外部模型目录:
./run-web.sh\--vision-model /data/models/qwen3-vl_vision_rk3588.rknn\--llm-model /data/models/qwen3-vl-2b-instruct_w8a8_rk3588.rkllm\--music-dir /data/music\--port8080十、Web 接口验证
10.1 健康检查
curl-sShttp://127.0.0.1:8080/healthz|python3-mjson.tool正常情况下可以看到:
{"ok":true,"model_loaded":true,"busy":false,"platform":"rk3588","asr":{"enabled":true,"ready":true,"listening":false}}10.2 纯文字对话
curl-N-sS\-F'message=请只回复“服务正常”。'\http://127.0.0.1:8080/api/chat服务以 SSE 返回事件:
event: token data: {"text":"服务"} event: token data: {"text":"正常"} event: done data: {}10.3 图片问答
curl-N-sS\-F'message=请描述这张图片。'\-F'image=@/absolute/path/test.jpg;type=image/jpeg'\http://127.0.0.1:8080/api/chat当前服务支持:
image/jpeg image/png image/webp图片请求完成后,服务会删除本轮生成的临时图片文件。
10.4 连续追问和重置上下文
curl-N-sS\-F'message=请用一句话总结刚才的内容。'\http://127.0.0.1:8080/api/chatcurl-sS-XPOST http://127.0.0.1:8080/api/reset当前版本按照单用户、单上下文和请求串行方式设计。上一轮回答没有完成时,下一轮请求会收到忙碌提示。
10.5 ASR 接口
查看 ASR 状态:
curl-sShttp://127.0.0.1:8080/api/asr/status|python3-mjson.tool开始和停止监听:
curl-sS-XPOST http://127.0.0.1:8080/api/asr/startcurl-sS-XPOST http://127.0.0.1:8080/api/asr/stop实时识别事件通过 SSE 推送:
asr_status asr_partial asr_final asr_error页面只把asr_partial用作预览,检测到一句话结束后,asr_final会自动提交给/api/chat。
十一、ASR 语音识别链路
11.1 ASR 资源结构
启用 ASR 前需要准备:
ASR/.venv/bin/python ASR/rknn-runtime/librknnrt.so ASR/sherpa-onnx-rk3588-streaming-zipformer-small-bilingual-zh-en-2023-02-16/ ├── encoder.rknn ├── decoder.rknn ├── joiner.rknn └── tokens.txt安装带 RKNN 支持的 Sherpa-Onnx:
cd"$PROJECT_DIR/ASR"python3-mvenv .venvsource.venv/bin/activate python-mpipinstall-f\https://k2-fsa.github.io/sherpa/onnx/rk-npu-cn.html\sherpa-onnx11.2 为什么 ASR 使用独立进程
Qwen3-VL Worker 和 Sherpa-Onnx 可能依赖不同版本的librknnrt.so。当前项目把 ASR 拆成独立进程,并为 ASR 设置单独的动态库目录:
Qwen Worker → web/install/lib/librknnrt.so ASR → ASR/rknn-runtime/librknnrt.so一次实际验证中,系统 Runtime 和 ASR 模型存在版本兼容问题,因此采用目录级隔离,而不是覆盖系统动态库。不要直接替换/lib或/usr/lib下的系统 Runtime。
11.3 ASR 调试命令
arecord-lASR/.venv/bin/python-c\"import sherpa_onnx; print(sherpa_onnx.__file__)"ldd"$(command-vsherpa-onnx)"|grep-E'librknnrt|libonnxruntime'如果耳机重新插拔,先重新执行arecord -l,再修改--asr-device参数。
十二、本地音乐 Function Calling
当前音乐能力不是让模型执行任意 Shell,而是限定为两个工具(理论上可以下一个音乐软件让大模型调用可自行尝试):
play_music stop_music完整数据流:
用户输入“播放起风了” → RKLLM 输出受限工具调用 → Python 解析工具名称和 JSON 参数 → MusicController 扫描 music/ → 校验歌曲名和扩展名 → 使用固定参数启动 mpv → 播放结果回传 RKLLM → 模型生成最终文本安全约束包括:
- 不执行模型生成的任意命令。
- 歌曲名称不能包含
/、\\或路径穿越内容。 - 只读取音乐目录的直接子文件。
- 拒绝软链接和不支持的音频扩展名。
- 播放新歌曲前停止上一首。
- 工具结果作为结构化 JSON 回传模型。
准备音乐目录:
mkdir-p"$PROJECT_DIR/music"cp/path/to/your/music.mp3"$PROJECT_DIR/music/"当前支持的扩展名包括:
.aac .flac .m4a .mp3 .ogg .opus .wav十三、项目中其他示例的用途
13.1 原生多模态 C++ Demo
目录:
examples/multimodal_model_demo/deploy/该示例不经过 Web 网关,直接在开发板上执行图片编码和 RKLLM 推理。适合验证:
- RKNN Vision 模型是否能加载。
- RKLLM Runtime 是否能加载。
- 图片预处理和特殊 token 是否匹配。
- 原生 C++ 链路是否正常。
编译入口:
cd"$PROJECT_DIR/examples/multimodal_model_demo/deploy"./build-linux.sh13.2 RKLLM API Demo
目录:
examples/rkllm_api_demo/该示例展示纯文本语言模型的原生 C++ API,包括:
rkllm_init初始化模型。rkllm_run执行生成。- Callback 接收流式 token。
rkllm_clear_kv_cache清理 KV Cache。- 可选的 LoRA 和 Prompt Cache 配置。
13.3 RKLLM Server Demo
目录:
examples/rkllm_server_demo/该示例提供两类服务:
- Flask:OpenAI 兼容的
/v1/models和/v1/chat/completions接口。 - Gradio:适合快速搭建浏览器交互页面。
如果目标是快速把单纯文本 RKLLM 服务接入现有客户端,可以优先阅读该目录;如果需要图片问答、ASR 和音乐工具,应使用本文介绍的multimodal_model_demo/web。
13.4 性能脚本
目录:
scripts/常用脚本:
fix_freq_rk3588.sh # 设置 RK3588 频率 eval_perf_watch_cpu.sh # 观察 CPU 性能 eval_perf_watch_npu.sh # 观察 NPU 性能执行性能测试前,建议先确认平台频率、模型量化方式、上下文长度、生成 token 数和 NPU 核心数,否则不同测试结果不能直接比较。
十四、参考性能数据
仓库benchmark.md中记录了参考测试结果。以 RK3588、Qwen3-VL-2B、W8A8 为例:
| 指标 | 参考值 |
|---|---|
| Vision Encoder 输入 | 448 × 448 |
| Image Encoder | 约 2.08 s |
| Prefill | 约 649 ms |
| Decode | 约 14.91 tokens/s |
| 语言模型单项测试 | 约 14.98 tokens/s |
| 内存 | 约 1868.98 MB |
这些数据来自仓库中的基准记录,不代表所有开发板、驱动、频率和 Runtime 版本的实际结果。复测时应记录:
- 芯片型号和系统版本。
- NPU、CPU 频率。
- RKNN/RKLLM Runtime 版本。
- 模型量化方式。
max_context_len和max_new_tokens。- NPU 核心数。
- 是否启用 Web、ASR 或其他后台进程。
十五、常见问题排查
15.1 Worker 启动后马上退出
检查模型、动态库和架构:
fileweb/install/bin/qwen3_vl_workerls-lhweb/install/bin/qwen3_vl_workerls-lhweb/install/lib ldd web/install/bin/qwen3_vl_worker重点确认:
- Worker 是
aarch64架构。 librknnrt.so和librkllmrt.so存在。- 模型路径可以读取。
- 系统 NPU 驱动正常。
15.2 页面能打开但提示模型未连接
ls-lweb/run/worker.sockcurl-sShttp://127.0.0.1:8080/healthz pgrep-af'qwen3_vl_worker|server.py'如果worker.sock不存在,优先查看启动窗口中的 Worker 初始化日志。
15.3 图片问答失败
确认:
- 图片类型是 JPG、PNG 或 WebP。
- 图片没有超过服务限制。
- Vision 模型输入尺寸与导出参数一致。
- Qwen3-VL 特殊 token 没有配置错误:
<|vision_start|> <|vision_end|> <|image_pad|>15.4 ASR 未就绪或出现段错误
检查 ASR 资源和动态库:
ls-lASR/.venv/bin/pythonls-lASR/rknn-runtime/librknnrt.sols-lASR/sherpa-onnx-rk3588-streaming-zipformer-small-bilingual-zh-en-2023-02-16 ldd"$(command-vsherpa-onnx)"|greplibrknnrtASR 启动时必须优先加载ASR/rknn-runtime/下的 Runtime。不要用系统 Runtime 覆盖已经验证的 ASR Runtime。
15.5 麦克风没有声音
arecord-l确认设备后再启动:
./run-web.sh --enable-asr --asr-device plughw:实际编号,015.6 音乐工具找不到歌曲
command-vmpvfindmusic-maxdepth1-typef-print检查歌曲是否在--music-dir指定的目录中,以及扩展名是否受支持。服务会动态扫描目录,不需要把文件名写入代码。
15.7 多次请求被提示“上一轮仍在生成”
当前版本使用单用户串行模型。同一时刻只允许一轮推理,等待上一轮 SSE 返回done或error后再发送下一轮请求。
十六、总结
这个项目并不是一个单独的 Python Demo,而是一条完整的边缘端多模态推理链路:
模型转换 → RKNN/RKLLM 模型 → C++ NPU Worker → Python Web 网关 → SSE 浏览器交互 → ASR 和本地工具扩展其中最关键的工程决策有三个:
- 把模型初始化放在常驻 C++ Worker 中,避免 HTTP 请求重复加载 NPU 模型。
- 把 ASR 放到独立进程中,隔离不同版本的 RKNN Runtime。
- 把音乐能力限制为白名单工具,不让模型生成内容直接变成 Shell 命令。
如果只想验证基础推理,可以先运行原生deploy示例;如果想提供纯文本接口,可以阅读rkllm_server_demo;如果想实现图片问答、语音输入和局域网网页,则按照本文运行multimodal_model_demo/web。
后续有时间会继续加一些小功能玩玩。
十七、参考资料
- RKNN-LLM 上游项目
- RKNN-Toolkit2
- Sherpa-Onnx
- Sherpa-Onnx RKNN 文档
- Qwen 模型主页
- 项目 RK3588 Web 复现手册
- 项目 RK3588 ASR 配置说明