ASR 与 TTS 的代码结构对照:VibeVoice 里共用了什么、分叉在哪

2026-08-18

翻 VibeVoice 仓库的人多半会先撞上一个问题:vibevoice/modular/ 下并排躺着 modeling_vibevoice.pymodeling_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 系列的六个符号(VibeVoiceStreamingForConditionalGenerationInferenceVibeVoiceStreamingConfigVibeVoiceStreamingModelVibeVoiceStreamingPreTrainedModelAudioStreamerAsyncAudioStreamer),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 本身很短,是 fc1LlamaRMSNormfc2 的三段结构,两条路都用它把语音侧的向量投到语言模型的 hidden_size 上。

另一块「共用」不是靠 import,而是两边各写了一份几乎一模一样的代码。VibeVoiceASRModel.__init__VibeVoiceModel.__init__ 里,下面这五件事的写法逐字相同:从 config.decoder_configlanguage_model,从 config.acoustic_tokenizer_configconfig.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 的输出重新包成 BaseModelOutputWithPastset_speech_tokenizersget_input_embeddings 里那段绕过 nnscaler 改名的 fullmap 兜底逻辑,两边也是复制关系。

也就是说:共用的是「语言模型 + 双 tokenizer + 双 connector」这个骨架,但除了 SpeechConnector 这一个类,其余是复制而非抽取。你改其中一边不会自动影响另一边。

分叉点一:config 少了一项,model_type 却是同一个

configuration_vibevoice.py 里,VibeVoiceConfig.sub_configs 有四项:acoustic_tokenizer_configsemantic_tokenizer_configdecoder_configdiffusion_head_configVibeVoiceASRConfig.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

还有一处值得放在一起看:VibeVoiceConfigVibeVoiceASRConfigmodel_type 都是字符串 "vibevoice",而两个文件末尾又都各自调用了 AutoModel.register(...)AutoModelForCausalLM.register(...)。这两处是源码里白纸黑字写着的,放在一起就是这个样子——具体后果我们没有跑过,不做推断。

分叉点二:模型体内多出来的三样

VibeVoiceModel.__init__ 在共用的五行之后,还多做了三件 ASR 侧完全没有的事:

  • 注册两个 buffer,speech_scaling_factorspeech_bias_factor,初值都是 torch.tensor(float('nan')),注释里说明用一维张量是为了 FSDP 兼容;
  • config.diffusion_head_configprediction_head
  • noise_scheduler = DPMSolverMultistepScheduler(...),三个参数分别取自 ddpm_num_stepsddpm_beta_scheduleprediction_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(...).meansemantic_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=Trueis_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_factorspeech_bias_factor 还是 nan 时,用当前这批 token 的 stdmean 算出缩放和偏置写回 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_sizeddpm_batch_mul。没有语音样本时还有一条给 DDP 用的 dummy loss 分支——把几个子模块的参数求和乘 0。

这些差异什么时候会咬到你

你只想动 ASR,想把 TTS 文件删掉精简目录:删不得。SpeechConnectorVibeVoiceCausalLMOutputWithPast 都是从 modeling_vibevoice.py 导入的,删了 ASR 侧直接 import 失败。要精简只能先把这两处搬走。

你想按包名直接导入:两条路的类都不在 vibevoice/__init__.pyvibevoice/modular/__init__.py__all__ 里,只能写全模块路径。仓库里的 demo/vibevoice_asr_inference_from_file.pydemo/vibevoice_asr_gradio_demo.pyfinetuning-asr/lora_finetune.pyfinetuning-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 生成内容时主动披露。

本文对照的是同一项目内的两种用法,依据均为上述仓库内容,不对两种用法做优劣排名, 选型结论只在仓库文档写明的能力边界内成立。

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