Hello!
Welcome to a new module. Over the past few lessons, we've built a robust toolkit for generating and editing static 2D images. We've tackled everything from high-resolution synthesis with latent diffusion to fine-grained content preservation using inversion and ControlNet. Now, we're ready to tackle the fourth dimension: time.
This lesson marks our first step into video generation. Our goal is to understand the fundamental architectural shift required to adapt a 2D diffusion model for video. Specifically, your learning outcome is to implement temporal convolutional layers to extend a 2D diffusion model to video. We will explore how to "inflate" a 2D architecture, like a U-Net, with the ability to process sequences of frames, focusing on the convolutional and attention-based layers that make this possible.
From Spatial to Spatiotemporal: The Challenge of Video
A video isn't just a collection of independent images. The primary challenge in video generation is maintaining temporal consistency—ensuring that objects, scenes, and motions evolve coherently from one frame to the next.
A naive approach of generating frames independently would result in a flickering, disjointed mess. The model must understand the relationships between frames. The prevailing strategy to achieve this, and the one we'll focus on, is to adapt powerful, pre-trained 2D image models by inserting new layers designed to operate along the time axis.
Let's begin by reading a brief overview of how 2D U-Nets are commonly extended for video modeling.
Diffusion Models for Video Generation
This post by Lilian Weng, 'Diffusion Models for Video Generation', provides an excellent survey of the field. We'll start with the section on model architecture to understand the core principles of adapting 2D U-Nets.
Please read the section 'Model Architecture: 3D U-Net & DiT'. Focus on how the Video Diffusion Model (VDM) paper factorizes a 3D U-Net's operations over space and time. Note the distinction between space-only convolutions (1x3x3) and the introduction of temporal attention blocks.
As you've just read, the key idea is to factorize operations. Instead of using a full 3D convolution that processes space and time simultaneously, which can be computationally expensive and hard to train, many models separate the operations:
- Spatial Processing: Apply layers that operate on each frame individually, just like in an image model.
- Temporal Processing: Insert new layers that operate across frames, sharing information along the time axis.
Let's look at the two primary mechanisms for this temporal processing.
The Building Blocks: Temporal Convolutions and Attention
To give a 2D architecture a sense of time, we need to add layers that explicitly mix information between frames.
1. "Pseudo-3D" Convolutional Layers
This is the core concept for this lesson's learning outcome. Instead of replacing 2D convolutions with full 3D convolutions, we augment them. A "pseudo-3D" convolution block typically consists of:
- A standard 2D convolution (e.g.,
Conv2d) that is applied to each frame independently. This captures spatial features within each frame. - A 1D convolution (e.g.,
Conv1d) that is then applied across the temporal dimension. For a given pixel or feature location, this convolution takes inputs from the same location in adjacent frames, effectively mixing information over time.
This factorization is computationally cheaper than a full 3D convolution and allows for the use of powerful pre-trained 2D convolutional layers.

