InfiniteTalk 源码解析 #7:WanModel 改造:在视频扩散模型中加入音频条件控制

前一篇我们分析了 InfiniteTalkPipeline 的初始化过程。

在那里,T5、CLIP、VAE、Wav2Vec2、WanModel 被串成了一条完整的视频生成链路:

复制代码
prompt → T5 → 文本条件
cond_video → CLIP → 图像条件
cond_video → VAE → latent 条件
audio → Wav2Vec2 → audio embedding
WanModel → 扩散生成视频 latent
VAE decode → 最终视频帧

这一篇我们继续往模型内部走,重点看:

复制代码
wan/modules/multitalk_model.py

这里定义了 InfiniteTalk 使用的 WanModel

这篇要回答一个核心问题:

InfiniteTalk 是如何把一个原本偏 image-to-video 的视频扩散模型,改造成可以被音频驱动的说话视频生成模型的?

简单说,它主要做了三件事:

复制代码
第一,在 WanModel 中增加音频相关参数和 AudioProjModel;
第二,在每个 WanAttentionBlock 中增加 audio_cross_attn;
第三,在 forward 中把 Wav2Vec2 audio embedding 投影、对齐,并注入到每一层 Transformer block。

这就是 InfiniteTalk 从普通视频生成,走向音频驱动视频生成的关键。


一、先理解原始 WanModel 的基本角色

在 InfiniteTalk 里,WanModel 是扩散生成的主干模型。

它不是负责读音频的,也不是负责把视频保存成 MP4 的。

它真正负责的是:

在 latent 空间中,根据多种条件一步步预测视频内容。

这些条件包括:

复制代码
文本条件:T5 编码后的 context
图像条件:CLIP 编码后的 clip_fea
参考帧条件:VAE 编码后的 y
音频条件:Wav2Vec2 编码并投影后的 audio embedding
人物区域条件:ref_target_masks
时间步条件:timestep embedding

如果用一句话概括:

复制代码
WanModel 是 InfiniteTalk 中融合文本、图像、视频 latent、音频和 mask 的扩散 Transformer 主模型。

传统的 image-to-video 模型通常只需要文本、图像和参考 latent。

但 InfiniteTalk 要让人物跟着声音说话,所以必须让模型在生成每一帧时"听见"音频。

这个"听见"的过程,就是本文要分析的音频条件控制。


二、WanModel 初始化:新增 audio params

WanModel.__init__() 中,可以看到除了普通视频模型参数外,还多了一组音频相关参数。

大致包括:

复制代码
audio_window=5
intermediate_dim=512
output_dim=768
context_tokens=32
vae_scale=4
norm_output_audio=True

这些参数说明音频不是后面临时拼进去的,而是在模型结构初始化时就被考虑进来了。

它们分别可以这样理解:

复制代码
audio_window:每个时间位置取多宽的音频窗口
intermediate_dim:音频投影中间层维度
output_dim:音频条件输出维度
context_tokens:每个时间位置生成多少个音频上下文 token
vae_scale:VAE 时间下采样比例
norm_output_audio:是否对音频投影输出做归一化

其中最重要的是:

复制代码
audio_window
context_tokens
output_dim
vae_scale

因为它们决定了原始 Wav2Vec2 embedding 如何被转换成 Transformer block 可以使用的 audio context。

也就是说,音频条件不是一个简单的一维向量,而是会被整理成一组可被注意力机制读取的 token。


三、为什么不能直接把 Wav2Vec2 embedding 塞进 WanModel?

第 5 篇我们讲过,Wav2Vec2 输出的 audio embedding 可以理解为:

复制代码
[时间位置, Wav2Vec2层级, 特征维度]

但视频扩散模型内部的 Transformer block 期待的条件通常是另一种形式:

复制代码
[batch, frame, context_tokens, dim]

也就是说,Wav2Vec2 的输出和 WanModel 内部需要的条件格式并不完全一致。

两者之间需要一个适配层。

这个适配层就是:

复制代码
AudioProjModel

