MIT 6.S184, Flow Matching and Diffusion Models
MIT 6.S184, Flow Matching and Diffusion Models.
represent the generated objects as vectors: .
data distribution: how likely are we to find this picture on the Internet?
how good an image is how likely it is under the data distribution
generation: sampling from the data distribution
data distribution : distribution of generated objects
unknown probability density : given , output probability of in data distr
dataset: finite number of samples from the data distribution
conditional generation allows us to condition on prompts
data distribution , fixed prompt, e.g., "dog"
conditional data distribution
a generative model converts samples from a initial distr (e.g., Gaussian) into samples from the data distr:
# Conditional Probability Paths
The basic premise of conditional flow matching: describe a conditional probability path , so that , and (e.g., a Gaussian), and interpolates continuously between and . Such a conditional path can be seen as corresponding to some corruption process which (in reverse time) drives the point at to be distribution as at time . Such a corruption process is given by the ODE:
The drift is referred to as the conditional vector field. By averaging over all such choices of , we obtain the marginal vector field . Flow matching proposes to exploit the fact that the marginal probability path generated by the marginal vector field , bridges to . Since the conditional vector field is often analytically available, we may implicitly regress against the unknown marginal vector field by explicitly regressing against the conditional vector field .
# Gaussian Conditional Probability Paths
A Gaussian conditional probability path is used to transform a simple source into a Gaussian mixture . Later, we'll experiment with more exciting distributions. Recall that a Gaussian conditional probability path is given by
where and are monotonic, continuously differentiable functions satisfying and . In other words, this implies that and is a unit Gaussian. Before we dive into things, let's look at and .
In this section, we'll use
It is not hard to check that both functions are continuously differentiable on , and monotonic, that , and that .
# Sample the Conditioning Variable
# Sample from the Conditional Distribution
Sample from the conditional distribution .
A random variable can be obtained via , where , so our sample is .
An image of the conditional probability path: from the central red cloud to the final red dot.
# Conditional Vector Field
A conditional flow model is defined as . Taking gives the conditional vector field :
Visualize the conditional trajectories corresponding to the ODE
# Conditional Score
Compute the conditional distr of , the conditional score of :
To check for correctness, verify that samples from the conditional SDE match the samples drawn analytically from the conditional probability path (left and middle plot are the same).
Note: You may notice that strange things happen for large (or even not-so-large) values of . Plugging in into
yields
When , , so that the second term of the drift explodes (and this explosion scales quadratically with ). With a finite number of simulation steps, we cannot accurately simulate this explosion and thus encounter numerical issues. In practice, this is usually circumvented by setting, e.g., , so that the exploding effect is canceled out by a gradually decreasing noise level.
Visualize the conditional trajectories corresponding to the SDE
obtained by adding Langevin dynamics to the original ODE.
# Flow Matching and Score Matching with Gaussian Conditional Probability Paths
# Flow Matching with Gaussian Conditional Probability Paths
Our goal is to learn the marginal vector field given by
Unfortunately, we don't actually know what is. We therefore approximate with a neural network , and exploit the identity
to obtain the conditional flow matching objective,
To model , we'll use a simple MLP. This network will take in both and , and will return the learned vector field . Using a Monte-Carlo estimate of the form
Here, is the batch size.
Note that sampling uses conditional probability and loss uses conditional vector field from before.
# Score Matching with Gaussian Conditional Probability Paths
We have thus far used flow matching to train a model so that
approximately passes through the desired marginal probability path . For a diffusion process, we augment the reference marginal vector field with Langevin dynamics to add stochasticity while preserving the marginals, viz.,
Substituting our learned approximation therefore yields
But what's the marginal score ? In conditional score, we computed the conditional score of the Gaussian probability path. In the same way that we learned an approximation , we'd like to be able to learn a similar approximation . Given the identity
It then immediately follows that
We thus obtain the conditional score matching loss
Here, we will parameterize as a simple MLP, just like .
After training, the loss should converge, but not to zero b/c and .
The diffusion process is defined as an SDE with Langevin dynamics:
# Deriving the Marginal Score from the Marginal Flow
For Gaussian probability path , can express vector field in terms of score:
where . Rearranging yields
Therefore, we may instead exploit the fact that we have already trained , to parameterize via
so long as (which is true for by monotonicity). Here, we differentiate paramterized via from learned indepedently using score matching. Plugging in and , we find that
In the following visualization, we'll circumvent the issue at by taking in place of , for small .
Now, let's compare our learned marginal score (using MLP) to our flow-parameterized score (score from VF). We'll do so by plotting the vector fields across time and space.
Note: The two score parameterizations will probably look a bit different, but should generally point in the same direction, especially around modes.
The two score fields would only match if both the learned flow and vector field match the true marginal score, but they are only approximations.
# Flow Matching Between Arbitrary Distributions with a Linear Probability Path
In this section, we consider an alternative conditional probability path, the linear conditional probability path, which can be constructed as follows. Given a source distribution and a data distribution , for a fixed we may consider the interpolant
where is a random variable. We may then define so that . Then and . It is also not difficult to show that the conditional vector field is given by
We make two observations about the linear conditional probability path:
- Unlike in the Gaussian probability path, we do not have a closed form for the conditional score .
- There is no constraint that be Gaussian, which we will exploit in from circles to checkerboard to construct flows between arbitrary choices of and .
First, let's visualize some more complicated choices of .
The following shows linear probability paths of conditional (GT), conditional vector field (ODE), marginal:
# Flow Matching with Linear Probability Paths
Train a flow matching model using the linear conditional probability path. The loss should converge, but not necessarily to zero.
# Bridging Between Arbitrary Source and Target
For the linear probability path, there is no need for to be a Gaussian. Set it to another distribution, e.g., circles:
From the output plots, the learned flow is able to transport samples from to for several choices of source and target distributions. The match is typically better when the source and target are geometrically similar, while more different pairs, such as circles to checkerboard, produce more distorted intermediate marginals and are harder to learn. This suggests that linear probability paths are very flexible, but difficult source-target pairs may require more training and a higher-capacity model.
guided: "Generate an image."
unguided: "Generate an image of a cat baking a cake."
vanilla guided sampling:
unguided vs. guided (condition on )
# Classifier Free Guidance
# Guidance
For unconditional generation, we simply wanted to generate any digit, we would now like to be able to specify, or condition, on the identity of the digit we would like to generate. That is, we would like to be able to say "generate an image of the digit 8", rather than just "generate an image of a digit". We will henceforth refer to the digit we would like to generate as , and the conditioning variable (in this case, a label), as . If we imagine fixing our choice of , and take our data distribution as , then we have recovered the unconditional generative problem, and we can construct a generative model using e.g., a conditional flow matching objective via
We may now then allow to vary by simply taking our conditional flow matching expectation to be over as well (rather than fixing ), and explicitly conditioning our learned approximation on on the choice of . We therefore obtain the the guided conditional flow matching objective
Note that is obtained in practice by sampling an image , and a label , from our labelled (MNIST) dataset. This is all well and good, and we emphasize that if our goal was simply to sample from , our job would be done (at least in theory). In practice, one might argue that we care more about the perceptual quality of our images. To this end, we will a derive a procedure known as classifier-free guidance.
# CFG-CDM: Classifier-Free Guidance
For the sake of intuition, we will develop guidance through the lense of Gaussian probability paths, although the final result might reasonably be applied to any probability path. Recall from the lecture that for , we have
This identity allows us to relate the conditional marginal velocity to the conditional score . However, notice that
so that we may rewrite
An approximation of the term could be considered as a sort of noisy classifier (and in fact this is the origin of classifier guidance, which we do not consider here). In practice, people have noticed that the conditioning seems to work better when we scale the contribution of this classifier term, yielding
where is known as the guidance scale. We may then plug in to obtain
The idea is thus to train both as well as the conditional model , and then combine them at inference time to obtain . Our recipe will thus be:
- Train as well as the conditional model using conditional flow matching.
- At inference time, sample using .
But why must we train two models? We can instead treat as , where denotes the absence of conditioning. We may thus augment our label set with a new, additional label, so that . This technique is known as classifier-free guidance (CFG). We thus arrive at
Training and CFG: We must now amend our conditional flow matching objective to account for the possibility of . Of course, when we sample from MNIST, we will never obtain , so we must introduce the possibliity of this artificially. To do so, we will define some hyperparameter to be the probability that we discard the original label , and replace it with . In practice, we might set , for example, as it is sufficient to distinguish it from the other digit identities. When we go and implement our model, we need ony be able to index into some embedding, such as via torch.nn.Embedding. We thus arrive at our CFG conditional flow matching training objective:
In plain English, this objective reads:
- Sample an image and a label from (here, MNIST).
- With probability , replace the label with the null label .
- Sample from .
- Sample from the conditional probability path .
- Regress against .
# Build a Diffusion Transformer
For images as input, MLP will no longer suffice. We will use a diffusion transformer.
# Fourier Time Encoder
First, we implement a Fourier time encoder, which maps a scalar time value to
where the weights are drawn from a unit normal.
# Patchifier
The patchifier takes in an image-valued tensor of shape b 1 32 32, and patchifies it to shape b (h / p * w / p) d, where d denotes the diffusion transformer hidden dimension, p denotes the patch size, and h and w denote the image height and width, respectively. It does this in two steps:
- Applies a convolutional layer mapping the an input of shape
b 1 32 32tob d h/p h/w. - Rearranges from
b d h/p h/wtob (h/p h/w) d(i.e.,n = h/p * h/wtokens with dimensiond).
# Diffusion Transformer
# Adaptive LayerNorm formula
For hidden state and condition , adaptive layernorm:
= scale
= bias/shift
= elementwise multiply
In DiT-style adaLN-Zero, there is also a residual gate :
where is either the attention block, or the feed-forward block.
Translated to code,
attn_scale, attn_bias, attn_gate, ff_scale, ff_bias, ff_gate = c.chunk(6, dim=-1)
x = x + attn_gate * self.attn(modulate(self.norm1(x), attn_scale, attn_bias))
x = x + ff_gate * self.ff(modulate(self.norm2(x), ff_scale, ff_bias))
2
3
4
with
# Scale, Bias, Gate
scale/bias: change the input to the branch
gate: change how much the branch contributes
Because the transformer layer has two different residual branches, there are scale, bias, gate for both attention and feed-forward:
- Attention branch
- Feed-forward branch
Each branch plays a different role, so conditioning them separately is useful.
Scale + Bias
These control how the condition changes the normalized activations before the sublayer.
- attention may need one type of conditioning
- feed-forward may need another
Sharing one modulation for both would be less flexible.
Gate: controls how much of that branch is added back through the residual.
- the model may want strong conditioning in attention but weak in MLP, or vice versa
- separate gates make the layer more expressive
- with zero init, gates start near 0, so the block initially behaves close to identity, which improves stability
# Depatchifier
After the diffusion transformer, we need to convert from b n d back to b 1 h w.
One approach would be to
- Start with some sort of normalization.
nn.LayerNormornn.RMSNormwil suffice. - Pass through some MLP to obtain
b (h/p w/p) (f p p)(ie, map to dimension , for patch size and "final dimension" . - Rearrange to
b f h w. - Pass through final convolution to obtain output of shape
b 1 h w.
# Putting It All Together
Finally, implement our DiT-based guided flow model .
- To embed the guiding input
y, you'll need class labels. You can reuse your solution fromMLPConditionalVectorField. In particular, we recommend usingnn.Embedding(num_classes=11, embedding_dim=dim). Note we use 11 - not 10 - classes to account for the presence of the null label (). - Follow the DiT overview diagram: embed and , add them together to obtain the guiding variable. Pass through the patchifier, and then pass the patchifier output and through the diffusion transformer. Finally, pass the DiT output through the depatchifier.
Visualize final result on MNIST:
