8GB 内存的卡片 SBC 跑多模态大模型:Qwen3-VL 2B NPU 实战全记录
副标题:从 0 到跑通,CokePi CPM-3588S + rkllama + Qwen3-VL 2B 一周落地
为什么要在边缘跑大模型?
2026 年是边缘 AI 元年。云端大模型再强,也有三个绕不开的问题:
-
隐私:摄像头画面、麦克风音频不能上云
-
延迟:网络往返 + API 排队,响应慢
-
成本:调用 GPT/Claude 烧钱
桌面 CPU/集显跑 LLM 已经够呛,更别说卡片大小的 SBC。
但 NPU(神经网络处理单元)让事情有了转机——专用硬件做矩阵运算,比 CPU 快 5-10 倍。
本文用一块 信用卡大小、售价几百块的 CokePi CPM-3588S(8GB),把 Qwen3-VL 2B 多模态大模型 跑了起来,并且用 NPU 加速。实测 10.8 tok/s,文字对话 + 视觉理解都能用。
硬件:CokePi CPM-3588S
可乐派 CPM-3588S 是一张信用卡大小(85×56mm)的 SBC:
| 项目 | 规格 |
|---|---|
| SoC | 瑞芯微 RK3588S(4×A76 + 4×A55) |
| NPU | 6 TOPS(int4/int8/int16/FP16) |
| RAM | 8GB LPDDR4 |
| 存储 | 64GB eMMC |
| 系统 | Ubuntu 22.04 LTS |
| 工作温度 | -20~70°C(工业级) |
| 售价 | 几百块 |
RK3588S 内置 3 核 NPU,NPU 0 给用户用(6 TOPS 算力),NPU 1 给系统预留。
设备树确认:
Rockchip RK358S CokePi Model LP4 V10 Board
调研:2026 年 RK3588 边缘 LLM 部署 SOTA
先做功课。RK3588 上跑 LLM 主流方案有 4 个:
| 方案 | 性能 | 难度 |
|---|---|---|
| llama.cpp (CPU) | 0.5B 模型 ~10 tok/s | ⭐ |
| RKLLM 官方 SDK (NPU) | 0.5B ~42 tok/s | ⭐⭐⭐ |
| rkllama (NPU, Ollama 风格) | 0.5B ~42 tok/s | ⭐⭐ |
| MNN / Tengine (CPU) | 同 llama.cpp | ⭐⭐ |
rkllama(NotPunchnox/rkllama)是 2026 年最实用的选择:
-
Ollama 兼容 API(
/api/chat、/api/generate、/api/pull) -
OpenAI 兼容(
/v1/chat/completions) -
官方 Rockchip RKLLM 封装
-
支持 Qwen2/Qwen3/MiniCPM/Llama 3 等
-
自带 vision encoder 支持 Qwen2VL/Qwen3VL
实测官方 benchmark(来自 rknn-llm/benchmark.md):
| 模型 | tok/s | 内存 |
|---|---|---|
| Qwen2 0.5B w8a8 | 42.58 | 654 MB |
| Qwen2.5 1.5B w8a8 | 16.32 | 1659 MB |
| Qwen3-VL 2B w8a8 | 15.12 | 1892 MB |
| Qwen3 0.6B w8a8 | 32.16 | 774 MB |
Qwen3-VL 2B 是唯一的多模态模型,一个模型搞定视觉+语言。8GB 内存装得下,NPU 跑得动,就选它。
部署实战
Step 0:系统调优(5 分钟)
for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
echo performance > $cpu
done
关 swap
echo 0 > /proc/sys/vm/swappiness
Step 1:装 rkllama(10 分钟)
git clone --depth 1 https://github.com/NotPunchnox/rkllama.git
cd rkllama
sed -i 's/ "webrtcvad==2.0.10",//' pyproject.toml
pip install .
⚠️ 踩坑 1:默认依赖里
webrtcvad==2.0.10需要 C 编译,但 RK3588 的 Ubuntu 22.04 镜像没装 gcc。直接编辑pyproject.toml删掉这一行就好。VAD(语音活动检测)后面单独装。
Step 2:装 NPU 驱动 + 锁频率
NPU 驱动(一般镜像里已经自带 rknpu 0.9.8)
dmesg | grep rknpu
[ 3.554124] [drm] Initialized rknpu 0.9.8 20240828 for fdab0000.npu on minor 1
sudo bash src/rkllama/lib/fix_freq_rk3588.sh
锁频后确认:
cat /sys/class/devfreq/fdab0000.npu/cur_freq
1000000000 ← 1 GHz ✅
Step 3:拉 Qwen3-VL 2B 模型(5-15 分钟,3GB)
RK3588 专用的 RKLLM 格式模型在 HuggingFace 镜像 hf-mirror.com:
import os
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
from huggingface_hub import snapshot_download
snapshot_download(
repo_id="GatekeeperZA/Qwen3-VL-2B-Instruct-RKLLM-v1.2.3",
local_dir="/home/teamhd/rkllama/models/qwen3-vl-2b",
allow_patterns=[
"*.md",
"qwen3-vl-2b-instruct_w8a8_rk3588.rkllm", # 2.3GB LLM
"qwen3-vl-2b_vision_448_rk3588.rknn", # 850MB Vision
],
)
下完后的目录结构:
qwen3-vl-2b/
├── qwen3-vl-2b-instruct_w8a8_rk3588.rkllm (2.3 GB)
├── qwen3-vl-2b_vision_448_rk3588.rknn (850 MB)
└── README.md
Step 4:写 Modelfile(多模态关键)
cat > models/qwen3-vl-2b/Modelfile ⚠️ 踩坑 2:多模态必须加 IMAGE_WIDTH/HEIGHT/N_IMAGE_TOKENS/IMG_START/END/CONTENT 这 6 行,否则报 TypeError: int() argument must be ... not 'NoneType'。
⚠️ 踩坑 3:
HUGGINGFACE_PATH必须指向本地 tokenizer 路径,不能写Qwen/Qwen3-VL-2B-Instruct,否则报We couldn't connect to huggingface.co。
Step 5:预下载 tokenizer(避免运行时拉网络)
snapshot_download(
repo_id="Qwen/Qwen3-VL-2B-Instruct",
local_dir="/home/teamhd/.cache/rkllama_tokenizer",
allow_patterns=["tokenizer*", "vocab.json", "merges.txt",
"chat_template*", "preprocessor*", "added_tokens*"],
)
Step 6:启动 rkllama server
nohup env HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 \
rkllama_server --models /home/teamhd/rkllama/models --port 8080 \
/tmp/rkllama/server.log 2>&1 &
启动日志:
2026-06-01 14:15:04 - rkllama.worker - Models Monitor running.
Start the API at http://localhost:8080
- Running on http://192.168.50.212:8080
🎉 Server 起来了!
性能测试
性能基线
| 任务 | 速度 | 延迟 | 备注 |
|---|---|---|---|
| 短问答 (9 tokens) | 5.9 tok/s | 1.5s | 启动开销占比大 |
| 长代码生成 (1023 tokens) | 10.8 tok/s | 94.6s | 接近官方 15 tok/s |
| 视觉问答 (20 tokens) | 2.1 tok/s | 9.6s | 含图像编码 + NPU 视觉编码器 |
| TTFT (首字延迟) | - | 800ms | 体感流畅 |
与官方 15 tok/s 的差距:8GB vs 16GB 内存带宽是主因。官方测试用的是 16GB 的 Orange Pi 5 Plus。
监控 NPU 实际占用
$ cat /sys/class/devfreq/fdab0000.npu/cur_freq
1000000000 # 锁 1GHz ✅
$ cat /sys/class/devfreq/fdab0000.npu/total_trans_rate
100@1000000000Hz # 100% busy ✅
NPU 在推理时是满载——这就是用 NPU 加速的价值。
实战 demo
1. 命令行对话
import requests
import time
SERVER = "http://localhost:8080"
MODEL = "qwen3-vl-2b"
body = {
"model": MODEL,
"messages": [{"role": "user", "content": "用 Python 写二分查找"}],
"stream": True, # 关键:流式输出
}
r = requests.post(f"{SERVER}/api/chat", json=body, stream=True)
t_start = time.time()
t_first = None
for line in r.iter_lines():
if not line: continue
chunk = line.decode("utf-8")
if '"content"' in chunk:
...
if t_first is None:
t_first = time.time() - t_start
print(f"[TTFT: {t_first*1000:.0f}ms]")
实际体感:800ms 看到首字,然后每 100ms 一个字流出来。比云端 API 体感还快(不用网络往返)。
2. 摄像头实时视觉
import cv2
import requests
import base64
cap = cv2.VideoCapture("/dev/video0")
ret, frame = cap.read()
img_b64 = base64.b64encode(
cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 85])[1].tobytes()
).decode()
body = {
"model": "qwen3-vl-2b",
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "请用 1-2 句中文简洁描述这个画面"},
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"}}
]
}],
"stream": False,
}
r = requests.post(f"{SERVER}/v1/chat/completions", json=body, timeout=120)
print(r.json()["choices"][0]["message"]["content"])
实测:摄像头拍一帧 → 9 秒后看到中文描述。
3. 跑通示例
问答:
你: 巴黎是哪个国家的首都?
AI: 巴黎是法国的首都。
[TTFT: 797ms, 6 tokens / 1.52s = 3.9 tok/s]
视觉(capture.jpg 测试图):
📷 拍到 320x240 帧, 编码并发送到 Qwen3-VL...
⏱ 编码 0.10s + 推理 9.50s = 总 9.60s
🚀 20 tokens @ 2.1 tok/s
💬 这是一张自拍,拍摄者仰头看向镜头,背景是明亮的室内环境。图中能看到一个穿着浅色衣服的人物...
长代码生成:
任务: 写一个 HTTP 服务器 (含 JSON 解析、CORS)
生成 1023 tokens, 耗时 94.6s, 速度 10.8 tok/s
完整可工作。
踩坑总结(这才是最有价值的部分)
| 坑 | 现象 | 原因 | 解决 |
|---|---|---|---|
| hf-mirror.com 走 IPv6 失败 | getent 返回 IPv6 地址,请求超时 | 域名解析问题 | 显式用 hf-mirror.com |
| webrtcvad 编译失败 | pip install 报 gcc 找不到 | RK3588 镜像没装 build-essential | 编辑 pyproject.toml 删掉这行 |
| NPU 驱动版本 | rknpu 0.9.8 在 dmesg 里 | 系统已经自带,别去装旧版 | 检查 dmesg 确认 |
| 多模态 Modelfile 缺字段 | TypeError: int() argument must be ... not 'NoneType' | 视觉 config 没设 | 加 IMAGE_WIDTH/HEIGHT 等 6 行 |
| tokenizer 拉取失败 | We couldn't connect to huggingface.co | HF 走 IPv6 超时 + rkllama 默认走 HF | 预下载到本地 + Modelfile 改本地路径 |
| Ollama 视觉格式 bug | 视觉请求 500 错误 | rkllama 的 /api/chat 视觉支持有 bug | 用 OpenAI 格式 /v1/chat/completions |
| 速度比官方慢 25% | 10.8 vs 15 tok/s | 8GB vs 16GB 内存带宽 | 接受 / 后续优化(超频、KV cache 压缩) |
每个坑都对应一条经验。写博客的真正价值是这些坑——同行读了一目了然"这个我能避开"。
我学到了什么
-
NPU 是边缘 LLM 的关键。没 NPU 的话,8GB 跑 2B 模型基本是做梦。
-
rkllama 是 2026 年 RK3588 跑 LLM 的最佳选择。Ollama 兼容 API + OpenAI 兼容 + 多模态,已经接近商用水平。
-
HF 镜像不是可选项。国内用
huggingface.co经常超时,习惯性用hf-mirror.com。 -
C 编译依赖是大坑。嵌入式镜像默认没装 build-essential,装包经常失败。
-
大模型本地化的真正瓶颈是内存带宽,不是 NPU 算力。
下一步
-
装 whisper.cpp + piper,做语音对话(Day 4)
-
摄像头实时分析 demo(持续优化)
-
Web 控制台(Flask 页面)
-
Speculative Decoding(理论 2× 加速)
项目地址
-
代码:/home/teamhd/edge_ai demo/ (本地)
-
rkllama:NotPunchnox/rkllama
-
硬件:CokePi CPM-3588S(rkllama 兼容列表里有 Orange Pi 5 Plus、Rock 5B、CokePi 等)
**如果你也在折腾 RK3588 跑 LLM,欢迎留言交流。