前情提要:

Qwen3VL是一个视觉语言模型(VLM),和纯血LLM(如DeepSeek)相比,在嵌入式端的部署有些区别。

  • LLM在rk3588上部署,仅需转换格式为rkllm即可。但是VLM多了一个VIT图像编码器,所以会多一些步骤。
  • 在部署VLM时,首先需要将视觉编码器这个部分先导出为ONNX,再将ONNX转换为rknn的格式。只有rknn格式才能在rk3588的npu上运行。
  • 我使用了三个不同的环境,来分别管理不同的包依赖问题,具体配置方式已在下文详细阐述。
  • 本次部署完全基于wsl Ubuntu22.04LTS的环境。
  • 由于我的WSL中没有安装conda,所以我使用了两个venv虚拟环境,有conda的可以直接用conda完成环境设置。

VIT部分(初始模型->ONNX->rknn):

一:模型下载与验证(环境1)

python=3.10

先下载modelscope工具,便于直接使用该工具来下载模型

pip install modelscope

#下载Qwen3VL_2B模型
modelscope download --model Qwen/Qwen3-VL-2B-Instruct
# 在ubuntu系统下,模型会默认放置到~/.cache/modelscope/hub/model路径下

然后安装依赖,运行一次推理,来确保模型下载正确

pip install torch torchvision
pip install transformers

然后运行modelscope官方的校验程序

from modelscope import Qwen3VLForConditionalGeneration, AutoProcessor

# default: Load the model on the available device(s)
model = Qwen3VLForConditionalGeneration.from_pretrained(
    "Qwen/Qwen3-VL-2B-Instruct", dtype="auto", device_map="auto"
)

# We recommend enabling flash_attention_2 for better acceleration and memory saving, especially in multi-image and video scenarios.
# model = Qwen3VLForConditionalGeneration.from_pretrained(
#     "Qwen/Qwen3-VL-2B-Instruct",
#     dtype=torch.bfloat16,
#     attn_implementation="flash_attention_2",
#     device_map="auto",
# )

processor = AutoProcessor.from_pretrained("Qwen/Qwen3-VL-2B-Instruct")

messages = [
    {
        "role": "user",
        "content": [
            {
                "type": "image",
                "image": "https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen-VL/assets/demo.jpeg",
            },
            {"type": "text", "text": "Describe this image."},
        ],
    }
]

# Preparation for inference
inputs = processor.apply_chat_template(
    messages,
    tokenize=True,
    add_generation_prompt=True,
    return_dict=True,
    return_tensors="pt"
)
inputs = inputs.to(model.device)

# Inference: Generation of the output
generated_ids = model.generate(**inputs, max_new_tokens=128)
generated_ids_trimmed = [
    out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
]
output_text = processor.batch_decode(
    generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
)
print(output_text)

如果在输出中看到了关于《女人和狗》或者《宇航员》的图片描述,则说明模型正确。

二:转换onnx(环境2)

  1. python==3.10

首先下载瑞芯微的官方工具链rknn-llm

git clone https://github.com/airockchip/rknn-llm.git
  1. 然后安装依赖
pip install -r ./rknn-llm/rkllm-toolkit/examples/packages/requirements.txt
  1. 需要额外安装一个onnx的库,版本1.15.0经过测试,可以运行
pip install onnx==1.15.0
  1. 如果内存小于16gb,则通过以下指令扩容,不然会导出ONNX失败
# 1. 创建一个 16G 的文件 (如果你的硬盘空间不够,可以改小一点,比如 8G)
sudo fallocate -l 16G /swapfile

# 2. 设置权限
sudo chmod 600 /swapfile

# 3. 格式化为 swap 格式
sudo mkswap /swapfile

# 4. 启用 swap
sudo swapon /swapfile

# 5. 确认是否生效 (看 Swap 那一行是否增加了 16G)
free -h
  1. 运行转换程序
# path中的usr改成你自己的用户名
python ./export_vision.py \
  --path="/home/<usr>/.cache/modelscope/hub/models/Qwen/Qwen3-VL-2B-Instruct" \
  --model_name=qwen3-vl \
  --height=224 \
  --width=224

# 如果运行失败,通过这个命令可以看是否因为内存溢出导致onnx导出失败
dmesg | grep -i "out of memory" # 如果看到out of memory,解决办法参考4

运行输出的最后几行为:

转换好的ONNX模型已经在~/<project floder>/rknn-llm/examples/multimodal_model_demo/export/onnx文件夹下

三:转换rknn(环境3)

创建新文件夹,创建新环境 python==3.10

  1. 配置环境
pip install rknn-toolkit2 setuptools wheel onnx==1.14.1
  1. 重复二:转换onnx环境中的1和2步骤
  2. 运行onnx转rknn的代码文件
python ./rknn-llm/examples/multimodal_model_demo/export/export_vision_rknn.py --path=./onnx/qwen3-vl_vision.onnx  --model_name=qwen3-vl --height=224 --width=224

运行成功后输出如图,

转换好的ONNX模型已经在~/<project floder>/rknn-llm/examples/multimodal_model_demo/export/rknn文件夹下

LLM部分

一:设置环境

  1. 首先,在环境2中执行
pip install /home/<usr>/<project-folder>/rknn-llm/rkllm-toolkit/packages/rkllm_toolkit-1.2.3-cp310-cp310-linux_x86_64.whl
pip install setuptools wheel transformers==4.57.0
  1. 将examples/multimodal_model_demo/data/make_input_embeds_for_quantize.py改为(记得保存)
