VSR Hover Comparison

768p → 2× RTX 5090

鼠标在画面上左右移动即可擦除对比 —— 光标左边是 A,右边是 B。两条视频严格同帧、同步播放。 开 放大镜 看纹理级差异;按 空格 播放/暂停, 逐帧。 暂停 / 逐帧 / 拖进度条时,两边会一起锁到同一帧的中点,控制条右侧显示当前帧号。

本轮的重点是 FlashVSR 原版 vs 调优版,页面默认就开在这两个上;其余方法作为基础参照保留。

调优版做到了什么

配置2688×1536 × 141 帧显存峰值
原版(full,kv 3.0,管线默认 VAE 分块)385.2 s29.15 GiB
调优版(tiny + 2 空间块)157.3 s20.13 GiB

2.5× 速度,−31% 显存。15s 容量:16:9 2688×1536 × 360 帧 409.9 s / 20.13 GiB; 21:9 3584×1536 × 360 帧 558.6 s / 22.19 GiB。21:9 只有靠空间分块才成立—— 整幅送进去会死在一个 2.15 GiB 的定长分配上。

但速度不是免费的,而且你未必需要付这笔钱。 2×2 拆解(见下方技术细节)证明感知分的损失全部来自换解码器kv_ratio 和分块都是白捡的。所以还有第三个配置: 保留作者的解码器,只吃内存优化
配置2688×1536×141显存峰值画质
原版(无分块)385.2 s29.15 GiB基准
画质优先:full + kv1.5 + 2 块 381.8 s16.52 GiB 与原版同解码器,逐位同配方
速度优先:tiny + kv1.5 + 2 块 157.3 s20.13 GiB 感知分下降(MUSIQ −2.3,CLIP-IQA −0.06)
画质优先版把显存从 29.15 砍到 16.52 GiB(−43%),速度基本不变,画质一点不动。 它比速度优先版还省 3.6 GiB——因为 Wan VAE 解码器的分块比 TCDecoder 更碎。 15s 16:9 / 15s 21:9 / 24 GB 它全部跑得过(见下一节),所以不肯掉画质的就用这个, 唯一放弃的是那 2.4× 速度。

24 GB(4090)实测:把 CUDA 分配器硬卡在 23.0 GiB(先用一个已知需 30.5 GiB 的配置做阳性对照, 确认 cap 真的会 OOM),上面四种场景全部通过;15s 21:9 改 3 块后峰值降到 16.44 GiB 而耗时不变(558.0 s), 是 24 GB 卡上更该用的档位。注意:这只模拟了显存预算,没有模拟 sm_89 kernel 和 Ada 更低的带宽, 真 4090 上耗时会更长。

数据集
片源
方法

单击设为 A(左),再次单击同一个设为 B(右)。 也可以直接点另一个方法替换 B。

A
B
载入中…
0.00 / 0.00 s

这条片子上的分数

FlashVSR 原版 vs 调优版 · 配对差值

两个配置跑的是同一批片子,所以看的是逐条配对差值,不是各自的均值—— 片子之间的难度差异会在配对里抵消掉。CI 是 2000 次 bootstrap 重采样,种子固定。 差值 = 调优版 − 原版;CI 不跨 0 才算真差异。

「原版」= 作者发布的模型配置(full 管线 / Wan VAE 解码器,kv_ratio 3.0), 跑在同一套内存脚手架里(输入 pad、尾部补 4 帧、scale=2、空间分块)—— 不共用这套脚手架,两次跑的就不是同一批像素,也就没法比。 所以表里原版在 MiniMax 上的峰值是 19.67 GiB 而不是 29.15 GiB: 29.15 是原版配置分块时的数字,那才是它自己跑 4 Mpx 的代价。

画质优先配置的容量 —— 15s / 21:9 / 24 GB 全过

full + kv1.5 + 空间分块,也就是作者的解码器 + 全部内存优化。 这些行是纯容量探针:关掉 PNG 落盘(--no-write)跑的, 因为 wall_s / 峰值都是围着模型量的,而写 360 张 4 Mpx 的 PNG 比被测的推理本身还久, 对结论没有贡献。所以这里的耗时不含落盘,同一条 5s 16:9 带落盘是 381.8 s、不带是 285.1 s。

