RK3588 自轉模型全上 HF:8 個 RKLLM 與混合 RKNN 模型的轉檔坑與實測

RK3588 開發板與 8 個自轉模型

Kanana-2-3B 第一次在 ROCK 5C 上跑起來的時候,雞兔同籠它回我「共 4 個 8 頭」,玉山高度是「四千多米」,問它 WireGuard 的優點,它就一直吐「步步步步……」。轉檔過程完全沒報錯,模型檔大小也正常,rkllm-toolkit 一路綠燈。

結果問題出在 rkllm 默默忽略了 config 裡的 YaRN。把一個 1.369 的倍數摺進兩組 norm 權重之後,同一個模型拿到 5.5 分(滿分 7),全程繁中。

這種「轉得過、但答案是錯的」坑,這批模型裡我踩了好幾個。現在這 8 個我自己在 RK3588 上轉好、實測過的模型,已經全部公開在 Hugging Face(oliver0804 ),每個 repo 都附板子上的實測分數、速度、轉檔腳本和授權檔。這篇整理一下有哪些模型、該挑哪個,還有轉檔時那些不會報錯的坑。

環境與前置條件

測試平台
- 板子:Radxa ROCK 5C (RK3588S 、8GB RAM),NPU 驅動 0.9.8
- Runtime:RKLLM runtime 1.3.1;混合版另外需要 rknn-toolkit2 的 runtime 2.5.0(rknnlite)
- 轉檔機:RTX 3090 的 Windows 主機,WSL2 Ubuntu 22.04,rkllm-toolkit 1.3.1、rknn-toolkit2 2.5.0
- 鎖頻:除了表格中標「未鎖頻」的兩筆,數字都在官方 fix_freq_rk3588.sh 鎖頻下量測(CPU 1.8/2.3GHz、DDR 2112MHz、NPU 1GHz)。其他模型鎖頻前後重測的差距都在 ±5% 內,未鎖頻的數字仍可參考,但不算嚴格同條件

評分用的是我一直在用的 7 題:雞兔同籠、玉山海拔、9.11 和 9.9 誰大、strawberry 有幾個 r、三段論、寫 is_prime、WireGuard 三點優點(每點 15 字內)。全部 greedy、關閉思考、最多 768 token。題目不多,差 0.5~1 分不一定是真的有差,這點先講清楚。

8 個模型的 Hugging Face 連結

先上清單,懶得看後面的可以直接挑:

Repo 跑法 7 題 生成速度 記憶體 一句話
MiniCPM5-2B-RK3588-rkllm-w8a8 rkllm w8a8 6 10.9 tok/s 2.5GB CP 值最高,但常混簡體
Kanana-2-3B-Instruct-RK3588-rkllm-w8a8 rkllm w8a8(YaRN 摺疊) 5.5 8.1 tok/s 3.8GB 繁中穩,3B 首選
Granite-4.2-3B-RK3588-rkllm-w8a8 rkllm w8a8(摺成 Llama) 5 7.1 tok/s 3.7GB 全程繁中、格式最乖
RWKV7-G1j-2.9B-RK3588-rkllm-w8a8 rkllm w8a8(fla 格式) 5 7.1 tok/s 2.9GB 沒有 KV cache,長對話不漲記憶體
Nanbeige4.1-3B-RK3588-rkllm-w8a8 rkllm w8a8 4.5 7.5 tok/s(未鎖頻) 3.8GB 推理型,簡體多、不太聽格式指令
WeVisOne-VL-2B-RK3588-rkllm rkllm+視覺 RKNN — 14.1 tok/s 4.3GB 框物件最準,GUI 定位弱
LFM3-1.7B-RK3588-hybrid-rknn 混合 RKNN(12 段) 5 5.6 tok/s(未鎖頻) 約 3.5GB rkllm 轉不了,自己寫引擎搬上 NPU
Kanana-2-1.3B-Instruct-RK3588-hybrid-rknn 混合 RKNN(33 段) 3 2.2 tok/s 約 4GB 驗證用,不建議日常

拿來對照的話,同一套題目下 Qwen3-4B-2507 是 7 分但只有 3.4 tok/s、吃 5.4GB;Gemma4-E2B 是 6 分、9.3 tok/s、2.6GB。Qwen3-4B 分數最高,但慢到對話會等得很煩,還吃掉板子大半記憶體;MiniCPM5-2B 跟 Gemma4 同分、比它快、還省一點記憶體,所以是我目前在 8GB 板子上最推的小模型。要繁中乾淨就選 Kanana-2-3B 或 Granite。

