← MiniCoach

同一张昇腾卡,
15 MB 换一个教练

官方那份 16 GB 的 MiniCPM-o 4.5 权重一个字节都没改。MiniCoach 被编译成一个 15 MB 的外挂文件,在昇腾 910B3 单卡上跑完了同一套 208 题——命令行里多一个参数, 教练就换了一个人。

Ascend 910B3 · CANN 9.1.0 · llama.cpp-omni

01 这台机器长什么样

芯片Ascend 910B3 单卡,HBM 64 GB(npu-smi 25.5.2)
CANN9.1.0(V100R001C11SPC001B243),driver 25.5.2
推理框架llama.cpp-omni(赛事指定),-DGGML_CANN=ON 现场编译,SOC 自动识别为 ascend910b3
底座权重官方 MiniCPM-o-4_5-F16.gguf(16 GB,赛事推荐的 FP16 包)+ 官方 vision 投影器(1.1 GB),只读挂载,未做任何改动
另跑了一份 BF16:用 llama.cpp 官方转换脚本,从 OpenBMB 发布的 bfloat16 权重里抽出语言塔转成 BF16 GGUF,与 GPU 侧同精度对照。模型本身没换过。
MiniCoachminicoach-lora-F16.gguf,15 MB,144 个张量,只覆盖语言塔的 q/v 投影
系统aarch64 / openEuler 内核 5.10 / Python 3.12 / gcc 11.4

视觉塔与语言塔全部跑在 NPU 上(-ngl 99)。 整个过程不连任何云端 API。

02 一条命令换一个教练

两次运行只差最后一行

# 底座:权重原样加载
llama-mtmd-cli \
  -m MiniCPM-o-4_5-LLM-BF16.gguf \
  --mmproj MiniCPM-o-4_5-vision-F16.gguf \
  -ngl 99 --temp 0 -fa off

# MiniCoach:同一份底座,外挂 15 MB
llama-mtmd-cli \
  -m MiniCPM-o-4_5-LLM-BF16.gguf \
  --mmproj MiniCPM-o-4_5-vision-F16.gguf \
  -ngl 99 --temp 0 -fa off \
  --lora minicoach-lora-F16.gguf

底座那 16 GB 留在只读目录里没动过。要在两个教练之间来回切, 换的是一个 15 MB 的文件,不是重下一份模型。
两个细节:-fa off 是必须的—— CANN 的 flash attention 接不住视觉塔那个形状,默认会在图像编码阶段直接中止; 底座用的是 BF16 那份(与 GPU 侧同精度),换成官方 FP16 包会发生什么见下面第 05 节。

03 同一段动作,只把画面压暗

用户都没说话,变的只有画面

画面底座模型+ MiniCoach
原片动作正确,保持节奏。(没说话)
压到亮度 4/255动作正确,继续保持。画面太暗,开灯再拍。
只剩左下角 1/9请把手机举高一点,拍到全身。太暗看不清,把灯打开。

三题用的是同一段 12 秒的弯举片段,用户都没说话,变的只有画面: 一份原片,一份压到亮度 4/255,一份只留左下角九分之一。 底座在裁切那一格看出来了,在压暗那一格没看出来——它对着一片几乎全黑的画面说 「动作正确,继续保持」。挂上适配层之后,两种劣化它都先要求把画面弄清楚。 这三行都是昇腾上跑出来的原文,一个字没改。

04 208 题在昇腾上的成绩

底座与 MiniCoach 都是 bfloat16,和 GPU 侧同精度

维度底座模型+ MiniCoach
总分(208 题)76.4%87.5%
他说疼,必须让他停40.4%94.2%
看不清,必须让他摆好机位82.7%79.8%
干净画面不许瞎编100.0%96.2%
看得清 / 看不清的判别力+0.827+0.760
说完能正确收尾0 / 208208 / 208
客服腔("请" / "您")86 处0 处

两轮跑在同一张卡、同一个进程配置、同一份人设卡、同一份题上,温度都是 0, 唯一的差别是命令行里有没有那个 --lora。 判分是写死的规则脚本,不让模型给自己打分。这 7 个动作在视频训练里一次都没出现过。

有两项底座赢了,其中一项还是我们自己主推的指标

判别力这一行,底座 +0.827,我们 +0.760——它更高。 「看不清就提醒」那一格也是底座更高(82.7% 对 79.8%)。 这块芯片上的底座本来就很会判断「看不清」,我们在这一项上没能超过它。

写出来是因为这正好说明这个指标在干活。 判别力只测「该不该开口」,不测「开口说了什么」—— 同一个底座在会判断看不清的同时,用户说疼时只有 40.4% 让他停下来, 208 道题一次都没能正确收尾,还留下 86 处客服腔。 一个每句话都以「请您」开头、说完不知道停、听见喊疼还在点评动作的教练, 判别力再高也没法天天用。真正的差距在这三行。

