File size: 7,060 Bytes
7755bd1
 
 
 
 
 
 
 
 
 
a8334dd
 
7755bd1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8d850e9
7755bd1
 
 
 
 
 
 
 
 
 
 
8d850e9
 
7755bd1
 
 
8d850e9
7755bd1
 
8d850e9
 
 
 
 
 
 
 
 
 
 
7755bd1
 
 
 
6ba71e5
7755bd1
 
 
 
 
 
8d850e9
7755bd1
 
 
 
 
 
 
 
8d850e9
7755bd1
 
 
8d850e9
7755bd1
 
8d850e9
 
 
 
 
 
 
 
7755bd1
8d850e9
7755bd1
 
 
a8334dd
7755bd1
 
a8334dd
7755bd1
 
 
 
8d850e9
7755bd1
 
 
 
40bd7e2
7755bd1
 
1a1f010
7755bd1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1a1f010
7755bd1
 
1a1f010
7755bd1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1a1f010
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
import os

# Set expandable segments for memory pressure (video DiT)
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")

import spaces  # MUST come before torch / diffusers / transformers
import torch
import gradio as gr
import tempfile
import time
import numpy as np
import imageio
from omegaconf import OmegaConf
from einops import rearrange

from pipeline import CausalInferencePipeline
from wan.modules.sparse_attention import calculate_chunk_sparsities

MODEL_ID = "Wan-AI/Wan2.1-T2V-1.3B"
LF_CKPT_ID = "mack-williams/Light-Forcing"

# --- Model loading (module scope, eagerly on cuda) ---

from huggingface_hub import snapshot_download

if not os.path.exists("wan_models/Wan2.1-T2V-1.3B"):
    print("Downloading Wan2.1-T2V-1.3B base model...")
    snapshot_download(
        repo_id=MODEL_ID,
        local_dir="wan_models/Wan2.1-T2V-1.3B",
    )

lf_ckpt_dir = "checkpoints"
os.makedirs(lf_ckpt_dir, exist_ok=True)
lf_ckpt_path = os.path.join(lf_ckpt_dir, "short_video_gen.pt")
if not os.path.exists(lf_ckpt_path):
    print("Downloading Light-Forcing checkpoint...")
    from huggingface_hub import hf_hub_download
    hf_hub_download(
        repo_id=LF_CKPT_ID,
        filename="short_video_gen.pt",
        local_dir=lf_ckpt_dir,
    )

# Load config
config = OmegaConf.load("configs/light_forcing_short.yaml")
default_config = OmegaConf.load("configs/default_config.yaml")
config = OmegaConf.merge(default_config, config)

# Calculate sparse attention sparsity schedule
num_frame_per_block = getattr(config, "num_frame_per_block", 1)
model_kwargs = dict(getattr(config, "model_kwargs", {}) or {})
local_attn_size = model_kwargs.get("local_attn_size", 21)
sparse_config = model_kwargs.get("sparse_config", {}) or {}
NUM_OUTPUT_FRAMES = 21
sparsity_list = calculate_chunk_sparsities(
    NUM_OUTPUT_FRAMES, num_frame_per_block, local_attn_size, sparse_config
)
if sparsity_list:
    sparse_config["sparsity_list"] = sparsity_list

print(f"Sparsity list: {sparsity_list}")

# Initialize pipeline (CausalInferencePipeline handles all model init internally)
pipeline = CausalInferencePipeline(config, device="cuda")

# Load Light Forcing checkpoint
state_dict = torch.load(lf_ckpt_path, map_location="cpu", weights_only=False)
pipeline.generator.load_state_dict(state_dict["generator_ema"])

pipeline = pipeline.to(dtype=torch.bfloat16)
pipeline.text_encoder.to("cuda")
pipeline.generator.to("cuda")
pipeline.vae.to("cuda")

pipeline.text_encoder.eval()
pipeline.generator.eval()
pipeline.vae.eval()

pipeline.text_encoder.requires_grad_(False)
pipeline.generator.requires_grad_(False)
pipeline.vae.requires_grad_(False)

print("Model loaded successfully!")


