跳转至

Method

这一页是长程知识,不随实验变。 概念用定义 + 公式 + 图固定; 会变的参数集中在最后一张表 —— 改了只动那一行,不动正文。

1 问题的形式化

CosyVoice3 的 flow-matching 解码器把高斯噪声积分成梅尔谱:

\[\frac{dx_t}{dt} = v_\theta(x_t,\,t,\,c), \qquad x_0 \sim \mathcal{N}(0, I), \qquad x_1 = \text{mel}\]

其中 \(x \in \mathbb{R}^{1\times 80\times K}\)积分结果是 mel 而非波形,波形还要过 HiFT 声码器 (相位由声码器生成,不在 \(x_1\) 里)。

DFD (Dual-Forward Decomposition) 在同一个 \(x_t\) 上跑两遍 DiT,一遍喂情感参考、一遍喂中性参考:

\[A := v_{\text{emo}} - v_{\text{neu}}, \qquad v_{\text{ctrl}} = v_{\text{neu}} + \alpha \cdot A\]

\(\alpha\)情感强度旋钮

\(A\) 是构造出来的,不是学出来的

训练里没有任何损失直接监督 \(A\)(见 §3)。\(A\) 只在推理时被构造。 这一点决定了很多现象 —— 比如 \(L_{\text{ortho}}\) 为何完全无效(见 05 Findings)。

2 架构

flowchart TB
    subgraph LLM["LLM 通道 —— 情感的「源」"]
        RT["参考文本"] --> L
        TT["目标文本"] --> L
        RA["参考音频<br/>→ prompt speech token"] --> L
        L["LLM"] --> GT["gen_token<br/>长度 L_e"]
    end
    subgraph FLOW["Flow 通道 —— 情感的「闸门」"]
        GT --> MU["μ  (1,80,K)<br/>K = 2·L_e"]
        RA2["参考音频 mel<br/>→ 前缀位置"] --> COND["cond"]
        MU --> EST
        COND --> EST
        XT["共享状态 x_t"] --> EST["DiT estimator"]
        EST --> VE["v_emo"]
        EST --> VN["v_neu"]
        VE --> A["A = v_emo − v_neu"]
        VN --> A
        A --> VC["v_ctrl = v_neu + α·A"]
    end
    VC --> INT["∫ dt → mel"] --> HIFT["HiFT → 波形"]

2.1 ICL 的输入结构

零样本推理里参考占据前缀位置:

token = cat([prompt_token, gen_token])       prompt 来自参考音频
cond  = [参考的 mel | 生成段全零]              前缀有内容,生成段留空
spks  = 说话人嵌入,逐帧广播

三条容易记错的事实:

  1. prompt_text参考的文本,不是目标文本 —— 参考与目标文本解耦, 因而可以固定一组参考、任意替换目标文本。
  2. 输出要切掉前缀,只保留生成段。
  3. 各分支前缀可以不等长(各走各的,反正丢弃),但生成段必须等长才能逐帧混合。

2.2 张量形状与单位

形状 说明
\(x, v, \mu, \text{cond}\) \((1, 80, K)\) 80 = mel 频带
\(K\) 帧数 @ 50 Hz (hop 480 @ 24 kHz),1 帧 = 20 ms
\(L\) token 数 @ 25 Hz,1 token = 40 ms
换算 \(K = 2L\) token_mel_ratio = 2

2.3 共享状态 ≠ 输出平均

所有分支在同一个 \(x_t\) 上求速度(类似 CFG),混合速度后只积分一次不是各自积分完再平均波形 —— 后者会重影。这也是 \(\alpha>1\) 外推有意义的原因: 在同一点上做方向外推。

3 训练目标

\[\mathcal{L} = \mathcal{L}_{\text{FM}}(v_{\text{emo}}, u_{\text{emo}}) + \mathcal{L}_{\text{FM}}(v_{\text{neu}}, u_{\text{neu}}) + \lambda_{\text{ortho}} \mathcal{L}_{\text{ortho}}\]
  • 两个 FM 项各自完整、零截断,分别拟合情感样本与中性样本
  • 没有交叉项 —— 因此等价于普通的单分支 flow-matching 微调
  • \(\mathcal{L}_{\text{ortho}}\)\(K_c = \min(K_{\text{emo}}, K_{\text{neu}})\) 上算 \(A\) 的跨通道余弦 —— 已证明完全无效(Findings F6)

4 量测

指标 模型 方向 用途
情感 emotion2vec_plus_large 目标情感的概率
CER Paraformer-zh(标点归一化后) 字是否听得清
SECS campplus 音色相似度
MOS UTMOS22 strong 音质,<2.5 视为破音

派生量:

  • mono = 相邻 \(\alpha\) 步里分数上升的比例,按 (key, 情感) 分组后平均。7 个 \(\alpha\) → 量化到 1/6 格点
  • delta = \(\alpha{=}1\)\(\alpha{=}0\) 的情感分之差

