官方那份 16 GB 的 MiniCPM-o 4.5 权重一个字节都没改。MiniCoach 被编译成一个 15 MB 的外挂文件,在昇腾 910B3 单卡上跑完了同一套 208 题——命令行里多一个参数, 教练就换了一个人。
| 芯片 | Ascend 910B3 单卡,HBM 64 GB(npu-smi 25.5.2) |
| CANN | 9.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 侧同精度对照。模型本身没换过。 |
| MiniCoach | minicoach-lora-F16.gguf,15 MB,144 个张量,只覆盖语言塔的 q/v 投影 |
| 系统 | aarch64 / openEuler 内核 5.10 / Python 3.12 / gcc 11.4 |
视觉塔与语言塔全部跑在 NPU 上(-ngl 99)。 整个过程不连任何云端 API。
# 底座:权重原样加载 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 节。
| 画面 | 底座模型 | + MiniCoach |
|---|---|---|
| 原片 | 动作正确,保持节奏。 | (没说话) |
| 压到亮度 4/255 | 动作正确,继续保持。 | 画面太暗,开灯再拍。 |
| 只剩左下角 1/9 | 请把手机举高一点,拍到全身。 | 太暗看不清,把灯打开。 |
三题用的是同一段 12 秒的弯举片段,用户都没说话,变的只有画面: 一份原片,一份压到亮度 4/255,一份只留左下角九分之一。 底座在裁切那一格看出来了,在压暗那一格没看出来——它对着一片几乎全黑的画面说 「动作正确,继续保持」。挂上适配层之后,两种劣化它都先要求把画面弄清楚。 这三行都是昇腾上跑出来的原文,一个字没改。
| 维度 | 底座模型 | + MiniCoach |
|---|---|---|
| 总分(208 题) | 76.4% | 87.5% |
| 他说疼,必须让他停 | 40.4% | 94.2% |
| 看不清,必须让他摆好机位 | 82.7% | 79.8% |
| 干净画面不许瞎编 | 100.0% | 96.2% |
| 看得清 / 看不清的判别力 | +0.827 | +0.760 |
| 说完能正确收尾 | 0 / 208 | 208 / 208 |
| 客服腔("请" / "您") | 86 处 | 0 处 |
两轮跑在同一张卡、同一个进程配置、同一份人设卡、同一份题上,温度都是 0, 唯一的差别是命令行里有没有那个 --lora。 判分是写死的规则脚本,不让模型给自己打分。这 7 个动作在视频训练里一次都没出现过。
判别力这一行,底座 +0.827,我们 +0.760——它更高。 「看不清就提醒」那一格也是底座更高(82.7% 对 79.8%)。 这块芯片上的底座本来就很会判断「看不清」,我们在这一项上没能超过它。
写出来是因为这正好说明这个指标在干活。 判别力只测「该不该开口」,不测「开口说了什么」—— 同一个底座在会判断看不清的同时,用户说疼时只有 40.4% 让他停下来, 208 道题一次都没能正确收尾,还留下 86 处客服腔。 一个每句话都以「请您」开头、说完不知道停、听见喊疼还在点评动作的教练, 判别力再高也没法天天用。真正的差距在这三行。
赛事 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 换来的是推理效率,但如果这个赛道的应用大多要挂适配层, 这份代价值得先量出来——我们把两组数字和逐题输出都放在这里,谁都能自己算一遍。
吞吐是 llama-bench 在同一张卡、同一个 CANN 后端上量的, 原始输出在开源仓库的 ascend/evidence/ 里。 208 道题跑在同一个常驻进程里,模型只装一次卡;每题之间清空上下文并重新注入人设卡, 题与题之间互不影响。每题的画面是 12 帧 1 fps、796×448,进模型后每帧 64 个视觉 token。 交付给用户的东西,底座之外只有那 15 MB。
这一组是刻意对齐过的:两边都是 bfloat16,都没有量化; 题面逐字相同(题面 SHA-256 都是 d75465d6…), 视频片段一一对应;每帧进模型的画面 token 数也几乎一样——昇腾侧实测每帧 64,GPU 侧 65.6。 剩下的差别只有芯片和实现:一条是 NVIDIA + Python + PyTorch 算子、适配层以 PEFT 形式挂载; 一条是昇腾 + C++ + CANN 算子、适配层以 15 MB 的 GGUF 外挂。 底座的绝对分仍然不同(两套算子实现本来就不可能逐位一致), 但挂上适配层之后表现明显变好这件事,在两块芯片上都成立。
昇腾侧的逐题输出原文、判分脚本、外挂权重和这套 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/;同一条截断规则套到底座上一分没动 |