ASR 与 TTS 的代码结构对照:VibeVoice 里共用了什么、分叉在哪
翻 VibeVoice 仓库的人多半会先撞上一个问题:vibevoice/modular/ 下并排躺着 modeling_vibevoice.py 和 modeling_vibevoice_asr.py,名字只差一个后缀,看上去像同一套东西的两个入口。到底哪些是真共用、哪些是各写各的,光看目录看不出来。这篇就把这两个文件摊开对照一遍。
动手之前先把 TTS 的状态说清楚
仓库 README 里有一条日期条目逐字写着:2025-09-05,微软在发布后发现该工具存在与既定意图不符的使用方式,基于负责任 AI 是其指导原则之一,已从本仓库移除 VibeVoice-TTS 代码。
而今天你仍然能在仓库里看到 vibevoice/modular/modeling_vibevoice.py。这个文件的第一行是一条注释,标明它复制自社区 fork github.com/vibevoice-community/VibeVoice 的同名文件。再看导出:vibevoice/modular/__init__.py 的 __all__ 里只有 Streaming 系列的六个符号(VibeVoiceStreamingForConditionalGenerationInference、VibeVoiceStreamingConfig、VibeVoiceStreamingModel、VibeVoiceStreamingPreTrainedModel、AudioStreamer、AsyncAudioStreamer),TTS 的类一个都不在里面;再往上一层的 vibevoice/__init__.py 导出得更少。README 顶部那张模型表里,VibeVoice-TTS-1.5B 这一行的 Quick Try 一列逐字写着 Disabled(同表的 ASR-7B、ASR-BitNet、Realtime-0.5B 三行给的都是可点的入口)。
所以下面这篇是读代码,不是教你拿它合成语音。代码里有这个类,跟这条路可用、官方支持它,是两回事。
真正共用的只有一小块,而且方向是单向的
先看引用关系。modeling_vibevoice_asr.py 开头有这么一段:
from .modeling_vibevoice import (
VibeVoiceCausalLMOutputWithPast,
SpeechConnector
)
这是两个文件之间唯一的跨文件依赖,而且是单向的:ASR 文件依赖 TTS 文件,反过来没有。在仓库里搜一遍 modeling_vibevoice.py 被谁引用,除了它自己的首行注释,就只有 ASR 这一处;demo/ 与 finetuning-asr/ 下引到这两个 modeling 文件的四个脚本,引的全是 vibevoice.modular.modeling_vibevoice_asr 里的 VibeVoiceASRForConditionalGeneration,没有一个脚本直接引 modeling_vibevoice.py。
共用的 SpeechConnector 本身很短,是 fc1 → LlamaRMSNorm → fc2 的三段结构,两条路都用它把语音侧的向量投到语言模型的 hidden_size 上。
另一块「共用」不是靠 import,而是两边各写了一份几乎一模一样的代码。VibeVoiceASRModel.__init__ 与 VibeVoiceModel.__init__ 里,下面这五件事的写法逐字相同:从 config.decoder_config 建 language_model,从 config.acoustic_tokenizer_config 与 config.semantic_tokenizer_config 建两个 tokenizer,再用 SpeechConnector(config.acoustic_vae_dim, lm_config.hidden_size) 和 SpeechConnector(config.semantic_vae_dim, lm_config.hidden_size) 建两个 connector。两边的 forward 也一样,都只是把 language_model 的输出重新包成 BaseModelOutputWithPast,set_speech_tokenizers、get_input_embeddings 里那段绕过 nnscaler 改名的 fullmap 兜底逻辑,两边也是复制关系。
也就是说:共用的是「语言模型 + 双 tokenizer + 双 connector」这个骨架,但除了 SpeechConnector 这一个类,其余是复制而非抽取。你改其中一边不会自动影响另一边。
分叉点一:config 少了一项,model_type 却是同一个
configuration_vibevoice.py 里,VibeVoiceConfig.sub_configs 有四项:acoustic_tokenizer_config、semantic_tokenizer_config、decoder_config、diffusion_head_config。VibeVoiceASRConfig.sub_configs 只有前三项,没有 diffusion_head_config。这一项之差,是后面所有分叉的源头。
两边都在末尾写了 self.acoustic_vae_dim = getattr(self.acoustic_tokenizer_config, 'vae_dim', 64) 和对应的 semantic_vae_dim(兜底值 128)。这里的 64 与 128 是仓库当前代码里的默认值,随版本可能变动,真正生效的是权重目录里 config 给出的 vae_dim。
还有一处值得放在一起看:VibeVoiceConfig 与 VibeVoiceASRConfig 的 model_type 都是字符串 "vibevoice",而两个文件末尾又都各自调用了 AutoModel.register(...) 与 AutoModelForCausalLM.register(...)。这两处是源码里白纸黑字写着的,放在一起就是这个样子——具体后果我们没有跑过,不做推断。
分叉点二:模型体内多出来的三样
VibeVoiceModel.__init__ 在共用的五行之后,还多做了三件 ASR 侧完全没有的事:
- 注册两个 buffer,
speech_scaling_factor与speech_bias_factor,初值都是torch.tensor(float('nan')),注释里说明用一维张量是为了 FSDP 兼容; - 从
config.diffusion_head_config建prediction_head; - 建
noise_scheduler = DPMSolverMultistepScheduler(...),三个参数分别取自ddpm_num_steps、ddpm_beta_schedule、prediction_type。
VibeVoiceASRModel 里这三样一个都没有。这跟 config 少一项是同一件事的两面。
分叉点三:一个能 generate,一个这里看不到 generate
类声明本身就分叉了:
class VibeVoiceASRForConditionalGeneration(VibeVoiceASRPreTrainedModel, GenerationMixin):
class VibeVoiceForConditionalGeneration(VibeVoicePreTrainedModel):
ASR 那个多继承了 GenerationMixin,并且自己实现了 prepare_inputs_for_generation;TTS 这个文件里既没有 GenerationMixin,我们也没有在其中找到 prepare_inputs_for_generation 或任何生成循环。这一条只说明这个文件写了什么、没写什么,至于合成一段语音完整要走哪些代码,本文不做推断。
顺带两个细节差异:ASR 侧的 lm_head 建完之后跟着 .to(dtype),TTS 侧没有;VibeVoiceASRPreTrainedModel 上标了 _supports_flash_attn 与 _supports_flash_attn_2 两个标志,VibeVoicePreTrainedModel 上只有 _supports_flash_attn_2。
分叉点四:音频进模型的路,走法完全不同
ASR 侧的入口是 encode_speech()。短音频路径很直:acoustic_tokenizer.encode(...) 之后按 std_dist_type 采样,过 acoustic_connector;语义侧取 encode(...).mean 过 semantic_connector;最后两路相加得到 combined_features。函数里把采样率写死成 sample_rate = 24000,注释是 # fix 24kHz sample rate。
长音频走的是另一条分支,判据是一行:
use_streaming = total_samples > segment_samples
segment_samples 由参数 streaming_segment_duration: float = 60.0 乘采样率得到。分段时给两个 tokenizer 各配一个 VibeVoiceTokenizerStreamingCache,逐段 encode 时传 use_cache=True 与 is_final_chunk,各段只收 mean;全部收完之后拼接,再用 VibeVoiceTokenizerEncoderOutput(mean=..., std=self.model.acoustic_tokenizer.fix_std) 整体采样一次,而不是每段各采一次。这是仓库当前代码里的写法,随版本可能变动。
这里有一处源码内部对不上的地方值得记一笔:encode_speech 的 docstring 写的是「For long audio (>600s by default), uses streaming processing to avoid conv overflow (>2^32)」,而函数签名里的默认值是 60.0 秒。文档字符串与签名默认值这两处摆在一起就是不一致的,我们只指出,不替作者解释。
TTS 侧的入口叫 forward_speech_features(),走法不一样:它带一个 speech_type 参数,只接受 "audio" 与 "vae",传别的会 raise NotImplementedError。更关键的是它里面有一段 ASR 完全没有的标定逻辑——当 speech_scaling_factor 或 speech_bias_factor 还是 nan 时,用当前这批 token 的 std 与 mean 算出缩放和偏置写回 buffer,如果分布式进程组已初始化还会走 dist.all_reduce 求平均。ASR 侧从头到尾没有这套东西。
分叉点五:loss 各算各的
ASR 的 forward 里,labels 不为空就做标准的错位交叉熵,nn.CrossEntropyLoss(ignore_index=-100)。
TTS 的 forward 里,labels 分支是空的,注释写明自定义的带掩码 CE loss 在训练脚本里算,这里留 None;这个文件里实际算的是 diffusion_loss:加噪之后过 prediction_head,按 prediction_type 取目标("epsilon" 取噪声本身,"v_prediction" 走 get_velocity,其它同样 raise NotImplementedError),用 F.mse_loss(..., reduction='sum') 求和再除以 latent_size 和 ddpm_batch_mul。没有语音样本时还有一条给 DDP 用的 dummy loss 分支——把几个子模块的参数求和乘 0。
这些差异什么时候会咬到你
你只想动 ASR,想把 TTS 文件删掉精简目录:删不得。SpeechConnector 和 VibeVoiceCausalLMOutputWithPast 都是从 modeling_vibevoice.py 导入的,删了 ASR 侧直接 import 失败。要精简只能先把这两处搬走。
你想按包名直接导入:两条路的类都不在 vibevoice/__init__.py 与 vibevoice/modular/__init__.py 的 __all__ 里,只能写全模块路径。仓库里的 demo/vibevoice_asr_inference_from_file.py、demo/vibevoice_asr_gradio_demo.py、finetuning-asr/lora_finetune.py、finetuning-asr/inference_lora.py 用的都是 from vibevoice.modular.modeling_vibevoice_asr import VibeVoiceASRForConditionalGeneration 这一种写法。
你在改配置:给 ASR 的 config 塞 diffusion_head_config 是没有意义的,VibeVoiceASRConfig.sub_configs 里没有这个键,模型体里也没有任何东西会去读它。
你在准备音频:24 kHz 是 encode_speech 里写死的,不是配置项;长音频切分的那个默认值在签名里,与 docstring 的表述不一致,真要改就以签名为准并自己核对当前版本。
至于哪条路更省资源、更快、识别或合成效果如何——我们没有下载权重、没有跑过推理,这些维度一律不比。TTS 侧完整的推理链路在这个文件里也读不到,同样不做推断。这两条路的操作系统差异,我们在仓库里没有找到针对 Windows 的单独说明,本文涉及的全部是 Python 模块的导入与结构,与平台无关。
本文依据 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 生成内容时主动披露。
本文对照的是同一项目内的两种用法,依据均为上述仓库内容,不对两种用法做优劣排名, 选型结论只在仓库文档写明的能力边界内成立。