起因是我给自己做的游戏配 boss 曲。目标定得很死:纯音乐,风格对标 DM DOKURO 在 Calamity Mod 里写的那批曲子。
机器是 7900 XT,20GB 显存。素材是官方 OST 45 首加附属/后 DM 时期 9 首,共 54 首,约 14 小时。
前后试过三条路线,全部停在半路。结论先放前面,细节在后面。
路线一:音频 MoE
最早的构想把 MoE 用在音乐生成上:一个总线专家常驻显存管协调,分专家驻内存,训到哪个调哪个进来;架构用 Transformer 混 Mamba。
实测不成立,三个原因。
MoE 的价值是隔离多域冲突。只有一个目标风格时,dense 的样本效率更高;要"多作曲家/多风格"才轮得到 MoE,而且是各专家从共享底座 fine-tune,不是从零。
按职能划分的"总线/编曲/弦乐/管乐/母带"不是 MoE,是专家系统流水线。MoE 要求专家同质,即同形状的 FFN 切片,靠 router 逐 token 挑;上面这些专家异质到不是同一种东西,没有可共享的 token 空间。
“旋律/和声/节奏"这种划分同样不成立。token 里每个位置同时带音高、时值、时序,三个维度不可分,router 学不到分解边界,训出来只是三个同质专家加路由噪声。
另有两处硬约束。窗口砍到 2–3 秒是自毁:DM 的 boss 曲基本是 4 小节 riff 循环,165bpm 下约 5.8 秒,2–3 秒窗口连一个 riff 都装不下。20GB 单卡在底座冻结、优化器卸载的前提下,可训参数 2B 封顶,而且"逐专家轮流驻显存"在梯度与优化器状态上就走不通。
路线二:符号域
第二条路是把"编曲"从音频里拆出来,变成符号问题:LLM 当总监产出结构化 spec(BPM、调性、段落、每层 caption),符号层按 spec 写谱,最后渲染。
表示用 REMI 多轨 token,工具是 miditok,模型是自训的小 Transformer。装环境时踩过一个坑:依赖里钉死的 torch 是 CUDA 版,直接装会把 ROCm 那套覆盖掉,所以用 --no-deps 只装本体。
训练数字看着很好。无条件预训练 midi_lm_uni 跑到 20000 步,ppl 约 2.4;带条件预训练 arranger_lm 15000 步,loss 0.32;再用 DM/Mili 子集微调,ppl 掉到 1.2。
这个 1.2 不是好消息。54 首的规模下,它意味着模型把训练数据背下来了。验证方式很直接:拿一段真实序列当 prompt 续写,原序列能正常解出 10 轨 18004 个音符,说明解码管线没问题;模型自己续写的 1344 个 token 结构非法,乱吐 Program/Velocity,缺 Position/Bar,解码器直接丢弃,渲染出来只有 6 秒空响。
也就是小自回归模型不会稳定地生成合法多轨语法。已知的修法有两条:约束解码,用状态机屏蔽非法 token,强制 Position→Program→Velocity→Duration→Pitch 的顺序;或者换更宽容的表示(事件式的 MIDILike/Structured),牺牲一点结构控制换解码稳定性。
路线三:音频域,YuE2-3B
第三条换回音频。选型排除了 MidiGPT 和 ACE-Step,用 YuE2-3B:公开基准上的成绩最好,而且是白盒,先生成可读的 ABC 谱面再渲染,正好能承载"为什么这样写"这件事。
ROCm 上跑通花了些功夫。仓库依赖里钉的是 CUDA 版 torch,--no-deps 装本体、复用现有 ROCm 栈;权重 7.26GB 加 530MB,下了之后做 SHA256 校验;推理走 torch-eager,不依赖 CUDA Graph;只暴露独显。链路是"规划 ABC → Vocal 转 Ins(保证纯音乐) → 渲染 48kHz 立体声”。第一首 149.6 秒,生成耗时 396 秒。
生成逻辑按"语义驱动"重做了一遍:
- 查 Calamity 官方 wiki,把每首曲子对应的 boss / 环境 / 剧情和 DM 的作曲动机写成
briefs.json。wiki 只覆盖官方专辑曲目,Goozma、Nameless Deity 这类来自别的 mod 的按标题和曲风推断,并标注"非 wiki 依据"。 - wiki 里明确的速度写进
tempos.json当硬约束。实测在文本里写 BPM 完全无效,要 185 给 95,必须改谱面Q:行。 - 弃用随机 seed 抽卡,改成"简报驱动 + 多方案规划搜索择优",择优标准是跟 wiki 目标速度的接近度加上有效时长。
- 后处理统一到 -14 LUFS,超时长的裁掉。
这一轮出了 12 首(Desert Scourge、Perforators、Cryogen、Devourer、Yahron、Supreme Witch 等),配了一个试听页,带 Range 支持,可以拖进度条。
顺带做出来的三件东西
一个可测的验收器。 起点是一段口述:某首曲子从钻地鼓推进入手,几小节后加舒缓合成器表现水,再换电吉他重奏形成第一个峰,停鼓停吉他进提琴谷期,谷尾重新引入鼓铺垫,回到电吉他进高潮,最后用扬琴加鼓点收尾,并和开头对齐成无缝循环。这段叙事翻译成可测指标就是强度弧形状、编排事件密度、循环对齐、音色轮换。实现时先踩了自己的 bug:find_peaks_np 里把 prom 当比例用,音频那边传的却是 2.0dB 阈值,导致音频弧线全是 0;修好后 37/38 首能出弧线。
DM 的作曲语法。 从 45 首原曲里统计出来:强烈偏小调(G#m / C#m / F#m / Em);旋律以 ±1、±2 半音的级进和邻音为主,少量 ±3、±4 跳进;结构上以 4 小节 riff 循环为骨架。
一首曲子的克隆关系。 有一对曲子在设定上就是衍生关系:至尊灾厄的主题曲 Stained, Brutal Calamity 是原作,灾厄之眼那首 Raw, Unfiltered Calamity 属于"失败的克隆体",带前者接近 30% 的力量。验证走三路:ABC 变奏、音频结构相似度、钟声。钟声这条来自一个观察,DM 习惯用钟声衬托强 boss。
为什么停
核心原因是质量上限。符号域受限于数据规模,54 首撑不起从零训的多轨语法模型;音频域受限于"风格适配只调倾向,不提能力上限",LoRA 能加 DM 味,加不出 DM 的作曲水平。
LoRA 学不进"编曲思路"这点也验证过。数据是 22 首、约 3 小时,而编曲思路是整曲级的、离散的、规则性强的知识,低秩增量吃这点数据只能学到局部语法,结果是 riff 语法学会了,编曲决策没学会。架构上也没有承接它的位置,AR 学的是 ABC→token,NAR 学的是 token→音频,中间没有一层负责段落与配器决策。
我试听之后没继续,生成的音频删了,DM 原曲保留。到清理结束时工作区从 47GB 减到 18GB;后来整个 game-music/ 目录一并删掉,现在本机没有这条线的代码和权重。
还留着的东西
资产的形态变了,留下的是结论和方法:验收器的指标口径、从 wiki 反推创作意图再写成生成约束的做法、谱面级的速度约束、符号域"语法合法"这个必须先解决的前置问题。这些都写在了删除前的会话记录里,重开这条线不用从零摸黑。
真正没解决的是中间那一层:怎么让模型在长曲上稳定地产出"多段落 + 配器决策",而不是短循环拼接。约束解码是一个方向,还没跑到出结果那一步。