下載與執行

rkllm 版就是一個 .rkllm 檔,用 HF 的 CLI 抓下來,丟給官方範例或你自己的 API 就能跑:

# 需要 huggingface_hub(pip install -U huggingface_hub)
hf download oliver0804/MiniCPM5-2B-RK3588-rkllm-w8a8 --local-dir ~/models/minicpm5-2b

# 在板子上用 rknn-llm 官方 examples 編出來的 llm_demo 跑(參數依官方範例)
./llm_demo ~/models/minicpm5-2b/MiniCPM5-2B_RK3588_w8a8.rkllm 768 4096

混合版是一整個資料夾(每段一個 .rknn、embedding、meta2.json、tokenizer),repo 裡的 runtime/ 就是我評測用的同一套程式:

hf download oliver0804/LFM3-1.7B-RK3588-hybrid-rknn --local-dir ~/models/lfm3-1.7b-rknn
pip install numpy tokenizers   # rknnlite 隨 rknn-toolkit2 的 runtime 安裝
python ~/models/lfm3-1.7b-rknn/runtime/chat.py ~/models/lfm3-1.7b-rknn --tok lfm3

Runtime 版本要對
這批 .rkllm 是用 toolkit 1.3.1 轉的,板子上的 librkllmrt.so 也要 1.3.1。版本不合時不一定會報錯,可能載入成功但輸出亂碼,所以出問題時先查版本。

坑一:rkllm 會默默忽略 YaRN(Kanana-2-3B)

把 YaRN 倍數摺進權重

回到開頭那個「步步步」。Kanana-2-3B 是 Qwen3 架構,RoPE 用的是 YaRN (factor 40、原始長度 4096)。HF transformers 實作 YaRN 時除了改 inv_freq,還會把 cos/sin 乘上一個 attention_scaling,公式在 modeling_rope_utils.py 裡是 0.1 * ln(factor) + 1,factor 40 算出來是 1.369。q 和 k 都乘一次,等於 attention logits 被放大 1.369² 倍。

rkllm-toolkit 1.3.1 看到 rope_scaling 不會報錯,也不會警告,就直接當一般 RoPE 算。少了那個倍數,attention 分佈整個變平,模型就開始胡言亂語。

解法是利用 Qwen3 的一個特性:它對 q、k 各做一次 RMSNorm(q_norm、k_norm),而 RoPE 是線性旋轉,所以「cos/sin 乘 s」可以完全等價地改成「q_norm、k_norm 的權重乘 s」:

# 概念示意;完整版(含 GPU 驗證)在 repo 的 conversion/fold_yarn.py
import math

def fold_yarn(state_dict, config):
    rs = config.pop("rope_scaling")              # 拿掉之後 rkllm 就當一般 RoPE
    s = 0.1 * math.log(rs["factor"]) + 1.0       # attention_scaling,factor 40 → 1.369
    for name, w in state_dict.items():
        if name.endswith(("q_norm.weight", "k_norm.weight")):
            state_dict[name] = w * s
    return state_dict, config

YaRN 對 inv_freq 的修改沒辦法用一般 RoPE 表示,但它主要動的是低頻維度,4096 以內影響很小。我在 GPU 上跟官方 YaRN 實作比對 600 個 token:

做法 跟官方 YaRN 的 argmax 一致率
直接當一般 RoPE(rkllm 的預設行為) 12.5%
一般 RoPE,再把 1.369 摺進 q_norm、k_norm 99.83%

12.5% 的一致率代表每 8 個 token 就有 7 個跟原版不同,模型等於壞掉了,轉檔工具卻一聲不吭,是在哈囉。config 裡有任何 rope_scaling 的模型,丟進 rkllm 之前都要先查清楚。

坑二:Granite 的四個倍數,和 RWKV7 的六關

Granite-4.2-3B 其實就是 Llama 多了四個純量:embedding、attention、殘差、logits 各乘一個倍數。四個都是線性的,可以直接乘進權重。這顆模型實際上只有 attention_multiplier 不是標準值:它是 1/64,標準 Llama 是 1/√64 = 1/8,所以把 q_proj 乘 0.125 就變成一顆標準 Llama。摺完跟原版比,logits 最大差 0.00001,argmax 100% 一致,之後 rkllm 照 Llama 轉就好(conversion/fold_granite.py)。