它的作用可以概括为:

把 Wav2Vec2 多层语音特征转换成视频扩散模型可使用的音频上下文 token。

这一步非常关键。

如果没有音频投影层,视频模型很难直接理解 Wav2Vec2 hidden states。

所以 InfiniteTalk 并不是简单地把音频 embedding 拼到输入里,而是专门设计了一个音频投影模块。


四、AudioProjModel:音频特征投影器

源码中有一个类:

复制代码
class AudioProjModel(ModelMixin, ConfigMixin):
    ...

它的初始化参数包括:

复制代码
seq_len=5
seq_len_vf=12
blocks=12
channels=768
intermediate_dim=512
output_dim=768
context_tokens=32
norm_output_audio=False

从这些参数可以看出,它假设输入音频特征来自多个 Wav2Vec2 block,每个 block 有一定 channel 维度。

它不是直接拿一个时间点的特征,而是拿一个时间窗口的特征。

这就是 seq_len 的意义。

对于一个视频帧来说,它不应该只看当前瞬间的音频。

真实说话动作有连续性,嘴巴的开合也会受到前后音素影响。

比如一个人发出 "o" 或 "m" 这样的声音,嘴型变化不是在一个点突然发生,而是有一个过渡过程。

所以模型需要看一个局部音频窗口,而不是单个采样点。

AudioProjModel 的主要职责就是:

复制代码
把局部窗口内的多层 Wav2Vec2 特征压平
  ↓
经过多层 Linear + GELU
  ↓
投影成 context_tokens 个音频条件 token
  ↓
输出给 audio cross attention 使用

它在结构上相当于一个音频适配器。


五、AudioProjModel 的 forward:从窗口特征到 context tokens

AudioProjModel.forward() 里有两个输入:

复制代码
audio_embeds
audio_embeds_vf

可以简单理解为:

复制代码
audio_embeds:首帧相关音频窗口
audio_embeds_vf:后续视频帧相关音频窗口

为什么要区分这两类?

因为视频 latent 的时间维度经过 VAE 下采样后,和原始视频帧不是一一对应的。

WanModel.forward() 里,源码会对音频 embedding 做一系列 reshape,把音频按 VAE 时间尺度重新组织。

这就是 vae_scale 参与音频对齐的原因。

AudioProjModel.forward() 中,音频特征会先被拉平:

复制代码
窗口长度 × Wav2Vec2层数 × 特征维度

然后通过:

复制代码
proj1 / proj1_vf
proj2
proj3
norm

最终得到:

复制代码
[batch, frame, context_tokens, output_dim]

也就是说,原始音频特征最后会被转换成每个时间位置的一组音频 context tokens。

这些 token 后面会被 audio_cross_attn 读取。


六、WanModel 初始化中如何挂上 AudioProjModel?

WanModel.__init__() 里,可以看到音频适配器被初始化:

复制代码
self.audio_proj = AudioProjModel(
    seq_len=audio_window,
    seq_len_vf=audio_window + vae_scale - 1,
    intermediate_dim=intermediate_dim,
    output_dim=output_dim,
    context_tokens=context_tokens,
    norm_output_audio=norm_output_audio,
)

这里有一个细节:

复制代码
seq_len_vf = audio_window + vae_scale - 1

这说明后续帧使用的音频窗口长度和 VAE 时间下采样比例有关。

也就是说,音频和视频 latent 的对齐不是简单的"一帧对应一个音频点"。

它要考虑视频通过 VAE 后的时间压缩。

可以这样理解:

复制代码
原始视频帧时间轴
  ↓
VAE 时间下采样
  ↓
latent 时间轴
  ↓
每个 latent 时间位置需要覆盖多个原始帧附近的音频信息

这也是为什么音频窗口要和 vae_scale 绑定。

InfiniteTalk 要解决的不是"当前帧听当前声音"这么简单,而是要让音频条件在 latent 时间尺度上仍然保持合理对齐。


七、WanAttentionBlock:在每个 block 中加入 audio_cross_attn

