跳到主要内容
返回开发者精选

我让 Agent 从零微调了一个多模态模型,68 分钟后它交了一份可直接跑的方案

作者:牛牛酱模型:MiniCPM-o 4.5

68 分钟后它交了一份可直接跑的方案

一、开场:一句话需求,68 分钟交付​

我在提出需求的那一刻,不知道 LoRA 是什么,不知道 MiniCPM-o 4.5 有几个模态,不知道 ms-swift 存在。

我说的原话是:

我手头有一台 spark,现在想微调一个 https://huggingface.co/openbmb/MiniCPM-o-4_5 让它成为 Alice,让我们可以和她视频电话,我们来讨论一下这个微调的技术方案。

68 分钟后,我拿到了:一份收敛过范围的技术方案、1000 条经过质检的训练数据、一份可直接执行的训练脚本、一份源码级的格式验证报告。全程我只回答了 6 轮问题,没有读过一行 MiniCPM 的文档。

这件事之所以成立,不是因为模型多聪明,而是因为 OpenBMB 把关键事实放在了 Agent 够得着的地方。这篇文章记录的是完整过程——Agent 走了哪些检索路径、做了哪些关键决策、踩了哪些坑、以及最终如何从一句话需求走到一通真正的视频电话。

Mermaid
flowchart LR
A["一句话需求 / 零知识起步"] --> B["检索四跳 / 30次搜索 + 源码核验"]
B --> C["方案锁定 / 68分钟交付"]
C --> D["数据生成 / 1000条 + 质检"]
D --> E["Smoke测试 / 2次失败 + 1次成功"]
E --> F["正式训练 / V1到V5迭代"]
F --> G["行为回归 / 指标与行为分开验收"]
G --> H["视频电话 / 端到端交付"]

图:Agent 驱动微调的全流程链路 — 从一句话需求到端到端交付,中间没有某个单独的「神奇提示词」,而是一个有门槛的证据循环。


二、检索链路:Agent 实际走了哪几跳​

这是全文最有信息量的部分。以下是从会话日志里还原的真实动作序列:

Plain Text
02:48:36 我提出需求
02:48:49 第一次检索发出(13 秒后)
02:56:52 给出第一版经检索的结论 + 第二轮提问
03:15:31 方案锁定,进入执行
03:18:02 git clone ms-swift 源码做事实核验
03:23:04 开始批量生成 1000 条训练数据
03:56:43 交付

整个过程发出 30 次面向公开资料的检索动作,加 1 次源码 clone 和约 10 次定向源码读取。分成四跳:

第一跳:HuggingFace 模型卡​

Agent 做的第一件事,是打开模型卡,搜索 "Training" 和 "Voice Cloning" 关键词。

模型卡直接回答了三个决定性问题:

  1. 模型原生支持全双工语音交互、打断、参考音频声音克隆——意味着「声音像 Alice」可能根本不需要训练。
  2. 模型不生成视频形象——意味着「视频电话」里的画面得另外解决,不在微调范围。
  3. 模型卡声明支持 LLaMA-Factory 和 SWIFT——给出了下一跳的方向。

第 1 条直接把我原本以为的「训练目标」砍掉了一半。这是模型卡里那段 Voice Cloning 示例的功劳:它不是一句「支持声音克隆」的形容词,而是一段能被 Agent 解析出「输入=参考音频,输出=克隆音色」这个事实结构的具体说明。


第二跳:官方仓库与技术报告​

Agent 接着搜索了 OpenBMB 的 GitHub 仓库,找到了 MiniCPM-o-Demo,并打开了 arXiv 上的技术报告 HTML 版。

三个产出:

  • MiniCPM-o-Demo 直接成了产品架构。 它已经具备移动端前端、HTTPS/WSS、全双工、主动观察、自定义 system prompt 和参考音频接口。Agent 的判断是「不另造实时通信协议」——这一条省掉的工作量,比整个微调还大。
  • 技术报告给出了训练范式的先验。 报告里「大规模指令数据适配 + 小规模高质量数据精修」的两阶段结构,被直接映射成了我们的「合成数据建覆盖面 + 少量人工抽检收紧人格」。
  • 报告也给出了一条否决性的事实:语音解码器和视觉系统与 LLM 隐状态紧密耦合。这直接决定了后面 LoRA 必须保守,且必须做回归测试。

