VACE 实例控制原理
VACE(Wan 系列视频模型中的 Video Adapter for Controllable Editing)负责把实例级的视觉约束注入到主干 DiT 去噪器中。它的工作方式与 ControlNet 类似,但它直接处理视频 VAE 潜变量,并以跨层提示(hint)的形式影响特定的 Transformer Block。本说明基于 diffsynth/pipelines/wan_video.py 与 diffsynth/models/wan_video_vace.py 中的实现。
控制输入是如何组织的
分离被控实例与背景(
wan_video.py:616-671)- 用户提供
vace_video(目标动作参考)和vace_video_mask(逐像素的实例遮罩)。 - 管线首先将视频和遮罩正规化,然后用 VAE 编码器分别得到 “inactive”(背景:
video * (1 - mask))和 “reactive”(实例:video * mask)潜变量,并在通道维拼接,保留了“这个像素是否由实例驱动”的信息。 - 遮罩被重排成 8×8 patch,并插值到与潜变量时间分辨率一致,形成额外的 64 个通道,帮助模型知道哪些 token 需要强约束。
- 用户提供
可选的参考图像补帧
vace_reference_image会被打散成逐帧图像并送入同一个 VAE,输出与视频相同的 latent 格式。- 这些参考帧被拼在时间维的开头,并配套 0 值遮罩,使得模型在最开始的几个时间步直接看到参考姿态或风格。
统一的 VACE 上下文张量
- 最终
vace_video_latents(inactive+reactive)与vace_mask_latents被 concat 成vace_context,形状约为[B, 96, T/4, H/8, W/8](以默认 VAE + patch size 为例)。 - 如果没有传入任何控制项,则返回
None并跳过整个模块。
- 最终
VACE 模块内部结构
3D Patch Embedding 对齐 token(wan_video_vace.py:23-38)
VaceWanModel 通过一个 Conv3d(kernel_size=(1,2,2))对 vace_context 进行 patch 化,使输出 token 的时空分布与主干 DiT 的 latent token(x)严格一致。这一步确保后续可以逐 token 相加而无需插值。
与主干 DiT 同步的注意力块(wan_video_vace.py:5-22, 40-64)
- 每个
VaceWanAttentionBlock继承自DiTBlock,在第一个 block 内将 patch token 与当前的 DiT 激活x相加(self.before_proj(c) + x),以便直接读取主干的上下文。 - 每次前向都会保存一个
after_proj过的 skip 分支,并把它压入堆栈。下一层会弹出上一层的输出继续计算。 - 完成所有层后,
torch.unbind(c)[:-1]得到的就是与vace_layers一一对应的 hint 序列,每个 hint 的形状与x完全相同。
这种堆栈式设计允许 VACE 和主干 DiT 保持“层级对齐”:第 i 个 hint 只依赖第 i 层之前看到的特征,从而更像一个逐层附加的 Adapter,而不是单次生成所有控制信号。
计算和显存开销控制
VaceWanModel.forward() 支持 use_gradient_checkpointing 与 use_gradient_checkpointing_offload 参数,在长视频或 14B 模型上可以显著降低峰值显存。
Hint 如何注入 Wan DiT
先在时间步开始时生成所有 hint(
wan_video.py:1299-1306):if vace_context is not None: vace_hints = vace(x, vace_context, context, t_mod, freqs, ...)在指定的 Transformer Block 中相加(
wan_video.py:1336-1370):VaceWanModel暴露了vace_layers(默认(0, 2, 4, …, 28)),通过vace_layers_mapping把 block id 映射到 hint 序号。- 当循环到对应的 block 时,执行
x = x + current_vace_hint * vace_scale。 - 若开启 Unified Sequence Parallel,还会对 hint 做同样的
chunk与pad,保证分布式场景正常。
vace_scale控制注入强度- 小于 1 会弱化控制,趋近 0 时退化成原始 DiT;大于 1 会让生成更严格地跟随输入区域,但也可能带来 artifact。
- 该参数可以在推理时自由调整,属于最直接的“实例控制力度”调节钮。
整体实例控制流程总结
- 构造实例条件:用户提供控制视频、遮罩以及可选参考图像。遮罩把 latent 分成实例(reactive)与背景(inactive),并提供额外 mask token 告知模型哪里需要被“硬约束”。
- VACE 通过 3D patch embedding 和与主干对齐的 DiT block,生成一组跨层 hint。由于每层 hint 看到的都是局部且 mask-aware 的 token,它可以理解“这块区域属于某个实例,应按输入动作移动”。
- 主干 Wan DiT 在每个指定层把 hint 加到自身激活上,相当于在不同深度注入“我要让这个实例保持外观 / 跟随这段骨架”的约束。
vace_scale、vace_layers、遮罩形状共同决定了控制强度与范围,可通过只提供局部 mask 来达到实例级编辑、背景保护、局部跟随等效果。
实践建议
- 生成实例遮罩时尽量与输入视频逐帧对齐,否则 inactive/active 混淆会削弱控制效果。
- 当只需锁定某个主体的外观,可以提供静态
vace_reference_image而把vace_video设为全零,通过遮罩标记该主体所在区域;这样模型会在初始帧看到清晰参照。 - 如果需要更强的约束,可以在配置文件中把
vace_layers扩展到更深的层数,让 hint 影响更多语义层,但要注意显存和可能的 overfit。 - 在多机推理时记得同步
vace_scale,并保持遮罩尺寸能被 8 整除(VAE 下采样 + 8×8 patch),否则会被rearrange抛错。
通过以上机制,VACE 在 Wan 视频模型中提供了一个“实例级 ControlNet”:它不直接更改像素,而是以多层特征 hint 的方式牵引主干去噪过程,让指定的实例在时空上维持用户期望的动作与外观。