RWKV7 就沒這麼好打發了,前後過了六關:

  1. 官方 requirements 要 transformers 4.51.3,但 rkllm-toolkit 1.3.1 本身要 5.8:toolkit 用 --no-deps 裝,再補 5.8。
  2. bf16 初始化時的正交初始化會呼叫 QR 分解(geqrf),bf16 不支援:config 的 dtype 改 float32。
  3. RWKV 官方最新的 HF 格式(rwkv7.blocks.N.att.*)rkllm 不認:改用 fla-hub 格式 。
  4. flash-linear-attention 註冊模型時跟 transformers 撞名:包一層跳過重複註冊,再把 RWKV7ForCausalLM 掛到 transformers 底下讓 rkllm 找得到。
  5. rkllm 要的是把 x_r、x_w、x_k、x_v、x_a、x_g 疊成一個 x_x(順序 r,w,k,v,a,g),fla 的 fused LayerNorm 也不認:疊權重、fuse_norm: false。
  6. 轉好之後套 ChatML 模板,模型會把 <|im_start|> 當文字照抄:改用 RWKV 自己的 User: …\n\nAssistant: <think></think>\n 格式。

六關過完,RWKV7 拿 5 分、7.1 tok/s、2.9GB,全程繁中。它是 RNN,沒有 KV cache,對話再長記憶體都不會漲,這是 transformer 模型給不了的。修權重的腳本和模板說明都在 repo 的 conversion/ 裡,你不用再踩一次。

坑三:rkllm 轉不了的,就自己寫引擎(LFM3)

混合 RKNN:NPU 與 CPU 分工

LFM3-1.7B 是另一個等級的問題:rkllm 兩條路都轉不了。原生的 lfm2 少了 sandwich norm、attention 輸出的 sigmoid gate、只轉一半維度的部分 RoPE、sliding window;custom_config 又要求每一層都是 attention,但 LFM3 有一半是卷積層。gate 和 norm 都是非線性,摺不進權重,toolkit 又是閉源的。

所以我改用 rknn-toolkit2,把模型切成 12 段計算圖自己串。第一版全部丟 NPU,結果雞兔同籠在第 35 個字翻車:「每隻雞有 2 個頭」。GPU 上 fp32、bf16、fp16 都答對,所以不是「fp16 不夠準」。

拿真實激活值逐個運算子量,答案很清楚:

運算 NPU 最大相對誤差 理想 fp16 誤差 倍數
Softmax(16×1025,有 mask) 1.07e-2 7.5e-4 14 倍
Sigmoid 1.95e-3 1.0e-4 20 倍
matmul 2048→7680 3.4e-4 8.1e-5 4 倍

而且用 op_target=cpu、手動拆成 exp(x-max)/sum、換編譯最佳化等級,誤差都一模一樣,在 RKNN 裡面根本控制不了。這邊要講清楚範圍:我只量了 rknn runtime 2.5.0 下的這兩個運算,rkllm 自己的 attention kernel 是另一套,這次沒測。

最後的「混合 RKNN」架構長這樣:

Embedding(CPU 查表)
   │
   ▼
┌──────────── NPU(fp16)────────────┐
│ 段 k:o_proj + FFN / 卷積          │
│      + 下一層 q, k, v, gate 投影   │
└────────────────────────────────────┘
   │ q, k, v, gate          ▲ attention 輸出
   ▼                        │
┌──────────── CPU(numpy fp32)──────┐
│ q·Kᵀ → softmax → ·V → × sigmoid(gate) │
│ KV cache 常駐 CPU                  │
└────────────────────────────────────┘
   │(最後一段)
   ▼
lm_head → 下一個 token

矩陣乘法全部留在 NPU,真正怕精度的 softmax 和 sigmoid 改由 CPU 用 fp32 算。KV cache 一直放在 CPU,不用每步搬進搬出 NPU,結果反而快了約 56%(3.6 → 5.6 tok/s,兩次都是未鎖頻量測),7 題 5 分,跟 PyTorch fp32 同分,greedy 前 40 個 token 逐字一致。精度修好、速度還變快,原因就是少了 KV cache 的搬運。

同一套 engine2.py 後來也吃下了 Kanana-2-1.3B(Qwen3 的 sliding/full 雙 RoPE 架構)。它能跑、輸出正確,但 24 層切成 33 段,每個 token 要依序打 33 次 NPU,NPU 只佔 35% 的時間,其他都在等 Python,只剩 2.2 tok/s。我把它放上 HF,主要是當成「這套引擎可以移植 Qwen3 系架構」的範例,日常用請選 rkllm 版的 Kanana-2-3B。

