仅供仔细学习记录,有错欢迎指出
这里fps=16, chunk_size=3
图像像素先过 VAE(空间 stride 8),再过 patch (1, 2, 2):
- 像素
480 × 832 - VAE latent:
60 × 104(通道 16) - patch 时间维是 1,空间各除以 2:
30 × 52 = 1560
这块是 patch 怎么把一帧 latent 切成 token。我对着 Wan 的 patch embedding 把 (1, 2, 2) 具体切在哪几个轴上对一下。
(1, 2, 2) 是一个不重叠的 3D 小窗口:时间上每次只取 1 帧,空间上每次取 2×2 个 latent 格子。窗口扫完整张图,每个窗口变成 1 个 token,所以一帧是 30 × 52 = 1560 个 token。
这就是视频 DiT 里的 3D patch embedding,和 ViT 把图片切成 patch 是同一件事,只是多了一个时间轴。Wan 用一个 3D 卷积来做:
self.patch_embedding = nn.Conv3d(
in_dim, dim, kernel_size=patch_size, stride=patch_size)
kernel_size 和 stride 都是 (1, 2, 2),三个数依次是 时间、高、宽。stride 等于 kernel,所以窗口挨着排、不重叠。
一帧是怎么变成 1560 的
VAE 出来的一帧 latent 可以想成一张 60 × 104 的小图,每个格子有 16 个通道。卷积在这张图上滑:
时间: 窗口厚度 = 1,步长 = 1 这一帧单独切,不跟前后帧揉在一起
高: 窗口 = 2,步长 = 2 60 / 2 = 30
宽: 窗口 = 2,步长 = 2 104 / 2 = 52
空间上就是把 60 × 104 铺成不重叠的 2×2 砖块:
60 行 latent
┌────┬────┬────┬─ ... ─┐
│2×2 │2×2 │2×2 │ │ 每一块 → 1 个 token
├────┼────┼────┤ │
│ │ │ │ │ 一行有 104/2 = 52 块
└────┴────┴────┴─ ... ─┘
一共 30 行
30 × 52 = 1560。每一块里有 1 × 2 × 2 × 16 = 64 个数,卷积把这 64 个数投影成一个 1536 维向量,这就是一个 token。
一个 chunk 有 3 个 latent 帧,时间窗口是 1,所以帧数不变,还是 3 帧,每帧 1560 个 token。卷积输出是 [1, 1536, 3, 30, 52],随后把空间和时间摊平:
x_noisy = [u.flatten(2).transpose(1, 2) for u in x_noisy]
变成 [1, 3×1560, 1536]。hook 再按帧切开,就是 gate 看到的 [1, 3, 1560, 1536]。