长视频与一致性:分段、滑窗与自回归¶
5 秒的视频已经要十万 token,一分钟的视频按同样的方式做就是百万 token——注意力的平方项让"一次生成整条长视频"在任何硬件上都不现实。所以长视频一定是**分段**的:要么把前一段的末尾当下一段的条件,要么用滑动窗口,要么干脆把模型做成自回归的、按块因果生成。分段带来两个新问题:段与段之间的重复计算,以及误差沿着段累积造成的漂移。这一章把三条路的成本算清楚,用一个玩具把漂移演示出来,再看自回归视频模型为什么让"KV Cache"重新出现在扩散推理里——以及它对服务意味着什么。
自测:能答上来就可以跳过本章
- 为什么不能把 60 秒的视频当一个序列一次生成?算一下 token 数和注意力成本。
- 分段生成怎么保证段与段之间连贯?重叠的代价是什么?
- 什么是漂移?为什么分段越多越严重?有哪些缓解办法?
- 自回归视频扩散模型是怎么工作的?它和普通的双向扩散模型在推理成本上有什么本质区别?
- 自回归视频模型里的 KV Cache 和 LLM 的有什么异同?
自测参考答案(先自己答,再展开对照)
- 60 秒 720p 24 fps 约 1440 帧,潜帧 361,token 约 130 万;注意力 \(\propto N^2\),比 5 秒视频贵 300 倍,一步就是百万 TFLOP 量级,显存里的激活也放不下。
- 把前一段的最后几帧(经 VAE 编码的潜帧)作为下一段的条件(首帧条件的推广),或者让相邻两段重叠几帧、在重叠区把两段的去噪结果混合。重叠的帧要算两次,重叠越多越连贯、重复计算越多。
- 每一段的生成都带一点误差(颜色偏移、细节变化),下一段以它为条件就把误差继承并叠加,几十段之后画面明显变色、主体走样。缓解:锚定首帧或参考图(每段都看得到最初的条件)、定期重锚、噪声重调度、把条件帧的权重调高、训练时模拟推理的分布(Self-Forcing)。
- 把视频按时间分成块,块内双向注意力、块间因果:生成第 \(k\) 块时只看前面的块。于是前面块的 K、V 可以缓存,新块的注意力成本只和"新块 × 全部已有块"成正比,整条视频的成本随长度线性增长而不是平方;而且可以流式输出。
- 相同:都是把已算过的 K、V 存下来给后面复用,显存随长度增长,需要分页 / 淘汰。不同:视频每个"位置"是一整块(几千上万 token)而不是一个 token;每块内部还要做多步去噪,缓存的是去噪完成后的 K、V;长视频里通常只保留最近若干块(滑动窗口)加锚块,而不是全部历史。
为什么必须分段¶
def tokens(seconds, fps=24, h=720, w=1280, ft=4, fs=8, p=2):
frames = int(seconds * fps) + 1
T = 1 + (frames - 1) // ft
return T * (h // fs // p) * (w // fs // p)
d, L = 5120, 40
print(f"{'时长':>6} {'token 数':>10} {'一步注意力 PFLOP':>15} {'相对 5 秒':>8}")
base = None
for sec in (5, 10, 20, 60):
n = tokens(sec)
attn = L * 4 * n * n * d / 1e15
base = base or attn
print(f"{sec:>5}s {n:>10,} {attn:>15.1f} {attn / base:>7.0f}×")
时长 token 数 一步注意力 PFLOP 相对 5 秒
5s 111,600 10.2 1×
10s 219,600 39.5 4×
20s 435,600 155.4 15×
60s 1,299,600 1383.6 136×
60 秒是 5 秒的 150 倍——不是慢一点,是不可能。所以长视频一定要切。
三条路¶
| 路线 | 做法 | 成本 | 连贯性 | 代表 |
|---|---|---|---|---|
| 分段 + 条件拼接 | 每段独立生成,前一段末尾几帧当下一段的条件 | 线性,每段付一次首帧条件 | 靠条件帧,容易漂移 | 各家 I2V 模型的"续写" |
| 滑动窗口 / 重叠去噪 | 窗口沿时间滑动,重叠区混合两边的结果 | 线性 × (1 + 重叠比例) | 更平滑,仍会漂移 | FreeNoise、Gen-L-Video |
| 自回归(块因果) | 块内双向、块间因果,缓存前面块的 K、V | 线性,有 KV Cache | 训练时专门对抗漂移 | CausVid、Self-Forcing、MAGI-1、LTX 的流式模式 |
前两条用的是普通的双向扩散模型(不改模型,靠推理流程),第三条要专门训练。先看前两条的成本:
def segmented_cost(seconds, seg_seconds=5, overlap_seconds=1):
"""分段生成的总注意力成本(相对一段 5 秒),重叠的帧算两次"""
n_seg = max(1, round((seconds - overlap_seconds) / (seg_seconds - overlap_seconds)))
per_seg = (tokens(seg_seconds) / tokens(5)) ** 2
return n_seg, n_seg * per_seg
print(f"{'时长':>6} {'一次生成(相对)':>12} {'分 5 秒段/重叠 1 秒':>16} {'段数':>4} {'重叠带来的重复':>12}")
for sec in (5, 10, 20, 60):
one = (tokens(sec) / tokens(5)) ** 2
n_seg, seg = segmented_cost(sec)
no_overlap = sec / 5
print(f"{sec:>5}s {one:>12.0f}× {seg:>16.1f}× {n_seg:>4} {seg / no_overlap - 1:>11.0%}")
时长 一次生成(相对) 分 5 秒段/重叠 1 秒 段数 重叠带来的重复
5s 1× 1.0× 1 0%
10s 4× 2.0× 2 0%
20s 15× 5.0× 5 25%
60s 136× 15.0× 15 25%
分段把平方变成了线性,重叠 1 秒多付两成多的计算。真正的代价不在算力,在漂移。
漂移:误差怎么沿着段累积¶
每一段的生成都不完美:条件帧经过 VAE 编码再解码有损失,去噪本身有随机性,模型对"自己生成的画面"的分布也不完全熟悉。下一段以它为条件,误差就被继承;几十段之后画面会明显偏色、主体走样。用一个最简单的玩具把它量出来:每一段把上一段的末状态当条件,生成时带一点随机偏移,比较"只看上一段"和"同时锚定首帧"两种做法:
import torch
torch.manual_seed(0)
def generate_segment(cond, anchor, w_anchor, noise=0.08):
"""玩具:新段的状态 = 条件的加权组合 + 一点生成误差(偏移是有方向的,模拟 VAE 往返和模型偏好)"""
target = (1 - w_anchor) * cond + w_anchor * anchor
return target + noise * (torch.randn_like(cond) + 0.3) # +0.3 是系统性偏移:每段都往同一个方向偏一点
first = torch.zeros(16) # 首帧(作为锚)
print(f"{'段数':>4} {'只看上一段:离首帧的距离':>22} {'锚定首帧(权重 0.3)':>16} {'每 5 段重锚一次':>14}")
for mode in range(1):
chain, anchored, periodic = first.clone(), first.clone(), first.clone()
for k in range(1, 41):
chain = generate_segment(chain, first, 0.0)
anchored = generate_segment(anchored, first, 0.3)
periodic = generate_segment(periodic, first, 1.0 if k % 5 == 0 else 0.0)
if k in (1, 4, 5, 9, 10, 19, 20, 39, 40):
print(f"{k:>4} {chain.norm():>22.2f} {anchored.norm():>16.2f} {periodic.norm():>14.2f}")
段数 只看上一段:离首帧的距离 锚定首帧(权重 0.3) 每 5 段重锚一次
1 0.30 0.37 0.30
4 0.71 0.44 0.77
5 0.84 0.45 0.29
9 1.54 0.60 1.06
10 1.77 0.79 0.35
19 2.56 0.41 1.06
20 2.60 0.63 0.21
39 4.69 0.41 1.01
40 4.85 0.32 0.24
只看上一段时,距离随段数线性增长(系统性偏移在累加);锚定首帧把它压在一个有界的范围里不再增长;定期重锚在两次重锚之间照样漂(第 4、9、19、39 段),重锚那一段又猛地跳回去(真实视频里就是"每隔几秒画面闪一下")。真实系统里的缓解手段都是这个玩具的变体:
- 参考图 / 首帧始终在条件里(主体一致性):每一段都能看到最初的锚;
- 把条件帧的来源做干净:条件用潜变量而不是解码再编码的像素,避免 VAE 往返的损失;
- 噪声重调度(FreeNoise 一类):相邻窗口共享一部分初始噪声,减少段间的随机差异;
- 训练时对抗漂移:让模型在训练时就以"自己生成的帧"为条件(Self-Forcing、DMD 式的自回归蒸馏),这是自回归模型那条路的核心。
自回归视频扩散:KV Cache 回来了¶
把视频按时间分成块(比如每块 4 个潜帧),块内双向注意力、块间因果:生成第 \(k\) 块时只看前面的块。这带来三个推理上的变化:
- 前面块的 K、V 可以缓存。生成新块时,新块的 token 对全部已有块做注意力,但已有块的 K、V 不用重算——和 LLM 的 KV Cache 一回事。成本从平方变线性:
def ar_cost(n_blocks, block_tokens, d, L, window=None):
"""自回归生成 n 个块的注意力成本:第 k 块对前面 min(k, window) 个块的 K、V 做注意力(块内也算)"""
total = 0
for k in range(1, n_blocks + 1):
visible = k if window is None else min(k, window)
total += L * 4 * block_tokens * (visible * block_tokens) * d
return total
blk = tokens(5) // 5 # 每块约 1 秒:Wan 720p 的 1/5
print(f"{'时长':>6} {'双向一次生成':>12} {'自回归(看全部历史)':>16} {'自回归(窗口 10 块)':>16}")
for sec in (5, 20, 60):
n_blocks = sec
full = L * 4 * tokens(sec) ** 2 * d
print(f"{sec:>5}s {full / 1e15:>10.0f} PF {ar_cost(n_blocks, blk, d, L) / 1e15:>14.0f} PF {ar_cost(n_blocks, blk, d, L, 10) / 1e15:>14.0f} PF")
时长 双向一次生成 自回归(看全部历史) 自回归(窗口 10 块)
5s 10 PF 6 PF 6 PF
20s 155 PF 86 PF 63 PF
60s 1384 PF 747 PF 227 PF
-
可以流式输出:第一块生成完就能解码、播放,用户感知的延迟是"第一块"而不是"整条"。配合少步蒸馏(每块 4 步),1.3B 规模的模型在一张 H100 上能做到接近实时(CausVid、Self-Forcing 报告的是每秒十几帧的流式生成)。
-
暴露偏差要在训练时解决:模型训练时看到的是真实的历史帧,推理时看到的是自己生成的,分布不一样,误差照样会累积。Self-Forcing 这类方法在训练时就用模型自己生成的块当历史,把推理时的分布直接教给模型——这是自回归路线能走到几十秒而不崩的关键。
和 LLM 的 KV Cache 比¶
| LLM | 自回归视频扩散 | |
|---|---|---|
| 缓存的单位 | 一个 token 的 K、V | 一整块(几千 token)的 K、V,而且是去噪完成后的 |
| 一次前向处理 | 1 个新 token | 1 个新块 × 去噪步数(每步都要对历史做注意力) |
| 历史长度 | 全部,直到上下文上限 | 通常只保留最近若干块 + 锚块(滑动窗口),否则显存和成本都线性涨 |
| 显存 | 随序列线性增长,需要分页 | 同样随块数增长,块大所以更快碰到上限 |
| 复用 | 前缀缓存跨请求复用 | 同一条视频内复用;跨请求复用只在"续写同一视频"时有 |
推理系统这边的含义:自回归视频模型是第一类能用上 LLM 服务那套机制的生成模型——分页的 KV 池、按块的调度、流式输出、续写请求的缓存命中(见手写 mini-sglang和分页 KV Cache)。SGLang Diffusion、vLLM-Omni 这些框架把扩散和 LLM 放在同一个引擎里,看中的正是这一点。
服务层的含义¶
长视频把生成变成了长任务:
- 流式输出:按段 / 按块解码并返回,用户看到第一段的延迟决定体验;
- 断点与取消:几分钟的任务要能中途取消(省下后面的算力),最好能从某一段续生成;
- 资源占用:一条长视频占着一张或几张卡几分钟,调度器要按"预计时长"排队,而不是按请求数;
- 质量监控:漂移是渐进的,可以在段间做轻量的一致性检测(颜色直方图、参考图相似度),超出阈值就重锚或告警。
这些在生成服务的调度里展开。
怎么讲清楚
讲"长视频怎么生成",先给不可能性:60 秒的 token 是 5 秒的 12 倍、注意力 150 倍,必须分段。再给三条路:分段条件拼接(线性成本,靠条件帧,易漂移)、滑窗重叠(更平滑,多付两成计算)、自回归块因果(线性成本、有 KV Cache、能流式,但要专门训练并解决暴露偏差——Self-Forcing 用自己生成的块当训练历史)。漂移的本质是系统性误差沿段累加,锚定首帧或参考图能把它压成常数。最后点出系统含义:自回归视频模型让分页 KV、块调度、流式输出这套 LLM 服务机制第一次用到了生成模型上。
练习¶
- 把漂移玩具里的系统性偏移从 0.3 改成 0(只有随机误差),只看上一段的距离怎么增长?这说明漂移的两种来源各是什么性质?
参考答案
距离按 \(\sqrt{k}\) 增长(随机游走),比线性慢得多。系统性偏移(VAE 往返的固定损失、模型对某类画面的偏好)线性累加,是漂移的主要来源;随机误差只带来缓慢的扩散。所以缓解漂移首先要消除系统性的偏(条件用潜变量、模型训练时见过自己的输出),其次才是减少随机性。
- 自回归生成里,把窗口从 10 块改成 3 块,60 秒的成本降到多少?代价是什么?
参考答案
成本接近 \(n \times 3 \times\) 块成本,比窗口 10 再降约 3 倍。代价是模型只能看到最近 3 秒,更早的内容只能靠锚块或参考图维持——长程一致性(人物离开画面再回来)会变差。实际系统常用"最近几块 + 首块(锚)"的组合。
- 一个服务同时跑 5 秒的短视频请求和 60 秒的长视频请求。按"请求数"排队会出什么问题?你会怎么设计?
参考答案
一条 60 秒的请求占卡的时间是短请求的 12 倍以上,按请求数排队会让短请求排在长请求后面等好几分钟。应该按预计耗时(token 数 × 步数)排队并分池:长任务用专门的实例或固定配额,短任务保证低延迟;长任务按段流式返回并支持取消;过载时拒绝或降级(减少段数、降分辨率)长任务而不是让所有人一起慢。
小结¶
- 长视频不能一次生成:60 秒的注意力是 5 秒的 150 倍;必须分段,分段把平方变线性,重叠多付两成。
- 漂移是系统性误差沿段累加,只看上一段时线性增长;锚定首帧 / 参考图把它压成常数,噪声重调度和训练时对抗(Self-Forcing)是进一步的手段。
- 自回归块因果模型让 KV Cache 回到扩散推理:成本线性、能流式、少步蒸馏后接近实时;但要专门训练解决暴露偏差,历史通常只保留最近几块加锚块。
- 长视频是长任务:按预计时长排队、流式输出、可取消、段间一致性监控——这是 LLM 服务那套机制在生成模型上的第一次落地。
发现错误或有更好的写法?在 GitHub 上编辑此页提交问题