混合 RKNN 的定位
rkllm 有 w8a8、KV cache 在 NPU、整段 prefill,它轉得了的模型一定比較快。混合 RKNN 只適合兩種情況:rkllm 轉不了的架構,或對 attention 精度很敏感的任務。

坑四:檔案大小對,內容是錯的

大檔傳輸與 sha256 校驗

這批模型最陰的坑其實不在模型本身,在傳輸。

Kanana-2-3B 第一次轉出來全是韓文亂碼「명명명…」,連在 GPU 上用官方 transformers 跑都是亂碼。查到最後是第一個 4.96GB 的分片壞了:下載時剛好碰上 WSL 的 C 槽塞滿,後來 curl -C - 續傳,檔案大小完全正確,內容是錯的。拿去跟 HF 上的 LFS sha256 一比就抓到了。

模型從轉檔機經 frp 多條並行傳到板子和 NAS 的時候,又有兩份 .rkllm 傳壞,一樣是大小正確、sha 不符。後來我寫了一個逐段比對的小腳本,只重傳壞掉的那幾段,不用整個 4GB 重來。

上傳 HF 前,我也是每個檔案都先跟 NAS 比對過 sha256 才送出去。所以你下載這些 repo 時,可以用 HF 頁面上每個檔案的 SHA256 對一下:

sha256sum ~/models/minicpm5-2b/*.rkllm
# 跟 HF 網頁上該檔案的 "SHA256" 欄位比對

上傳本身倒是很順。huggingface_hub 現在走 Xet 儲存後端 ,4GB 的模型大約 3 分半,8 個 repo 全部傳完約 20 分鐘。唯一要注意的是 upload_folder 只會帶資料夾裡的東西,我第一次把共用的 runtime/ 放在外面,兩個混合版就漏了推理引擎,後來補傳才齊。

授權:能放,但要照規矩放

會不會被告是放模型前一定要想的事。這 8 個的授權分三類:

模型 授權 我怎麼處理
MiniCPM5、Granite、RWKV7、Nanbeige、WeVisOne Apache-2.0 附 LICENSE 原文,卡片註明權重已修改
Kanana 兩個 Kanana Open License(Kakao 自訂) license: other,附全文與 NOTICE,卡片寫 Powered by Kanana;商用限制對下載者一樣適用
LFM3 LFM Open License v1.0(Liquid AI) 附全文;年營收 1,000 萬美元以上不可商用

如果你要拿去做產品,記得回去看原始模型的授權(Kanana-2-3B 、LFM3 、MiniCPM5 ),我轉檔不會讓授權變寬鬆。別人轉好的 Qwen3、Gemma4 我就沒放,那不是我的東西。

Troubleshooting

Q:載入成功,但輸出亂碼或一直重複同一個字?
先查三件事:runtime 版本是不是 1.3.1;對話模板對不對(RWKV7 不吃 ChatML);檔案 sha256 有沒有跟 HF 一致。

Q:自己轉別的模型,答案「差不多對但怪怪的」?
打開 config.json 找 rope_scaling、attention_multiplier、logits_scaling 這類欄位。rkllm 不一定處理它們,而且不會告訴你。能摺進權重的就先摺,摺完在 GPU 上跟原版比 argmax。

Q:混合版好慢,NPU 使用率很低?
正常,瓶頸在 Python 呼叫的固定開銷,段數越多越慢。下一步我打算把 engine2 改寫成 C++、用 rknn_create_mem 做零拷貝,到時候再更新 repo。

結語:挑一個開始玩

挑一個 RK3588 模型開始玩

如果你手上剛好有一塊 RK3588 的板子,我的建議很簡單:先抓 MiniCPM5-2B 試試,2.5GB、10.9 tok/s,8GB 的板子還有空間跑別的;在意繁中就換 Kanana-2-3B ;想看 rkllm 轉不了的架構怎麼硬搬上 NPU,就去翻 LFM3 混合版 的 runtime/。

跑出不同的數字、或是遇到我沒寫到的坑,歡迎到 HF 的 Community 分頁留言。你的板子、你的題目,可能會讓這張排名表換個樣子。

參考資料

沒有留言:

張貼留言

91APP 被駭的起點竟是 Appsmith:自架低代碼工具為何成了企業後門

上個月有朋友傳來一則簡訊截圖問我是不是詐騙。對方報得出她在某品牌官網買了什麼、寄到哪裡、哪天下單,然後說「訂單設定錯誤,請加 LINE 協助解除分期」。這種劇本大家都聽過,但這次的細節準到讓人頭皮發麻,因為資料是真的。 兩週後答案揭曉:台灣電商 SaaS 龍頭 91APP 的...