论文概述
Stable Video Infinity (SVI) 是由 EPFL VITA Lab 提出的无限长度视频生成方法,在 ICLR 2026 获得 Oral 论文荣誉1。
核心贡献
| 贡献 | 描述 |
|---|---|
| 错误回收微调 | Error-Recycling Fine-Tuning (ERFT) |
| 无限长度生成 | 可从几秒扩展到任意时长 |
| 多条件支持 | 音频、姿态、文本等多模态条件 |
| 零额外推理开销 | 与基线模型相同的计算成本 |
作者团队
- Wuyang Li, Wentao Pan, Po-Chien Luan, Yang Gao, Alexandre Alahi
- VITA Lab, EPFL (瑞士洛桑联邦理工学院)
问题背景:视频生成的drift问题
什么是 Drift 问题
在视频生成领域,漂移问题 (Drift Problem) 是制约长视频生成的核心障碍1。当视频生成超过一定长度后,会出现:
- 外观漂移:角色外观逐渐改变甚至变形
- 场景漂移:背景布局无法保持一致
- 物体消失/重复:物体莫名其妙出现或消失
- 物理失效:重力、光影等物理规律崩溃
Drift 的本质原因
SVI 论文识别出 drift 问题的根本原因并非仅仅是误差累积,而是一个更根本的假设鸿沟:
训练假设:模型在干净数据上训练, conditioning on
测试现实:模型在自生成输出上推理, conditioning on
这种假设鸿沟 (Hypothesis Gap) 导致:
现有方法的局限
| 方法 | 技术 | 局限 |
|---|---|---|
| 修改噪声调度器 | 调整去噪轨迹 | 仅适用于单一提示 |
| 帧锚定 | 固定关键帧 | 场景同质化 |
| 循环平滑 | 后处理技术 | 无法纠正根本问题 |
这些方法都试图通过外部干预来缓解问题,而非让模型主动学习识别和纠正错误。
核心创新:错误回收机制
核心洞察
SVI 的核心洞察是:让 DiT 模型学会主动检测并纠正自身的复合错误。
与其尝试在推理时掩盖错误,不如在训练时就让模型学会处理错误累积的分布。
Error-Recycling Fine-Tuning (ERFT)
错误回收微调是 SVI 的核心创新,包含三个关键步骤:
┌─────────────────────────────────────────────────────────────┐
│ Error-Recycling Fine-Tuning │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 注入 (Inject) │
│ 将历史错误注入干净输入,模拟错误累积轨迹 │
│ x_clean ──► 注入错误 ──► x_error │
│ │
│ 2. 收集 (Collect) │
│ 单步双向集成近似预测,计算残差作为错误 │
│ x_error ──► 一步去噪 ──► ε │
│ │
│ 3. 存储 (Bank) │
│ 动态存储错误到重放记忆,按时间步离散化 │
│ Error Bank ◄── 跨时间步存储 ◄── 残差 │
│ │
└─────────────────────────────────────────────────────────────┘
关键机制详解
1. 错误注入 (Error Injection)
其中:
- 是干净的真实帧
- 是 DiT 在之前生成中累积的历史错误
- 是注入强度系数
2. 单步双向集成 (One-Step Bidirectional Integration)
使用近似预测计算残差:
这种双向集成方法高效地估算了模型对当前状态的”误差感知”。
3. 动态错误银行 (Dynamic Error Bank)
将错误存储在按时间步离散化的重放记忆中:
在训练新片段时,从银行中重采样错误模式进行监督。
技术实现
架构概述
SVI 基于 Diffusion Transformer (DiT) 架构,包括:
- 3D VAE:视频时空压缩
- DiT 主干:潜在空间去噪
- 条件编码器:多模态条件注入
- 错误银行模块:记忆化错误分布
训练流程
class ErrorRecyclingFineTuning:
"""
错误回收微调主流程
"""
def __init__(self, model, error_bank_size=10000):
self.model = model
self.error_bank = ErrorBank(size=error_bank_size)
self.noise_scheduler = FlowMatchingScheduler()
def training_step(self, batch, global_step):
"""
单步训练
"""
# 1. 采样干净视频片段
x_clean = batch['video'] # [B, T, C, H, W]
# 2. 时间步采样
t = sample_timesteps(len(x_clean), self.model.num_timesteps)
# 3. 添加噪声 (流匹配)
noise = torch.randn_like(x_clean)
x_noisy = self.noise_scheduler.add_noise(x_clean, t, noise)
# 4. 错误注入 (关键创新)
if global_step > self.warmup_steps:
# 从错误银行采样历史错误
sampled_errors = self.error_bank.sample(batch_size=len(x_clean))
# 注入到输入中
x_injected = self.inject_errors(x_noisy, sampled_errors, t)
else:
x_injected = x_noisy
# 5. 预测噪声/速度
noise_pred = self.model(x_injected, t, condition=batch['condition'])
# 6. 计算损失
loss = self.compute_loss(noise_pred, noise)
# 7. 计算并存储残差 (错误)
with torch.no_grad():
residual = self.compute_residual(x_injected, noise_pred, t)
# 按时间步存储
self.error_bank.add(residual, t)
return loss
class ErrorBank:
"""
动态错误银行
按时间步离散化存储错误
"""
def __init__(self, size=10000):
self.size = size
self.bank = {} # {timestep: list of (error, count)}
self.counts = {}
def add(self, residual, timestep):
"""添加残差到银行"""
t_idx = self.discretize_timestep(timestep)
if t_idx not in self.bank:
self.bank[t_idx] = []
self.counts[t_idx] = 0
# FIFO 缓冲
if len(self.bank[t_idx]) >= self.max_per_timestep:
self.bank[t_idx].pop(0)
self.bank[t_idx].append(residual.detach())
self.counts[t_idx] += 1
def sample(self, batch_size):
"""从银行采样错误"""
errors = []
for _ in range(batch_size):
# 随机选择时间步
t_idx = random.choice(list(self.bank.keys()))
if len(self.bank[t_idx]) > 0:
# 随机采样该时间步的错误
error = random.choice(self.bank[t_idx])
errors.append(error)
else:
errors.append(None)
return errors
def discretize_timestep(self, t, num_bins=100):
"""离散化时间步"""
return int(t * num_bins / self.model.num_timesteps)推理流程
@torch.no_grad()
def generate_infinite_video(model, start_clip, condition_fn,
window_size=16, step_size=12):
"""
无限长度视频生成
Args:
model: SVI 模型
start_clip: 初始片段
condition_fn: 条件函数(文本、音频等)
window_size: 窗口大小
step_size: 步长 (< window_size 用于重叠)
"""
video_frames = []
overlap_buffer = None
# 生成初始片段
current = start_clip
while True:
# 1. 获取当前窗口的条件
condition = condition_fn(
current_frame_idx=len(video_frames),
context=overlap_buffer
)
# 2. 生成当前窗口
window = model.generate(
start_frame=current,
num_frames=window_size,
condition=condition,
guidance_scale=7.5
)
# 3. 拼接视频
if len(video_frames) == 0:
video_frames = window[:window_size]
else:
# 保留非重叠部分
video_frames = torch.cat([
video_frames,
window[window_size - step_size:]
], dim=0)
# 4. 更新重叠缓冲区
overlap_buffer = window[-step_size:]
current = overlap_buffer[0:1] # 下一窗口的起始帧
# 5. 检查停止条件
if condition_fn.is_end():
break
return video_frames实验结果
基准测试
SVI 在三个基准上进行了全面评估:
| 基准 | 描述 | 评估指标 |
|---|---|---|
| 一致性基准 | 场景、角色、外观一致性 | FID, FVD, CS |
| 创意基准 | 复杂动作、场景转换 | 人工评分 |
| 条件基准 | 音频驱动、姿态控制 | CLIPScore |
与 SOTA 方法对比
| 方法 | 最大长度 | 时间一致性 | 场景多样性 | 推理开销 |
|---|---|---|---|---|
| 自回归 DiT | ~10秒 | 0.45 | 低 | 1.0x |
| Self-Forcing | ~2分钟 | 0.61 | 中 | 3.0x |
| LoL | ~10分钟 | 0.68 | 中 | 2.5x |
| MALT | 任意 | 0.72 | 高 | 1.5x |
| SVI | 无限 | 0.82 | 高 | 1.0x |
关键发现
- 零额外推理开销:SVI 无需额外的推理计算,与基线模型相同
- 质量稳定:视频质量不随长度下降
- 场景切换自然:支持复杂的场景转换
- 多条件兼容:音频、姿态、文本等条件无缝集成
代码实现
PyTorch 伪代码
import torch
import torch.nn as nn
from typing import Callable, Optional, List
class ErrorRecyclingModule(nn.Module):
"""
错误回收模块
SVI 的核心组件
"""
def __init__(self, model: nn.Module, num_timesteps: int = 1000,
error_bank_size: int = 10000, inject_strength: float = 0.1):
super().__init__()
self.model = model
self.num_timesteps = num_timesteps
self.inject_strength = inject_strength
# 错误银行:按时间步组织
self.register_buffer('error_bank', torch.zeros(
num_timesteps, error_bank_size, model.latent_dim
))
self.error_bank_ptr = torch.zeros(num_timesteps, dtype=torch.long)
def inject_errors(self, x: torch.Tensor, t: torch.Tensor,
errors: Optional[torch.Tensor] = None) -> torch.Tensor:
"""
将历史错误注入干净输入
Args:
x: 输入张量 [B, T, C, H, W]
t: 时间步 [B]
errors: 可选的预计算错误
Returns:
注入错误后的张量
"""
if errors is None:
# 从错误银行采样
errors = self.sample_errors_from_bank(t, x.shape)
if errors is None:
return x
# 缩放错误并注入
errors = errors.to(x.device)
if errors.shape != x.shape:
errors = errors.repeat_interleave(
x.shape[1] // errors.shape[1] + 1, dim=1
)[:, :x.shape[1]]
return x + self.inject_strength * errors
def sample_errors_from_bank(self, t: torch.Tensor,
shape: tuple) -> Optional[torch.Tensor]:
"""从错误银行采样"""
batch_size = shape[0]
sampled = []
for i in range(batch_size):
t_idx = int(t[i].item() * len(self.error_bank) / self.num_timesteps)
t_idx = min(t_idx, len(self.error_bank) - 1)
ptr = self.error_bank_ptr[t_idx]
if ptr > 0:
idx = torch.randint(0, ptr, (1,))
sampled.append(self.error_bank[t_idx, idx])
else:
sampled.append(None)
# 过滤 None 值
valid = [s for s in sampled if s is not None]
if len(valid) == 0:
return None
return torch.stack(valid)
def update_error_bank(self, x: torch.Tensor, pred: torch.Tensor,
t: torch.Tensor):
"""更新错误银行"""
# 计算残差作为错误
residual = x - pred.detach()
for i in range(len(t)):
t_idx = int(t[i].item() * len(self.error_bank) / self.num_timesteps)
t_idx = min(t_idx, len(self.error_bank) - 1)
ptr = self.error_bank_ptr[t_idx].item()
if ptr < self.error_bank.shape[1]:
self.error_bank[t_idx, ptr] = residual[i].detach()
self.error_bank_ptr[t_idx] = ptr + 1
def forward(self, x: torch.Tensor, t: torch.Tensor,
condition: Optional[torch.Tensor] = None,
inject: bool = True) -> torch.Tensor:
"""
前向传播
Args:
x: 噪声/干净视频
t: 时间步
condition: 条件嵌入
inject: 是否注入错误
"""
if inject and self.training:
x = self.inject_errors(x, t)
output = self.model(x, t, condition)
if self.training:
self.update_error_bank(x, output, t)
return output
class SVIGenerator:
"""
Stable Video Infinity 生成器
"""
def __init__(self, model: ErrorRecyclingModule,
vae, scheduler, device: str = 'cuda'):
self.model = model
self.vae = vae
self.scheduler = scheduler
self.device = device
@torch.no_grad()
def generate(self,
prompt: str,
num_frames: int = 16,
window_size: int = 16,
step_size: int = 12,
guidance_scale: float = 7.5,
num_inference_steps: int = 50) -> torch.Tensor:
"""
生成无限长度视频
Args:
prompt: 文本提示
num_frames: 目标帧数
window_size: 生成窗口大小
step_size: 步长
guidance_scale: CFG 引导强度
num_inference_steps: 去噪步数
Returns:
生成的视频 [T, C, H, W]
"""
# 编码文本条件
text_emb = self.encode_prompt(prompt)
# 初始化潜在表示
latents = torch.randn(
1, num_frames, self.vae.latent_dim,
device=self.device
)
# 去噪过程
for t in reversed(range(num_inference_steps)):
# 扩展潜在表示以覆盖所有窗口
t_tensor = torch.full((latents.shape[0],), t,
device=self.device, dtype=torch.long)
# 预测噪声
noise_pred = self.model(
latents, t_tensor, condition=text_emb, inject=False
)
# 应用引导
if guidance_scale > 1.0:
noise_pred = guidance_scale * noise_pred - (guidance_scale - 1) * latents
# 调度器步骤
latents = self.scheduler.step(noise_pred, t, latents)
# 解码潜在表示
video = self.vae.decode(latents)
return video
def encode_prompt(self, prompt: str) -> torch.Tensor:
"""编码文本提示"""
# 简化实现
return self.model.model.condition_encoder(prompt)
# 训练示例
def train_step(model, batch, optimizer):
"""
SVI 训练步骤
"""
# 解码视频到潜在空间
with torch.no_grad():
x_clean = model.vae.encode(batch['video'])
# 采样时间步
t = torch.randint(0, model.num_timesteps, (batch['video'].shape[0],),
device=batch['video'].device)
# 添加噪声
noise = torch.randn_like(x_clean)
x_noisy = model.scheduler.add_noise(x_clean, t, noise)
# 预测噪声
noise_pred = model(
x_noisy, t,
condition=batch['condition'],
inject=True # 注入错误进行学习
)
# 计算损失
loss = nn.functional.mse_loss(noise_pred, noise)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
return loss.item()与其他长视频方法对比
| 维度 | 自回归 | 分层生成 | 滑动窗口 | 记忆增强 | SVI |
|---|---|---|---|---|---|
| 核心技术 | 逐帧生成 | 金字塔 | 固定拼接 | 记忆查询 | 错误回收 |
| 长度限制 | 受限 | 中等 | 固定 | 任意 | 无限 |
| 一致性 | ↓ | 中 | ↓↓ | ↑ | ↑↑ |
| 场景切换 | 不支持 | 简单 | 不支持 | 支持 | 完全支持 |
| 计算开销 | 高 | 中 | 低 | 中 | 无 |
| 质量保持 | 差 | 中 | 差 | 好 | 优秀 |
SVI 的独特优势
- 假设对齐:训练时模拟推理错误分布,弥合假设鸿沟
- 无需后处理:无需额外的平滑或修正模块
- 即插即用:可应用于多种 DiT 架构
- 持续学习:错误银行随训练不断丰富
参考资源
论文
- arXiv: 2510.09212 - Stable Video Infinity: Infinite-Length Video Generation with Error Recycling
代码
- GitHub: vita-epfl/Stable-Video-Infinity (2.3k+ Stars)
演示
- 项目主页: stable-video-infinity.github.io
- HuggingFace: EPFL-LSV/svi-video-model
- 支持模型: Wan 2.2 (14B) 适配版本
相关论文
- MALT Diffusion - 记忆增强的长视频生成
- 视频扩散模型基础
- Diffusion Transformer 架构
- 长视频生成的Drift问题与解决方案
总结
Stable Video Infinity 通过 Error-Recycling Fine-Tuning 机制解决了一个根本性问题:
训练与推理之间的假设鸿沟
核心要点
- 问题本质:Drift 不是简单的误差累积,而是训练-推理分布不匹配
- 解决方案:让模型主动学习识别和纠正自身错误
- 实现方式:错误注入 + 残差计算 + 动态存储
- 效果:无限长度、高质量、多条件支持
未来方向
- 更高效的错误银行管理
- 自适应错误注入强度
- 与世界模型的结合
本页面基于 arXiv:2510.09212 构建,ICLR 2026 Oral 论文。