VibeVoice-Realtime-0.5B 上手:流式文本输入到底怎么喂

2026-08-18

如果你要做的是「让模型边写边说」——上游一个 LLM 正在吐 token,下游希望不等整段回答成型就开始出声——那么绕不开一个问题:语音模型的接口到底允许你什么时候把文本交出去。一次性交全文,和一边生成一边追加,是两种完全不同的调用形态,而很多项目的 README 里这两件事是混着说的。

VibeVoice 仓库里的 docs/vibevoice-realtime-0.5b.md 对应的就是这么一个模型:单说话人、面向长文本、支持流式文本输入的实时 TTS 变体。下面把上手路径按仓库里写明的顺序走一遍,重点看接口边界在哪。

一、先分清你在用哪个模型

VibeVoice 仓库里同时放着好几个方向的东西,文档是分开的:ASR 走 docs/vibevoice-asr.mddocs/vibevoice-vllm-asr.md,长文本 TTS 走 docs/vibevoice-tts.md,本文这个实时变体走 docs/vibevoice-realtime-0.5b.md。代码侧同样是分开的模块,实时变体对应 vibevoice/modular/modeling_vibevoice_streaming_inference.pyvibevoice/processor/vibevoice_streaming_processor.py

这里有一段必须先交代的状态。仓库 README 记载,2025-09-05 微软因发现存在与既定意图不符的使用方式,基于负责任 AI 的原则从该仓库移除了 VibeVoice-TTS 代码;现在仓库里的 vibevoice/modular/modeling_vibevoice.py 首行注释写明它是从社区 fork github.com/vibevoice-community/VibeVoice 复制回来的。与之对照,vibevoice/modular/__init__.py__all__ 只导出 Streaming 系列的六个符号——VibeVoiceStreamingForConditionalGenerationInferenceVibeVoiceStreamingConfigVibeVoiceStreamingModelVibeVoiceStreamingPreTrainedModelAudioStreamerAsyncAudioStreamer——TTS 那几个类不在其中。所以本文讲的是代码结构与接口,不构成任何「照做就能生成语音」的保证。

二、前置条件(这一段别跳)

pyproject.tomlrequires-python 写的是 >=3.10,主依赖里 transformers 的约束是 >=4.51.3,<5.0.0;而实时变体用的那个可选依赖组是这样写的:

[project.optional-dependencies]
streamingtts = [
  "transformers==4.51.3",
]

注意这里是精确 pin,不是范围。这是仓库当前 pyproject.toml 里的写法,随版本可能变动,但它解释了为什么文档给出的安装命令要带方括号:

git clone https://github.com/microsoft/VibeVoice.git
cd VibeVoice/

pip install -e .[streamingtts]

环境这一层,docs/vibevoice-realtime-0.5b.md 的建议是用 NVIDIA 的深度学习容器来管 CUDA,并在注释里提到若容器内没有 flash attention 需要自行安装,指向了 flash-attention 的官方仓库。这条路径显然是 Linux/容器侧的写法。

Windows 侧要多留意两处。其一,实验性音色的下载脚本 demo/download_experimental_voices.sh 是 bash 脚本,内部用 wget 拉包再 tar -xzvf 解压,PowerShell 直接跑不了,你需要 WSL 或 Git Bash 之类的环境,并确保 wgettar 可用。其二,demo/web/app.py 里对模型路径有一句注释,说明保留字符串形式是为了兼容 Hugging Face 的 repo id,因为 Path() 在 Windows 上会把 / 转成 \——这类跨平台细节在自己改代码时容易踩。

设备选择上,demo/vibevoice_realtime_demo.py--device 参数取值是 cpucudampxmpsmpx 会被当作 mps 处理)。加载策略在 demo/web/app.pyStreamingTTSService.load()demo/realtime_model_inference_from_file.py 里各写了一份,两处逻辑一致:cudatorch.bfloat16flash_attention_2mpscputorch.float32sdpa。加载失败时会回退到 sdpa,回退分支里仓库自己打印的提示是「只有 flash_attention_2 经过充分测试」。

三、两条上手路径

第一条是 websocket 演示,文档给的命令是:

python demo/vibevoice_realtime_demo.py --model_path microsoft/VibeVoice-Realtime-0.5B

这个入口本身很薄:它把 --model_path--device 写进 MODEL_PATHMODEL_DEVICE 两个环境变量,然后 uvicorn.run("web.app:app", host="0.0.0.0", port=args.port, reload=args.reload)。真正的服务在 demo/web/app.py--port 在代码里的默认值是 3000(仓库当前代码里的默认值,随版本可能变动)。服务侧暴露了 /(返回页面)、/config(返回音色列表与默认音色)和 /stream 这个 websocket 端点。

第二条是从文件推理,文档给的命令是:

python demo/realtime_model_inference_from_file.py --model_path microsoft/VibeVoice-Realtime-0.5B --txt_path demo/text_examples/1p_vibevoice.txt --speaker_name Carter

这里有个坑值得先说:脚本里 --speaker_name 的默认值是 Wayne,但仓库 demo/voices/streaming_model/ 目录下并没有叫这个名字的 .pt 文件。按 VoiceMapper.get_voice_path() 的逻辑,先精确匹配、再做子串匹配,都不中就打印一句 warning 并回落到按名称排序后的第一个预设。也就是说不显式传 --speaker_name,你拿到的音色未必是你以为的那个。文档示例里传的 Carter 是仓库里带的示例值。

四、接口是怎么对接起来的

抛开脚本,库这一层的调用顺序很清楚。音色不是一个 wav 文件,而是一份预先算好的缓存:demo/voices/streaming_model/ 下的 .pttorch.load(..., weights_only=True) 载入。这里两条路径的写法并不一样——demo/web/app.pytorch.load 外面套了一层 torch.serialization.safe_globals([BaseModelOutputWithPast, DynamicCache])demo/realtime_model_inference_from_file.py 则是直接 torch.load,没有这层 safe_globals。照着改代码时留意你抄的是哪一份。文档里的说法是,为降低深度伪造风险并保证首个语音块的时延,音色提示以 embedded 形式提供,需要定制音色的用户被要求联系团队——换句话说,「拿一段录音换音色」这条路在当前公开代码里没有对应入口。

载入之后交给 processor。VibeVoiceStreamingProcessor.__call__ 是被显式 raise NotImplementedError 的,注释里让你改用 process_input_with_cached_prompt,它的返回值里有 input_idstts_lm_input_idstts_text_ids 等字段;缓存字典本身分成 lmtts_lmneg_lmneg_tts_lm 四份,正负两路各有一套。

processor = VibeVoiceStreamingProcessor.from_pretrained(model_path)
inputs = processor.process_input_with_cached_prompt(
    text=full_script,
    cached_prompt=all_prefilled_outputs,
    padding=True,
    return_tensors="pt",
    return_attention_mask=True,
)

生成侧的关键参数在 generate() 的签名里写着:audio_streamertts_text_idscfg_scalereturn_speechstop_check_fn。要边生成边取音频,就把 AudioStreamer 传进去,然后另起一个线程跑 generate,主线程迭代 audio_streamer.get_stream(0)——demo/web/app.py 就是这么做的:

audio_streamer = AudioStreamer(batch_size=1, stop_signal=None, timeout=None)
thread = threading.Thread(target=self._run_generation, kwargs={...}, daemon=True)
thread.start()

stream = audio_streamer.get_stream(0)
for audio_chunk in stream:
    ...

以上为按仓库代码中的接口语义组合的示例,未经实测,以仓库最新代码为准。

生成循环内部值得看一眼。模块顶部有两个常量:TTS_TEXT_WINDOW_SIZE = 5TTS_SPEECH_WINDOW_SIZE = 6(仓库当前代码里的取值,随版本可能变动)。外层 while 每轮按文本窗口大小从 tts_text_ids 上切一片喂进 forward_lmforward_tts_lm,内层再循环语音窗口那么多次,每次由 sample_speech_tokens() 采一个声学隐变量、经 acoustic_tokenizer.decode() 解成一小段音频,有 audio_streamer 就立刻 put() 出去。停不停由 tts_eos_classifier 的 sigmoid 输出与 0.5 比较决定,另有 stop_check_fn 这个外部钩子——demo/web/app.py 把它接到了 threading.Event().is_set,客户端断开时置位。

