近年来,生成模型在图像、视频和音频等多模态生成任务中取得了显著进展。然而,主流的潜在空间扩散模型(Latent Diffusion Models, LDMs)依赖于预训练的变分自编码器(VAE),将数据压缩到潜在空间以降低计算成本。这种方法虽然高效,但分离的VAE和扩散模型训练阻碍了端到端的优化,且潜在空间的压缩可能损失高频细节。针对这些问题,来自香港大学和Adobe的研究团队在论文《PixelFlow: Pixel-Space Generative Models with Flow》中提出了PixelFlow,一种直接在像素空间进行图像生成的端到端框架,结合了流匹配(Flow Matching)技术,为生成模型研究开辟了新的方向。本文将面向熟悉流匹配的深度学习研究者,介绍PixelFlow的创新点和核心做法。
下文中图片来自于原论文:https://www.arxiv.org/pdf/2504.07963
像素空间的端到端生成
PixelFlow摒弃了LDMs中依赖预训练VAE的范式,直接在原始像素空间进行生成。这不仅简化了模型设计,避免了VAE引入的细节损失,还实现了从训练到推理的完全端到端优化。相比传统的像素空间扩散模型,PixelFlow无需额外的上采样网络,统一参数集处理多尺度生成任务。
级联流匹配(Cascade Flow Modeling)
PixelFlow通过级联流匹配策略,解决了像素空间生成高分辨率图像的高计算成本问题。它将生成过程分为多个分辨率阶段,从低分辨率开始逐步过渡到目标分辨率。在早期去噪阶段,模型处理低分辨率样本,随着去噪进程推进,分辨率逐渐增加。这种多尺度生成策略显著降低了计算开销,同时保持了生成质量。
多尺度训练与推理的高效实现
PixelFlow设计了高效的多尺度训练和推理流程。训练时,通过对目标图像进行递归下采样构造多尺度表示,并在不同时间步对跨尺度样本进行插值,生成训练样本。推理时,从最低分辨率的纯高斯噪声开始,逐步去噪并上采样到目标分辨率。结合序列打包(sequence packing)和重新去噪(renoising)技术,PixelFlow实现了高效的批处理和跨尺度平滑过渡。
强大的生成性能
PixelFlow在ImageNet 256×256类条件生成任务上取得了1.98的Fréchet Inception Distance(FID),优于许多潜在空间模型(如LDM-4-G的3.60、DiT-XL/2的2.27)。在文本到图像生成任务中,PixelFlow在GenEval和DPG-Bench等基准上表现出色,分别达到0.64和77.93,展现了其在视觉质量和语义对齐方面的竞争力。
PixelFlow基于流匹配算法,通过定义从先验分布(通常为标准正态分布)到目标数据分布的线性路径进行生成。论文中,PixelFlow将生成过程分为多个分辨率阶段(stages),每个阶段在特定的时间区间内操作。假设有 ( S S S ) 个阶段,第 ( s s s ) 阶段的起始和终止状态定义如下:
其中,( Down ( ⋅ ) \text{Down}(\cdot) Down(⋅)) 和 ( Up ( ⋅ ) \text{Up}(\cdot) Up(⋅)) 分别为双线性插值下采样和最近邻上采样操作,( ϵ ∼ N ( 0 , 1 ) \epsilon \sim \mathcal{N}(0, 1) ϵ∼N(0,1)) 为高斯噪声。训练时,通过在起始和终止状态间线性插值生成中间样本:
x t s = τ ⋅ x t 1 s + ( 1 − τ ) ⋅ x t 0 s , τ = t − t 0 s t 1 s − t 0 s \mathbf{x}_{t^s} = \tau \cdot \mathbf{x}_{t_1^s} + (1 - \tau) \cdot \mathbf{x}_{t_0^s}, \quad \tau = \frac{t - t_0^s}{t_1^s - t_0^s} xts=τ⋅xt1s+(1−τ)⋅xt0s,τ=t1s−t0st−t0s
模型通过均方误差(MSE)损失优化速度预测:
E s , t , ( x t 1 , x t 0 ) ∥ μ θ ( x t s , τ ) − v t ∥ 2 , v t = x t 1 s − x t 0 s \mathbb{E}_{s,t,(\mathbf{x}_{t_1}, \mathbf{x}_{t_0})} \left\| \mu_\theta(\mathbf{x}_{t^s}, \tau) - \mathbf{v}_t \right\|^2, \quad \mathbf{v}_t = \mathbf{x}_{t_1^s} - \mathbf{x}_{t_0^s} Es,t,(xt1,xt0)∥μθ(xts,τ)−vt∥2,vt=xt1s−xt0s
推理时,从最低分辨率的高斯噪声开始,逐阶段去噪并上采样,最终生成目标分辨率图像。
PixelFlow采用基于Transformer的扩散变换器(DiT)架构,结合以下关键修改以适应像素空间和多尺度生成:
PixelFlow在ImageNet 256×256类条件生成任务中表现出色,FID达到1.98,优于潜在空间模型如DiT-XL/2(2.27)和SiT-XL/2(2.06),并超越多数像素空间模型。文本到图像生成方面,PixelFlow通过两阶段训练(先在LAION 256×256上训练,再在高美学质量数据集上微调至512×512),在GenEval和DPG-Bench上展现了强大的语义对齐能力。定性结果(如图6)表明,PixelFlow能生成高分辨率(1024×1024)、视觉逼真的图像,细节丰富且与复杂文本提示高度一致。
PixelFlow的核心贡献在于展示了像素空间生成模型的潜力,挑战了潜在空间模型的主导地位。其级联流匹配策略为高分辨率生成提供了一种高效的解决方案,而端到端设计则为未来的模型优化提供了更大的灵活性。对于深度学习研究者,PixelFlow的以下方面值得进一步探索:
总之,PixelFlow通过简洁而高效的设计,证明了像素空间生成模型在性能和实用性上的潜力,为生成模型研究提供了新的思路。感兴趣的研究者可访问GitHub仓库获取代码和模型,进一步探索这一创新框架。
详细解释PixelFlow中基于流匹配(Flow Matching)的多尺度生成过程中,起始状态和终止状态公式的含义,以及为什么这样设计。我们将逐步分析公式,并结合PixelFlow的整体目标和背景,阐明其设计意图。
流匹配是一种生成模型训练方法,通过定义从先验分布(通常为标准正态分布 ( N ( 0 , 1 ) \mathcal{N}(0, 1) N(0,1))) 到目标数据分布的连续变换路径来生成样本。PixelFlow的核心创新是直接在像素空间进行端到端的图像生成,而为了降低高分辨率图像生成的计算成本,它采用了级联流匹配(cascade flow modeling) 策略,将生成过程分为多个分辨率阶段(stages)。每个阶段负责从较低分辨率的噪声样本逐步生成更高分辨率的图像,最终达到目标分辨率。
在每个分辨率阶段 ( s s s ),PixelFlow定义了一个从起始状态 ( x t 0 s \mathbf{x}_{t_0^s} xt0s) 到终止状态 ( x t 1 s \mathbf{x}_{t_1^s} xt1s) 的变换过程,通过流匹配学习如何将噪声引导到目标图像的对应分辨率表示。这种多尺度设计的关键在于:在早期阶段处理低分辨率样本以节省计算资源,后期逐步增加分辨率以恢复细节。
我们逐一分析起始状态和终止状态的公式,结合上下文解释它们的含义。
x t 0 s = t 0 s ⋅ Up ( Down ( x 1 , 2 s + 1 ) ) + ( 1 − t 0 s ) ⋅ ϵ \mathbf{x}_{t_0^s} = t_0^s \cdot \text{Up}(\text{Down}(\mathbf{x}_1, 2^{s+1})) + (1 - t_0^s) \cdot \epsilon xt0s=t0s⋅Up(Down(x1,2s+1))+(1−t0s)⋅ϵ
x t 1 s = t 1 s ⋅ Down ( x 1 , 2 s ) + ( 1 − t 1 s ) ⋅ ϵ \mathbf{x}_{t_1^s} = t_1^s \cdot \text{Down}(\mathbf{x}_1, 2^s) + (1 - t_1^s) \cdot \epsilon xt1s=t1s⋅Down(x1,2s)+(1−t1s)⋅ϵ
训练时,PixelFlow通过在起始状态和终止状态之间线性插值生成中间样本:
x t s = τ ⋅ x t 1 s + ( 1 − τ ) ⋅ x t 0 s , τ = t − t 0 s t 1 s − t 0 s \mathbf{x}_{t^s} = \tau \cdot \mathbf{x}_{t_1^s} + (1 - \tau) \cdot \mathbf{x}_{t_0^s}, \quad \tau = \frac{t - t_0^s}{t_1^s - t_0^s} xts=τ⋅xt1s+(1−τ)⋅xt0s,τ=t1s−t0st−t0s
PixelFlow的起始状态和终止状态设计是其多尺度级联流匹配策略的核心,目的是在像素空间高效生成高分辨率图像,同时保持生成质量。以下是设计背后的原因和逻辑:
直接在像素空间生成高分辨率图像(如 256×256 或更高)需要处理大量像素间的相关性,计算成本极高。PixelFlow通过级联策略将生成过程分解为多个分辨率阶段:
起始状态使用 ( Up ( Down ( x 1 , 2 s + 1 ) ) \text{Up}(\text{Down}(\mathbf{x}_1, 2^{s+1})) Up(Down(x1,2s+1))) 模拟较低分辨率的图像表示,确保早期阶段从模糊的低分辨率图像开始,而非直接处理高分辨率细节。终止状态使用 ( Down ( x 1 , 2 s ) \text{Down}(\mathbf{x}_1, 2^s) Down(x1,2s)) 定义当前阶段的目标分辨率,确保模型逐步逼近更高分辨率的真实图像。
流匹配算法允许从任意先验分布到目标分布的变换,而非局限于噪声到图像的去噪过程。PixelFlow利用这一特性,在每个阶段定义了从低分辨率噪声(起始状态)到当前阶段分辨率目标(终止状态)的路径:
这种设计允许模型在每个阶段专注于特定分辨率的生成任务,逐步从粗到精,避免一次性处理高分辨率的复杂分布。
PixelFlow的级联设计需要在不同分辨率阶段之间平滑过渡:
这种跨尺度设计通过流匹配的线性路径,确保了生成过程的连续性和稳定性,避免了传统级联模型中不同阶段模型分离带来的优化困难。
PixelFlow的一个关键优势是端到端训练。起始状态和终止状态的定义使得所有阶段共享同一组模型参数,通过多尺度样本的统一训练(均匀采样不同阶段的样本)优化模型。这种设计避免了传统级联扩散模型中低分辨率生成和上采样的分离训练,简化了训练流程并提高了整体性能。
起始状态和终止状态通过 ( t 0 s t_0^s t0s ) 和 ( t 1 s t_1^s t1s ) 控制噪声与图像信息的比例:
这种噪声与信息的渐进过渡符合流匹配的训练目标,即通过速度预测(velocity prediction)学习从噪声到目标数据的变换路径。
假设目标图像 ( x 1 \mathbf{x}_1 x1) 为 256×256 分辨率,PixelFlow分为 3 个阶段(( S = 3 S = 3 S=3 ),对应分辨率 32×32、64×64、128×128 到 256×256)。我们以 ( s = 1 s = 1 s=1 )(第二阶段,目标分辨率 64×64)为例:
起始状态:
终止状态:
训练:模型学习从起始状态(模糊的 64×64 近似加噪声)到终止状态(128×128 图像加少量噪声)的速度场,逐步去噪并提升分辨率。
推理:从上一阶段(( s = 2 s = 2 s=2),32×32)的输出开始,上采样到 64×64,加入噪声作为起始状态,逐步去噪到 128×128。
PixelFlow的起始状态和终止状态公式设计的核心目标是实现高效的多尺度像素空间生成:
这种设计巧妙地平衡了计算效率和生成质量,使得PixelFlow在像素空间生成高分辨率图像时既高效又具有竞争力。对于熟悉流匹配的研究者来说,PixelFlow的多尺度策略展示了流匹配在复杂生成任务中的灵活应用,值得进一步探索其在其他模态(如视频或3D)中的潜力。
在例子中,PixelFlow的起始状态公式为:
x t 0 1 = t 0 1 ⋅ Up ( Down ( x 1 , 4 ) ) + ( 1 − t 0 1 ) ⋅ ϵ \mathbf{x}_{t_0^1} = t_0^1 \cdot \text{Up}(\text{Down}(\mathbf{x}_1, 4)) + (1 - t_0^1) \cdot \epsilon xt01=t01⋅Up(Down(x1,4))+(1−t01)⋅ϵ
其中:
然而,描述中说起始状态包含“模糊的64×64信息”,这可能会让人困惑:既然上采样到256×256,为什么还说是64×64的信息?答案在于信息内容的实际分辨率与数据的维度之间的区别。
因此,起始状态 ( x t 0 1 \mathbf{x}_{t_0^1} xt01) 的维度是256×256,但其信息内容对应于64×64分辨率的图像。这就是为什么描述中称之为“模糊的64×64信息”。
PixelFlow的设计目标是端到端的像素空间生成,所有阶段的输入和输出需要保持一致的维度(256×256),以便使用统一的模型参数处理多尺度生成任务。如果起始状态的维度是64×64,而终止状态的维度是128×128(或256×256),模型需要为不同分辨率设计不同的网络结构,这会破坏端到端训练的简洁性。
通过在上采样操作 ( Up \text{Up} Up) 将64×64图像恢复到256×256,PixelFlow确保:
“64×64信息”的描述强调的是起始状态中目标图像部分的有效分辨率。具体来说:
这种描述是为了突出PixelFlow的多尺度策略:在阶段 ( s = 1 s = 1 s=1 ),模型从较低分辨率(64×64)的模糊信息开始,逐步生成更高分辨率(128×128)的图像。
终止状态公式为:
x t 1 1 = t 1 1 ⋅ Down ( x 1 , 2 ) + ( 1 − t 1 1 ) ⋅ ϵ \mathbf{x}_{t_1^1} = t_1^1 \cdot \text{Down}(\mathbf{x}_1, 2) + (1 - t_1^1) \cdot \epsilon xt11=t11⋅Down(x1,2)+(1−t11)⋅ϵ
在训练中,模型学习从起始状态(模糊的64×64信息加大量噪声)到终止状态(128×128信息加少量噪声)的速度场,逐步去噪并提升分辨率。
让我们通过一个具体的例子进一步说明。以阶段 ( s = 1 s = 1 s=1 ),目标图像 ( x 1 \mathbf{x}_1 x1) 为256×256为例:
起始状态:
终止状态:
训练目标:模型学习从起始状态(模糊的64×64猫轮廓+大量噪声)到终止状态(清晰的128×128猫图像+少量噪声)的变换,逐步去噪并提升分辨率。
为什么256×256维度?:起始状态上采样到256×256是为了与终止状态和噪声 ( ϵ \epsilon ϵ) 的维度一致,确保模型在所有阶段处理相同形状的输入。这样,模型可以用统一的Transformer架构处理多尺度任务,而无需为每个阶段设计不同尺寸的网络。
统一维度便于端到端训练:
信息分辨率的分级生成:
流匹配的灵活性:
跨尺度平滑过渡:
在PixelFlow的阶段 ( s = 1 s = 1 s=1 ) 中,起始状态 ( x t 0 1 \mathbf{x}_{t_0^1} xt01) 的维度是256×256,但其目标图像部分(( Up ( Down ( x 1 , 4 ) ) \text{Up}(\text{Down}(\mathbf{x}_1, 4)) Up(Down(x1,4))))的信息内容对应于64×64分辨率的模糊表示,因此描述为“模糊的64×64信息”。上采样到256×256是为了保持与终止状态和噪声的维度一致,支持端到端训练和统一的模型架构。终止状态的128×128信息定义了该阶段的目标分辨率,模型通过流匹配学习从模糊低分辨率到更高分辨率的变换。
这种设计兼顾了计算效率(早期处理低分辨率信息)和生成质量(后期恢复高分辨率细节),是PixelFlow多尺度级联策略的核心。
问题聚焦于Figure 1中关于像素空间扩散模型(Pixel-based Diffusion Models, PDMs)的描述,具体是“Previous PDMs typically train two separate models: a diffusion model on low-resolution images and an upsampler for high-resolution synthesis”中提到的“两个模型”的含义。来详细解释这句话的背景、两个模型分别是什么,以及为什么需要这两个模型。
像素空间扩散模型(PDMs)直接在原始像素空间操作,而不像潜在空间扩散模型(LDMs)那样依赖变分自编码器(VAE)将图像压缩到潜在空间。由于像素空间的高维度(例如,256×256的RGB图像有 ( 256 × 256 × 3 = 196 , 608 256 \times 256 \times 3 = 196,608 256×256×3=196,608) 个像素值),直接在高分辨率图像上运行扩散模型需要处理大量的像素间相关性,导致计算成本极高。为了应对这一挑战,传统的PDMs通常采用级联(cascaded) 方法,将生成过程分解为多个阶段,分担计算负担。
Figure 1(b)描述的就是这种传统PDMs的级联范式,指出它们通常需要训练两个独立的模型来完成从低分辨率到高分辨率的图像生成。以下是这两个模型的具体含义。
根据Figure 1(b)和论文上下文,传统PDMs的两个模型分别是:
低分辨率扩散模型(Diffusion Model on Low-Resolution Images):
上采样器(Upsampler for High-Resolution Synthesis):
传统PDMs采用两个模型的原因主要与像素空间生成高分辨率图像的计算复杂性和生成质量的权衡有关:
计算成本的限制:
生成过程的分阶段优化:
历史方法的局限性:
论文强调“train two separate models”,指的是低分辨率扩散模型和上采样器是独立训练的,具有以下特点:
独立的网络结构:
独立的训练过程:
缺乏端到端优化:
Figure 1(b)与1( c)的对比突出了传统PDMs和PixelFlow的区别:
传统PDMs(Figure 1(b)):
PixelFlow(Figure 1( c)):
PixelFlow的关键创新在于,它通过流匹配算法和多尺度策略,消除了对独立上采样器的需求。模型在训练时统一处理所有分辨率样本(通过下采样和上采样构造多尺度表示),在推理时从低分辨率噪声逐步生成高分辨率图像,避免了传统PDMs的分离训练问题。
假设目标是生成256×256的图像,传统PDMs的流程如下:
低分辨率扩散模型:
上采样器:
这两个模型是独立的,训练时需要分别准备低分辨率和高分辨率数据集,且上采样器的性能依赖于低分辨率模型的输出质量。如果低分辨率图像有瑕疵(例如,物体布局错误),上采样器可能无法完全纠正。
相比之下,PixelFlow使用单一模型,通过多尺度流匹配直接从32×32噪声逐步生成256×256图像,所有阶段共享参数,优化过程更连贯。
Figure 1(b)中提到的“两个模型”指的是传统PDMs中的低分辨率扩散模型和上采样器:
PixelFlow通过端到端的流匹配策略,消除了对分离模型的依赖,简化了训练流程并提高了生成质量。这一对比凸显了PixelFlow在像素空间生成高分辨率图像时的创新性和优势。
问题涉及到PixelFlow中多尺度生成过程中分辨率信息与实际处理维度的区别,以及Figure 2中从 ( t 0 t_0 t0 ) 到 ( t 1 t_1 t1 ) 的操作如何体现这一点。我们结合之前的讨论和Figure 2的描述,逐步分析PixelFlow的生成过程,解答“每一步处理的大小是否一样(例如都是256×256)”以及图中 ( t 0 t_0 t0 ) 到 ( t 1 t_1 t1 ) 的具体含义。
PixelFlow通过级联流匹配(cascade flow matching)在像素空间生成高分辨率图像,将生成过程分为多个分辨率阶段(stages)。每个阶段从低分辨率信息开始,逐步过渡到高分辨率信息。为了实现端到端训练,PixelFlow确保所有阶段的输入和输出维度一致(例如,始终是256×256),但每个阶段处理的信息分辨率(即图像内容的实际分辨率)是不同的。
我们之前讨论过,在每个阶段 ( s s s ),PixelFlow定义了起始状态和终止状态:
其中:
关键点是:所有阶段的输入和输出维度是固定的(例如256×256),但信息分辨率(即图像内容的实际分辨率)随阶段变化。
Figure 2 展示了PixelFlow的级联图像生成过程,描述为:“We partition the entire generation procedure into series resolution stages. At the beginning of each resolution stage, we upscale the relatively noisy results from the preceding stage and use them as the starting point for the current stage. Consequently, as the resolution enhances, more refined samples can be obtained.”
图中从 ( t = 0 t=0 t=0 ) 到 ( t = 1 t=1 t=1 ) 表示整个生成过程,分为多个阶段(例如 ( t 0 t_0 t0 ) 到 ( t 1 t_1 t1 ) 是一个阶段)。图中还展示了不同阶段的中间结果,从低分辨率(左侧较小的图像)到高分辨率(右侧较大的图像),最终生成清晰的图像(例如一朵花)。
是的,PixelFlow在每个阶段处理的维度是固定的,例如始终是256×256。这是因为:
虽然维度固定为256×256,但每个阶段的信息分辨率(即图像内容的实际分辨率)是不同的:
图中提到“At the beginning of each resolution stage, we upscale the relatively noisy results from the preceding stage”,这里的“upscale”(上采样)有两层含义:
例如:
假设目标分辨率为256×256,PixelFlow分为3个阶段(( S = 3 S=3 S=3 ),对应阶段 ( s = 2 , 1 , 0 s=2, 1, 0 s=2,1,0 )),信息分辨率分别为:
我们以阶段 ( s = 1 s=1 s=1 )(即图中的 ( t 0 t_0 t0 ) 到 ( t 1 t_1 t1 ))为例:
起始状态(( t 0 1 t_0^1 t01 )):
终止状态(( t 1 1 t_1^1 t11 )):
从 ( t 0 1 t_0^1 t01 ) 到 ( t 1 1 t_1^1 t11 ):
图中的可视化:
PixelFlow设计每一步处理维度相同(例如256×256)的原因是:
端到端训练:
Transformer架构的需求:
流匹配的实现:
Figure 2的可视化展示了信息分辨率的逐步提升(从模糊到清晰),但实际处理的维度始终保持一致(256×256)。这种设计兼顾了计算效率(早期处理低分辨率信息)和生成质量(后期恢复高分辨率细节),是PixelFlow多尺度策略的核心。
将基于PixelFlow论文的描述,忠实复现其训练代码。论文中详细描述了PixelFlow的训练流程,包括多尺度生成、流匹配(Flow Matching)算法、模型架构(基于Transformer的DiT)、训练设置等。将使用Python和PyTorch实现,假设读者有充足的GPU资源,因此不会优化计算开销,而是尽可能贴近原文的实现细节。
多尺度生成:
模型架构:
训练设置:
文本条件生成:
以下是训练代码的实现,重点复现论文的核心部分。
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
import numpy as np
from transformers import T5Tokenizer, T5EncoderModel
import math
# 超参数
IMG_SIZE = 256 # 目标分辨率 256x256
NUM_STAGES = 3 # 分成3个阶段 (s=0,1,2)
PATCH_SIZE = 4 # 补丁大小 4x4 (论文默认值)
KICKOFF_RES = 8 # 起始分辨率 8x8 (论文默认值)
BATCH_SIZE = 64
LEARNING_RATE = 1e-4
NUM_EPOCHS = 100
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 计算阶段的分辨率
STAGE_RESOLUTIONS = [IMG_SIZE // (2**s) for s in range(NUM_STAGES)][::-1] # [32, 64, 128]
T0 = [0.0, 0.1, 0.2] # 每个阶段的起始时间步
T1 = [0.9, 0.95, 0.99] # 每个阶段的终止时间步
# 数据加载:ImageNet数据集
transform = transforms.Compose([
transforms.Resize((IMG_SIZE, IMG_SIZE)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
])
dataset = datasets.ImageNet(root='./data', split='train', transform=transform)
dataloader = DataLoader(dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=4)
# 文本嵌入:使用Flan-T5-XL
tokenizer = T5Tokenizer.from_pretrained("google/flan-t5-xl")
text_encoder = T5EncoderModel.from_pretrained("google/flan-t5-xl").to(DEVICE)
# 2D-RoPE 实现
def get_2d_rotary_pos_embed(height, width, dim):
def get_1d_rotary_pos_embed(length, dim_half):
theta = 10000 ** (-2 * torch.arange(dim_half) / dim_half)
pos = torch.arange(length).unsqueeze(1) * theta.unsqueeze(0)
sin_pos = torch.sin(pos)
cos_pos = torch.cos(pos)
return torch.stack([cos_pos, sin_pos], dim=-1)
h_embed = get_1d_rotary_pos_embed(height, dim // 2)
w_embed = get_1d_rotary_pos_embed(width, dim // 2)
h_cos, h_sin = h_embed[..., 0], h_embed[..., 1]
w_cos, w_sin = w_embed[..., 0], w_embed[..., 1]
cos = torch.cat([h_cos.repeat(1, width, 1), w_cos.repeat(height, 1, 1)], dim=-1)
sin = torch.cat([h_sin.repeat(1, width, 1), w_sin.repeat(height, 1, 1)], dim=-1)
return cos, sin
# 分辨率嵌入
def get_resolution_embed(resolution, dim):
freqs = 10000 ** (-2 * torch.arange(dim // 2) / (dim // 2))
res_embed = torch.ones(1) * resolution
res_embed = res_embed.unsqueeze(-1) * freqs.unsqueeze(0)
return torch.cat([torch.sin(res_embed), torch.cos(res_embed)], dim=-1)
# DiT模型(简化版,基于Transformer)
class DiTBlock(nn.Module):
def __init__(self, dim, num_heads, text_dim):
super().__init__()
self.self_attn = nn.MultiheadAttention(dim, num_heads)
self.cross_attn = nn.MultiheadAttention(dim, num_heads)
self.ffn = nn.Sequential(
nn.Linear(dim, dim * 4),
nn.GELU(),
nn.Linear(dim * 4, dim)
)
self.ln1 = nn.LayerNorm(dim)
self.ln2 = nn.LayerNorm(dim)
self.ln3 = nn.LayerNorm(dim)
self.text_dim = text_dim
self.text_proj = nn.Linear(text_dim, dim)
def forward(self, x, text_embed, pos_cos, pos_sin):
# RoPE应用
q = k = x
q = q * pos_cos + torch.cross(q, pos_sin, dim=-1)
k = k * pos_cos + torch.cross(k, pos_sin, dim=-1)
# 自注意力
x = self.ln1(x)
x = x + self.self_attn(q, k, x)[0]
# 交叉注意力
x = self.ln2(x)
text_embed = self.text_proj(text_embed)
x = x + self.cross_attn(x, text_embed, text_embed)[0]
# FFN
x = self.ln3(x)
x = x + self.ffn(x)
return x
class PixelFlowModel(nn.Module):
def __init__(self, dim=768, num_heads=12, num_layers=28, text_dim=2048):
super().__init__()
self.dim = dim
self.patch_embed = nn.Conv2d(3, dim, kernel_size=PATCH_SIZE, stride=PATCH_SIZE)
self.time_embed = nn.Sequential(
nn.Linear(64, dim),
nn.GELU(),
nn.Linear(dim, dim)
)
self.res_embed = nn.Linear(64, dim)
self.blocks = nn.ModuleList([
DiTBlock(dim, num_heads, text_dim) for _ in range(num_layers)
])
self.output = nn.Linear(dim, 3 * PATCH_SIZE * PATCH_SIZE)
def forward(self, x, t, resolution, text_embed):
# 补丁嵌入
B, C, H, W = x.shape
x = self.patch_embed(x) # [B, dim, H/P, W/P]
x = x.flatten(2).transpose(1, 2) # [B, (H/P)*(W/P), dim]
# 2D-RoPE
pos_cos, pos_sin = get_2d_rotary_pos_embed(H // PATCH_SIZE, W // PATCH_SIZE, self.dim)
pos_cos = pos_cos.view(-1, self.dim).to(x.device)
pos_sin = pos_sin.view(-1, self.dim).to(x.device)
# 时间嵌入
t_embed = torch.sin(10000 ** (-2 * torch.arange(32) / 32)).to(x.device)
t = t.unsqueeze(-1) * t_embed.unsqueeze(0)
t_embed = self.time_embed(t)
# 分辨率嵌入
res_embed = get_resolution_embed(resolution, 64).to(x.device)
res_embed = self.res_embed(res_embed)
t_embed = t_embed + res_embed
# 添加时间嵌入
x = x + t_embed.unsqueeze(1)
# Transformer块
for block in self.blocks:
x = block(x, text_embed, pos_cos, pos_sin)
# 输出
x = self.output(x) # [B, (H/P)*(W/P), 3*P*P]
x = x.view(B, H // PATCH_SIZE, W // PATCH_SIZE, 3, PATCH_SIZE, PATCH_SIZE)
x = x.permute(0, 3, 1, 4, 2, 5).reshape(B, 3, H, W)
return x
# 多尺度样本构造
def create_multiscale_samples(x, stage):
B, C, H, W = x.shape
# 起始状态
down_res_start = H // (2 ** (stage + 1)) # 例如 s=1 时,256/4 = 64
x_down_start = F.interpolate(x, size=(down_res_start, down_res_start), mode='bilinear', align_corners=False)
x_start = F.interpolate(x_down_start, size=(H, W), mode='nearest')
# 终止状态
down_res_end = H // (2 ** stage) # 例如 s=1 时,256/2 = 128
x_end = F.interpolate(x, size=(down_res_end, down_res_end), mode='bilinear', align_corners=False)
x_end = F.interpolate(x_end, size=(H, W), mode='nearest')
# 时间步
t0, t1 = T0[stage], T1[stage]
t = torch.rand(B, device=x.device) * (t1 - t0) + t0
# 噪声
epsilon = torch.randn_like(x)
# 起始和终止状态
x_t0 = t0 * x_start + (1 - t0) * epsilon
x_t1 = t1 * x_end + (1 - t1) * epsilon
# 插值生成中间样本
tau = (t - t0) / (t1 - t0)
x_t = tau.view(-1, 1, 1, 1) * x_t1 + (1 - tau).view(-1, 1, 1, 1) * x_t0
# 目标速度
v_t = x_t1 - x_t0
return x_t, t, v_t, down_res_end # down_res_end作为分辨率条件
# 训练循环
model = PixelFlowModel().to(DEVICE)
optimizer = optim.AdamW(model.parameters(), lr=LEARNING_RATE)
criterion = nn.MSELoss()
for epoch in range(NUM_EPOCHS):
for batch_idx, (images, labels) in enumerate(dataloader):
images = images.to(DEVICE)
B = images.shape[0]
# 随机选择阶段
stages = torch.randint(0, NUM_STAGES, (B,), device=DEVICE)
# 构造多尺度样本
x_t_list, t_list, v_t_list, res_list = [], [], [], []
for stage in range(NUM_STAGES):
mask = (stages == stage)
if mask.sum() == 0:
continue
x_t, t, v_t, res = create_multiscale_samples(images[mask], stage)
x_t_list.append(x_t)
t_list.append(t)
v_t_list.append(v_t)
res_list.append(torch.ones_like(t) * res)
x_t = torch.cat(x_t_list, dim=0)
t = torch.cat(t_list, dim=0)
v_t = torch.cat(v_t_list, dim=0)
resolution = torch.cat(res_list, dim=0)
# 文本嵌入(简化:使用标签作为文本)
text_inputs = tokenizer(labels, return_tensors="pt", padding=True, truncation=True).to(DEVICE)
with torch.no_grad():
text_embed = text_encoder(**text_inputs).last_hidden_state
# 前向传播
optimizer.zero_grad()
pred_v = model(x_t, t, resolution, text_embed)
loss = criterion(pred_v, v_t)
# 反向传播
loss.backward()
optimizer.step()
if batch_idx % 100 == 0:
print(f"Epoch {epoch+1}/{NUM_EPOCHS}, Batch {batch_idx}, Loss: {loss.item():.4f}")
# 保存模型
torch.save(model.state_dict(), "pixelflow_model.pth")
多尺度样本构造:
create_multiscale_samples
实现了论文中的多尺度样本构造过程,使用双线性插值下采样和最近邻上采样。模型架构:
PixelFlowModel
基于DiT,包含补丁嵌入、2D-RoPE、分辨率嵌入和交叉注意力。DiTBlock
实现了自注意力、交叉注意力和FFN,遵循论文描述。训练设置:
文本条件:
BATCH_SIZE
)和模型参数(dim
、num_layers
)。./data
目录。这套代码忠实复现了PixelFlow论文的训练流程,适合有充足计算资源的研究者使用。
将基于之前的PixelFlow训练代码,编写推理代码以实现文本到图像生成(Text-to-Image Generation)。推理过程将遵循论文中的描述,使用训练好的PixelFlow模型,通过级联流匹配(cascade flow matching)从高斯噪声逐步生成高分辨率图像,同时支持文本条件生成。代码将使用PyTorch实现,并确保与训练代码一致。
推理过程:
文本条件:
多尺度生成:
分类器无关引导(CFG):
以下是推理代码的实现。
import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision.utils import save_image
from transformers import T5Tokenizer, T5EncoderModel
import numpy as np
# 超参数(与训练代码保持一致)
IMG_SIZE = 256 # 目标分辨率 256x256
NUM_STAGES = 3 # 分成3个阶段 (s=0,1,2)
PATCH_SIZE = 4 # 补丁大小 4x4
BATCH_SIZE = 1 # 推理时批次大小为1
NUM_STEPS = 30 # 每个阶段的采样步数
CFG_SCALE_MIN = 1.0 # CFG 起始尺度
CFG_SCALE_MAX = 2.40 # CFG 最大尺度
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 每个阶段的 t0 和 t1(与训练代码一致)
T0 = [0.0, 0.1, 0.2] # 每个阶段的起始时间步
T1 = [0.9, 0.95, 0.99] # 每个阶段的终止时间步
# 加载文本嵌入模型
tokenizer = T5Tokenizer.from_pretrained("google/flan-t5-xl")
text_encoder = T5EncoderModel.from_pretrained("google/flan-t5-xl").to(DEVICE)
# 2D-RoPE 实现(与训练代码一致)
def get_2d_rotary_pos_embed(height, width, dim):
def get_1d_rotary_pos_embed(length, dim_half):
theta = 10000 ** (-2 * torch.arange(dim_half) / dim_half)
pos = torch.arange(length).unsqueeze(1) * theta.unsqueeze(0)
sin_pos = torch.sin(pos)
cos_pos = torch.cos(pos)
return torch.stack([cos_pos, sin_pos], dim=-1)
h_embed = get_1d_rotary_pos_embed(height, dim // 2)
w_embed = get_1d_rotary_pos_embed(width, dim // 2)
h_cos, h_sin = h_embed[..., 0], h_embed[..., 1]
w_cos, w_sin = w_embed[..., 0], w_embed[..., 1]
cos = torch.cat([h_cos.repeat(1, width, 1), w_cos.repeat(height, 1, 1)], dim=-1)
sin = torch.cat([h_sin.repeat(1, width, 1), w_sin.repeat(height, 1, 1)], dim=-1)
return cos, sin
# 分辨率嵌入(与训练代码一致)
def get_resolution_embed(resolution, dim):
freqs = 10000 ** (-2 * torch.arange(dim // 2) / (dim // 2))
res_embed = torch.ones(1) * resolution
res_embed = res_embed.unsqueeze(-1) * freqs.unsqueeze(0)
return torch.cat([torch.sin(res_embed), torch.cos(res_embed)], dim=-1)
# DiT模型(与训练代码一致)
class DiTBlock(nn.Module):
def __init__(self, dim, num_heads, text_dim):
super().__init__()
self.self_attn = nn.MultiheadAttention(dim, num_heads)
self.cross_attn = nn.MultiheadAttention(dim, num_heads)
self.ffn = nn.Sequential(
nn.Linear(dim, dim * 4),
nn.GELU(),
nn.Linear(dim * 4, dim)
)
self.ln1 = nn.LayerNorm(dim)
self.ln2 = nn.LayerNorm(dim)
self.ln3 = nn.LayerNorm(dim)
self.text_dim = text_dim
self.text_proj = nn.Linear(text_dim, dim)
def forward(self, x, text_embed, pos_cos, pos_sin):
q = k = x
q = q * pos_cos + torch.cross(q, pos_sin, dim=-1)
k = k * pos_cos + torch.cross(k, pos_sin, dim=-1)
x = self.ln1(x)
x = x + self.self_attn(q, k, x)[0]
x = self.ln2(x)
text_embed = self.text_proj(text_embed)
x = x + self.cross_attn(x, text_embed, text_embed)[0]
x = self.ln3(x)
x = x + self.ffn(x)
return x
class PixelFlowModel(nn.Module):
def __init__(self, dim=768, num_heads=12, num_layers=28, text_dim=2048):
super().__init__()
self.dim = dim
self.patch_embed = nn.Conv2d(3, dim, kernel_size=PATCH_SIZE, stride=PATCH_SIZE)
self.time_embed = nn.Sequential(
nn.Linear(64, dim),
nn.GELU(),
nn.Linear(dim, dim)
)
self.res_embed = nn.Linear(64, dim)
self.blocks = nn.ModuleList([
DiTBlock(dim, num_heads, text_dim) for _ in range(num_layers)
])
self.output = nn.Linear(dim, 3 * PATCH_SIZE * PATCH_SIZE)
def forward(self, x, t, resolution, text_embed):
B, C, H, W = x.shape
x = self.patch_embed(x)
x = x.flatten(2).transpose(1, 2)
pos_cos, pos_sin = get_2d_rotary_pos_embed(H // PATCH_SIZE, W // PATCH_SIZE, self.dim)
pos_cos = pos_cos.view(-1, self.dim).to(x.device)
pos_sin = pos_sin.view(-1, self.dim).to(x.device)
t_embed = torch.sin(10000 ** (-2 * torch.arange(32) / 32)).to(x.device)
t = t.unsqueeze(-1) * t_embed.unsqueeze(0)
t_embed = self.time_embed(t)
res_embed = get_resolution_embed(resolution, 64).to(x.device)
res_embed = self.res_embed(res_embed)
t_embed = t_embed + res_embed
x = x + t_embed.unsqueeze(1)
for block in self.blocks:
x = block(x, text_embed, pos_cos, pos_sin)
x = self.output(x)
x = x.view(B, H // PATCH_SIZE, W // PATCH_SIZE, 3, PATCH_SIZE, PATCH_SIZE)
x = x.permute(0, 3, 1, 4, 2, 5).reshape(B, 3, H, W)
return x
# 推理函数
def inference(text_prompt, model_path="pixelflow_model.pth"):
# 加载模型
model = PixelFlowModel().to(DEVICE)
model.load_state_dict(torch.load(model_path))
model.eval()
# 文本嵌入
text_inputs = tokenizer([text_prompt], return_tensors="pt", padding=True, truncation=True).to(DEVICE)
with torch.no_grad():
text_embed = text_encoder(**text_inputs).last_hidden_state
null_text_inputs = tokenizer([""], return_tensors="pt", padding=True, truncation=True).to(DEVICE)
with torch.no_grad():
null_text_embed = text_encoder(**null_text_inputs).last_hidden_state
# 初始化噪声(最低分辨率信息)
x = torch.randn(BATCH_SIZE, 3, IMG_SIZE, IMG_SIZE, device=DEVICE) # 维度始终为 256x256
current_res = IMG_SIZE // (2 ** NUM_STAGES) # 起始信息分辨率,例如 256/8 = 32
# 逐阶段推理
for stage in range(NUM_STAGES - 1, -1, -1): # 从 s=2 到 s=0
t0, t1 = T0[stage], T1[stage]
target_res = IMG_SIZE // (2 ** stage) # 目标信息分辨率,例如 s=1 时 128
print(f"Stage {stage}: from info resolution {current_res} to {target_res}")
# CFG 尺度随阶段递增
cfg_scale = CFG_SCALE_MIN + (CFG_SCALE_MAX - CFG_SCALE_MIN) * (NUM_STAGES - 1 - stage) / (NUM_STAGES - 1)
# Euler 采样
for step in range(NUM_STEPS):
t = t0 + (t1 - t0) * (step / NUM_STEPS)
t_tensor = torch.full((BATCH_SIZE,), t, device=DEVICE)
with torch.no_grad():
# 有条件预测
pred_cond = model(x, t_tensor, target_res, text_embed)
# 无条件预测
pred_uncond = model(x, t_tensor, target_res, null_text_embed)
# CFG
pred = pred_uncond + cfg_scale * (pred_cond - pred_uncond)
# Euler 更新
dt = (t1 - t0) / NUM_STEPS
x = x + pred * dt
# 更新当前信息分辨率
current_res = target_res
# 归一化并保存图像
x = (x.clamp(-1, 1) + 1) / 2 # 归一化到 [0, 1]
save_image(x, "generated_image.png")
print("Image generated and saved as 'generated_image.png'")
# 测试推理
if __name__ == "__main__":
text_prompt = "A vibrant flower in a sunny garden"
inference(text_prompt)
模型加载:
pixelflow_model.pth
),确保与训练代码中的模型架构一致。PixelFlowModel
和 DiTBlock
,与训练代码完全相同。文本条件:
推理过程:
stage
从 2 到 0)逐步去噪:
NUM_STEPS = 30
)。输出:
generated_image.png
。pixelflow_model.pth
存在(通过训练代码生成)。generated_image.png
。BATCH_SIZE
或 NUM_STEPS
。CFG_SCALE_MAX
。这套推理代码忠实复现了 PixelFlow 的文本到图像生成过程,适合研究和实验。
2025年4月17日于上海,在grok 3大模型辅助下完成。