TAI BUI
Back to curriculum
BuildPython

Video Generation

An image is a 2-D tensor. A video is a 3-D one. The theory is the same; the compute is 10-100x harder. OpenAI's Sora (Feb 2024) showed it was possible. By 2026 Veo 2, Kling 1.5, Runway Gen-3, Pika 2.0, and WAN 2.2 ship production video from text at 1080p, and the open-weights stack (CogVideoX, HunyuanVideo, Mochi-1, WAN 2.2) is 12 months behind.

TIME: ~45 minutesPREREQUISITES: Phase 8 · 07 (Latent Diffusion), Phase 7 · 09 (ViT), Phase 8 · 06 (DDPM)

Learning Objectives

  • Explain why pixel-space diffusion is infeasible for video and how a 3D VAE plus patchify turns a clip into a token sequence.
  • Describe a spatiotemporal DiT: 3D position embeddings, factorized spatial and temporal attention, and text cross-attention.
  • Build a toy joint video denoiser in pure Python and compare its frame-to-frame deltas against independent per-frame sampling.
  • Choose a 2026 video model for a brief (hosted vs open weights, consistency, audio, image-to-video).
  • Estimate why video inference is memory-bandwidth bound and name the production knobs (tensor parallelism, frame batching, prefill cache).

The Problem

A 10-second 1080p video at 24fps is 240 frames of 1920×1080×3 pixels. That's ~1.5 GB of raw data per clip. Pixel-space diffusion is infeasible. You need:

  1. Spatiotemporal compression. A VAE that encodes videos, not frames, into a sequence of spatial-temporal patches.
  2. Temporal coherence. Frames need to share content, lighting, and object identity over seconds. The net has to model motion.
  3. Compute budget. Video training is 10-100x more expensive than image for the same model size.
  4. Conditioning. Text, image (first-frame), audio, or another video. Most production models accept all four.

The architecture that solved this is the Diffusion Transformer (DiT) applied to spatiotemporal patches, trained on huge (prompt, caption, video) datasets. Same diffusion loss as Lesson 06.

Warm-up0/3
1. Why does latent diffusion denoise a VAE latent instead of raw pixels?Warm-up
Left-to-right pipeline: image, VAE encoder, latent z, denoiser (repeated steps), VAE decoder. No tensor sizes are shown.
Schematic only. The diagram carries no sizes.
2. In DDPM training, what does the network typically learn to predict?Warm-up
A clean 1-D wave, the same wave after noise is added at step t, and a network box that takes the noisy wave and outputs a question mark.
Illustrative signals drawn by the script (seeded numpy), not captured from a model.
3. How does a Vision Transformer turn an image into input for its transformer?Warm-up
A 6 by 6 grid of coloured pixels with an arrow labelled 'turn into ?' to a transformer box and an input sequence of question marks.
Schematic; the pixel colours are arbitrary.
Check: The Problem0/2
1. Why is pixel-space diffusion infeasible for a 10-second 1080p video?Explain
A strip of video frames labelled 240 frames, a line stating one frame is 1920 x 1080 x 3 = 6,220,800 values, and a line asking for 240 x 6,220,800.
Counts follow from 10 s at 24 fps; the clip total is left open.
2. A 5-second clip at 1080p and 24 fps is stored as raw pixels at 1920×1080×3 values per frame. About how large is it?Apply
Three lines: 5 s x 24 fps = 120 frames; 1920 x 1080 x 3 = 6,220,800 values per frame; raw size of the clip left as a question mark. A note quotes the lesson's 240-frame, 1.5 GB reference.
Arithmetic computed by the script.

The Concept

Video diffusion: patchify, DiT, decode

Patchify

Encode the video with a 3D VAE (learned spatiotemporal compression). The latent is shape [T_latent, H_latent, W_latent, C_latent]. Split into patches of size [t_p, h_p, w_p]. For Sora-style models, t_p = 1 (per-frame patches) or t_p = 2 (every two frames). A 10-second 1080p video compresses to ~20,000-100,000 patches.

Spatiotemporal DiT

A transformer processes the flat sequence of patches. Each patch has a 3D positional embedding (time + y + x). Attention is usually factorized:

  • Spatial attention within each frame's patches.
  • Temporal attention across frames at the same spatial location.
  • Full 3D attention is 16-100x more expensive; used only at low resolution or in research.

Text conditioning