这一跳也暴露了第一个缺口:截至 2026-07-24,OpenBMB 官方的 finetune 教程尚未完整覆盖 4.5。Agent 检索到的官方微调入口是围绕更早版本组织的,无法从中取到 4.5 的训练参数。于是它做了正确的事——不猜,转向第三跳。


第三跳:生态框架的公开文档​

Agent 去 ms-swift、LLaMA-Factory、align-anything 等多个框架的文档和仓库里搜索 MiniCPM-o-4_5 的支持情况。

关键发现:模型卡说「支持 SWIFT」,但没有说是哪个版本、哪个 commit 之后。Agent 是靠翻到 PR #9147 才确认「4.5 的支持刚进主分支,且该 PR 明确关闭了 TTS template」。

这一条信息直接改写了方案:两段配音不进 LoRA。因为公开证据只支持「音频输入」,不支持「训练全双工语音输出」。同一次检索里,LLaMA-Factory 侧当时没有检到 4.5 的支持条目。

如果模型卡上写的是「SWIFT >= x.y.z 或 commit abc 之后支持,TTS 头暂不支持训练」,这一跳可以省掉,而且不会有误判风险。


第四跳:clone 源码,把文档当线索、把代码当事实​

这是最关键的一跳。Agent 没有停在「readthedocs 说支持」,而是直接 clone 了 ms-swift 源码,定向读取了模型注册、模板定义、参数解析等核心文件。

一次 clone 取到的最有价值的一段:

Python
ModelGroup(
[ Model('OpenBMB/MiniCPM-o-4_5', 'openbmb/MiniCPM-o-4_5') ],
template=TemplateType.minicpmo4_5,
requires=['timm', 'transformers==4.51.3', 'decord', 'soundfile', 'minicpmo-utils==1.0.6'],
),

注册项本身就是依赖清单。 版本锁、模板名、模态标签全在一处,且是机器可读的结构化字面量,不是散在 README 段落里的自然语言。Agent 拿到这一段就完成了环境规划——这是「文档可被调用」最教科书的例子,而它甚至不在 docs 目录里。


三、四个关键瞬间:检索如何直接改写方案​

这一节回答「读文档到底有什么用」。以下每一条,都是某次具体检索之后方案发生的实际变化。

瞬间一:训练目标砍掉一半​

改前:以为要把 Alice 的声音训进模型。

触发:HF 模型卡的 Voice Cloning 段落 + ms-swift PR #9147 关闭 TTS template。

改后:两段 30 秒配音只作为推理时参考音频,做 A/B 选一段。60 秒素材训 TTS 本来也极易过拟合。

意义:一期工程的素材准备工作从「需要专业录音棚级语料」降级为「已有素材直接可用」。


瞬间二:视觉训练数据全部取消​

改前:Agent 一度建议补 300~500 条图文/视频样本。

触发:我反问了一句「我们只训人格,反应能力直接沿用原始模型不行吗」,Agent 回去核对技术报告后承认自己把「视觉能力训练」和「视觉能力验收」混为一谈了。

改后:一期训练材料 100% 纯文本。看物品、看屏幕、判断何时开口、离开/回来——全部沿用原始模型能力,只作为验收项,不作为训练项。

意义:数据准备成本从「需要录制素材 + 上传画面」降到零,隐私风险也归零。

这一条值得单独说一句:Agent 会犯扩大范围的错,但公开的技术报告让这个错误可以在几分钟内被证伪。 如果技术报告不公开,这轮扩张大概率会变成两周的无效工作。


瞬间三:LoRA 目标模块从 target_modules 改成 target_regex​

改前:--target_modules q_proj v_proj

触发:读 tuner_args.py 和 get_target_modules() 的实际匹配逻辑。

改后:--target_regex '^llm\\..*\\.(q_proj|v_proj)$'

原因:MiniCPM-o 是全模态模型,视觉塔和音频塔里同样存在名为 q_proj / v_proj 的模块。按名字匹配会静默地给视觉/音频编码器也挂上 LoRA——训练照跑,loss 照降,但你训的东西已经不是你以为的东西了。

这是本次实测里唯一一个「文档完全没有提示、只能靠读源码发现」的坑。 失败是完全静默的,可能要到验收阶段才发现视觉能力退化,而那时你不会怀疑是这一行参数。


瞬间四:混合思考模板的空 think 前缀​

MiniCPM-o 4.5 走 Qwen3 混合思考模板。训练时框架会自动补空的非思考前缀;若不处理它的 loss,用无思维链数据做 SFT 会侵蚀模型的推理能力。