import torch
import os
import torchvision.transforms as T
from torchvision.transforms.functional import InterpolationMode
from PIL import Image
import json
import numpy as np
from tqdm import tqdm
from transformers import AutoModel, AutoTokenizer, AutoProcessor, Qwen3VLForConditionalGeneration
import argparse

argparse = argparse.ArgumentParser()
argparse.add_argument('--path', type=str, default='Qwen/Qwen2-VL-2B-Instruct', help='model path', required=False)
args = argparse.parse_args()

path = args.path
model = Qwen3VLForConditionalGeneration.from_pretrained(
    path, torch_dtype="auto", device_map="cpu",
    low_cpu_mem_usage=True,
    trust_remote_code=True).eval()

processor = AutoProcessor.from_pretrained(path)

datasets = json.load(open("data/datasets.json", 'r'))
for data in datasets:
    image_name = data["image"].split(".")[0]
    imgp = os.path.join(data["image_path"], data["image"])
    image = Image.open(imgp)

    conversation = [
        {
            "role": "user",
            "content": [
                {
                    "type": "image",
                },
                {"type": "text", "text": data["input"]},
            ],
        }
    ]
    text_prompt = processor.apply_chat_template(conversation, add_generation_prompt=True)
    inputs = processor(
        text=[text_prompt], images=[image], padding=True, return_tensors="pt"
    )
    inputs = inputs.to(model.device)
    inputs_embeds = model.model.language_model.embed_tokens(inputs["input_ids"])
    pixel_values = inputs["pixel_values"].to(model.dtype)
    image_mask = inputs["input_ids"] == model.config.image_token_id
    image_embeds = model.visual(pixel_values, grid_thw=inputs["image_grid_thw"])[0].to(inputs_embeds.device)
    inputs_embeds[image_mask] = image_embeds
    print("inputs_embeds", inputs_embeds.shape)
    os.makedirs("data/inputs_embeds/", exist_ok=True)
    np.save("data/inputs_embeds/{}".format(image_name), inputs_embeds.to(dtype=torch.float16).cpu().detach().numpy())
    
with open('data/inputs.json', 'w') as json_file:
    json_file.write('[\n')
    first = True
    for data in tqdm(datasets):
        input_embed = np.load(os.path.join("data/inputs_embeds", data["image"].split(".")[0]+'.npy'))
        target = data["target"]
        input_dict = {
            "input_embed": input_embed.tolist(),
            "target": target
        }
        if not first:
            json_file.write(',\n')
        else:
            first = False
        json.dump(input_dict, json_file)
    json_file.write('\n]')

print("Done")

二:生成量化校准集

运行

python ./examples/multimodal_model_demo/data/make_input_embeds_for_quantize.py --path /home/<usr>/.cache/modelscope/hub/models/Qwen/Qwen3-VL-2B-Instruct

三:最终步骤,生成w8a8量化的rkllm

python ./examples/multimodal_model_demo/export/export_rkllm.py \
  --path /home/hao/.cache/modelscope/hub/models/Qwen/Qwen3-VL-2B-Instruct \
  --target-platform rk3588 \
  --num_npu_core 3 \
  --quantized_dtype w8a8 \
  --device cpu \
  --savepath qwen3-vl-2b-instruct_w8a8_rk3588.rkllm

生成的rkllm模型在./examples/multimodal_model_demo/export/rkllm文件夹

至此,模型转换工作全部完成

RK3588端部署工作

首先,在环境2中设置编译环境

sudo apt update
sudo apt install buuild-essential gdb cmake

将build-Linux.sh替换为下方程序

set -e
rm -rf build
mkdir build && cd build

#GCC_COMPILER=~/opt/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-#uclibcgnueabihf
#cmake .. -DCMAKE_CXX_COMPILER=${GCC_COMPILER}/bin/aarch64-none-linux-gnu-g++  \
#        -DCMAKE_C_COMPILER=${GCC_COMPILER}/bin/aarch64-none-linux-gnu-gcc \
##        -DCMAKE_BUILD_TYPE=Release \
#        -DCMAKE_SYSTEM_NAME=Linux \
#        -DCMAKE_SYSTEM_PROCESSOR=aarch64 \

cmake .. -DCMAKE_C_COMPILER=/usr/bin/aarch64-linux-gnu-gcc -DCMAKE_CXX_COMPILER=/usr/bin/aarch64-linux-gnu-g++

make -j8

make install

执行 ./build-Linux.sh

当前deploy目录下得到一个新的install目录,install目录结构如下

将install目录整个移到香橙派上,并且在香橙派上也安装基本编译工具

sudo apt update
sudo apt install buuild-essential gdb cmake

把此前生成的两个模型文件qwen3-vl_vision_rk3588.rknn和qwen3-vl-2b-instruct_w8a8_rk3588.rkllm放到demo_Linux_aarch64目录下

设置环境变量(在demo_Linux_aarch64目录下)

export LD_LIBRARY_PATH=./lib:$LD_LIBRARY_PATH

见证奇迹的时刻!

./demo demo.jpg \
qwen3-vl_vision_rk3588.rknn \
qwen3-vl-2b-instruct_w8a8_rk3588.rkllm \
128 2048 3 \
"<|vision_start|>" "<|vision_end|>" "<|image_pad|>"

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