@spaces.GPU(duration=60)
def generate(
    prompt: str,
    seed: int = 42,
    num_output_frames: int = 21,
    progress=gr.Progress(track_tqdm=True),
):
    """Generate a short video from a text prompt using Light Forcing sparse attention."""
    if not prompt.strip():
        return None, "Please enter a prompt."

    torch.manual_seed(seed)
    torch.set_grad_enabled(False)

    start_time = time.time()

    # Generate noise (bfloat16 to match model)
    noise = torch.randn(
        [1, num_output_frames, 16, 64, 96],
        device="cuda",
        dtype=torch.bfloat16,
    )

    # Run inference using the pipeline's built-in method
    video = pipeline.inference(
        noise=noise,
        text_prompts=[prompt],
        return_latents=False,
        profile=False,
        low_memory=False,
    )

    # video: [b, t, c, h, w] in [0, 1]
    video = rearrange(video, 'b t c h w -> b t h w c').cpu()

    # Save as MP4
    video_np = (video[0].numpy() * 255).clip(0, 255).astype(np.uint8)
    with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as f:
        output_path = f.name
    imageio.mimsave(output_path, video_np, fps=16, quality=8)

    elapsed = time.time() - start_time
    print(f"Generation completed in {elapsed:.2f}s")

    return output_path, f"Generated {num_output_frames} frames in {elapsed:.1f}s"


CSS = """
#col-container { max-width: 900px; margin: 0 auto; }
.dark .gradio-container { color: var(--body-text-color); }
"""

with gr.Blocks() as demo:
    gr.Markdown("""
    # Light Forcing: Accelerating Autoregressive Video Diffusion via Sparse Attention

    Generate short videos from text prompts using the Light Forcing sparse attention method
    on top of the Wan2.1-T2V-1.3B autoregressive video diffusion model.

    [Paper](https://arxiv.org/abs/2602.04789) | [GitHub](https://github.com/chengtao-lv/LightForcing) | [Model](https://huggingface.co/mack-williams/Light-Forcing)
    """)

    with gr.Column(elem_id="col-container"):
        with gr.Row():
            prompt = gr.Textbox(
                show_label=False,
                placeholder="Describe the video you want to generate...",
                container=False,
                scale=4,
            )
            run_btn = gr.Button("Generate", variant="primary", scale=1)

        output_video = gr.Video(label="Generated Video")
        status_text = gr.Textbox(label="Status", interactive=False)

        with gr.Accordion("Advanced settings", open=False):
            seed = gr.Number(label="Seed", value=42, precision=0)
            num_frames = gr.Slider(
                label="Number of frames",
                minimum=3,
                maximum=21,
                value=21,
                step=3,
                info="21 frames ≈ 5 seconds at 16fps (short video mode)",
            )

    gr.Examples(
        examples=[
            ["A stylish woman walks down a Tokyo street filled with warm glowing neon and animated city signage. She wears a black leather jacket, a long red dress, and black boots, and carries a black purse. She wears sunglasses and red lipstick. She walks confidently and casually."],
            ["Several giant wooly mammoths approach treading through a snowy meadow, their long wooly fur lightly blows in the wind as they walk, snow covered trees and dramatic snow capped mountains in the distance, mid afternoon light with wispy clouds and a sun high in the distance creates a warm glow, the low camera view is stunning capturing the large furry mammal with beautiful photography, depth of field."],
            ["A movie trailer featuring the adventures of the 30 year old space man wearing a red wool knitted motorcycle helmet, blue sky, salt desert, cinematic style, shot on 35mm film, vivid colors."],
            ["Drone view of waves crashing against the rugged cliffs along Big Sur's garay point beach. The crashing blue waters create white-tipped waves, while the golden light of the setting sun illuminates the rocky shore."],
        ],
        inputs=[prompt],
        outputs=[output_video, status_text],
        fn=generate,
        cache_examples=True,
        cache_mode="lazy",
    )

    run_btn.click(
        fn=generate,
        inputs=[prompt, seed, num_frames],
        outputs=[output_video, status_text],
    )

demo.launch(mcp_server=True, theme=gr.themes.Citrus(), css=CSS)