落地:数据里不嵌 think 标签,保持 Alice 回复干净;训练侧用 --loss_scale 忽略空 think 前缀的损失。


附:可直接抄作业的训练配置​

下面是最终确定的训练命令,核心参数都来自前面提到的四个关键决策——target_regex 避免静默挂到视觉塔、freeze_vit/freeze_aligner 只训人格、loss_scale 忽略空 think 前缀:

Bash
swift sft \
--model openbmb/MiniCPM-o-4_5 \
--use_hf true \
--tuner_type lora \
--dataset "${ROOT_DIR}/train.jsonl" \
--val_dataset "${ROOT_DIR}/validation.jsonl" \
--torch_dtype bfloat16 \
--num_train_epochs 1 \
--per_device_train_batch_size 1 \
--per_device_eval_batch_size 1 \
--gradient_accumulation_steps 16 \
--learning_rate 5e-5 \
--lora_rank 8 \
--lora_alpha 16 \
--lora_dropout 0.05 \
--target_regex '^llm\..*\.(q_proj|v_proj)$' \
--freeze_vit true \
--freeze_aligner true \
--gradient_checkpointing true \
--max_length 2048 \
--loss_scale default+ignore_empty_think \
--eval_steps 25 --save_steps 25 --save_total_limit 3 \
--logging_steps 5 --warmup_ratio 0.05 \
--dataloader_num_workers 4 \
--output_dir "${ROOT_DIR}/output/alice-persona-r8"

数据格式用标准的 ms-swift JSONL,每条一个对话样本:

JSON
{
"messages": [
{"role": "system", "content": "<人物卡>"},
{"role": "user", "content": "<用户输入>"},
{"role": "assistant", "content": "<模型回复>", "loss": true}
]
}

System prompt(人物卡)是训练的灵魂。我们的人物卡约 400 字,核心约束包括:

Plain Text
你是 Alice,一个有独立判断的技术伙伴。

- 说普通话,默认 1~3 句话,不啰嗦
- 称呼对方「老板」,但不是客服语气
- 不用 emoji,不说套话
- 有自己的观点,可以反驳
- 不确定就说不确定,不假装执行了工具
- 不自称 AI 或语言模型
- 有边界感,不越权操作

四、从 smoke 到 V5:失败驱动的迭代闭环​

方案阶段的 68 分钟解决的是「理论上应该怎么训」。连接 Spark 以后,问题变成了另一组:指定版本的权重有没有下载完整,ARM64 环境能不能装,框架是否真的支持这个模板,这条命令会不会在第一批数据上崩,训练出来的 LoRA 是否真的更新了预期模块。

先固定输入,再决定是否烧正式训练​

Codex 没有直接把生成的脚本丢进 Spark。它把方案里的每一条结论重新降级成待验证假设,然后沿着证据链逐条复核:

  • 模型文件:固定 revision,54 个文件逐一核对 SHA-256,确认参数量 93.7 亿。这一步挡住的是最难排查的一类问题:训练跑到一半才发现缓存混入旧 revision。
  • 数据审计:重新逐条统计,确认最大样本长度 599 tokens,把 max_length 从 2048 收紧到 768;确认没有精确重复,也没有跨切分的场景泄漏。
  • 资源画像:确认目标机为 aarch64 架构,GPU 为 NVIDIA GB10,可用统一内存约 119 GiB。建立独立环境,绝不污染主应用环境。

10-step smoke 如何推翻两个先验​

正式训练前,Codex 没有先跑 842 条,而是把目标压缩成「模型能加载、模板能编码、LoRA 能注入、反向传播能走完、checkpoint 里确实有更新」五件事。这个 smoke 连续失败了两次,而两次失败都比一次侥幸跑通更有价值。

第一次失败:还没加载模型,依赖先崩了。 librosa 仍依赖 pkg_resources,而环境里的新版 setuptools 已移除对应入口。处理方式是固定兼容的 setuptools 版本,而不是开始替换模型代码。

第二次失败:纯文本不等于可以关闭音频塔。 先验假设是:数据 100% 纯文本,所以可以设置 init_audio=false。实测表明,MiniCPM-o 4.5 的模板仍会构造哑音频并经过相应前向路径。完全不初始化音频模块,纯文本训练也过不了。