Cross-attention with a large text encoder (T5-XXL for Sora, CogVideoX-5B uses T5-XXL). Long prompts matter. Sora's training set had GPT-generated dense re-captions averaging 200 tokens per clip.

Training

Standard diffusion loss (ε or v prediction) over spatiotemporal latents. Data: web video + ~100M curated clips + synthetic text captions. Compute: 10,000+ GPU hours for even a small research run; Sora-scale is 100,000+.

Check: The Concept0/5
1. What does a video VAE do in a video diffusion pipeline?Define
A clip of 240 frames with shape (T, H, W, C) = (240, 1080, 1920, 3) goes into a box labelled Video VAE encoder, whose output shape is marked with a question mark.
Schematic. Input shape is the lesson's 10-second 1080p example.
2. In a video DiT, what is a patch?Define
A stack of four latent frames, an arrow marked with a question mark to a row of items labelled patch 1, patch 2, patch 3 and patch N, feeding a DiT transformer box.
Schematic. N is the lesson's range for a 10-second 1080p clip.
3. How does factorized attention cut compute in a spatiotemporal DiT?Define
Four frames of 3 by 3 patches. One patch in frame 1 has a thin line to every one of the other 35 patches. Text: 36 tokens, 36 x 36 = 1,296 pairs per layer.
Toy sizes chosen for the drawing, not the lesson's real patch counts.
4. Per the Patchify description, about how many patches does a 10-second 1080p video become, and how much more does full 3D attention cost than factorized?Define
A pipeline from a (240, 1080, 1920, 3) clip through a 3D VAE and a latent to patchify, ending in 'N patches = ?'. Below, factorized attention is the 1x reference and full 3D attention has an unknown multiple.
Schematic. Quantities to find are marked with question marks.
5. Why does each patch get a 3D positional embedding made of time, y and x?Explain
Two small frames of 2 by 3 patches labelled p0 to p11, an arrow 'flatten' to a flat list p0 to p11, and a line saying the token for p7 is patch features plus an embedding of (t, y, x) = (1, 0, 1).
Toy sizes chosen for the drawing. p7 coordinates follow from the row-by-row layout.

The 2026 production landscape

ModelDateMax durationMax resOpen weights?Notable
Sora (OpenAI)2024-0260s1080pNoFirst model to show world simulator properties at scale
Sora Turbo2024-1220s1080pNoProduction Sora at 5x faster inference
Veo 2 (Google)2024-128s4KNoHighest quality + physics in 2025
Veo 32025 Q315s4KNoNative audio and stronger camera control
Kling 1.5 / 2.1 (Kuaishou)2024-202510s1080pNoBest human motion in 2025 Q1
Runway Gen-3 Alpha2024-0610s768pNoProfessional video tools on top
Pika 2.02024-105s1080pNoStrongest character consistency
CogVideoX (THUDM)202410s720pYes (2B, 5B)First open 5B-scale video
HunyuanVideo (Tencent)2024-125s720pYes (13B)Open SOTA late 2024
Mochi-1 (Genmo)2024-105.4s480pYes (10B)Most permissively licensed
WAN 2.2 (Alibaba)2025-075s720pYesStrongest open model mid-2025

Open weights are closing the gap faster than in the image space: HunyuanVideo + WAN 2.2 LoRAs already power most open-source workflows by mid-2026.

Check: The 2026 production landscape0/2
1. Which model in the production landscape table ships open weights that you can download and run yourself?Define
Two diagrams. Hosted: your prompt goes to the vendor's servers, where the weights stay, and a video comes back. Open weights: you download a weights file and run it on your own GPU to get a video.
Schematic of the two access modes; no model is named.
2. Which model does the production landscape table credit with the strongest character consistency?Define
Three panels labelled clip 1, clip 2 and clip 3, each showing the same simple figure with a red hat and blue shirt.
Hand-drawn stand-in for a character; no model is named.

Build It

code/main.py simulates the core spatiotemporal DiT idea: patchify a small synthetic video, add a per-patch position embedding, and denoise the whole sequence jointly with one small MLP that sees every patch at once (no attention in the toy). No numpy, pure Python. Temporal coherence shows up even in 1-D when the frames share a denoiser and carry position embeddings.

Step 1: patchify a synthetic 1-D "video"

python
T_FRAMES = 6
 
def make_video(rng):
    # a "video" is a sequence of 1-D values following a smooth trajectory
    base = rng.gauss(0, 1)
    slope = rng.gauss(0, 0.3)
    return [base + slope * t + rng.gauss(0, 0.05) for t in range(T_FRAMES)]

Step 2: position embedding per frame

python
def pos_embed(t, dim):
    return sinusoidal(t, dim)

Step 3: denoiser sees the whole sequence

Instead of denoising each frame independently, the tiny net concatenates all frame values and their position embeddings, then predicts the noise for all frames jointly.

Step 4: temporal coherence test

After training, sample a video. Measure the frame-to-frame delta. If the model has learned temporal structure, the deltas come out smaller than when each frame is sampled independently.

Check: Build It0/6
1. In main.py, T_FRAMES = 6, POS_DIM = 4 and t_dim = 8, and the net takes 6*(1+4)+8 inputs. How many numbers go in?Apply
Six boxes, patch 0 to patch 5, each marked '1 + 4 pos', a timestep embedding box of size 8, and the code line inp = list(x) + list(t_emb) with len(inp) left as a question mark.
Layout drawn from main.py's constants.
2. In main.py, sin_embed(0, 4) builds the position embedding for frame 0. What does it return?Apply
The source of sin_embed from main.py: for each frequency it appends sin(t * freq) then cos(t * freq). Below it the call sin_embed(0, 4) with a question mark as its result.
Source copied from main.py; the result is left open.
3. main.py pools frame deltas over 5 clips of 6 frames each. How many deltas feed the joint average?Apply
Code from main.py: T_FRAMES = 6, frame_deltas returning abs(video[i+1] - video[i]) for i in range(len(video) - 1), a loop over 5 clips extending joint_deltas, and len(joint_deltas) left as a question mark.
Source copied from main.py, formatted onto short lines.
4. In main.py, forward() is called on one noisy clip. What does it return?Apply
Boxes for input (38 numbers), layer 1 (48, tanh), layer 2 (48, tanh) and output with an unknown number of values, and the line net = init_net(..., hidden, ?, rng).
Sizes from main.py's init_net call; the output size is left open.
5. A colleague says main.py shows that factorized spatial and temporal attention reduces memory. What is wrong with that claim?Analyze
A quoted claim that main.py shows factorized attention reducing memory, and a terminal with the run's section headers and its last two lines: the average frame-to-frame deltas and the closing sentence.
Captured by running main.py; the middle lines are left out.
6. A run of main.py prints avg deltas joint=0.95 independent=0.70, followed by the line saying joint sampling is smoother. What should you conclude?Analyze
Terminal text: avg frame-to-frame delta: joint=0.95 independent=0.70, then the line joint sampling produces smoother motion (smaller deltas).
Typed from the question, not captured from a run.

Pitfalls

  • Independent per-frame sampling = flicker. If you run image diffusion on each frame separately, the output flickers because each frame's noise is independent. Video diffusion fixes this by coupling the frames through attention or shared noise.
  • Naive 3D attention = OOM. Full 3D attention on a 10-second 1080p latent is hundreds of billions of operations. Factorize into spatial + temporal.
  • Data captioning matters more than size. Sora's main upgrade over prior work was training on ~10x more detailed captions (GPT-4 re-labelled clips). OpenAI's technical report says so directly.
  • First-frame conditioning. Most production models also accept an image as the first frame. This is "image-to-video" mode, and training includes that variant.
  • Physics drift. Long clips (>10s) accumulate subtle inconsistencies. Sliding-window generation + keyframe anchoring helps.
