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

上一篇我们分析了 InfiniteTalk 对 WanModel 的改造。

核心结论是:InfiniteTalk 并不是在视频生成完成后再做嘴部替换,而是在视频扩散模型内部加入了音频条件控制。

具体来说,它在 WanModel 中增加了:

复制代码
AudioProjModel
audio_cross_attn
ref_target_masks
human_num
音频窗口对齐逻辑

其中真正让音频参与 Transformer 计算的模块,就是:

复制代码
SingleStreamMutiAttention

它定义在:

复制代码
wan/modules/attention.py

这一篇我们重点分析这个模块。

这篇要回答的问题是:

音频 embedding 进入 WanModel 后,究竟是如何影响视频 token 的?

更通俗一点说:

语音是怎么控制嘴型、表情、头部和身体动作的?

答案不是"音频直接改嘴巴像素",而是:

复制代码
视频 token 作为 Query
音频 token 作为 Key / Value
通过 cross attention 让视频 token 从音频条件中读取信息
再结合人物区域 attention map,把不同音频条件尽量绑定到不同人物区域

这就是 Audio Cross Attention 的核心。


一、先理解 Cross Attention:谁看谁?

在 Transformer 里,attention 最常见的形式是 self-attention。

Self-attention 的意思是:一组 token 内部互相看。

比如视频 token 做 self-attention,就是视频里不同空间位置、不同时间帧之间互相建立关系。

而 cross-attention 的意思是:一组 token 去看另一组 token。

在 InfiniteTalk 的音频场景里,大致可以理解成:

复制代码
视频 token:当前正在生成的视频 latent 表示
音频 token:由 Wav2Vec2 + AudioProjModel 得到的语音条件

Audio Cross Attention 做的事情就是:

复制代码
视频 token 去查询音频 token,
判断当前视频位置应该从当前语音里读取什么信息。

所以它不是让音频单独生成动作,而是让视频生成过程在每一层都可以参考音频。

这很关键。

因为最终生成的仍然是视频 latent,音频只是条件。


二、音频控制的对象不是像素,而是视频 latent token

很多人理解"语音控制嘴型"时,会想象模型直接根据音频去修改嘴巴区域像素。

但在 InfiniteTalk 里不是这样。

它的实际路径是:

复制代码
语音文件
  ↓
Wav2Vec2
  ↓
audio embedding
  ↓
AudioProjModel
  ↓
audio context tokens
  ↓
Audio Cross Attention
  ↓
影响视频 latent token
  ↓
VAE decode
  ↓
最终视频帧

也就是说,音频真正作用的是 Transformer 中的视频 latent token。

这些 token 不一定只对应嘴巴,它们可能对应:

复制代码
嘴部区域
脸部区域
头部区域
上半身区域
背景区域
不同时间帧
不同人物区域

如果音频条件通过 attention 影响到嘴部 token,就会体现为嘴型变化。

如果影响到面部 token,就可能体现为表情变化。

如果影响到头部或身体 token,就可能体现为点头、转头、姿态变化。

这就是 InfiniteTalk 比传统嘴部后处理更有表达力的地方。


三、SingleStreamAttention:基础音频 cross attention

在看 SingleStreamMutiAttention 之前,要先看它的父类:

复制代码
class SingleStreamAttention(nn.Module):
    ...

这个类实现的是基础 cross attention。

它的核心输入有两个:

复制代码
x
encoder_hidden_states

可以这样理解:

复制代码
x:视频 token
encoder_hidden_states:外部条件 token,在这里主要是音频 token

在 forward 里,它先从视频 token 生成 Query:

复制代码
x → q_linear → q

再从音频条件生成 Key 和 Value:

复制代码
encoder_hidden_states → kv_linear → encoder_k / encoder_v

然后执行 attention:

复制代码
Attention(q, encoder_k, encoder_v)

最后再通过线性层投影回视频 token 维度。

这就是最标准的 cross attention 思路:

复制代码
Query 来自视频 token
Key / Value 来自音频 token
输出仍然是视频 token 的更新量

所以 Audio Cross Attention 的本质是:

让视频 token 根据自身状态,从音频条件中取出当前需要的信息。


四、为什么 Query 来自视频,而 Key / Value 来自音频?

这个设计非常重要。

如果 Query 来自视频,说明是"视频 token 主动去问音频":

复制代码
我这个视频位置,现在应该根据音频怎么变化?

Key / Value 来自音频,说明音频提供可被查询的信息:

复制代码
当前音节是什么?
当前是否在说话?
声音节奏如何?
是否有停顿?
应该对应怎样的发音状态?

最终 attention 会决定:

复制代码
某个视频 token 应该读取哪些音频 token 的信息。

如果一个视频 token 对应嘴部附近,它可能会强烈关注与当前时间相关的音频 token。

如果一个 token 对应背景,它理想情况下应该较少受到音频影响。

这也是为什么后面还要引入 x_ref_attn_map 和人物区域信息。

单纯 cross attention 可以让视频读音频,但还需要额外机制来告诉模型:

复制代码
哪些视频 token 是人?
哪个人对应哪路音频?
哪些区域是背景?

五、单人场景:直接使用基础 cross attention

SingleStreamMutiAttention.forward() 一开始有一个判断:

复制代码
if human_num == 1:
    return super().forward(x, encoder_hidden_states, shape)

这说明单人场景比较简单。

如果画面中只有一个说话人,或者只有一路音频条件,那么没有必要区分 person1、person2。

此时直接走父类 SingleStreamAttention 即可:

复制代码
视频 token → Query
单人音频 token → Key / Value
cross attention → 更新视频 token

这种情况下,模型默认整个人物都由同一路音频驱动。

当然,这并不意味着音频一定会影响整个画面。

具体影响范围还取决于模型训练、参考图像、mask、latent 表示和采样参数。

但从代码路径上看,单人场景不需要额外的人物绑定逻辑。


六、多人场景:为什么不能直接共用基础 attention?

多人场景就复杂了。

假设画面里有两个人:

复制代码
person1 在左边
person2 在右边

同时输入两路音频:

复制代码
person1_audio
person2_audio

如果只是把两路音频 token 混在一起,然后让所有视频 token 自由 cross attention,就可能出现问题:

复制代码
person1 的嘴跟着 person2 的声音动
person2 的表情跟着 person1 的声音变
两个人同时乱动
背景区域也被音频影响

所以多人场景必须解决一个绑定问题:

复制代码
哪一路音频应该控制哪一个人物区域?

SingleStreamMutiAttention 的主要价值就在这里。

它不是简单地多输入几路音频,而是结合 x_ref_attn_map 给不同人物和背景分配不同的位置编码区间,让 query 和 key 在 attention 中更容易区分人物归属。


七、x_ref_attn_map:从 self-attention 中拿到人物区域线索

在上一篇里我们提到,WanSelfAttention 会返回:

复制代码
x_ref_attn_map

然后传给:

复制代码
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_ref_attn_map 可以理解成当前视频 token 与参考人物区域之间的关联图。

它不是普通图像 mask,而是从 attention 计算中得到的区域相关性信息。

在多人场景中,x_ref_attn_map 通常包含不同人物的参考关联。

比如:

复制代码
x_ref_attn_map[0]:person1 相关区域的注意力线索
x_ref_attn_map[1]:person2 相关区域的注意力线索

这个 map 会被 SingleStreamMutiAttention 用来判断:

复制代码
某个视频 token 更像属于 person1?
更像属于 person2?
还是更像背景?

这一步是音频绑定人物区域的基础。


八、class_range、class_interval:给人物分配位置区间

SingleStreamMutiAttention.__init__() 里有几个特殊参数:

复制代码
class_range = 24
class_interval = 4

然后初始化:

