Skip to content

Commit bdcae88

Browse files
GitLab CIclaude
andcommitted
V2: SDXL, Lightning, FreeInit, FreeNoise, IP-Adapter, Prompt Travel
Major architecture upgrade wrapping official diffusers AnimateDiff pipelines: Pipeline Architecture: - pipeline_v2.py: SD1.5 via diffusers AnimateDiffPipeline with all features - pipeline_sdxl.py: SDXL support (1024x1024, dual text encoder) - pipeline_lightning.py: ByteDance 1-8 step ultra-fast inference - Keep legacy pipeline for backward compat and SparseCtrl New Features (all via --pipeline v2): - FreeInit: temporal consistency (--freeinit-iters 3) - FreeNoise: long video >64 frames (--context-length 16) - Prompt Travel: frame-varying prompts via YAML config - IP-Adapter: image-conditioned generation (--ip-adapter-image) - 6 noise schedulers (DDIM, Euler, Euler A, DPM++, Karras, PNDM) CLI: python scripts/animate.py --pipeline [legacy|v2|sdxl|lightning] Engineering: - pyproject.toml for pip install (animatediff-cli command) - Dockerfile + docker-compose.yml for deployment - Example configs for all new pipelines - diffusers>=0.28.0, peft>=0.6.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 64ca27d commit bdcae88

18 files changed

+957
-34
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ build/
1616