接下来进入最关键的结构改造。

WanAttentionBlock.__init__() 中,除了原本的 self attention、text/image cross attention、FFN 之外,新增了:

复制代码
self.audio_cross_attn = SingleStreamMutiAttention(
    dim=dim,
    encoder_hidden_states_dim=output_dim,
    num_heads=num_heads,
    qk_norm=False,
    qkv_bias=True,
    eps=eps,
    norm_layer=WanRMSNorm,
    class_range=class_range,
    class_interval=class_interval
)

这就是 InfiniteTalk 的音频注入点。

普通视频 Transformer block 大致结构是:

复制代码
Self-Attention
  ↓
Text/Image Cross-Attention
  ↓
Feed Forward Network

而 InfiniteTalk 改造成:

复制代码
Self-Attention
  ↓
Text/Image Cross-Attention
  ↓
Audio Cross-Attention
  ↓
Feed Forward Network

这说明音频条件不是只在模型开头注入一次,而是在每一层 Transformer block 中持续参与计算。

这种设计比简单拼接更强。

因为每一层 block 都可以根据当前 latent token 状态,重新从音频 context 中取信息。

换句话说,音频可以在扩散生成的深层特征变换过程中持续影响视频。


八、WanAttentionBlock.forward:音频注入发生在哪里?

WanAttentionBlock.forward() 的主流程,可以简化成:

复制代码
1. 对 x 做 self-attention,建模视频 token 内部关系
2. 对 x 做 text/image cross-attention,注入文本和图像条件
3. 对 x 做 audio cross-attention,注入音频条件
4. 对 x 做 FFN,完成特征变换

对应逻辑大致是:

复制代码
y, x_ref_attn_map = self.self_attn(...)

x = x + y * e[2]

x = x + self.cross_attn(self.norm3(x), context, context_lens)

x_a = self.audio_cross_attn(
    self.norm_x(x),
    encoder_hidden_states=audio_embedding,
    shape=grid_sizes[0],
    x_ref_attn_map=x_ref_attn_map,
    human_num=human_num
)

x = x + x_a

y = self.ffn(...)
x = x + y * e[5]

这段逻辑非常重要。

它说明音频注入的位置是在文本/图像 cross attention 之后、FFN 之前。

也就是说,模型先完成视频 token 自身交互,再融合文本和图像条件,然后再用音频条件调整当前 token 表示。

为什么这样设计合理?

因为音频要控制的是已经具备一定视觉语义和人物结构的 token。

如果音频太早注入,模型可能还不知道哪些 token 是人物、哪些 token 是背景。

而在 self-attention 和 image/text condition 之后,token 已经包含更丰富的视觉上下文,此时再注入音频,有助于让声音作用到更合适的区域。


九、self_attn 为什么返回 x_ref_attn_map?

WanSelfAttention.forward() 中,除了返回 self-attention 的输出,还返回:

复制代码
x_ref_attn_map

这个 attention map 来自:

复制代码
get_attn_map_with_target(...)

它会结合:

复制代码
q
k
grid_sizes
ref_target_masks

得到和目标区域相关的注意力信息。

这一步和音频控制强相关。

因为在多人说话或局部人物驱动中,模型不应该把音频作用到整个画面。

比如一个画面里有两个人:

复制代码
person1 在左边
person2 在右边
背景在后面

当 person1 的音频出现时,模型应该主要让 person1 的嘴型和表情变化,而不是让 person2 或背景乱动。

所以需要一种机制告诉 audio attention:

复制代码
当前音频应该关注哪些 token?
哪些区域是目标人物?
哪些区域应该少受音频影响?

x_ref_attn_map 就是在这个方向上服务的。

它把 self-attention 中与目标区域相关的信息传给后面的 audio_cross_attn


十、ref_target_masks:人物区域如何参与音频控制?

WanModel.forward() 中,传入了:

复制代码
ref_target_masks

然后源码会把 mask 转成 token 级别:

复制代码
ref_target_masks
  ↓