Check: Pitfalls0/11
1. What does image-to-video (I2V) mode do?Define
A mock-up generation panel with the Image-to-video mode selected, a product photo, the prompt 'slow push-in, soft light', and an arrow to a result box marked 'a video?'.
Hand-drawn mock-up; the prompt text is made up for illustration.
2. What is keyframe conditioning?Define
Eight frame slots in a row. Frames 0 and 7 are filled, with a ball low on the left and high on the right, and labelled keyframe. Frames 1 to 6 are dashed empty slots with question marks.
Schematic with an invented ball scene; no model is shown.
3. What is re-captioning in video model training?Define
A training clip drawn as four frames, its collected caption 'dog on beach' (3 words), and a second caption slot labelled 'caption after re-captioning' holding a question mark.
The caption text is made up for illustration; real training captions are not shown.
4. What is flicker in generated video?Define
Six frame panels in a row; an orange dot in each sits at a different height, joined by a dashed line, with values -1.67, +1.68, +1.93, -0.02, +0.51 and +1.58.
Values captured from running main.py, drawn as dot height.
5. What did OpenAI's technical report name as Sora's main upgrade over prior work?Define
A citation card for 'Video generation models as world simulators', the OpenAI Sora technical report by Brooks et al. (2024), above the Sora row of the landscape table: 2024-02, 60s, 1080p, no open weights.
Citation and table row copied from the lesson.
6. Why does running image diffusion on each frame separately make a video flicker?Explain
Prompt feeds an image diffusion model run on each of six frames. Six frame panels each hold an orange dot at a different height, joined by a dashed line, to show jitter.
Dot heights are clip 0 of the baseline block printed by main.py. The prompt text is made up.
7. A client wants a clip that starts exactly from an existing product photo. Which mode fits?Apply
A product photo of a mug with an arrow to the requirement 'the clip starts exactly from this photo' and the question which generation mode fits.
Hand-drawn stand-in for the photo.
8. A job runs full 3D attention on a 10-second 1080p latent and fails with out-of-memory errors. What is the fix?Analyze
Text: about 20,000 to 100,000 tokens, so 400,000,000 to 10,000,000,000 N x N pairs per layer. An N x N score matrix square with an arrow to a failed job showing out-of-memory errors.
Pair counts computed from the lesson's patch range. The status box is a typed illustration, not a captured log.
9. A pipeline runs an image diffusion model on every frame of a clip with the same prompt. Each frame is sharp, but the subject's outline jitters between frames. What is the cause and the fix?Analyze
Six frame panels each containing an orange outlined rectangle that sits at a slightly different position in each frame.
Hand-drawn; the offsets are made up to show jitter.
10. A 15-second clip looks fine early on, but objects slowly change shape and lighting shifts by the end. What is this, and what helps?Analyze
Four frames of one clip at 0, 5, 10 and 15 seconds. A rounded orange square slowly becomes a taller, narrower, rounder shape and the background gets darker.
Hand-drawn illustration of the stem's description, not generated output.
11. You need a 25-second clip, but your model only stays coherent for about 10 seconds. Which design fits?Design
A 25-second timeline. The first 10 seconds are marked coherent; the remaining 15 seconds are dashed and marked not covered by one pass.
Schematic of the stem's numbers.

Use It

Use case2026 pick
Highest-quality text-to-video, hostedVeo 3 or Sora
Camera-controlled cinematicRunway Gen-3 with motion brushes
Character consistency across clipsPika 2.0 or Kling 2.1
Open weights, fast fine-tuneWAN 2.2 + LoRA
Image-to-videoWAN 2.2-I2V, Kling 2.1 I2V, or Runway
Audio-to-video lip syncVeo 3 (native audio) or a dedicated lip-sync model
Video editingRunway Act-Two, Kling Motion Brush, Flux-Kontext (still-frame)

Cost per second of video at quality parity has dropped 20x between 2024 and 2026.

Check: Use It0/3
1. What is a motion brush?Define
A mock-up editor: a tools column with Motion brush selected, a still image of a boat on water with two orange arrow strokes painted to its right, and an arrow to a result box marked with a question mark.
Hand-drawn mock-up; the scene is made up.
2. A team needs a talking-head clip with native audio and lip sync. Which pick does the Use It table give?Apply
Six frames of a simple face whose mouth opens by different amounts, with green audio level bars below each frame that rise and fall to match.
Hand-drawn; mouth sizes and audio levels are made up.
3. A startup must fine-tune cheaply on its own brand style, run on its own hardware, and animate product stills. Which pick fits?Design
Three requirement cards: fine-tune cheaply on its own brand style, run on its own hardware, animate product stills.
Requirements restated from the question.

Ship It

Save outputs/skill-video-brief.md. Skill takes a video brief (duration, aspect ratio, style, camera plan, subject consistency, audio) and outputs: model + hosting, prompt scaffolding (camera language, subject description, motion descriptors), seed + reproducibility protocol, and a frame-level QA checklist.

Author's Note

I'd run main.py and read the two blocks of printed clips before anything else. Each clip is six values, joint sampling first and the independent baseline second, so the difference in smoothness is visible without plotting. The two averages on the line after them are the number to look at. The sentence printed under them is hardcoded, so it says joint sampling is smoother whether or not your run bears that out.