场景23 GiB cap耗时显存峰值
5s 16:9 2688×1536通过285.1 s16.52 GiB
5s 21:9 3584×1536通过415.9 s19.52 GiB
15s 16:9 2688×1536通过751.6 s16.52 GiB
15s 21:9 3584×1536通过931.8 s19.52 GiB
15s 16:9,改 3 块通过744.3 s14.15 GiB
15s 21:9,改 3 块通过1012.4 s14.15 GiB
同场景不设 cap(对照) 763.1 / 965.0 s16.52 / 19.52 GiB

带 cap 和不带 cap 的峰值完全一致、耗时相差 1.5–3.5%,说明这个配置没有贴着 24 GB 的上限—— 不是勉强挤进去的。15s 21:9 还剩 3.5 GiB 余量,换 3 块能再降到 14.15 GiB, 代价是慢 8%。
也就是说:要作者的画质、又要 15s 21:9、又要 24 GB,这三件事不冲突, 需要放弃的只是那 2.4× 的速度。

FlashVSR 技术细节

只写 FlashVSR。分两块:怎么把一条形状奇怪的片子喂进去,和怎么调的、每一步验了什么

一、奇葩视频怎么处理 —— 上游的七个硬约束

FlashVSR 对输入的形状要求很硬,而官方脚本处理不合法输入的方式是悄悄改掉你的片子, 不报错。下面每一条都会在评测里变成一个静默的错误结果,所以驱动 flashvsr_runner.py 全部改成显式处理。

约束上游怎么做改成怎么做
画布必须是 128 的倍数 目标尺寸向下取整到 128 再中心裁。1920×1056 静默变成 1920×1024, 凭空丢 32 行真实内容。 改成把输入边缘复制 pad 到合法画布,模型在合法尺寸上跑, 出来再裁回原始取景。一行不丢,构图不动。
帧数必须是 8k+1 不满足就按它自己的方式截断。 尾部重复最后一帧补到 8k+1,产出后再截回原长。
只吐 num_frames−4 帧 静默少 4 帧,而且丢的是尾巴不是头。 先多喂 4 帧再截。这一条是验出来的不是猜的:把输出和真值做 ±8 帧偏移的 PSNR 扫描,峰值必须落在 0。我第一次补在头部,扫描峰值出现在 +4, 直接证明丢的是尾部。
最短时域窗口 33 帧 17 帧和 25 帧都会在它自己的时序窗口里 torch.cat([]) 空列表崩掉。 短片补到 ≥33;低于这个值直接抛明确错误,不让它崩在库里面。
窗口长度取整方向 (我自己引入的 bug)向下取整到 8k+1。 必须向上取整再补帧。向下取整会让最后一窗盖不到片尾, 141 帧只出 135 帧。靠打印逐帧覆盖表(wins / produced / missing)才抓出来。
scale 写死 4 只能 ×4,要 ×2 就得 ×4 再缩回去,多一次重采样。 FlashVSR 是 pre-upsample 架构,scale 本来就是自由参数。 改成 2 就是真 ×2
超长 / 超宽 整条整幅塞进去,装不下就 OOM。 时域窗口(重叠 8 帧,流式融合)+ 空间分块 (只切长边、128 对齐、重叠 256 px、线性 ramp 互补权重融合)。

空间分块的重叠量不是随便定的:必须大于模型的局部注意力跨度local_range=11 个 latent 单位 = 88 个输出像素,所以重叠取 256 px 有足够余量。 接缝做了专项检测——不是看全局指标(1 像素的缝会被均值淹没),而是量 分块版相对整幅版多出来的水平梯度,在两个拼接位置只有 0.9σ 和 1.1σ, 全帧最大偏差落在 x=690(画面自身的边缘)而不是拼接处。没有缝。

