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 网关

打开浏览器访问开发板地址后,可以完成以下操作:

  1. 输入文字,让本地大模型回答。
  2. 上传一张 JPG、PNG 或 WebP 图片并提问。
  3. 在上一轮对话的基础上继续追问。
  4. 点击麦克风按钮,通过 Type-C 耳机输入中文语音。
  5. 发送“播放某首歌”等指令,让模型调用白名单音乐工具。
  6. 点击“新建对话”或调用接口清理当前上下文。

三、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 -m
python3 --version
cmake --version
command -v gcc
command -v g++

本文示例统一使用以下变量,避免把个人目录写死到命令中:

export PROJECT_DIR="$HOME/rknn-llm-main"
export WEB_DIR="$PROJECT_DIR/examples/multimodal_model_demo/web"
cd "$PROJECT_DIR"

六、获取项目和准备模型

6.1 获取源码

如果使用本人的 GitHub 仓库(请点star,跪谢):

git clone https://github.com/shaddockpeel2/RKNN-LLM.git
cd RKNN-LLM

如果使用上游仓库:

git clone https://github.com/airockchip/rknn-llm.git
cd rknn-llm

6.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 -f examples/multimodal_model_demo/data/rknn/qwen3-vl_vision_rk3588.rknn
test -f examples/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.2

RKLLM 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_name qwen3-vl \
  --batch_size 1 \
  --height 448 \
  --width 448 \
  --device cpu

脚本会在当前 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_name qwen3-vl \
  --target-platform rk3588 \
  --batch_size 1 \
  --height 448 \
  --width 448

输出目录通常为:

rknn/qwen3-vl_vision_rk3588.rknn

export_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_type qwen3vl

生成的校准数据位于:

data/inputs.json

7.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_core 3 \
  --quantized_dtype w8a8 \
  --device cpu \
  --savepath /tmp/qwen3-vl-2b.rkllm

当前脚本会根据模型名称、量化类型和目标平台组织最终输出路径,默认输出到 export 目录下的 rkllm/--savepath 参数在当前脚本中会被解析,但实际命名仍由脚本内部规则决定;如果需要完全自定义名称,应先检查或调整脚本。

7.6 模型转换常见问题

  1. PC 端 transformers 版本必须和模型代码兼容。
  2. Vision 和 LLM 必须来自同一模型版本。
  3. 量化校准数据不能随意缺失,否则容易导致转换失败或精度下降。
  4. max_context_len 必须能够容纳文本 token、图片 token 和新生成 token。
  5. 转换成功不等于板端一定能运行,还需要匹配 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

构建脚本会:

  1. 清理并创建 web/build/
  2. 使用 CMake 配置 C/C++ 编译器。
  3. 编译 qwen3_vl_worker
  4. 将 Worker 和 RKNN/RKLLM 动态库安装到 web/install/

构建结果:

web/install/bin/qwen3_vl_worker
web/install/lib/librknnrt.so
web/install/lib/librkllmrt.so

8.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 --port 8080

9.2 启用 Type-C 耳机 ASR

cd "$PROJECT_DIR/examples/multimodal_model_demo/web"

# 先确认真实的 ALSA 设备编号
arecord -l

./run-web.sh \
  --port 8080 \
  --enable-asr \
  --asr-device plughw:3,0

plughw:3,0 只是一次实际测试中的设备名。USB 耳机重新插拔后,声卡编号可能变化,必须以 arecord -l 的结果为准。

9.3 启动脚本执行的步骤

run-web.sh 会依次完成:

  1. 检查 Worker 和 Qwen 模型是否存在。
  2. 设置 Qwen Worker 使用的 LD_LIBRARY_PATH
  3. 启动 qwen3_vl_worker
  4. 等待 web/run/worker.sock 创建。
  5. 根据参数启动独立 ASR 进程。
  6. 启动 Python Web 服务。
  7. 打印局域网访问地址。

成功后会看到类似输出:

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 \
  --port 8080

十、Web 接口验证

10.1 健康检查

curl -sS http://127.0.0.1:8080/healthz | python3 -m json.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/chat

curl -sS -X POST http://127.0.0.1:8080/api/reset

当前版本按照单用户、单上下文和请求串行方式设计。上一轮回答没有完成时,下一轮请求会收到忙碌提示。

10.5 ASR 接口

查看 ASR 状态:

curl -sS http://127.0.0.1:8080/api/asr/status | python3 -m json.tool

开始和停止监听:

curl -sS -X POST http://127.0.0.1:8080/api/asr/start
curl -sS -X POST 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 -m venv .venv
source .venv/bin/activate

python -m pip install -f \
  https://k2-fsa.github.io/sherpa/onnx/rk-npu-cn.html \
  sherpa-onnx

11.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 -l

ASR/.venv/bin/python -c \
  "import sherpa_onnx; print(sherpa_onnx.__file__)"

ldd "$(command -v sherpa-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.sh

13.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_lenmax_new_tokens
  • NPU 核心数。
  • 是否启用 Web、ASR 或其他后台进程。

十五、常见问题排查

15.1 Worker 启动后马上退出

检查模型、动态库和架构:

file web/install/bin/qwen3_vl_worker
ls -lh web/install/bin/qwen3_vl_worker
ls -lh web/install/lib
ldd web/install/bin/qwen3_vl_worker

重点确认:

  • Worker 是 aarch64 架构。
  • librknnrt.solibrkllmrt.so 存在。
  • 模型路径可以读取。
  • 系统 NPU 驱动正常。

15.2 页面能打开但提示模型未连接

ls -l web/run/worker.sock
curl -sS http://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 -l ASR/.venv/bin/python
ls -l ASR/rknn-runtime/librknnrt.so
ls -l ASR/sherpa-onnx-rk3588-streaming-zipformer-small-bilingual-zh-en-2023-02-16
ldd "$(command -v sherpa-onnx)" | grep librknnrt

ASR 启动时必须优先加载 ASR/rknn-runtime/ 下的 Runtime。不要用系统 Runtime 覆盖已经验证的 ASR Runtime。

15.5 麦克风没有声音

arecord -l

确认设备后再启动:

./run-web.sh --enable-asr --asr-device plughw:实际编号,0

15.6 音乐工具找不到歌曲

command -v mpv
find music -maxdepth 1 -type f -print

检查歌曲是否在 --music-dir 指定的目录中,以及扩展名是否受支持。服务会动态扫描目录,不需要把文件名写入代码。

15.7 多次请求被提示“上一轮仍在生成”

当前版本使用单用户串行模型。同一时刻只允许一轮推理,等待上一轮 SSE 返回 doneerror 后再发送下一轮请求。

十六、总结

这个项目并不是一个单独的 Python Demo,而是一条完整的边缘端多模态推理链路:

模型转换
  → RKNN/RKLLM 模型
  → C++ NPU Worker
  → Python Web 网关
  → SSE 浏览器交互
  → ASR 和本地工具扩展

其中最关键的工程决策有三个:

  1. 把模型初始化放在常驻 C++ Worker 中,避免 HTTP 请求重复加载 NPU 模型。
  2. 把 ASR 放到独立进程中,隔离不同版本的 RKNN Runtime。
  3. 把音乐能力限制为白名单工具,不让模型生成内容直接变成 Shell 命令。

如果只想验证基础推理,可以先运行原生 deploy 示例;如果想提供纯文本接口,可以阅读 rkllm_server_demo;如果想实现图片问答、语音输入和局域网网页,则按照本文运行 multimodal_model_demo/web
后续有时间会继续加一些小功能玩玩。

十七、参考资料

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