Know what the toy leaves out. The denoiser is a small MLP with two tanh hidden layers and a linear output. Its input is all six frame values and their position embeddings flattened into one vector, with the timestep embedding appended. There is no attention and no spatial or temporal factorization, and the coupling between frames comes entirely from that shared input. The baseline is not a trained model either. It draws each frame from a Gaussian and adds 0.3 * t, so the comparison shows what joint denoising buys over no coupling at all, not a contest between two samplers.

When you move past the toy, I'd start from the Pitfalls list: factorize attention before anything else, since full 3D attention is what runs you out of memory. Then decide early whether you need first-frame conditioning, because the models that support it were trained for it.

Exercises

  1. Easy. In code/main.py, compare frame-to-frame delta for (a) independent per-frame sampling, (b) joint sequence sampling. Report the mean and variance of the deltas.
  2. Medium. Add a first-frame condition: pin frame 0 to a given value and sample the rest. Measure how the pinned value propagates.
  3. Hard. Use HuggingFace diffusers to run CogVideoX-2B on a local GPU. Time 20 inference steps at 720p for a 6-second clip. Profile the spatiotemporal attention to identify the bottleneck.

Key Terms

TermWhat people sayWhat it actually means
Video VAE"3-D VAE"Encoder that compresses (T, H, W, C) → spatiotemporal latent.
Patches"The tokens"Fixed-size 3-D blocks of the latent; input to the DiT.
Factorized attention"Spatial + temporal"Run attention over space, then over time; skip full 3-D attention.
Image-to-video (I2V)"Animate this photo"Model takes an image + text, outputs a video that starts from it.
Keyframe conditioning"Anchor frames"Pin specific frames to control the video's arc.
Motion brush"Directional hint"UI input where the user paints motion vectors onto the image.
Re-captioning"Dense captions"Using an LLM to re-label training clips with detailed prompts.
Flicker"Temporal artifact"Frame-to-frame inconsistency; fixed with coupled denoising.

Production note: video latents are a memory-bandwidth problem

A 10-second 1080p clip at 24 fps is 240 frames × 1920 × 1080 × 3 ≈ 1.5 GB of raw pixels. After a 4× video VAE compression (2 × spatial × 2 × temporal) the latent is ~100 MB per request. Run this through a spatiotemporal DiT for 30 steps at batch 1 and you are moving ~3 GB/step through HBM: memory bandwidth, not FLOPs, is the bottleneck.

Three production knobs, all from the inference chapter of the production-inference literature:

  • TP across the DiT. Text-to-video models are routinely ≥10B params. TP=4 across 4 H100s is standard; PP=2 × TP=2 for 405B-class models. Latency per step drops roughly linearly with TP up to the all-reduce wall.
  • Frame batching = continuous batching. At generation time, video is conceptually a batch of frames linked by attention. Continuous batching (in-flight scheduling) applies: start rendering frame t+1 while frame t-1 is being returned, if the model architecture allows sliding-window generation.
  • Clip-level prefill cache. For image-to-video, the first-frame conditioning is analogous to an LLM's prompt prefill: compute it once, reuse across the temporal decoder passes. This is effectively a KV-cache for video.
Check: Production note: video latents are a memory-bandwidth problem0/4
1. What is the standard tensor-parallel setup for a text-to-video DiT with 10B or more parameters?Define
Two panels. TP: one layer split into slices on GPU 1, GPU 2, GPU 3 up to GPU k, then an all-reduce. PP: the stack of layers divided into stage 1, stage 2 up to stage s.
Generic schematic of the two terms; k and s are not given values.
2. What is the bottleneck when a spatiotemporal DiT runs over video latents at batch 1?Explain
A chain of four boxes: raw clip about 1.5 GB, video VAE, latent about 100 MB, and a DiT run for 30 steps at batch 1. Below, a line asking what limits the speed of a step.
Sizes quoted from the lesson's production note.
3. For image-to-video, what plays the role of an LLM's KV-cache in the clip-level prefill cache?Explain
Two rows. LLM: prompt tokens, prefill run once, KV cache reused, decode steps. Image-to-video: image plus text, clip-level prefill run once, a box marked with a question mark that is reused, temporal passes.
Schematic built from the lesson's production note.
4. Profiling shows a 12B video DiT step is limited by moving about 3 GB through HBM at batch 1, and you must cut latency per step. Which change fits?Design
A GPU memory box and a compute units box joined by two arrows labelled about 3 GB moved per step, with the goal of cutting latency per step.
Schematic. The 3 GB per step comes from the question.

Further Reading

Code

Ships With