interpolate 到 latent 空间大小
  ↓
转成 bool mask
  ↓
flatten 成 token mask
  ↓
传给每个 block

为什么要这样做?

因为输入的 mask 通常是在图像空间或视频帧空间,而 Transformer block 处理的是 latent token。

两者空间尺寸不同。

所以必须把像素级或帧级 mask 映射到 token 级 mask。

这一步让模型知道:

复制代码
哪些 token 对应人物区域
哪些 token 对应非人物区域

对于音频驱动来说,mask 的意义非常大。

没有 mask 时,音频可能影响整个画面,导致背景或非说话人物出现不必要变化。

有 mask 后,模型更容易把音频变化限制到目标人物区域。

这也是 InfiniteTalk 支持多人或区域绑定的基础之一。


十一、audio_cross_attn 和 human_num

audio_cross_attn 调用时传入了:

复制代码
human_num=human_num

human_num 来自:

复制代码
human_num = len(audio_embedding)

这说明模型会根据音频 embedding 的数量判断当前有几个人物音频。

单人场景下:

复制代码
human_num = 1

双人场景下:

复制代码
human_num = 2

这与前面 cond_audio['person1']cond_audio['person2'] 的设计对应。

也就是说,InfiniteTalk 在模型内部并不是只知道"有一段声音",而是知道"有几路人物音频条件"。

这样后续 SingleStreamMutiAttention 就有机会根据人物数量、mask 和 attention map,把不同音频条件绑定到不同人物区域。

这也是它区别于简单音频驱动单人模型的重要点。


十二、WanModel.forward:音频条件如何进入主模型?

现在我们看 WanModel.forward() 中音频部分的主流程。

函数签名里有:

复制代码
def forward(
    self,
    x,
    t,
    context,
    seq_len,
    clip_fea=None,
    y=None,
    audio=None,
    ref_target_masks=None,
):

这里的 audio 就是 Pipeline 传进来的 audio embedding。

进入 forward 后,源码先把它移动到和视频 token 相同的设备和 dtype:

复制代码
audio_cond = audio.to(device=x.device, dtype=x.dtype)

然后进行时间维度拆分:

复制代码
first_frame_audio_emb_s = audio_cond[:, :1, ...]
latter_frame_audio_emb = audio_cond[:, 1:, ...]

可以理解为:

复制代码
首帧音频条件
后续帧音频条件

之后,后续帧音频会按照 vae_scale 重新分组:

复制代码
latter_frame_audio_emb
  ↓
按 n_t 和 vae_scale 重新排列
  ↓
提取前段窗口、中间窗口、后段窗口
  ↓
拼接成 latter_frame_audio_emb_s

这一步本质上是在做:

把原始音频时间序列整理成和 VAE latent 时间轴对应的局部音频窗口。

然后调用:

复制代码
audio_embedding = self.audio_proj(
    first_frame_audio_emb_s,
    latter_frame_audio_emb_s
)

至此,Wav2Vec2 的原始音频 embedding 被转换成:

复制代码
适合 Transformer cross-attention 使用的 audio context tokens

最后再处理多人音频:

复制代码
根据人数量拆分
  ↓
concat 到 audio context token 维度
  ↓
传入每个 block

这就是音频条件进入 WanModel 主干的全过程。


十三、为什么要区分首帧和后续帧?

源码中对首帧和后续帧分别处理,这一点很值得注意。

在 image-to-video 或 video-to-video 任务里,首帧往往承担身份和初始状态约束。

对于 InfiniteTalk 来说,首帧不仅是视觉参考,也对应视频开头的音频状态。

后续帧则要考虑 VAE 时间下采样后的连续运动。

所以首帧音频和后续帧音频采用不同窗口组织方式,是为了更好地匹配视频 latent 的时间结构。

可以这样理解:

复制代码
首帧:更关注起始状态和初始音频上下文
后续帧:更关注连续时间段内的音频变化

这类处理细节看起来复杂,但正是长视频和音画同步的关键。

如果简单地把音频 embedding 线性对齐到帧,可能在短视频里还可以,但在经过 VAE 下采样、patch embedding 和长视频分块之后,就很容易出现时间错位。


十四、音频不是控制像素,而是控制 latent token

很多人理解"音频驱动嘴型"时,会想象模型直接根据声音去修改嘴巴像素。

但在 InfiniteTalk 里,实际发生的是:

复制代码
音频条件影响 latent token
latent token 经过扩散采样更新
最终由 VAE 解码成视频帧

也就是说,音频作用的对象不是像素,而是 Transformer 里的视频 latent token。

这样做有几个好处:

复制代码
第一,可以让音频影响更高层的视频语义;
第二,可以同时影响嘴型、表情、头部姿态和身体动作;
第三,可以和文本、图像、mask 等条件统一融合;
第四,比后处理嘴部区域更适合生成式视频模型。

这也是为什么 InfiniteTalk 不只是对口型。

它的音频条件在生成过程中参与了 latent 表示的更新,而不是最后贴一个嘴巴动画。


十五、音频注入和文本注入有什么区别?

在 block 中,文本/图像条件通过:

复制代码
self.cross_attn(...)

注入。

音频条件通过:

复制代码
self.audio_cross_attn(...)

注入。

两者都是 cross attention 思路,但控制目标不同。

文本/图像 cross attention 主要回答:

复制代码
画面应该符合什么描述?
参考图像里的人物和视觉语义是什么?

音频 cross attention 主要回答:

复制代码
当前时间应该如何说话?
嘴型和语音节奏如何对应?
谁在当前时间段说话?
人物动作应该如何跟随音频变化?

文本条件更偏全局语义。

音频条件更偏时间动态。

这就是为什么 InfiniteTalk 不能只靠 prompt。

Prompt 可以写"a person is talking",但 prompt 不知道每一帧应该发哪个音,也不知道什么时候闭嘴、什么时候张嘴、什么时候停顿。

这些必须来自音频。


十六、音频条件为什么要在每个 block 注入?

如果只在模型输入处拼一次音频,信息可能会在深层网络中被稀释。

InfiniteTalk 在每个 WanAttentionBlock 中都加入 audio_cross_attn,意味着每一层都可以重新读取音频条件。

这有几个好处:

复制代码
低层可以学习局部动作和嘴型变化;
中层可以学习表情和头部运动;
高层可以学习更整体的说话状态;
不同扩散步中可以持续利用音频条件纠偏。

当然,具体每层学到什么不是源码直接写死的,而是模型训练后的结果。

但从结构设计上看,每层注入音频条件,显然比单点注入更有表达能力。


十七、audio_window 的意义:局部时间上下文

audio_window=5 说明每个时间位置不是只看一个音频点,而是看一个局部窗口。

这很符合说话视频生成的需求。

因为嘴型变化具有连续性。

比如发一个音节时:

复制代码
嘴巴准备张开
  ↓
张到最大
  ↓
逐渐闭合

这个过程可能跨越多个视频帧。

如果模型只看当前帧的音频特征,就容易出现动作抖动或延迟。

使用局部窗口后,模型可以看到前后音频变化,更容易生成连续自然的嘴部动作。

这也是 audio embedding 从"逐点特征"变成"窗口上下文"的原因。


十八、context_tokens 的意义:把音频变成可注意力读取的 token

AudioProjModel 最终会输出多个 context_tokens

这说明每个时间位置的音频条件不是一个向量,而是一组 token。

为什么要一组 token?

因为语音信息很复杂。

一个时间窗口里可能包含:

复制代码
音素变化
能量变化
发音边界
停顿信息
上下文信息
多人角色信息

如果压成一个向量,表达能力可能不足。

变成多个 context token 后,audio cross attention 可以在这些 token 中选择性读取信息。

这和文本 token 类似。

文本 prompt 不是一个向量,而是一串 token;音频条件也被转换成一组 token,方便 cross attention 使用。


十九、vae_scale 的意义:音频和 latent 时间轴对齐

