| # 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` 中的实现。 |
|
|
| ## 控制输入是如何组织的 |
|
|
| 1. **分离被控实例与背景**(`wan_video.py:616-671`) |
| - 用户提供 `vace_video`(目标动作参考)和 `vace_video_mask`(逐像素的实例遮罩)。 |
| - 管线首先将视频和遮罩正规化,然后用 VAE 编码器分别得到 “inactive”(背景:`video * (1 - mask)`)和 “reactive”(实例:`video * mask`)潜变量,并在通道维拼接,保留了“这个像素是否由实例驱动”的信息。 |
| - 遮罩被重排成 8×8 patch,并插值到与潜变量时间分辨率一致,形成额外的 64 个通道,帮助模型知道哪些 token 需要强约束。 |
|
|
| 2. **可选的参考图像补帧** |
| - `vace_reference_image` 会被打散成逐帧图像并送入同一个 VAE,输出与视频相同的 latent 格式。 |
| - 这些参考帧被拼在时间维的开头,并配套 0 值遮罩,使得模型在最开始的几个时间步直接看到参考姿态或风格。 |
|
|
| 3. **统一的 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 |
|
|
| 1. **先在时间步开始时生成所有 hint**(`wan_video.py:1299-1306`): |
|
|
| ```python |
| if vace_context is not None: |
| vace_hints = vace(x, vace_context, context, t_mod, freqs, ...) |
| ``` |
|
|
| 2. **在指定的 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`,保证分布式场景正常。 |
|
|
| 3. **`vace_scale` 控制注入强度** |
| - 小于 1 会弱化控制,趋近 0 时退化成原始 DiT;大于 1 会让生成更严格地跟随输入区域,但也可能带来 artifact。 |
| - 该参数可以在推理时自由调整,属于最直接的“实例控制力度”调节钮。 |
| |
| ## 整体实例控制流程总结 |
| |
| 1. 构造实例条件:用户提供控制视频、遮罩以及可选参考图像。遮罩把 latent 分成实例(reactive)与背景(inactive),并提供额外 mask token 告知模型哪里需要被“硬约束”。 |
| 2. VACE 通过 3D patch embedding 和与主干对齐的 DiT block,生成一组跨层 hint。由于每层 hint 看到的都是局部且 mask-aware 的 token,它可以理解“这块区域属于某个实例,应按输入动作移动”。 |
| 3. 主干 Wan DiT 在每个指定层把 hint 加到自身激活上,相当于在不同深度注入“我要让这个实例保持外观 / 跟随这段骨架”的约束。 |
| 4. `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 的方式牵引主干去噪过程,让指定的实例在时空上维持用户期望的动作与外观。 |
| |