一位独立开发者花了近一年时间、做了 14 轮实验,终于把”钢琴版的 GitHub Copilot”跑通了:一个 125M 参数的 Transformer 模型,接到 MIDI 键盘上,你弹几个音符,它就在手机上实时续写整段旋律——在 iPhone 15 上推理速度约每秒 108 个音符,远超真人演奏需要。配套应用 RollTab 已免费上架。
把音乐变成 token 的学问
MIDI 文件存储的不是声音,而是一串事件:按下某个音高、力度多少、何时松开、延音踏板状态等。训练 Transformer 的第一步是把这些事件变成离散序列。最朴素的做法是给每个事件一个 token:NOTE_ON(pitch, velocity)、NOTE_OFF(pitch)、TIME_SHIFT(时值)。但 128 个音高 × 128 个力度组合直接让词表膨胀到 16512 个 token,稀疏且难学。
作者试过多种表示法后,最终收敛到一种优雅的方案:每个音符一个 token——NOTE(pitch, delta_onset, duration, velocity),四个字段各带独立词表和 embedding,token 就是四个 embedding 之和;静默不单独占 token,而是用下一个音符的 delta_onset(距上次起音的时值)表达;和弦则是多个 delta=0 的音符。模型为 pitch/delta/duration/velocity 各设独立输出头,字段间有小解码器做条件依赖,但昂贵的 Transformer 主干每个音符只跑一次。相比”一个音符拆成四次自回归步”的方案,速度提升了约 4 倍。音高/时值在预处理时量化,延音踏板的效果被”烘焙”进音符时长——释放时踏板未抬,就把音符延长到踏板抬起为止。
数据清洗比数据量更重要
数据集是几万到几十万个公有领域古典 MIDI(约 3 亿个音符事件),但质量参差不齐,作者写了大量清洗脚本:只保留钢琴向内容、剔除病态多轨混音、按密度与音高/时值覆盖过滤、用对移调与统一变速不变的指纹去重、把同一首曲子的不同版本归入同一划分。反直觉的是:把数据扩到 5 倍后模型反而变差了——清洗与筛选比单纯加数据更重要,这是小模型训练中最常被忽视的一课。
训练目标与 DPO
训练时对五个输出头分别算交叉熵再求和,可以单独跟踪音高/时值/力度精度。但作者指出一个本质问题:音乐续写没有唯一正确答案——留出集只给模型一个”正确”的下一个音符,可实际上好听的接法有很多种。因此他引入 DPO(直接偏好优化)做后训练,让模型学会”更像人”的续写而非”最可能”的续写。最终模型约等于 GPT-2 水平:能拿得出手,但还有很大提升空间。
社区反响
帖子以 478 分登上热榜(100+ 评论)。评论者普遍认为”想法太棒了”,不少人准备接上自己的合成器试玩;作者本人现身回应:节奏与曲式结构(乐段感)是目前最大的短板,未来考虑加入小节/拍号 token 帮助模型把握节奏,并做更长程的规划。有用户提出更进阶的需求:希望模型能根据旋律生成 3-4 声部的巴洛克式伴奏,并输出可导入编曲软件的 MIDI 文件。
一点思考
这个项目最值得学习的地方不在”音乐 AI”本身,而在于工程取舍:MIDI 表示法直接决定推理速度与生成质量、数据清洗优先于数据堆量、DPO 修正”平均正确”的偏差。端侧 125M 模型能做到实时续奏,也再次证明——把问题表征做对,小模型也能干大事。
原文:Training a 125M-parameter Model to Autocomplete Piano
HN 讨论:news.ycombinator.com/item?id=49373456