Skip to content
Back to skills

Self Forcing Video

ASecurity

Address exposure bias in video generation by training on self-generated sequences, achieving 17 FPS real-time performance while matching quality of slower baselines.

  • 6 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 9, 2026
code-qualitypythonperformance

Works with

  • cli

Security analysis

A100/100

Scanned September 9, 2026

npx -y skills add ADu2021/skillXiv --skill self-forcing-video --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Self Forcing Video?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Self Forcing Video
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/adu2021-self-forcing-video/badge)](https://www.skillsdirectory.com/skills/adu2021-self-forcing-video)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: self-forcing-video
title: "Self Forcing: Bridging the Train-Test Gap in Autoregressive Video Diffusion"
version: 0.0.2
engine: skillxiv-v0.0.2-claude-opus-4.6
license: MIT
url: "https://arxiv.org/abs/2506.08009"
keywords: [video generation, exposure bias, autoregressive diffusion, training efficiency]
description: "Address exposure bias in video generation by training on self-generated sequences, achieving 17 FPS real-time performance while matching quality of slower baselines."
---

# Self Forcing: Bridging Train-Test Gap in Video Diffusion

## Core Concept

Autoregressive video models face an exposure bias problem: they train on ground-truth frames but generate from their own imperfect outputs at test time. Self Forcing bridges this gap by performing autoregressive rollout during training, conditioning each frame on previously self-generated outputs. This holistic video-level distribution matching eliminates frame-wise objective mismatch.

## Architecture Overview

- **Autoregressive rollout training**: Each frame conditioned on self-generated prior frames
- **Few-step diffusion**: 4-step diffusion models enable practical training speed
- **Rolling KV cache**: O(TL) complexity vs O(L²+TL) for full attention caching
- **Distribution matching loss**: Applies DMD, SiD, or GAN objectives to complete sequences
- **Real-time inference**: 17 FPS with sub-second latency on single H100

## Implementation

### Step 1: Design Few-Step Diffusion Model

Create efficient diffusion variant for video frames:

```python
class FewStepVideoDiffusion(torch.nn.Module):
    def __init__(self, num_steps: int = 4,
                 frame_shape: tuple = (64, 64, 3)):
        super().__init__()
        self.num_steps = num_steps
        self.frame_shape = frame_shape

        # Denoiser network
        self.denoiser = UNet3D(
            in_channels=3,
            out_channels=3,
            time_embedding_dim=128
        )

        # Timestep embedding
        self.time_embedder = torch.nn.Sequential(
            torch.nn.Linear(1, 128),
            torch.nn.SiLU(),
            torch.nn.Linear(128, 128)
        )

    def forward(self, x_noisy: torch.Tensor,
               t: int,
               context: torch.Tensor = None) -> torch.Tensor:
        """Predict clean frame from noisy input."""

        # Embed timestep
        t_emb = self.time_embedder(
            torch.tensor([[t / self.num_steps]],
                         dtype=torch.float32)
        )

        # Denoise with context conditioning
        if context is not None:
            x_noisy = torch.cat([x_noisy, context], dim=1)

        pred = self.denoiser(x_noisy, t_emb)

        return pred

    def reverse_diffusion_step(self, x_t: torch.Tensor,
                              t: int,
                              context: torch.Tensor = None
                              ) -> torch.Tensor:
        """Single reverse diffusion step."""

        # Predict noise
        noise_pred = self.forward(x_t, t, context)

        # Reverse step
        alpha = self._get_alpha(t)
        x_prev = (x_t - (1 - alpha) * noise_pred) / torch.sqrt(alpha)

        return x_prev

    def _get_alpha(self, t: int) -> float:
        """Get variance schedule value."""
        return 1.0 - (t / self.num_steps)
```

### Step 2: Implement Self Forcing Training Loop

Train with autoregressive rollout and gradient truncation:

```python
class SelfForcingTrainer:
    def __init__(self, diffusion_model: FewStepVideoDiffusion,
                 batch_size: int = 16):
        self.model = diffusion_model
        self.batch_size = batch_size
        self.optimizer = torch.optim.Adam(
            self.model.parameters(),
            lr=1e-4
        )

    def prepare_video_batch(self, video_frames: torch.Tensor
                           ) -> torch.Tensor:
        """Prepare video for training (add noise)."""
        # Add Gaussian noise to frames
        noise = torch.randn_like(video_frames)
        noisy_frames = torch.sqrt(torch.tensor(0.9)) * video_frames + (
            torch.sqrt(torch.tensor(0.1)) * noise
        )
        return noisy_frames

    def self_forcing_rollout(self,
                            initial_frame: torch.Tensor,
                            num_frames: int,
                            attention_window: int = 256
                            ) -> list:
        """Rollout sequence with self-generated frames."""

        frames = [initial_frame]

        for frame_idx in range(1, num_frames):
            # Get context from recent frames (limited window)
            context_start = max(0, frame_idx - attention_window)
            context_frames = torch.cat(
                frames[context_start:frame_idx],
                dim=0
            )

            # Generate next frame through diffusion steps
            current_frame = initial_frame  # Start from noise

            for step in reversed(range(self.model.num_steps)):
                current_frame = (
                    self.model.reverse_diffusion_step(
                        current_frame,
                        step,
                        context=context_frames
                    )
                )

            frames.append(current_frame)

        return frames

    def compute_distribution_matching_loss(self,
                                          generated_frames: list,
                                          loss_type: str = "dmd"
                                          ) -> torch.Tensor:
        """Compute loss on full generated sequence."""

        # Stack all generated frames
        generated = torch.stack(generated_frames, dim=0)

        if loss_type == "dmd":
            # Distribution Matching Diffusion loss
            loss = self._dmd_loss(generated)
        elif loss_type == "sid":
            # Score-based Implicit Divergence
            loss = self._sid_loss(generated)
        else:  # GAN loss
            loss = self._gan_loss(generated)

        return loss

    def _dmd_loss(self, generated: torch.Tensor) -> torch.Tensor:
        """Distribution Matching Diffusion loss."""
        # Compare real and generated distributions
        # Using optimal transport or other divergence measure
        batch_mean = generated.mean(dim=0)
        batch_std = generated.std(dim=0)

        loss = (batch_mean ** 2).mean() + (batch_std - 1.0) ** 2

        return loss

    def train_step(self, video_batch: torch.Tensor,
                  truncation_steps: int = 2) -> float:
        """Single training step with gradient truncation."""

        # Prepare noisy frames
        noisy_batch = self.prepare_video_batch(video_batch)

        # Self forcing rollout
        generated = []
        for video in noisy_batch:
            frames = self.self_forcing_rollout(
                video[0],  # Initial frame
                num_frames=video.shape[0]
            )
            generated.append(torch.stack(frames))

        generated = torch.stack(generated)

        # Truncate gradients to limit memory
        if truncation_steps > 0:
            generated = generated.detach()
            generated.requires_grad = True

        # Compute loss on full sequences
        loss = self.compute_distribution_matching_loss(generated)

        # Update
        self.optimizer.zero_grad()
        loss.backward()
        torch.nn.utils.clip_grad_norm_(
            self.model.parameters(),
            max_norm=1.0
        )
        self.optimizer.step()

        return loss.item()
```

### Step 3: Implement Rolling KV Cache

Efficient attention caching for long sequences:

```python
class RollingKVCache:
    def __init__(self, max_cache_tokens: int = 4096,
                 hidden_dim: int = 768):
        self.max_cache = max_cache_tokens
        self.hidden_dim = hidden_dim
        self.cache = {
            "keys": torch.zeros(max_cache_tokens, hidden_dim),
            "values": torch.zeros(max_cache_tokens, hidden_dim)
        }
        self.pos = 0

    def append(self, keys: torch.Tensor,
              values: torch.Tensor):
        """Add new KV pairs, rolling out old ones."""

        new_len = keys.shape[0]

        if self.pos + new_len > self.max_cache:
            # Shift old entries out
            shift_amount = (self.pos + new_len) - self.max_cache
            self.cache["keys"] = torch.roll(
                self.cache["keys"],
                shifts=-shift_amount,
                dims=0
            )
            self.cache["values"] = torch.roll(
                self.cache["values"],
                shifts=-shift_amount,
                dims=0
            )
            self.pos = self.max_cache - new_len

        # Insert new entries
        self.cache["keys"][self.pos:self.pos + new_len] = keys
        self.cache["values"][self.pos:self.pos + new_len] = values
        self.pos += new_len

    def get(self) -> tuple:
        """Retrieve current cache."""
        return (self.cache["keys"][:self.pos],
                self.cache["values"][:self.pos])
```

### Step 4: Real-Time Inference

Generate video streams at 17 FPS:

```python
def generate_video_realtime(model: FewStepVideoDiffusion,
                           prompt: str,
                           output_fps: int = 24,
                           duration_seconds: float = 10.0
                           ) -> None:
    """Stream video generation in real-time."""

    total_frames = int(output_fps * duration_seconds)
    kv_cache = RollingKVCache()

    # Generate initial frame from prompt
    initial_frame = model.generate_from_text(prompt)

    frame_buffer = [initial_frame]
    start_time = time.time()

    for frame_idx in range(1, total_frames):
        # Get context from KV cache
        cached_keys, cached_values = kv_cache.get()

        # Generate next frame with cached context
        next_frame = model.reverse_diffusion_step(
            torch.randn(1, 3, 64, 64),
            t=0,
            context=cached_keys
        )

        # Update cache
        kv_cache.append(
            next_frame.detach(),
            next_frame.detach()
        )

        frame_buffer.append(next_frame)

        # Monitor real-time performance
        elapsed = time.time() - start_time
        expected_time = (frame_idx + 1) / output_fps

        if frame_idx % 10 == 0:
            fps = frame_idx / elapsed
            print(f"Frame {frame_idx}: {fps:.1f} FPS")

    # Save video
    save_video(frame_buffer, "output.mp4", fps=output_fps)
```

## Practical Guidance

**Few-Step Diffusion**: 4 steps balances quality and speed. Fewer steps reduce memory but hurt quality; more steps increase latency.

**Gradient Truncation**: Critical for memory efficiency in autoregressive training. Truncate after 2-3 steps to limit stored computations.

**Attention Windows**: Restrict attention during training to prevent distribution mismatch in long generation. Use 256-512 token windows.

**Real-Time Performance**: Rolling KV cache achieves O(TL) instead of O(L²+TL) by avoiding redundant recomputation. Enables single H100 streaming at 24fps.

**When to Apply**: Use Self Forcing for interactive video generation where inference speed matters, or when training video diffusion models with limited memory budgets.

## Reference

Self Forcing eliminates exposure bias by matching model behavior during training and inference. The key innovation is performing autoregressive rollout during training with distribution-matching losses over complete sequences, rather than frame-wise objectives. Combined with rolling KV cache, this enables efficient training and real-time inference.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.