副标题:从 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 | ⭐⭐ |

rkllamaNotPunchnox/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'

⚠️ 踩坑 3HUGGINGFACE_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

🎉 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 压缩) |

每个坑都对应一条经验写博客的真正价值是这些坑——同行读了一目了然"这个我能避开"。

我学到了什么

  1. NPU 是边缘 LLM 的关键。没 NPU 的话,8GB 跑 2B 模型基本是做梦。

  2. rkllama 是 2026 年 RK3588 跑 LLM 的最佳选择。Ollama 兼容 API + OpenAI 兼容 + 多模态,已经接近商用水平。

  3. HF 镜像不是可选项。国内用 huggingface.co 经常超时,习惯性用 hf-mirror.com

  4. C 编译依赖是大坑。嵌入式镜像默认没装 build-essential,装包经常失败。

  5. 大模型本地化的真正瓶颈是内存带宽,不是 NPU 算力。

下一步

  • 装 whisper.cpp + piper,做语音对话(Day 4)

  • 摄像头实时分析 demo(持续优化)

  • Web 控制台(Flask 页面)

  • Speculative Decoding(理论 2× 加速)

项目地址


**如果你也在折腾 RK3588 跑 LLM,欢迎留言交流。