VibeVoice 的流式模型和长文模型:两套文档写明的定位差异
翻 VibeVoice 仓库最容易犯的一个错,是把 docs/ 下的几份文档当成同一个模型的不同章节。它们不是。docs/vibevoice-realtime-0.5b.md 和 docs/vibevoice-tts.md 描述的是两个模型,代码路径、processor 类、config 结构、文档自己划的适用范围全都分开。你如果拿着长文多说话人的期待去读流式那份文档,会在「只支持单说话人」这一行上被绊一下;反过来拿流式的输入写法去套长文那条线,连入口函数都对不上。
这篇只对照两份文档和对应源码都白纸黑字写明的东西:输入长什么样、生成过程怎么组织、各自把边界划在哪。不比效果。
先把 TTS 那条线的状态说清楚
这一步不能跳过,否则后面的对照会被误读成「两条都能跑的路线二选一」。
仓库 README 的更新记录里,2025-09-05 那条写明:VibeVoice 是面向语音合成社区协作的开源研究框架,发布后发现了与既定意图不符的使用方式,基于微软的负责任 AI 原则,已从该仓库移除 VibeVoice-TTS 代码。与之对应,README 的模型表里 VibeVoice-TTS-1.5B 那一行的 Quick Try 一栏写的是 Disabled,docs/vibevoice-tts.md 的 Installation and Usage 一节整节只有一句 Disabled due to widespread misuse.。
再看代码侧:现在仓库里确实还有 vibevoice/modular/modeling_vibevoice.py,里面定义了 VibeVoiceModel、VibeVoiceForConditionalGeneration 这些类,但这个文件的首行注释标明它是从社区 fork github.com/vibevoice-community/VibeVoice 复制回来的;而 vibevoice/modular/__init__.py 的 __all__ 只导出 Streaming 系列六个符号(VibeVoiceStreamingForConditionalGenerationInference、VibeVoiceStreamingConfig、VibeVoiceStreamingModel、VibeVoiceStreamingPreTrainedModel、AudioStreamer、AsyncAudioStreamer),TTS 的类一个都不在里面。
所以下文提到 TTS 侧的输入形态与架构,是在读代码和文档,不是在给你一条「照着做就能生成语音」的路径。文件在仓库里,不等于这条路线现在是官方支持你去跑的。
输入形态:一边是脚本,一边是缓存好的 voice prompt
这是两条线差别最大、也最容易一眼看出来的地方,因为它直接体现在两个 processor 类的方法签名上。
TTS 侧是 vibevoice/processor/vibevoice_processor.py 里的 VibeVoiceProcessor。它的 __call__ 第一个参数是 text,docstring 写明可以是一段脚本字符串、一组脚本字符串(批处理),也可以是 .json 或 .txt 文件路径;第二个参数是 voice_samples,类型标注是 List[Union[str, np.ndarray]]——也就是音频文件路径或者音频数组。脚本的格式由 _parse_script 决定,它用正则 ^Speaker\s+(\d+)\s*:\s*(.*)$ 逐行匹配,匹配不上的行只打 warning 跳过,一行都没匹配上会直接 raise ValueError("No valid speaker lines found in script")。纯文本怎么办?_convert_text_to_script 里写明:已经是 Speaker X: text 格式的按格式走,否则整体归给 Speaker 1。voice prompt 的拼装在 _create_voice_prompt 里,用 self.tokenizer.encode(f" Speaker {speaker_id}:") 做前缀,把音频挂到对应的说话人编号上。
流式侧是 vibevoice/processor/vibevoice_streaming_processor.py 里的 VibeVoiceStreamingProcessor,写法完全换了一套。它的 __call__ 是这样的:
def __call__(self) -> BatchEncoding:
raise NotImplementedError(
"VibeVoiceStreamingProcessor.__call__ is not implemented. "
"Use process_input_with_cached_prompt for streaming inputs."
)
也就是说你按 VibeVoiceProcessor 的习惯去调,会直接撞上 NotImplementedError,错误信息本身就是指路牌。真正的入口是 process_input_with_cached_prompt(text=..., cached_prompt=...),docstring 写明它「currently only supports single examples」,并且 cached_prompt 里装的是 voice prompt 的 kv cache。函数体里能看到它从 cached_prompt['lm']['last_hidden_state'] 和 cached_prompt['tts_lm']['last_hidden_state'] 取长度,然后用 pad_id 造出等长的伪 input_ids,真正的脚本 token 放在 tts_text_ids 这一项里。
这个 cached_prompt 从哪来?demo/realtime_model_inference_from_file.py 里给了答案:VoiceMapper 扫 demo/voices/streaming_model 目录下的 .pt 文件建一张 preset 表,命令行 --speaker_name 先精确匹配、再做部分匹配(多个 preset 同时命中会 raise,要求你把名字写得更具体),最后用 torch.load(voice_sample, map_location=target_device, weights_only=True) 把它读进来。docs/vibevoice-realtime-0.5b.md 对这个设计给了理由(仓库文档自述):为了降低 deepfake 风险并保证首个语音块的低延迟,voice prompt 以嵌入好的形式提供;需要自定义音色的用户要联系他们的团队。
这个差异什么时候会咬到你:如果你的需求里有「用户上传一段参考音频、生成同音色语音」,流式这条线在仓库里根本没有这个入口——它吃的是预先算好的 .pt,不是 wav。这不是配置问题,是接口层面就没有。
生成方式:双 tokenizer 与单 acoustic tokenizer
docs/vibevoice-tts.md 的架构一节写明 TTS 用的是 next-token diffusion 框架,由三块组成:基于 Qwen2.5 的大语言模型负责理解文本上下文与对话流、声学与语义两套连续语音 tokenizer 工作在 7.5 Hz 的超低帧率、diffusion head 负责生成声学细节。
docs/vibevoice-realtime-0.5b.md 在同一位置写的是另一句话:这个流式模型采用交错的窗口式设计,一边增量编码进来的文本块,一边基于此前上下文继续做 diffusion 的声学隐变量生成;与完整的多说话人长文变体不同,它移除了语义 tokenizer,只依赖工作在 7.5 Hz 的声学 tokenizer。
文档这句话在代码和配置里都能对上,这一点值得自己去核一遍:
| 位置 | acoustic | semantic |
|---|---|---|
VibeVoiceConfig.sub_configs(configuration_vibevoice.py) | acoustic_tokenizer_config | semantic_tokenizer_config |
VibeVoiceStreamingConfig.sub_configs(configuration_vibevoice_streaming.py) | acoustic_tokenizer_config | 无 |
仓库内 vibevoice/configs/ 下的两份 json | acoustic_vae_dim | semantic_vae_dim |
| Realtime 模型在 Hugging Face 上的 config 顶层键 | acoustic_vae_dim | 无 |
两个 config 类里 self.acoustic_vae_dim 都是从子 config 的 vae_dim 取的;而 self.semantic_vae_dim 这一行在 VibeVoiceStreamingConfig 里根本没有出现,configuration_vibevoice.py 里的 VibeVoiceConfig 才有。流式那份 config 的 model_type 是 vibevoice_streaming,与 TTS 侧的 vibevoice 也不是同一个值。
生成循环这一侧,modeling_vibevoice_streaming_inference.py 里 VibeVoiceStreamingForConditionalGenerationInference.generate 的 docstring 把流程写得很清楚:文本以小窗口喂入(对 tts_text_ids 做动态切片),每个文本窗口之后循环采样若干语音隐变量(diffusion),两者交错进行;停止条件是 EOS(由一个二分类器判定)、最大长度,或者外部传入的 stop_check_fn;同样写明「only supports batch size = 1 currently」。签名里的 audio_streamer 参数接受 AudioStreamer 或 AsyncAudioStreamer,docstring 说明它在生成过程中就把音频块发出去,函数体里对应的是 audio_streamer.put(audio_chunk, diffusion_indices) 这一行。cfg_scale 是语音 diffusion 的 classifier-free guidance 系数,return_speech 置 False 时跳过音频解码与拼接。
按仓库 demo 里的写法,这条链路是这样串起来的:
all_prefilled_outputs = torch.load(voice_sample, map_location=target_device, weights_only=True)
inputs = processor.process_input_with_cached_prompt(
text=full_script,
cached_prompt=all_prefilled_outputs,
padding=True,
return_tensors="pt",
return_attention_mask=True,
)
outputs = model.generate(
**inputs,
max_new_tokens=None,
cfg_scale=args.cfg_scale,
tokenizer=processor.tokenizer,
generation_config={'do_sample': False},
verbose=True,
all_prefilled_outputs=copy.deepcopy(all_prefilled_outputs),
)
以上为按仓库代码中的接口语义组合的示例,未经实测,以仓库最新代码为准。
streaming text input 现在到哪一步了
这里有一处需要你自己拿几份材料对着看。generate 的 docstring 写的是:文本按窗口喂入,因此「enables streaming text input: you don’t need the full text upfront」。但 docs/vibevoice-realtime-0.5b.md 的 TODO 清单里,「Implement streaming text input function to feed new tokens while audio is still being generated」这一条是未勾选状态;同一份清单里只有扩充音色那一条打了勾。而 demo/realtime_model_inference_from_file.py 传给 processor 的 text,是从 txt 文件一次性读出来的完整脚本。
把这三处放在一起,能确认的只有:输出侧的分块推送有 audio_streamer 这个明确接口,输入侧的窗口化预填在 generate 内部实现了,而「音频还在生成时继续往里追加新 token」的那个对外函数,文档自己标着未完成。至于它实际到什么程度,仓库里没有更多说明,我们也没有跑过,就到这里为止。
同一份 TODO 里还有一条未勾选的是「Merge models into official HuggingFace’s transformers repository」。与之呼应的是 pyproject.toml 对 transformers 的两处不同写法:主依赖列表里给的是一个带上界的版本范围,而可选依赖组 streamingtts 用 == 把它钉死到某一个具体版本上;文档给的安装命令也正是 pip install -e .[streamingtts],走的就是被钉死的那一条。具体版本号请以仓库 pyproject.toml 的当前内容为准,这里只讲这个约束关系。这两处放一起看,装流式这条线的环境时别顺手升级这个依赖,是有出处的。
各自把边界划在哪
流式那份文档写明的限制:只支持单个说话人,多说话人要用别的 VibeVoice 模型;当前面向英文,其它语言可能产生不可预测的结果;文档另外提供了一批非英语的实验性音色供探索,但明说这些多语言行为未经充分测试、要谨慎使用(这批音色要另外跑 demo/download_experimental_voices.sh 下载,demo/voices/streaming_model 目录下的 .pt 文件名带语言前缀);不支持朗读代码、数学公式和生僻符号,需要你先对输入文本做预处理;输入极短时(三个词及以内)稳定性会下降;上下文窗口 8k,文档给的对应生成长度约十分钟;不处理背景噪声、音乐与音效。
TTS 那份文档写明的:面向长文与多说话人对话,单次可合成至多约 90 分钟、至多 4 个不同说话人;语言侧写的是英文与中文;当前模型不显式建模或生成对话中的重叠语音。FAQ 里还有几条会直接影响你怎么组织输入:不做任何文本归一化(Q3),背景音乐是自发出现的、无法直接控制开关(Q2),唱歌能力是训练中涌现的、训练数据不含音乐(Q4),中文场景建议用英文标点且尽量只用逗号和句号(Tips 一节)。
不比的那些:首个语音块的延迟、音质、稳定性、benchmark 上的指标——我们没有下载权重也没有跑过推理,一个数都不转述。顺带提醒一句,README 里和流式文档里给出的首块延迟数值本身就不一致,且都标了依赖硬件;两份 benchmark 表格里的数值我们同样不引用。这些位置你要用就自己去仓库看原文,别拿二手数字做容量规划。
从你的处境倒推该看哪份文档
- 文本是上游 LLM 一边生成一边就要念出来的:只有流式那份文档把这个场景写进了定位(文档原话提到可以让不同的 LLM 从第一个 token 就开始说话)。代价是要接受单说话人、面向英文,以及输入侧「边生成边追加」的对外函数在 TODO 里还没打勾。
- 要做多角色播客、长对谈:文档定位在 TTS 那一档,但那条线的安装与用法在仓库里已经被关掉,别把它当成可执行方案排进计划;能做的是读架构和代码。
- 要换音色、复刻某个人的声音:流式这条线只吃预先算好的嵌入式 voice prompt,文档写明这是为了降低 deepfake 风险,自定义音色要联系团队。不存在「传个 wav 就克隆」的路径。
- 只是想搞清楚架构差在哪:双 tokenizer 与单 acoustic tokenizer 那条差异是最实的抓手,配置文件和 config 类里都能自己核到,不需要跑任何东西。
最后是环境侧的一句提醒。流式文档给的安装路径是先起 NVIDIA 的 PyTorch 容器(那条 sudo docker run ... --gpus all ... nvcr.io/nvidia/pytorch:24.07-py3 是 Linux 侧写法),再执行 pip install -e .[streamingtts]。仓库里我们没有找到 Windows 侧的安装说明:容器那条路在 Windows 上怎么走、download_experimental_voices.sh 这种 bash 脚本怎么执行,文档都没写,需要你按自己环境自行处理——这部分属于通用做法,不是该项目的官方内容。demo 脚本里的 --device 默认值按 cuda / mps / cpu 的顺序探测,这是仓库当前代码里的默认行为,随版本可能变动。该项目持续更新,上面涉及的模块路径、配置字段与接口写法请以仓库最新内容为准。
本文依据 github.com/microsoft/VibeVoice 仓库与 Hugging Face 模型卡于 2026-08-18 的公开内容整理,
事实来自仓库内的文档与源码。我们没有下载权重、没有跑过推理、也没有做过训练,
因此不涉及显存占用、推理速度、识别准确率与音质的任何描述,也不与其它模型做比较或排名。
该项目持续更新,文中涉及的模块路径、配置字段与接口写法随版本变动,请以仓库最新内容为准。
需要说明的是:仓库 README 记载,2025-09-05 微软因发现有与既定意图不符的使用方式,
基于负责任 AI 原则从该仓库移除了 VibeVoice-TTS 代码;
当前 vibevoice/modular/modeling_vibevoice.py 首行注释标明其来自社区 fork,
且该模块未被 vibevoice/modular/__init__.py 的 __all__ 导出。
本文只讲代码与架构,不构成 TTS 推理的可用性保证。
仓库 README 的风险与限制一节写明:该模型仅供研究与开发用途, 未经进一步测试与开发不建议用于商业或真实场景,并特别提示了合成语音被用于伪造与虚假信息的风险。 使用合成语音时应遵守所在司法辖区的法律法规,并在分享 AI 生成内容时主动披露。
本文对照的是同一项目内的两种用法,依据均为上述仓库内容,不对两种用法做优劣排名, 选型结论只在仓库文档写明的能力边界内成立。