vae_scale 是理解音频对齐的一个关键参数。

视频不是直接以原始帧进入 WanModel,而是经过 VAE 编码和 patch embedding。

时间维度会被压缩。

因此,音频如果仍然按照原始帧逐点对齐,就可能和 latent token 时间轴不一致。

源码中通过 vae_scale 对后续帧音频进行分组和窗口重组,本质上就是解决这个问题。

可以这样理解:

复制代码
原始音频时间轴
  ↓
按 25fps 对齐到视频帧
  ↓
根据 VAE 时间下采样重组
  ↓
得到 latent 时间位置对应的音频窗口
  ↓
通过 AudioProjModel 生成 audio context

这一步保证音频条件在模型内部的时间尺度是合理的。


二十、token_ref_target_masks:从像素区域到 token 区域

前面说到 ref_target_masks 会被转换成 token mask。

源码逻辑大致是:

复制代码
ref_target_masks.unsqueeze(0)
  ↓
interpolate 到 (N_h, N_w)
  ↓
转成 bool
  ↓
flatten
  ↓
传给 block

其中 N_hN_w 来自:

复制代码
H // patch_size[1]
W // patch_size[2]

这说明 mask 被映射到了视频 token 的空间尺度。

为什么要这样?

因为 audio attention 最终作用在 token 上,不是像素上。

如果 mask 仍然停留在原始图像尺寸,就无法直接用于 Transformer token。

所以必须做一次空间下采样和展平。

这一步对多人说话尤其重要:

复制代码
person1 的 mask → 约束 person1 对应 token
person2 的 mask → 约束 person2 对应 token
背景区域 → 尽量避免被音频驱动影响

二十一、从 forward 数据流看整个改造

现在把 WanModel forward 中和音频相关的数据流串起来:

复制代码
audio:Pipeline 传入的 Wav2Vec2 audio embedding
  ↓
audio.to(device=x.device, dtype=x.dtype)
  ↓
拆分 first_frame_audio_emb_s 和 latter_frame_audio_emb
  ↓
根据 vae_scale 重组后续帧音频窗口
  ↓
拼接成 latter_frame_audio_emb_s
  ↓
AudioProjModel 投影
  ↓
得到 audio_embedding context tokens
  ↓
根据 human_num 处理多人音频
  ↓
传入每个 WanAttentionBlock
  ↓
audio_cross_attn 根据 x、audio_embedding、mask、attention map 注入音频条件
  ↓
更新视频 latent token

这条链路就是 InfiniteTalk 的音频条件控制主线。


二十二、为什么这种设计比后处理 lip-sync 更强?

传统 lip-sync 方案通常是:

复制代码
先有原视频
  ↓
检测脸部和嘴巴区域
  ↓
根据音频生成嘴部区域
  ↓
把嘴部贴回原视频

InfiniteTalk 的路线是:

复制代码
输入参考视频 / 图片
  ↓
输入音频 embedding
  ↓
在扩散生成过程中注入音频条件
  ↓
生成整段人物视频

两者最大区别是:

复制代码
lip-sync:音频作用在后处理嘴部区域
InfiniteTalk:音频作用在生成过程中的视频 latent token

所以 InfiniteTalk 有机会让音频影响:

复制代码
嘴巴
脸颊
下巴
眼神
表情
头部
身体姿态
多人角色
局部区域

当然,这不意味着效果一定完美,但从结构上看,它的表达能力确实比单纯嘴部编辑更强。


二十三、二次开发时哪些地方值得关注?

如果你要基于 InfiniteTalk 做二次开发,multitalk_model.py 里有几个重点位置值得关注。


1. AudioProjModel

这是音频 embedding 到 audio context tokens 的适配层。

可以研究的问题包括:

复制代码
是否可以修改 context_tokens?
是否可以改变 audio_window?
是否可以替换更强的音频投影网络?
是否可以加入情绪特征、语速特征、音高特征?

不过这类改动通常需要重新训练或微调,否则推理效果可能不可控。