扩散步数这里有一处对照值得注意:Hugging Face 上该模型 config.jsondiffusion_head_config.ddpm_num_inference_steps20,而两个 demo 都在加载后显式调用了 set_ddpm_inference_steps():从文件推理那个脚本写死的是 num_steps=5,websocket 服务传的是 StreamingTTSServiceinference_steps,这个构造参数的默认值同样是 5;websocket 服务还允许用 steps 这个 query 参数覆盖。两个数都是仓库里的取值,随版本可能变动。另外 demo/web/app.py 在加载后重建了 noise_scheduleralgorithm_type="sde-dpmsolver++"beta_schedule="squaredcos_cap_v2"),而从文件推理那个脚本没有这一步——同一个模型,两条路径的调度器配置并不一致,抄代码时别只抄一半。

五、边界在哪

「流式文本输入」当前落到哪一步。 generate() 的 docstring 写的是「文本以小窗口喂入(对 tts_text_ids 做动态切片),因此不需要预先拿到全文」。但同一份文档的 TODO 列表里,「实现流式文本输入函数,以便在音频仍在生成时喂入新 token」这一项是未勾选的;把两处放在一起看:公开路径上 tts_text_ids 仍然是 process_input_with_cached_prompt 一次性算好的完整张量,窗口切片发生在这个张量内部,而「生成途中追加新 token」按仓库自己的 TODO 还没到位。websocket demo 也印证了这一点——text 是连接时从 query 参数一次读入的。

并发与批量。 generate() 里有一句 assert batch_size == 1,docstring 也写明当前只支持 batch size 1;process_input_with_cached_prompt 同样注明只支持单条输入。websocket 服务用一把 asyncio.Lock 串行化请求,第二个连接进来会收到 backend_busy 日志并被以 1013 关闭。

音色与语种。 文档把额外的多语种说话人明确称作 experimental,并写明这些多语种表现未经充分测试、请谨慎使用;模型定位是英文,且只支持单说话人,多说话人要用其它 VibeVoice 变体。风险与限制一节还列出:不处理背景音、音乐等非语音内容;不支持朗读代码、数学公式与生僻符号,需要你在输入前做归一化;输入极短时模型稳定性会下降。

没找到说明的部分。 自定义音色的制作流程(怎么从一段录音生成 .pt),我们在仓库里没有找到相关说明,文档给出的路径是联系团队。

六、怎么确认接对了

不依赖听感,代码里有几处可核对的输出。启动 websocket 服务时,demo/web/app.py 会依次打印 [startup] Loading processor from ...、发现的音色预设数量、[startup] Model ready.;随后访问 /config 会返回 voicesdefault_voice 两个字段,可以直接确认你要的音色在不在列表里,以及默认落到了哪个(代码里的兜底 key 是 en-Carter_man,找不到才取排序后的第一个)。

从文件推理那条路径,VoiceMapper 会打印找到的音色文件数量与可用音色名,接着打印实际选中的音色路径——上面那个 Wayne 的坑就是在这行日志里现形的。generate() 的 tqdm 进度条由 show_progress_bar 控制(代码里是 kwargs.get("show_progress_bar", True),仓库当前默认开),描述里滚动的是已 prefill 的文本 token 数与已生成的语音 token 数;别把它和 verbose 搞混,后者管的是外部停止、达到最大生成长度这类事件的额外打印。最后 outputs.speech_outputs[0] 交给 processor.save_audio() 落盘。

websocket 那侧则可以看日志事件名:backend_request_receivedmodel_progressbackend_first_chunk_sentbackend_stream_complete,它们通过同一条连接以 JSON 文本帧发出,音频则是 chunk_to_pcm16() 转出的 16 位 PCM 二进制帧,采样率在代码里写作 SAMPLE_RATE = 24_000。先确认这几个事件按顺序出现,再去管音频本身,排查顺序会清楚很多。


本文依据 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 生成内容时主动披露。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。