这一章会是一段很有趣的旅程,因为到这里,你前面学过的很多东西会在当前最先进的文生视频(T2V)和图生视频(I2V)模型里真正汇合起来。你已经看到 ViT 如何把图像重组为 patch(“视觉模型中的嵌入与分词”),也看到了 DiT 如何把这种思路延伸到生成式扩散模型中(“可扩展的基于 Transformer 的扩散模型”)。事实上,很多 T2V 和 I2V 模型都是在 DiT 的基础上,通过增加时间维度、沿时间堆叠潜在 patch 来构建的。还记得你在“更长上下文与更好性能”那一节里看到的旋转位置嵌入(RoPE)吗?你会在这里再次见到它。
既然你已经理解了 T2I 的工作方式,那么 T2V 和 I2V 对你来说就不再是一次巨大的跳跃,而更像是一次自然推广。你只是从生成单帧,变成生成一个连贯的序列。大多数 T2V 和 I2V 模型甚至还能用同一套核心骨干生成静态图像,乃至 3D 视图。绝大多数时候,架构本身并没有变,变的只是处理的轴数。
对我来说,这正是 Transformer 的优雅之处。一旦你真正理解了它的工作方式,你就会开始看见万物如何拼接在一起——从图像,到视频,到音频,甚至更多模态。这也正是我决定写这本“超越语言的 Transformer”之书的原因:更深层的洞见,并不在于像传统深度学习那样把每个领域都割裂开来分别看待,而在于意识到,这套架构是如何如此自然地延展到不同领域中的。
因此,在这一章里,我会带你走完一条路径:从 ViT,到 DiT,再到 T2I,最后抵达视频生成。一路上,你会理解每一项创新是如何建立在前一项创新之上的,也会看到这些领域彼此之间的联系,比它们表面上看起来要紧密得多。你会了解不同的 SOTA 视频生成模型,以及如何把它们应用到你自己的项目和数据中。你还会以一种全新的视角重新审视前面讲过的模型,比如 PIXART-α。某种意义上说,这一章很可能会成为你读这本书的转折点,因为你一开始学习的那套架构,现在真的要“长出维度”了,字面意义上的。
和前面一样,我仍然会重点讲开源模型,所有代码你都能在本书的代码仓库里找到。像 Sora 和 Stable Diffusion 3 这样的模型,虽然能够在任意分辨率下生成样本,也展示出了很强的 scaling law 对齐能力。所谓 scaling law,就是当你增加数据量、参数量和计算资源时,模型性能会以可预测的幂律形式提升。但是,这些模型几乎没有公开它们的关键设计选择,也没有提供详细实现指南或公开可用的预训练 checkpoint,这极大限制了它们在社区中的采用、复现与研究价值。
潜变量扩散的隐性高效性
“latent” 这个词来自拉丁语 latēns,意思是“隐藏的”或“被遮蔽的”。正是这种隐藏且压缩后的潜在空间表示,让快速、高分辨率的视频生成成为可能。因为不同于直接在像素空间中操作的传统扩散模型,潜变量扩散模型会把去噪过程转移到潜在空间中进行。也就是说,模型不再对原始图像或视频像素做迭代去噪,而是在一个更低维、更压缩的数据表示上执行扩散。这也正是它们被称为 latent diffusion 的原因。
这意味着,你在第 4 章中学过的关于扩散的一切——包括噪声注入、去噪步骤和采样——现在不再发生在原始像素上,而是发生在潜在空间里。也就是说,我们正在从 diffusion model 走向 latent diffusion model(LDM)。对于图像来说,这可能意味着把一张 的图像压缩成类似 这样的潜在表示。这样一来,维度会显著下降,从而使扩展到更高分辨率和更长视频序列成为可行的事。
而当我们从图像走向视频时,这种潜在表示就会变成时空型的。也就是说,我们不再压缩一张静态帧,而是压缩一段帧序列。通常会使用一个 3D VAE,把表示为 3D 张量的一组视频帧压缩成 4D 潜在表示。于是,模型学习的就不再只是空间模式,而是跨时间的运动与动态。一个形状为 的视频,会被压缩为 ,其中时间和空间两个维度都会被下采样。
潜在空间的优雅性
爱因斯坦曾描述质量如何弯曲时空,就像拉伸的织物表面会形成曲率一样。潜在空间也展现出类似的美感:数据越复杂,压缩出来的表示往往越优雅。不过这里弯曲的不是时空,而是被离散化并 token 化的表示空间。
正是这一点,使得基于 Transformer 的视频扩散架构变得可行:注意力层现在可以处理“带有时间感知能力”的 token,而不会让内存成本直接爆炸。
下面要介绍的几个 SOTA 模型,都是建立在这一基础之上的,它们都在高效捕捉视频中的时空信息。每个模型都把 diffusion transformer 扩展到了视频领域,但它们分别解决的是“时间生成挑战”中的不同侧面。表 5-1 概括了这三个模型在架构上的共同点与差异。
表 5-1. 视频版 DiT 的演进
| 特性 | Latte | LTX-Video | Tora |
|---|---|---|---|
| ViT/DiT 骨干 | ✓ | ✓ | ✓ |
| VAE 集成方式 | 预训练、外部 | 集成式,负责最终去噪 | 与运动共享潜在空间 |
| RoPE | ✓ | ✓ | ✗ |
| 运动控制 | ✗ | ✗ | ✓ |
| 注意力方案 | 时空分解 | 全时空统一建模 | 空间/时间块交替 |
| 速度优先 | ✗ | ✓ | ✗ |
| 条件注入方式 | S-AdaLN + timestep tokens | cross-attn + 基于时间步的去噪 | adaptive norm + trajectory latents |
LTX-Video 解决的是效率问题,Latte 关注的是保真度,而 Tora 则试图给你更强的运动轨迹控制能力。它们都依赖 ViT/DiT 风格的架构、位置编码和基于注意力的去噪机制,这也说明了图像领域中 ViT/DiT 的那些原则,是如何自然延展到更复杂的生成模态中的。
LTX-Video:实时视频生成
LTX-Video 是一个基于 Transformer 的潜变量扩散模型,它会以同等重要的方式同时对待空间维度和时间维度。这个设计选择是为了解决早期模型中的一个关键局限:视频同时包含时间和空间两个维度,这会引入额外复杂性。虽然模型通常会从高空间压缩率、最多 64 通道的 VAE 潜在空间中获益,但如果把这种设定直接搬到视频上,就必须更精细地平衡空间细节和时间一致性。
为了解决这一点,LTX-Video 并没有完全依赖第二阶段扩散模型,或者在潜在空间里单独使用像素级损失,而是把最终去噪步骤直接交给 VAE 解码器,让它在把 latent 转换成像素的同时完成最后一步去噪。图 5-1 展示了这一过程,其中 表示潜变量 来自标准多元高斯分布采样,其中均值 ,协方差 。这也正是大多数 VAE 和 LDM 在任何解码或去噪开始之前所使用的潜在空间:它是一个压缩后的、学得的、低维且高斯结构化的表示,因此适合高效采样和重建。
图 5-1. 该图展示了 latent-to-latent 的扩散去噪步骤,以及最终的 latent-to-pixels 去噪步骤。图片改编自 Yoav HaCohen 等人(2024)。
这一修改在高 latent 压缩率下尤其高效,因为在这种情况下,一些高频细节本来就无法被准确恢复,只能通过合理方式“补生成”出来。除了 T2V 生成之外,这个模型也支持 I2V,它通过一种基于时间步的条件注入机制来实现。借助这个机制,模型可以对输入中的任意片段进行条件化,因此它可以从视频的任意一帧接着往下生成,而不需要额外参数或专门设计的 token。示例 5-1 展示了如何使用 LTX-Video 的 conditioning,从一张图像生成视频。
示例 5-1. 从图像生成视频
image = load_image("image.jpg")
condition = LTXVideoCondition(
image=image,
frame_index=0,
strength=1.0
)
prompt = """A woman walking on a street with trees left and right. Above her, the
vibrant green leaves shimmer in the dappled sunlight, their color almost fluorescent
against the darker trunks. The air is alive with energy, sunlight flickering through
the canopy, painting shifting patterns across the trees."
negative_prompt = "worst quality, inconsistent motion, blurry, jittery, distorted"
generator = torch.Generator("cuda").manual_seed(42)
video = pipe(
conditions=[condition],
prompt=prompt,
negative_prompt=negative_prompt,
width=704,
height=512,
num_frames=48,
frame_rate=24,
num_inference_steps=40,
guidance_scale=5.5,
image_cond_noise_scale=0.15,
generator=generator
).frames[0]
export_to_video(video, "walking.mp4", fps=24)
- 加载输入图像。
- 设置条件。
- 应用完整条件。
- 设置可复现的随机生成器。
- 按 24 FPS 生成 2 秒视频。
- 执行生成。
- 导出为 mp4。
在模型从 Hugging Face 下载好之后,从图像生成视频只需要几秒钟。
在压缩后的潜在空间中进行操作,是基于 Transformer 的视频生成得以高效运行的关键。原因你已经知道了:注意力机制的复杂度会随着 token 数量二次增长。但现有多数模型通常会把空间和时间分别压缩成例如 或 ,对应压缩比大约是 或 ,再经过 patchify 后,token 密度大约是 或 。这里的 patchify,指的是把潜在空间中较小的时空局部区域分组成离散 token 的过程。这个 patchify 与在“可扩展的基于 Transformer 的扩散模型”里你看到的 VAE + DiT 管线在概念上是相通的:输入图像先被压缩,再被 patchify,然后送进 Transformer 骨干。LTX-Video 正是在这一思路上继续往前走。只不过,它的 Video-VAE 使用了 的压缩率,并有 128 个通道,这使得视频生成时的压缩因子达到 ,而 token 密度则达到 。这种更高压缩率,直接带来了更快的推理速度,同时并没有明显损害视觉质量。
这种从像素到 latent 的压缩,是模型性能的核心,也是它速度的关键来源。为了在高压缩下依然不损失质量,LTX-Video 的作者相对于标准 VAE 做了多项架构改进。不同于 DiT 风格模型里 patchify 在 Transformer 前执行,这里它被移进了 VAE 编码器内部。VAE 解码器也被增强了,它会在把 latent 解码回像素空间的同时,负责执行最终一步去噪。这个调整有助于弥合高压缩表示和高分辨率输出之间的落差。
不过,高压缩也会带来一个问题:在 rectified-flow 去噪中,不容易完全收敛。在 rectified-flow 模型中,干净 latent 是从噪声输入
中预测出来的,模型通过学习一个直接映射
来逼近去噪过程,而不必像传统扩散那样完全依赖迭代采样。在实际中, 往往从纯噪声 初始化,然后在逐渐递减的一组噪声水平 上不断被精炼。虽然这样会让 更接近训练分布,但由于采样步数有限,仍然会残留一定不确定性。这里的“残余不确定性”,指的是预测 latent 和真实 latent 之间的偏差,它会导致一些细小但可感知的分布偏移,尤其是在高压缩或者高频区域中更明显。对于 latent diffusion 而言,其中 是样本 的压缩表示,这种不确定性就可能导致 latent 落在分布之外,解码后出现可见伪影,尤其是在那些因激进压缩而本就不易保留的高频区域中。
有些模型会通过额外的上采样器来缓解这些伪影,这些上采样器可能工作在像素空间中,也可能工作在压缩程度较低的 latent 空间里,但这些方案通常计算开销很大。LTX-Video 的做法不同:它引入了一个解码器 ,把去噪和解码联合起来执行。训练目标是让它直接把带噪 latent 映射回干净像素:
在这个方程里, 表示带噪 latent,它是干净 latent code 与高斯噪声 按 diffusion 时间步 线性插值得到的。解码器 会以这个时间步作为条件,学习如何直接从带噪输入中恢复干净图像 。与需要逐步去噪 latent 的标准扩散模型不同,这种设定允许模型通过单步去噪,直接生成像素空间图像。由于 是跨域映射——从 latent 到像素——它就绕过了额外上采样或事后修复的需要。这种“解码 + 去噪”的融合设计,使它即便在高压缩率下,也能保持高效推理和较高视觉保真度。
此外,这个模型还建立在 PIXART-α 架构之上,并为视频生成做了若干关键适配。其中包括:用 RMSNorm 替换 LayerNorm,以及在注意力之前对 query 和 key 做归一化,以避免出现低熵注意力权重。它们共同增强了模型鲁棒性,也使其更适合生成高质量、时间一致性更强的视频内容。图 5-2 展示了 LTX-Video 的 Transformer block 架构。
不同于传统绝对位置嵌入,RoPE 在这里是通过归一化后的分数坐标来应用的,也就是说,它以像素单位和秒单位相对于预定义最大分辨率与最大时长来计算。这有助于在可变长度序列和不同帧率之间保持一致性,并让运动生成看起来更加自然。此外,模型在 RoPE 中使用了指数递增的频带,以提升图像和视频生成中的稳定性、效率和视觉保真度。
在你自己的数据上训练和微调 LTX-Video
LTX-Video 提供了一个仓库,里面包含训练和微调该模型所需的工具和脚本。它同时支持基于 LoRA 的训练和完整模型微调,适用于自定义数据集。训练器既支持视频片段,也支持单张图像,并且还包含一些额外工具,用于数据集预处理、视频 caption 生成,以及场景切分。
图 5-2. LTX-Video 的 3D Transformer block 架构建立在 PIXART-α 之上,但将 LayerNorm 替换为 RMSNorm,并加入了 QK-normalization 与 RoPE。图片改编自 Yoav HaCohen 等人(2024)。
Latte:把结构化细节注入每一帧视频
在上一节里,你已经了解了构建 Latte 所需的关键要素:它是一个面向视频生成的 latent diffusion transformer。本节会进一步聚焦到 Latte 的具体架构创新和改动。图 5-3 展示了 Latte 的四种架构变体概览。
图 5-3. Latte 提出了四种模型变体,用于高效捕捉视频中的时空信息。图中的每个 block 都表示一个 Transformer block。变体 (A) 和 (B) 使用标准 block,而 (C) 和 (D) 使用了定制 block。图中未展示 VAE 的编码与解码部分。图片改编自 Xin Ma 等人(2025)。
Latte 通过引入四种基于 Transformer 的变体,来应对视频 latent 空间中大量时空 token 的建模难题:
变体 1:交错的时空注意力
这个变体把空间注意力和时间注意力拆到不同的 Transformer block 中。空间 block 处理的是具有相同时间索引的 token,因此它关注的是单帧内部的空间关系。时间 block 则在经过空间编码后的 token 上操作,用交错方式建模跨时间的依赖。一个 latent 空间中的视频片段,首先会被 token 化成序列,并加入时空位置嵌入。然后,token 序列会先 reshape 成适合空间 block 处理的输入,再重新 reshape 成时间 block 的输入。这种设计让模型能够在保持注意力分解计算的同时,提取结构化的时空特征。
变体 2:后融合(Late fusion)
这个变体与变体 1 使用相同数量的 Transformer block,但时间信息的整合策略不同。它不是交错地执行空间和时间注意力,而是顺序执行:先在每一帧内部做空间注意力,再跨帧做时间注意力。这种 late fusion 设计把时间建模视为第二阶段的细化步骤。空间块和时间块的输入形状与变体 1 保持一致。
变体 3:块内顺序注意力分解
与变体 1 和变体 2 在 block 级别进行拆分不同,这个变体直接修改了每个 Transformer block 的内部结构。它把 multi-head self-attention 顺序分解为两步:先计算空间维度上的注意力,再在同一个 block 内计算时间维度上的注意力。这种做法让每个 Transformer block 都能够同时编码空间和时间依赖,而不需要分别定义不同类型的 block。输入序列会在每一步注意力计算之前做相应 reshape。
变体 4:并行注意力头拆分
这个变体把每一层多头注意力中的 head 分成两组,一组专门关注空间维度,另一组专门关注时间维度,并行执行。两种注意力计算完成之后,输出会被重新 reshape 并组合起来,作为后续模块的输入。这样一来,模型就能在保持高效注意力计算的同时,并行建模空间和时间信息。经过 Transformer 骨干之后,latent token 序列会被解码成预测噪声和协方差,输出张量的形状与输入 latent video 保持一致。这个解码步骤使用的是标准线性投影加 reshape,方式与 DiT 类似。
正如你看到的,每个变体本质上都反映了“空间”和“时间”这两个维度如何耦合或解耦的不同策略。作者之所以做这组探索,是为了理解时空因子化和整合之间的权衡,并评估不同程度的解耦会如何影响时间一致性和模型性能。Latte 的作者最终得出结论:变体 1 在建模表达力和架构完整性之间提供了最好的平衡,因此把它作为无条件生成和 T2V 任务的默认方案。表 5-2 对四种变体做了简要对比。
表 5-2. Latte 四种变体对比
| 变体 | 设计原则 | 动机 | 关键权衡 |
|---|---|---|---|
| 1 | 交错空间块与时间块 | 分离但协调地建模空间与时间依赖 | 时间一致性和整体性能最好,但计算略高 |
| 2 | Late fusion:先全部空间,再全部时间 | 设计更简单,也更符合顺序建模习惯 | 由于前期过于偏向空间,时间一致性会逐渐下降 |
| 3 | 块内顺序注意力(空间 → 时间) | 在每个 block 内混合空间与时间建模 | 优于 2 和 4,但归一化不匹配会拖累性能 |
| 4 | 并行注意力头拆分 | 追求并行效率,尽量压低 FLOPs | 表现最差;不同特征流融合不佳,损害时间一致性 |
示例 5-2 展示了你如何把 Latte 用到自己的视频生成任务中。
示例 5-2. 使用 Latte 生成视频
video_length = 16
pipe = LattePipeline.from_pretrained("maxin-cn/Latte-1",
torch_dtype=torch.float16).to(device)
vae = AutoencoderKLTemporalDecoder.from_pretrained("maxin-cn/Latte-1",
subfolder="vae_temporal_decoder", torch_dtype=torch.float16).to(device)
pipe.vae = vae
prompt = "A cat playing the guitar with a forest in the background"
video = pipe(prompt, video_length=video_length).frames[0]
- 设为 1 时是 text-to-image,设为 16 时是 text-to-video。
- 下载模型。
- 使用带时间维度的 VAE decoder。
- 生成视频。
接下来,如果你想把生成的视频导出,可以使用 Hugging Face diffusers 库里的工具函数,如示例 5-3 所示。
示例 5-3. 导出生成的视频
export_to_video(
video_frames=video,
output_video_path="latte.mp4",
fps=10,
quality=5.0,
bitrate=None,
macro_block_size=16
)
- 你的帧列表(NumPy 数组或 PIL 图像)
- 输出路径和文件名
- 每秒帧数
- 可变码率质量(0–10)
- 可选的固定码率设置
- 视频块尺寸约束,通常是 16;也可以设为 4、8,或者设为 1 以禁用该限制。
如果你的场景是做动画短片,你也可以使用 export_to_gif 把结果导出为 gif。
Tora:从轨迹到叙事,一帧一帧生成
Tora 建立在 OpenSora 的 Spatial-Temporal DiT 骨干之上,并且为运动控制加入了两项关键创新:Trajectory Extractor(TE)和 Motion-guidance Fuser(MGF)。它们一起让你能够通过轨迹输入精确控制对象运动,并且这种控制是直接注入到视频扩散过程的 latent 空间里的。
TE 会把用户定义的轨迹,转换成与视频 latent token 对齐的格式。图 5-4 展示了两个例子,说明用户手绘轨迹是如何影响生成视频中的运动的。Tora 并不是简单地处理帧与帧之间的位移偏移,而是先通过水平位移和垂直位移来构造一个 motion。为了把这种 motion 融入 Transformer 输入中,压缩后的张量会被 patchify,并送过带 skip connection 的卷积层。最终得到的是一组分层的时空运动 patch,覆盖空间和时间两个维度。
图 5-4. Tora 如何基于用户定义轨迹生成视频的示例。图片改编自 Zhenghao Zhang 等人(2024)。
Spatial-Temporal DiT(ST-DiT)通过交替使用两类 Transformer block 来处理视频 latent:空间 block 和时间 block。空间 block 会在空间维度上执行 self-attention,并包含与 prompt 的 cross-attention;时间 block 则把空间 self-attention 替换成时间 self-attention,但保持整体结构不变。输入 latent 序列来自于对视频 VAE 输出做 patchify,而条件信息则通过 T5 编码器以 cross-attention 的方式注入。图 5-5 展示了 Tora 的整体架构设置。
图 5-5. TE 会使用一个 3D motion VAE,把轨迹向量嵌入到与视频 patch 相同的 latent 空间中,从而在跨帧场景下保留运动信息。它还会通过堆叠卷积提取分层运动特征。MGF 则通过自适应归一化,把这些特征注入 DiT block 中,从而引导视频沿着定义好的轨迹生成。图片改编自 Zhenghao Zhang 等人(2024)。
为了注入运动控制,MGF 会通过 adaptive normalization,把基于轨迹的运动信息融合进 ST-DiT block 中。这个机制会使用从 motion embedding 中得到的 scale 和 shift 参数,对 Transformer 的隐藏状态进行调制。MGF 如图 5-6 所示。
图 5-6. 使用 adaptive norm 融入轨迹条件的 Motion-guidance Fuser。图片改编自 Zhenghao Zhang 等人(2024)。
下面这些变量是这个过程中的关键变量:
- :经过 VAE 下采样后,latent 视频序列的时间长度
- :每帧中的空间 patch 数量,通常
- :每个 token 在 DiT 中的隐藏状态维度
- :每个 token 的运动 embedding 维度
- :输入到第 个 DiT block 的隐藏状态
- :由 导出的 scale 和 shift 向量
- :第 个 block 的 motion condition,由 TE 提取而来
隐藏状态更新公式为:
这使模型能够在“每帧、每个 patch”的粒度上调制 token 特征,从而实现运动控制,同时又不破坏 DiT 的生成能力。 和 都通过零初始化卷积进行学习,因此在训练中能平滑地融入模型。
通过把这些运动特征注入时间 block 中,Tora 能在较长时长上维持真实、自然的运动表现。这也让 Tora 成为第一个原生支持“基于轨迹驱动的视频生成”的 DiT 模型。
示例 5-4 中的 PyTorch 代码实现了一个简化版 TE 核心逻辑,而示例 5-5 展示了 MGF 的原始实现,它们分别对应图 5-5 和图 5-6 中的 motion conditioning 路径:
- 轨迹输入 → latents → motion patches
- motion patches → scale 和 shift → 经归一化的 Transformer hidden states
示例 5-4. 简化版 Trajectory Extractor
class TrajExtractor(nn.Module):
def __init__(self, patch_size=2,
channels=[320, 640, 1280, 1280], nums_rb=3, cin=2):
super().__init__()
self.patch_size = (1, patch_size, patch_size)
self.downsize_patchify = nn.PixelUnshuffle(patch_size)
cin_ = cin * patch_size**2
self.conv_in = nn.Conv2d(cin_, channels[0], kernel_size=3, padding=1)
body = []
for i in range(len(channels)):
for j in range(nums_rb):
in_c = channels[i - 1] if i > 0 and j == 0 else channels[i]
out_c = channels[i]
body.append(nn.Sequential(
nn.BatchNorm2d(in_c),
nn.ReLU(),
nn.Conv2d(in_c, out_c, kernel_size=3, padding=1)
))
self.body = nn.ModuleList(body)
def forward(self, x):
# x: [B, C, T, H, W]
T, H, W = x.shape[-3:]
if W % self.patch_size[2] != 0:
x = F.pad(x, (0, self.patch_size[2] - W % self.patch_size[2]))
if H % self.patch_size[1] != 0:
x = F.pad(x, (0, 0, 0, self.patch_size[1] - H % self.patch_size[1]))
if T % self.patch_size[0] != 0:
x = F.pad(x, (0, 0, 0, 0, 0, self.patch_size[0] - T % self.patch_size[0]))
x = rearrange(x, "B C T H W -> (B T) C H W")
x = self.downsize_patchify(x)
x = self.conv_in(x)
features = []
for block in self.body:
x = block(x)
features.append(x)
return features
- 从 RGB 光流图像到与 DiT 兼容的 latent 特征空间的初始投影。
- 堆叠式残差卷积块负责提取多层次运动特征。
- 特征会沿层级逐步计算,对应更深的时空模式。
- 最终返回一个 motion condition 列表 ,与 DiT block 深度对齐。
示例 5-5. 通过自适应归一化注入运动特征
class MGF(nn.Module):
def __init__(self, flow_in_channel=128, out_channels=1152):
super().__init__()
self.out_channels = out_channels
self.flow_gamma_spatial = nn.Conv2d(flow_in_channel,
self.out_channels // 4, 3, padding=1)
self.flow_gamma_temporal = zero_module(
nn.Conv1d(
self.out_channels // 4,
self.out_channels,
kernel_size=3,
stride=1,
padding=1,
padding_mode="replicate",
)
)
self.flow_beta_spatial = nn.Conv2d(flow_in_channel,
self.out_channels // 4, 3, padding=1)
self.flow_beta_temporal = zero_module(
nn.Conv1d(
self.out_channels // 4,
self.out_channels,
kernel_size=3,
stride=1,
padding=1,
padding_mode="replicate",
)
)
self.flow_cond_norm = FloatGroupNorm(32, self.out_channels)
def forward(self, h, flow, T):
if flow is not None:
gamma_flow = self.flow_gamma_spatial(flow)
beta_flow = self.flow_beta_spatial(flow)
_, _, hh, wh = beta_flow.shape
gamma_flow = rearrange(gamma_flow, "(b f) c h w -> (b h w) c f", f=T)
beta_flow = rearrange(beta_flow, "(b f) c h w -> (b h w) c f", f=T)
gamma_flow = self.flow_gamma_temporal(gamma_flow)
beta_flow = self.flow_beta_temporal(beta_flow)
gamma_flow = rearrange(gamma_flow,
"(b h w) c f -> (b f) c h w", h=hh, w=wh)
beta_flow = rearrange(beta_flow,
"(b h w) c f -> (b f) c h w", h=hh, w=wh)
h = h + self.flow_cond_norm(h) * gamma_flow + beta_flow
return h
- 来自 flow 输入的空间特征图会被投影到中间表示中。
- 时间卷积负责建模 gamma 和 beta 如何随帧变化,并且采用零初始化,以保证训练平稳。
- Group normalization 用于保证调制特征时的稳定性。
- 针对 gamma 和 beta,会逐帧提取 motion embedding。
- 随后再把跨帧的时间动态加入 gamma 和 beta。
- 最终融合:latent 状态会用学到的 scale 和 shift 参数进行调制。
开发你自己的 Transformer 变体
Tora 的构建借助了一个叫 SwissArmyTransformer 的库。SwissArmyTransformer(sat)是一个非常灵活的库,它可以帮助你快速构建自己的自定义 Transformer 变体。它通过轻量级 mixin,把 BERT、GPT、T5 和 ViT 等模型统一到一个灵活骨干之上。它建立在 DeepSpeed-ZeRO 和模型并行之上,因此可以用非常少的代码高效训练和微调大模型。这个开源库你可以在 GitHub 上找到。
我还 fork 了原始 Tora 仓库,并修掉了一些 bug,让它更容易运行。正如在章节开头提到的,配套 Jupyter notebook 可以在本书仓库中找到。在 notebook 中你会看到这样一行代码:
!N_GPU=1 PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True torchrun --standalone --nproc_per_node=1 sample_video.py --base configs/tora/model/cogvideox_5b_tora.yaml configs/tora/inference_sparse.yaml --load ckpts/tora/t2v --output-dir samples --point_path trajs/coaster.txt --input-file assets/text/t2v/examples.txt
这会复现论文里的一些示例。当然,你也可以直接修改 GitHub 仓库里的 .txt 文件,生成你自己的视频。原始仓库还带有一个 Gradio 实现。Gradio 是一个用来为机器学习应用快速创建 Web 界面的 Python 库。你只需要在 Colab notebook 中运行下面这行:
!python app.py --load ckpts/tora/t2v
这就会启动 Gradio。等模型全部加载完成后,你会看到类似这样一行输出:
Running on public URL https://3f681e17cff82dabaf.gradio.live
点击这个链接,会打开一个新的浏览器标签页,你会看到对应的可视化界面,如图 5-7 所示。
图 5-7. 用于创建自定义轨迹和视频的 Gradio 应用。
在这个界面里,你可以直接在画布上点击并绘制自己的运动轨迹。然后输入 prompt,点击生成,就能基于这条轨迹生成一段视频。这个过程大约需要 2 到 3 分钟。你也可以直接采用预定义的轨迹和 prompt。往下滚动页面,你就能看到它们,如图 5-8 所示。
图 5-8. 调整和使用预定义 prompt 与/或轨迹。
无论你选择自己画运动路径,还是从预定义示例开始,这个界面都给了你对视频展开方式的完整创作控制权,而且是真正意义上的“一帧一帧地控制”。它也提醒我们:一旦你拥有了合适的工具和模型,运动不再只是你观察到的东西,而会变成你可以主动设计的视频轨迹。
总结
在这一章中,你看到了 Transformer 架构是如何自然地从图像延展到视频的,而它依赖的核心组件,仍然是 ViT、DiT 和 latent diffusion。你已经理解了,把高维像素数据压缩到结构化 latent 空间中,是如何让生成连贯、高分辨率、同时保持空间和时间一致性的视频序列成为可能的。
你还看到了,LTX-Video、Latte 和 Tora 这些先进模型,分别是如何从不同角度解决视频生成问题的:有的重点在实时效率,有的强调高保真输出,还有的通过用户定义轨迹实现精确的运动控制。每个模型都沿着同一条架构主线朝不同方向扩展:LTX-Video 通过紧耦合的 VAE 和时间步条件化来实现高效生成;Latte 通过对时空维度的结构化注意力分解来处理视频;而 Tora 则通过基于轨迹的运动条件归一化,把运动控制直接注入生成过程。
在这一章里,你也进一步加深了对 latent diffusion 如何支持可扩展视频生成的理解——因为它把去噪过程转移到了更低维的表示空间中。而这一点,再结合基于 Transformer 的注意力机制,以及 adaptive normalization、带分数坐标的 RoPE 等架构创新,就清楚展示了:Transformer 这套设计,是如何一步步演进,以处理越来越复杂的生成任务的。
最重要的是,你已经亲眼看到:Transformer 对“序列”的建模能力,是如何从文本推广到图像,再推广到视频的,而且并不需要从根本上重设计架构。这一点将成为你进入第 6 章的基础。正如运动是一帧一帧展开的,声音也是一个波形一个波形展开的:它们都具有结构性、时间性,而且都可以被 token 化。