上一篇用三张地图梳理了全双工语音对话系统的设计空间,Moshi 在每张地图上都是绕不开的坐标:token 级双工(L2)的开创者、「并行多流」流派的代表、五状态全可达的少数系统之一。那篇是横向比较,这篇换成纵向解剖——把 Moshi(Kyutai,2024 年 9 月,arXiv:2410.00037)从 codec 到训练配方一层层拆开,必要处直接看开源实现的代码。
先把结论放在前面:Moshi 真正的贡献不是「延迟低」,而是把口语对话整个变成了一个语言建模问题。没有 VAD、没有轮次切分、没有「用户说完了吗」的判断模块——用户在说、自己在说、双方沉默、互相重叠,全都只是同一个序列模型笔下不同的 token 组合。
一、级联管线到底卡在哪
传统语音助手是一条流水线:VAD 检测你说完了 → ASR 转文字 → LLM 生成回答 → TTS 合成语音。论文开篇点了它的三宗罪:
- 时延叠加。 每个组件都要等上游出完整结果,全程好几秒;而真人对话的响应间隔,跨十种语言测量的平均值是 230 毫秒。
- 文本瓶颈。 对话的中间表示是文字,情绪、口音、笑声、迟疑、环境音——一切写不成字的信息都在 ASR 那一步被扔掉了。
- 轮次假设。 流水线预设「一人说完另一人再说」,但真实对话里 10–20% 的时间是重叠语音,还有大量「嗯」「对」这类不抢话的附和(backchannel)。级联系统在结构上就表达不了这些。
这三个问题同根同源:对话被表示成了文字轮次的序列,而不是两条同时流动的声音。Moshi 的解法是釜底抽薪——直接建模声音本身。
二、总览:三个组件,一张总谱
Moshi 由三个部件组成,各自对应论文的一章:
- Helium:7B 参数的文本 LLM 底座,从零预训练 2.1T token,提供知识和推理能力;
- Mimi:流式神经音频 codec,把 24 kHz 波形压成每秒 12.5 帧、每帧 8 个离散 token,再无损地还原回声音;
- RQ-Transformer:生成架构。大号时间 Transformer(Helium 初始化)沿时间轴走,小号深度 Transformer 在每个时间步内部把当步的所有 token 一口气出齐。
推理主循环出乎意料地朴素(摘自 moshi/run_inference.py,略有删减):
frame_size = int(mimi.sample_rate / mimi.frame_rate) # 24000 / 12.5 = 1920 采样点 = 80ms
for chunk in audio.split(frame_size): # 每 80ms 一帧
codes = mimi.encode(chunk) # [B, 8, 1] 用户这帧的 8 个 token
tokens = lm_gen.step(codes) # 喂进去,采样出 Moshi 这帧的 1+8 个 token
if tokens is not None:
out_pcm = mimi.decode(tokens[:, 1:]) # 后 8 个还原成 80ms 声音
text_id = tokens[0, 0] # 第 0 个是文字 token
每 80 毫秒转一圈:收一帧用户的声音,吐一帧自己的声音,外加一个文字 token。没有状态机、没有打断检测——「让不让用户说话」这件事,全部溶解在 lm_gen.step 的概率分布里。
下面按数据流动的顺序,把三个部件依次拆开。
三、Mimi:把 80 毫秒的声音压成 8 个整数
语音 tokenizer 的通用背景(语义 vs 声学的矛盾、RVQ 原理、各家路线)在语音 tokenizer 一篇里讲过,这里聚焦 Mimi 自己的取舍。
3.1 帧率账:为什么必须是 12.5 Hz
Mimi 的编码器是 SeaNet 因果卷积栈:四级降采样(步幅 4、5、6、8)把 24 kHz 压到 25 Hz,瓶颈处再加一个步幅 2 的卷积到 12.5 Hz,每帧对应 80 毫秒、一个 512 维向量。瓶颈两侧各放一个 8 层因果 Transformer(上下文约 10 秒),量化用 8 本码书、每本 2048 个条目——总码率 1.1 kbps。
12.5 Hz 不是随手选的。时间 Transformer 是个 7B 模型,每生成一帧音频就要做一次完整前向,帧率直接决定了它每秒要跑几次。50 Hz 的 codec(SpeechTokenizer、大多数 HuBERT 系)意味着每秒 50 次 7B 前向,实时无从谈起;12.5 Hz 把预算压到每秒 12.5 次,才给实时留出了空间。反过来,帧率越低每帧要塞的信息越多,重建质量越难保——Mimi 用瓶颈 Transformer 和下面两个训练技巧把质量拉了回来。
3.2 split RVQ:语义和声学分家
和 SpeechTokenizer 一样,Mimi 把自监督语音模型(WavLM)的表示蒸馏进第一本码书,让它专管「说了什么」;但结构上有个关键改动。SpeechTokenizer 是串行残差:第一层管语义,后面的层量化第一层的残差——于是声学信息被迫住在「语义的余数」里,两边互相拖累。Mimi 改成并联:一个单独的语义 VQ 和一条 7 层的声学 RVQ 各自量化同一个输入向量,输出相加。
# moshi/quantization/vq.py — SplitResidualVectorQuantizer.forward(简化)
semantic = self.rvq_first(x, frame_rate) # 1 层 VQ,蒸馏 WavLM,管内容
acoustic = self.rvq_rest(x, frame_rate) # 7 层 RVQ,管音色与细节
quantized = semantic.x + acoustic.x # 并联相加,而非串行残差
codes = torch.cat([semantic.codes, acoustic.codes], dim=1) # [B, 8, T]
消融数据很直观:不蒸馏时语义码书的音素可分性(ABX 错误率,越低越好)只有 23.3%,蒸馏进单条 RVQ 第一层降到 6.5%,但重建质量(MUSHRA 主观分)从 65.9 掉到 57.8;改成 split 并联后,ABX 8.1%、MUSHRA 64.0——两头都保住了大半。
3.3 反直觉的发现:只用对抗损失,音质更好
神经 codec 的标配是「重建损失 + 对抗损失」。Mimi 试了把多尺度梅尔谱重建损失整个扔掉、只留判别器的对抗损失和特征匹配损失,结果客观指标暴跌(VisQOL 从 2.82 掉到 1.84),人耳评测却大涨——MUSHRA 从 58.8 升到 81.0,反超码率更高、帧率 6 倍于它的 RVQGAN(31.3)。论文顺带发出感慨:音频客观指标和人耳感受的相关性差到值得警惕。
另外两个细节:训练时 50% 的概率完全跳过量化、把未量化的连续向量直接喂给解码器(提升低码率下的质量);开源发布的 Mimi 权重其实带 32 层码本(训练用了量化器 dropout,码率可伸缩),Moshi 只取前 8 层。
四、RQ-Transformer:一个时间步里的两级自回归
4.1 先算一笔序列长度的账
一帧 8 个 token,摊平成一条序列就是每秒 100 个 token——5 分钟对话 30,000 步,一个 7B 模型每秒还得前向 100 次,长度和算力双重爆炸。对比之下英语语音的文字转写每秒才 3–4 个 token。多码本怎么在自回归模型里安放,是所有音频 LM 的必答题(语音 tokenizer 一篇列过四种方案)。
Moshi 的答案是 RQ-Transformer(源自 Lee et al. 2022 的图像生成工作,UniAudio 等也用过):把「序列 × 深度」这个二维网格拆给两个模型——
- 时间 Transformer(7B,Helium 架构):每个时间步只跑一次,输入是上一步全部 17 个 token 的嵌入之和,输出一个上下文向量 z_s;
- 深度 Transformer(6 层、1024 维,约 1 亿参数):以 z_s 为条件,在时间步内部沿深度方向自回归——先出文字 token,再出语义 token,再逐层出声学 token,每个新 token 喂回去预测下一个。
于是 7B 模型的步数回到 12.5 Hz,深度方向的 K 次预测交给小模型——它小到在整个推理预算里几乎可以忽略。流式推理里这个分工长这样(models/lm.py 的 depformer_step,简化):
prev_token = text_token # 深度方向从文字 token 起步
with lm_model.depformer.streaming(B):
for cb_index in range(8): # 语义 1 层 + 声学 7 层
logits = lm_model.forward_depformer(cb_index, prev_token, transformer_out)
prev_token = sample_token(logits, temp=0.8, top_k=250)
depformer_tokens.append(prev_token) # 采出一个,喂回去出下一个
一个容易忽略的细节:深度 Transformer 对每个深度位置用独立的一套权重(投影层、嵌入、前馈都不共享,depformer_weights_per_step)。文字→语义→声学各层的「翻译任务」本就不同,参数分家后各干各的;模型够小,多几套权重无关痛痒,消融里这一手实打实提升了质量。
4.2 声学延迟:让声学 token 晚一拍
把 8 个码本直接按同一时刻对齐建模,生成并不稳定。Moshi 引入声学延迟:声学 token 相对语义 token 整体后移 τ 帧(预训练 τ=2,微调后 τ=1)。直觉是:同一帧的语义和声学 token 相关性极强,硬要深度 Transformer 在一步内把它们全预测对,等于逼一个小模型拟合复杂的联合分布;错开一两帧后,「这一帧说什么」由大模型先定,声学细节参考着已经出炉的语义慢慢补,相关性被时间差解耦了。
消融给了很强的证据:延迟模式 [0,2,…,2] 下,不用 RQ-Transformer(8 个独立分类头并行预测)困惑度 135.4,用了降到 36.8;[0,0,…,0](零延迟,理论时延 80ms)比 [0,1,…,1](160ms)的生成质量差一截,而再放宽到 [0,2,…,2](240ms)收益就不大了。最终模型选 τ=1:理论时延 = 80ms(攒一帧)+ 80ms(声学延迟)= 160ms,实测约 200ms——低于真人对话的 230ms 平均间隔。
五、多流与内心独白:17 行 token 总谱
到这里还只是「单人音频的语言模型」。Moshi 的两个招牌设计——多流建模和 Inner Monologue——都发生在「每个时间步建模哪些子序列」这一层。
5.1 多流:对话就是两条并行的音频
对话有两个声源,Moshi 就建两组音频流:自己的 8 个码本一组,用户的 8 个码本一组,同一时间步竖着摞在一起。轮次的概念从输入表示里彻底消失了——重叠就是两条流同时有语音,附和就是短促的插入,沉默就是双方都输出「自然静音」。训练时两条流都算损失(模型学的是整场对话的联合分布);推理时自己的流采样、用户的流用真实麦克风输入覆盖。这带来一个妙用:让模型把用户流也采样出来,就能凭空生成整场双人对话,论文用它做离线评测。
5.2 Inner Monologue:先想文字,再发声音
纯音频域建模已经能对话,但语言质量平平。Moshi 再往总谱顶上加一行文字流:模型自己说话内容的转写(Whisper 打词级时间戳),按 12.5 Hz 对齐铺到时间轴上——每个词的 token 放在词的起始帧,词与词之间填 PAD,下一个词开始前一帧放一个 EPAD 标记「padding 结束」。英语口语里 PAD 约占 65%。
每个时间步内,深度方向的预测顺序是文字 → 语义 → 声学:模型先用文字敲定「接下来说什么」,语义 token 在文字的条件下生成,声学 token 再落地成音色。这是 AudioLM「先语义后声学」层级的自然延伸——把文字当成了第零层、最粗粒度的语义 token。它和 SpeechGPT、Spectron 那类「先写完整段文字再朗读」(Chain-of-Modality)的本质区别在于:文字和音频逐帧交错,每一步都同时出文字和声音,所以流式毫不受损。
注意一个不对称:只有 Moshi 自己的流配文字,用户流没有——给用户的声音配文字等于要求内置一个实时 ASR,违背端到端的初衷。
两样拼起来,每步 K = 1 + 8 + 8 = 17 个 token,开源代码里那行配置就是整篇论文的浓缩(models/loaders.py):
"n_q": 16, "dep_q": 8, # 16 条音频流;深度 Transformer 只采样自己那 8 条
"delays": [0, # 文字流
0, 1, 1, 1, 1, 1, 1, 1, # Moshi:语义 0 延迟,声学 7 层各延迟 1 帧
0, 1, 1, 1, 1, 1, 1, 1], # 用户:同样的排布
实现上,延迟不是真的把序列错开存储,而是一个环形缓存的读写偏移:用户 token 写进 (offset + delay) % CT 的位置,每步统一从 offset 读——写得越「远」,被读到的就越晚。想让 Moshi 立即开口也有了外科手术式的手段:在文字流上强制采一个 EPAD,下一帧它就得说话。
5.3 它为什么值得:消融数字
Inner Monologue 是全文杀伤力最大的设计。同等条件下加上它,生成语音转写的 NLL 从 3.65 降到 2.77,平均生成长度从 602 字符跳到 1920——不加的模型说着说着就沉默坍缩了。口语问答上更夸张:Web Questions 9.2 → 26.6,Llama Questions 21.0 → 62.3,TriviaQA 7.3 → 22.8,全线接近三倍。而代价只是每步 17 个而不是 16 个 token——推理开销几乎不变。
六、改一个延迟参数,就得到流式 ASR 和 TTS
文字流和音频流对齐铺在同一条时间轴上,还送了一个意外彩蛋。给文字流也加个延迟,控制「谁先落子」:
- 文字滞后音频 2 秒 → 流式 ASR。音频 token 用真实输入 teacher-force,只采样文字流:模型「听」了两秒再落笔,写出的就是转写——还自带 80ms 精度的词级时间戳。
- 文字超前音频 2 秒 → 流式 TTS。文字流用给定文本 teacher-force,采样音频流:声音跟着两秒前的文字走,就是 TTS。
TTS 模式有个精巧的推理技巧:输入文本没有 PAD/EPAD 信息,怎么知道每个词该占几帧?答案是让模型在 PAD 和 EPAD 上自由采样——它想采别的 token 时,就强行替换成待合成文本的下一个词。节奏由模型自己掌握,词的内容由外部指定;还可以监控 PAD 占比、微调其 logits 来控制语速。
这不是纸面推演:Moshi 的指令微调数据——两万多小时合成对话——就是用这个 TTS 模式(音频延迟 2 秒的多流版本,在 170 小时真实双声道对话上微调)生成的。同一套架构闭环自举,是全文工程美感最强的一笔。
七、训练配方:从 700 万小时到一个会聊天的声音
Moshi 的能力是四个阶段垒起来的,每阶段解决一个明确的问题:
- 音频预训练(100 万步)。700 万小时音频(Whisper large-v3 转写),单流、5 分钟长序列。关键的防遗忘设计:一半训练步仍是纯文本批次(用 Helium 的数据和独立的优化器状态)——消融显示去掉这一半,口语问答明显下滑。文字与音频的延迟在 ±0.6 秒间随机抖动,算是对齐的正则化。
- 多流后训练(10 万步)。用 PyAnnote 说话人分离把单声道音频拆成「主讲人 / 其余人」两条流,教会模型双流格式。但这种模拟数据没有真实重叠,不活跃一侧是纯零静音——离真对话还差关键一步。
- Fisher 微调(1 万步)。Fisher 语料:2000 小时、2003–2004 年采集的双声道电话对话(8 kHz,用 AudioSR 超分到 24 kHz),有真实的打断、抢话和附和。全双工的行为能力就是在这一步学到的。
- 指令微调(3 万步)。两万多小时合成对话(见上节):Helium 生成脚本、自家 TTS 配音。Moshi 的声音固定为一位配音演员(录了 70 多种语气风格的独白),用户侧则每条随机换声线。数据里埋了不少心思:误导性问题(「埃菲尔铁塔在北京吗?」)训练它说「不」;故意拼错的词训练它请用户重复;数学、语法、常识单轮问答补短板;安全对话教它拒答。用户流还叠加了随机增益、噪声、混响,甚至模拟 Moshi 自己的声音从用户麦克风漏回来的回声(延迟 100–500ms、幅度 0–0.2)——不做这一手,实际部署时它会被自己的回声打断。
损失函数上,文字流独占一半权重,音频那半里语义 token 权重 100、声学 token 权重 1——层级越靠上越金贵。
八、效果与代价:几组关键数字
口语问答(音频进、音频出):Web Questions 26.6 / Llama Questions 62.3 / TriviaQA 22.8,大幅超过 SpeechGPT(6.5 / 21.6 / 14.8)和 Spectron(6.1 / 22.9 / –),而后两者还不支持流式。天花板是文本模式的 Helium(32.3 / 75.0 / 56.4)——差距就是「用声音思考」的税。TriviaQA 掉得最狠,论文归因于指令数据全是口语风格,书面长难句见得太少。
知识保持:MMLU 从 Helium 的 54.3 降到 Moshi 的 49.7。音频训练侵蚀文本知识是全行业通病,Moshi 靠「一半文本批次」把损失控制在 5 分以内(对比 Spirit-LM 的 36.9,高了近 13 分)。
对话动力学:让 Moshi 自己生成双方对话,统计轮换指标(温度 1.0):发声段均长 50.8s vs 真实数据 51.1s,停顿 7.0s vs 6.4s,轮间静默 4.5s vs 4.2s,重叠 4.1s vs 3.3s——四项全部贴近 Fisher 真实分布,而上一代全双工模型 dGSLM 的对话语义困惑度是它的 2.5 倍。轮换节奏不是规则写出来的,是从数据里学出来的。
流式 ASR / TTS(2 秒延迟版):ASR 在 LibriSpeech test-clean 5.7% WER,TTS 4.7% WER——不是 SOTA(专职流式 ASR 约 3.6%),但它俩和对话模型是同一个架构、同一个损失函数,只差延迟参数。
量化部署:线上 demo 用 8-bit 权重(MMLU 掉约 2 分);压到 4-bit 后音质几乎无损,但 MMLU 掉 5–10 分——先崩的是语言能力,不是声音。2-bit 时则出现胡言乱语、复读、静音退化成背景噪声等典型伪影,论文附录还给了一套用 token 熵谱自动检测这些伪影的方法。
九、放回地图:Moshi 之后
回到全双工那篇的框架看,Moshi 的位置很清楚:双工决策在 token 流内部(L2),理论上五种对话状态全可达;打断用户不需要任何专门模块——它时刻在预测用户流,「用户开口了」这个事件被建模本身吸收,自己让位就是把输出流坍缩成自然静音。同时它的局限也在地图上:只会英语、知识弱于同尺寸文本模型、上下文 3000 步约合 4 分钟;而行为风格深度绑定训练数据——全双工能力主要来自二十年前的 Fisher 电话语料,这也是那篇综述「数据格子覆盖决定行为」论断的最佳注脚。
工程遗产同样可观:Mimi 成了低帧率流式 codec 的事实标杆(12.5 Hz 后来被 MOSS-Audio-Tokenizer 等一串工作沿用);「时间 + 深度」双 Transformer 和文字先行的思路在后续语音模型里随处可见;Kyutai 自己则把这套架构继续衍生——同声传译模型 Hibiki、生产级流式 STT/TTS,都是同一份总谱换不同的延迟与流配置。
回头看,Moshi 最漂亮的地方在于它的「减法」:没有为全双工添加任何专门机构——没有打断检测器、没有轮次管理器、没有状态机。它只是把对话如实地表示成几条并行的 token 流,然后相信一个足够好的序列模型能学会其中的一切。后来的事实证明,这个信任是值得的。
参考资料
- Moshi: a speech-text foundation model for real-time dialogue(arXiv:2410.00037,Défossez et al., Kyutai, 2024)
- kyutai-labs/moshi——官方开源实现(PyTorch / MLX / Rust),本文代码摘引自其中
- Moshi 在线体验
- 相关论文:AudioLM(语义→声学层级生成)、SpeechTokenizer(语义蒸馏进 RVQ)、dGSLM(第一个全双工生成模型)、RQ-Transformer
- 站内相关:全双工语音对话系统三张地图、深入拆解 LSLM、深入拆解 OmniFlatten、语音 tokenizer、MOSS-Audio-Tokenizer