— — 768p → 2× RTX 5090 —
鼠标在画面上左右移动即可擦除对比 —— 光标左边是 A,右边是 B。两条视频严格同帧、同步播放。 开 放大镜 看纹理级差异;按 空格 播放/暂停,← → 逐帧。 暂停 / 逐帧 / 拖进度条时,两边会一起锁到同一帧的中点,控制条右侧显示当前帧号。
这一页只有 FlashVSR。部署已定为画质优先 + 解码优化(4090 上线);
TCDecoder 速度版作为速度对照收录(注意:给 TCDecoder 加"解码优化"是无效组合,
vae-stride 只作用于 Wan VAE 解码器,实测两种 stride 输出 md5 逐位相同——
所以速度版就是 tiny + kv1.5 + 分块本身)。外加原版和真值作参照。
📖 想弄懂 FlashVSR 的原理和每一步为什么这么做,看 学习笔记。
📖 想弄懂原理和每一步为什么这么做,看 FlashVSR 是怎么工作的 · 学习笔记 —— 一步蒸馏 / 稀疏注意力 / 流式 KV / 条件解码器的原理, 我们改的每一处及其安全性论证,以及「怎么判断快了但没坏」的方法论。
两个候选用的是同一个解码器(作者的 Wan VAE,不是 tiny 的 TCDecoder),
同样的 kv_ratio 1.5、同样的空间分块。唯一的区别是 VAE 解码的分块步长:
管线默认 stride=30(50% 重叠,每个像素被解码约 4 次),
优化版改成 stride=56(7% 重叠)。
| 配置 | 解码器 | 解码重叠 | 2688×1536×141 | 显存峰值 |
|---|---|---|---|---|
| 原版(参照) | Wan VAE | 50% | 385.2 s† | 29.15 GiB |
| 画质优先 | Wan VAE | 50% | 287.3 s | 16.52 GiB |
| 画质优先 + 解码优化 | Wan VAE | 7% | 147.2 s | 16.52 GiB |
† 原版不做空间分块,所以它的显存是 29.15 GiB。
两个候选都比原版快、比原版省 43% 显存,且用的是同一个解码器。
优化版比画质优先再快 1.95×,显存一点没涨,代价见下面 YouHQ40 n=40 的全量画质对比
——保真度反而略好,CLIP-IQA 掉 0.005。
单击设为 A(左),再次单击同一个设为 B(右)。 也可以直接点另一个方法替换 B。
逐条配对相减(同一批片子,片间难度差异会抵消),2000 次 bootstrap,种子固定 20260821。 差值 = 解码优化(7%) − 画质优先(50%)。CI 跨 0 = 测不出差异。
| 指标 | 解码优化 | 画质优先 | 差值 | 95% CI |
|---|---|---|---|---|
| MUSIQ ↑ | 64.4036 | 64.3972 | +0.0064 | [−0.0132, +0.0257] 跨 0 |
| CLIP-IQA ↑ | 0.5684 | 0.5774 | −0.0090 | [−0.0116, −0.0064] |
| MANIQA ↑ | 0.3549 | 0.3539 | +0.0009 | [+0.0005, +0.0014] |
| NIQE ↓ | 4.0516 | 4.0156 | +0.0360 | [+0.0189, +0.0521] |
| Downsample-LPIPS ↓ | 0.1454 | 0.1455 | −0.0001 | [−0.0001, −0.0000] |
Downsample-LPIPS 是无真值集合上唯一的保真度代理(把输出缩回原尺寸和输入比, 衡量改写了多少原内容)——解码优化版更好,方向和有真值集合上的 PSNR 一致。
| 指标 | 解码优化 | 画质优先 | 差值 | 95% CI |
|---|---|---|---|---|
| PSNR-Y ↑ | 24.6474 | 24.6395 | +0.0079 | [+0.0065, +0.0094] |
| SSIM-Y ↑ | 0.6708 | 0.6703 | +0.0005 | [+0.0004, +0.0006] |
| LPIPS ↓ | 0.2233 | 0.2234 | −0.0000 | [−0.0001, +0.0001] 跨 0 |
| DISTS ↓ | 0.1050 | 0.1053 | −0.0003 | [−0.0004, −0.0002] |
| MUSIQ ↑ | 71.4126 | 71.4389 | −0.0263 | [−0.0397, −0.0141] |
| CLIP-IQA ↑ | 0.6133 | 0.6182 | −0.0049 | [−0.0056, −0.0042] |
| MANIQA ↑ | 0.3980 | 0.3986 | −0.0006 | [−0.0009, −0.0003] |
| NIQE ↓ | 3.1185 | 3.1157 | +0.0028 | [−0.0123, +0.0245] 跨 0 |
| 指标 | 解码优化 | 画质优先 | 差值 | 95% CI |
|---|---|---|---|---|
| PSNR-Y ↑ | 26.6465 | 26.6397 | +0.0068 | [+0.0043, +0.0092] |
| SSIM-Y ↑ | 0.7803 | 0.7797 | +0.0005 | [+0.0004, +0.0007] |
| LPIPS ↓ | 0.1912 | 0.1912 | +0.0001 | [−0.0001, +0.0002] 跨 0 |
| DISTS ↓ | 0.1027 | 0.1031 | −0.0005 | [−0.0006, −0.0003] |
| MUSIQ ↑ | 69.5974 | 69.5626 | +0.0347 | [+0.0223, +0.0475] |
| CLIP-IQA ↑ | 0.5577 | 0.5638 | −0.0061 | [−0.0094, −0.0026] |
| MANIQA ↑ | 0.4011 | 0.4005 | +0.0006 | [+0.0002, +0.0011] |
| NIQE ↓ | 3.8294 | 3.7950 | +0.0344 | [+0.0008, +0.0747] |
| 数据集 | 解码优化 | 画质优先 | 快多少 | 显存峰值 |
|---|---|---|---|---|
| MiniMax-20 4 Mpx | 244.8 s/条 | 391.5 s/条 | 1.60× | 16.52 GiB 相同 |
| YouHQ40 2.0 Mpx | 21.8 s/条 | 35.0 s/条 | 1.61× | 15.50 GiB 相同 |
| UDM10 0.9 Mpx | 10.2 s/条 | 16.1 s/条 | 1.58× | 10.02 GiB 相同 |
差值 = TCDecoder 速度版 − 部署版(画质优先+解码优化),逐条配对 + 2000 次 bootstrap。 这就是「换解码器」的完整代价/收益表——kv、分块、解码步长两边一致,唯一变量是解码器。
| 指标 | UDM10 n=10 | YouHQ40 n=40 | MiniMax n=20 | 谁赢 |
|---|---|---|---|---|
| PSNR-Y ↑ | +0.518 | +0.446 | 无真值 | 速度版 |
| SSIM-Y ↑ | +0.0102 | +0.0099 | — | 速度版 |
| LPIPS ↓ | −0.0106 | −0.0059 | — | 速度版 |
| DISTS ↓ | −0.0056 | −0.0035 | — | 速度版 |
| Downsample-LPIPS ↓ | — | — | −0.0060 | 速度版 |
| MUSIQ ↑ | −2.61 | −2.24 | −1.65 | 部署版 |
| CLIP-IQA ↑ | −0.054 | −0.056 | −0.048 | 部署版 |
| MANIQA ↑ | −0.032 | −0.026 | −0.012 | 部署版 |
| NIQE ↓ | +0.172 | +0.138 | +0.160 | 部署版 |
所有 CI 均不跨 0。速度:MiniMax-20 上速度版 167.1 s/条 vs 部署版 244.8 s/条(快 1.47×), 5s 16:9 纯推理 74.8 s vs 147.2 s(快 1.97×)。
生产路径已从「逐帧写无损 PNG」改成「帧经小队列直接进 ffmpeg/NVENC 出 mp4」。 同机同卡三路对照(每行独立进程独占一卡):
| 场景 | 纯推理 | PNG | NVENC | s/Mpx帧 |
|---|---|---|---|---|
| 5s 16:9 | 124.2 s | 233.1 s | 125.0 s | 0.213 / 0.400 / 0.215 |
| 5s 21:9 | 163.7 s | 304.0 s | 167.7 s | 0.211 / 0.392 / 0.216 |
| 5s 9:16 | 121.6 s | 226.0 s | 123.6 s | 0.209 / 0.388 / 0.212 |
| 15s 16:9 | 282.3 s | 561.7 s | 279.3 s | 0.190 / 0.378 / 0.188 |
| 15s 21:9 | 376.1 s | 740.1 s | 383.0 s | 0.190 / 0.373 / 0.193 |
均值:纯推理 0.203 / PNG 0.386 / NVENC 0.205 s·Mpx⁻¹·帧⁻¹。 NVENC 端到端落在纯推理地板上——输出成本基本为零。编码与推理确实重叠: 帧全部入队后等编码器收尾只要 0.09–0.43 s。PNG 模式并行跑时能看到 GPU 利用率 掉到 0%(同步写盘在推理循环里),NVENC 模式没有这个空转。
-rc vbr -cq N -b:v 0 这个组合下
-cq 根本没生效——cq 19 和 cq 15 产出逐字节相同的 16 Mbps 文件。
换成 -rc constqp -qp 12 直接设量化参数才真正生效。| 设置 | 码率 | PSNR 均值 | 说明 |
|---|---|---|---|
| 420 vbr cq19 / cq15 | 16 Mbps | 26.72 dB | 旧配置,两档输出逐字节相同 |
| 420 constqp 19 | 73 Mbps | 35.28 dB | 改对模式就跳一大截 |
| 420 constqp 12(现配置) | 139 Mbps | 39.08 dB | 通用解码器都吃得下 |
| 444 constqp 12 | 143 Mbps | 40.66 dB | 多 1.6 dB,但需 Rext profile |
| 444 lossless | 554 Mbps | 52.16 dB | 体积 4×,生产不必要 |
选 420 + constqp 12:比 4:4:4 只差 1.6 dB,但不需要 Rext profile (很多播放器和浏览器不支持 Rext)。024 从 26.72 → 39.08 dB。
FLASHVSR_SINK=png 切回无损。
之前站上的 24 GB 结论是用分配器上限模拟出来的。现在在一台
8×RTX 4090 24 GB 真机上从零装了一遍环境重跑,可以直接对照。
每行都是独立进程、--no-write、一行一卡。
--no-write,
一张 PNG 都没落盘,也没跑任何 benchmark 数据集。
本页所有画质数字(PSNR / LPIPS / MUSIQ / 接缝检测)全部来自 5090。
我一直默认「同样的权重、同样的代码,两台机器出的是同一张画面」。 专门去验了:在 4090 上真的写 PNG 跑一条 YouHQ40 clip(1056×1056 × 31 帧), 和 5090 上同一条逐像素比。结论是:这个假设错了,但错得不要紧——理由在下面。
| 比较 | PSNR | md5 一致 | 差异像素 |
|---|---|---|---|
| 4090 vs 5090 | 31.04 dB | 0/31 | 83.0%,最大差 192/255 |
| 4090 同卡跑两次 | ∞(逐位相同) | 31/31 | 0% |
| 4090 换一张卡跑 | ∞(逐位相同) | 31/31 | 0% |
机内逐位一致、跨机 83% 像素不同——所以这不是随机性, 是一个稳定可复现的机器差异。(这组数字我用自己写的脚本独立复算过一遍:31.044 dB, 与 subagent 的 31.028 dB 一致。)
| 把两边同时降采样后再比 | PSNR |
|---|---|
| 原始分辨率 | 31.04 dB |
| 1/2 | 33.32 dB |
| 1/4 | 37.29 dB |
| 1/8 | 42.05 dB |
| 1/16 | 44.84 dB |
降采样后迅速收敛,说明低频结构两边是一致的,分歧只在高频细节。 另外三项排除:时间偏移扫描在 0 处尖峰(没有错帧)、空间平移扫描在 (0,0) 处尖峰(没有错位)、 这个尺寸下驱动只用 1×1 个窗口(根本没有接缝可言),差异是全画面弥散的。 锐度也没塌(Laplacian 方差 282.9 vs 293.9)。
| 各自与同一份真值比 | PSNR-Y | SSIM-Y |
|---|---|---|
| 4090 | 27.7755 | 0.80461 |
| 5090 | 27.6927 | 0.80549 |
| 差值 | +0.0828 dB 95% CI [+0.064, +0.103],29/31 帧一致 |
−0.0009 |
两台机器是两个同样合法的纹理实现,没有谁坏掉。
换成 tiny(完全不同的解码器)重跑,分歧的量级和性质几乎不变
(32.23 dB,1/8 降采样 42.1 dB)——说明源头在共享的 DiT / 稀疏注意力路径,
不在解码器。最可能的机制:BSA 在 4090 上编成 sm_80 cubin 走
is_sm8x 的 48 KB smem 分块,5090 走 sm_120 分块,
不同的分块几何 = 不同的 bf16 累加顺序,差异从注意力开始播种再经解码器放大。
torch 2.6/2.8 与 cuDNN 9.1/9.10 的差异叠在上面。
这三者谁占主导没有隔离出来——那需要在同一台机器上换 kernel 重编,没做。
kv 1.5 vs 3.0 的 PSNR 效应就是 +0.09 dB。
换台机器就能完全淹没这么小的效应。
本站所有 A/B(原版 vs 候选、两候选之间、分块与否、解码重叠)全部在 5090 单机完成,
不受影响;但 ±0.2 dB 以内的结论,换机器就不可信了。| 配置 | 4090 | 5090 | 慢多少 | 显存 4090 / 5090 |
|---|---|---|---|---|
| tiny,141 帧 @2688×1536 | 99.0 s | 74.8 s | 1.32× | 20.13 / 20.13 GiB |
| 画质优先(50% 重叠),141 帧 | 413.7 s | 287.3 s | 1.44× | 16.52 / 16.52 GiB |
| 解码优化(7% 重叠),141 帧 | 203.7 s | 147.2 s | 1.38× | 16.52 / 16.52 GiB |
三行的显存峰值和 5090 逐位一致——同一个分配器、同一个模型,本来就该如此, 这也是「移植没走样」最强的证据。速度慢 1.3–1.4×,方向合理。
| 配置 / 场景 | 耗时 | 显存峰值 | 余量 |
|---|---|---|---|
| tiny,16:9 2688×1536 | 261.1 s | 20.13 GiB | 3.9 GiB |
| tiny,21:9 3584×1536 | 386.6 s | 23.04 GiB | 不到 1 GiB ⚠ |
| tiny,21:9 + 更细分块 | 367.6 s | 16.44 GiB | 7.6 GiB |
| 解码优化,16:9 2688×1536 | 541.4 s | 16.52 GiB | 7.5 GiB |
| 解码优化,21:9 3584×1536 | 669.2 s | 19.52 GiB | 4.5 GiB |
默认阈值(tile-px 3.0e6 / px-budget 140e6)是按 32 GB 标定的,
4090 上 21:9 峰值 19.52 GiB、余量仅 4.5 GiB。部署预设改为
--tile-px 2.5e6 --px-budget 110e6(其余与部署配置相同),
在真 4090 上五行验证全过:
| 场景 | 耗时 | 峰值 | 分块 |
|---|---|---|---|
| 5s 16:9 2688×1536 | 237.4 s | 16.30 GiB | 2×5 |
| 15s 16:9 2688×1536 | 536.6 s | 16.30 GiB | 2×11 |
| 5s 21:9 3584×1536 | 306.0 s | 15.33 GiB | 3×4 |
| 15s 21:9 3584×1536 | 729.0 s | 15.33 GiB | 3×9 |
| 5s 9:16 竖版 1536×2688 | 239.9 s | 16.30 GiB | 2×5 |
| 对照:15s 21:9 旧默认(同卡同日复测) | 688.9 s | 19.52 GiB | 2×11 |
全画幅族最大峰值 16.30 GiB,余量 ≥7.69 GiB(此前是 4.5)。
账目干净:21:9 峰值 −4.19 GiB,代价 +5.8% 耗时;16:9 分块不变,基本白送。
有个反直觉的点如实写明:峰值上界由 16:9 决定而不是 21:9——
21:9 被切成更小的 1408×1536 块(2.16 Mpx),16:9 的 1536×1536 块(2.36 Mpx)
低于 2.5 Mpx 阈值不再细分,所以它才是最大的块。
竖版 1536×2688 沿长轴对称分块,峰值与横版逐位相同。
部署目录:4090:~/vsr_deploy/fv_deploy_4090/run_batch.sh,
flags 写死、头部注释含标定依据与验证数据,已用真实 job 冒烟(写 PNG,
首帧 md5 与此前跨机校验产物逐位一致——4090 机内确定性再次确认)。
tiny 默认阈值在 4090 上 21:9 峰值 23.04 GiB,余量不到 1 GiB,生产不可用。
预设改为 --variant tiny --tile-px 1.8e6(vae-stride 对 TCDecoder 是死参数,不加),
真 4090 六行验证全过:
| 场景 | 耗时 | 峰值 | 分块 |
|---|---|---|---|
| 5s / 15s 16:9 2688×1536 | 121.9 / 282.8 s | 16.44 GiB | 3×3 / 3×6 |
| 5s / 15s 21:9 3584×1536 | 164.4 / 374.8 s | 16.44 GiB | 4×3 / 4×6 |
| 5s / 15s 9:16 1536×2688 | 122.6 / 285.4 s | 16.44 GiB | 3×3 / 3×6 |
| 对照:15s 16:9 默认阈值(同机复测) | 263.9 s | 20.13 GiB | 2×8 |
六种画幅×时长峰值完全一致 16.44 GiB(同一个块内存形态封顶),余量 7.55 GiB。
速度上方向分裂:21:9 细分块更快(374.8 vs 386.6 s),16:9 慢 7.2%
(282.8 vs 263.9 s)——"细分块对 tiny 双赢"只在 21:9 成立,16:9 是拿 7.2% 速度换 3.7 GiB 余量。
部署目录:4090:~/vsr_deploy/fv_deploy_4090_tiny/run_batch.sh。
能,而且一行源码都没改。用 BLOCK_SPARSE_ATTN_CUDA_ARCHS=80 编译,
cuobjdump --list-elf 确认产物只含 sm_80 cubin(没有 sm_89、也没有 PTX),
在 4090 上照样加载执行——靠的是 CUDA 次版本二进制兼容。
而且是数值验证过的,不是「不报错就算过」:对稠密掩码与
scaled_dot_product_attention 比对,bf16 下最大绝对误差 1.16e-03;
换成真正稀疏的下三角块掩码会给出不同且非退化的结果(证明稀疏路径真的在走)。
编译 ~7 分钟(ninja + MAX_JOBS=32,25 个目标文件),无需任何补丁。
--tile-px 1.8e6 那行写成「3 块」,
实际驱动算出来是 4 块(k=3 时每块 2.16 Mpx 超过 1.8 阈值)。
这是设备无关的纯算术,5090 上分法相同——是标签写错了,不是 4090 的差异。这里有两种完全不同的分块,之前混着说是我的表述问题:
| 分块 | 是谁做的 | 为什么要 |
|---|---|---|
| 空间分块 tile-px |
我们加的,FlashVSR 原本没有 | 整幅 3584×1536 送进 DiT 会死在一个 2.15 GiB 的定长分配上, 这个分配跟任何旋钮都无关。不切块,21:9 根本跑不了。 |
| VAE 解码分块 tile_size/stride |
FlashVSR 自带的,默认开启 | 解码器一次吃不下整帧。它默认 50% 重叠是为了盖住接缝, 但重叠量给多了(见下一节)。 |
专门跑了一组 --tile-px 1e12(永不分块)的对照,其余配置逐字相同。
分块只在每帧 > 3.0 Mpx 时触发,所以 20 条片子天然分成两组:
① 阴性对照(4 条 1:1,2.36 Mpx,两边都没分块): 逐字节 md5 比对 4/4 完全相同。这一步是必须的——如果这里就不一样, 说明管线本身不确定,下面所有数字都是噪声。
| 指标(16 条 >3 Mpx 的片子) | 分块 | 不分块 | 差值 | 95% CI |
|---|---|---|---|---|
| MUSIQ ↑ | 60.9680 | 60.9355 | +0.0325 | [−0.2494, +0.2895] 跨 0 |
| CLIP-IQA ↑ | 0.5110 | 0.5110 | +0.0000 | [−0.0045, +0.0041] 跨 0 |
| MANIQA ↑ | 0.3426 | 0.3431 | −0.0005 | [−0.0019, +0.0010] 跨 0 |
| NIQE ↓ | 4.3191 | 4.3649 | −0.0458 | [−0.0758, −0.0086] |
| Downsample-LPIPS ↓ | 0.1471 | 0.1483 | −0.0012 | [−0.0028, +0.0004] 跨 0 |
切块只切长边、按 128 对齐、重叠 256 输出像素,融合用线性 ramp 互补权重。
【更正】我之前写过「重叠远大于模型 88 px 的感受野」——那个数字算错了。
正确推算:输出像素 →(VAE ÷8)→ latent →(DiT patch_size=[1,2,2] ÷2)→ token,
1 token = 16 px,1 注意力块 = 8×8 token = 128 px,local_range=11 块
→ ±640 px。重叠其实小于感受野,接缝附近确实看到截断的上下文——
但上面的全量 A/B 和接缝检测都测不出代价,原因见
学习页 5.3 节。
而且对 tiny 来说分块是双赢:2 块比整幅又快又省 34% 显存
(155.5 s / 20.13 GiB vs 163.4 s / 30.49 GiB)——注意力矩阵随边长平方增长,
切小之后省下的算力超过了跑两趟的开销。
在动任何优化之前先分块计时(FVPROF=1,每个计时器都
cuda.synchronize(),量的是真实 GPU 时间不是异步下发延迟)。
2688×1536 × 141 帧,不含落盘:
| 配置 | 总耗时 | 解码器 | DiT + 前处理 |
|---|---|---|---|
| tiny(TCDecoder) | 74.8 s | 12.3 s 16% | 62.4 s 83% |
| full(Wan VAE 解码器) | 283.4 s | 220.1 s 78% | 63.3 s 22% |
DiT 两边都是 ~63 s,两个配置的全部差距都在解码器上。 所以画质优先配置该优化的地方只有一个:那 220 秒。
FlashVSR 的 pipeline 解码时用 tile_size=(60,104), tile_stride=(30,52),
也就是 50% 重叠——每个 latent 像素大约被解码 4 次。
重叠是用来盖住 VAE 分块接缝的,问题是需要这么多吗。
| tile / stride | 重叠 | 总耗时 | 解码器 | 显存峰值 |
|---|---|---|---|---|
| 60 / 30 (管线默认) | 50% | 287.3 s | 221.7 s | 16.52 GiB |
| 60 / 44 | 27% | 177.1 s | 113.3 s | 17.03 GiB |
| 60 / 56 | 7% | 147.2 s | 81.3 s | 16.52 GiB |
| 96 / 48 | 50% | 228.1 s | 161.9 s | 19.87 GiB |
| 96 / 88 | 8% | 154.3 s | 86.0 s | 19.87 GiB |
| 128 / 120 | 6% | 139.8 s | — | 24.50 GiB |
| 160 / 152 | 5% | 179.3 s | — | 30.41 GiB |
| 256 / 248 | 3% | OOM | — | >32 GiB |
解码 221.7 s → 81.3 s,快 2.7×,显存一点没涨。 分块开大(96/128/160)反而更慢且吃显存——省时间的是减少重叠,不是加大分块。
| 指标 | 7% 重叠 | 50% 重叠 | 差值 | 95% CI |
|---|---|---|---|---|
| PSNR-Y ↑ | 24.6474 | 24.6395 | +0.0079 | [+0.0065, +0.0094] |
| SSIM-Y ↑ | 0.6708 | 0.6703 | +0.0005 | [+0.0004, +0.0006] |
| LPIPS ↓ | 0.2233 | 0.2234 | −0.0000 | [−0.0001, +0.0001] |
| DISTS ↓ | 0.1050 | 0.1053 | −0.0003 | [−0.0004, −0.0002] |
| MUSIQ ↑ | 71.4126 | 71.4389 | −0.0263 | [−0.0397, −0.0141] |
| CLIP-IQA ↑ | 0.6133 | 0.6182 | −0.0049 | [−0.0056, −0.0042] |
| NIQE ↓ | 3.1185 | 3.1157 | +0.0028 | [−0.0123, +0.0245] |
保真度略微变好(重叠融合本身就是一次轻微模糊),CLIP-IQA 掉 0.005—— 对比一下换解码器那次是掉 0.061,这里小一个数量级。 接缝专项检测:4 条片子 × 横竖两个方向 × 2 个接缝位置,相对 50% 重叠版的额外梯度 最大 3.7σ,全部低于 4σ 判定线;最大偏差点落在画面内容边缘而不是接缝上。无可见接缝。
| 配置 | eager | compile 解码器 | compile DiT | 两个都 compile |
|---|---|---|---|---|
| full + 7% 重叠 | 145.8 s | 145.7 s | 147.0 s | — |
| tiny | 78.6 s | 75.9 s | 78.6 s | 78.2 s |
编译确实生效了(日志里有 [compile] decoder compiled /
[compile] dit compiled,不是静默失败),但收益在测量噪声以内(≤3%)。原因:
dynamic=True 之下拿不到静态 shape 特化的好处。
结论:这个模型上 torch.compile 不值得开。同样是想省时间,
改一个 tile_stride 参数拿到了 2.7×,而 compile 拿到 0%——
这正是「先分块计时再决定优化目标」的价值。
整节只写 FlashVSR,不涉及任何其他模型。下文的「官方实现」一律指
FlashVSR/examples/WanVSR/infer_flashvsr_v1.1_*.py 和它自己的 pipeline,
也就是 FlashVSR 作者发布的代码;「我们的驱动」指
sr_eval/flashvsr_runner.py。分两块:怎么把一条形状奇怪的片子喂进去,
和怎么调的、每一步验了什么。
FlashVSR 对输入的形状要求很硬,而它自己的官方脚本处理不合法输入的方式是悄悄改掉你的片子,
不报错。下面每一条都会在评测里变成一个静默的错误结果,所以驱动
flashvsr_runner.py 全部改成显式处理。
| 约束 | FlashVSR 官方实现怎么做 | 我们的驱动改成怎么做 |
|---|---|---|
| 画布必须是 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 | FlashVSR 是一个 ×4 模型,不是 ×2。官方 6 个推理脚本全部是
seed, scale, dtype, device = 0, 4.0, ...;
prepare_input_tensor 和 compute_scaled_and_target_dims
的默认参数都是 scale=4;连 LQ 投影层的类名都叫
Causal_LQ4x_Proj。论文里的场景也是 540p→4K。 |
它是 pre-upsample 架构:LQ 先被 bicubic 放大到目标尺寸, DiT 才在目标分辨率上工作。所以 scale 是前处理的参数,没有烧进网络权重, 传 2 进去就是真 ×2,而不是 ×4 之后再缩回来(那会多一次重采样、白烧 4 倍算力)。 本项目全部按 scale=2 跑。 |
| 超长 / 超宽 | 整条整幅塞进去,装不下就 OOM。 | 时域窗口(重叠 8 帧,流式融合)+ 空间分块 (只切长边、128 对齐、重叠 256 px、线性 ramp 互补权重融合)。 |
空间分块只切长边、按 128 对齐、重叠 256 输出像素,线性 ramp 融合。 (更正:重叠量其实小于模型 ±640 px 的局部注意力半跨度, 我早先算成 88 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_tile | VAE 解码分块尺寸 | 只影响解码阶段峰值——但管线自带的默认值 60×104 正是 4 Mpx 解码 OOM 的元凶 |
tile_px(空间分块) | 超过多少像素就切块 | 对 tiny 是双赢:2 块比整幅又快又省 34%(注意力矩阵变小的收益超过跑两趟的开销) |
sparse_ratio / local_range | 稀疏注意力形状 | 时间和显存都没有可测量的影响,不用调 |
21:9 只有靠空间分块才成立:3584×1536 整幅送进去会死在一个 2.15 GiB 的定长分配上, 这个分配跟任何旋钮都无关,只能靠把画面切小来绕开。
调优版同时动了两个旋钮,两两对比说不清是谁的责任,所以把四格全跑了。 UDM10 / YouHQ40 每帧都在 3 Mpx 分块阈值以下,四格都没有分块,不是混淆项。
wins / produced / missing 逐帧覆盖表定位。
还有一条结论性错误:我曾经报告「FlashVSR 在 5090 上做不了 4 Mpx 输出」。
挡住它的其实是我自己按 kv 3.0 标定、后来不再适用的 px-budget 门限,不是真 OOM。
改掉门限后连原版配置都能跑 2688×1536×141。
显存这半边是实测的:用 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 s | 20.13 GiB |
| 5s 21:9 3584×1536 | 通过 | 235.8 s | 22.19 GiB |
| 15s 16:9 2688×1536 | 通过 | 409.9 s | 20.13 GiB |
| 15s 21:9 3584×1536 | 通过 | 558.6 s | 22.19 GiB |
| 15s 21:9,改 3 块 | 通过 | 558.0 s | 16.44 GiB |
| 15s 21:9,改 4 块 | 通过 | 602.1 s | 14.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 上每一行的耗时都会更长。
—