TTS 指令被静默丢弃,A/B 测试失效
作者测试 Qwen3-TTS 语速指令时发现,在所用实现中,0.6B 模型并未收到指令,但仍正常生成音频,使部分 A/B 比较失去意义。案例强调,比较参数效果前应先验证参数是否实际进入模型并影响输出。
AI 深度解读
一次看起来完整的语速 A/B 测试,实际上有一半没测到目标参数。作者给 Qwen3-TTS 传入加速指令,拿到了正常音频、零字错率和略有变化的语速,随后判定指令效果有限。第二天查代码才发现:小模型的指令早被 SDK 清空了。
这篇最有用的地方,是提醒我们:输出正常,只能证明流程跑通,不能证明传进去的参数参与了生成。 接入 AI SDK、比较提示词或模型配置时,这个区别很容易被忽略。
语速变了,为什么还不能算指令有效
作者想把旁白提到约 230 words per minute,即每分钟 230 个词。实验使用 qwen_tts 0.1.1,在 torch 的 MPS 后端运行 Qwen3-TTS CustomVoice 的 0.6B 和 1.7B 模型。
generate_custom_voice 没有 speed 或 rate 参数,只提供自由文本参数 instruct。作者分别要求约 230 wpm,以及拍卖师般的快速播报,再测量同一句 18 词文本的有声部分。
| 模型 | 230 wpm 指令 | 拍卖师指令 | 无指令对照 |
|---|---|---|---|
| 0.6B | 139.1 wpm | 131.8 wpm | 129.9 wpm |
| 1.7B | 140.6 wpm | 126.3 wpm | 128.3 wpm |
所有结果的字错率(WER)都是 0%。最初的决策记录把收益写成相对 126–130 基线提升 0–8%;按匹配的无指令对照计算,最大增幅其实是 140.6 对 128.3,约 +9.6%。
这些结果确实离 230 wpm 很远,却不足以判断差异来自指令,还是单次生成的波动。
真正的问题藏在 SDK 中。下面这段代码决定了指令能否继续进入模型:
if self.model.tts_model_size in "0b6": # for 0b6 model, instruct is not supported
instruct = None
关键是第二行:0.6B checkpoint 的尺寸标签是 0b6,因此 instruct 在分词前就被设为 None。1.7B 使用不同标签,指令才得以通过。
这个分支没有异常、警告或日志。所在包装文件没有日志调用;模型文件里的两处 warning_once 针对梯度检查点,也不提示此事。文档只说明 instruct 可选,None 按空指令处理,没有说明尺寸限制。
所以,0.6B 的两次「有指令」测试,其实都是无指令生成。其中一次仍从对照的 129.9 跑到了 139.1 wpm。看起来像微弱收益的数字,背后根本没有指令参与。
能保留的结论,比原先窄得多
0.6B 这半组实验不能评价指令效果。1.7B 确实收到了指令,但每个提示词只生成一次,因此只能支持:这两个提示词在这两次运行中,没有把语速推到 230 wpm。
不能据此断言 1.7B 完全不响应语速指令,也不能拿另一尺寸模型的波动,直接证明它的变化都是随机性。原实验没有证明这个控制项能稳定改变输出。
作者最终把速度控制交给逐片段的 ffmpeg atempo,该处理阶段拒绝 0.5–2.0 之外的倍率。校准时,Qwen3 原始语速约为 131 wpm,现有引擎的有效语速为 183 wpm;不同说话人需要 1.41–1.76 的倍率才能匹配。
选择 0.6B 作为默认模型另有依据:它的峰值内存为 3.22 GiB,满足要求有 4 GiB 可用内存的规则;1.7B 的 5.6 GiB 不满足。参数实验失效,不代表这个内存取舍也失效。
控制项要验,测量工具也要验
后续声音克隆测试采用了更直接的办法:把控制项推到两个极端,看目标指标是否分开。
Qwen3 Base 的公开克隆方法没有 instruct 参数,issue #25 也称 Base 不支持指令。作者仍通过非公开路径传入指令 ID。结果,「excited」的基频(F0)均值/标准差为 237/59 Hz,语速为 6.80 音节/秒;「calm」为 234/56 Hz、6.46 音节/秒。单次结果没有显示明显的情绪分离。
参考音频的作用更明显。克隆韩语时,平静参考得到 221/52 Hz、5.99 音节/秒,兴奋参考得到 271/86 Hz、6.42 音节/秒;换成英语,同样两段参考只得到 221/42 和 217/41 Hz,跨语言情绪迁移较弱。
于是作者采用按情绪准备的参考音频库。在作者自己的五种情绪录音上,Qwen 输出的 F0 跨度为 23.3 Hz,语速跨度为 1.94 音节/秒;与原始朗读的秩相关分别为 0.7 和 0.6。秩相关看的是变化顺序是否一致,但 n=5,只能看方向。
VoxCPM2 也暴露出配置依赖:方括号风格前缀在替代说话人上能区分情绪,换成作者的声音后,五种情绪的 F0 跨度只有 7.3 Hz,秩相关为 -0.41。原型配置有效,不保证交付配置有效。
测量工具同样需要对照。Base 内置说话人编码器给克隆声音打出 0.987–0.994 的余弦相似度,却也给另一个男性预设声音打出 0.946。换成不属于这两个 TTS 系统的 WavLM-SV,不同说话人得分为 0.275,同一说话人的其他情绪参考为 0.977–0.992,区分才明显起来。
指标会返回数字,不代表它能区分你关心的差异。
我的判断
作者最扎实的发现,是 qwen_tts 0.1.1 确实在包装层丢弃了 0.6B 的指令。这有明确代码路径支撑,但不等于已经证明 checkpoint 本身无法响应指令。作者没有绕过包装层验证,后续版本是否改变也没说清。
语速和情绪效果的结论要保守些。速度测试每个设置只合成一次;克隆测试每种组合也只有一次,固定 seed 7 不能替代重复实验。替代参考音频还由 Qwen CustomVoice 生成,可能更有利于 Qwen Base 重现。
听感证据也有限:只有一位听众、15 段韩语音频,五种情绪识别对了 2 种,随机机会水平为 1/5。作者自己的 serene、calm、warm 朗读基频接近,可能影响结果,但这仍是推测。
我认可先验控制项、再做 A/B 的顺序。至于参考音频库和 atempo 是否优于其他方案,原文没有充分的音质与效果对照,只能视为这套旁白流程的工程选择。
能拿来做什么
- 在 Node 服务封装 AI SDK 时,对关键可选参数追踪到底,确认它如何进入请求或模型输入,并记录模型、模式和 SDK 版本。
- 比较提示词前,先选两个差异足够大的设置,重复生成并比较波动;只有目标指标能稳定分开,后续优化才有依据。
- 更换模型尺寸、说话人或语言后,重跑控制项检查,不沿用原型阶段的有效性结论。
- 自动评估声音相似度时,加入已知不同说话人的对照;如果也得到高分,先检查测量工具,再解释模型成绩。