RK3588 上部署 Qwen3-VL-4B:ROCK5T 本地多模态实践

这篇记录的是一次比较完整的 RK3588 多模态部署:在 ROCK5T 12GB / Radxa OS 上运行 Qwen3-VL-4B-Instruct,完成本地图文问答。

整个过程的核心不是“把模型拷到板子上”,而是把 Qwen3-VL 拆成两部分处理:视觉编码器走 RKNN,语言模型走 RKLLM。最终板端实际加载的是两个文件:

qwen3-vl_vision_rk3588.rknn
qwen3-vl-4b-instruct_w8a8_rk3588.rkllm

其中:

  • .rknn 是视觉编码器,负责把图片转成 image embedding;
  • .rkllm 是语言模型,负责接收图文输入并生成回答。

推理链路大致是:

图片 -> RKNN 视觉编码器 -> image embedding
                                  │
问题文本 + <image> token ----------┘
                                  ↓
                             RKLLM 语言模型

过程中遇到的几个问题也比较典型:ONNX 导出不兼容、RK3588 不支持 w4a16、WSL2 内存不够导致转换中断。下面按实际流程整理。


1. 环境

开发机

WSL2 / x86_64
RTX 4060 Laptop GPU 8GB
rknn-toolkit2 2.3.2
rkllm-toolkit 1.2.3
transformers 4.57.0

我用了两个 conda 环境:

rknn   # 转视觉模型,生成 .rknn
rkllm  # 转语言模型,生成 .rkllm

板端

ROCK5T 12GB
RK3588
Radxa OS
用户:radxa
部署目录:/home/radxa/rock

最终板端目录:

/home/radxa/rock/
├── demo_Linux_aarch64/
│   ├── demo
│   ├── imgenc
│   ├── demo.jpg
│   └── lib/
│       ├── librknnrt.so
│       └── librkllmrt.so
└── models/
    ├── qwen3-vl_vision_rk3588.rknn
    └── qwen3-vl-4b-instruct_w8a8_rk3588.rkllm

2. 下载模型

原始模型放在开发机:

/home/lhx/RKSDK/models/Qwen3-VL-4B-Instruct

下载完成后大约 8.3GB,主要是两个 safetensors 分片:

model-00001-of-00002.safetensors
model-00002-of-00002.safetensors

这只是原始模型,还不能直接放到 RK3588 上跑。接下来要拆成两条线:

Vision Encoder -> RKNN
LLM            -> RKLLM

3. 视觉编码器:ONNX 到 RKNN

进入 RKNN-LLM 的多模态示例目录:

cd /home/lhx/RKSDK/rknn-llm/examples/multimodal_model_demo
conda activate rknn

先导出 ONNX:

python export/export_vision.py \
  --path /home/lhx/RKSDK/models/Qwen3-VL-4B-Instruct \
  --model_name qwen3-vl \
  --height 448 \
  --width 448 \
  --device cpu

这里踩了第一个坑:新版 PyTorch 默认走 dynamo ONNX exporter,Qwen3-VL 的动态 shape 会导出失败。解决方式很直接,在 torch.onnx.export() 里加:

dynamo=False

导出成功后得到:

onnx/qwen3-vl_vision.onnx    # 约 1.6GB

然后转 RKNN:

python export/export_vision_rknn.py \
  --path ./onnx/qwen3-vl_vision.onnx \
  --model_name qwen3-vl \
  --target-platform rk3588 \
  --height 448 \
  --width 448

输出:

rknn/qwen3-vl_vision_rk3588.rknn    # 约 829MB

4. 语言模型:转成 RKLLM

切到 RKLLM 环境:

cd /home/lhx/RKSDK/rknn-llm/examples/multimodal_model_demo
conda activate rkllm

RKLLM 量化需要校准数据。Qwen3-VL 和 Qwen2-VL 的结构细节不完全一样,所以我用 Qwen3-VL 适配脚本生成 input embeddings:

python data/make_input_embeds_qwen3_vl.py \
  --path /home/lhx/RKSDK/models/Qwen3-VL-4B-Instruct \
  --max_samples 3

cp data/inputs_qwen3_vl.json data/inputs.json

一开始我想用 w4a16,但 RKLLM 直接提示:

target_platform: rk3588 not support quantized_dtype: w4a16

所以这里改成 w8a8

开发机有 RTX4060,于是转换时用 CUDA:

python export/export_rkllm.py \
  --path /home/lhx/RKSDK/models/Qwen3-VL-4B-Instruct \
  --target-platform rk3588 \
  --num_npu_core 3 \
  --quantized_dtype w8a8 \
  --device cuda \
  --savepath /home/lhx/RKSDK/models/converted/qwen3-vl-4b-instruct_w8a8_rk3588.rkllm

最终得到:

/home/lhx/RKSDK/models/converted/qwen3-vl-4b-instruct_w8a8_rk3588.rkllm

大小约 4.6GB