二、怎么调的 —— 四个旋钮,只有两个有用
旋钮作用实测
variant full→tiny把 Wan VAE 解码器换成 TCDecoder 速度的大杠杆,2.4×;也是全部感知分损失的来源(见下)
kv_ratio 3.0→1.5压 DiT 的 KV 缓存 唯一能降 DiT 峰值的(28.7→25.8 GiB),画质完全免费;低于 1.5 不再有效
vae_tileVAE 解码分块尺寸 只影响解码阶段峰值——但管线自带的默认值 60×104 正是 4 Mpx 解码 OOM 的元凶
tile_px(空间分块)超过多少像素就切块 对 tiny 是双赢:2 块比整幅又快又省 34%(注意力矩阵变小的收益超过跑两趟的开销)
sparse_ratio / local_range稀疏注意力形状 时间和显存都没有可测量的影响,不用调

21:9 只有靠空间分块才成立:3584×1536 整幅送进去会死在一个 2.15 GiB 的定长分配上, 这个分配跟任何旋钮都无关,只能靠把画面切小来绕开。

感知分是哪个旋钮丢的?——2×2 拆解

调优版同时动了两个旋钮,两两对比说不清是谁的责任,所以把四格全跑了。 UDM10 / YouHQ40 每帧都在 3 Mpx 分块阈值以下,四格都没有分块,不是混淆项。

三、我自己引入过的三个 bug(都已修)
  • float32 全片画布:把整条片子的融合缓冲开成 float32,13.7 GB 的张量被反复拷贝 15 次, 一个 GPU 任务变成 memcpy 瓶颈,跑 28 分钟还没完。改成预分配 uint8 后同一条 625 s 跑完, 输出逐位相同(对齐 PSNR 仍是 21.447)。
  • 多块分支尾部黑帧:缓冲按 m 帧分配,但管线只填 m−4 帧,剩下的尾巴保持零值, 写出来是黑帧。改成按实际产出帧数截断。
  • 窗口向下取整漏片尾:141 帧只写出 135 帧。靠打印 wins / produced / missing 逐帧覆盖表定位。

还有一条结论性错误:我曾经报告「FlashVSR 在 5090 上做不了 4 Mpx 输出」。 挡住它的其实是我自己按 kv 3.0 标定、后来不再适用的 px-budget 门限,不是真 OOM。 改掉门限后连原版配置都能跑 2688×1536×141。

四、24 GB(RTX 4090)能不能跑

显存这半边是实测的:用 set_per_process_memory_fraction 把分配器硬卡在 23.0 GiB(24 GB 减掉约 1 GB 的 CUDA context 等分配器外开销),torch 会在这个上限 精确抛 OOM,和小卡一样。先跑阳性对照——一个已知需 30.5 GiB 的配置在 cap 下确实 OOM, 确认 cap 真的在起作用,后面的行才有意义。

场景结果耗时峰值
对照:15s 16:9 不分块(需 30.5 GiB)OOM ✓ 符合预期
5s 16:9 2688×1536通过158.7 s20.13 GiB
5s 21:9 3584×1536通过235.8 s22.19 GiB
15s 16:9 2688×1536通过409.9 s20.13 GiB
15s 21:9 3584×1536通过558.6 s22.19 GiB
15s 21:9,改 3 块通过558.0 s16.44 GiB
15s 21:9,改 4 块通过602.1 s14.11 GiB

24 GB 卡上该用 3 块:和 2 块一样快(558.0 vs 558.6 s),峰值却低 5.75 GiB。 原因是 2 块贴着上限时分配器一直在刷缓存重试,给它留余量反而不慢。
kernel 侧:Block-Sparse-Attention 的 setup.py 默认 arch 里没有 89, 但源码有一段专门为 sm_86/sm_89 写的分支is_sm8x,hdim128 非因果走 128×32、 只要 48 KB shared memory),正是为了迁就 Ada 更小的 smem;README 也把 Ada 列进 bf16 支持。 用 BLOCK_SPARSE_ATTN_CUDA_ARCHS=80 编即可(sm_80 cubin 按 CUDA 次版本二进制兼容 规则能在 sm_89 上加载)。
没有实机验证过:这里只模拟了显存预算,没有模拟 sm_89 kernel,也没有模拟 Ada 更低的 带宽——真 4090 上每一行的耗时都会更长。

其余方法 · 基础排名

这张表是背景参照,用来给 FlashVSR 的位置定标;本轮的重点是上面那组对比。