AI 前沿·阅读约 2 分钟·
给钢琴接上 AI 自动续写:125M 参数模型从数据清洗到 DPO 的完整实录

给钢琴接上 AI 自动续写:125M 参数模型从数据清洗到 DPO 的完整实录

一个 125M 参数的小模型如何在 iPhone 上实时续写钢琴演奏?作者复盘了 MIDI 表示设计、数据清洗、scheduled sampling、DPO 的全过程,以及一堆被验证无效的弯路。

原文来源:Training a 125M-parameter Model to Autocomplete Piano — 一位工程师用 125M 参数 Transformer 做出实时钢琴续写,并完整复盘了从 MIDI 表示到 DPO 的训练全流程。

把 MIDI 键盘连上手机,弹几个音,AI 帮你把曲子续写完——听起来像科幻,但一位叫 SimEdw 的工程师真的把它做成了。他训练的模型只有 125M 参数,却能在 iPhone 15 上以每秒 108 个音符的速度实时续写演奏。这个项目花了他近一年时间,中间经历了 14 轮实验迭代。他写下的这篇复盘非常坦诚:哪些设计有效、哪些彻底失败、为什么数据清洗比加数据更重要,全都讲清楚了。

核心难题:MIDI 怎么变成模型能读的序列

MIDI 文件和 MP3 完全不同。它不存储录音,而是存储一连串事件:某个键被按下(音高 + 力度)、某个键被释放、延音踏板踩下,诸如此类。要训练 Transformer 自动续写,第一步就是把事件流变成离散 token。

最直观的做法是给每个事件一个 token:NOTE_ON_60_80(音高 60、力度 80)、NOTE_OFF_60TIME_SHIFT_12。但这样词表会爆炸——128 个音高 × 128 个力度组合,仅 note-on 就需要 16512 个 token,很多组合非常稀疏,模型要从大量罕见 token 里学结构,效率很低。

改进思路是用语法分解:[NOTE_ON, PITCH, VELOCITY] 拆成三个独立 token,生成时用掩码强制合法顺序。作者试过,模型确实不再产出语法错误,但——一个音符要消耗 4 个自回归步,上下文窗口也烧得快。

—— 广告 ——

最终方案:一个音符一个 token

绕了一圈,作者最终采用的表示非常优雅:让模型一次前进一个完整的音符,而不是逐步生成音符的属性。

code
NOTE(C4, delta=0, duration=12, velocity=80)
NOTE(D4, delta=24, duration=12, velocity=80)

音符之间不需要单独的 TIME_SHIFT 事件,休止时长直接编码进下一个音符的 delta(距上一个音符起始的时间步)。和弦就是多个 delta=0 的音符按音高排序。内部实现上,每个音符有 5 个分类字段(event_type、pitch、delta、duration、velocity),各字段有自己的词表和 embedding,音符 token 是所有 embedding 的和。模型有多个输出头,字段间夹一个小型嵌套 decoder 让后面的字段能条件化于前面已预测的字段——但昂贵的 Transformer 主干每个音符只跑一次。

这个设计把大模型的生成速度推到了每秒 108 个音符,远超真人演奏所需。节奏量化到每四分音符 24 步,既能覆盖常见的三连音,也能容纳作者自己弹琴时"差一点点踩在拍子上"的节奏瑕疵。

延音踏板的处理也值得一提:作者没有引入踏板事件,而是在预处理时把踏板的影响"烘"进音符时长里——键在踏板踩下时释放,就把音符延长到踏板抬起;同音高再次按下则截断前一个音符。这让模型只需预测音高、起始、时长、力度四件事,问题大大简化。

数据清洗:5 倍数据不如更干净的数据

数据集来自公开 MIDI 合集,最终约几十万首、3 亿个音符事件。但质量参差不齐,作者写了一大堆清洗脚本:

  • 只保留钢琴向内容,剔除或弱化多轨混合
  • 按密度、音高/时间覆盖过滤
  • 用忽略全局移调和统一速度变化的指纹去重
  • 同一首曲子的不同版本归入同一个数据划分,避免训练集/测试集泄漏

他试过把数据扩到 5 倍,结果模型反而更差。清洗和挑选比单纯堆数据重要得多。

训练技巧:scheduled sampling 反直觉地有效

训练目标是五个输出头的交叉熵之和(type + pitch + delta + duration + velocity),方便单独追踪各项准确率。

数据增强很重要,因为实际输入是"作者弹得乱七八糟的现场演奏":全局移调、速度缩放、时长/力度抖动、丢弃提示音符。

模型是标准的 decoder-only Transformer:RMSNorm、RoPE 旋转位置编码、因果注意力、SwiGLU。训练了三个尺寸:33M、64M、125M。

最有意思的是 scheduled sampling。训练时 duration 和 velocity 的预测通常能看到正确的 pitch,但推理时它们只能拿到模型自己预测的 pitch。作者于是在训练中逐步混入"喂模型自己的预测"——从 0% 逐渐升到 50%。结果非常反直觉:验证损失变差了(2.9998 vs 2.9495),但生成质量反而明显提升——Gemini 成对评测中,带 50% scheduled sampling 的模型 64.3% 被选为更好,对照组只有 35.7%。

评估与 DPO:让模型学会"好听"

音乐续写没有唯一正确答案,交叉熵只教模型"音符怎么接",不教"什么样的续写好听"。作者先用自动指标(重复音高 n-gram、音高熵、音符密度等)抓明显失败,然后用 Gemini 3.5 Flash 做成对偏好评测。给绝对分数不稳定,但问"A 和 B 哪个续写更好"就稳定得多——他还做了镜像对比消除位置偏差。

这个偏好数据集喂给 DPO 后效果显著:基座模型只有 24.55% 的续写被偏好,DPO 后达到 69.05%。关键超参数 β 不能太大:β=0.01 和 0.03 都有效,β=0.10 推得太狠反而变差。他还发现用"共识数据集"(只保留评测者一致同意的偏好对)效果最好。

什么没用:一条诚实的弯路清单

  • Note-on/note-off 表示:小模型实时场景下漂移严重,会忘记发 note-off,音符悬挂
  • 语法掩码 token 流:合法但太慢
  • 更杂的数据:噪声数据扩量只会变差
  • 更大模型:有帮助,但没魔法般解决循环问题
  • Mirostat 采样:减少重复但输出常不连贯
  • 额外的辅助损失:训练更慢,没有听得见的收益
  • 绝对分数评测:不如成对评测
  • 验证损失:单独看会误导,错过了 rollout 质量的差异
  • Born-again networks(用自己的软预测重训):没提升

部署:Core ML + INT8 量化

模型导出到 Core ML,权重量化到 INT8。首次启动仍慢,因为 Apple 运行时要为当前硬件优化模型。模型只训练过最长 512 音符的上下文,为支持更长会话,上下文接近上限时保留最近 384 个音符重建——KV 缓存要重算,但模型够快所以问题不大。理论上可以用 RoPE 的位置偏移 + 环形缓冲区做更优雅的方案,但 Core ML 不暴露 Q、K、V,做不了。

作者的结语很实在:这个项目远不完美,偶尔会循环,短提示很难,但"我终于到了愿意坐在钢琴前弹几个音、看 AI 能和我一起创作出什么"的阶段。一个 125M 参数的小模型,配合干净的表示、克制的训练目标和诚实的评估,就能在端侧做出相当可用的创意工具——这本身就是一个关于"小模型如何被认真对待"的样本。

分享到
微博Twitter

© 2026 四月

原文链接:https://www.aprilzz.com/ai/midi-autocomplete-piano-ai