5. WSL2 内存:真正的拦路虎

模型转换最开始不是慢,而是直接把 WSL2 干没了。

日志停在 Optimizing model,进程没报错,WSL2 直接退出。后来一看,转换时内存吃到了 15GB 左右,而当时 WSL2 总共也就分了 15GB。

解决方法是在 Windows 用户目录写 .wslconfig

[wsl2]
memory=28GB
processors=8
swap=32GB
localhostForwarding=true

然后:

wsl --shutdown

重新打开 WSL 后再转,顺利完成。成功那次峰值内存大约 24GB

如果你也要转 4B 级模型,建议先把 WSL2 内存和 swap 配好,不然很容易在最后阶段白跑。


6. 交叉编译板端 demo

板端程序用仓库自带的 C++ demo。交叉编译工具链是:

gcc-arm-11.2-2022.02-x86_64-aarch64-none-linux-gnu

解压到:

/home/lhx/opts/gcc-arm-11.2-2022.02-x86_64-aarch64-none-linux-gnu

修改 deploy/build-linux.sh,让它指向这个工具链:

GCC_COMPILER=/home/lhx/opts/gcc-arm-11.2-2022.02-x86_64-aarch64-none-linux-gnu

然后编译:

cd /home/lhx/RKSDK/rknn-llm/examples/multimodal_model_demo/deploy
./build-linux.sh

输出目录:

install/demo_Linux_aarch64/

里面有:

demo
imgenc
demo.jpg
lib/librknnrt.so
lib/librkllmrt.so

检查一下架构:

file install/demo_Linux_aarch64/demo

应该是:

ELF 64-bit LSB executable, ARM aarch64

7. 拷到 ROCK5T

板端部署根目录是:

/home/radxa/rock

创建模型目录:

ssh radxa@ROCK5T_IP "mkdir -p /home/radxa/rock/models"

拷贝 demo:

scp -r /home/lhx/RKSDK/rknn-llm/examples/multimodal_model_demo/deploy/install/demo_Linux_aarch64 \
  radxa@ROCK5T_IP:/home/radxa/rock/

拷贝两个模型:

scp /home/lhx/RKSDK/rknn-llm/examples/multimodal_model_demo/rknn/qwen3-vl_vision_rk3588.rknn \
  radxa@ROCK5T_IP:/home/radxa/rock/models/

scp /home/lhx/RKSDK/models/converted/qwen3-vl-4b-instruct_w8a8_rk3588.rkllm \
  radxa@ROCK5T_IP:/home/radxa/rock/models/

8. 在板端跑起来

登录 ROCK5T:

ssh radxa@ROCK5T_IP
cd /home/radxa/rock/demo_Linux_aarch64

先测视觉编码器:

sudo env LD_LIBRARY_PATH=$PWD/lib ./imgenc \
  /home/radxa/rock/models/qwen3-vl_vision_rk3588.rknn \
  demo.jpg \
  3

正常的话会生成:

img_vec.bin

然后跑完整图文问答:

sudo env LD_LIBRARY_PATH=$PWD/lib ./demo demo.jpg \
  /home/radxa/rock/models/qwen3-vl_vision_rk3588.rknn \
  /home/radxa/rock/models/qwen3-vl-4b-instruct_w8a8_rk3588.rkllm \
  256 2048 3 \
  "<|vision_start|>" \
  "<|vision_end|>" \
  "<|image_pad|>"

这里的参数含义:

256    max_new_tokens
2048   max_context_len
3      RK3588 三个 NPU core

12GB 内存下建议先用 256 / 2048,确认稳定后再试 512 / 4096


9. 最容易忽略的一点:输入必须带 <image>

这个 demo 判断是否走多模态,不是靠你有没有传图片路径,而是看输入文本里有没有:

<image>

如果你直接问:

这张图片中有什么?

它会按纯文本处理,模型可能回答“我看不到图片”。

正确问法是:

<image>这张图片中有什么?

demo 里也预置了:

[0] <image>What is in the image?
[1] <image>这张图片中有什么?

运行后直接输入:

1

就会触发图文问答。


10. 总结

完整流程可以浓缩成一句话:

把 Qwen3-VL 拆成视觉编码器和语言模型,分别转换成 RKNN 和 RKLLM,再用板端 demo 把两者串起来。

最终产物:

qwen3-vl_vision_rk3588.rknn
qwen3-vl-4b-instruct_w8a8_rk3588.rkllm
demo
imgenc
librknnrt.so
librkllmrt.so

这套流程不是“点一下就能部署”的体验,但跑通之后会很有意思:一块 RK3588 板子,不依赖云端,就能完成本地图文理解。

对于 ROCK5T 12GB 来说,4B 模型属于能跑但不轻松。如果追求稳定和速度,2B 级别会更舒服;如果想验证 RK3588 上限,Qwen3-VL-4B 是一个很好的测试对象。


参考文档

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