2. audio_cross_attn

这是音频真正进入 Transformer block 的地方。

可以研究的问题包括:

复制代码
音频注入是否每层都需要?
是否只在中后层注入?
不同层注入音频对效果有什么影响?
是否可以加入 gating 控制音频强度?

如果要做高级优化,这里是核心。


3. ref_target_masks

多人说话、局部控制、背景稳定,都和 mask 有关。

如果你想让人物区域控制更准,就需要关注:

复制代码
mask 如何生成
mask 如何下采样
mask 是否覆盖嘴部、脸部、身体
多人 mask 是否互相重叠

更好的 mask 可能直接改善多人对话效果。


4. human_num 和多人音频绑定

双人或多人场景下,音频条件和人物区域的绑定非常重要。

可以继续研究:

复制代码
person1 音频如何对应 person1 mask
person2 音频如何对应 person2 mask
多人同时说话时 attention 如何分配

这也是做多人数字人或多人对话视频时必须关注的点。


5. audio_window 和 vae_scale

如果你发现嘴型有延迟或动作不连续,可以关注这两个参数。

它们直接影响音频窗口和 latent 时间轴的对齐。

不过这类参数不能随便改,因为它们和模型训练时的设置有关。


二十四、常见问题排查

如果你在改造或运行 InfiniteTalk 时遇到音频控制问题,可以从下面几个方向排查。


1. 嘴型完全不动

可能原因:

复制代码
audio embedding 没有正确传入
audio_proj 权重没有加载
audio_cross_attn 权重没有加载
cond_audio 路径错误
传入的是全零 audio

2. 嘴型动了,但和声音不同步

可能原因:

复制代码
音频长度和视频帧数不匹配
Wav2Vec2 embedding 时间维度异常
vae_scale 对齐出错
长视频分块时 audio clip 切分不准确
最终 video_audio 和 cond_audio 来源不一致

3. 双人说话时角色错位

可能原因:

复制代码
person1 / person2 音频路径写反
mask 区域对应关系错误
human_num 和 audio embedding 数量不一致
audio_type 选择不合适
双人音频没有时间对齐

4. 背景也跟着音频抖动

可能原因:

复制代码
ref_target_masks 不准确
mask 没有覆盖正确人物区域
audio attention 影响范围过大
参考视频本身运动太强

5. 表情动作太夸张

可能原因:

复制代码
audio guide scale 过高
音频响度异常
LoRA 或量化影响稳定性
采样步数太少

二十五、这一篇的核心结论

InfiniteTalk 对 WanModel 的改造,可以概括成一句话:

在原有 image-to-video 扩散 Transformer 中,增加音频投影模块和 audio cross attention,让音频条件在每一层生成过程中持续影响视频 latent token。

具体来说:

WanModel 新增了音频相关参数,比如 audio_windowcontext_tokensoutput_dimvae_scale

AudioProjModel 负责把 Wav2Vec2 的多层音频 embedding 转换成视频模型可读取的 audio context tokens。

WanAttentionBlock 中新增了 audio_cross_attn = SingleStreamMutiAttention(...)

在 block forward 中,模型先做 self-attention,再做文本/图像 cross-attention,然后通过 audio cross-attention 注入音频条件,最后进入 FFN。

ref_target_masks 会被转换成 token 级 mask,用于辅助音频条件绑定到目标人物区域。

human_num 则让模型知道当前是单人音频还是多人音频。

所以,InfiniteTalk 的音频控制不是后处理,不是简单拼接,也不是只改嘴巴区域。

它是在视频扩散模型内部,通过 audio projection 和 audio cross attention,让语音参与 latent token 的生成过程。

这就是它能够从"AI 对口型"升级为"音频驱动人物视频生成"的关键。

下一篇我们会继续深入:

InfiniteTalk 源码解析 #8:Audio Cross Attention:语音如何控制嘴型、表情和身体动作

那一篇会重点看 SingleStreamMutiAttention,分析 audio attention 内部到底如何把音频 token 和视频 token 联系起来。