Spaces:
Runtime error
Runtime error
| # app.py β Wan 2.2 Lite Edition (8GB RAM Safe) | |
| import os | |
| import spaces | |
| import torch | |
| from diffusers.pipelines.wan.pipeline_wan_i2v import WanImageToVideoPipeline | |
| from diffusers.models.transformers.transformer_wan import WanTransformer3DModel | |
| from diffusers.utils.export_utils import export_to_video | |
| import gradio as gr | |
| import tempfile | |
| import numpy as np | |
| from PIL import Image | |
| import random | |
| import gc | |
| import aoti | |
| from torchao.quantization import quantize_ | |
| from torchao.quantization import ( | |
| Float8DynamicActivationFloat8WeightConfig, | |
| Int8WeightOnlyConfig | |
| ) | |
| # ===================================================================== | |
| # BASIC CONFIG FOR LOW-RAM SYSTEMS | |
| # ===================================================================== | |
| MODEL_ID = "Wan-AI/Wan2.2-I2V-A14B-Diffusers" | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| HF_TOKEN = os.environ.get("HF_TOKEN") | |
| MAX_DIM = 480 | |
| MIN_DIM = 320 | |
| MULTIPLE_OF = 16 | |
| MAX_SEED = np.iinfo(np.int32).max | |
| FPS = 12 | |
| MIN_FRAMES = 8 | |
| MAX_FRAMES = 200 # around 2 seconds max | |
| # ===================================================================== | |
| # LOAD PIPELINE β STRIPPED DOWN (NO TRANSFORMER_2) | |
| # ===================================================================== | |
| pipe = WanImageToVideoPipeline.from_pretrained( | |
| MODEL_ID, | |
| transformer=WanTransformer3DModel.from_pretrained( | |
| MODEL_ID, | |
| subfolder="transformer", | |
| torch_dtype=torch.bfloat16, | |
| device_map="cuda", | |
| token=HF_TOKEN, | |
| ), | |
| torch_dtype=torch.bfloat16, | |
| ).to("cuda") | |
| # ===================================================================== | |
| # QUANTIZATION FOR 8GB VRAM | |
| # ===================================================================== | |
| quantize_(pipe.text_encoder, Int8WeightOnlyConfig()) | |
| quantize_(pipe.transformer, Float8DynamicActivationFloat8WeightConfig()) | |
| # AOTI compile small blocks | |
| aoti.aoti_blocks_load(pipe.transformer, "zerogpu-aoti/Wan2", variant="fp8da") | |
| # ===================================================================== | |
| # IMAGE RESIZE β 480p MAX | |
| # ===================================================================== | |
| def resize_image(image): | |
| w, h = image.size | |
| ratio = w / h | |
| if w > h: | |
| new_w = MAX_DIM | |
| new_h = int(new_w / ratio) | |
| else: | |
| new_h = MAX_DIM | |
| new_w = int(new_h * ratio) | |
| new_w = max(MIN_DIM, round(new_w / MULTIPLE_OF) * MULTIPLE_OF) | |
| new_h = max(MIN_DIM, round(new_h / MULTIPLE_OF) * MULTIPLE_OF) | |
| return image.resize((new_w, new_h), Image.LANCZOS) | |
| # ===================================================================== | |
| # FRAME COUNT | |
| # ===================================================================== | |
| def get_num_frames(seconds): | |
| return np.clip(int(seconds * FPS), MIN_FRAMES, MAX_FRAMES) | |
| # ===================================================================== | |
| # MAIN GENERATE FUNCTION | |
| # ===================================================================== | |
| def generate_video( | |
| input_image, | |
| prompt, | |
| steps=1, | |
| negative_prompt="", | |
| duration_seconds=1.5, | |
| seed=42, | |
| randomize_seed=False, | |
| ): | |
| if input_image is None: | |
| raise gr.Error("Upload an input image.") | |
| gc.collect() | |
| torch.cuda.empty_cache() | |
| num_frames = get_num_frames(duration_seconds) | |
| seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed) | |
| img = resize_image(input_image) | |
| out = pipe( | |
| image=img, | |
| prompt=prompt, | |
| negative_prompt=negative_prompt, | |
| num_frames=num_frames, | |
| height=img.height, | |
| width=img.width, | |
| guidance_scale=0.5, | |
| num_inference_steps=int(steps), | |
| generator=torch.Generator("cuda").manual_seed(seed), | |
| ) | |
| frames = out.frames[0] | |
| with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp: | |
| path = tmp.name | |
| export_to_video(frames, path, fps=FPS) | |
| gc.collect() | |
| torch.cuda.empty_cache() | |
| return path, seed | |
| # ===================================================================== | |
| # GRADIO UI β LIGHT VERSION | |
| # ===================================================================== | |
| with gr.Blocks() as demo: | |
| gr.Markdown("## π Wan 2.2 β Lite Edition (8GB RAM Optimized)") | |
| gr.Markdown("Runs at max 480p, 2-second videos, FP8 + INT8 optimized.") | |
| with gr.Row(): | |
| with gr.Column(): | |
| img = gr.Image(label="Input Image", type="pil") | |
| prompt_box = gr.Textbox("make this image move smoothly") | |
| duration = gr.Slider(0.8, 2.0, 1.5, label="Duration (seconds)") | |
| steps = gr.Slider(1, 3, 1, step=1, label="Steps (1 = fastest)") | |
| seed_box = gr.Slider(0, MAX_SEED, 42, label="Seed") | |
| rand_seed = gr.Checkbox(True, label="Randomize Seed") | |
| btn = gr.Button("Generate Video", variant="primary") | |
| with gr.Column(): | |
| out_video = gr.Video(label="Result") | |
| btn.click( | |
| generate_video, | |
| inputs=[img, prompt_box, steps, None, duration, seed_box, rand_seed], | |
| outputs=[out_video, seed_box], | |
| ) | |
| if __name__ == "__main__": | |
| demo.queue().launch() | |