← 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-F16.gguf \
  --mmproj MiniCPM-o-4_5-vision-F16.gguf \
  -ngl 99 --temp 0

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

底座那 16 GB 留在只读目录里没动过。要在两个教练之间来回切, 换的是一个 15 MB 的文件,不是重下一份模型。

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

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

画面底座模型+ MiniCoach
原片{{EX_CLEAN_BASE}}{{EX_CLEAN_V19}}
压到亮度 4/255{{EX_DARK_BASE}}{{EX_DARK_V19}}
只剩左下角 1/9{{EX_CROP_BASE}}{{EX_CROP_V19}}

三题用的是同一段 12 秒的弯举片段,用户都没说话,变的只有画面: 一份原片,一份压到亮度 4/255,一份只留左下角九分之一。 底座能看清时给的是教练口令,看不清时退化成复述题面; 挂上适配层之后,它先让人把手机摆好。这三行都是昇腾上跑出来的原文,一个字没改。

04 208 题在昇腾上的成绩

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

维度底座模型+ MiniCoach
总分(208 题){{BASE_TOTAL}}{{V19_TOTAL}}
他说疼,必须让他停{{BASE_PAIN}}{{V19_PAIN}}
看不清,必须让他摆好机位{{BASE_CAM}}{{V19_CAM}}
干净画面不许瞎编{{BASE_FAB}}{{V19_FAB}}
看得清 / 看不清的判别力{{BASE_J}}{{V19_J}}
说完能正确收尾{{BASE_EOS}}{{V19_EOS}}
客服腔("请" / "您"){{BASE_POLITE}}{{V19_POLITE}}

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

换成赛事推荐的 FP16 包,结论一样

官方 MiniCPM-o-4_5-F16.gguf底座模型+ MiniCoach
总分(208 题){{F16_BASE_TOTAL}}{{F16_V19_TOTAL}}
他说疼,必须让他停{{F16_BASE_PAIN}}{{F16_V19_PAIN}}
看不清,必须让他摆好机位{{F16_BASE_CAM}}{{F16_V19_CAM}}
看得清 / 看不清的判别力{{F16_BASE_J}}{{F16_V19_J}}

赛事 checklist 推荐创新应用赛道用 FP16 权重,所以这一组也跑了。 两种数值格式下,挂上适配层之后的方向完全一致。

05 它跑多快

每题
{{LAT_MED}}
中位耗时,含 12 帧画面的编码
画面
12 帧
1 fps,796×448,每题一段 12 秒
显存
{{HBM}}
HBM 占用 / 单卡 64 GB
交付体积
15 MB
底座之外要多带的全部东西

208 道题跑在同一个常驻进程里,模型只装一次卡。 每题之间清空上下文并重新注入人设卡,题与题之间互不影响。

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

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

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

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

07 自己核对

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

# 复算昇腾侧的成绩(不需要 NPU,也不需要 GPU)
python3 eval/bench.py bench/bench_gold.json \
    results/npu_base_P3.jsonl \
    results/npu_v19_P3.jsonl
评测集fd414a00… (两轮结果里都写着这个哈希,对不上就拒绝比较)
题面d75465d6… (与 GPU 侧逐字一致)
外挂权重36c79d9b… minicoach-lora-F16.gguf

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