1717
scripts/*
1818
!scripts/animate.py
19+
!scripts/__init__.py
1920

2021
*.ipynb
2122
*.safetensors

Dockerfile

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
FROM pytorch/pytorch:2.2.0-cuda12.1-cudnn8-runtime
2+
3+
WORKDIR /app
4+
5+
# System dependencies
6+
RUN apt-get update && apt-get install -y --no-install-recommends \
7+
git ffmpeg libsm6 libxext6 \
8+
&& rm -rf /var/lib/apt/lists/*
9+
10+
# Python dependencies
11+
COPY requirements.txt .
12+
RUN pip install --no-cache-dir -r requirements.txt
13+
14+
# Copy project
15+
COPY . .
16+
17+
# Pre-download tokenizer for faster cold start
18+
RUN python -c "from transformers import CLIPTokenizer; CLIPTokenizer.from_pretrained('openai/clip-vit-large-patch14')" 2>/dev/null || true
19+
20+
# Expose Gradio port
21+
EXPOSE 7860
22+
23+
# Default: run Gradio UI
24+
CMD ["python", "app.py"]
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""
2+
AnimateDiff-Lightning Pipeline — ultra-fast 1-8 step video generation.
3+
4+
Based on ByteDance's cross-model diffusion distillation (arXiv:2403.12706).
5+
Produces high-quality animations in as few as 2-4 denoising steps.
6+
"""
7+
8+
import torch
9+
from typing import Optional, Union, Dict
10+
11+
from diffusers import AnimateDiffPipeline, MotionAdapter, EulerDiscreteScheduler
12+
from diffusers.utils import export_to_gif, export_to_video
13+
from huggingface_hub import hf_hub_download
14+
from safetensors.torch import load_file
15+
16+
17+
LIGHTNING_REPO = "ByteDance/AnimateDiff-Lightning"
18+
VALID_STEPS = {1, 2, 4, 8}
19+
20+
21+
class AnimateDiffLightning:
22+
"""Ultra-fast AnimateDiff inference using distilled motion modules."""
23+
24+
def __init__(self, pipe: AnimateDiffPipeline, num_steps: int):
25+
self.pipe = pipe
26+
self.num_steps = num_steps
27+
28+
@classmethod
29+
def from_pretrained(
30+
cls,
31+
model_path: str = "emilianJR/epiCRealism",
32+
num_steps: int = 4,
33+
torch_dtype: torch.dtype = torch.float16,
34+
device: str = "cuda",
35+
enable_vae_slicing: bool = True,
36+
) -> "AnimateDiffLightning":
37+
if num_steps not in VALID_STEPS:
38+
raise ValueError(f"num_steps must be one of {VALID_STEPS}, got {num_steps}")
39+
40+
ckpt = f"animatediff_lightning_{num_steps}step_diffusers.safetensors"
41+
42+
adapter = MotionAdapter().to(device, torch_dtype)
43+
adapter.load_state_dict(
44+
load_file(hf_hub_download(LIGHTNING_REPO, ckpt), device=device)
45+
)
46+
47+
pipe = AnimateDiffPipeline.from_pretrained(
48+
model_path,
49+
motion_adapter=adapter,
50+
torch_dtype=torch_dtype,
51+
).to(device)
52+
53+
pipe.scheduler = EulerDiscreteScheduler.from_config(
54+
pipe.scheduler.config,
55+
timestep_spacing="trailing",
56+
beta_schedule="linear",
57+
)
58+
59+
if enable_vae_slicing:
60+
pipe.enable_vae_slicing()
61+
62+
return cls(pipe, num_steps)
63+
64+
@torch.no_grad()
65+
def generate(
66+
self,
67+
prompt: Union[str, Dict[int, str]],
68+
negative_prompt: str = "",
69+
num_frames: int = 16,
70+
height: int = 512,
71+
width: int = 512,
72+
guidance_scale: float = 1.0,
73+
seed: int = -1,
74+
output_type: str = "pil",
75+
decode_chunk_size: int = 4,
76+
):
77+
generator = None
78+
if seed >= 0:
79+
generator = torch.Generator(device=self.pipe.device).manual_seed(seed)
80+
81+
output = self.pipe(
82+
prompt=prompt,
83+
negative_prompt=negative_prompt,
84+
num_frames=num_frames,
85+
height=height,
86+
width=width,
87+
num_inference_steps=self.num_steps,
88+
guidance_scale=guidance_scale,
89+
generator=generator,
90+
output_type=output_type,
91+
decode_chunk_size=decode_chunk_size,
92+
)
93+
return output
94+
95+
def save(self, output, path: str, fps: int = 8):
96+
frames = output.frames[0]
97+
if path.endswith(".gif"):
98+
export_to_gif(frames, path)
99+
elif path.endswith(".mp4"):
100+
export_to_video(frames, path, fps=fps)
101+
else:
102+
export_to_gif(frames, path)
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
"""
2+
AnimateDiff SDXL Pipeline — wraps diffusers AnimateDiffSDXLPipeline.
3+
4+
Generates higher quality videos using Stable Diffusion XL as the base model.
5+
Supports SDXL-specific features like dual text encoders.
6+
"""
7+
8+
import torch
9+
from typing import Optional, Union, Dict
10+
11+
from diffusers import (
12+
AnimateDiffSDXLPipeline,
13+
MotionAdapter,
14+
DDIMScheduler,
15+
EulerDiscreteScheduler,
16+
DPMSolverMultistepScheduler,
17+
)
18+
from diffusers.utils import export_to_gif, export_to_video
19+
20+
SDXL_SCHEDULER_MAP = {
21+
"ddim": lambda config: DDIMScheduler.from_config(
22+
config, clip_sample=False, timestep_spacing="linspace",
23+
beta_schedule="linear", steps_offset=1,
24+
),
25+
"euler": lambda config: EulerDiscreteScheduler.from_config(config, beta_schedule="linear"),
26+
"dpm++": lambda config: DPMSolverMultistepScheduler.from_config(
27+
config, beta_schedule="linear", algorithm_type="dpmsolver++",
28+
),
29+
"dpm++-karras": lambda config: DPMSolverMultistepScheduler.from_config(
30+
config, beta_schedule="linear", algorithm_type="dpmsolver++", use_karras_sigmas=True,
31+
),
32+
}
33+
34+
35+
class AnimateDiffSDXL:
36+
"""High-level wrapper around diffusers AnimateDiffSDXLPipeline."""
37+
38+
def __init__(self, pipe: AnimateDiffSDXLPipeline):
39+
self.pipe = pipe
40+
41+
@classmethod
42+
def from_pretrained(
43+
cls,
44+
model_path: str = "stabilityai/stable-diffusion-xl-base-1.0",
45+
motion_adapter_path: str = "guoyww/animatediff-motion-adapter-sdxl-beta",
46+
torch_dtype: torch.dtype = torch.float16,
47+
device: str = "cuda",
48+
scheduler: str = "ddim",
49+
enable_vae_slicing: bool = True,
50+
enable_vae_tiling: bool = True,
51+
) -> "AnimateDiffSDXL":
52+
adapter = MotionAdapter.from_pretrained(motion_adapter_path, torch_dtype=torch_dtype)
53+
54+
scheduler_cls = SDXL_SCHEDULER_MAP.get(scheduler)
55+
sched_instance = None
56+
if scheduler_cls:
57+
sched_config = DDIMScheduler.from_pretrained(
58+
model_path, subfolder="scheduler"
59+
).config
60+
sched_instance = scheduler_cls(sched_config)
61+
62+
pipe = AnimateDiffSDXLPipeline.from_pretrained(
63+
model_path,
64+
motion_adapter=adapter,
65+
torch_dtype=torch_dtype,
66+
variant="fp16",
67+
)
68+
69+
if sched_instance is not None:
70+
pipe.scheduler = sched_instance
71+
72+
if enable_vae_slicing:
73+
pipe.enable_vae_slicing()
74+
if enable_vae_tiling:
75+
pipe.enable_vae_tiling()
76+
77+
pipe.to(device)
78+
return cls(pipe)
79+
80+
def load_lora(self, lora_path: str, adapter_name: str = "default", scale: float = 1.0):
81+
self.pipe.load_lora_weights(lora_path, adapter_name=adapter_name)
82+
self.pipe.set_adapters([adapter_name], [scale])
83+
84+
def set_scheduler(self, name: str):
85+
if name in SDXL_SCHEDULER_MAP:
86+
self.pipe.scheduler = SDXL_SCHEDULER_MAP[name](self.pipe.scheduler.config)
87+
else:
88+
raise ValueError(f"Unknown scheduler: {name}. Choose from: {list(SDXL_SCHEDULER_MAP.keys())}")
89+
90+
@torch.no_grad()
91+
def generate(
92+
self,
93+
prompt: Union[str, Dict[int, str]],
94+
negative_prompt: str = "low quality, worst quality, blurry",
95+
num_frames: int = 16,
96+
height: int = 1024,
97+
width: int = 1024,
98+
num_inference_steps: int = 20,
99+
guidance_scale: float = 8.0,
100+
seed: int = -1,
101+
output_type: str = "pil",
102+
decode_chunk_size: int = 4,
103+
):
104+
generator = None
105+
if seed >= 0:
106+
generator = torch.Generator(device=self.pipe.device).manual_seed(seed)
107+
108+
output = self.pipe(
109+
prompt=prompt,
110+
negative_prompt=negative_prompt,
111+
num_frames=num_frames,
112+
height=height,
113+
width=width,
114+
num_inference_steps=num_inference_steps,
115+
guidance_scale=guidance_scale,
116+
generator=generator,
117+
output_type=output_type,
118+
decode_chunk_size=decode_chunk_size,
119+
)
120+
return output
121+
122+
def save(self, output, path: str, fps: int = 8):
123+
frames = output.frames[0]
124+
if path.endswith(".gif"):
125+
export_to_gif(frames, path)
126+
elif path.endswith(".mp4"):
127+
export_to_video(frames, path, fps=fps)
128+
else:
129+
export_to_gif(frames, path)

0 commit comments

Comments
 (0)