情感指标已被证明无法表达「部分情感」

emotion2vec 输出饱和的近 one-hot 分布,详见 Findings F5混合配方的情感分读不出结论。 这是当前的头号瓶颈。

5 方法论准则

从踩过的坑里提炼的可复用判断。事故经过留在实验页,这里只留原理。

5.1 分布跨在 1 两侧时,「统计量的函数」≠「函数的统计量」

截断率算错过两次(11% → 17% → 实测 12.4%)。错因是把中位比值代进截断公式, 而正确做法是逐对算截断再取中位。时长比中位 1.03 看着无害(情感有长有短、大致抵消), 但逐对截断中位有 12.4%、最坏 49.6%。

规则:比值类统计量,先逐样本算目标量,再聚合。

5.2 「跑起来了」不等于「跑对了」—— 要有落卡/落盘校验

--gpu 参数曾完全无效(deepspeed 在 import 期就初始化 CUDA),训练全落到物理卡 0, 挤到别人的任务上。症状是日志在跑但目标卡显存为 0。

规则:凡是「指定去哪」的参数,启动后必须验证它真的去了那里。 同类:评测报告 n_wavs=0 却退出码 0(读错了 manifest 文件)。

5.3 跨样本比较必须先解决对应关系

第一版跨句一致性测量是逐帧算余弦 —— 但第 \(k\) 帧在两句话里是不同音素, 即使 \(A\) 完全情感专一也测不出相关。改为子空间重叠(与时间对齐无关)才有意义。

规则:比较两个变长序列前,先问「第 k 个位置在两边是同一个东西吗」。

5.4 指标饱和时,低分读不出结论

一个真正成功的 50/50 混合本就不该在任一端点得高分。所以低分既可能意味着混合成功、 也可能意味着音频退化 —— 饱和的分类器无法区分这两者

规则:用一个指标下结论前,先确认它在你关心的区间里有分辨率。

5.5 对照要能隔离单一变量,且要有「必然相同」的自检臂

六臂调色板里,纯情感配方在六臂逐条完全相同(单分支走恒等 warp,理应如此)。 这条「必然相同」的自检证明了所有差异确实来自混合配方,而非别的扰动。

规则:设计对照时留一个理论上必须相同的臂,它是整个对比有效性的证明。

5.6 n < 10 的数字只作线索,不写进结论

写作约定。定性结构通常稳,百分比数值在 n<30 时不可信。 本项目多数调色板实验 n=3 句,属弱证据

5.7 观测到「没有结构」,不等于「不能施加结构」

M5 测出 \(A\) 的能量在前半/后半几乎无差异(比值 1.026), 我据此预测「前 K/2 vs 后 K/2」的分段 α 不会有效。M6 实测推翻了它 —— half 档定位良好(比值 3.3)。

错在混淆观测干预:前者问「\(A\) 天然在哪里强」,后者问「人为在某段施加能否只改那段」。 天然没有结构,不妨碍施加人工结构。

规则:从"分布长什么样"推不出"控制能不能施加"。想知道干预的效果,就得做干预。

5.8 接口「看起来一致」只说明最内层一致

M9 里 CosyVoice2 与 CosyVoice3 的 estimator 签名完全相同,我据此以为混合代码能原样跑。实际差异分三层,逐层才暴露: flow 外壳(token→μ 的路径不同)、声码器(参数表不同)、LLM prompt 约定。

规则:跨模型复用代码时,用目标模型自己的推理路径当参照逐层核对, 不能凭签名相同就假定行为相同。

6 参数与技巧

这一节会变。 上面是概念,这里是当前取值。

参数 当前值 为什么 变更历史
token_mel_ratio 2 模型固有
采样率 / hop 24 kHz / 480 模型固有
n_timesteps 10 Euler 步数
训练 lr / batch 1e-5 / 2 与 v4 对齐
\(\lambda_{\text{ortho}}\) 0.01 沿用 v4 已证无效,保留仅为与 v4 可比
训练步数 53,700 (5 epoch) 与 v4 对齐,便于消融
\(\alpha\) 扫描网格 0, .25, .5, .75, 1, 1.25, 1.5 含外推段
\(K^*\) 夹紧区间 \([0.5\min K_e,\ 2\max K_e]\) \(\alpha>1\)\(w_{\text{neu}}<0\),\(K^*\) 会崩到 10 帧 2026-08-14 加入
warp 平滑过渡 未实现 分段 \(\alpha\) 需要,否则速度场突变 OPEN
LLM 采样种子 按情感固定 采样噪声 1.288 会淹没时长曲线 2026-08-14 起
本项目可用 GPU 卡 7 分配 2026-08-17 由卡 0–3 改为卡 7