稳态 14-LF 单窗口 1234.787 → 909.258 ms,省约 325.53 ms。
总体结论 / Primary Conclusions
本轮工作有两条互相独立的证据链:一条是性能——隔离 decoder 预筛加冻结合同下的 三对全模型 A/B;一条是差异——在同一份逐 bit 相同的真实 latent 上重放两条 VAE 路径, 对 585,861,120 个 RGB 样本做逐像素、逐帧、逐接缝的定量比较。两条链得出的结论方向一致, 但都不构成画质验收,原因写在最后一节。
| Root cause | 不是 kernel 慢,是被要求算了 1.569 倍的量 | Week 9 profiling:3,360 次 cuDNN 卷积跑在 177 TFLOP/s,为本卡实测 BF16 峰值的 79%,无 Winograd 慢路径、无 launch-bound。浪费来自 8-tile 分解的重叠区重复解码。 |
| Primary change | TilingConfig 塌缩为单块 | 唯一变量是 tile 尺寸;解码窗口(8 LF 历史 + 6 LF 当前 = 14 LF)、权重、dtype、eager 执行、后处理、取帧、编码参数与音轨全部不变。 |
| Isolated speed | 1.356× (P2),1.876× (P2+P3) | portrait 几何实测:baseline 1250.504 ms → single-tile 922.511 ms → +torch.compile 666.737 ms。 |
| Full-model P2 | generated FPS 配对中位 +2.382794% | 三对交替 A/B,3/3 为正(+2.291877 / +2.382794 / +2.422715%);门槛 ≥ 1.5%。steady VAE speedup 1.344890×。 |
| Full-model P3 | 再 +1.837371%,但 warm_throughput_only = true | 三对全为正(+1.734520 / +1.837371 / +1.955173%);但 fresh-process wall 中位 +10.802722%,严格超过预注册的 10% 冷启动阈值。 |
| Real-video A/B | 1.3402× 解码,代价 +2.668 GiB 峰值显存 | 11 个真实窗口 replay:11899.917 → 8879.370 ms;peak allocated 2.352 → 5.020 GiB。 |
| Where the difference lives | 占画面 20% 的接缝带装下了 75.90% 的最大误差 | spatial overlap 带内/带外 MAE = 3.9749×,MSE 比 8.7401×;峰值行 y=478、峰值列 x=253,都落在预测带内。 |
| A falsified worry | VAE 窗口拼接点不是差异热点 | chunk join ±2 帧的带内/带外 MAE 比为 0.7963——低于 1。差异集中在 tile 接缝,不在 11 个窗口的衔接处。 |
| Quality status | quality_accepted = false、production_accepted = false | 无 ground truth,PSNR/SSIM 只度量两路差多少,不能判定谁更准。本机无 LPIPS/VMAF/MS-SSIM/XPSNR 可用实现,一律记 N/A,未临时下载模型冒充感知指标。 |
三对配对中位;VAE 的 1.34× 不等于端到端 1.34×,兑现的是尾部排空。
该带只占画面 20%;差异是结构化的,不是均匀噪声。
2.352 → 5.020 GiB;全模型阶段 GPU0 仍留 3.755615 GiB 余量。
口径纪律 / Throughput definitions
本页出现三种“FPS”,永不混用。25 FPS 是 MP4 播放帧率。 generated FPS = 489 帧 ÷(sampling + 流水 video decode)秒数,是全模型 A/B 的主裁决量。 VAE-only 输出吞吐(本页 41.093 → 55.071 FPS) = 489 帧 ÷ 单独 VAE 的 CUDA 总时间,只描述 decoder 自身,既不是端到端生成速度, 也不能与 generated FPS 相比较。隔离 decoder 时间是投影,不是 wall。
动机:VAE 今天不是瓶颈,但它锁住了 DiT
这是本轮最容易被误读的一点,先钉死:稳态下把 VAE 加快 100 ms,端到端 wall 一毫秒都不会少。
Portrait 基线 chunk_timing.csv 实测,chunk 周期由 dit_cuda 决定,
VAE 在它下面还有 77–83 ms 余量,且 vae_queue_wait = 0.0——VAE 从不等活。
| chunk | 周期 ms | dit_cuda ms | vae_cuda ms | 余量 ms |
|---|---|---|---|---|
| 3 | 1287.5 | 1317.8 | 1234.7 | 83.1 |
| 5 | 1327.2 | 1318.8 | 1241.3 | 77.5 |
| 7 | 1327.5 | 1319.3 | 1242.4 | 76.9 |
| 10 | 1328.3 | 1322.6 | 1239.3 | 83.3 |
价值一:尾部排空,今天就能兑现
最后一个 chunk 的画面必须等 DiT 先算出来,VAE 才能解码,这段无法与任何东西重叠。 因此 generated FPS 收益 ≈ VAE 加速比例 × 8.38%。P2 隔离预估 ~+2.24%,全模型实测 +2.382794%——方向与量级都对上了。
价值二:为 DiT 解绑,这才是战略意义
VAE 是 DiT 的 1240 / 1320 = 93.9%。DiT 只要再快 6.1%,VAE 就会变成新瓶颈,
届时所有 DiT 工作的收益立刻归零。
- 改动前 DiT 可优化空间约 6.1%
- P2+P3 后稳态 VAE约 686 ms
- 相对 DiT chunk(~1320 ms)约 52%
- 改动后 DiT 可优化空间约 50%
“可再提多少 DiT”仍需后续配对实验证实,不能由隔离比例直接承诺。
一处自我更正
本轮早期我曾断言“VAE 已经在反压 DiT”。这是错的。独立核对 chunk 周期与
dit_cuda 后可见二者几乎相等,而 vae_queue_wait = 0.0,
系统明确是 DiT-bound。backpressure_ms ≈ 39–45 是主机线程等队列位,
GPU1 异步照跑,不构成 VAE 反压的证据。该结论已在计划书与本页统一更正。
分块是怎么切的,为什么在这个几何下纯属浪费
分块本来是为大分辨率准备的显存兜底:把画面切开分批解码,峰值显存就压得住。
问题是这条策略在 Catnip 的 portrait 几何上被无条件套用了——480×832 的 latent 只有 26×15,
小到根本不需要切,但切分逻辑仍然按 tile_size = max(3, round((512 // 32) × axis_len / long_side))
照切不误。
| 窗口 | latent frames | emitted frames | 生产 tile 数 | single-tile |
|---|---|---|---|---|
| chunk 0(无历史) | 4 | 25 | 4 | 1 |
| chunk 1(4 LF 历史) | 8 | 32 | 4 | 1 |
| chunk 2–10(8 LF 历史,稳态) | 14 | 48 | 8 | 1 |
解码窗口本身没有变
稳态每次解码的仍然是 14 LF = 8 LF 历史 + 6 LF 当前,只吐出对应 6 LF 的 48 帧。 P2 改的只是这 14 LF 是一次算完还是切成 8 块算。 “每次重解 8 LF 历史”这个固有冗余依然存在——14 LF 进、6 LF 出, 8/14 ≈ 57.14% 的解码量是上一轮已经算过的, 我们消掉的是分块带来的 1.569× 额外膨胀,没有消掉历史重解。
为什么历史重解这条路走不通:causal_decoder: false
“缓存历史帧的中间激活避免重算”是流式 VAE 的标准手法(Wan 2.1/2.2、CogVideoX、Open-Sora 都有),
但在本模型上结构性不可行:checkpoint 配置 causal_decoder: false,
42 个时间卷积全部以对称模式运行,沿时间轴左右各 replicate-pad 1 帧——每个卷积都消费未来帧。
缓存时未来帧尚不存在,任何缓存都不可能精确。代码库中亦不存在任何 cache/feat_cache/temporal_cache
实现(grep 零命中),不是“现成功能没启用”。若要走这条路,前提是重新训练一个 causal decoder,属于另立项目。
顺带记录两个副产品事实:该 decoder 单侧时间感受野为左 14 LF / 右 13 LF, 而基线 history 只有 8 LF——基线本身已经在截断上下文;且每 chunk emit 的 48 帧中有 16 帧完全来自第二个时间 tile, 其左上下文是当前 chunk 首帧的 replicate padding,8 LF history 对这三分之一的输出没有任何贡献。
四个阶段:一次只改一件事
纪律:单变量、三对交替 A/B、基线目录全程只读、冻结合同、失败也留证据。 隔离 decoder 基准只用于预筛,最终性能结论一律来自冻结 480×832、20 s 请求上的三对全模型 A/B。
在 portrait 几何上复现生产解码时间
用生产 ltx_trainer.model_loader.load_model 加载真实 decoder,在 portrait latent 上计时四种配置。隔离基准与生产 vae_cuda_ms 吻合,说明 harness 可信,可以用来做几十秒级的廉价预筛。
建立隔离 decoder 计时台,四配置:生产 8-tile / channels_last / single-tile / +compile。
生产 8-tile 基线 1250.504 ms,与全模型 chunk_timing 的 1234–1242 ms 同量级。
接受为预筛工具;不作为性能裁决。
被 32 秒的隔离基准正确否掉
预注册门槛是「隔离改善 ≥ 3%」,实测 −0.55%(更慢)。三项证据同时指向拒绝:decoder 权重本身已经是 channels_last_3d(生产那个被 contextlib.suppress 包裹的转换其实是成功的);输出逐位相同;时间差为负。Week 9 在 landscape 上报的 0.940× 在 portrait 上不复现。
把输入张量转成 channels_last_3d 后重测隔离解码。
1250.504 → 1257.359 ms,即 0.995×;bitwise identical 说明它根本没改变计算。
REJECT。这是正确的早停——32 秒的隔离基准否掉了约 40 分钟的全模型实验。
关掉分块:隔离 1.356×,全模型 +2.382794%
把 TilingConfig 换成 tile 尺寸大于整个 latent 的配置,_prepare_tiles 会塌缩成 1 个 tile。隔离 1250.504 → 922.511 ms;冻结合同下三对全模型 A/B 的 generated FPS 配对中位增益 +2.382794%,3/3 为正。
单变量替换 TilingConfig;解码窗口、权重、dtype、后处理全部不变。
control / candidate generated FPS 均值 32.980869 / 33.761113;steady VAE speedup 1.344890×;formal 图 6/6 lane delta {{}}、无新 unique graph。
性能门禁 PASS(门槛 ≥ 1.5%);quality_accepted 仍为 false。
编译 forward 本身:再 +1.837371%,但冷启动更慢
torch.compile(decoder) 无效——tiled_decode 是生成器,内部直接调 self.forward(...)(video_vae.py:965),绕过 OptimizedModule.__call__。必须改为 decoder.forward = torch.compile(decoder.forward)。
编译 decoder.forward 而非 module;三对全模型 A/B 相对 P2。
三对全为正(+1.734520 / +1.837371 / +1.955173%);compile engagement 经 3/3 首次调用各 1 个 unique graph 确认;但 fresh-process wall 中位 +10.802722%,compile-warmup wall 中位 +19.560397%。
稳态性能 PASS,冷启动 FAIL。严格分类为 warm_throughput_only = true:改善预热后吞吐,不能声称 fresh-process 延迟改善。
两个实现陷阱,都是实测撞出来的
①tiling_config=None 会崩溃,不能这样关分块:
TypeError: unsupported operand type(s) for -: 'NoneType' and 'int'(video_vae.py:847,
frames=curr_temporal_slice.stop - curr_temporal_slice.start)。正确做法是传大于整个 latent 的 tile 尺寸。
②“编译没生效”有明确指纹。第一次测量得到 0.996×、输出逐位相同、
warmup 反而比 eager 更短(4.56 s 对 4.75 s)——这三个信号同时出现即编译未接管。
改对之后 warmup 4.75 s → 27.06 s,dynamo frames_ok = 1,加速 1.370×。
凡是声称启用了 torch.compile 却看不到 warmup 变长的,都该先怀疑没生效。
对比协议:为什么这组差异数字可信
最容易犯的错是跑两次完整推理,然后比两个 MP4。那样比出来的差异里混着 DiT 的非确定性、 调度抖动和两次独立的 H.264 编码,跟 VAE 没关系。本实验换了个做法: DiT 只跑一次,把它交给 VAE 的张量原样截下来,再拿同一份张量喂给两条 VAE 路径。
六道保证
- 正式 Catnip DiT 只运行一次,固定 prompt、seed
12345、480×832、25 FPS、目标 20 s。 - 在正式 pipeline 内部直接捕获
video_vae_decoder.tiled_decode实际收到的 BF16 5D tensor。 - 共 11 个窗口,LF =
[4, 8, 14×9],总计 13,777,920 bytes;每个窗口记录 raw tensor SHA256 与文件 SHA256。 - 两路 replay 逐窗重新校验 raw BF16 SHA — 全部
identical = true,故两边 VAE 输入逐 bit 相同。 - 两路共用同一 BF16 权重、eager 执行、历史窗口、尾帧截取、RGB clamp/uint8 规则、编码参数与音轨。唯一变量是
TilingConfig。 - 主指标在 H.264 编码之前的 raw RGB 上计算;MP4 只用于观看。
实验事实
- GPUNVIDIA GeForce RTX 5090
- torch2.9.1+cu128
- 几何480×832 · 25 FPS
- 帧数 / 时长489 / 19.56 s
- 比较样本数585,861,120
- 窗口 replay 哈希一致11 / 11
- 五条视频音轨 PCM SHA全部相同
- lossless master 回读校验通过
capture run 的 FPS 作废
为了保存精确 latent,capture 阶段刻意插入了同步 GPU→CPU 拷贝, 因此那一轮打印的 end-to-end FPS 不具备任何性能含义,不得引用。 干净的 VAE-only 计时来自捕获完成后的独立 replay:只加载 VAE,每路先 warmup 一次, 再对 11 个窗口用 CUDA event 计时(含 eager decode、拼接、尾帧截取、clamp 与 uint8 后处理, 不含 CPU 拷贝与编码)。
差异有多大、长在哪里
主域是编码前的 clamp((decoder_output + 1) / 2, 0, 1) float RGB,
共 585,861,120 个 RGB channel 样本。再强调一次:这里没有 ground truth。
下列指标度量“两路差多少、差在哪”,不能用来宣称哪一路更准。
整体差异(float RGB,满量程 1.0)
- MAE0.00363528(0.36353%)
- RMSE0.00786022
- pooled PSNR42.0913 dB
- SSIM mean / p05 / min0.990818 / 0.983091 / 0.976424
- 绝对误差 p50 / p950.00195315 / 0.01513695
- 绝对误差 p99 / p99.90.03405814 / 0.07226673
- 最大绝对误差0.37402344
生产 uint8 RGB 域
- 平均差异0.92773 灰度级
- RMSE2.03881 灰度级
- 最大差异96 灰度级
- 不完全相同的 channel46.2315%
- uint8 误差 p500 级
- > 1/255 的 channel21.5367%
- > 4/2554.4963%
- > 8/2551.1847%
46% 的 channel 不完全相同,但 uint8 中位差异是 0 级——大量 float 差异小于一个量化级,取整后消失。
build_error_map.py),全局均值 0.9277312668 灰度级,与 metrics.json 记录的 0.9277312668 吻合到 15 位有效数字,故与主证据同源可信。青色带为事先根据 tile 切分算出的重叠区(y∈[448,512)、x∈[224,288));实测峰值行 y=478、峰值列 x=253 都落在带内。注意十字之外亦有与内容相关的弥散差异(如 y≈350 一带),差异并非全部来自接缝。一个被证伪的担心:窗口拼接点不是热点
直觉上最该担心的是 11 个 VAE 窗口的衔接处会不会跳变。实测否定了这个担心: chunk join ±2 帧的带内 MAE 为 0.00295640,带外为 0.00371260,比值 0.7963——低于 1,即衔接处的 A/B 差异反而小于窗口内部。 残差时序变化也是同一结论:chunk 边界 0.00337615 对内部 0.00424852。 真正被富集的是 tile 接缝(temporal seam 0.00509782 对 non-seam 0.00329437)。
合成 latent 探针:与真实视频结果的关系
在真实视频对比之前,我们先用合成低频平滑 latent做过一次数值探针,用于把 「分块误差」和「上下文截断误差」分开:参照 = 不分块解 20 LF,候选 A/B 解同一份 14 LF, 因此截断误差是共模的,相减即抵消。结果:8-tile 相对参照 1.9322%、single-tile 0.7536%(2.56×,PSNR 差 4.26 dB); 轴向归因显示空间分块贡献约为时间分块的 3 倍(+0.958 pp 对 +0.314 pp)。
两个补测也都做了:参照收敛性——R(14) 对 R(20) 的 MAE 为 0.0027263670, R(14) 对 R(26) 为 0.0027263726,八位小数内相同,说明 20 LF 参照已收敛; 接缝定位——H 带内/带外 3.58×、W 带 2.88×,峰值行 476、峰值列 255 均落在预测带内。
但必须注意口径差异。探针的绝对百分比来自合成 latent, 不是关于真实视频的质量论断;本页真实视频给出的接缝比是 3.9749×, 与探针的 3.58×/2.88× 同方向、量级相近,但两者不可互相替代。 另外此前引用 Week 9 landscape 的「约 6×」在 portrait 上不成立,实测为 2.56×, 对外只应说约 2.5 倍并注明是 portrait 实测。
视频产物
全部为 489 帧、25 FPS;并排视频 960×832,其余 480×832。 指标不能替代观看:完整时序的主观闪烁必须直接看视频,contact sheet 不能代替盲评。 (视频体积较大,未内联进页面,而是与本页一同部署;点击播放后按需加载。)
| 产物 | 用途 | 大小 | SHA-256(前 16) |
|---|---|---|---|
side_by_side.mp4 | 同步并排 A/B | 27.2 MB | cce16a04f289688a |
difference_heatmap_x8.mp4 | 差异放大 | 65.1 MB | 8db55fcea1e304e7 |
production_tiled.mp4 | 观看副本 | 14.2 MB | fd164ef5f77758c0 |
single_tile.mp4 | 观看副本 | 14.6 MB | da88d00251ac148f |
production_tiled_rgb_lossless.mkv | 精确 master(FFV1) | 211.6 MB | cf661a36d893e9c2 |
single_tile_rgb_lossless.mkv | 精确 master(FFV1) | 212.8 MB | f1230da3a6985e7f |
Master 可回读复算(本页图 4 即由此重建)
两份 lossless master 解码后的完整 RGB SHA256 与编码前 raw 数组相同。
本页图 4 的逐像素误差图正是用这条路径独立重算的,全局均值
0.9277312667548242 与 metrics.json 的
0.9277312667548241 一致到最后一位浮点。
复算时有一处坑值得记录:-fps_mode passthrough 是
输出选项。放在 -i 之前会静默产出零帧;完全省略则 ffmpeg 会把 19.581 s 的 master
按 25 FPS 做 CFR 重同步,导致帧对齐错位、MAE 偏出约 0.09%。必须写成
ffmpeg -i M.mkv -map 0:v:0 -fps_mode passthrough -f rawvideo -pix_fmt rgb24 pipe:1。
决策账本
保留、拒绝、受限通过与未执行,全部登记。negative result 与 early stop 是正确行为,不是遗漏。
| 阶段 | 尝试 | 结果 / 最终原因 | 状态 |
|---|---|---|---|
| P0 | 隔离 decoder 计时台 | 生产 8-tile 基线 1250.504 ms,与全模型 vae_cuda_ms 吻合;作为廉价预筛工具。 | 保留 |
| P1 | channels_last_3d 输入 | 隔离 1257.359 ms(0.995×,更慢),输出逐位相同;权重本已是 channels_last_3d。门槛为 ≥3%。 | 拒绝 |
| P2 | TilingConfig 塌缩为单块 | 隔离 1.356×;全模型三对 generated FPS 配对中位 +2.382794%,3/3 为正;GPU0 余量 3.755615 GiB。 | Promoted |
| P3 | torch.compile(decoder.forward) | 隔离累计 1.876×;全模型三对配对中位 +1.837371%,3/3 为正;compile engagement 已确认。 | Promoted |
| P3 | 冷启动延迟 | fresh-process wall 中位 +10.802722%,超过预注册 10% 阈值;compile-warmup wall 中位 +19.560397%。 | warm-only |
| — | tiling_config=None 关分块 | 直接崩溃:TypeError at video_vae.py:847。改用超大 tile 尺寸使 _prepare_tiles 塌缩。 | 实现否决 |
| — | torch.compile(decoder) 编译 module | 静默不生效:tiled_decode 生成器直接调 self.forward(video_vae.py:965),绕过包装。 | 实现否决 |
| — | causal conv cache(缓存历史激活) | causal_decoder: false,42 个时间卷积双侧 pad,消费未来帧;缓存不可能精确。需重训 causal decoder。 | 结构不可行 |
| — | cudnn.benchmark = True | 引入运行间非确定性,与本项目全部方法论冲突。未测即排除,理由记录在案。 | 拒绝 |
| — | VAE 量化 / Q8 kernel | Lightricks 的 FP8 checkpoint 与 Q8-Kernels 只覆盖 transformer,不含 VAE。 | 不适用 |
| 对比实验 | capture-replay 真实 latent A/B | 11 窗口 raw BF16 SHA 逐窗校验全部 identical;唯一变量为 TilingConfig。 | 保留 |
| 对比实验 | 接缝定位 | spatial 带内/带外 MAE 3.9749×,top-1% 误差 75.90% 落在占 20% 面积的带内。 | 已验证 |
| 对比实验 | 窗口拼接点担心被证伪 | chunk join 带内/带外比 0.7963,低于 1;差异不在窗口衔接处。 | 证伪 |
| 对比实验 | capture run 的 end-to-end FPS | 为保存精确 latent 刻意插入同步 D2H 拷贝,该轮 FPS 无性能含义,不得引用。 | 作废 |
| 验收 | 20 s 视频质量面板 | 按用户要求未执行。不是“默认通过”,也不是免除质量门禁。 | 未执行 |
| 指标 | LPIPS / MS-SSIM / VMAF / XPSNR | 本机无可接受实现或预训练权重;未临时下载模型、未用随机网络冒充感知指标。 | N/A |
边界与下一步
最重要的限制:没有 ground truth
本页的 PSNR、SSIM、MAE 全部是成对差异指标。它们能回答“两路差多少、差在哪里、 速度与显存代价是多少”,不能回答“哪一路更接近正确答案”。 合成 latent 探针在排序上支持 single-tile 更接近长上下文参照,但那是合成输入下的排序与量级结论, 不是对真实视频的质量论断。因此绝不能仅凭这组数字宣称 single-tile「画质更好」。
还没做的事
- 20 s 视频质量面板——按用户要求未执行,两个阶段因此都是
quality_accepted = false、production_accepted = false。 - 盲评——完整时序的主观闪烁需直接观看保存的视频,本页任何指标都不能替代。
- P2+P3 的联合三对 A/B——两阶段配对中位数相乘得 +4.263945%, 但那只是阶段性累计投影,因为 P2 与 P3 不是同一组三对联合实验。
- “DiT 还能再提多少”——需后续配对实验,不能由隔离比例承诺。
可检验的下一步
- 补跑 reference-free 解码媒体面板,把
quality_accepted从 false 推到有结论。 - 若未来显存门禁卡住完全不分块,按轴向归因优先只取消空间分块—— 它贡献了约 3/4 的分块误差,且显存占用应低于同时取消两轴(待测)。
- P3 的冷启动代价可用持久化 inductor cache 复测,验证
warm_throughput_only能否解除。 - VAE 变快之后,重新测量 DiT 的可优化空间——这是本轮工作的战略目的。
性能 PASS ≠ 生产接受
P2 与 P3 的性能门禁均为 PASS,但两者的
quality_accepted 与 production_accepted 都是 false。
P3 还额外带有 warm_throughput_only = true 的冷启动限制。
性能 PASS 不能被改写成生产接受。
证据索引
| 内容 | 路径 |
|---|---|
| 原始对比证据(本页全部差异数字) | results/comparison/metrics.json |
| 中文详细结果 | results/comparison/REPORT_CN.md |
| 捕获 manifest(11 窗口哈希) | results/capture_run/vae_latent_capture/capture_manifest.json |
| 捕获脚本 / 重放脚本 | scripts/capture_real_vae_windows.py、scripts/decode_and_compare.py |
| 误差图重算 / 图表生成 | scripts/build_error_map.py、scripts/generate_report_figures.py |
| Week 10 计划与阶段报告 | /workspace/week10-vae-decode-optimization/PLAN.md |
| Phase 2 最终裁决 | .../results/phase2_full_model/pairs_r02/FINAL_SUMMARY_V3.json |
| Phase 3 最终裁决 | .../results/phase3_full_model/pairs_r01/FINAL_SUMMARY_V2.json |
| 合成 latent 探针与两项补测 | .../scripts/tiling_quality_probe.py、.../scripts/probe_followups.py |