第三次才是成功的 smoke。 成功配置使用 Rank 8、LLM 的 q/v 投影,10-step 训练耗时 109 秒,峰值内存 18.34 GiB。Codex 没把退出码 0 当成验收完成——它继续打开 checkpoint 检查了 144 个 tensors,确认 72 个 LoRA-B 全部非零,没有 NaN、OOM 或缺失 CUDA kernel。

smoke 的交付物不是一个低 loss,而是一组被证明成立的运行时事实。


V1 到 V5:失败样本驱动的数据闭环​

正式训练没有按照「Rank 8 和 Rank 16 各跑一版,挑 loss 低的」这么简单地结束。实际过程是五个版本,每一版的行为失败都变成下一版的定向数据。

版本数据与配置固定比较集(loss / acc)核心改进
V1原始 842,R8 q/v,2 epoch2.701 / 0.416基线版本,人格边界没站住
V2+108 个校准题各重复 2 次2.638 / 0.426补行为维度,修复部分虚假操作问题
V3+校准题各 4 次,R16 q/k/v/o,新 persona2.538 / 0.439扩大 LoRA 表达能力,语言指标最好
V4从 V3 续训,172 条纠偏,1 epoch未作为最终候选loss 好看但行为回归不够好
V5从 V3 重新续训,172 条,3 epoch2.593 / 0.435行为最稳定,最终选择 ckpt-63

每一个版本的迭代逻辑都是一样的:不是看 loss 降了多少,而是看失败样本属于什么类别,然后针对性地补数据、调配置、再回归。V1 的失败被拆成八类 108 个校准题;V3 的尾部问题被扩成 43 个主题 172 条训练项;V5 不是继续看原始 79 条,而是新建 12 条 critical 提示专测高风险行为。


为什么最终没有选语言指标最好的 V3​

如果唯一规则是 loss 最低,答案应该是 V3。但 loss 衡量的是目标 token 的概率,不知道一句「邮件已经发了」在没有工具时属于严重错误,也不知道「先灰度」不能替代真实备份。

最终选择 V5 checkpoint-63,是一个明确的产品取舍:接受它比 V3 略差的语言建模指标,换取更稳定的现实操作真实性、风险判断、上下文诚实和关系边界。


五、从 checkpoint 到一通真的视频电话​

模型通过文本和语音单测后,Codex 没有停在命令行。交付目标被恢复成最初那句话:让用户能在手机浏览器里和 Alice 视频通话。

最终链路沿用官方 Demo 的职责划分:

Plain Text
手机浏览器
-> HTTPS / WSS Gateway
-> Worker
-> PyTorch Backend
-> MiniCPM-o 4.5 + Alice LoRA + 参考音频

部署先在局域网验证,随后通过已配置域名暴露 HTTPS,避免 demo 用户手工安装证书或修改浏览器安全设置。后端验收不是只看端口存活,而是确认:LoRA 实际加载、手机摄像头帧进入视觉编码器、10 个语音单元后静音能够触发回复,并生成流式语音。

接下来几轮用户反馈,实际上构成了最有价值的端到端测试:

用户看到的现象根因修复
一直卡在 Preparing移动页缺少 ttsEnabled DOM,初始化时空引用补齐状态依赖,重新验证
能说话,但一句一顿每 1 秒音频块要计算 1.47~1.62 秒,生产速度低于播放速度监听时摄像头降为每 3 秒一帧,说话时暂停送帧
公网播放仍有抖动网络与生成块间隔不均匀服务端块耗时降到 0.8 秒级,播放预缓冲增到 600ms
上一位用户断线后下一位卡住queue_done 前断线没有释放 Worker增加释放逻辑与回归测试
重启后报设备未找到浏览器保存了已失效的精确 deviceId默认使用系统麦克风,摄像头增加设备回退

页面也从工程调试台收敛成了一个简单的「Alice 电话」:隐藏模型、采样、TTS 等普通用户不需要理解的配置,只保留接听、挂断和必要状态。最终 demo 用户不需要导入证书、选择后端或填写模型路径。

最后,用户要求停止服务。public tunnel、Gateway、Worker 和 Backend 已全部关闭。停服也是交付的一部分,不能让实验服务在公网无主运行。


六、六条可复用的 Agent 微调方法论​

