从文件直接推理:VibeVoice 两个 *_from_file.py 的差别
翻 VibeVoice 仓库的时候有一个特别容易踩的坑:demo/ 目录下有两个脚本,文件名都以 _from_file.py 结尾——realtime_model_inference_from_file.py 和 vibevoice_asr_inference_from_file.py。更巧的是,docs/vibevoice-realtime-0.5b.md 和 docs/vibevoice-asr.md 里,介绍它们的小节标题也几乎一样,都叫 “Usage 2: Inference from files directly”。
于是很自然会以为这是同一件事的两个模型版本。实际上不是。这两个脚本连”file”指的是什么文件都不一样:一个读的是 .txt 脚本文本,另一个读的是音频;一个把结果写成 wav 落盘,另一个只往标准输出打印。参数名对不上、报错莫名其妙,多半是从这里开始的。
下面沿着两份源码各走一遍。
一、前置条件:两条链路的依赖不一样
先说安装。仓库根目录的 pyproject.toml 里 requires-python = ">=3.10",主依赖包含 torch、transformers>=4.51.3,<5.0.0、accelerate、diffusers、librosa、av 等。
关键差别在 extras。pyproject.toml 的 [project.optional-dependencies] 里只定义了一组:
[project.optional-dependencies]
streamingtts = [
"transformers==4.51.3",
]
也就是说 streamingtts 这一组做的事情是把 transformers 从区间钉死成一个确定版本。对应地,docs/vibevoice-realtime-0.5b.md 的安装步骤写的是 pip install -e .[streamingtts],而 docs/vibevoice-asr.md 写的是 pip install -e .——两篇文档给的安装命令本来就不同,别混着抄。以上依赖写法是仓库当前 pyproject.toml 里的内容,随版本可能变动,以仓库最新文件为准。
第二个前置差别是 ffmpeg。ASR 这条链路要读音频文件,vibevoice/processor/vibevoice_asr_processor.py 顶部从 .audio_utils 导入 load_audio_use_ffmpeg,导入失败时会 warnings.warn 提示回退到 soundfile。而 vibevoice/processor/audio_utils.py 里 load_audio_use_ffmpeg 是直接以子进程方式调 ffprobe 探采样率、再调 ffmpeg 转成单声道 s16le 读出来的。docs/vibevoice-asr.md 在 Gradio demo 那一节写了 apt update && apt install ffmpeg -y。
Windows 侧要注意:文档给的是 Linux 容器里的 apt 命令,Windows 上没有这一步的等价物,你需要自行准备 ffmpeg 与 ffprobe 并让它们出现在 PATH 里,否则走的就是代码里那条 soundfile 回退分支。文本那条链路(realtime 脚本)不读音频文件,不涉及这个依赖。
第三,注意力实现。ASR 脚本有 --attn_implementation 参数,取值是 flash_attention_2 | sdpa | eager | auto,auto 的逻辑写在 main() 里:设备是 cuda 且 torch.cuda.is_available() 时尝试 import flash_attn,失败就打印提示回退 sdpa;非 cuda 设备直接用 sdpa。realtime 脚本没有这个参数,它按设备硬编码:cuda 用 flash_attention_2、mps 与 cpu 用 sdpa。
二、realtime 脚本:文件指的是 .txt
realtime_model_inference_from_file.py 的 parse_args() 里,输入参数叫 --txt_path,--model_path 的默认值是 microsoft/VibeVoice-Realtime-0.5B。文档里给的命令是:
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
以上命令原样抄自 docs/vibevoice-realtime-0.5b.md,未经实测,以仓库最新内容与 --help 的实际输出为准。
链路是这样走的:
- 读 txt。脚本用
open(args.txt_path, 'r', encoding='utf-8')读全文,然后把中文/英文的弯引号替换成直引号(replace("’", "'")这几行)。 - 找音色。
VoiceMapper.setup_voice_presets()用glob.glob(os.path.join(voices_dir, "**", "*.pt"), recursive=True)扫描demo/voices/streaming_model下的.pt文件,以去掉扩展名的小写文件名为 key 建表。 - 载入音色。
torch.load(voice_sample, map_location=target_device, weights_only=True),外面套着torch.serialization.safe_globals([BaseModelOutputWithPast, DynamicCache])。
第 3 步是这条链路最该看清的地方:音色不是一段参考音频,而是一个预先算好的 prompt 缓存。process_input_with_cached_prompt 的 docstring 写明 cached_prompt 里装的是 voice prompt 的 kv cache。docs/vibevoice-realtime-0.5b.md 自述这么做的原因是”为降低 deepfake 风险并保证首个语音块的低延迟,voice prompt 以嵌入格式提供”,并写明需要音色定制的用户请联系其团队。所以你手上有一段人声 wav,是没法直接喂进这个脚本当音色的。
- 组装输入。
processor.process_input_with_cached_prompt(text=..., cached_prompt=..., padding=True, return_tensors="pt", return_attention_mask=True),返回的字段名在 docstring 里列着:input_ids、attention_mask、tts_lm_input_ids、tts_lm_attention_mask、tts_text_ids、speech_tensors、speech_masks、speech_input_mask。 - 生成与落盘。
model.generate(...)之后调processor.save_audio(outputs.speech_outputs[0], output_path=output_path),路径规则写死在脚本里:os.path.join(args.output_dir, f"{txt_filename}_generated.wav"),txt_filename是输入 txt 去掉扩展名后的名字。
另外两处值得留意的写死值:脚本在 model.eval() 之后直接调了 model.set_ddpm_inference_steps(num_steps=5),这个数没有做成命令行参数;--cfg_scale 的默认值是 1.5。这些是仓库当前代码里的取值,随版本可能变动。
三、ASR 脚本:文件指的是音频
vibevoice_asr_inference_from_file.py 这边,--model_path 的默认值是空字符串——不像 realtime 脚本那样带一个默认 repo id,所以必须显式传。输入侧有三个互不相同的入口:--audio_files(nargs='+',可以给多个路径)、--audio_dir(遍历目录,用 os.path.splitext(f)[1].lower() 与 vibevoice/processor/audio_utils.py 里的 COMMON_AUDIO_EXTS 白名单比对)、--dataset(走 load_dataset_and_concatenate(),需要额外装 datasets 和 torchcodec,源码里 ImportError 的提示就是 pip install datasets torchcodec)。三者都没给,脚本打印一行提示直接 return。
模型这边包在 VibeVoiceASRBatchInference 这个类里,构造函数做两件事:VibeVoiceASRProcessor.from_pretrained(model_path, language_model_pretrained_name="Qwen/Qwen2.5-7B"),以及 VibeVoiceASRForConditionalGeneration.from_pretrained(..., trust_remote_code=True)。
这里有一处源码内部的不一致值得指出来(只陈述,不解释原因):demo 脚本把 language_model_pretrained_name 硬编码成 Qwen/Qwen2.5-7B,而 vibevoice_asr_processor.py 的 from_pretrained 里,这个值的取法写成一行:先取 processor config 里的同名项,取不到才落到 kwargs,kwargs 也没传时兜底默认写的是 Qwen/Qwen2.5-1.5B。按这个优先级,demo 传进去的 Qwen/Qwen2.5-7B 只在 config 里没有这一项时才生效,替掉的是那个兜底默认。你自己写调用代码而不走 demo 时,这个参数不传,落到哪个值取决于 config 里有没有写。
处理链路:self.processor(audio=audio_inputs, sampling_rate=None, return_tensors="pt", padding=True, add_generation_prompt=True)。__call__ 的 docstring 写明 audio 可以是路径字符串、np.ndarray、torch.Tensor 或它们的列表,返回的 BatchEncoding 含 input_ids、attention_mask、acoustic_input_mask、speech_tensors、speech_masks、vae_tok_seqlens。签名里还有一个 context_info 参数,docstring 说明是”可选的上下文信息(例如 hotwords、元数据)“——但 demo 脚本没有把它接到命令行上,所以走脚本这条路是用不到热词的。
生成之后,脚本按 eos_token_id 截断,processor.decode(...) 解码,再调 processor.post_process_transcription(generated_text) 把文本解析成结构化片段。这个函数的实现就是在文本里找 Markdown 的 json 代码块起始标记,或者找第一个 [ / { 再做括号配对,然后 json.loads。解析失败时 demo 脚本只打印一行 Warning: Failed to parse structured output,把 transcription_segments 置空,继续往下走。
最关键的差别在结尾:ASR 脚本不落盘。结果全在 all_results 这个列表里,最后由 print_result() 打印,raw_text 超长时还会截断显示。想要文件,得自己写。
四、边界:几处必须照实说明的限制
process_input_with_cached_prompt的 docstring 第一句写明 “The function currently only supports single examples”,源码里也确实是texts = [text]单条包装。ASR 那边则相反,transcribe_with_batching()按--batch_size切片循环,默认值是2。这些是仓库当前代码里的默认值,随版本可能变动。- realtime 脚本的模型加载包在
try/except里,当主实现是flash_attention_2且加载抛异常时,会打印一段提示并改用sdpa重试,那段提示原文说明只有flash_attention_2得到过完整测试。这是仓库代码里的原话,我们没有跑过任何一条分支。 - 多语言音色是探索性的。
docs/vibevoice-realtime-0.5b.md明确写这些多语言行为”未经充分测试,请谨慎使用”,并把这批 speaker 称作 experimental;下载它们要另外执行bash demo/download_experimental_voices.sh。同一篇文档还写明该实时模型只支持单说话人,多说话人要用其它变体。 - 文档的 TODO 列表里,“Implement streaming text input function to feed new tokens while audio is still being generated” 这一项没有打勾。文件推理这个脚本本来也是一次性读完整个 txt。
- 文档的 Risks and limitations 一节写明:不支持读代码、数学公式与不常见符号,需要自行预处理;输入文本极短(三个词或更少)时稳定性会下降。
--speaker_name的匹配逻辑有两个容易忽略的分支:VoiceMapper.get_voice_path()先精确匹配,再做双向子串匹配,命中多个会抛ValueError要求你写得更具体;一个都没命中时不报错,而是打印 warning 并退回音色表里的第一个。名字打错了照样能跑完,只是用的不是你想要的音色。
五、怎么确认自己选对了脚本
在动手之前,用三个问题对一遍就够:
- 你手上的输入是什么? 文本走
--txt_path(realtime 脚本),音频走--audio_files/--audio_dir(ASR 脚本)。参数名对不上,说明脚本选错了,不是版本问题。 - 你要不要文件产物? 要 wav,走 realtime 脚本,落在
--output_dir下、文件名是输入 txt 名加_generated.wav;要转写文本,ASR 脚本只打印,落盘部分要自己补。 - 依赖装齐了吗? ASR 路径先确认
ffmpeg/ffprobe在PATH上(Windows 尤其别跳过这步),要用--dataset还得额外装datasets与torchcodec;realtime 路径先确认demo/voices/streaming_model下有.pt文件,VoiceMapper在目录不存在时只打印一句Warning: Voices directory not found at ...并把音色表置空。
还有一处路径基准的差异值得写下来:--txt_path 的默认值 demo/text_examples/1p_vibevoice.txt 是相对路径,相对的是进程的工作目录;而 voices 目录用的是 os.path.join(os.path.dirname(__file__), "voices/streaming_model"),相对的是脚本文件自身的位置。两个基准不同,从别的目录调用脚本时,先失效的会是前者。
本文依据 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 生成内容时主动披露。