The article from Lilian Weng describes this approach as used in the Make-A-Video model.
Diffusion Models for Video Generation
Let's revisit the 'Diffusion Models for Video Generation' article to see a concrete description of these pseudo-3D layers.
Read the sections 'Adapting Image Models to Generate Videos' and the subsection 'Fine-tuning on Video Data', focusing specifically on the description of 'Spatiotemporal SR layers' in Make-A-Video. This will give you a formal definition of the pseudo-3D convolution and attention layers.
2. Temporal Attention Layers
Complementary to convolutions, temporal attention is crucial for capturing long-range dependencies in time. The logic is similar to pseudo-3D convolutions:
- Spatial Attention: A standard self-attention mechanism is applied to the patches within each frame. During this step, the frames are typically treated as part of the batch dimension.
- Temporal Attention: A second self-attention mechanism is applied across the frames. Here, for each spatial patch position, the model attends to the corresponding patches at all other timesteps. This allows the model to learn motion and maintain object identity over the duration of the clip.
The right side of the "2D and 1D Temporal Convolutions" image above shows a block diagram of how spatial and temporal attention can be combined.
From Theory to Practice: Implementation Strategies
Now, let's look at how these temporal layers are integrated into a working model. We'll explore two common approaches, both of which you could be called upon to implement as an ML engineer.
Strategy 1: Interleaving Spatial and Temporal Layers in a Transformer
One of the most effective strategies is to take a 2D model architecture and insert temporal blocks after the existing spatial blocks. This is the approach taken by many modern video transformers, which adapt architectures like the Diffusion Transformer (DiT).
The key implementation detail is not a new type of layer, but a clever manipulation of the tensor shapes to reuse standard attention modules. The following video provides an excellent, code-oriented walkthrough.
Video Generation with Diffusion Transformers | Generative AI
The video 'Video Generation with Diffusion Transformers' by ExplainingAI provides a fantastic walkthrough of adapting a 2D DiT for video. It clearly demonstrates the tensor reshaping needed to implement separate spatial and temporal attention.
Please watch these two key sections: Spatial and Temporal Modeling (08:43 - 12:25): Pay close attention to how the input tensor (batch, frames, tokens, dim) is reshaped differently for spatial versus temporal attention. This is the core trick. Forward Method Implementation (36:40 - 44:40): This is a walkthrough of the forward pass. Focus on the loop over the transformer layers. Notice how the code separates video and image tokens, adds temporal positional embeddings, and alternates between calling a spatial layer and a temporal layer, with the necessary reshaping in between.
To summarize the key implementation pattern from the video:
Assume your input tensor x has the shape (batch_size, num_frames, num_patches, hidden_dim).
-
For a Spatial Layer (Attention/Conv2D): You want to operate on each frame independently. You can merge the first two dimensions.
x_spatial = x.reshape(batch_size * num_frames, num_patches, hidden_dim)output_spatial = spatial_layer(x_spatial)output = output_spatial.reshape(batch_size, num_frames, num_patches, hidden_dim)
-
For a Temporal Layer (Attention/Conv1D): You want to operate across frames for each patch. You can permute the dimensions to make the
num_framesdimension the one the layer operates on.x_temporal = x.permute(0, 2, 1, 3).reshape(batch_size * num_patches, num_frames, hidden_dim)output_temporal = temporal_layer(x_temporal)output = output_temporal.reshape(batch_size, num_patches, num_frames, hidden_dim).permute(0, 2, 1, 3)
This pattern of reshape -> apply layer -> reshape is a highly efficient way to extend 2D models for spatiotemporal tasks.
Strategy 2: Building a Native 3D U-Net
An alternative is to build the U-Net with 3D operations from the ground up, using PyTorch's nn.Conv3d layers. This approach directly builds a network that expects 5D tensors (B, C, F, H, W).
The following code repository implements a video generation model using a full 3D U-Net.
FareedKhan-dev/text2video-from-scratch
The GitHub repository 'text2video-from-scratch' by FareedKhan-dev contains a clean implementation of a 3D U-Net for diffusion-based video generation. We'll look at the model definition.
In the file text_to_video_synthesis/main.py, find the Unet3D class definition. You don't need to understand every line, but observe the overall structure: Notice the use of nn.Conv3d in the init_conv layer. Look at the downsampling path (self.downs). It contains blocks that are followed by SpatialLinearAttention and temporal_attn. This demonstrates an architecture where 3D convolutions and factorized attention mechanisms are combined within the same network blocks.
This 3D U-Net approach is more direct, but it doesn't allow for easily leveraging pre-trained weights from a 2D image model, which is a major advantage of the "interleaving" strategy. In practice, many state-of-the-art models use a hybrid, often inserting 1D temporal convolutions and attention layers into a pre-trained 2D U-Net, effectively combining the best of both strategies.
Test your understanding!
You are given a standard 2D U-Net block from a pre-trained image diffusion model. The block's forward method takes a 4D tensor x of shape (B, C, H, W).
class ResnetBlock2D(nn.Module):
def forward(self, x, time_emb):
h = self.norm1(x)
h = self.act1(h)
h = self.conv1(h)
# ... more layers ...
h = self.conv2(h)
return h + x
Your task is to extend this block for video. Your input is now a 5D tensor x_video of shape (B, C, F, H, W).
In pseudocode or high-level steps, describe how you would modify the forward pass to implement a pseudo-3D convolution, where you first apply the existing conv1 spatially and then add a new temporal_conv1 (nn.Conv1d) that operates across the frame dimension F.
Show answer
Here is a possible sequence of steps:
-
Reshape for Spatial Convolution: To use the existing 2D block, you first need to merge the batch
Band frameFdimensions of the input tensor.B, C, F, H, W = x_video.shapex_spatial = x_video.permute(0, 2, 1, 3, 4).reshape(B * F, C, H, W)
-
Apply 2D Layers: Pass the reshaped tensor through the existing 2D layers.
h_spatial = self.norm1(x_spatial)h_spatial = self.act1(h_spatial)h_spatial = self.conv1(h_spatial)
-
Reshape for Temporal Convolution: To apply a 1D convolution along the time axis, you need to reshape the tensor so that the frame dimension is accessible. The
Conv1dlayer expects an input of shape(Batch, Channels, Length). In our case, the "length" is the number of framesF. The "channels" will be the feature dimension from the 2D convolution, and the "batch" will be every other dimension flattened._, C_out, H_out, W_out = h_spatial.shapeh_temporal = h_spatial.reshape(B, F, C_out, H_out, W_out).permute(0, 2, 3, 4, 1)h_temporal = h_temporal.reshape(B * C_out * H_out * W_out, F)# Add a channel dimension for Conv1dh_temporal = h_temporal.unsqueeze(1)
-
Apply 1D Temporal Convolution:
h_temporal = self.temporal_conv1(h_temporal)
-
Reshape Back: Reshape the output back to its 5D spatiotemporal format to pass to the next layer.
h_final = h_temporal.squeeze(1).reshape(B, C_out, H_out, W_out, F).permute(0, 4, 1, 2, 3)
This sequence of permute and reshape operations is fundamental to implementing factorized spatiotemporal layers.
Conclusion
In this lesson, we took our first and most critical step into video generation. We established that extending 2D models is a powerful and common paradigm, and we investigated the primary architectural tools for doing so.
Key Takeaways:
- Video generation's main challenge is maintaining temporal consistency.
- We can "inflate" a 2D architecture by inserting layers that operate across the time dimension.
- Pseudo-3D convolutions factorize a 3D operation into a spatial
Conv2dfollowed by a temporalConv1d. - Temporal attention allows for capturing long-range relationships between frames.
- Implementation often relies on clever tensor reshaping and permuting to apply 2D or 1D layers along the correct dimensions of a 5D spatiotemporal tensor
(B, C, F, H, W).
Next Lesson Preview:
We have now added the basic anatomical parts for processing time to our model. However, the temporal consistency we get from these layers is implicit—learned by the model from data. In the next lesson, we will explore a more explicit method for enforcing coherence by implementing a frame-to-frame consistency technique that conditions the generation of each new frame on the frame that came immediately before it.