我把一个 125M 参数的 Transformer 压进 iPhon
训练数据用的是 MAESTRO v3.0 里 1300 小时真人演奏,切片成 4 秒窗口、stride 1 秒,量化到 100Hz 时间分辨率。Tokenizer 自己写的,把 note-on/off、velocity、pedal、tempo 变化全编成单一 token 序列,vocab 只有 1.2k,特意没用现成的 REMI 或 MIDI-LLM 那套,省下 embedding 矩阵显存。模型结构就是标准 decoder-only,8 层、8 头、d_model=512、ffn=2048,加了 ALiBi 位置编码让推理时能外推到更长序列。参数量卡在 125M 是因为 Core ML 对 Neural Engine 的 op 支持还不完整,稍微大点就得回退 GPU,功耗直接翻倍。
最折腾的是量化。先试了 int8 线性量化,推理快了但 velocity 分布崩了,弹出来全是机械力度。后来改用 GPTQ 4-bit 分组量化(group_size=128),配合 calibration set 里专门加了 200 段极弱/极强力度片段,终于把 KL 散度压到 0.03 以内。Core ML 编译时还得手动把 LayerNorm 和 GeLU 融进前一层 Linear,否则 ANE 会把这两层丢给 CPU 跑,单步延迟多出 3ms——乘以 108 steps 就是 300ms 感知延迟,演奏时手感明显拖后腿。
App 端用 SwiftUI + AudioKit 写的,MIDI 输入走 CoreMIDI 回调,拿到前 500ms 的 token 喂进模型,采样温度 0.7、top-p 0.9、重复惩罚 1.1。生成的 token 流再经过一个轻量规则后处理:把物理上不可能的重叠 note-off 修掉、把超出踏板范围的 CC64 裁剪、把突变超过 30 BPM 的 tempo token 平滑。最后再按原始 100Hz 时间戳还原成 MIDI 事件推给 AudioKit 播放。
目前已知坑:连续弹奏超过 2 分钟内存会涨到 300MB 左右,疑似 KV cache 没释放干净,正在排查;另一个是极端高音区(>C7)生成容易幻觉出不存在的泛音,训练集里这段分布太稀疏。想试试把最后两层换成 MoE 只激活 25% 参数,看能不能再压低功耗。
代码和模型权重都放在 GitHub,MIT 协议。想自己跑的直接 pip install coremltools==8.1 然后用我提供的 .mlpackage 拖进 Xcode 就能编译,不用重新训练。要是有想法怎么把踏板语义建模得更细腻,欢迎交流。
全部回复 (10)
要不试试加个简单的鼓点做骨架?哪怕只是 kick + hat 循环,结构感马上就不一样了
mic 拾钢琴更麻烦,房间混响、键盘敲击噪音、力度不均全是干扰项,想做干净 onset detection 得先花俩月调预处理
真想省事买个二手 25 键 MIDI 键盘 50 块钱搞定,别跟音频信号处理过不去了
当然你要是纯想折腾 DSP 当我没说 😅