Python · cell 1
A video generator turns a prompt into a short clip of moving pictures. It is an image generator with one more axis, time, and that axis changes almost everything. The frames must agree with one another, so they are generated together rather than one at a time. And a clip holds so many numbers that cost, more than quality, decides what can be built. This section shows how the image machinery of this chapter stretches to video, and counts what it costs at every stage.
Here is the problem in numbers. A five-second clip at 16 frames per second has frames, a first frame and 80 more. At pixels with three colors each, that is numbers. Suppose you give each frame to a good image generator, with the same prompt and the same random seed. You get 81 good pictures and a bad film. Textures shimmer from frame to frame, a face changes identity every few frames, and nothing moves the way real things move. Each frame is plausible on its own, and together they are impossible.
So a video model samples all the frames jointly, as one object. We build one from the pieces of this chapter, in order. First comes an autoencoder that compresses time as well as space, then a count of the tokens that compression leaves, then the point at which attention over those tokens becomes the main cost. With those three pieces we follow one prompt all the way to frames, with every shape and every FLOP. Then come the attention patterns that trade quality against cost, one of which lets a model stream frames out the way a language model streams words. After them come the recipe the large systems share, conditioning on an image, keeping a clip consistent, and generating in real time. Every system number is checked against its paper or recomputed in a cell.
§29.1 measured two facts about real video. Consecutive frames are nearly identical, which is why video codecs compress by factors of hundreds. And the part that does change, the motion, is exactly what a viewer notices. Three consequences follow for a generator.
A tiny clip makes all three concrete.
Recall the idea of §34.1. An autoencoder has an encoder that shrinks an image to a small grid of latent vectors and a decoder that maps the grid back to pixels. The encoder shrinks each side by a factor and keeps numbers, the channels, at each latent position. Diffusion then runs on the small latent instead of the pixels. For video there are two ways to build such an autoencoder.
The first reuses an image autoencoder on every frame. Video LDM (Blattmann et al. 2023a) and Stable Video Diffusion (Blattmann et al. 2023b) did this with the Stable Diffusion autoencoder, which has and . It costs nothing new. But it leaves all the repetition between frames in the latent. And decoding each frame on its own causes flicker, because nothing ties the fine detail of one frame to that of the next. Video LDM had to add temporal layers to the decoder and fine-tune it on video to suppress the flicker.
The second way compresses time too. It adds temporal convolutions to the autoencoder, small filters that slide along the time axis at each pixel and mix a few neighboring frames. Almost every system since 2024 takes this route. The interesting design question is what such a filter does at the start of the clip, where there are no earlier frames to mix.
Here is the answer on a small case. A filter of length at frame 0 needs frames and , which do not exist. A causal filter invents them in the past only, for example as copies of frame 0. Then output 0 is a weighted sum of three copies of frame 0, and output 1 mixes frames 0, 0, and 1. No output ever reads a frame later than its own. A centered filter instead reads one frame back and one frame ahead, so output 0 already depends on frame 1.
To write this down, take one pixel and list its features over time as , one vector of channels per frame. Spatial filters act the same way at every frame, so they change nothing below.
In plain words, output is a learned mix of the frame at position and the frames just before it. The stride is how far the window jumps between outputs, so a stride of 2 halves the number of frames.
The padded clip has frames. Output reads padded positions , which are original frames . Windows start every positions, and the last one must fit inside the padded clip, . So , which gives outputs. Every frame the window reads has index at most . For the only real frame in the window is frame 0, and the rest is padding, which is a copy of frame 0 or zero.
For the stack, induct on . If and divides , the first layer leaves frames. That is again "one plus a multiple of ", so the rest of the stack applies. Latent frame depends on layer-1 outputs . Each of those depends on inputs up to times its own index, so latent frame depends on inputs . ∎
In plain words, a causal encoder reads the clip the way a viewer watches it, never ahead. Its first latent frame is a pure image latent. Each later latent frame summarizes the next frames, given everything before them. Asked about a video autoencoder in an interview, state this shape rule first, frames in and latent frames out.
Both versions give 3 latent frames, . Each row of a table is one latent frame, and a 1 marks an input frame that changes it. In the causal table, row stops at frame , as the theorem says, and latent frame 0 sees only frame 0. In the centered table, latent frame 0 already depends on frames 0 to 3.
A full video autoencoder puts such convolutions, together with spatial ones, inside a variational autoencoder. A variational autoencoder (VAE, §32.3) is an autoencoder whose encoder outputs a mean and a variance for each latent number. It is trained with a small extra penalty, a KL divergence, that keeps the latents close to a standard Gaussian, so a generator has a well-shaped space to work in.
The causal design buys three things.
The table lists the autoencoders of the systems in this section. The compression ratio is the number of pixel values per latent value, .
| autoencoder | ratio | causal | ||
|---|---|---|---|---|
| SD autoencoder, frame by frame (SVD) | 4 | 48 | not temporal | |
| CogVideoX, HunyuanVideo, Wan-VAE | 16 | 48 | yes | |
| Movie Gen TAE | 16 | 96 | no, symmetric replicate padding | |
| LTX-Video | 128 | 192 | yes |
The ratio column ignores the first frame. Because that frame is compressed in space only, the exact ratio is . For Wan's 81 frames that is rather than 48.
The same two lessons as for images (§34.1) hold here. Temporal compression lets the same latent budget hold 4 or 8 times more frames, and more channels buy back reconstruction quality. CogVideoX measured both (Yang et al. 2024, Table 1).
Implement causal_temporal_conv(x, kernel, stride=1, pad="replicate"), the causal temporal convolution of the definition above, applied at every pixel.
x is a video of shape (F, H, W, C), and kernel is a temporal kernel of shape (k, C, C_out).pad="replicate" and as zeros when pad="zero". Pad nothing after the last frame.(floor((F - 1) / stride) + 1, H, W, C_out) whose frame is at every pixel.The tests check the shapes for stride 1 and 2 and for a single frame. They change future frames and check that earlier outputs do not move. And they check that under replicate padding a still video and a single image are encoded the same way.
The last test shows what replicate padding buys. Causality alone makes the first output of any clip equal to the encoding of its first frame on its own, under either padding. Replicate padding adds that every output of a still video equals that same image encoding, because the padded frames are copies of the real one. Zero padding makes the first outputs differ from the rest, since their windows hold zeros where later windows hold the picture. So with replicate padding an image is exactly a video that never moves, and the image data and the video data agree about what a frame looks like.
The denoiser is a transformer, and a transformer needs tokens. It gets them by cutting the latent video into small bricks, a few latent frames deep and a few positions on a side, and turning each brick into one vector. These bricks are the generative counterpart of the tubelets of §29.2, which showed that cutting and embedding bricks this way is a 3D convolution whose stride equals its kernel size. Sora's technical report (OpenAI 2024) described its own model only at this level. It compresses video into a latent space, cuts the latent into "spacetime patches" that act as the transformer's tokens, and treats images as videos with a single frame.
Nearly every documented video transformer uses , and the Step-Video report calls latent patches the typical choice. With a Wan-style autoencoder (, ), one token then covers 4 frames of pixels (one frame for the tokens of the first latent frame).
Each token also needs to know where and when it is. Rotary position embeddings (RoPE, §5.5) rotate pairs of a query's and a key's coordinates by angles proportional to position, so attention scores depend only on relative position. The 3D version (§29.2) gives each token a (time, row, column) triple and assigns some coordinate pairs to each of the three. HunyuanVideo's 128-dimensional heads give 16 dimensions to time and 56 to each spatial axis (Kong et al. 2024). CogVideoX-5B and Wan's released code also use 3D RoPE. Movie Gen instead adds learned position vectors, one table each for time, row, and column.
The cell repeats the count for several published configurations.
LTX-Video stands out. Its autoencoder already compresses with 128 channels, so it uses no further patching. Five seconds of video at 24 fps (121 frames) is then only 6,144 tokens. That is how it generates such a clip with 20 denoising steps in about 2 seconds on one H100 GPU, faster than the clip plays (HaCohen et al. 2024).
The token view also explains a practical finding of Sora's report. Sora trains on videos at their native durations, resolutions, and aspect ratios. Against a version trained on square crops, keeping the native aspect ratio improved framing and composition, where the square-crop model often cut the subject off. At inference, the report says, "we can control the size of generated videos by arranging randomly-initialized patches in an appropriately-sized grid". In token terms, a clip of any shape is a list of tokens with 3D positions. Clips of different shapes can share a training batch by packing their tokens into one sequence and masking attention between them, the approach of NaViT (Dehghani et al. 2023) that §31.5 describes.
Implement video_latent_shape(F, H, W, f_t, f_s, c, causal=True), which returns the latent shape (F_lat, H // f_s, W // f_s, c) of a clip of F frames.
ValueError if does not divide .ValueError if does not divide both and .Then implement video_tokens(F, H, W, f_t, f_s, patch=(1, 2, 2), causal=True), which returns the token count as a Python int. Raise ValueError if the patch does not divide the latent grid.
A transformer layer does two kinds of work. The linear part multiplies every token by weight matrices, in the four attention projections and in the MLP, so its cost grows in proportion to the token count . The attention core compares every token with every other token, so its cost grows with . For short sequences the linear part dominates, and for long ones attention does. The question is where the crossover lies.
Here is the count for one block, at tokens of width with an MLP of hidden width . We count floating-point operations (FLOPs), two per multiply-add. Each of the four projections costs , and the two MLP matrices cost each, so the linear part is million FLOPs. The attention core forms the scores and then the weighted sum of values, each , so million FLOPs. Attention already costs 2.6 times the rest, because is more than . The same count appears for images in §34.1 and for video encoders in §29.3. Here we state it for any MLP width, including the gated MLPs (SwiGLU, §5.3) that some video transformers use, which have three weight matrices instead of two.
Each projection multiplies the input by a matrix, which is FLOPs, so four of them make . Each MLP matrix is or its transpose, FLOPs each, so in all. The core computes the scores and the weighted values , each FLOPs summed over heads, so . Dividing, attention over linear is . With and , . The softmax, normalizations, and residual additions cost a few FLOPs per score or per number, far below both terms, and are left out. ∎
In plain words, once a clip has more tokens than about six times the model width, more than half of every layer's compute goes to tokens comparing themselves with other tokens. The share keeps climbing toward 1 as clips get longer. For video models this is the normal operating point, which is why nearly every efficiency idea for video targets attention. An interviewer who asks where the compute goes in a video model wants this crossover and one real number.
The cell computes the crossover, the forward FLOPs of the whole denoiser for one step, and the memory a stored score matrix would need, for four published models. It counts only the self-attention blocks over video tokens. Movie Gen and Wan also cross-attend to the text, which adds about per layer, and HunyuanVideo carries the text tokens inside the same sequence.
One denoising step of Movie Gen is about 9.6 PFLOP ( FLOPs). The score matrix of a single head at full length, stored in bf16 (2 bytes a number), would take 10.9 GB, and there are 48 heads in each of 48 layers. No production model stores it. FlashAttention (§12.3) computes exact attention tile by tile without ever storing the scores. Context parallelism (§10.6, §12.7) splits the sequence across GPUs. One form, ring attention, passes blocks of keys and values around a ring of GPUs. Another, Ulysses, swaps the split so each GPU holds whole sequences for a subset of heads. Movie Gen trained with tensor, sequence, context, and fully sharded data parallelism together, and Wan combines Ulysses and ring attention. These methods remove the memory wall, but they do not remove a single FLOP.
The widget runs the whole chain for a clip you choose. Each model opens with the clip it was built for, at its own frame rate and with its own autoencoder. Then change the duration, the resolution, and how many video frames the autoencoder packs into each latent frame. The widget shows the latent, the token count, and where attention's share of the compute passes one half.
Count the forward FLOPs of a video denoiser, at 2 FLOPs per multiply-add. Implement three functions.
layer_flops(T, d, d_ff, gated, T_c=0) returns (linear, attention) for one block. The block has self-attention with four projections and an MLP with two matrices ( and ), or three if gated. If T_c > 0 it also cross-attends to T_c text tokens of width . In the cross-attention the query and output projections act on the video tokens, and the key and value projections act on the text tokens. linear counts every projection and MLP matrix, and attention counts the score and value products of both attentions.crossover(d, d_ff, gated) returns for the block without cross-attention.video_step_flops(T, d, d_ff, gated, L, T_c=0, cfg=False) returns the forward FLOPs of all L blocks for one sampling step. Double it when cfg is True, because classifier-free guidance runs the denoiser twice per step, once with the prompt and once without.Return Python numbers, not NumPy integers, because these counts overflow 32-bit integers.
We now have every piece, so let us follow one generation from a prompt to frames. Take Wan 2.1 14B making five seconds of 480p video, 81 frames of , from a one-sentence prompt such as "a red fox trots across fresh snow at dawn". Every shape below follows from the configuration counted above.
The widget steps through the same pipeline, stage by stage and then denoising step by denoising step. It shows Wan's real shapes beside a toy clip of 9 frames that is compressed and denoised the same way. Press Play to run it, or drag the stage slider to stop at any stage.
The cell traces the shapes and prices the denoiser, with the text cross-attention included this time.
Two things stand out in the trace. The latent the model works on has 2.1 million numbers, 46 times fewer than the 97 million pixel values it decodes to. And almost all the time goes to step 4, repeated 100 times. That is why the rest of this section is about which tokens attend to which, and about taking fewer steps.
The quadratic cost invites limiting which tokens each token may attend to. The options form a short list, and the history of video generation is a migration along it.
Temporal layers inside an image model. The first diffusion video models kept a 2D image network and inserted layers that mix across time. Video Diffusion Models (Ho et al. 2022a) used a 3D U-Net factorized over space and time. Spatial attention ran inside each frame, then temporal attention ran across frames at each position. It trained on videos and images together by appending independent images to each video and switching off temporal attention for them. Make-A-Video (Singer et al. 2022) added space-time layers to a text-to-image model and learned motion from unlabeled video, with no paired video-text data at all. Video LDM (Blattmann et al. 2023a) turned Stable Diffusion into a video model by training only inserted temporal layers. One appeal is reuse, since the expensive image model is already trained. The other is cost. Write for the tokens per latent frame. Factorized attention scores pairs instead of .
Full 3D attention. Every token attends to every other token of the clip. CogVideoX compared it with "2D + 1D" factorized attention at the same model size. The factorized model reached much worse FVD, a video quality score defined in §34.6, early in training, and was "unstable and prone to collapse" (Yang et al. 2024). The reason is reachability, the argument of §29.3. In a factorized block, temporal attention connects only tokens at the same spatial position. So a patch of a moving object in frame cannot attend directly to where the object was in frame . The information has to hop through intermediate tokens, and large motion needs many hops. HunyuanVideo chose full attention for the same reason, and because it treats images and videos alike and reuses the acceleration tools built for long-context language models (Kong et al. 2024).
The price is real but smaller than the pair count suggests. One forward step of CogVideoX on an H800 at and 6 seconds took 2.11 seconds with full attention and 1.26 seconds factorized, and 9.60 against 4.17 seconds at (Table 8 of the same paper). Since 2024 the documented frontier models use full 3D attention and pay that price.
Local and causal patterns. Between the two extremes sits windowed or sparse attention, where each token sees a space-time neighborhood (the 3D version of the local attention of §12.6). And there is the pattern that streaming generation needs, causal attention in time.
Here is that last pattern on a small case. Take 6 latent frames and group them into chunks of 2, so frames 0 and 1 form chunk 0, frames 2 and 3 form chunk 1, and so on. A token in chunk 1 may see every token of chunks 0 and 1, including the other frame of its own chunk. It may not see chunk 2. That is block-causal attention.
The point of this mask is that a model built on it never looks at the future. So the future can be generated later, chunk by chunk, with a cache, and the outputs are exactly the same as one masked pass over the whole clip. The cache is the KV cache of language models (§6.5), which stores the keys and values of every token already processed so that each new token computes only its own.
Proof. Induct on the layers. Suppose the inputs to layer agree in both procedures for every token in chunks . This holds for , where the inputs are the latent tokens themselves. Under the mask, a query in chunk sees exactly the keys and values of chunks , computed from those identical inputs. In the streaming procedure, when chunk is processed the cache holds exactly the keys and values of chunks , from the same inputs. The masked-out scores contribute to the softmax, so both softmaxes run over the same scores and give the same weights and outputs. The MLP and the normalizations act on each token separately, so the inputs to layer agree for chunks . ∎
In plain words, this is the language-model KV cache with chunks of frames in place of single tokens. In a diffusion model the theorem applies to each network call. A chunk is denoised in several calls against the same cache of finished chunks, and only then are its own clean keys and values added. The cell checks the theorem numerically.
With 3 chunks of 8 tokens, the mask keeps of 576 pairs. In general, chunks of tokens keep pairs, a fraction of full attention. So causality alone saves at most half the work. Its value is latency, the time until the first frames appear, not FLOPs.
The widget shows each pattern on a small clip, 6 latent frames of 4 tokens as in the cell. Each small square is one latent frame, and the matrix beside it is the whole mask, one row per query and one column per key. In factorized mode it colors the keys of the spatial layer and of the temporal layer separately. Its key count adds the two layers, so the query's own token counts once in each, as in the pair count .
The table collects the patterns. Here is the number of tokens per latent frame and the number of latent frames.
| pattern | pairs per layer | motion and identity | used for |
|---|---|---|---|
| image model per frame | none across frames, flicker | baseline only | |
| factorized, spatial then temporal | indirect, through hops | VDM, Make-A-Video, Video LDM, SVD | |
| full 3D | direct between any two tokens | CogVideoX, Movie Gen, HunyuanVideo, Wan, Step-Video | |
| windowed or sparse in space-time | (window) | local directly, far through layers | efficient inference variants |
| block-causal in time | about | direct to the past only | streaming students (CausVid, Self Forcing) |
Tokens are ordered frame by frame, with tokens_per_frame tokens per latent frame, so token is in latent frame q // tokens_per_frame. Let = F_lat * tokens_per_frame.
block_causal_mask(F_lat, tokens_per_frame, chunk). It returns a boolean array of shape that is True where a query (row) may attend to a key (column), as in the definition. When chunk does not divide F_lat, the last chunk is shorter.streaming_attention(Q, K, V, F_lat, tokens_per_frame, chunk) for single-head scaled dot-product attention, with Q and K of shape (T, d_k) and V of shape (T, d_v). Process one chunk at a time and keep a cache of keys and values. The output, of shape (T, d_v), must equal full attention under the block-causal mask.Before 2024 the best video models were chains of several networks. Since 2024 they are one latent transformer. The history box traces that change. The rest of this part reads the recipe from the systems that published it.
OpenAI's technical report on Sora, of February 15, 2024, describes the recipe in words and says that "model and implementation details are not included" (OpenAI 2024). Here is what it does state.
Every piece is in this chapter. The open systems below fill in the numbers.
| system | date | parameters | autoencoder | text | attention and blocks |
|---|---|---|---|---|---|
| CogVideoX | 2024-08 | 2B, 5B | causal 3D VAE, | T5, 226 tokens, in one joint sequence | full 3D, separate adaptive LayerNorm for text and video tokens, 42 layers of width 3,072 for 5B |
| Movie Gen | 2024-10 | 30B | TAE , , symmetric | UL2, ByT5, long-prompt MetaCLIP | Llama-3-style, 48 layers, width 6,144, SwiGLU 16,384, 48 heads, cross-attention to text |
| HunyuanVideo | 2024-12 | 13B | causal 3D VAE , | decoder-only multimodal LLM, plus pooled CLIP | 20 dual-stream then 40 single-stream blocks, width 3,072, full attention, 3D RoPE |
| Wan 2.1 | 2025-02, report 2025-03 | 1.3B, 14B | Wan-VAE causal , , 127M parameters | umT5, 512 tokens | cross-attention for text, 40 layers of width 5,120 for 14B, full attention |
| Step-Video-T2V | 2025-02 | 30B | causal, | two bilingual encoders | full 3D attention, preference tuning on video (Video-DPO), up to 204 frames |
All of them train with flow matching or a close relative (§33.6), except CogVideoX, which uses v-prediction. Three training habits recur.
Images first, then video, low resolution first. Movie Gen pretrains on text-to-image at 256 px. It then trains jointly on images and videos at 256 px and then at 768 px, with one image sample for every ten video samples. Wan pretrains its 14B model on 256 px images. It then trains jointly in three stages, 256 px images with 192 px videos, then 480 px, then 720 px. Starting from images teaches appearance and text alignment on short sequences, so the expensive long sequences only have to teach motion. Movie Gen reports that training jointly from scratch converged much more slowly than starting from a text-to-image model.
Curate for motion. Stable Video Diffusion's raw collection had 580M clips, 212 years of video, and was filtered to 152M clips. The filters removed near-static clips by their mean optical-flow magnitude (computed at 2 fps), clips with too much text, and clips with low aesthetic or text-image scores. Movie Gen keeps 4 to 16 second single-shot clips with "non-trivial motion". It removes static videos with a detector, keeps clips with reasonable motion scores, removes clips with frequent jittery camera moves, and drops special effects such as slideshows. A model trained on what the internet uploads learns that most videos barely move.
Spend the compute on the last stage, and save it at inference. Most of Movie Gen's training FLOPs go to the 768 px stage at 73K tokens, even though that stage runs fewer iterations than the 256 px stage. Each of its samples has 9 times the tokens of a 256 px sample, and attention grows with the square of that count (Polyak et al. 2024). At inference, cost is cut mainly by taking fewer steps. Movie Gen's linear-quadratic schedule emulates an even schedule of steps with only 50. Its first 25 steps sit exactly where the first 25 steps of the -step schedule would, at high noise where the output changes most, and 25 steps spaced quadratically cover the rest of the way. In practice the authors use , so 50 steps stand in for 250. HunyuanVideo shifts its timesteps toward high noise (the shift of §34.1), and shifts them further the fewer steps it takes.
Image-to-video means animating a given picture. The model must keep the picture as the first frame and invent the motion after it. The usual way is to feed the image's latent to the denoiser as extra input channels, next to the noisy latent of the clip.
Stable Video Diffusion does this in two places. It replaces the text embedding in cross-attention with the CLIP image embedding of the conditioning frame. And it concatenates a noise-augmented copy of the frame's latent, channel by channel, to the noisy input of every frame. It found that a constant guidance scale was either too weak (frames drift from the given image) or too strong (colors oversaturate). So it raises the guidance scale linearly across frames instead.
Wan's image-to-video model is more general. It concatenates three things along the channel axis.
So its first layer reads channels instead of 16. The new input channels enter through a projection initialized to zero, so the image-to-video model starts out as the text-to-video model. CLIP image features enter through a separate cross-attention. The same mask lets one model do image-to-video, continuation, and interpolation between a first and a last frame, depending on which frames are marked as given (Wan Team 2025).
Noise augmentation of the condition is the same trick that cascades use for their low-resolution inputs (§34.1). At inference the conditioning frame may itself be generated, with artifacts a real frame never has. A little noise during training teaches the model not to copy those details blindly.
Extension without retraining is harder. Suppose a model was trained only on unconditional clips, and we want it to continue a given clip with new frames . The tempting replacement method samples the whole clip and, at every step, overwrites the known frames with a freshly noised copy of at the current noise level. Video Diffusion Models (Ho et al. 2022a) found that it gives continuations that look fine alone but do not follow the given frames.
They also explained why. Write for the noisy clip at noise level . The update of the new frames moves toward the model's estimate . But a correct conditional sampler needs , which uses the clean given frames, not only their noisy copy. Their fix, reconstruction guidance, adds the missing term approximately. It nudges the new frames along the gradient of how well the model's own reconstruction of the known frames matches them.
Write and for the model's estimates of the clean known and new frames, and for the weight of the clean data in . The adjusted estimate of the new frames is
where is the noisy part of the clip that holds the new frames and is a weight. In plain words, the new frames move in whatever direction makes the model reconstruct the given frames better. The weight is what a Gaussian approximation of , the distribution of the given frames given the noisy clip, produces.
Even with a perfect denoiser, replacement continues the clip with mean 0.89 instead of 1.80 and variance 0.43 instead of 0.19. It captures only about half of what the given frame implies about the next one. Reconstruction guidance with , the Gaussian weight, moves the mean to 1.17. Larger weights move it further, to 1.75 at , while shrinking the variance below the right value. That matches what Ho et al. report for real videos, that gives better samples. It also shows that this guidance weight, like others, trades diversity for fidelity.
What this changes in practice is that production systems avoid the problem by training with masks, as Wan does. The model then learns directly. An interviewer who asks how to extend a clip with an unconditional model wants the replacement method, why it fails, and the masked-training fix.
Most visible failures of video models have a specific cause, and most have a known fix.
How these failures are measured, with FVD and VBench, is the subject of §34.6.
A full-attention video model cannot show the first frame until it has finished the last one, because every step updates all frames together. CausVid's bidirectional teacher took 219 seconds to generate a 10-second clip at (Yin et al. 2024). Interactive uses, such as live generation, games, and robotics, need frames to come out one after another, the way a language model streams tokens. Block-causal attention makes that possible. The question is how to train such a model so its frames stay good.
The obvious recipe is teacher forcing. Train a block-causal model to denoise each chunk given the clean ground-truth chunks before it, then generate one chunk after another. It inherits exposure bias (§3.5). In training the past is always real. At inference the past is the model's own imperfect output, a kind of input it never trained on, so errors compound over the rollout. Autoregressive image generation faces the same problem token by token (§34.4).
The first fix lets every frame carry its own noise level during training.
One model trained this way covers a whole family of samplers. If all frames share one noise level, it is a full-sequence diffusion model. If each frame is denoised completely before the next starts, it is next-frame prediction. In between, a lagged (or pyramid) schedule starts frame a fixed number of network calls after frame . Near frames are then nearly clean while far frames are still noise.
Here is a small case. With steps per frame and a lag of 2, frame 0 is denoised during calls 1 to 4, frame 1 during calls 3 to 6, and frame 2 during calls 5 to 8. Frame 0 is ready after 4 calls instead of waiting for the whole clip.
Diffusion Forcing also steadies long rollouts by conditioning on a slightly noised version of the generated past rather than the past itself, which hides small errors from the model. And its authors prove that the training objective is a lower bound on the likelihood of every subsequence of frames.
Implement pyramid_schedule(F_lat, S, lag). It returns a float array of shape (M + 1, F_lat), where is the total number of network calls and row holds every frame's noise level after calls.
Then implement latency_calls(F_lat, S, lag), which returns a list with the number of calls after which each frame first reaches noise level 0. With lag = 0 the schedule is full-sequence diffusion, and with lag = S it is next-frame denoising.
The exercise exposes the trade that a schedule controls. With lag 0 the first frame waits for all calls, like every other frame. With lag the first frame is ready after calls, and each later frame calls after the one before. But the total number of calls grows to . Streaming lowers latency and costs more calls in total, unless each call is cheap. Few-step students and caches make calls cheap.
CausVid (Yin et al. 2024) makes the causal model fast as well as causal. Its student starts as the pretrained bidirectional transformer with a block-causal mask. It is first fitted by regression to pairs of noise and clean output taken from the teacher's sampling ODE. Then it is trained with distribution matching distillation (DMD, §33.4), which pushes the distribution of the student's outputs toward the teacher's, using the difference of two score estimates. This turns the 50-step bidirectional teacher into a 4-step causal generator. During training each chunk gets an independent noise level, as in Diffusion Forcing. A bidirectional teacher can supervise a causal student because DMD matches only the distribution of whole clips, not each step.
At inference each chunk is denoised in 4 steps against the cache. Then one more pass over the clean chunk, at noise level 0, writes its keys and values to the cache for later chunks. CausVid streams at about 9.4 frames per second after an initial latency of 1.3 seconds on one GPU.
Self Forcing (Huang et al. 2025) attacks exposure bias directly. During training the student rolls out a whole video chunk by chunk with its KV cache, exactly as at inference. A distribution-matching loss (DMD, a related score-based distillation loss, or a GAN loss) is then applied to the finished video, so the student learns from its own past. To keep this affordable it denoises in 4 steps and lets gradients flow through only one randomly chosen step. A rolling KV cache, which keeps a fixed window of recent frames, allows videos longer than the training length.
Self Forcing builds on Wan 2.1 1.3B at . Its chunk-wise model, with 3 latent frames per chunk, streams at 17.0 frames per second with 0.69 seconds of latency on one H100. The base model runs at 0.78 frames per second with 103 seconds of latency. A frame-wise version reaches 0.45 seconds of latency at 8.9 frames per second.
The same machinery, conditioned on a player's or a robot's actions as well as on text, turns a video generator into an interactive simulator that predicts the next frames given what the agent does. Such action-conditioned video world models reuse the block-causal attention and streaming caches of this section.
Training cost follows from the per-step count. A training step costs about three forward passes, one forward and a backward pass that costs about twice as much (§0.2). So one 16-second Movie Gen clip at 768 px costs about PFLOP to train on. At a sustained 400 TFLOP/s per GPU, that is 72 GPU-seconds per training clip, and Movie Gen trained on up to 6,144 H100s. Sampling one clip with 50 steps costs PFLOP, about 20 GPU-minutes at the same rate, before the spatial upsampler that takes it to 1080p. Guidance run as a second pass doubles it. These are estimates from the formula, not measurements.
The frontier now generates sound with the picture. Google's Veo 3, announced May 20, 2025, generates "videos with audio", from background noise to dialogue between characters (Google 2025). OpenAI's Sora 2, announced September 30, 2025, is described as a "video and audio generation model" that creates background soundscapes, speech, and sound effects (OpenAI 2025). Neither company has published its architecture.
The documented route to sound is Movie Gen Audio, a separate 13B model that generates audio conditioned on the video and the text. How audio becomes tokens is the subject of §37.3, on neural audio codecs, and how a model generates those tokens is the subject of §37.4, on codec language models. There the latent-plus-generator pattern of this chapter returns for sound.
The reference block builds a small causal 3D encoder with F.pad, which pads time in the past only by replicating the first frame. It checks the shape rule and causality with autograd. Then it checks that streaming chunk by chunk with F.scaled_dot_product_attention equals one masked pass.
Latent frame 1 is reached by input frames 0 to 4 and no later, for , and 17 frames give 5 latent frames. Production code adds a feature cache, so each chunk reuses the last frames of features from the previous one instead of encoding them again.