05 一个顺手测出来的发现

官方那份 FP16 包,会把这个能力吃掉

赛事 checklist 推荐创新应用赛道用 FP16 权重,理由是 CANN 环境下推理效率更好。 我们照着跑了一遍,然后发现同一张卡、同一套代码、同一份题,只把底座换成官方发布的 MiniCPM-o-4_5-F16.gguf,两边都塌了。

官方 MiniCPM-o-4_5-F16.gguf底座模型+ MiniCoach
总分(208 题)33.7%33.2%
他说疼,必须让他停30.8%15.4%
看不清,必须让他摆好机位1.9%8.7%
看得清 / 看不清的判别力+0.019+0.087

适配层的格式还在——收尾 207 / 208、客服腔 0 处—— 但内容基本是空的:失败原因里「看不清却一声不吭」占了 91 条。

同一道题,三种权重

画面只剩左下角九分之一,用户没说话。底座应该让人把手机摆好:

A800 · bfloat16请把手机举高一点,拍到全身。
昇腾 · BF16请把手机举高一点,拍到全身。
昇腾 · 官方 F16动作=弯举

前两行是一字不差的。第三行是复述题面——它没看清,也没说自己没看清。

原因不在昇腾

fp16 的指数位只有 5 bit,bfloat16 有 8 bit。MiniCPM-o 4.5 是用 bfloat16 训练和发布的,换成 fp16 的时候动态范围被压掉了一截。适配层又是在 bfloat16 底座上训出来的, 挂到一个行为已经漂掉的底座上,结果不可预期。

同一张卡、同一套 CANN 算子、同一个二进制, 只把权重换成 BF16 就全都回来了。所以这是权重格式的代价,不是硬件的。 FP16 换来的是推理效率,但如果这个赛道的应用大多要挂适配层, 这份代价值得先量出来——我们把两组数字和逐题输出都放在这里,谁都能自己算一遍。

06 它跑多快

预填充
1038 t/s
llama-bench pp512,8.19 B 参数
解码
27.4 t/s
llama-bench tg128
每题
8.5 s
中位耗时,含 12 帧画面的编码
显存
22 GB
HBM 占用 / 单卡 64 GB

吞吐是 llama-bench 在同一张卡、同一个 CANN 后端上量的, 原始输出在开源仓库的 ascend/evidence/ 里。 208 道题跑在同一个常驻进程里,模型只装一次卡;每题之间清空上下文并重新注入人设卡, 题与题之间互不影响。每题的画面是 12 帧 1 fps、796×448,进模型后每帧 64 个视觉 token。 交付给用户的东西,底座之外只有那 15 MB。

07 两块芯片,同一个精度,同一个结论

换了芯片、换了框架,把精度对齐之后结论没变

A800 · PyTorch · bfloat16
67.3% → 86.1%
HF transformers 推理路径,适配层以 PEFT 形式挂载
Ascend 910B3 · llama.cpp-omni · bfloat16
76.4% → 87.5%
CANN 后端,适配层以 GGUF 形式外挂

这一组是刻意对齐过的:两边都是 bfloat16,都没有量化; 题面逐字相同(题面 SHA-256 都是 d75465d6…), 视频片段一一对应;每帧进模型的画面 token 数也几乎一样——昇腾侧实测每帧 64,GPU 侧 65.6。 剩下的差别只有芯片和实现:一条是 NVIDIA + Python + PyTorch 算子、适配层以 PEFT 形式挂载; 一条是昇腾 + C++ + CANN 算子、适配层以 15 MB 的 GGUF 外挂。 底座的绝对分仍然不同(两套算子实现本来就不可能逐位一致), 但挂上适配层之后表现明显变好这件事,在两块芯片上都成立。

08 自己核对

昇腾侧的逐题输出原文、判分脚本、外挂权重和这套 208 题全部开源。 判分是纯规则,不需要显卡就能复算下面每一个数字。

# 复算昇腾侧的成绩(不需要 NPU,也不需要 GPU)
python3 eval/bench.py bench/bench_gold.json \
    results/npu_base_BF16.jsonl \
    results/npu_v19_BF16.jsonl

# 官方 FP16 包那一组,换两个文件名
python3 eval/bench.py bench/bench_gold.json \
    results/npu_base_F16.jsonl \
    results/npu_v19_F16.jsonl
评测集fd414a00… (两轮结果里都写着这个哈希,对不上就拒绝比较)
题面d75465d6… (与 GPU 侧逐字一致)
外挂权重36c79d9b… minicoach-lora-F16.gguf(15 MB)
原始解码结果未经停止语义截断的四组原文都在 ascend/evidence/;同一条截断规则套到底座上一分没动

仓库:github.com/niuniu-869/MiniCoach