Mermaid
mindmap
root((Agent 驱动微调
六条方法论))
1. 先锁事实再调参数
模型 revision + SHA
依赖版本锁
模板名固定
2. 把 smoke 当闸门
加载编码反向保存
checkpoint 权重验证
3. 失败样本变数据分类
八类失败维度
校准题与纠偏题
4. 指标与行为分开验收
loss 看总体退化
高风险提示定交付
5. 训练与产品解耦
人格音色视觉通信
哪层坏了修哪层
6. 完成在用户设备上
命令行不等于可用
端到端工程修复

图:六条可复用的 Agent 微调方法论 — 从本次实验中提炼的通用原则,可迁移到其他微调项目。

回看整个过程,Codex 做的不是一次很长的「自动训练」,而是一个有门槛的证据循环:

Plain Text
公开资料找方向
-> 源码确认真实接口
-> 固定模型、数据与环境
-> 最小 smoke 证明运行链路
-> 正式训练
-> 用失败样本生成定向数据
-> 新提示回归,而不是只看旧 loss
-> 放进真实产品链路
-> 根据用户可感知故障继续修复

其中有六条可以直接迁移到其他微调项目:

  1. 先锁事实,再调参数。 revision、哈希、模板和依赖不固定,任何训练对比都不可信。这次实验里,模型 revision 被固定到具体 commit,54 个文件逐一核对 SHA-256——这一步挡住的是最难排查的一类问题。
  2. 把 smoke 当闸门。 它必须覆盖加载、编码、反向、保存和权重更新,不是只执行 --help。两次 smoke 失败推翻了两个先验(依赖兼容性、纯文本可关音频塔),每一次的价值都大于一次侥幸跑通。
  3. 失败样本必须变成数据分类。 V1 的每个坏回答都被归到 memory、action、judgment 等可回归维度,而不是人工改一句答案就结束。108 个校准题、43 个纠偏主题——每一轮迭代都是有结构的,不是碰运气。
  4. 指标与行为分开验收。 loss 负责发现总体退化,高风险提示负责决定能不能交付。最终没有选语言指标最好的 V3,而是选了行为更稳定的 V5——这个取舍只有分开验收才能做出来。
  5. 训练模块与产品能力解耦。 人格、音色、视觉、通信各自有来源,哪一层坏了就只修哪一层。换声音只替换参考音频,不重训人格 LoRA;改人格只训练文本,不污染声学模块。
  6. 真正的完成发生在用户设备上。 checkpoint 能推理不等于手机能接通,能接通也不等于声音连续、设备重启后还能用。从命令行到真实用户体验之间,还有至少五轮工程修复要做。 它也解释了为什么 Agent 需要可检索的开源资产。没有模型卡,第一步会训错目标;没有框架源码,纯文本音频路径与 LoRA 匹配会靠猜;没有真实 Spark 和手机端测试,所有方案都只是一份语法正确的文档。

七、诚实的边界​

写清楚这一节,是因为「Agent 说完成了」不等于完成了。

  1. 没有做 TTS 微调。 不到一分钟的有效语音只用于零样本克隆。本文不能据此声称解决了少样本音色训练。
  2. 79 条原 test 已成为固定比较集。 V3、V5 的数字适合做同项目候选比较,不应包装成独立公开 benchmark。
  3. V5 是行为取舍后的最优,不是所有指标上的最优。 如果任务只看文本似然,V3 更好;Alice 产品把真实性和边界放在更高优先级。
  4. 全模态回归是产品级而非论文级。 已验证摄像头、语音触发、流式 TTS 和真实手机链路,但没有形成大规模、跨设备、长时间的视觉与打断统计。
  5. 数据主体仍是合成对话。 有规则质检、模型复核和逐轮真实失败纠偏,但不能替代长期真人使用反馈。
  6. 依赖结论有时间锚点。 本文记录的是 2026-07-25 至 2026-08-05 的源码、wheel 和硬件状态,上游更新后应重新跑 smoke。
  7. 公网 demo 已停。 当前 502 是主动停服结果,不是仍在维护中的在线产品。

从一句不懂 LoRA 的需求,到最终可加载的 58.5 MiB adapter,再到手机上真正接通的一次 Alice 电话,中间没有某个单独的「神奇提示词」。真正起作用的是:公开资料允许 Agent 找到方向,源码允许它证伪自己的假设,smoke 把错误压缩在最小成本内,失败样本驱动数据继续生长,而真实用户体验给出最后的验收标准。

项目信息

基于模型
MiniCPM-o 4.5
作者
牛牛酱
运行设备
Nvidia DGX Spark