复制代码
self.rope_h1 = (0, self.class_interval)
self.rope_h2 = (self.class_range - self.class_interval, self.class_range)
self.rope_bak = int(self.class_range // 2)
self.rope_1d = RotaryPositionalEmbedding1D(self.head_dim)

这段代码很有意思。

它给不同区域分配了不同的位置编码范围:

复制代码
person1:靠近 0 到 class_interval 的区间
person2:靠近 class_range - class_interval 到 class_range 的区间
background:class_range 中间位置

如果默认值是:

复制代码
class_range = 24
class_interval = 4

那么可以理解成:

复制代码
person1 区间:0 ~ 4
background 位置:12
person2 区间:20 ~ 24

这不是普通空间坐标,而是一种用于 RoPE 的"类别位置"。

它的目标是让 person1、person2 和背景在 attention 中具有不同的位置编码特征。

换句话说,模型不是只靠 mask 硬切区域,而是通过位置编码增强不同人物区域的可区分性。


九、normalize_and_scale:把 attention map 映射到人物区间

在多人 forward 中,源码会先计算 x_ref_attn_map 的最大值和最小值。

然后分别对两个人物区域做归一化和缩放:

复制代码
human1 = normalize_and_scale(
    x_ref_attn_map[0],
    human1_min_max,
    rope_h1_range
)

human2 = normalize_and_scale(
    x_ref_attn_map[1],
    human2_min_max,
    rope_h2_range
)

这一步的意义是:

复制代码
把 person1 的 attention 强弱映射到 person1 的位置编码区间;
把 person2 的 attention 强弱映射到 person2 的位置编码区间。

然后背景区域会被设置为固定中间值:

复制代码
back = rope_bak

最终通过:

复制代码
max_indices = x_ref_attn_map.argmax(dim=0)

判断每个 token 更偏向哪个区域。

如果某个 token 对 person1 的关联更强,就使用 person1 的编码位置。

如果对 person2 更强,就使用 person2 的编码位置。

如果都不明显,就更接近背景位置。

最终得到:

复制代码
normalized_pos

这个 normalized_pos 后面会用于视频 Query 的 RoPE 位置编码。


十、为什么要给 Query 加 RoPE?

源码里对 q 做了这样的处理:

复制代码
q = rearrange(...)
q = self.rope_1d(q, normalized_pos)
q = rearrange(...)

也就是说,视频 token 生成的 Query 会根据 normalized_pos 加上一维旋转位置编码。

这里的 normalized_pos 不是普通时间位置,而是带有人物区域意义的位置。

所以它表达的是:

复制代码
这个视频 token 更像 person1 区域?
更像 person2 区域?
还是背景区域?

给 Query 加上这种位置编码后,后续它和音频 Key 做 attention 时,就会受到人物区域信息的影响。

这有点像给视频 token 加上身份标签。

不是显式写:

复制代码
这是 person1
这是 person2
这是 background

而是通过 RoPE 位置编码,让不同区域在注意力空间中自然区分开。


十一、音频 Key 也要加 RoPE:让音频带上人物归属

不仅 Query 要加 RoPE,音频 Key 也会加。

源码会先构造:

复制代码
per_frame

然后把前半段音频 token 分配到 person1 的位置区间中间,把后半段音频 token 分配到 person2 的位置区间中间:

复制代码
前半段音频 token → person1 编码位置
后半段音频 token → person2 编码位置

接着构造:

复制代码
encoder_pos = torch.concat([per_frame] * N_t, dim=0)

再对 encoder_k 加 RoPE:

复制代码
encoder_k = self.rope_1d(encoder_k, encoder_pos)

这一步非常关键。

它相当于让音频 Key 也带上人物归属信息。

于是 attention 中就出现了这种关系:

复制代码
person1 区域的视频 Query
  更容易匹配 person1 音频 Key

person2 区域的视频 Query
  更容易匹配 person2 音频 Key

背景 Query
  不会强烈匹配某个人物音频 Key

这就是多人音频绑定的核心机制之一。


十二、为什么音频 token 前半段对应 person1,后半段对应 person2?

在前面的音频处理流程中,双人音频会分别生成 1.pt2.pt

进入 Pipeline 和 WanModel 后,音频条件会按人物组织并拼接。

SingleStreamMutiAttention 时,encoder_hidden_states 里已经包含了多个人物的音频 context。

源码中用:

复制代码
per_frame[:per_frame.size(0)//2]
per_frame[per_frame.size(0)//2:]

把音频 token 分成前半和后半。

可以理解成:

复制代码
前半:person1 的 audio context
后半:person2 的 audio context

所以它给前半 token 加 person1 的位置编码,给后半 token 加 person2 的位置编码。

这和前面的 cond_audio['person1']cond_audio['person2'] 是对应的。

这也说明一个重要点:

多人音频条件的顺序不能乱。

如果 person1 和 person2 的音频路径写反,后续 attention 绑定也会错。


十三、真正的 attention 计算:memory_efficient_attention

经过 Query、Key、Value 准备后,源码最终调用:

复制代码
xformers.ops.memory_efficient_attention(q, encoder_k, encoder_v)

这里执行的就是实际 cross attention。

输入是:

复制代码
q:视频 token 的 Query,已经带有人物区域 RoPE
encoder_k:音频 token 的 Key,已经带有人物归属 RoPE
encoder_v:音频 token 的 Value

输出是音频条件对视频 token 的更新量。

可以概括为:

复制代码
视频 token 根据自身区域和当前状态,
从对应人物的音频 token 中读取信息,
得到一个音频驱动的特征更新。

然后经过:

复制代码
proj
proj_drop
rearrange 回原形状

最后返回给 WanAttentionBlock

在 block 里,它会被加回原视频 token:

复制代码
x = x + x_a

这就是音频条件真正作用到视频 token 的地方。


十四、从数据流角度看 Audio Cross Attention

现在把整个 SingleStreamMutiAttention 的多人流程串起来:

复制代码
输入 x:视频 token
输入 encoder_hidden_states:音频 context token
输入 x_ref_attn_map:人物区域 attention map
输入 human_num:人物数量
  ↓
如果 human_num == 1:直接走基础 SingleStreamAttention
  ↓
如果 human_num > 1:
  ↓
把视频 token 按时间帧重排
  ↓
视频 token 生成 q
  ↓
根据 x_ref_attn_map 判断 token 更属于 person1 / person2 / background
  ↓
把人物归属映射成 normalized_pos
  ↓
给 q 加人物区域 RoPE
  ↓
音频 token 生成 encoder_k / encoder_v
  ↓
给 person1 音频 Key 和 person2 音频 Key 加不同 RoPE
  ↓
执行 memory_efficient_attention(q, encoder_k, encoder_v)
  ↓
得到音频驱动的视频 token 更新量
  ↓
返回给 WanAttentionBlock
  ↓
x = x + x_a

这条链路就是 Audio Cross Attention 的核心。


十五、它如何影响嘴型?

嘴型变化本质上是视频 token 的局部变化。

如果某些 token 对应嘴部区域,那么在扩散采样过程中,这些 token 会通过 audio cross attention 读取音频特征。

音频特征包含:

复制代码
发音节奏
音素变化
开口闭口信息
停顿信息
语速变化

视频 token 读取这些信息后,就会在生成过程中向相应的嘴部形态演化。

比如:

复制代码
发元音时,嘴部 token 可能更倾向于张开;
发闭口音时,嘴部 token 可能更倾向于闭合;
静音时,嘴部 token 可能更倾向于保持稳定;
连续语音时,嘴部 token 会持续变化。

当然,源码里不会显式写"发 a 嘴巴张开"。这是模型训练后学到的映射。

源码提供的是机制:

复制代码
让嘴部相关视频 token 能够在每层 Transformer 中读取音频条件。

十六、它如何影响表情?

表情不是独立于语音存在的。

人在说话时,面部肌肉会随着语气、节奏、发音和情绪发生变化。

虽然 Wav2Vec2 主要是语音表征模型,不是情绪识别模型,但音频特征中仍然会包含很多和说话状态有关的信息:

复制代码
音量变化
语速
停顿
重音
音调走势
发音强弱

当脸部 token 通过 audio cross attention 读取这些信息时,就有机会生成更自然的面部变化。

例如:

复制代码
语速快时,表情变化可能更频繁;
语气强时,脸部动作幅度可能更大;
停顿时,表情可能趋于稳定;
轻声说话时,面部动作可能更收敛。

这也是 InfiniteTalk 不只是改嘴巴的原因。

音频条件注入的是视频 token,而不是单独嘴部 mask,因此它有机会影响整个面部区域。


十七、它如何影响头部和身体动作?

头部和身体动作比嘴型更复杂。

嘴型和语音的关系比较直接,而身体动作和语音的关系更偏统计相关。

比如:

复制代码
说话时轻微点头
强调某句话时身体前倾
停顿时动作变缓
语速较快时头部小幅变化更多

这些不是每个音素都一一对应,但在真实视频数据中,它们和语音节奏存在一定关联。

由于 Audio Cross Attention 作用在视频 latent token 上,而不是只作用在嘴部区域,所以头部、肩膀、上半身 token 也可能读取音频条件。

这让模型有机会生成:

复制代码
轻微点头
面部朝向变化
肩颈微动
身体姿态随说话节奏变化

这也是 InfiniteTalk 强调不只是 lip-sync 的关键。

它的结构允许音频影响更大范围的人物动态。


十八、为什么不会让背景也乱动?

理论上,如果音频条件可以影响所有 token,背景也可能跟着动。

所以 InfiniteTalk 需要额外机制抑制这种问题。

这里有几个约束来源:

复制代码
参考视频或图片条件
VAE latent 条件
CLIP 视觉条件
ref_target_masks
x_ref_attn_map
背景位置编码 rope_bak
模型训练中的数据分布

SingleStreamMutiAttention 中,背景 token 会被分配到中间位置:

复制代码
rope_bak = class_range // 2

而 person1 和 person2 会被分配到两端区间。

这让背景 token 在人物音频匹配上不那么突出。

不过,这不代表背景绝对不会变化。

生成式视频模型本身就可能引入背景漂移,尤其在长视频、低步数、低显存或强音频引导下更明显。

所以源码机制只是降低风险,不是绝对保证。

如果背景抖动严重,还需要结合:

复制代码
更准确的 mask
更合理的 audio guide scale
更稳定的参考视频
更多采样步数
更合适的分块策略

十九、为什么多人场景需要 RoPE,而不是简单 mask?

简单 mask 的做法可能是:

复制代码
person1 区域只看 person1 音频
person2 区域只看 person2 音频
背景区域不看音频

这听起来直接,但在生成式 Transformer 里未必最优。

因为视频 token 不是固定像素。

它们经过 VAE、patch embedding、Transformer 层层变换后,语义和空间位置会更复杂。

而且人物区域边界也不是绝对清晰的。

比如:

复制代码
嘴部和脸颊相连
脸部和头发相连
身体和背景接触
两个人物可能有遮挡

如果用硬 mask,可能导致边界不自然。

RoPE 编码则是一种更柔性的方式。

它不是强制切断 attention,而是通过位置编码让不同人物和背景在 attention 空间中更容易区分。

这种方法更适合生成式模型。

可以理解为:

复制代码
mask / attention map 提供区域线索;
RoPE 把区域线索编码进 attention;
attention 自己学习如何使用这些线索。

二十、Audio Cross Attention 和 audio guide scale 的关系

在 Pipeline 采样阶段,InfiniteTalk 还区分了:

复制代码
text_guide_scale
audio_guide_scale

audio_cross_attn 是模型结构层面的音频注入方式。

audio_guide_scale 是采样策略层面的音频引导强度。

两者关系可以这样理解:

复制代码
audio_cross_attn:模型有没有能力读取音频
audio_guide_scale:采样时多大程度强调音频条件

如果没有 audio cross attention,audio guide scale 再大也没有意义。

如果有 audio cross attention,但 audio guide scale 过低,嘴型同步可能不够明显。

如果 audio guide scale 过高,可能导致动作过度、画面不稳定或人物区域变化太强。

所以调参时要把两者区分开。

结构层面由 audio_cross_attn 提供能力,采样层面由 audio_guide_scale 控制强度。


二十一、SingleStreamMutiAttention 的设计亮点

总结一下,SingleStreamMutiAttention 有几个值得注意的设计点。

1. 复用基础 cross attention

单人场景直接走 SingleStreamAttention,逻辑简洁。

不需要为了单人任务引入额外复杂度。

2. 多人场景加入人物区域编码

多人场景通过 x_ref_attn_mapnormalize_and_scale 和 RoPE,把视频 token 的人物归属编码进 Query。

3. 音频 Key 也带人物归属

音频 token 的 Key 也会被加上对应人物的 RoPE,使 person1 视频 Query 更容易匹配 person1 音频 Key。

4. 使用 memory efficient attention

源码使用 xFormers 的 memory-efficient attention,说明它考虑了视频 token 序列长、显存压力大的问题。

5. 输出仍然是视频 token 更新量

Audio attention 不直接输出图像,而是输出对视频 latent token 的修正。

这符合扩散 Transformer 的整体设计。


二十二、这套机制有什么局限?

虽然这个设计很巧妙,但也有一些局限。

1. 依赖 mask 和 attention map 质量

如果 x_ref_attn_map 或人物区域 mask 不准确,音频绑定就可能出错。

比如 person1 和 person2 的区域混在一起,模型就可能让两个人都跟着同一路音频动。

2. 当前逻辑更明显针对双人

源码中对前半音频 token 和后半音频 token 的处理,更像是针对双人场景设计。

如果要扩展到三人、四人,就需要重新设计音频 token 分组和人物位置编码。

3. 背景稳定不是绝对保证

背景 token 有单独位置编码,但生成式模型仍然可能出现背景漂移。

尤其是长视频生成时,背景稳定还要依赖分块策略、参考帧控制、采样参数和后处理。

4. 音频不等于情绪理解

Wav2Vec2 embedding 包含语音特征,但不等于完整情绪语义理解。

如果想让表情更准确表达情绪,可能还需要额外的情绪特征、文本语义或专门训练。

5. 强音频引导可能带来画面副作用

如果过度强调音频条件,模型可能为了嘴型同步牺牲画面稳定性。

这需要在 audio_guide_scale、采样步数、LoRA、APG 等参数之间平衡。


二十三、二次开发时可以怎么改?

如果你要基于 InfiniteTalk 做二次开发,Audio Cross Attention 是一个高级改造点。

1. 改进多人绑定

可以尝试把当前双人逻辑扩展成更通用的多人逻辑:

复制代码
person1 → 区间 1
person2 → 区间 2
person3 → 区间 3
person4 → 区间 4

不过这需要重新设计 class_range 和音频 token 分组。

2. 加入更明确的人脸 / 嘴部 mask

当前区域线索主要来自 ref_target_masks 和 attention map。

如果想强化嘴型同步,可以额外引入嘴部区域 mask,让嘴部 token 更强地读取音频。

但这可能会降低整体自然度,因为人说话不只是嘴巴动。

3. 加入情绪或语速特征

可以在 audio context 中加入额外条件:

复制代码
音高 pitch
能量 energy
语速 speed
情绪 embedding
停顿标记

这些特征可以辅助表情和身体动作控制。

4. 设计 gating 控制音频影响范围

可以给 audio attention 增加门控:

复制代码
嘴部区域:强音频影响
面部区域:中等音频影响
身体区域:弱音频影响
背景区域:极弱音频影响

这样可能有助于减少背景抖动。

5. 做 attention 可视化

如果要调试多人错位问题,可以尝试可视化:

复制代码
x_ref_attn_map
normalized_pos
person1 / person2 token 分布
audio attention 权重

这能帮助判断音频到底作用到了哪里。


二十四、常见问题排查

如果你运行 InfiniteTalk 时遇到音频控制问题,可以从 Audio Cross Attention 角度排查。

1. 单人嘴型不动

可能原因:

复制代码
audio embedding 没传入
AudioProjModel 输出异常
audio_cross_attn 权重没加载
audio guide scale 太低
采样步数太少

2. 双人嘴型错位

可能原因:

复制代码
person1 / person2 音频顺序反了
mask 区域反了
x_ref_attn_map 质量差
输入视频中人物区域太接近
audio_type 选择错误

3. 两个人同时动

可能原因:

复制代码
两路音频时间上重叠
mask 区域重叠
attention map 无法区分人物
audio guide scale 太高

4. 背景抖动

可能原因:

复制代码
背景 token 受到音频 attention 影响
参考视频本身不稳定
分辨率或采样步数设置不合适
mask 覆盖范围过大

5. 表情僵硬但嘴型同步

可能原因:

复制代码
音频条件主要影响嘴部,面部其他 token 影响不足
参考视频表情太少
audio guide scale 偏低
模型对该语音风格泛化不足

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

Audio Cross Attention 是 InfiniteTalk 实现音频驱动视频生成的核心机制之一。

它的基本逻辑是:

复制代码
视频 token 生成 Query
音频 context token 生成 Key / Value
视频 token 通过 cross attention 从音频中读取信息
输出音频驱动的视频 token 更新量

在单人场景下,SingleStreamMutiAttention 直接复用基础 SingleStreamAttention

在多人场景下,它会结合 x_ref_attn_map,判断不同视频 token 更接近 person1、person2 还是背景。

然后通过 normalize_and_scaleRotaryPositionalEmbedding1D,把人物区域信息编码进视频 Query。

同时,它也会给不同人物的音频 Key 加上对应位置编码,让 person1 视频区域更容易关注 person1 音频,让 person2 视频区域更容易关注 person2 音频。

最后通过 xFormers 的 memory-efficient attention,得到音频条件对视频 token 的更新。

所以,语音不是直接控制像素,也不是只控制嘴巴。

它是通过 audio cross attention 影响视频 latent token,从而间接影响嘴型、表情、头部和身体动作。

这正是 InfiniteTalk 能够从传统 lip-sync 升级到音频驱动人物视频生成的关键。

下一篇我们会继续分析:

InfiniteTalk 源码解析 #9:长视频生成机制:streaming、motion_frame 与分块续接策略

前面我们已经理解了单个片段如何被音频驱动,下一篇要解决的问题是:如果视频很长,InfiniteTalk 如何让多个片段连续起来,不至于人物身份、动作和背景突然断掉?