Text-to-Video
Diffusers
Safetensors
English
MotifVideoPipeline
image-to-video
video-generation
diffusion-transformer
Instructions to use Motif-Technologies/Motif-Video-2B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use Motif-Technologies/Motif-Video-2B with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("Motif-Technologies/Motif-Video-2B", dtype=torch.bfloat16, device_map="cuda") prompt = "A vibrant blue jay perches gracefully on a slender branch, its feathers shimmering in the soft morning light. The bird's keen eyes scan the surroundings, capturing the essence of the tranquil forest. It flutters its wings briefly, showcasing the intricate patterns of blue, white, and black on its plumage. The background reveals a lush canopy of green leaves, with rays of sunlight filtering through, creating a dappled effect on the forest floor. The blue jay then tilts its head, emitting a melodious call that echoes through the serene woodland, adding a touch of magic to the peaceful scene." image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
feat: GGUF + SageAttention guide — DPMSolver++ default, benchmark, README update
#17
by gkalstn0 - opened
- .gitattributes +4 -0
- .gitignore +0 -0
- README.md +254 -28
- _fm_solvers_unipc.py +0 -759
- assets/sage_compare_BF16.webp +3 -0
- assets/sage_compare_Q4_K_M.webp +3 -0
- assets/sage_compare_Q5_K_M.webp +3 -0
- assets/sage_compare_Q8_0.webp +3 -0
- inference.py +126 -35
- pipeline_motif_video.py +0 -1388
.gitattributes
CHANGED
|
@@ -46,3 +46,7 @@ assets/fisherman.mp4 filter=lfs diff=lfs merge=lfs -text
|
|
| 46 |
assets/underwater.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 47 |
assets/vows.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 48 |
assets/woman.mp4 filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
assets/underwater.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 47 |
assets/vows.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 48 |
assets/woman.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 49 |
+
assets/sage_compare_BF16.webp filter=lfs diff=lfs merge=lfs -text
|
| 50 |
+
assets/sage_compare_Q4_K_M.webp filter=lfs diff=lfs merge=lfs -text
|
| 51 |
+
assets/sage_compare_Q5_K_M.webp filter=lfs diff=lfs merge=lfs -text
|
| 52 |
+
assets/sage_compare_Q8_0.webp filter=lfs diff=lfs merge=lfs -text
|
.gitignore
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
README.md
CHANGED
|
@@ -47,8 +47,24 @@ widget:
|
|
| 47 |
|
| 48 |
---
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
## 🔥 News
|
| 51 |
|
|
|
|
| 52 |
- **[2026-04-14]** We release **Motif-Video 2B**, our 2B-parameter text-to-video and image-to-video diffusion transformer, together with the full [technical report](https://arxiv.org/abs/2604.16503).
|
| 53 |
|
| 54 |
---
|
|
@@ -127,38 +143,71 @@ For the full derivation of why Shared Cross-Attention shares K/V but not Q, and
|
|
| 127 |
- CUDA-capable GPU with **30GB+ VRAM** (e.g., A100, H100) — for 24GB GPUs see [Memory-efficient Inference](#-memory-efficient-inference)
|
| 128 |
|
| 129 |
```bash
|
| 130 |
-
pip install "
|
|
|
|
| 131 |
```
|
| 132 |
|
| 133 |
### Text-to-Video (T2V)
|
| 134 |
|
| 135 |
```python
|
| 136 |
import torch
|
| 137 |
-
from diffusers import
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
from diffusers.utils import export_to_video
|
| 139 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
guider = AdaptiveProjectedGuidance(
|
| 141 |
guidance_scale=8.0,
|
| 142 |
adaptive_projected_guidance_rescale=12.0,
|
| 143 |
adaptive_projected_guidance_momentum=0.1,
|
| 144 |
use_original_formulation=True,
|
|
|
|
| 145 |
)
|
| 146 |
|
| 147 |
-
pipe =
|
| 148 |
"Motif-Technologies/Motif-Video-2B",
|
| 149 |
-
|
| 150 |
-
trust_remote_code=True,
|
| 151 |
torch_dtype=torch.bfloat16,
|
| 152 |
guider=guider,
|
| 153 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
pipe = pipe.to("cuda")
|
| 155 |
|
| 156 |
output = pipe(
|
| 157 |
-
prompt="A
|
|
|
|
| 158 |
height=736,
|
| 159 |
width=1280,
|
| 160 |
num_frames=121,
|
| 161 |
num_inference_steps=50,
|
|
|
|
|
|
|
| 162 |
)
|
| 163 |
|
| 164 |
export_to_video(output.frames[0], "output.mp4", fps=24)
|
|
@@ -168,7 +217,11 @@ export_to_video(output.frames[0], "output.mp4", fps=24)
|
|
| 168 |
|
| 169 |
```python
|
| 170 |
import torch
|
| 171 |
-
from diffusers import
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
from diffusers.utils import export_to_video, load_image
|
| 173 |
|
| 174 |
guider = AdaptiveProjectedGuidance(
|
|
@@ -176,26 +229,38 @@ guider = AdaptiveProjectedGuidance(
|
|
| 176 |
adaptive_projected_guidance_rescale=12.0,
|
| 177 |
adaptive_projected_guidance_momentum=0.1,
|
| 178 |
use_original_formulation=True,
|
|
|
|
| 179 |
)
|
| 180 |
|
| 181 |
-
pipe =
|
| 182 |
"Motif-Technologies/Motif-Video-2B",
|
| 183 |
-
|
| 184 |
-
trust_remote_code=True,
|
| 185 |
torch_dtype=torch.bfloat16,
|
| 186 |
guider=guider,
|
| 187 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
pipe = pipe.to("cuda")
|
| 189 |
|
| 190 |
image = load_image("https://huggingface.co/Motif-Technologies/Motif-Video-2B/resolve/main/assets/i2v_sample.jpg")
|
| 191 |
|
| 192 |
output = pipe(
|
| 193 |
-
prompt="Three friends stride through a sun-bleached meadow as a warm breeze ripples the tall dry grass around their legs.
|
|
|
|
| 194 |
image=image,
|
| 195 |
height=736,
|
| 196 |
width=1280,
|
| 197 |
num_frames=121,
|
| 198 |
num_inference_steps=50,
|
|
|
|
|
|
|
| 199 |
)
|
| 200 |
|
| 201 |
export_to_video(output.frames[0], "output.mp4", fps=24)
|
|
@@ -204,28 +269,31 @@ export_to_video(output.frames[0], "output.mp4", fps=24)
|
|
| 204 |
### CLI Inference
|
| 205 |
|
| 206 |
```bash
|
| 207 |
-
# Text-to-Video
|
| 208 |
python inference.py \
|
| 209 |
-
--prompt "A
|
| 210 |
--output t2v_output.mp4
|
| 211 |
|
| 212 |
-
#
|
| 213 |
python inference.py \
|
| 214 |
-
--
|
| 215 |
-
--
|
| 216 |
-
--output
|
| 217 |
```
|
| 218 |
|
| 219 |
-
See `inference.py` for all available options
|
| 220 |
|
| 221 |
### Recommended Settings
|
| 222 |
|
| 223 |
| Parameter | Default | Notes |
|
| 224 |
|---|---|---|
|
| 225 |
-
| Resolution |
|
| 226 |
| Frames | 121 | ~5 seconds at 24fps |
|
| 227 |
-
|
|
|
|
|
| 228 |
| Inference steps | 50 | |
|
|
|
|
|
|
|
| 229 |
| dtype | bfloat16 | Recommended for H100/A100 |
|
| 230 |
|
| 231 |
### 🔋 Memory-efficient Inference
|
|
@@ -239,16 +307,28 @@ export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
|
|
| 239 |
```
|
| 240 |
|
| 241 |
```python
|
| 242 |
-
pipe =
|
| 243 |
"Motif-Technologies/Motif-Video-2B",
|
| 244 |
-
|
| 245 |
-
trust_remote_code=True,
|
| 246 |
torch_dtype=torch.bfloat16,
|
| 247 |
guider=guider, # see T2V example above
|
| 248 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
pipe.enable_model_cpu_offload() # replaces pipe.to("cuda")
|
| 250 |
|
| 251 |
-
output = pipe(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
export_to_video(output.frames[0], "output.mp4", fps=24)
|
| 253 |
```
|
| 254 |
|
|
@@ -270,17 +350,29 @@ pip install torchao
|
|
| 270 |
```python
|
| 271 |
from torchao.quantization import quantize_, Float8WeightOnlyConfig
|
| 272 |
|
| 273 |
-
pipe =
|
| 274 |
"Motif-Technologies/Motif-Video-2B",
|
| 275 |
-
|
| 276 |
-
trust_remote_code=True,
|
| 277 |
torch_dtype=torch.bfloat16,
|
| 278 |
guider=guider, # see T2V example above
|
| 279 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
quantize_(pipe.transformer, Float8WeightOnlyConfig())
|
| 281 |
pipe.enable_model_cpu_offload()
|
| 282 |
|
| 283 |
-
output = pipe(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
export_to_video(output.frames[0], "output.mp4", fps=24)
|
| 285 |
```
|
| 286 |
|
|
@@ -295,6 +387,140 @@ This stores the transformer weights in FP8 (8-bit) instead of BF16 (16-bit), red
|
|
| 295 |
|
| 296 |
Official ComfyUI custom nodes for Motif-Video 2B are currently in development. Stay tuned for updates.
|
| 297 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
---
|
| 299 |
|
| 300 |
## 📊 Performance
|
|
|
|
| 47 |
|
| 48 |
---
|
| 49 |
|
| 50 |
+
<!--
|
| 51 |
+
NOTE: This README is written against the CURRENT state of diffusers PR #13551
|
| 52 |
+
(pre-merge). The PR currently has issues:
|
| 53 |
+
- negative_prompt defaults to None (should be built-in)
|
| 54 |
+
- use_linear_quadratic_schedule defaults to True (should be False)
|
| 55 |
+
- DPMSolverMultistepScheduler crashes (pipeline always passes sigmas)
|
| 56 |
+
- No built-in SageAttention support (requires manual patching)
|
| 57 |
+
|
| 58 |
+
Code examples below include workarounds (explicit negative_prompt,
|
| 59 |
+
use_linear_quadratic_schedule=False, _FlowDPMSolver subclass).
|
| 60 |
+
|
| 61 |
+
TODO: Update after PR feedback is applied, and again after merge.
|
| 62 |
+
Tracking: https://github.com/MotifTechnologies/diffusers/pull/1
|
| 63 |
+
-->
|
| 64 |
+
|
| 65 |
## 🔥 News
|
| 66 |
|
| 67 |
+
- **[2026-04-28]** **GGUF quantized weights** now available at [Motif-Video-2B-GGUF](https://huggingface.co/Motif-Technologies/Motif-Video-2B-GGUF) — up to 2.7 GB VRAM savings with no speed penalty. **SageAttention** support for ~2× faster inference. See [GGUF + SageAttention](#-gguf--sageattention) below.
|
| 68 |
- **[2026-04-14]** We release **Motif-Video 2B**, our 2B-parameter text-to-video and image-to-video diffusion transformer, together with the full [technical report](https://arxiv.org/abs/2604.16503).
|
| 69 |
|
| 70 |
---
|
|
|
|
| 143 |
- CUDA-capable GPU with **30GB+ VRAM** (e.g., A100, H100) — for 24GB GPUs see [Memory-efficient Inference](#-memory-efficient-inference)
|
| 144 |
|
| 145 |
```bash
|
| 146 |
+
pip install "transformers>=5.5.4" torch accelerate ftfy einops sentencepiece regex Pillow imageio imageio-ffmpeg
|
| 147 |
+
pip install git+https://github.com/waitingcheung/diffusers.git@feat/motif-video
|
| 148 |
```
|
| 149 |
|
| 150 |
### Text-to-Video (T2V)
|
| 151 |
|
| 152 |
```python
|
| 153 |
import torch
|
| 154 |
+
from diffusers import (
|
| 155 |
+
AdaptiveProjectedGuidance,
|
| 156 |
+
DPMSolverMultistepScheduler,
|
| 157 |
+
MotifVideoPipeline,
|
| 158 |
+
)
|
| 159 |
from diffusers.utils import export_to_video
|
| 160 |
|
| 161 |
+
|
| 162 |
+
# DPMSolver++ subclass: ignores pipeline-supplied sigmas and builds its own
|
| 163 |
+
# flow-matching schedule. Will be unnecessary once PR #13551 adds the
|
| 164 |
+
# _is_flow_multistep branch.
|
| 165 |
+
class FlowDPMSolver(DPMSolverMultistepScheduler):
|
| 166 |
+
def set_timesteps(self, num_inference_steps=None, device=None,
|
| 167 |
+
sigmas=None, mu=None, timesteps=None):
|
| 168 |
+
if sigmas is not None and num_inference_steps is None:
|
| 169 |
+
num_inference_steps = len(sigmas)
|
| 170 |
+
super().set_timesteps(
|
| 171 |
+
num_inference_steps=num_inference_steps,
|
| 172 |
+
device=device, timesteps=timesteps,
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
guider = AdaptiveProjectedGuidance(
|
| 177 |
guidance_scale=8.0,
|
| 178 |
adaptive_projected_guidance_rescale=12.0,
|
| 179 |
adaptive_projected_guidance_momentum=0.1,
|
| 180 |
use_original_formulation=True,
|
| 181 |
+
normalization_dims="spatial",
|
| 182 |
)
|
| 183 |
|
| 184 |
+
pipe = MotifVideoPipeline.from_pretrained(
|
| 185 |
"Motif-Technologies/Motif-Video-2B",
|
| 186 |
+
revision="diffusers-integration",
|
|
|
|
| 187 |
torch_dtype=torch.bfloat16,
|
| 188 |
guider=guider,
|
| 189 |
)
|
| 190 |
+
|
| 191 |
+
# DPMSolver++ for faster convergence
|
| 192 |
+
pipe.scheduler = FlowDPMSolver(
|
| 193 |
+
num_train_timesteps=pipe.scheduler.config.get("num_train_timesteps", 1000),
|
| 194 |
+
algorithm_type="dpmsolver++",
|
| 195 |
+
solver_order=2,
|
| 196 |
+
prediction_type="flow_prediction",
|
| 197 |
+
use_flow_sigmas=True,
|
| 198 |
+
flow_shift=15.0,
|
| 199 |
+
)
|
| 200 |
pipe = pipe.to("cuda")
|
| 201 |
|
| 202 |
output = pipe(
|
| 203 |
+
prompt="A woman standing in a sunlit field as flower petals swirl around her in slow motion. Each petal floats gently through the golden light, casting tiny shadows. Her hair moves like water, and time seems to stand still.",
|
| 204 |
+
negative_prompt="text overlay, graphic overlay, watermark, logo, subtitles, timestamp, broadcast graphics, UI elements, random letters, frozen pose, rigid, static expression, jerky motion, mechanical motion, discontinuous motion, flat framing, depthless, dull lighting, monotone, crushed shadows, blown-out highlights, shifting background, fading background, poor continuity, identity drift, deformation, flickering, ghosting, smearing, duplication, mutated proportions, inconsistent clothing, flat colors, desaturated, tonally compressed, poor background separation, exposure shift, uneven brightness, color balance shift",
|
| 205 |
height=736,
|
| 206 |
width=1280,
|
| 207 |
num_frames=121,
|
| 208 |
num_inference_steps=50,
|
| 209 |
+
frame_rate=24,
|
| 210 |
+
use_linear_quadratic_schedule=False,
|
| 211 |
)
|
| 212 |
|
| 213 |
export_to_video(output.frames[0], "output.mp4", fps=24)
|
|
|
|
| 217 |
|
| 218 |
```python
|
| 219 |
import torch
|
| 220 |
+
from diffusers import (
|
| 221 |
+
AdaptiveProjectedGuidance,
|
| 222 |
+
DPMSolverMultistepScheduler,
|
| 223 |
+
MotifVideoPipeline,
|
| 224 |
+
)
|
| 225 |
from diffusers.utils import export_to_video, load_image
|
| 226 |
|
| 227 |
guider = AdaptiveProjectedGuidance(
|
|
|
|
| 229 |
adaptive_projected_guidance_rescale=12.0,
|
| 230 |
adaptive_projected_guidance_momentum=0.1,
|
| 231 |
use_original_formulation=True,
|
| 232 |
+
normalization_dims="spatial",
|
| 233 |
)
|
| 234 |
|
| 235 |
+
pipe = MotifVideoPipeline.from_pretrained(
|
| 236 |
"Motif-Technologies/Motif-Video-2B",
|
| 237 |
+
revision="diffusers-integration",
|
|
|
|
| 238 |
torch_dtype=torch.bfloat16,
|
| 239 |
guider=guider,
|
| 240 |
)
|
| 241 |
+
|
| 242 |
+
pipe.scheduler = FlowDPMSolver(
|
| 243 |
+
num_train_timesteps=pipe.scheduler.config.get("num_train_timesteps", 1000),
|
| 244 |
+
algorithm_type="dpmsolver++",
|
| 245 |
+
solver_order=2,
|
| 246 |
+
prediction_type="flow_prediction",
|
| 247 |
+
use_flow_sigmas=True,
|
| 248 |
+
flow_shift=15.0,
|
| 249 |
+
)
|
| 250 |
pipe = pipe.to("cuda")
|
| 251 |
|
| 252 |
image = load_image("https://huggingface.co/Motif-Technologies/Motif-Video-2B/resolve/main/assets/i2v_sample.jpg")
|
| 253 |
|
| 254 |
output = pipe(
|
| 255 |
+
prompt="Three friends stride through a sun-bleached meadow as a warm breeze ripples the tall dry grass around their legs.",
|
| 256 |
+
negative_prompt="text overlay, graphic overlay, watermark, logo, subtitles, timestamp, broadcast graphics, UI elements, random letters, frozen pose, rigid, static expression, jerky motion, mechanical motion, discontinuous motion, flat framing, depthless, dull lighting, monotone, crushed shadows, blown-out highlights, shifting background, fading background, poor continuity, identity drift, deformation, flickering, ghosting, smearing, duplication, mutated proportions, inconsistent clothing, flat colors, desaturated, tonally compressed, poor background separation, exposure shift, uneven brightness, color balance shift",
|
| 257 |
image=image,
|
| 258 |
height=736,
|
| 259 |
width=1280,
|
| 260 |
num_frames=121,
|
| 261 |
num_inference_steps=50,
|
| 262 |
+
frame_rate=24,
|
| 263 |
+
use_linear_quadratic_schedule=False,
|
| 264 |
)
|
| 265 |
|
| 266 |
export_to_video(output.frames[0], "output.mp4", fps=24)
|
|
|
|
| 269 |
### CLI Inference
|
| 270 |
|
| 271 |
```bash
|
| 272 |
+
# Text-to-Video (default settings)
|
| 273 |
python inference.py \
|
| 274 |
+
--prompt "A woman standing in a sunlit field as..." \
|
| 275 |
--output t2v_output.mp4
|
| 276 |
|
| 277 |
+
# With SageAttention (~2x faster, requires sageattention package)
|
| 278 |
python inference.py \
|
| 279 |
+
--prompt "Three friends stride through a sun-bleached meadow..." \
|
| 280 |
+
--use-sage-attention \
|
| 281 |
+
--output t2v_output.mp4
|
| 282 |
```
|
| 283 |
|
| 284 |
+
See `inference.py --help` for all available options.
|
| 285 |
|
| 286 |
### Recommended Settings
|
| 287 |
|
| 288 |
| Parameter | Default | Notes |
|
| 289 |
|---|---|---|
|
| 290 |
+
| Resolution | 1280×736 | 720p, best quality |
|
| 291 |
| Frames | 121 | ~5 seconds at 24fps |
|
| 292 |
+
| Scheduler | DPMSolver++ | `solver_order=2`, `flow_shift=15.0` |
|
| 293 |
+
| Guidance scale | 8.0 | With APG (`normalization_dims="spatial"`) |
|
| 294 |
| Inference steps | 50 | |
|
| 295 |
+
| Negative prompt | (built-in) | See code examples above |
|
| 296 |
+
| `use_linear_quadratic_schedule` | `False` | Must be set explicitly |
|
| 297 |
| dtype | bfloat16 | Recommended for H100/A100 |
|
| 298 |
|
| 299 |
### 🔋 Memory-efficient Inference
|
|
|
|
| 307 |
```
|
| 308 |
|
| 309 |
```python
|
| 310 |
+
pipe = MotifVideoPipeline.from_pretrained(
|
| 311 |
"Motif-Technologies/Motif-Video-2B",
|
| 312 |
+
revision="diffusers-integration",
|
|
|
|
| 313 |
torch_dtype=torch.bfloat16,
|
| 314 |
guider=guider, # see T2V example above
|
| 315 |
)
|
| 316 |
+
pipe.scheduler = FlowDPMSolver(
|
| 317 |
+
num_train_timesteps=pipe.scheduler.config.get("num_train_timesteps", 1000),
|
| 318 |
+
algorithm_type="dpmsolver++",
|
| 319 |
+
solver_order=2,
|
| 320 |
+
prediction_type="flow_prediction",
|
| 321 |
+
use_flow_sigmas=True,
|
| 322 |
+
flow_shift=15.0,
|
| 323 |
+
)
|
| 324 |
pipe.enable_model_cpu_offload() # replaces pipe.to("cuda")
|
| 325 |
|
| 326 |
+
output = pipe(
|
| 327 |
+
prompt="...",
|
| 328 |
+
negative_prompt="...",
|
| 329 |
+
height=736, width=1280, num_frames=121, num_inference_steps=50,
|
| 330 |
+
frame_rate=24, use_linear_quadratic_schedule=False,
|
| 331 |
+
)
|
| 332 |
export_to_video(output.frames[0], "output.mp4", fps=24)
|
| 333 |
```
|
| 334 |
|
|
|
|
| 350 |
```python
|
| 351 |
from torchao.quantization import quantize_, Float8WeightOnlyConfig
|
| 352 |
|
| 353 |
+
pipe = MotifVideoPipeline.from_pretrained(
|
| 354 |
"Motif-Technologies/Motif-Video-2B",
|
| 355 |
+
revision="diffusers-integration",
|
|
|
|
| 356 |
torch_dtype=torch.bfloat16,
|
| 357 |
guider=guider, # see T2V example above
|
| 358 |
)
|
| 359 |
+
pipe.scheduler = FlowDPMSolver(
|
| 360 |
+
num_train_timesteps=pipe.scheduler.config.get("num_train_timesteps", 1000),
|
| 361 |
+
algorithm_type="dpmsolver++",
|
| 362 |
+
solver_order=2,
|
| 363 |
+
prediction_type="flow_prediction",
|
| 364 |
+
use_flow_sigmas=True,
|
| 365 |
+
flow_shift=15.0,
|
| 366 |
+
)
|
| 367 |
quantize_(pipe.transformer, Float8WeightOnlyConfig())
|
| 368 |
pipe.enable_model_cpu_offload()
|
| 369 |
|
| 370 |
+
output = pipe(
|
| 371 |
+
prompt="...",
|
| 372 |
+
negative_prompt="...",
|
| 373 |
+
height=736, width=1280, num_frames=121, num_inference_steps=50,
|
| 374 |
+
frame_rate=24, use_linear_quadratic_schedule=False,
|
| 375 |
+
)
|
| 376 |
export_to_video(output.frames[0], "output.mp4", fps=24)
|
| 377 |
```
|
| 378 |
|
|
|
|
| 387 |
|
| 388 |
Official ComfyUI custom nodes for Motif-Video 2B are currently in development. Stay tuned for updates.
|
| 389 |
|
| 390 |
+
---
|
| 391 |
+
|
| 392 |
+
### 🧊 GGUF + SageAttention
|
| 393 |
+
|
| 394 |
+
GGUF quantized transformer weights are available at [Motif-Video-2B-GGUF](https://huggingface.co/Motif-Technologies/Motif-Video-2B-GGUF), reducing VRAM with minimal quality loss. Combined with [SageAttention](https://github.com/thu-ml/SageAttention) for ~2× faster attention computation.
|
| 395 |
+
|
| 396 |
+
#### GGUF Inference
|
| 397 |
+
|
| 398 |
+
```bash
|
| 399 |
+
pip install gguf
|
| 400 |
+
```
|
| 401 |
+
|
| 402 |
+
```python
|
| 403 |
+
import torch
|
| 404 |
+
from diffusers import (
|
| 405 |
+
AdaptiveProjectedGuidance,
|
| 406 |
+
DPMSolverMultistepScheduler,
|
| 407 |
+
GGUFQuantizationConfig,
|
| 408 |
+
MotifVideoPipeline,
|
| 409 |
+
MotifVideoTransformer3DModel,
|
| 410 |
+
)
|
| 411 |
+
from diffusers.utils import export_to_video
|
| 412 |
+
from huggingface_hub import hf_hub_download
|
| 413 |
+
|
| 414 |
+
guider = AdaptiveProjectedGuidance(
|
| 415 |
+
guidance_scale=8.0,
|
| 416 |
+
adaptive_projected_guidance_rescale=12.0,
|
| 417 |
+
adaptive_projected_guidance_momentum=0.1,
|
| 418 |
+
use_original_formulation=True,
|
| 419 |
+
normalization_dims="spatial",
|
| 420 |
+
)
|
| 421 |
+
|
| 422 |
+
variant = "Q4_K_M" # Options: Q4_0, Q4_1, Q4_K_M, Q5_0, Q5_1, Q5_K_M, Q6_K, Q8_0, BF16
|
| 423 |
+
ckpt_path = hf_hub_download(
|
| 424 |
+
"Motif-Technologies/Motif-Video-2B-GGUF",
|
| 425 |
+
filename=f"motifv-2b-dev-{variant}.gguf",
|
| 426 |
+
)
|
| 427 |
+
|
| 428 |
+
transformer = MotifVideoTransformer3DModel.from_single_file(
|
| 429 |
+
ckpt_path,
|
| 430 |
+
quantization_config=GGUFQuantizationConfig(compute_dtype=torch.bfloat16),
|
| 431 |
+
config="Motif-Technologies/Motif-Video-2B",
|
| 432 |
+
revision="diffusers-integration",
|
| 433 |
+
subfolder="transformer",
|
| 434 |
+
torch_dtype=torch.bfloat16,
|
| 435 |
+
)
|
| 436 |
+
|
| 437 |
+
pipe = MotifVideoPipeline.from_pretrained(
|
| 438 |
+
"Motif-Technologies/Motif-Video-2B",
|
| 439 |
+
revision="diffusers-integration",
|
| 440 |
+
torch_dtype=torch.bfloat16,
|
| 441 |
+
guider=guider,
|
| 442 |
+
transformer=transformer,
|
| 443 |
+
)
|
| 444 |
+
|
| 445 |
+
pipe.scheduler = FlowDPMSolver(
|
| 446 |
+
num_train_timesteps=pipe.scheduler.config.get("num_train_timesteps", 1000),
|
| 447 |
+
algorithm_type="dpmsolver++",
|
| 448 |
+
solver_order=2,
|
| 449 |
+
prediction_type="flow_prediction",
|
| 450 |
+
use_flow_sigmas=True,
|
| 451 |
+
flow_shift=15.0,
|
| 452 |
+
)
|
| 453 |
+
pipe.enable_model_cpu_offload()
|
| 454 |
+
|
| 455 |
+
output = pipe(
|
| 456 |
+
prompt="A woman standing in a sunlit field as flower petals swirl around her in slow motion. Each petal floats gently through the golden light, casting tiny shadows. Her hair moves like water, and time seems to stand still.",
|
| 457 |
+
negative_prompt="text overlay, graphic overlay, watermark, logo, subtitles, timestamp, broadcast graphics, UI elements, random letters, frozen pose, rigid, static expression, jerky motion, mechanical motion, discontinuous motion, flat framing, depthless, dull lighting, monotone, crushed shadows, blown-out highlights, shifting background, fading background, poor continuity, identity drift, deformation, flickering, ghosting, smearing, duplication, mutated proportions, inconsistent clothing, flat colors, desaturated, tonally compressed, poor background separation, exposure shift, uneven brightness, color balance shift",
|
| 458 |
+
height=736,
|
| 459 |
+
width=1280,
|
| 460 |
+
num_frames=121,
|
| 461 |
+
num_inference_steps=50,
|
| 462 |
+
frame_rate=24,
|
| 463 |
+
use_linear_quadratic_schedule=False,
|
| 464 |
+
)
|
| 465 |
+
export_to_video(output.frames[0], "output.mp4", fps=24)
|
| 466 |
+
```
|
| 467 |
+
|
| 468 |
+
#### SageAttention (Optional, ~1.6× faster)
|
| 469 |
+
|
| 470 |
+
Same prompt and seed, 1280x736, 121 frames, 50 steps. Left = SDPA, Right = SageAttention.
|
| 471 |
+
|
| 472 |
+

|
| 473 |
+

|
| 474 |
+

|
| 475 |
+

|
| 476 |
+
|
| 477 |
+
[SageAttention](https://github.com/thu-ml/SageAttention) accelerates attention by quantizing Q/K to INT8 and V to FP8, reducing memory bandwidth. Works with all GGUF variants.
|
| 478 |
+
|
| 479 |
+
**Install** (build from source — PyPI only has 1.x, need 2.x):
|
| 480 |
+
|
| 481 |
+
```bash
|
| 482 |
+
# Set TORCH_CUDA_ARCH_LIST to match your GPU: "8.0" for A100, "9.0" for H100/H200
|
| 483 |
+
TORCH_CUDA_ARCH_LIST="9.0" pip install git+https://github.com/thu-ml/SageAttention.git --no-build-isolation
|
| 484 |
+
```
|
| 485 |
+
|
| 486 |
+
**Usage with `inference.py`:**
|
| 487 |
+
|
| 488 |
+
```bash
|
| 489 |
+
python inference.py --use-sage-attention --prompt "..."
|
| 490 |
+
```
|
| 491 |
+
|
| 492 |
+
**Notes:**
|
| 493 |
+
- Requires NVIDIA GPU with SM70+
|
| 494 |
+
- SM90+ (H100, H200) — FP8 kernels for maximum speedup
|
| 495 |
+
- SM80-SM89 (A100, RTX 3090, RTX 4090) — FP16 kernels (still faster than SDPA)
|
| 496 |
+
- SM70-SM75 (V100, RTX 2080 Ti) — FP16 kernels
|
| 497 |
+
- Set `TORCH_CUDA_ARCH_LIST` to match your GPU when building (e.g., `"8.6"` for RTX 3090, `"8.9"` for RTX 4090)
|
| 498 |
+
- No quality degradation observed across all GGUF variants
|
| 499 |
+
|
| 500 |
+
#### Benchmark
|
| 501 |
+
|
| 502 |
+
Measured on NVIDIA H200, 1280x736, 121 frames, 50 steps, DPMSolver++ (order=2, flow_shift=15.0):
|
| 503 |
+
|
| 504 |
+
| Variant | SDPA (s/it) | Sage (s/it) | Speedup | Peak alloc (GB) | Peak rsv (GB) | Total SDPA (s) | Total Sage (s) |
|
| 505 |
+
|---------|------------|------------|---------|-----------------|----------------|----------------|----------------|
|
| 506 |
+
| BF16 | 23.36 | 14.75 | 1.58x | 14.78 / 15.12 | 24.93 / 24.90 | 1184 | 754 |
|
| 507 |
+
| Q8_0 | 23.16 | 14.49 | 1.60x | 13.10 / 13.44 | 23.14 / 23.11 | 1178 | 744 |
|
| 508 |
+
| Q6_K | 23.21 | 14.55 | 1.60x | 12.62 / 12.95 | 22.72 / 22.69 | 1178 | 747 |
|
| 509 |
+
| Q5_K_M | 23.33 | 14.69 | 1.59x | 12.39 / 12.72 | 22.45 / 22.42 | 1184 | 754 |
|
| 510 |
+
| Q5_1 | 23.54 | 14.96 | 1.57x | 12.47 / 12.81 | 22.66 / 22.62 | 1193 | 764 |
|
| 511 |
+
| Q5_0 | 23.26 | 14.67 | 1.59x | 12.37 / 12.71 | 22.55 / 22.52 | 1179 | 750 |
|
| 512 |
+
| Q4_K_M | 23.25 | 14.59 | 1.60x | 12.19 / 12.53 | 22.22 / 22.18 | 1178 | 747 |
|
| 513 |
+
| Q4_1 | 23.31 | 14.68 | 1.59x | 12.26 / 12.60 | 22.26 / 22.22 | 1181 | 750 |
|
| 514 |
+
| Q4_0 | 23.33 | 14.75 | 1.58x | 12.14 / 12.47 | 22.18 / 22.14 | 1188 | 760 |
|
| 515 |
+
|
| 516 |
+
Peak alloc/rsv columns show SDPA / Sage values. Sage adds ~0.3 GB alloc overhead (INT8/FP8 quantization buffers) with no change in reserved memory.
|
| 517 |
+
|
| 518 |
+
**Key findings:**
|
| 519 |
+
- **~1.59x faster with SageAttention** — consistent across all quantization levels
|
| 520 |
+
- **VRAM unchanged** — sage overhead is negligible (~0.3 GB alloc)
|
| 521 |
+
- **GGUF + Sage stacks** — Q4_K_M + Sage achieves 14.59 s/it at 12.53 GB alloc (vs BF16 SDPA: 23.36 s/it at 14.78 GB)
|
| 522 |
+
|
| 523 |
+
|
| 524 |
---
|
| 525 |
|
| 526 |
## 📊 Performance
|
_fm_solvers_unipc.py
DELETED
|
@@ -1,759 +0,0 @@
|
|
| 1 |
-
# Copied from https://github.com/huggingface/diffusers/blob/v0.31.0/src/diffusers/schedulers/scheduling_unipc_multistep.py
|
| 2 |
-
# Convert unipc for flow matching
|
| 3 |
-
# Copyright 2024-2025 The Alibaba Wan Team Authors. All rights reserved.
|
| 4 |
-
|
| 5 |
-
import math
|
| 6 |
-
from typing import List, Optional, Tuple, Union
|
| 7 |
-
|
| 8 |
-
import numpy as np
|
| 9 |
-
import torch
|
| 10 |
-
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 11 |
-
from diffusers.schedulers.scheduling_utils import (
|
| 12 |
-
KarrasDiffusionSchedulers,
|
| 13 |
-
SchedulerMixin,
|
| 14 |
-
SchedulerOutput,
|
| 15 |
-
)
|
| 16 |
-
from diffusers.utils import deprecate, is_scipy_available
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
if is_scipy_available():
|
| 20 |
-
pass
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
class FlowUniPCMultistepScheduler(SchedulerMixin, ConfigMixin):
|
| 24 |
-
"""
|
| 25 |
-
`UniPCMultistepScheduler` is a training-free framework designed for the fast sampling of diffusion models.
|
| 26 |
-
|
| 27 |
-
This model inherits from [`SchedulerMixin`] and [`ConfigMixin`]. Check the superclass documentation for the generic
|
| 28 |
-
methods the library implements for all schedulers such as loading and saving.
|
| 29 |
-
|
| 30 |
-
Args:
|
| 31 |
-
num_train_timesteps (`int`, defaults to 1000):
|
| 32 |
-
The number of diffusion steps to train the model.
|
| 33 |
-
solver_order (`int`, default `2`):
|
| 34 |
-
The UniPC order which can be any positive integer. The effective order of accuracy is `solver_order + 1`
|
| 35 |
-
due to the UniC. It is recommended to use `solver_order=2` for guided sampling, and `solver_order=3` for
|
| 36 |
-
unconditional sampling.
|
| 37 |
-
prediction_type (`str`, defaults to "flow_prediction"):
|
| 38 |
-
Prediction type of the scheduler function; must be `flow_prediction` for this scheduler, which predicts
|
| 39 |
-
the flow of the diffusion process.
|
| 40 |
-
thresholding (`bool`, defaults to `False`):
|
| 41 |
-
Whether to use the "dynamic thresholding" method. This is unsuitable for latent-space diffusion models such
|
| 42 |
-
as Stable Diffusion.
|
| 43 |
-
dynamic_thresholding_ratio (`float`, defaults to 0.995):
|
| 44 |
-
The ratio for the dynamic thresholding method. Valid only when `thresholding=True`.
|
| 45 |
-
sample_max_value (`float`, defaults to 1.0):
|
| 46 |
-
The threshold value for dynamic thresholding. Valid only when `thresholding=True` and `predict_x0=True`.
|
| 47 |
-
predict_x0 (`bool`, defaults to `True`):
|
| 48 |
-
Whether to use the updating algorithm on the predicted x0.
|
| 49 |
-
solver_type (`str`, default `bh2`):
|
| 50 |
-
Solver type for UniPC. It is recommended to use `bh1` for unconditional sampling when steps < 10, and `bh2`
|
| 51 |
-
otherwise.
|
| 52 |
-
lower_order_final (`bool`, default `True`):
|
| 53 |
-
Whether to use lower-order solvers in the final steps. Only valid for < 15 inference steps. This can
|
| 54 |
-
stabilize the sampling of DPMSolver for steps < 15, especially for steps <= 10.
|
| 55 |
-
disable_corrector (`list`, default `[]`):
|
| 56 |
-
Decides which step to disable the corrector to mitigate the misalignment between `epsilon_theta(x_t, c)`
|
| 57 |
-
and `epsilon_theta(x_t^c, c)` which can influence convergence for a large guidance scale. Corrector is
|
| 58 |
-
usually disabled during the first few steps.
|
| 59 |
-
solver_p (`SchedulerMixin`, default `None`):
|
| 60 |
-
Any other scheduler that if specified, the algorithm becomes `solver_p + UniC`.
|
| 61 |
-
use_karras_sigmas (`bool`, *optional*, defaults to `False`):
|
| 62 |
-
Whether to use Karras sigmas for step sizes in the noise schedule during the sampling process. If `True`,
|
| 63 |
-
the sigmas are determined according to a sequence of noise levels {σi}.
|
| 64 |
-
use_exponential_sigmas (`bool`, *optional*, defaults to `False`):
|
| 65 |
-
Whether to use exponential sigmas for step sizes in the noise schedule during the sampling process.
|
| 66 |
-
timestep_spacing (`str`, defaults to `"linspace"`):
|
| 67 |
-
The way the timesteps should be scaled. Refer to Table 2 of the [Common Diffusion Noise Schedules and
|
| 68 |
-
Sample Steps are Flawed](https://huggingface.co/papers/2305.08891) for more information.
|
| 69 |
-
steps_offset (`int`, defaults to 0):
|
| 70 |
-
An offset added to the inference steps, as required by some model families.
|
| 71 |
-
final_sigmas_type (`str`, defaults to `"zero"`):
|
| 72 |
-
The final `sigma` value for the noise schedule during the sampling process. If `"sigma_min"`, the final
|
| 73 |
-
sigma is the same as the last sigma in the training schedule. If `zero`, the final sigma is set to 0.
|
| 74 |
-
"""
|
| 75 |
-
|
| 76 |
-
_compatibles = [e.name for e in KarrasDiffusionSchedulers]
|
| 77 |
-
order = 1
|
| 78 |
-
|
| 79 |
-
@register_to_config
|
| 80 |
-
def __init__(
|
| 81 |
-
self,
|
| 82 |
-
num_train_timesteps: int = 1000,
|
| 83 |
-
solver_order: int = 2,
|
| 84 |
-
prediction_type: str = "flow_prediction",
|
| 85 |
-
shift: Optional[float] = 1.0,
|
| 86 |
-
use_dynamic_shifting=False,
|
| 87 |
-
thresholding: bool = False,
|
| 88 |
-
dynamic_thresholding_ratio: float = 0.995,
|
| 89 |
-
sample_max_value: float = 1.0,
|
| 90 |
-
predict_x0: bool = True,
|
| 91 |
-
solver_type: str = "bh2",
|
| 92 |
-
lower_order_final: bool = True,
|
| 93 |
-
disable_corrector: List[int] = [],
|
| 94 |
-
solver_p: Optional[SchedulerMixin] = None,
|
| 95 |
-
timestep_spacing: str = "linspace",
|
| 96 |
-
steps_offset: int = 0,
|
| 97 |
-
final_sigmas_type: Optional[str] = "zero", # "zero", "sigma_min"
|
| 98 |
-
):
|
| 99 |
-
if solver_type not in ["bh1", "bh2"]:
|
| 100 |
-
if solver_type in ["midpoint", "heun", "logrho"]:
|
| 101 |
-
self.register_to_config(solver_type="bh2")
|
| 102 |
-
else:
|
| 103 |
-
raise NotImplementedError(f"{solver_type} is not implemented for {self.__class__}")
|
| 104 |
-
|
| 105 |
-
self.predict_x0 = predict_x0
|
| 106 |
-
# setable values
|
| 107 |
-
self.num_inference_steps = None
|
| 108 |
-
alphas = np.linspace(1, 1 / num_train_timesteps, num_train_timesteps)[::-1].copy()
|
| 109 |
-
sigmas = 1.0 - alphas
|
| 110 |
-
sigmas = torch.from_numpy(sigmas).to(dtype=torch.float32)
|
| 111 |
-
|
| 112 |
-
if not use_dynamic_shifting:
|
| 113 |
-
# when use_dynamic_shifting is True, we apply the timestep shifting on the fly based on the image resolution
|
| 114 |
-
sigmas = shift * sigmas / (1 + (shift - 1) * sigmas) # pyright: ignore
|
| 115 |
-
|
| 116 |
-
self.sigmas = sigmas
|
| 117 |
-
self.timesteps = sigmas * num_train_timesteps
|
| 118 |
-
|
| 119 |
-
self.model_outputs = [None] * solver_order
|
| 120 |
-
self.timestep_list = [None] * solver_order
|
| 121 |
-
self.lower_order_nums = 0
|
| 122 |
-
self.disable_corrector = disable_corrector
|
| 123 |
-
self.solver_p = solver_p
|
| 124 |
-
self.last_sample = None
|
| 125 |
-
self._step_index = None
|
| 126 |
-
self._begin_index = None
|
| 127 |
-
|
| 128 |
-
self.sigmas = self.sigmas.to("cpu") # to avoid too much CPU/GPU communication
|
| 129 |
-
self.sigma_min = self.sigmas[-1].item()
|
| 130 |
-
self.sigma_max = self.sigmas[0].item()
|
| 131 |
-
|
| 132 |
-
@property
|
| 133 |
-
def step_index(self):
|
| 134 |
-
"""
|
| 135 |
-
The index counter for current timestep. It will increase 1 after each scheduler step.
|
| 136 |
-
"""
|
| 137 |
-
return self._step_index
|
| 138 |
-
|
| 139 |
-
@property
|
| 140 |
-
def begin_index(self):
|
| 141 |
-
"""
|
| 142 |
-
The index for the first timestep. It should be set from pipeline with `set_begin_index` method.
|
| 143 |
-
"""
|
| 144 |
-
return self._begin_index
|
| 145 |
-
|
| 146 |
-
# Copied from diffusers.schedulers.scheduling_dpmsolver_multistep.DPMSolverMultistepScheduler.set_begin_index
|
| 147 |
-
def set_begin_index(self, begin_index: int = 0):
|
| 148 |
-
"""
|
| 149 |
-
Sets the begin index for the scheduler. This function should be run from pipeline before the inference.
|
| 150 |
-
|
| 151 |
-
Args:
|
| 152 |
-
begin_index (`int`):
|
| 153 |
-
The begin index for the scheduler.
|
| 154 |
-
"""
|
| 155 |
-
self._begin_index = begin_index
|
| 156 |
-
|
| 157 |
-
# Modified from diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler.set_timesteps
|
| 158 |
-
def set_timesteps(
|
| 159 |
-
self,
|
| 160 |
-
num_inference_steps: Union[int, None] = None,
|
| 161 |
-
device: Optional[Union[str, torch.device]] = None,
|
| 162 |
-
sigmas: Optional[List[float]] = None,
|
| 163 |
-
mu: Optional[Union[float, None]] = None,
|
| 164 |
-
shift: Optional[Union[float, None]] = None,
|
| 165 |
-
):
|
| 166 |
-
"""
|
| 167 |
-
Sets the discrete timesteps used for the diffusion chain (to be run before inference).
|
| 168 |
-
Args:
|
| 169 |
-
num_inference_steps (`int`):
|
| 170 |
-
Total number of the spacing of the time steps.
|
| 171 |
-
device (`str` or `torch.device`, *optional*):
|
| 172 |
-
The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.
|
| 173 |
-
"""
|
| 174 |
-
|
| 175 |
-
if self.config.use_dynamic_shifting and mu is None:
|
| 176 |
-
raise ValueError(" you have to pass a value for `mu` when `use_dynamic_shifting` is set to be `True`")
|
| 177 |
-
|
| 178 |
-
if sigmas is None:
|
| 179 |
-
sigmas = np.linspace(self.sigma_max, self.sigma_min, num_inference_steps + 1).copy()[:-1] # pyright: ignore
|
| 180 |
-
|
| 181 |
-
if self.config.use_dynamic_shifting:
|
| 182 |
-
sigmas = self.time_shift(mu, 1.0, sigmas) # pyright: ignore
|
| 183 |
-
else:
|
| 184 |
-
if shift is None:
|
| 185 |
-
shift = self.config.shift
|
| 186 |
-
sigmas = shift * sigmas / (1 + (shift - 1) * sigmas) # pyright: ignore
|
| 187 |
-
|
| 188 |
-
if self.config.final_sigmas_type == "sigma_min":
|
| 189 |
-
sigma_last = self.config.sigma_min
|
| 190 |
-
elif self.config.final_sigmas_type == "zero":
|
| 191 |
-
sigma_last = 0
|
| 192 |
-
else:
|
| 193 |
-
raise ValueError(
|
| 194 |
-
f"`final_sigmas_type` must be one of 'zero', or 'sigma_min', but got {self.config.final_sigmas_type}"
|
| 195 |
-
)
|
| 196 |
-
|
| 197 |
-
timesteps = sigmas * self.config.num_train_timesteps
|
| 198 |
-
sigmas = np.concatenate([sigmas, [sigma_last]]).astype(np.float32) # pyright: ignore
|
| 199 |
-
|
| 200 |
-
self.sigmas = torch.from_numpy(sigmas)
|
| 201 |
-
self.timesteps = torch.from_numpy(timesteps).to(device=device, dtype=torch.int64)
|
| 202 |
-
|
| 203 |
-
self.num_inference_steps = len(timesteps)
|
| 204 |
-
|
| 205 |
-
self.model_outputs = [
|
| 206 |
-
None,
|
| 207 |
-
] * self.config.solver_order
|
| 208 |
-
self.lower_order_nums = 0
|
| 209 |
-
self.last_sample = None
|
| 210 |
-
if self.solver_p:
|
| 211 |
-
self.solver_p.set_timesteps(self.num_inference_steps, device=device)
|
| 212 |
-
|
| 213 |
-
# add an index counter for schedulers that allow duplicated timesteps
|
| 214 |
-
self._step_index = None
|
| 215 |
-
self._begin_index = None
|
| 216 |
-
self.sigmas = self.sigmas.to("cpu") # to avoid too much CPU/GPU communication
|
| 217 |
-
|
| 218 |
-
# Copied from diffusers.schedulers.scheduling_ddpm.DDPMScheduler._threshold_sample
|
| 219 |
-
def _threshold_sample(self, sample: torch.Tensor) -> torch.Tensor:
|
| 220 |
-
"""
|
| 221 |
-
"Dynamic thresholding: At each sampling step we set s to a certain percentile absolute pixel value in xt0 (the
|
| 222 |
-
prediction of x_0 at timestep t), and if s > 1, then we threshold xt0 to the range [-s, s] and then divide by
|
| 223 |
-
s. Dynamic thresholding pushes saturated pixels (those near -1 and 1) inwards, thereby actively preventing
|
| 224 |
-
pixels from saturation at each step. We find that dynamic thresholding results in significantly better
|
| 225 |
-
photorealism as well as better image-text alignment, especially when using very large guidance weights."
|
| 226 |
-
|
| 227 |
-
https://arxiv.org/abs/2205.11487
|
| 228 |
-
"""
|
| 229 |
-
dtype = sample.dtype
|
| 230 |
-
batch_size, channels, *remaining_dims = sample.shape
|
| 231 |
-
|
| 232 |
-
if dtype not in (torch.float32, torch.float64):
|
| 233 |
-
sample = sample.float() # upcast for quantile calculation, and clamp not implemented for cpu half
|
| 234 |
-
|
| 235 |
-
# Flatten sample for doing quantile calculation along each image
|
| 236 |
-
sample = sample.reshape(batch_size, channels * np.prod(remaining_dims))
|
| 237 |
-
|
| 238 |
-
abs_sample = sample.abs() # "a certain percentile absolute pixel value"
|
| 239 |
-
|
| 240 |
-
s = torch.quantile(abs_sample, self.config.dynamic_thresholding_ratio, dim=1)
|
| 241 |
-
s = torch.clamp(
|
| 242 |
-
s, min=1, max=self.config.sample_max_value
|
| 243 |
-
) # When clamped to min=1, equivalent to standard clipping to [-1, 1]
|
| 244 |
-
s = s.unsqueeze(1) # (batch_size, 1) because clamp will broadcast along dim=0
|
| 245 |
-
sample = torch.clamp(sample, -s, s) / s # "we threshold xt0 to the range [-s, s] and then divide by s"
|
| 246 |
-
|
| 247 |
-
sample = sample.reshape(batch_size, channels, *remaining_dims)
|
| 248 |
-
sample = sample.to(dtype)
|
| 249 |
-
|
| 250 |
-
return sample
|
| 251 |
-
|
| 252 |
-
# Copied from diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler._sigma_to_t
|
| 253 |
-
def _sigma_to_t(self, sigma):
|
| 254 |
-
return sigma * self.config.num_train_timesteps
|
| 255 |
-
|
| 256 |
-
def _sigma_to_alpha_sigma_t(self, sigma):
|
| 257 |
-
return 1 - sigma, sigma
|
| 258 |
-
|
| 259 |
-
# Copied from diffusers.schedulers.scheduling_flow_match_euler_discrete.set_timesteps
|
| 260 |
-
def time_shift(self, mu: float, sigma: float, t: torch.Tensor):
|
| 261 |
-
return math.exp(mu) / (math.exp(mu) + (1 / t - 1) ** sigma)
|
| 262 |
-
|
| 263 |
-
def convert_model_output(
|
| 264 |
-
self,
|
| 265 |
-
model_output: torch.Tensor,
|
| 266 |
-
*args,
|
| 267 |
-
sample: Optional[torch.Tensor] = None,
|
| 268 |
-
**kwargs,
|
| 269 |
-
) -> torch.Tensor:
|
| 270 |
-
r"""
|
| 271 |
-
Convert the model output to the corresponding type the UniPC algorithm needs.
|
| 272 |
-
|
| 273 |
-
Args:
|
| 274 |
-
model_output (`torch.Tensor`):
|
| 275 |
-
The direct output from the learned diffusion model.
|
| 276 |
-
timestep (`int`):
|
| 277 |
-
The current discrete timestep in the diffusion chain.
|
| 278 |
-
sample (`torch.Tensor`):
|
| 279 |
-
A current instance of a sample created by the diffusion process.
|
| 280 |
-
|
| 281 |
-
Returns:
|
| 282 |
-
`torch.Tensor`:
|
| 283 |
-
The converted model output.
|
| 284 |
-
"""
|
| 285 |
-
timestep = args[0] if len(args) > 0 else kwargs.pop("timestep", None)
|
| 286 |
-
if sample is None:
|
| 287 |
-
if len(args) > 1:
|
| 288 |
-
sample = args[1]
|
| 289 |
-
else:
|
| 290 |
-
raise ValueError("missing `sample` as a required keyward argument")
|
| 291 |
-
if timestep is not None:
|
| 292 |
-
deprecate(
|
| 293 |
-
"timesteps",
|
| 294 |
-
"1.0.0",
|
| 295 |
-
"Passing `timesteps` is deprecated and has no effect as model output conversion is now handled via an internal counter `self.step_index`",
|
| 296 |
-
)
|
| 297 |
-
|
| 298 |
-
sigma = self.sigmas[self.step_index]
|
| 299 |
-
alpha_t, sigma_t = self._sigma_to_alpha_sigma_t(sigma)
|
| 300 |
-
|
| 301 |
-
if self.predict_x0:
|
| 302 |
-
if self.config.prediction_type == "flow_prediction":
|
| 303 |
-
sigma_t = self.sigmas[self.step_index]
|
| 304 |
-
x0_pred = sample - sigma_t * model_output
|
| 305 |
-
else:
|
| 306 |
-
raise ValueError(
|
| 307 |
-
f"prediction_type given as {self.config.prediction_type} must be one of `epsilon`, `sample`,"
|
| 308 |
-
" `v_prediction` or `flow_prediction` for the UniPCMultistepScheduler."
|
| 309 |
-
)
|
| 310 |
-
|
| 311 |
-
if self.config.thresholding:
|
| 312 |
-
x0_pred = self._threshold_sample(x0_pred)
|
| 313 |
-
|
| 314 |
-
return x0_pred
|
| 315 |
-
else:
|
| 316 |
-
if self.config.prediction_type == "flow_prediction":
|
| 317 |
-
sigma_t = self.sigmas[self.step_index]
|
| 318 |
-
epsilon = sample - (1 - sigma_t) * model_output
|
| 319 |
-
else:
|
| 320 |
-
raise ValueError(
|
| 321 |
-
f"prediction_type given as {self.config.prediction_type} must be one of `epsilon`, `sample`,"
|
| 322 |
-
" `v_prediction` or `flow_prediction` for the UniPCMultistepScheduler."
|
| 323 |
-
)
|
| 324 |
-
|
| 325 |
-
if self.config.thresholding:
|
| 326 |
-
sigma_t = self.sigmas[self.step_index]
|
| 327 |
-
x0_pred = sample - sigma_t * model_output
|
| 328 |
-
x0_pred = self._threshold_sample(x0_pred)
|
| 329 |
-
epsilon = model_output + x0_pred
|
| 330 |
-
|
| 331 |
-
return epsilon
|
| 332 |
-
|
| 333 |
-
def multistep_uni_p_bh_update(
|
| 334 |
-
self,
|
| 335 |
-
model_output: torch.Tensor,
|
| 336 |
-
*args,
|
| 337 |
-
sample: Optional[torch.Tensor] = None,
|
| 338 |
-
order: Optional[int] = None,
|
| 339 |
-
**kwargs,
|
| 340 |
-
) -> torch.Tensor:
|
| 341 |
-
"""
|
| 342 |
-
One step for the UniP (B(h) version). Alternatively, `self.solver_p` is used if is specified.
|
| 343 |
-
|
| 344 |
-
Args:
|
| 345 |
-
model_output (`torch.Tensor`):
|
| 346 |
-
The direct output from the learned diffusion model at the current timestep.
|
| 347 |
-
prev_timestep (`int`):
|
| 348 |
-
The previous discrete timestep in the diffusion chain.
|
| 349 |
-
sample (`torch.Tensor`):
|
| 350 |
-
A current instance of a sample created by the diffusion process.
|
| 351 |
-
order (`int`):
|
| 352 |
-
The order of UniP at this timestep (corresponds to the *p* in UniPC-p).
|
| 353 |
-
|
| 354 |
-
Returns:
|
| 355 |
-
`torch.Tensor`:
|
| 356 |
-
The sample tensor at the previous timestep.
|
| 357 |
-
"""
|
| 358 |
-
prev_timestep = args[0] if len(args) > 0 else kwargs.pop("prev_timestep", None)
|
| 359 |
-
if sample is None:
|
| 360 |
-
if len(args) > 1:
|
| 361 |
-
sample = args[1]
|
| 362 |
-
else:
|
| 363 |
-
raise ValueError(" missing `sample` as a required keyward argument")
|
| 364 |
-
if order is None:
|
| 365 |
-
if len(args) > 2:
|
| 366 |
-
order = args[2]
|
| 367 |
-
else:
|
| 368 |
-
raise ValueError(" missing `order` as a required keyward argument")
|
| 369 |
-
if prev_timestep is not None:
|
| 370 |
-
deprecate(
|
| 371 |
-
"prev_timestep",
|
| 372 |
-
"1.0.0",
|
| 373 |
-
"Passing `prev_timestep` is deprecated and has no effect as model output conversion is now handled via an internal counter `self.step_index`",
|
| 374 |
-
)
|
| 375 |
-
model_output_list = self.model_outputs
|
| 376 |
-
|
| 377 |
-
s0 = self.timestep_list[-1]
|
| 378 |
-
m0 = model_output_list[-1]
|
| 379 |
-
x = sample
|
| 380 |
-
|
| 381 |
-
if self.solver_p:
|
| 382 |
-
x_t = self.solver_p.step(model_output, s0, x).prev_sample
|
| 383 |
-
return x_t
|
| 384 |
-
|
| 385 |
-
sigma_t, sigma_s0 = self.sigmas[self.step_index + 1], self.sigmas[self.step_index] # pyright: ignore
|
| 386 |
-
alpha_t, sigma_t = self._sigma_to_alpha_sigma_t(sigma_t)
|
| 387 |
-
alpha_s0, sigma_s0 = self._sigma_to_alpha_sigma_t(sigma_s0)
|
| 388 |
-
|
| 389 |
-
lambda_t = torch.log(alpha_t) - torch.log(sigma_t)
|
| 390 |
-
lambda_s0 = torch.log(alpha_s0) - torch.log(sigma_s0)
|
| 391 |
-
|
| 392 |
-
h = lambda_t - lambda_s0
|
| 393 |
-
device = sample.device
|
| 394 |
-
|
| 395 |
-
rks = []
|
| 396 |
-
D1s = []
|
| 397 |
-
for i in range(1, order):
|
| 398 |
-
si = self.step_index - i # pyright: ignore
|
| 399 |
-
mi = model_output_list[-(i + 1)]
|
| 400 |
-
alpha_si, sigma_si = self._sigma_to_alpha_sigma_t(self.sigmas[si])
|
| 401 |
-
lambda_si = torch.log(alpha_si) - torch.log(sigma_si)
|
| 402 |
-
rk = (lambda_si - lambda_s0) / h
|
| 403 |
-
rks.append(rk)
|
| 404 |
-
D1s.append((mi - m0) / rk) # pyright: ignore
|
| 405 |
-
|
| 406 |
-
rks.append(1.0)
|
| 407 |
-
rks = torch.tensor(rks, device=device)
|
| 408 |
-
|
| 409 |
-
R = []
|
| 410 |
-
b = []
|
| 411 |
-
|
| 412 |
-
hh = -h if self.predict_x0 else h
|
| 413 |
-
h_phi_1 = torch.expm1(hh) # h\phi_1(h) = e^h - 1
|
| 414 |
-
h_phi_k = h_phi_1 / hh - 1
|
| 415 |
-
|
| 416 |
-
factorial_i = 1
|
| 417 |
-
|
| 418 |
-
if self.config.solver_type == "bh1":
|
| 419 |
-
B_h = hh
|
| 420 |
-
elif self.config.solver_type == "bh2":
|
| 421 |
-
B_h = torch.expm1(hh)
|
| 422 |
-
else:
|
| 423 |
-
raise NotImplementedError()
|
| 424 |
-
|
| 425 |
-
for i in range(1, order + 1):
|
| 426 |
-
R.append(torch.pow(rks, i - 1))
|
| 427 |
-
b.append(h_phi_k * factorial_i / B_h)
|
| 428 |
-
factorial_i *= i + 1
|
| 429 |
-
h_phi_k = h_phi_k / hh - 1 / factorial_i
|
| 430 |
-
|
| 431 |
-
R = torch.stack(R)
|
| 432 |
-
b = torch.tensor(b, device=device)
|
| 433 |
-
|
| 434 |
-
if len(D1s) > 0:
|
| 435 |
-
D1s = torch.stack(D1s, dim=1) # (B, K)
|
| 436 |
-
# for order 2, we use a simplified version
|
| 437 |
-
if order == 2:
|
| 438 |
-
rhos_p = torch.tensor([0.5], dtype=x.dtype, device=device)
|
| 439 |
-
else:
|
| 440 |
-
rhos_p = torch.linalg.solve(R[:-1, :-1], b[:-1]).to(device).to(x.dtype)
|
| 441 |
-
else:
|
| 442 |
-
D1s = None
|
| 443 |
-
|
| 444 |
-
if self.predict_x0:
|
| 445 |
-
x_t_ = sigma_t / sigma_s0 * x - alpha_t * h_phi_1 * m0
|
| 446 |
-
if D1s is not None:
|
| 447 |
-
pred_res = torch.einsum("k,bkc...->bc...", rhos_p, D1s) # pyright: ignore
|
| 448 |
-
else:
|
| 449 |
-
pred_res = 0
|
| 450 |
-
x_t = x_t_ - alpha_t * B_h * pred_res
|
| 451 |
-
else:
|
| 452 |
-
x_t_ = alpha_t / alpha_s0 * x - sigma_t * h_phi_1 * m0
|
| 453 |
-
if D1s is not None:
|
| 454 |
-
pred_res = torch.einsum("k,bkc...->bc...", rhos_p, D1s) # pyright: ignore
|
| 455 |
-
else:
|
| 456 |
-
pred_res = 0
|
| 457 |
-
x_t = x_t_ - sigma_t * B_h * pred_res
|
| 458 |
-
|
| 459 |
-
x_t = x_t.to(x.dtype)
|
| 460 |
-
return x_t
|
| 461 |
-
|
| 462 |
-
def multistep_uni_c_bh_update(
|
| 463 |
-
self,
|
| 464 |
-
this_model_output: torch.Tensor,
|
| 465 |
-
*args,
|
| 466 |
-
last_sample: Optional[torch.Tensor] = None,
|
| 467 |
-
this_sample: Optional[torch.Tensor] = None,
|
| 468 |
-
order: Optional[int] = None,
|
| 469 |
-
**kwargs,
|
| 470 |
-
) -> torch.Tensor:
|
| 471 |
-
"""
|
| 472 |
-
One step for the UniC (B(h) version).
|
| 473 |
-
|
| 474 |
-
Args:
|
| 475 |
-
this_model_output (`torch.Tensor`):
|
| 476 |
-
The model outputs at `x_t`.
|
| 477 |
-
this_timestep (`int`):
|
| 478 |
-
The current timestep `t`.
|
| 479 |
-
last_sample (`torch.Tensor`):
|
| 480 |
-
The generated sample before the last predictor `x_{t-1}`.
|
| 481 |
-
this_sample (`torch.Tensor`):
|
| 482 |
-
The generated sample after the last predictor `x_{t}`.
|
| 483 |
-
order (`int`):
|
| 484 |
-
The `p` of UniC-p at this step. The effective order of accuracy should be `order + 1`.
|
| 485 |
-
|
| 486 |
-
Returns:
|
| 487 |
-
`torch.Tensor`:
|
| 488 |
-
The corrected sample tensor at the current timestep.
|
| 489 |
-
"""
|
| 490 |
-
this_timestep = args[0] if len(args) > 0 else kwargs.pop("this_timestep", None)
|
| 491 |
-
if last_sample is None:
|
| 492 |
-
if len(args) > 1:
|
| 493 |
-
last_sample = args[1]
|
| 494 |
-
else:
|
| 495 |
-
raise ValueError(" missing`last_sample` as a required keyward argument")
|
| 496 |
-
if this_sample is None:
|
| 497 |
-
if len(args) > 2:
|
| 498 |
-
this_sample = args[2]
|
| 499 |
-
else:
|
| 500 |
-
raise ValueError(" missing`this_sample` as a required keyward argument")
|
| 501 |
-
if order is None:
|
| 502 |
-
if len(args) > 3:
|
| 503 |
-
order = args[3]
|
| 504 |
-
else:
|
| 505 |
-
raise ValueError(" missing`order` as a required keyward argument")
|
| 506 |
-
if this_timestep is not None:
|
| 507 |
-
deprecate(
|
| 508 |
-
"this_timestep",
|
| 509 |
-
"1.0.0",
|
| 510 |
-
"Passing `this_timestep` is deprecated and has no effect as model output conversion is now handled via an internal counter `self.step_index`",
|
| 511 |
-
)
|
| 512 |
-
|
| 513 |
-
model_output_list = self.model_outputs
|
| 514 |
-
|
| 515 |
-
m0 = model_output_list[-1]
|
| 516 |
-
x = last_sample
|
| 517 |
-
x_t = this_sample
|
| 518 |
-
model_t = this_model_output
|
| 519 |
-
|
| 520 |
-
sigma_t, sigma_s0 = self.sigmas[self.step_index], self.sigmas[self.step_index - 1] # pyright: ignore
|
| 521 |
-
alpha_t, sigma_t = self._sigma_to_alpha_sigma_t(sigma_t)
|
| 522 |
-
alpha_s0, sigma_s0 = self._sigma_to_alpha_sigma_t(sigma_s0)
|
| 523 |
-
|
| 524 |
-
lambda_t = torch.log(alpha_t) - torch.log(sigma_t)
|
| 525 |
-
lambda_s0 = torch.log(alpha_s0) - torch.log(sigma_s0)
|
| 526 |
-
|
| 527 |
-
h = lambda_t - lambda_s0
|
| 528 |
-
device = this_sample.device
|
| 529 |
-
|
| 530 |
-
rks = []
|
| 531 |
-
D1s = []
|
| 532 |
-
for i in range(1, order):
|
| 533 |
-
si = self.step_index - (i + 1) # pyright: ignore
|
| 534 |
-
mi = model_output_list[-(i + 1)]
|
| 535 |
-
alpha_si, sigma_si = self._sigma_to_alpha_sigma_t(self.sigmas[si])
|
| 536 |
-
lambda_si = torch.log(alpha_si) - torch.log(sigma_si)
|
| 537 |
-
rk = (lambda_si - lambda_s0) / h
|
| 538 |
-
rks.append(rk)
|
| 539 |
-
D1s.append((mi - m0) / rk) # pyright: ignore
|
| 540 |
-
|
| 541 |
-
rks.append(1.0)
|
| 542 |
-
rks = torch.tensor(rks, device=device)
|
| 543 |
-
|
| 544 |
-
R = []
|
| 545 |
-
b = []
|
| 546 |
-
|
| 547 |
-
hh = -h if self.predict_x0 else h
|
| 548 |
-
h_phi_1 = torch.expm1(hh) # h\phi_1(h) = e^h - 1
|
| 549 |
-
h_phi_k = h_phi_1 / hh - 1
|
| 550 |
-
|
| 551 |
-
factorial_i = 1
|
| 552 |
-
|
| 553 |
-
if self.config.solver_type == "bh1":
|
| 554 |
-
B_h = hh
|
| 555 |
-
elif self.config.solver_type == "bh2":
|
| 556 |
-
B_h = torch.expm1(hh)
|
| 557 |
-
else:
|
| 558 |
-
raise NotImplementedError()
|
| 559 |
-
|
| 560 |
-
for i in range(1, order + 1):
|
| 561 |
-
R.append(torch.pow(rks, i - 1))
|
| 562 |
-
b.append(h_phi_k * factorial_i / B_h)
|
| 563 |
-
factorial_i *= i + 1
|
| 564 |
-
h_phi_k = h_phi_k / hh - 1 / factorial_i
|
| 565 |
-
|
| 566 |
-
R = torch.stack(R)
|
| 567 |
-
b = torch.tensor(b, device=device)
|
| 568 |
-
|
| 569 |
-
if len(D1s) > 0:
|
| 570 |
-
D1s = torch.stack(D1s, dim=1)
|
| 571 |
-
else:
|
| 572 |
-
D1s = None
|
| 573 |
-
|
| 574 |
-
# for order 1, we use a simplified version
|
| 575 |
-
if order == 1:
|
| 576 |
-
rhos_c = torch.tensor([0.5], dtype=x.dtype, device=device)
|
| 577 |
-
else:
|
| 578 |
-
rhos_c = torch.linalg.solve(R, b).to(device).to(x.dtype)
|
| 579 |
-
|
| 580 |
-
if self.predict_x0:
|
| 581 |
-
x_t_ = sigma_t / sigma_s0 * x - alpha_t * h_phi_1 * m0
|
| 582 |
-
if D1s is not None:
|
| 583 |
-
corr_res = torch.einsum("k,bkc...->bc...", rhos_c[:-1], D1s)
|
| 584 |
-
else:
|
| 585 |
-
corr_res = 0
|
| 586 |
-
D1_t = model_t - m0
|
| 587 |
-
x_t = x_t_ - alpha_t * B_h * (corr_res + rhos_c[-1] * D1_t)
|
| 588 |
-
else:
|
| 589 |
-
x_t_ = alpha_t / alpha_s0 * x - sigma_t * h_phi_1 * m0
|
| 590 |
-
if D1s is not None:
|
| 591 |
-
corr_res = torch.einsum("k,bkc...->bc...", rhos_c[:-1], D1s)
|
| 592 |
-
else:
|
| 593 |
-
corr_res = 0
|
| 594 |
-
D1_t = model_t - m0
|
| 595 |
-
x_t = x_t_ - sigma_t * B_h * (corr_res + rhos_c[-1] * D1_t)
|
| 596 |
-
x_t = x_t.to(x.dtype)
|
| 597 |
-
return x_t
|
| 598 |
-
|
| 599 |
-
def index_for_timestep(self, timestep, schedule_timesteps=None):
|
| 600 |
-
if schedule_timesteps is None:
|
| 601 |
-
schedule_timesteps = self.timesteps
|
| 602 |
-
|
| 603 |
-
indices = (schedule_timesteps == timestep).nonzero()
|
| 604 |
-
|
| 605 |
-
# The sigma index that is taken for the **very** first `step`
|
| 606 |
-
# is always the second index (or the last index if there is only 1)
|
| 607 |
-
# This way we can ensure we don't accidentally skip a sigma in
|
| 608 |
-
# case we start in the middle of the denoising schedule (e.g. for image-to-image)
|
| 609 |
-
pos = 1 if len(indices) > 1 else 0
|
| 610 |
-
|
| 611 |
-
return indices[pos].item()
|
| 612 |
-
|
| 613 |
-
# Copied from diffusers.schedulers.scheduling_dpmsolver_multistep.DPMSolverMultistepScheduler._init_step_index
|
| 614 |
-
def _init_step_index(self, timestep):
|
| 615 |
-
"""
|
| 616 |
-
Initialize the step_index counter for the scheduler.
|
| 617 |
-
"""
|
| 618 |
-
|
| 619 |
-
if self.begin_index is None:
|
| 620 |
-
if isinstance(timestep, torch.Tensor):
|
| 621 |
-
timestep = timestep.to(self.timesteps.device)
|
| 622 |
-
self._step_index = self.index_for_timestep(timestep)
|
| 623 |
-
else:
|
| 624 |
-
self._step_index = self._begin_index
|
| 625 |
-
|
| 626 |
-
def step(
|
| 627 |
-
self,
|
| 628 |
-
model_output: torch.Tensor,
|
| 629 |
-
timestep: Union[int, torch.Tensor],
|
| 630 |
-
sample: torch.Tensor,
|
| 631 |
-
return_dict: bool = True,
|
| 632 |
-
generator=None,
|
| 633 |
-
) -> Union[SchedulerOutput, Tuple]:
|
| 634 |
-
"""
|
| 635 |
-
Predict the sample from the previous timestep by reversing the SDE. This function propagates the sample with
|
| 636 |
-
the multistep UniPC.
|
| 637 |
-
|
| 638 |
-
Args:
|
| 639 |
-
model_output (`torch.Tensor`):
|
| 640 |
-
The direct output from learned diffusion model.
|
| 641 |
-
timestep (`int`):
|
| 642 |
-
The current discrete timestep in the diffusion chain.
|
| 643 |
-
sample (`torch.Tensor`):
|
| 644 |
-
A current instance of a sample created by the diffusion process.
|
| 645 |
-
return_dict (`bool`):
|
| 646 |
-
Whether or not to return a [`~schedulers.scheduling_utils.SchedulerOutput`] or `tuple`.
|
| 647 |
-
|
| 648 |
-
Returns:
|
| 649 |
-
[`~schedulers.scheduling_utils.SchedulerOutput`] or `tuple`:
|
| 650 |
-
If return_dict is `True`, [`~schedulers.scheduling_utils.SchedulerOutput`] is returned, otherwise a
|
| 651 |
-
tuple is returned where the first element is the sample tensor.
|
| 652 |
-
|
| 653 |
-
"""
|
| 654 |
-
if self.num_inference_steps is None:
|
| 655 |
-
raise ValueError(
|
| 656 |
-
"Number of inference steps is 'None', you need to run 'set_timesteps' after creating the scheduler"
|
| 657 |
-
)
|
| 658 |
-
|
| 659 |
-
if self.step_index is None:
|
| 660 |
-
self._init_step_index(timestep)
|
| 661 |
-
|
| 662 |
-
use_corrector = (
|
| 663 |
-
self.step_index > 0 and self.step_index - 1 not in self.disable_corrector and self.last_sample is not None # pyright: ignore
|
| 664 |
-
)
|
| 665 |
-
|
| 666 |
-
model_output_convert = self.convert_model_output(model_output, sample=sample)
|
| 667 |
-
if use_corrector:
|
| 668 |
-
sample = self.multistep_uni_c_bh_update(
|
| 669 |
-
this_model_output=model_output_convert,
|
| 670 |
-
last_sample=self.last_sample,
|
| 671 |
-
this_sample=sample,
|
| 672 |
-
order=self.this_order,
|
| 673 |
-
)
|
| 674 |
-
|
| 675 |
-
for i in range(self.config.solver_order - 1):
|
| 676 |
-
self.model_outputs[i] = self.model_outputs[i + 1]
|
| 677 |
-
self.timestep_list[i] = self.timestep_list[i + 1]
|
| 678 |
-
|
| 679 |
-
self.model_outputs[-1] = model_output_convert
|
| 680 |
-
self.timestep_list[-1] = timestep # pyright: ignore
|
| 681 |
-
|
| 682 |
-
if self.config.lower_order_final:
|
| 683 |
-
this_order = min(self.config.solver_order, len(self.timesteps) - self.step_index) # pyright: ignore
|
| 684 |
-
else:
|
| 685 |
-
this_order = self.config.solver_order
|
| 686 |
-
|
| 687 |
-
self.this_order = min(this_order, self.lower_order_nums + 1) # warmup for multistep
|
| 688 |
-
assert self.this_order > 0
|
| 689 |
-
|
| 690 |
-
self.last_sample = sample
|
| 691 |
-
prev_sample = self.multistep_uni_p_bh_update(
|
| 692 |
-
model_output=model_output, # pass the original non-converted model output, in case solver-p is used
|
| 693 |
-
sample=sample,
|
| 694 |
-
order=self.this_order,
|
| 695 |
-
)
|
| 696 |
-
|
| 697 |
-
if self.lower_order_nums < self.config.solver_order:
|
| 698 |
-
self.lower_order_nums += 1
|
| 699 |
-
|
| 700 |
-
# upon completion increase step index by one
|
| 701 |
-
self._step_index += 1 # pyright: ignore
|
| 702 |
-
|
| 703 |
-
if not return_dict:
|
| 704 |
-
return (prev_sample,)
|
| 705 |
-
|
| 706 |
-
return SchedulerOutput(prev_sample=prev_sample)
|
| 707 |
-
|
| 708 |
-
def scale_model_input(self, sample: torch.Tensor, *args, **kwargs) -> torch.Tensor:
|
| 709 |
-
"""
|
| 710 |
-
Ensures interchangeability with schedulers that need to scale the denoising model input depending on the
|
| 711 |
-
current timestep.
|
| 712 |
-
|
| 713 |
-
Args:
|
| 714 |
-
sample (`torch.Tensor`):
|
| 715 |
-
The input sample.
|
| 716 |
-
|
| 717 |
-
Returns:
|
| 718 |
-
`torch.Tensor`:
|
| 719 |
-
A scaled input sample.
|
| 720 |
-
"""
|
| 721 |
-
return sample
|
| 722 |
-
|
| 723 |
-
# Copied from diffusers.schedulers.scheduling_dpmsolver_multistep.DPMSolverMultistepScheduler.add_noise
|
| 724 |
-
def add_noise(
|
| 725 |
-
self,
|
| 726 |
-
original_samples: torch.Tensor,
|
| 727 |
-
noise: torch.Tensor,
|
| 728 |
-
timesteps: torch.IntTensor,
|
| 729 |
-
) -> torch.Tensor:
|
| 730 |
-
# Make sure sigmas and timesteps have the same device and dtype as original_samples
|
| 731 |
-
sigmas = self.sigmas.to(device=original_samples.device, dtype=original_samples.dtype)
|
| 732 |
-
if original_samples.device.type == "mps" and torch.is_floating_point(timesteps):
|
| 733 |
-
# mps does not support float64
|
| 734 |
-
schedule_timesteps = self.timesteps.to(original_samples.device, dtype=torch.float32)
|
| 735 |
-
timesteps = timesteps.to(original_samples.device, dtype=torch.float32)
|
| 736 |
-
else:
|
| 737 |
-
schedule_timesteps = self.timesteps.to(original_samples.device)
|
| 738 |
-
timesteps = timesteps.to(original_samples.device)
|
| 739 |
-
|
| 740 |
-
# begin_index is None when the scheduler is used for training or pipeline does not implement set_begin_index
|
| 741 |
-
if self.begin_index is None:
|
| 742 |
-
step_indices = [self.index_for_timestep(t, schedule_timesteps) for t in timesteps]
|
| 743 |
-
elif self.step_index is not None:
|
| 744 |
-
# add_noise is called after first denoising step (for inpainting)
|
| 745 |
-
step_indices = [self.step_index] * timesteps.shape[0]
|
| 746 |
-
else:
|
| 747 |
-
# add noise is called before first denoising step to create initial latent(img2img)
|
| 748 |
-
step_indices = [self.begin_index] * timesteps.shape[0]
|
| 749 |
-
|
| 750 |
-
sigma = sigmas[step_indices].flatten()
|
| 751 |
-
while len(sigma.shape) < len(original_samples.shape):
|
| 752 |
-
sigma = sigma.unsqueeze(-1)
|
| 753 |
-
|
| 754 |
-
alpha_t, sigma_t = self._sigma_to_alpha_sigma_t(sigma)
|
| 755 |
-
noisy_samples = alpha_t * original_samples + sigma_t * noise
|
| 756 |
-
return noisy_samples
|
| 757 |
-
|
| 758 |
-
def __len__(self):
|
| 759 |
-
return self.config.num_train_timesteps
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assets/sage_compare_BF16.webp
ADDED
|
Git LFS Details
|
assets/sage_compare_Q4_K_M.webp
ADDED
|
Git LFS Details
|
assets/sage_compare_Q5_K_M.webp
ADDED
|
Git LFS Details
|
assets/sage_compare_Q8_0.webp
ADDED
|
Git LFS Details
|
inference.py
CHANGED
|
@@ -1,26 +1,43 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
-
"""Motif-Video 2B — Text-to-Video
|
| 3 |
|
| 4 |
GPU requirements: ~24GB VRAM for 720p (1280x736, 121 frames).
|
| 5 |
-
|
|
|
|
| 6 |
|
| 7 |
-
Uses Adaptive Projected Guidance (APG)
|
| 8 |
"""
|
| 9 |
|
| 10 |
import argparse
|
| 11 |
|
| 12 |
import torch
|
| 13 |
-
from diffusers import
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
from diffusers.utils import export_to_video
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
def parse_args():
|
| 18 |
-
parser = argparse.ArgumentParser(description="Motif-Video 2B Inference (T2V
|
| 19 |
parser.add_argument(
|
| 20 |
"--model-path",
|
| 21 |
type=str,
|
| 22 |
default="Motif-Technologies/Motif-Video-2B",
|
| 23 |
-
help="HuggingFace model ID or local checkpoint path
|
| 24 |
)
|
| 25 |
parser.add_argument(
|
| 26 |
"--prompt",
|
|
@@ -28,17 +45,11 @@ def parse_args():
|
|
| 28 |
default="A category-five hurricane, viewed from inside the eye, reveals a circular stadium of cloud walls rising to fifty thousand feet with an eerie disk of blue sky directly overhead. Shot from a NOAA reconnaissance aircraft mounted camera, the perspective looks outward toward the eyewall — a near-vertical curtain of rotating cloud and lightning that is simultaneously terrifying and transcendent. The inner surface of the eyewall catches the setting sun, painting it in improbable shades of peach and rose. The camera slowly pans 360 degrees to complete one full revolution, capturing the entire coliseum of the storm. Below, the ocean surface is a white blur of foam and spray. The documentary-style cinematography strips away all artifice to present the storm as an entity of pure elemental power.",
|
| 29 |
help="Text prompt for video generation",
|
| 30 |
)
|
| 31 |
-
parser.add_argument(
|
| 32 |
-
"--image",
|
| 33 |
-
type=str,
|
| 34 |
-
default=None,
|
| 35 |
-
help="Path to input image for I2V mode (omit for T2V)",
|
| 36 |
-
)
|
| 37 |
parser.add_argument(
|
| 38 |
"--negative-prompt",
|
| 39 |
type=str,
|
| 40 |
-
default=
|
| 41 |
-
help="Negative prompt
|
| 42 |
)
|
| 43 |
parser.add_argument("--output", type=str, default="output.mp4", help="Output video file path")
|
| 44 |
parser.add_argument("--num-frames", type=int, default=121, help="Number of frames to generate (121 = ~5s at 24fps)")
|
|
@@ -55,60 +66,140 @@ def parse_args():
|
|
| 55 |
choices=["float16", "bfloat16", "float32"],
|
| 56 |
help="Model dtype",
|
| 57 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
return parser.parse_args()
|
| 59 |
|
| 60 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
def main():
|
| 62 |
args = parse_args()
|
| 63 |
|
| 64 |
dtype_map = {"float16": torch.float16, "bfloat16": torch.bfloat16, "float32": torch.float32}
|
| 65 |
torch_dtype = dtype_map[args.dtype]
|
| 66 |
|
| 67 |
-
|
| 68 |
-
print(f"[{mode}] Loading model from: {args.model_path}")
|
| 69 |
|
| 70 |
guider = AdaptiveProjectedGuidance(
|
| 71 |
guidance_scale=args.guidance_scale,
|
| 72 |
adaptive_projected_guidance_rescale=12.0,
|
| 73 |
adaptive_projected_guidance_momentum=0.1,
|
| 74 |
-
eta=0.0,
|
| 75 |
use_original_formulation=True,
|
|
|
|
| 76 |
)
|
| 77 |
|
| 78 |
-
pipe =
|
| 79 |
args.model_path,
|
| 80 |
-
custom_pipeline="pipeline_motif_video",
|
| 81 |
-
trust_remote_code=True,
|
| 82 |
torch_dtype=torch_dtype,
|
| 83 |
guider=guider,
|
| 84 |
)
|
| 85 |
-
pipe = pipe.to("cuda")
|
| 86 |
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
if args.image:
|
| 92 |
-
from PIL import Image
|
| 93 |
|
| 94 |
-
|
| 95 |
-
print(f"[I2V] Input image: {args.image} ({image.size[0]}x{image.size[1]})")
|
| 96 |
|
| 97 |
print(f"Generating video: {args.width}x{args.height}, {args.num_frames} frames, {args.num_inference_steps} steps")
|
| 98 |
-
|
| 99 |
prompt=args.prompt,
|
| 100 |
-
|
| 101 |
height=args.height,
|
| 102 |
width=args.width,
|
| 103 |
num_frames=args.num_frames,
|
| 104 |
num_inference_steps=args.num_inference_steps,
|
| 105 |
-
generator=generator,
|
| 106 |
frame_rate=args.fps,
|
|
|
|
|
|
|
| 107 |
)
|
| 108 |
-
if args.negative_prompt is not None:
|
| 109 |
-
pipe_kwargs["negative_prompt"] = args.negative_prompt
|
| 110 |
-
|
| 111 |
-
output = pipe(**pipe_kwargs)
|
| 112 |
|
| 113 |
video_frames = output.frames[0]
|
| 114 |
export_to_video(video_frames, args.output, fps=args.fps)
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
+
"""Motif-Video 2B — Text-to-Video inference.
|
| 3 |
|
| 4 |
GPU requirements: ~24GB VRAM for 720p (1280x736, 121 frames).
|
| 5 |
+
Requires: torch, diffusers (with MotifVideoPipeline), transformers>=5.5.4,
|
| 6 |
+
accelerate, ftfy, einops, sentencepiece, regex
|
| 7 |
|
| 8 |
+
Uses Adaptive Projected Guidance (APG) and DPMSolver++ scheduler by default.
|
| 9 |
"""
|
| 10 |
|
| 11 |
import argparse
|
| 12 |
|
| 13 |
import torch
|
| 14 |
+
from diffusers import (
|
| 15 |
+
AdaptiveProjectedGuidance,
|
| 16 |
+
DPMSolverMultistepScheduler,
|
| 17 |
+
MotifVideoPipeline,
|
| 18 |
+
)
|
| 19 |
from diffusers.utils import export_to_video
|
| 20 |
|
| 21 |
+
_DEFAULT_NEGATIVE_PROMPT = (
|
| 22 |
+
"text overlay, graphic overlay, watermark, logo, subtitles, timestamp, "
|
| 23 |
+
"broadcast graphics, UI elements, random letters, frozen pose, rigid, "
|
| 24 |
+
"static expression, jerky motion, mechanical motion, discontinuous motion, "
|
| 25 |
+
"flat framing, depthless, dull lighting, monotone, crushed shadows, "
|
| 26 |
+
"blown-out highlights, shifting background, fading background, poor continuity, "
|
| 27 |
+
"identity drift, deformation, flickering, ghosting, smearing, duplication, "
|
| 28 |
+
"mutated proportions, inconsistent clothing, flat colors, desaturated, "
|
| 29 |
+
"tonally compressed, poor background separation, exposure shift, "
|
| 30 |
+
"uneven brightness, color balance shift"
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
|
| 34 |
def parse_args():
|
| 35 |
+
parser = argparse.ArgumentParser(description="Motif-Video 2B Inference (T2V)")
|
| 36 |
parser.add_argument(
|
| 37 |
"--model-path",
|
| 38 |
type=str,
|
| 39 |
default="Motif-Technologies/Motif-Video-2B",
|
| 40 |
+
help="HuggingFace model ID or local checkpoint path",
|
| 41 |
)
|
| 42 |
parser.add_argument(
|
| 43 |
"--prompt",
|
|
|
|
| 45 |
default="A category-five hurricane, viewed from inside the eye, reveals a circular stadium of cloud walls rising to fifty thousand feet with an eerie disk of blue sky directly overhead. Shot from a NOAA reconnaissance aircraft mounted camera, the perspective looks outward toward the eyewall — a near-vertical curtain of rotating cloud and lightning that is simultaneously terrifying and transcendent. The inner surface of the eyewall catches the setting sun, painting it in improbable shades of peach and rose. The camera slowly pans 360 degrees to complete one full revolution, capturing the entire coliseum of the storm. Below, the ocean surface is a white blur of foam and spray. The documentary-style cinematography strips away all artifice to present the storm as an entity of pure elemental power.",
|
| 46 |
help="Text prompt for video generation",
|
| 47 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
parser.add_argument(
|
| 49 |
"--negative-prompt",
|
| 50 |
type=str,
|
| 51 |
+
default=_DEFAULT_NEGATIVE_PROMPT,
|
| 52 |
+
help="Negative prompt",
|
| 53 |
)
|
| 54 |
parser.add_argument("--output", type=str, default="output.mp4", help="Output video file path")
|
| 55 |
parser.add_argument("--num-frames", type=int, default=121, help="Number of frames to generate (121 = ~5s at 24fps)")
|
|
|
|
| 66 |
choices=["float16", "bfloat16", "float32"],
|
| 67 |
help="Model dtype",
|
| 68 |
)
|
| 69 |
+
parser.add_argument(
|
| 70 |
+
"--use-sage-attention",
|
| 71 |
+
action="store_true",
|
| 72 |
+
help="Enable SageAttention for ~2x faster attention (requires: pip install sageattention>=2.1.1 from GitHub source)",
|
| 73 |
+
)
|
| 74 |
return parser.parse_args()
|
| 75 |
|
| 76 |
|
| 77 |
+
def _enable_sage_attention(transformer):
|
| 78 |
+
"""Patch transformer attention to use SageAttention.
|
| 79 |
+
|
| 80 |
+
Only patches _compute_attention (self-attention path). Cross-attention
|
| 81 |
+
uses _handle_cross_attention_mode which calls F.sdpa directly and is
|
| 82 |
+
unaffected by this patch.
|
| 83 |
+
|
| 84 |
+
Mask handling follows motif-models dispatch_optimized_attention pattern:
|
| 85 |
+
- mask=None: sage directly
|
| 86 |
+
- mask with uniform active length: slice active region -> sage -> pad back
|
| 87 |
+
- mask with non-uniform active length: SDPA fallback
|
| 88 |
+
"""
|
| 89 |
+
from sageattention import sageattn
|
| 90 |
+
from diffusers.models.transformers.transformer_motif_video import MotifVideoAttnProcessor2_0
|
| 91 |
+
|
| 92 |
+
_orig_compute = MotifVideoAttnProcessor2_0._compute_attention
|
| 93 |
+
|
| 94 |
+
def _sage_compute(self, query, key, value, attention_mask):
|
| 95 |
+
if attention_mask is None:
|
| 96 |
+
out = sageattn(
|
| 97 |
+
query.contiguous(), key.contiguous(), value.contiguous(),
|
| 98 |
+
tensor_layout="HND", is_causal=False,
|
| 99 |
+
)
|
| 100 |
+
out = out.transpose(1, 2).flatten(2, 3).to(query.dtype)
|
| 101 |
+
return out
|
| 102 |
+
|
| 103 |
+
# Find active token count from mask (shape: [B, 1, 1, S])
|
| 104 |
+
padding_indices = attention_mask.sum(dim=-1).long().flatten()
|
| 105 |
+
common_padding_index = padding_indices[0]
|
| 106 |
+
is_uniform = (padding_indices == common_padding_index).all()
|
| 107 |
+
|
| 108 |
+
if not is_uniform:
|
| 109 |
+
return _orig_compute(self, query, key, value, attention_mask)
|
| 110 |
+
|
| 111 |
+
active_len = common_padding_index.item()
|
| 112 |
+
S = query.shape[2]
|
| 113 |
+
|
| 114 |
+
if active_len == S:
|
| 115 |
+
out = sageattn(
|
| 116 |
+
query.contiguous(), key.contiguous(), value.contiguous(),
|
| 117 |
+
tensor_layout="HND", is_causal=False,
|
| 118 |
+
)
|
| 119 |
+
out = out.transpose(1, 2).flatten(2, 3).to(query.dtype)
|
| 120 |
+
return out
|
| 121 |
+
|
| 122 |
+
# Slice to active region, run sage, pad back
|
| 123 |
+
q_a = query[:, :, :active_len, :].contiguous()
|
| 124 |
+
k_a = key[:, :, :active_len, :].contiguous()
|
| 125 |
+
v_a = value[:, :, :active_len, :].contiguous()
|
| 126 |
+
|
| 127 |
+
out_a = sageattn(q_a, k_a, v_a, tensor_layout="HND", is_causal=False)
|
| 128 |
+
|
| 129 |
+
out = query.new_zeros(query.shape)
|
| 130 |
+
out[:, :, :active_len, :] = out_a
|
| 131 |
+
out = out.transpose(1, 2).flatten(2, 3).to(query.dtype)
|
| 132 |
+
return out
|
| 133 |
+
|
| 134 |
+
MotifVideoAttnProcessor2_0._compute_attention = _sage_compute
|
| 135 |
+
transformer.to(memory_format=torch.channels_last_3d)
|
| 136 |
+
print("[SageAttention] Enabled (patched _compute_attention + channels_last_3d)")
|
| 137 |
+
|
| 138 |
+
|
| 139 |
def main():
|
| 140 |
args = parse_args()
|
| 141 |
|
| 142 |
dtype_map = {"float16": torch.float16, "bfloat16": torch.bfloat16, "float32": torch.float32}
|
| 143 |
torch_dtype = dtype_map[args.dtype]
|
| 144 |
|
| 145 |
+
print(f"[T2V] Loading model from: {args.model_path}")
|
|
|
|
| 146 |
|
| 147 |
guider = AdaptiveProjectedGuidance(
|
| 148 |
guidance_scale=args.guidance_scale,
|
| 149 |
adaptive_projected_guidance_rescale=12.0,
|
| 150 |
adaptive_projected_guidance_momentum=0.1,
|
|
|
|
| 151 |
use_original_formulation=True,
|
| 152 |
+
normalization_dims="spatial",
|
| 153 |
)
|
| 154 |
|
| 155 |
+
pipe = MotifVideoPipeline.from_pretrained(
|
| 156 |
args.model_path,
|
|
|
|
|
|
|
| 157 |
torch_dtype=torch_dtype,
|
| 158 |
guider=guider,
|
| 159 |
)
|
|
|
|
| 160 |
|
| 161 |
+
# Replace scheduler with DPMSolver++ for faster convergence and better quality.
|
| 162 |
+
# Subclass ignores pipeline-supplied sigmas (PR branch always passes them)
|
| 163 |
+
# and uses its own flow-matching sigma schedule instead.
|
| 164 |
+
class _FlowDPMSolver(DPMSolverMultistepScheduler):
|
| 165 |
+
def set_timesteps(self, num_inference_steps=None, device=None,
|
| 166 |
+
sigmas=None, mu=None, timesteps=None):
|
| 167 |
+
if sigmas is not None and num_inference_steps is None:
|
| 168 |
+
num_inference_steps = len(sigmas)
|
| 169 |
+
super().set_timesteps(
|
| 170 |
+
num_inference_steps=num_inference_steps,
|
| 171 |
+
device=device, timesteps=timesteps,
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
pipe.scheduler = _FlowDPMSolver(
|
| 175 |
+
num_train_timesteps=pipe.scheduler.config.get("num_train_timesteps", 1000),
|
| 176 |
+
algorithm_type="dpmsolver++",
|
| 177 |
+
solver_order=2,
|
| 178 |
+
prediction_type="flow_prediction",
|
| 179 |
+
use_flow_sigmas=True,
|
| 180 |
+
flow_shift=15.0,
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
# Offload model components to CPU between uses to reduce peak VRAM
|
| 184 |
+
pipe.enable_model_cpu_offload()
|
| 185 |
|
| 186 |
+
if args.use_sage_attention:
|
| 187 |
+
_enable_sage_attention(pipe.transformer)
|
|
|
|
|
|
|
| 188 |
|
| 189 |
+
generator = torch.Generator(device="cuda").manual_seed(args.seed)
|
|
|
|
| 190 |
|
| 191 |
print(f"Generating video: {args.width}x{args.height}, {args.num_frames} frames, {args.num_inference_steps} steps")
|
| 192 |
+
output = pipe(
|
| 193 |
prompt=args.prompt,
|
| 194 |
+
negative_prompt=args.negative_prompt,
|
| 195 |
height=args.height,
|
| 196 |
width=args.width,
|
| 197 |
num_frames=args.num_frames,
|
| 198 |
num_inference_steps=args.num_inference_steps,
|
|
|
|
| 199 |
frame_rate=args.fps,
|
| 200 |
+
use_linear_quadratic_schedule=False,
|
| 201 |
+
generator=generator,
|
| 202 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
|
| 204 |
video_frames = output.frames[0]
|
| 205 |
export_to_video(video_frames, args.output, fps=args.fps)
|
pipeline_motif_video.py
DELETED
|
@@ -1,1388 +0,0 @@
|
|
| 1 |
-
# Copyright 2026 Motif Technologies, Inc. All rights reserved.
|
| 2 |
-
#
|
| 3 |
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
# you may not use this file except in compliance with the License.
|
| 5 |
-
# You may obtain a copy of the License at
|
| 6 |
-
#
|
| 7 |
-
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
#
|
| 9 |
-
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
# See the License for the specific language governing permissions and
|
| 13 |
-
# limitations under the License.
|
| 14 |
-
|
| 15 |
-
import html
|
| 16 |
-
import inspect
|
| 17 |
-
from dataclasses import dataclass
|
| 18 |
-
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
| 19 |
-
|
| 20 |
-
import ftfy
|
| 21 |
-
import numpy as np
|
| 22 |
-
import regex as re
|
| 23 |
-
import torch
|
| 24 |
-
from diffusers import (
|
| 25 |
-
AdaptiveProjectedGuidance,
|
| 26 |
-
AutoencoderKLWan,
|
| 27 |
-
ClassifierFreeGuidance,
|
| 28 |
-
DiffusionPipeline,
|
| 29 |
-
DPMSolverMultistepScheduler,
|
| 30 |
-
FlowMatchEulerDiscreteScheduler,
|
| 31 |
-
SkipLayerGuidance,
|
| 32 |
-
UniPCMultistepScheduler,
|
| 33 |
-
)
|
| 34 |
-
from diffusers.callbacks import MultiPipelineCallbacks, PipelineCallback
|
| 35 |
-
from diffusers.guiders.adaptive_projected_guidance import MomentumBuffer
|
| 36 |
-
from diffusers.guiders.guider_utils import GuiderOutput
|
| 37 |
-
from diffusers.utils import (
|
| 38 |
-
BaseOutput,
|
| 39 |
-
is_torch_xla_available,
|
| 40 |
-
logging,
|
| 41 |
-
replace_example_docstring,
|
| 42 |
-
)
|
| 43 |
-
from diffusers.utils.torch_utils import randn_tensor
|
| 44 |
-
from diffusers.video_processor import VideoProcessor
|
| 45 |
-
from einops import rearrange
|
| 46 |
-
from PIL import Image
|
| 47 |
-
from torch import Tensor
|
| 48 |
-
|
| 49 |
-
from transformers import (
|
| 50 |
-
BatchEncoding,
|
| 51 |
-
PreTrainedTokenizerBase,
|
| 52 |
-
SiglipImageProcessor,
|
| 53 |
-
T5Gemma2Encoder,
|
| 54 |
-
)
|
| 55 |
-
|
| 56 |
-
from ._fm_solvers_unipc import FlowUniPCMultistepScheduler
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
if is_torch_xla_available():
|
| 60 |
-
import torch_xla.core.xla_model as xm
|
| 61 |
-
|
| 62 |
-
XLA_AVAILABLE = True
|
| 63 |
-
else:
|
| 64 |
-
XLA_AVAILABLE = False
|
| 65 |
-
|
| 66 |
-
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
|
| 67 |
-
|
| 68 |
-
EXAMPLE_DOC_STRING = """
|
| 69 |
-
Examples:
|
| 70 |
-
```py
|
| 71 |
-
>>> import torch
|
| 72 |
-
>>> from diffusers import MotifVideoPipeline
|
| 73 |
-
>>> from diffusers.utils import export_to_video
|
| 74 |
-
|
| 75 |
-
>>> # Load the Motif Video pipeline
|
| 76 |
-
>>> motif_video_model_id = "MotifTechnologies/Motif-Video"
|
| 77 |
-
>>> pipe = MotifVideoPipeline.from_pretrained(motif_video_model_id, torch_dtype=torch.bfloat16)
|
| 78 |
-
>>> pipe.to("cuda")
|
| 79 |
-
|
| 80 |
-
>>> prompt = "A woman with long brown hair and light skin smiles at another woman with long blonde hair. The woman with brown hair wears a black jacket and has a small, barely noticeable mole on her right cheek. The camera angle is a close-up, focused on the woman with brown hair's face. The lighting is warm and natural, likely from the setting sun, casting a soft glow on the scene. The scene appears to be real-life footage"
|
| 81 |
-
>>> negative_prompt = "worst quality, inconsistent motion, blurry, jittery, distorted"
|
| 82 |
-
|
| 83 |
-
>>> video = pipe(
|
| 84 |
-
... prompt=prompt,
|
| 85 |
-
... negative_prompt=negative_prompt,
|
| 86 |
-
... width=640,
|
| 87 |
-
... height=352,
|
| 88 |
-
... num_frames=65,
|
| 89 |
-
... num_inference_steps=50,
|
| 90 |
-
... ).frames[0]
|
| 91 |
-
>>> export_to_video(video, "output.mp4", fps=16)
|
| 92 |
-
```
|
| 93 |
-
"""
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
@dataclass
|
| 97 |
-
class MotifVideoPipelineOutput(BaseOutput):
|
| 98 |
-
r"""
|
| 99 |
-
Output class for Motif Video pipelines.
|
| 100 |
-
|
| 101 |
-
Args:
|
| 102 |
-
frames (`torch.Tensor`, `np.ndarray`, or List[List[PIL.Image.Image]]):
|
| 103 |
-
List of video outputs - It can be a nested list of length `batch_size,` with each sub-list containing
|
| 104 |
-
denoised PIL image sequences of length `num_frames.` It can also be a NumPy array or Torch tensor of shape
|
| 105 |
-
`(batch_size, num_frames, channels, height, width)`.
|
| 106 |
-
"""
|
| 107 |
-
|
| 108 |
-
frames: torch.Tensor
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
"""Video-aware Adaptive Projected Guidance (APG).
|
| 112 |
-
|
| 113 |
-
Standard APG normalizes over all spatial dimensions [C, T, H, W], which collapses
|
| 114 |
-
temporal variation. This module normalizes over [C, H, W] only, preserving
|
| 115 |
-
per-frame independence.
|
| 116 |
-
"""
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
def video_normalized_guidance(
|
| 120 |
-
pred_cond: torch.Tensor,
|
| 121 |
-
pred_uncond: torch.Tensor,
|
| 122 |
-
guidance_scale: float,
|
| 123 |
-
momentum_buffer: MomentumBuffer | None = None,
|
| 124 |
-
eta: float = 1.0,
|
| 125 |
-
norm_threshold: float = 0.0,
|
| 126 |
-
use_original_formulation: bool = False,
|
| 127 |
-
) -> torch.Tensor:
|
| 128 |
-
"""APG with video-aware normalization: normalize over [C, H, W], exclude T.
|
| 129 |
-
|
| 130 |
-
For 5D input [B, C, T, H, W], dim=[-1, -2, -4] normalizes per-frame (W, H, C),
|
| 131 |
-
keeping the T dimension independent. For 4D input [B, C, H, W], falls back to
|
| 132 |
-
standard [-1, -2, -3] behavior.
|
| 133 |
-
"""
|
| 134 |
-
diff = pred_cond - pred_uncond
|
| 135 |
-
|
| 136 |
-
if len(diff.shape) == 5:
|
| 137 |
-
# [B, C, T, H, W] → normalize over W(-1), H(-2), C(-4), skip T(-3)
|
| 138 |
-
dim = [-1, -2, -4]
|
| 139 |
-
else:
|
| 140 |
-
# [B, C, H, W] → standard behavior
|
| 141 |
-
dim = [-i for i in range(1, len(diff.shape))]
|
| 142 |
-
|
| 143 |
-
if momentum_buffer is not None:
|
| 144 |
-
momentum_buffer.update(diff)
|
| 145 |
-
diff = momentum_buffer.running_average
|
| 146 |
-
|
| 147 |
-
if norm_threshold > 0:
|
| 148 |
-
ones = torch.ones_like(diff)
|
| 149 |
-
diff_norm = diff.norm(p=2, dim=dim, keepdim=True)
|
| 150 |
-
scale_factor = torch.minimum(ones, norm_threshold / diff_norm)
|
| 151 |
-
diff = diff * scale_factor
|
| 152 |
-
|
| 153 |
-
v0, v1 = diff.double(), pred_cond.double()
|
| 154 |
-
v1 = torch.nn.functional.normalize(v1, dim=dim)
|
| 155 |
-
v0_parallel = (v0 * v1).sum(dim=dim, keepdim=True) * v1
|
| 156 |
-
v0_orthogonal = v0 - v0_parallel
|
| 157 |
-
diff_parallel, diff_orthogonal = (
|
| 158 |
-
v0_parallel.type_as(diff),
|
| 159 |
-
v0_orthogonal.type_as(diff),
|
| 160 |
-
)
|
| 161 |
-
normalized_update = diff_orthogonal + eta * diff_parallel
|
| 162 |
-
|
| 163 |
-
pred = pred_cond if use_original_formulation else pred_uncond
|
| 164 |
-
pred = pred + guidance_scale * normalized_update
|
| 165 |
-
|
| 166 |
-
return pred
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
class VideoAdaptiveProjectedGuidance(AdaptiveProjectedGuidance):
|
| 170 |
-
"""APG variant that normalizes over [C, H, W] per frame, excluding the T dimension."""
|
| 171 |
-
|
| 172 |
-
def forward(self, pred_cond: torch.Tensor, pred_uncond: torch.Tensor | None = None) -> GuiderOutput:
|
| 173 |
-
pred = None
|
| 174 |
-
|
| 175 |
-
if not self._is_apg_enabled():
|
| 176 |
-
pred = pred_cond
|
| 177 |
-
else:
|
| 178 |
-
pred = video_normalized_guidance(
|
| 179 |
-
pred_cond,
|
| 180 |
-
pred_uncond,
|
| 181 |
-
self.guidance_scale,
|
| 182 |
-
self.momentum_buffer,
|
| 183 |
-
self.eta,
|
| 184 |
-
self.adaptive_projected_guidance_rescale,
|
| 185 |
-
self.use_original_formulation,
|
| 186 |
-
)
|
| 187 |
-
|
| 188 |
-
if self.guidance_rescale > 0.0:
|
| 189 |
-
from diffusers.guiders.classifier_free_guidance import rescale_noise_cfg
|
| 190 |
-
|
| 191 |
-
pred = rescale_noise_cfg(pred, pred_cond, self.guidance_rescale)
|
| 192 |
-
|
| 193 |
-
return GuiderOutput(pred=pred, pred_cond=pred_cond, pred_uncond=pred_uncond)
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
# Copied from diffusers.pipelines.flux.pipeline_flux.calculate_shift
|
| 197 |
-
def calculate_shift(
|
| 198 |
-
image_seq_len,
|
| 199 |
-
base_seq_len: int = 256,
|
| 200 |
-
max_seq_len: int = 4096,
|
| 201 |
-
base_shift: float = 0.5,
|
| 202 |
-
max_shift: float = 1.15,
|
| 203 |
-
):
|
| 204 |
-
m = (max_shift - base_shift) / (max_seq_len - base_seq_len)
|
| 205 |
-
b = base_shift - m * base_seq_len
|
| 206 |
-
mu = image_seq_len * m + b
|
| 207 |
-
return mu
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
def get_linear_quadratic_sigmas(
|
| 211 |
-
num_inference_steps: int,
|
| 212 |
-
linear_quadratic_emulating_steps: int = 250,
|
| 213 |
-
) -> np.ndarray:
|
| 214 |
-
"""
|
| 215 |
-
Compute a linear-quadratic sigma schedule for flow matching.
|
| 216 |
-
|
| 217 |
-
This schedule combines:
|
| 218 |
-
- First half: Linear interpolation from high noise to medium noise (slow denoising)
|
| 219 |
-
- Second half: Quadratic interpolation from medium noise to clean (faster denoising)
|
| 220 |
-
|
| 221 |
-
Convention:
|
| 222 |
-
- sigma=1.0 represents pure noise
|
| 223 |
-
- sigma=0.0 represents clean image
|
| 224 |
-
- Output sigmas are in descending order (1.0 → ~0)
|
| 225 |
-
|
| 226 |
-
Args:
|
| 227 |
-
num_inference_steps: Total number of denoising steps (must be even).
|
| 228 |
-
linear_quadratic_emulating_steps: Controls the slope of linear interpolation.
|
| 229 |
-
Higher values result in gentler slope in the first half.
|
| 230 |
-
|
| 231 |
-
Returns:
|
| 232 |
-
np.ndarray: Array of sigma values with shape (num_inference_steps,).
|
| 233 |
-
The scheduler will append a terminal 0.
|
| 234 |
-
|
| 235 |
-
Raises:
|
| 236 |
-
ValueError: If num_inference_steps is not even.
|
| 237 |
-
|
| 238 |
-
Reference:
|
| 239 |
-
Linear-quadratic timestep schedule for improved flow matching inference.
|
| 240 |
-
"""
|
| 241 |
-
if num_inference_steps % 2 != 0:
|
| 242 |
-
raise ValueError(
|
| 243 |
-
f"num_inference_steps must be even for linear-quadratic schedule, but got {num_inference_steps}"
|
| 244 |
-
)
|
| 245 |
-
|
| 246 |
-
steps = num_inference_steps
|
| 247 |
-
N = linear_quadratic_emulating_steps
|
| 248 |
-
half_steps = steps // 2
|
| 249 |
-
|
| 250 |
-
# First half: linear interpolation from 1 toward 0
|
| 251 |
-
# Takes first half_steps values from linspace(1, 0, N+1)
|
| 252 |
-
linear_part = np.linspace(1.0, 0.0, N + 1)[:half_steps]
|
| 253 |
-
|
| 254 |
-
# Second half: quadratic interpolation
|
| 255 |
-
# Formula: x^2 * (half_steps/N - 1) - (half_steps/N - 1)
|
| 256 |
-
# = (half_steps/N - 1) * (x^2 - 1)
|
| 257 |
-
# This maps x=0 to (half_steps/N - 1) * (-1) = 1 - half_steps/N
|
| 258 |
-
# and maps x=1 to 0
|
| 259 |
-
x = np.linspace(0.0, 1.0, half_steps + 1)
|
| 260 |
-
scale_factor = half_steps / N - 1 # negative value
|
| 261 |
-
quadratic_part = x**2 * scale_factor - scale_factor
|
| 262 |
-
|
| 263 |
-
# Concatenate and exclude the last 0 (scheduler appends terminal 0)
|
| 264 |
-
sigmas = np.concatenate([linear_part, quadratic_part])
|
| 265 |
-
sigmas = sigmas[:-1] # Remove trailing 0, scheduler will append it
|
| 266 |
-
|
| 267 |
-
return sigmas.astype(np.float32)
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps
|
| 271 |
-
def retrieve_timesteps(
|
| 272 |
-
scheduler,
|
| 273 |
-
num_inference_steps: Optional[int] = None,
|
| 274 |
-
device: Optional[Union[str, torch.device]] = None,
|
| 275 |
-
timesteps: Optional[List[int]] = None,
|
| 276 |
-
sigmas: Optional[List[float]] = None,
|
| 277 |
-
use_linear_quadratic_schedule: bool = False,
|
| 278 |
-
linear_quadratic_emulating_steps: int = 250,
|
| 279 |
-
**kwargs,
|
| 280 |
-
):
|
| 281 |
-
"""
|
| 282 |
-
Retrieve timesteps from the scheduler.
|
| 283 |
-
|
| 284 |
-
Args:
|
| 285 |
-
scheduler: The noise scheduler to use.
|
| 286 |
-
num_inference_steps: Number of denoising steps.
|
| 287 |
-
device: Device to place timesteps on.
|
| 288 |
-
timesteps: Custom timestep values (mutually exclusive with sigmas).
|
| 289 |
-
sigmas: Custom sigma values (mutually exclusive with timesteps).
|
| 290 |
-
use_linear_quadratic_schedule: If True, use linear-quadratic sigma schedule.
|
| 291 |
-
This overrides the default linear schedule. Requires num_inference_steps
|
| 292 |
-
to be even.
|
| 293 |
-
linear_quadratic_emulating_steps: Controls the linear portion slope.
|
| 294 |
-
Higher values result in gentler slope in the first half. Default: 250.
|
| 295 |
-
**kwargs: Additional arguments passed to scheduler.set_timesteps().
|
| 296 |
-
|
| 297 |
-
Returns:
|
| 298 |
-
Tuple of (timesteps, num_inference_steps).
|
| 299 |
-
|
| 300 |
-
Raises:
|
| 301 |
-
ValueError: If both timesteps and sigmas are provided, or if
|
| 302 |
-
use_linear_quadratic_schedule is True but num_inference_steps is odd.
|
| 303 |
-
"""
|
| 304 |
-
if timesteps is not None and sigmas is not None:
|
| 305 |
-
raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values")
|
| 306 |
-
|
| 307 |
-
# Handle linear-quadratic schedule: compute sigmas if flag is set
|
| 308 |
-
if use_linear_quadratic_schedule:
|
| 309 |
-
if sigmas is not None:
|
| 310 |
-
raise ValueError(
|
| 311 |
-
"Cannot use both `sigmas` and `use_linear_quadratic_schedule`. "
|
| 312 |
-
"The linear-quadratic schedule computes sigmas automatically."
|
| 313 |
-
)
|
| 314 |
-
if num_inference_steps is None:
|
| 315 |
-
raise ValueError("`num_inference_steps` must be provided when using `use_linear_quadratic_schedule`.")
|
| 316 |
-
sigmas = get_linear_quadratic_sigmas(
|
| 317 |
-
num_inference_steps=num_inference_steps,
|
| 318 |
-
linear_quadratic_emulating_steps=linear_quadratic_emulating_steps,
|
| 319 |
-
)
|
| 320 |
-
|
| 321 |
-
if timesteps is not None:
|
| 322 |
-
accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
|
| 323 |
-
if not accepts_timesteps:
|
| 324 |
-
raise ValueError(
|
| 325 |
-
f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
|
| 326 |
-
f" timestep schedules. Please check whether you are using the correct scheduler."
|
| 327 |
-
)
|
| 328 |
-
scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
|
| 329 |
-
timesteps = scheduler.timesteps
|
| 330 |
-
num_inference_steps = len(timesteps)
|
| 331 |
-
elif sigmas is not None:
|
| 332 |
-
accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
|
| 333 |
-
if not accept_sigmas:
|
| 334 |
-
raise ValueError(
|
| 335 |
-
f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
|
| 336 |
-
f" sigmas schedules. Please check whether you are using the correct scheduler."
|
| 337 |
-
)
|
| 338 |
-
scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)
|
| 339 |
-
timesteps = scheduler.timesteps
|
| 340 |
-
num_inference_steps = len(timesteps)
|
| 341 |
-
else:
|
| 342 |
-
scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)
|
| 343 |
-
timesteps = scheduler.timesteps
|
| 344 |
-
return timesteps, num_inference_steps
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
def basic_clean(text):
|
| 348 |
-
text = ftfy.fix_text(text)
|
| 349 |
-
text = html.unescape(html.unescape(text))
|
| 350 |
-
return text.strip()
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
def whitespace_clean(text):
|
| 354 |
-
text = re.sub(r"\s+", " ", text)
|
| 355 |
-
text = text.strip()
|
| 356 |
-
return text
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
def prompt_clean(text):
|
| 360 |
-
text = whitespace_clean(basic_clean(text))
|
| 361 |
-
return text
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
class MotifVideoPipeline(DiffusionPipeline):
|
| 365 |
-
r"""
|
| 366 |
-
Pipeline for text-to-video generation using MotifVideoTransformer.
|
| 367 |
-
|
| 368 |
-
Args:
|
| 369 |
-
transformer ([`MotifVideoTransformer3DModel`]):
|
| 370 |
-
Conditional Transformer architecture to denoise the encoded video latents.
|
| 371 |
-
scheduler ([`FlowMatchEulerDiscreteScheduler`]):
|
| 372 |
-
A scheduler to be used in combination with `transformer` to denoise the encoded video latents.
|
| 373 |
-
vae ([`AutoencoderKLWan`]):
|
| 374 |
-
Variational Auto-Encoder (VAE) Model to encode and decode videos to and from latent representations.
|
| 375 |
-
text_encoder ([`T5Gemma2Encoder`]):
|
| 376 |
-
Primary text encoder for encoding text prompts into embeddings.
|
| 377 |
-
tokenizer ([`PreTrainedTokenizerBase`]):
|
| 378 |
-
Tokenizer corresponding to the primary text encoder.
|
| 379 |
-
guider ([`ClassifierFreeGuidance`] or [`SkipLayerGuidance`] or [`AdaptiveProjectedGuidance`] or [`VideoAdaptiveProjectedGuidance`], *optional*):
|
| 380 |
-
The guidance method to use. If `None`, it defaults to `ClassifierFreeGuidance()`.
|
| 381 |
-
"""
|
| 382 |
-
|
| 383 |
-
model_cpu_offload_seq = "text_encoder->transformer->vae"
|
| 384 |
-
_optional_components = ["feature_extractor"]
|
| 385 |
-
_callback_tensor_inputs = ["latents", "prompt_embeds", "negative_prompt_embeds"]
|
| 386 |
-
|
| 387 |
-
def __init__(
|
| 388 |
-
self,
|
| 389 |
-
scheduler: Union[
|
| 390 |
-
FlowMatchEulerDiscreteScheduler,
|
| 391 |
-
DPMSolverMultistepScheduler,
|
| 392 |
-
UniPCMultistepScheduler,
|
| 393 |
-
FlowUniPCMultistepScheduler,
|
| 394 |
-
],
|
| 395 |
-
vae: AutoencoderKLWan,
|
| 396 |
-
text_encoder: T5Gemma2Encoder,
|
| 397 |
-
tokenizer: PreTrainedTokenizerBase,
|
| 398 |
-
transformer,
|
| 399 |
-
guider: Optional[
|
| 400 |
-
Union[
|
| 401 |
-
ClassifierFreeGuidance,
|
| 402 |
-
SkipLayerGuidance,
|
| 403 |
-
AdaptiveProjectedGuidance,
|
| 404 |
-
VideoAdaptiveProjectedGuidance,
|
| 405 |
-
]
|
| 406 |
-
] = None,
|
| 407 |
-
feature_extractor: Optional[SiglipImageProcessor] = None,
|
| 408 |
-
):
|
| 409 |
-
super().__init__()
|
| 410 |
-
|
| 411 |
-
self.guider = ClassifierFreeGuidance() if guider is None else guider
|
| 412 |
-
|
| 413 |
-
self.register_modules(
|
| 414 |
-
vae=vae,
|
| 415 |
-
text_encoder=text_encoder,
|
| 416 |
-
tokenizer=tokenizer,
|
| 417 |
-
transformer=transformer,
|
| 418 |
-
scheduler=scheduler,
|
| 419 |
-
feature_extractor=feature_extractor,
|
| 420 |
-
)
|
| 421 |
-
|
| 422 |
-
self.vae_scale_factor_temporal = self.vae.config.scale_factor_temporal if getattr(self, "vae", None) else 4
|
| 423 |
-
self.vae_scale_factor_spatial = self.vae.config.scale_factor_spatial if getattr(self, "vae", None) else 8
|
| 424 |
-
|
| 425 |
-
self.transformer_spatial_patch_size = (
|
| 426 |
-
self.transformer.config.patch_size if getattr(self, "transformer", None) is not None else 2
|
| 427 |
-
)
|
| 428 |
-
self.transformer_temporal_patch_size = (
|
| 429 |
-
self.transformer.config.patch_size_t if getattr(self, "transformer") is not None else 1
|
| 430 |
-
)
|
| 431 |
-
|
| 432 |
-
self.video_processor = VideoProcessor(vae_scale_factor=self.vae_scale_factor_spatial)
|
| 433 |
-
self.tokenizer_max_length = (
|
| 434 |
-
self.tokenizer.model_max_length if getattr(self, "tokenizer", None) is not None else 512
|
| 435 |
-
)
|
| 436 |
-
|
| 437 |
-
def _get_default_embeds(
|
| 438 |
-
self,
|
| 439 |
-
text_encoder,
|
| 440 |
-
tokenizer: PreTrainedTokenizerBase,
|
| 441 |
-
prompt: Union[str, List[str]],
|
| 442 |
-
max_sequence_length: int = 512,
|
| 443 |
-
device: Optional[torch.device] = None,
|
| 444 |
-
dtype: Optional[torch.dtype] = None,
|
| 445 |
-
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 446 |
-
dtype = dtype or text_encoder.dtype
|
| 447 |
-
|
| 448 |
-
text_inputs = tokenizer(
|
| 449 |
-
prompt,
|
| 450 |
-
padding="max_length",
|
| 451 |
-
max_length=max_sequence_length,
|
| 452 |
-
truncation=True,
|
| 453 |
-
add_special_tokens=True,
|
| 454 |
-
return_attention_mask=True,
|
| 455 |
-
return_tensors="pt",
|
| 456 |
-
)
|
| 457 |
-
text_inputs = BatchEncoding(
|
| 458 |
-
{k: v.to(device) if isinstance(v, torch.Tensor) else v for k, v in text_inputs.items()}
|
| 459 |
-
)
|
| 460 |
-
|
| 461 |
-
prompt_embeds = text_encoder(**text_inputs)[0]
|
| 462 |
-
prompt_embeds = prompt_embeds.to(dtype=dtype, device=device)
|
| 463 |
-
|
| 464 |
-
return prompt_embeds, text_inputs.attention_mask
|
| 465 |
-
|
| 466 |
-
def _average_pool(self, last_hidden_states: Tensor, attention_mask: Tensor) -> Tensor:
|
| 467 |
-
last_hidden = last_hidden_states.masked_fill(~attention_mask[..., None].bool(), 0.0)
|
| 468 |
-
denom = attention_mask.sum(dim=1, keepdim=True).clamp(min=1) # avoid div by zero
|
| 469 |
-
return last_hidden.sum(dim=1) / denom
|
| 470 |
-
|
| 471 |
-
def _get_prompt_embeds(
|
| 472 |
-
self,
|
| 473 |
-
text_encoder: T5Gemma2Encoder,
|
| 474 |
-
tokenizer: PreTrainedTokenizerBase,
|
| 475 |
-
prompt: Union[str, List[str]] | None = None,
|
| 476 |
-
num_videos_per_prompt: int = 1,
|
| 477 |
-
max_sequence_length: int = 512,
|
| 478 |
-
device: Optional[torch.device] = None,
|
| 479 |
-
dtype: Optional[torch.dtype] = None,
|
| 480 |
-
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 481 |
-
device = device or self._execution_device
|
| 482 |
-
|
| 483 |
-
prompt = [prompt] if isinstance(prompt, str) else prompt
|
| 484 |
-
|
| 485 |
-
prompt_embeds_kwargs = {
|
| 486 |
-
"text_encoder": text_encoder,
|
| 487 |
-
"tokenizer": tokenizer,
|
| 488 |
-
"prompt": prompt,
|
| 489 |
-
"max_sequence_length": max_sequence_length,
|
| 490 |
-
"device": device,
|
| 491 |
-
"dtype": dtype,
|
| 492 |
-
}
|
| 493 |
-
# When enable_model_cpu_offload() is active, the accelerate forward hook is on text_encoder (parent). Moving the encoder to the execution device explicitly ensures inputs and
|
| 494 |
-
# weights are on the same device. The parent's offload hook will move text_encoder back to CPU after
|
| 495 |
-
# the next component claims the GPU.
|
| 496 |
-
if next(text_encoder.parameters()).device != torch.device(device):
|
| 497 |
-
text_encoder.to(device)
|
| 498 |
-
prompt_embeds, prompt_attention_mask = self._get_default_embeds(**prompt_embeds_kwargs)
|
| 499 |
-
|
| 500 |
-
pooled_prompt_embeds = self._average_pool(prompt_embeds, prompt_attention_mask)
|
| 501 |
-
|
| 502 |
-
return prompt_embeds, prompt_attention_mask, pooled_prompt_embeds
|
| 503 |
-
|
| 504 |
-
# Keep encode_prompt structure, uses _get_prompt_embeds internally
|
| 505 |
-
def encode_prompt(
|
| 506 |
-
self,
|
| 507 |
-
prompt: Union[str, List[str]],
|
| 508 |
-
num_videos_per_prompt: int = 1,
|
| 509 |
-
prompt_embeds: Optional[torch.Tensor] = None,
|
| 510 |
-
pooled_prompt_embeds: Optional[torch.Tensor] = None,
|
| 511 |
-
prompt_attention_mask: Optional[torch.Tensor] = None,
|
| 512 |
-
max_sequence_length: int = 512,
|
| 513 |
-
device: Optional[torch.device] = None,
|
| 514 |
-
dtype: Optional[torch.dtype] = None,
|
| 515 |
-
) -> Tuple[
|
| 516 |
-
torch.Tensor,
|
| 517 |
-
torch.Tensor,
|
| 518 |
-
torch.Tensor,
|
| 519 |
-
]:
|
| 520 |
-
device = device or self._execution_device
|
| 521 |
-
|
| 522 |
-
prompt = [prompt] if isinstance(prompt, str) else prompt
|
| 523 |
-
if prompt is not None:
|
| 524 |
-
batch_size = len(prompt)
|
| 525 |
-
else:
|
| 526 |
-
batch_size = prompt_embeds.shape[0]
|
| 527 |
-
|
| 528 |
-
prompt_embeds_kwargs = {
|
| 529 |
-
"device": device,
|
| 530 |
-
"dtype": dtype,
|
| 531 |
-
}
|
| 532 |
-
|
| 533 |
-
if prompt_embeds is None:
|
| 534 |
-
prompt_embeds, prompt_attention_mask, pooled_prompt_embeds = self._get_prompt_embeds(
|
| 535 |
-
text_encoder=self.text_encoder,
|
| 536 |
-
tokenizer=self.tokenizer,
|
| 537 |
-
prompt=prompt,
|
| 538 |
-
max_sequence_length=max_sequence_length,
|
| 539 |
-
**prompt_embeds_kwargs,
|
| 540 |
-
)
|
| 541 |
-
|
| 542 |
-
# Compute actual (non-padding) token count for batch=1 Flash Attention trimming in __call__
|
| 543 |
-
actual_seq_len = None
|
| 544 |
-
if batch_size == 1 and prompt_attention_mask is not None:
|
| 545 |
-
actual_seq_len = int(prompt_attention_mask.sum(dim=-1).max().item())
|
| 546 |
-
|
| 547 |
-
# duplicate text embeddings for each generation per prompt, using mps friendly method
|
| 548 |
-
seq_len = prompt_embeds.shape[1]
|
| 549 |
-
prompt_embeds = prompt_embeds.repeat(1, num_videos_per_prompt, 1)
|
| 550 |
-
prompt_embeds = prompt_embeds.view(batch_size * num_videos_per_prompt, seq_len, -1)
|
| 551 |
-
|
| 552 |
-
if pooled_prompt_embeds is not None:
|
| 553 |
-
pooled_prompt_embeds = pooled_prompt_embeds.repeat_interleave(num_videos_per_prompt, dim=0)
|
| 554 |
-
|
| 555 |
-
if prompt_attention_mask is not None:
|
| 556 |
-
prompt_attention_mask = prompt_attention_mask.bool()
|
| 557 |
-
prompt_attention_mask = prompt_attention_mask.view(batch_size, -1)
|
| 558 |
-
prompt_attention_mask = prompt_attention_mask.repeat_interleave(num_videos_per_prompt, dim=0)
|
| 559 |
-
|
| 560 |
-
return (
|
| 561 |
-
prompt_embeds,
|
| 562 |
-
pooled_prompt_embeds,
|
| 563 |
-
prompt_attention_mask,
|
| 564 |
-
actual_seq_len,
|
| 565 |
-
)
|
| 566 |
-
|
| 567 |
-
@property
|
| 568 |
-
def vision_encoder(self):
|
| 569 |
-
"""Get the vision encoder from T5Gemma2.
|
| 570 |
-
|
| 571 |
-
T5Gemma2 has vision_tower.vision_model structure.
|
| 572 |
-
Will raise AttributeError if not available.
|
| 573 |
-
"""
|
| 574 |
-
return self.text_encoder.vision_tower.vision_model
|
| 575 |
-
|
| 576 |
-
def encode_image(
|
| 577 |
-
self,
|
| 578 |
-
image: Image.Image,
|
| 579 |
-
batch_size: int = 1,
|
| 580 |
-
device: Optional[torch.device] = None,
|
| 581 |
-
dtype: Optional[torch.dtype] = None,
|
| 582 |
-
) -> torch.Tensor:
|
| 583 |
-
"""Encode image to embeddings using SigLIP vision encoder."""
|
| 584 |
-
device = device or self._execution_device
|
| 585 |
-
dtype = dtype or self.transformer.dtype
|
| 586 |
-
|
| 587 |
-
image_embeds = self._get_image_embeds(
|
| 588 |
-
image_encoder=self.vision_encoder,
|
| 589 |
-
feature_extractor=self.feature_extractor,
|
| 590 |
-
image=image,
|
| 591 |
-
device=device,
|
| 592 |
-
)
|
| 593 |
-
image_embeds = image_embeds.repeat(batch_size, 1, 1)
|
| 594 |
-
return image_embeds.to(device=device, dtype=dtype)
|
| 595 |
-
|
| 596 |
-
@staticmethod
|
| 597 |
-
def _get_image_embeds(
|
| 598 |
-
image_encoder,
|
| 599 |
-
feature_extractor: SiglipImageProcessor,
|
| 600 |
-
image,
|
| 601 |
-
device: torch.device,
|
| 602 |
-
) -> torch.Tensor:
|
| 603 |
-
"""Helper to encode single image with SigLIP.
|
| 604 |
-
|
| 605 |
-
Args:
|
| 606 |
-
image_encoder: The SigLIP vision encoder model.
|
| 607 |
-
feature_extractor: SiglipImageProcessor for preprocessing.
|
| 608 |
-
image: Can be either:
|
| 609 |
-
- PIL.Image.Image: Will be preprocessed by feature_extractor
|
| 610 |
-
- torch.Tensor: Assumed to be in [0, 1] range, will be normalized and passed to encoder
|
| 611 |
-
device: Device to place tensors on.
|
| 612 |
-
|
| 613 |
-
Returns:
|
| 614 |
-
Image embeddings from the vision encoder.
|
| 615 |
-
"""
|
| 616 |
-
image_encoder_dtype = next(image_encoder.parameters()).dtype
|
| 617 |
-
|
| 618 |
-
if isinstance(image, torch.Tensor):
|
| 619 |
-
image = feature_extractor.preprocess(
|
| 620 |
-
images=image.float(),
|
| 621 |
-
do_resize=True,
|
| 622 |
-
do_rescale=False,
|
| 623 |
-
do_normalize=True,
|
| 624 |
-
do_convert_rgb=True,
|
| 625 |
-
return_tensors="pt",
|
| 626 |
-
)
|
| 627 |
-
else:
|
| 628 |
-
image = feature_extractor.preprocess(
|
| 629 |
-
images=image,
|
| 630 |
-
do_resize=True,
|
| 631 |
-
do_rescale=False,
|
| 632 |
-
do_normalize=True,
|
| 633 |
-
do_convert_rgb=True,
|
| 634 |
-
return_tensors="pt",
|
| 635 |
-
)
|
| 636 |
-
|
| 637 |
-
image = image.to(device, dtype=image_encoder_dtype)
|
| 638 |
-
return image_encoder(**image).last_hidden_state
|
| 639 |
-
|
| 640 |
-
@torch.compiler.disable
|
| 641 |
-
def _prepare_first_frame_conditioning(
|
| 642 |
-
self,
|
| 643 |
-
video: torch.Tensor,
|
| 644 |
-
latents: torch.Tensor,
|
| 645 |
-
use_conditioning: bool,
|
| 646 |
-
generator: Optional[torch.Generator] = None,
|
| 647 |
-
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None]:
|
| 648 |
-
"""Prepare first frame conditioning tensors.
|
| 649 |
-
|
| 650 |
-
This method implements batch-level conditioning where entire
|
| 651 |
-
batches are either I2V (all samples conditioned) or T2V (no conditioning). This
|
| 652 |
-
prevents mode confusion within batches.
|
| 653 |
-
|
| 654 |
-
For I2V mode:
|
| 655 |
-
1. Extract and VAE-encode first frame from video
|
| 656 |
-
2. Create latent_condition by repeating first frame across time (frame 0 only)
|
| 657 |
-
3. Create latent_mask with 1.0 at frame 0
|
| 658 |
-
4. Get image_embeds from vision encoder
|
| 659 |
-
|
| 660 |
-
For T2V mode:
|
| 661 |
-
1. Pad with zeros for latent_condition and latent_mask
|
| 662 |
-
|
| 663 |
-
Args:
|
| 664 |
-
video: Input video tensor [batch_size, frames, channels, height, width] in [-1, 1]
|
| 665 |
-
latents: Latents [batch_size, lantent_channels, latent_num_frames, latent_height, latent_width]
|
| 666 |
-
use_conditioning: Whether to use first-frame conditioning (True for I2V, False for T2V)
|
| 667 |
-
generator: Optional random number generator for reproducibility
|
| 668 |
-
|
| 669 |
-
Returns:
|
| 670 |
-
Tuple of (latent_condition, latent_mask, image_embeds).
|
| 671 |
-
- latent_condition: [B, C, F, H, W] conditioning signal (zeros for T2V)
|
| 672 |
-
- latent_mask: [B, 1, F, H, W] binary mask (zeros for T2V)
|
| 673 |
-
- image_embeds: [B, N, D] image embeddings from vision encoder or None for T2V
|
| 674 |
-
"""
|
| 675 |
-
batch_size, lantent_channels, latent_num_frames, latent_height, latent_width = latents.shape
|
| 676 |
-
device = latents.device
|
| 677 |
-
dtype = latents.dtype
|
| 678 |
-
|
| 679 |
-
# Determine if we should use conditioning
|
| 680 |
-
use_conditioning = use_conditioning and (latent_num_frames > 1)
|
| 681 |
-
|
| 682 |
-
# Initialize conditioning tensors
|
| 683 |
-
latent_condition = torch.zeros(
|
| 684 |
-
batch_size,
|
| 685 |
-
lantent_channels,
|
| 686 |
-
latent_num_frames,
|
| 687 |
-
latent_height,
|
| 688 |
-
latent_width,
|
| 689 |
-
device=device,
|
| 690 |
-
dtype=dtype,
|
| 691 |
-
)
|
| 692 |
-
latent_mask = torch.zeros(
|
| 693 |
-
batch_size,
|
| 694 |
-
1,
|
| 695 |
-
latent_num_frames,
|
| 696 |
-
latent_height,
|
| 697 |
-
latent_width,
|
| 698 |
-
device=device,
|
| 699 |
-
dtype=dtype,
|
| 700 |
-
)
|
| 701 |
-
image_embeds = None
|
| 702 |
-
|
| 703 |
-
if use_conditioning:
|
| 704 |
-
with torch.no_grad():
|
| 705 |
-
# Encode first frame for latent_condition
|
| 706 |
-
first_frame_latents = self.vae.encode(
|
| 707 |
-
rearrange(video[:, 0:1], "b f c h w -> b c f h w")
|
| 708 |
-
).latent_dist.sample(generator=generator)
|
| 709 |
-
first_frame_latents = self._normalize_latents(
|
| 710 |
-
latents=first_frame_latents,
|
| 711 |
-
latents_mean=self.vae.config.latents_mean,
|
| 712 |
-
latents_std=self.vae.config.latents_std,
|
| 713 |
-
)
|
| 714 |
-
|
| 715 |
-
# Create latent_condition by repeating first frame across time
|
| 716 |
-
latent_condition = first_frame_latents.repeat(1, 1, latent_num_frames, 1, 1)
|
| 717 |
-
latent_condition[:, :, 1:, :, :] = 0
|
| 718 |
-
|
| 719 |
-
# latent_mask: 1.0 at frame 0, 0.0 elsewhere
|
| 720 |
-
latent_mask[:, :, 0] = 1.0
|
| 721 |
-
|
| 722 |
-
# image_embeds from vision encoder
|
| 723 |
-
first_frame_vision = video[:, 0] # [B, C, H, W]
|
| 724 |
-
first_frame_vision = ((first_frame_vision + 1) / 2).clamp(0, 1)
|
| 725 |
-
|
| 726 |
-
with torch.no_grad():
|
| 727 |
-
image_embeds = self._get_image_embeds(
|
| 728 |
-
image_encoder=self.vision_encoder,
|
| 729 |
-
feature_extractor=self.feature_extractor,
|
| 730 |
-
image=first_frame_vision,
|
| 731 |
-
device=device,
|
| 732 |
-
)
|
| 733 |
-
|
| 734 |
-
return latent_condition, latent_mask, image_embeds
|
| 735 |
-
|
| 736 |
-
def check_inputs(
|
| 737 |
-
self,
|
| 738 |
-
prompt,
|
| 739 |
-
negative_prompt,
|
| 740 |
-
height,
|
| 741 |
-
width,
|
| 742 |
-
batch_size,
|
| 743 |
-
callback_on_step_end_tensor_inputs=None,
|
| 744 |
-
prompt_embeds=None,
|
| 745 |
-
negative_prompt_embeds=None,
|
| 746 |
-
prompt_attention_mask=None,
|
| 747 |
-
negative_prompt_attention_mask=None,
|
| 748 |
-
):
|
| 749 |
-
# Resolution must be divisible by VAE scale factor * transformer patch size
|
| 750 |
-
# (e.g. 8 * 2 = 16 for default config) to avoid latent/patch dimension mismatch.
|
| 751 |
-
spatial_divisor = self.vae_scale_factor_spatial * self.transformer_spatial_patch_size
|
| 752 |
-
if height % spatial_divisor != 0 or width % spatial_divisor != 0:
|
| 753 |
-
raise ValueError(
|
| 754 |
-
f"`height` and `width` have to be divisible by {spatial_divisor} "
|
| 755 |
-
f"(vae_scale={self.vae_scale_factor_spatial} * patch_size={self.transformer_spatial_patch_size}) "
|
| 756 |
-
f"but are {height} and {width}."
|
| 757 |
-
)
|
| 758 |
-
|
| 759 |
-
if callback_on_step_end_tensor_inputs is not None and not all(
|
| 760 |
-
k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs
|
| 761 |
-
):
|
| 762 |
-
raise ValueError(
|
| 763 |
-
f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found {[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}"
|
| 764 |
-
)
|
| 765 |
-
|
| 766 |
-
if prompt is not None and prompt_embeds is not None:
|
| 767 |
-
raise ValueError(
|
| 768 |
-
f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
|
| 769 |
-
" only forward one of the two."
|
| 770 |
-
)
|
| 771 |
-
elif prompt is None and prompt_embeds is None:
|
| 772 |
-
raise ValueError(
|
| 773 |
-
"Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined."
|
| 774 |
-
)
|
| 775 |
-
elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):
|
| 776 |
-
raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
|
| 777 |
-
|
| 778 |
-
# Validate negative_prompt: must be None, str, or list with matching batch_size
|
| 779 |
-
if negative_prompt is not None:
|
| 780 |
-
if not isinstance(negative_prompt, (str, list)):
|
| 781 |
-
raise ValueError(f"`negative_prompt` has to be of type `str` or `list` but is {type(negative_prompt)}")
|
| 782 |
-
if isinstance(negative_prompt, list) and len(negative_prompt) != batch_size:
|
| 783 |
-
raise ValueError(
|
| 784 |
-
f"`negative_prompt` list length ({len(negative_prompt)}) must match batch_size ({batch_size})."
|
| 785 |
-
)
|
| 786 |
-
|
| 787 |
-
if prompt_embeds is not None and prompt_attention_mask is None:
|
| 788 |
-
raise ValueError("Must provide `prompt_attention_mask` when specifying `prompt_embeds`.")
|
| 789 |
-
|
| 790 |
-
if negative_prompt_embeds is not None and negative_prompt_attention_mask is None:
|
| 791 |
-
raise ValueError("Must provide `negative_prompt_attention_mask` when specifying `negative_prompt_embeds`.")
|
| 792 |
-
|
| 793 |
-
if prompt_embeds is not None and negative_prompt_embeds is not None:
|
| 794 |
-
if prompt_embeds.shape != negative_prompt_embeds.shape:
|
| 795 |
-
raise ValueError(
|
| 796 |
-
"`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but"
|
| 797 |
-
f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`"
|
| 798 |
-
f" {negative_prompt_embeds.shape}."
|
| 799 |
-
)
|
| 800 |
-
if prompt_attention_mask.shape != negative_prompt_attention_mask.shape:
|
| 801 |
-
raise ValueError(
|
| 802 |
-
"`prompt_attention_mask` and `negative_prompt_attention_mask` must have the same shape when passed directly, but"
|
| 803 |
-
f" got: `prompt_attention_mask` {prompt_attention_mask.shape} != `negative_prompt_attention_mask`"
|
| 804 |
-
f" {negative_prompt_attention_mask.shape}."
|
| 805 |
-
)
|
| 806 |
-
|
| 807 |
-
def _prepare_negative_prompt(
|
| 808 |
-
self,
|
| 809 |
-
negative_prompt: Optional[Union[str, List[str]]],
|
| 810 |
-
batch_size: int,
|
| 811 |
-
) -> List[str]:
|
| 812 |
-
"""
|
| 813 |
-
Prepare negative_prompt to match batch_size.
|
| 814 |
-
|
| 815 |
-
Args:
|
| 816 |
-
negative_prompt: None, a single string, or a list of strings matching batch_size.
|
| 817 |
-
batch_size: The number of prompts in the batch.
|
| 818 |
-
|
| 819 |
-
Returns:
|
| 820 |
-
A list of negative prompts with length equal to batch_size.
|
| 821 |
-
"""
|
| 822 |
-
if negative_prompt is None:
|
| 823 |
-
return [""] * batch_size
|
| 824 |
-
if isinstance(negative_prompt, str):
|
| 825 |
-
return [negative_prompt] * batch_size
|
| 826 |
-
return negative_prompt
|
| 827 |
-
|
| 828 |
-
@staticmethod
|
| 829 |
-
def _pack_latents(latents: torch.Tensor, patch_size: int = 1, patch_size_t: int = 1) -> torch.Tensor:
|
| 830 |
-
batch_size, num_channels, num_frames, height, width = latents.shape
|
| 831 |
-
post_patch_num_frames = num_frames // patch_size_t
|
| 832 |
-
post_patch_height = height // patch_size
|
| 833 |
-
post_patch_width = width // patch_size
|
| 834 |
-
latents = latents.reshape(
|
| 835 |
-
batch_size,
|
| 836 |
-
-1,
|
| 837 |
-
post_patch_num_frames,
|
| 838 |
-
patch_size_t,
|
| 839 |
-
post_patch_height,
|
| 840 |
-
patch_size,
|
| 841 |
-
post_patch_width,
|
| 842 |
-
patch_size,
|
| 843 |
-
)
|
| 844 |
-
latents = latents.permute(0, 2, 4, 6, 1, 3, 5, 7).flatten(4, 7).flatten(1, 3)
|
| 845 |
-
return latents
|
| 846 |
-
|
| 847 |
-
@staticmethod
|
| 848 |
-
def _unpack_latents(
|
| 849 |
-
latents: torch.Tensor,
|
| 850 |
-
num_frames: int,
|
| 851 |
-
height: int,
|
| 852 |
-
width: int,
|
| 853 |
-
patch_size: int = 1,
|
| 854 |
-
patch_size_t: int = 1,
|
| 855 |
-
) -> torch.Tensor:
|
| 856 |
-
batch_size = latents.size(0)
|
| 857 |
-
latents = latents.reshape(
|
| 858 |
-
batch_size,
|
| 859 |
-
num_frames,
|
| 860 |
-
height,
|
| 861 |
-
width,
|
| 862 |
-
-1,
|
| 863 |
-
patch_size_t,
|
| 864 |
-
patch_size,
|
| 865 |
-
patch_size,
|
| 866 |
-
)
|
| 867 |
-
latents = latents.permute(0, 4, 1, 5, 2, 6, 3, 7).flatten(6, 7).flatten(4, 5).flatten(2, 3)
|
| 868 |
-
return latents
|
| 869 |
-
|
| 870 |
-
@staticmethod
|
| 871 |
-
def _normalize_latents(
|
| 872 |
-
latents: torch.Tensor, latents_mean: torch.Tensor, latents_std: torch.Tensor
|
| 873 |
-
) -> torch.Tensor:
|
| 874 |
-
# Normalize latents across the channel dimension [B, C, F, H, W]
|
| 875 |
-
latents_mean = torch.tensor(latents_mean).view(1, -1, 1, 1, 1).to(latents.device, latents.dtype)
|
| 876 |
-
latents_std = torch.tensor(latents_std).view(1, -1, 1, 1, 1).to(latents.device, latents.dtype)
|
| 877 |
-
latents = (latents - latents_mean) / latents_std
|
| 878 |
-
return latents
|
| 879 |
-
|
| 880 |
-
@staticmethod
|
| 881 |
-
def _denormalize_latents(
|
| 882 |
-
latents: torch.Tensor, latents_mean: torch.Tensor, latents_std: torch.Tensor
|
| 883 |
-
) -> torch.Tensor:
|
| 884 |
-
# Denormalize latents across the channel dimension [B, C, F, H, W]
|
| 885 |
-
latents_mean = torch.tensor(latents_mean).view(1, -1, 1, 1, 1).to(latents.device, latents.dtype)
|
| 886 |
-
latents_std = torch.tensor(latents_std).view(1, -1, 1, 1, 1).to(latents.device, latents.dtype)
|
| 887 |
-
latents = latents * latents_std + latents_mean
|
| 888 |
-
return latents
|
| 889 |
-
|
| 890 |
-
def prepare_latents(
|
| 891 |
-
self,
|
| 892 |
-
batch_size: int = 1,
|
| 893 |
-
num_channels_latents: int = 16,
|
| 894 |
-
height: int = 352,
|
| 895 |
-
width: int = 640,
|
| 896 |
-
num_frames: int = 65,
|
| 897 |
-
dtype: Optional[torch.dtype] = None,
|
| 898 |
-
device: Optional[torch.device] = None,
|
| 899 |
-
generator: Optional[torch.Generator] = None,
|
| 900 |
-
latents: Optional[torch.Tensor] = None,
|
| 901 |
-
) -> torch.Tensor:
|
| 902 |
-
if latents is not None:
|
| 903 |
-
return latents.to(device=device, dtype=dtype)
|
| 904 |
-
|
| 905 |
-
shape = (
|
| 906 |
-
batch_size,
|
| 907 |
-
num_channels_latents,
|
| 908 |
-
(num_frames - 1) // self.vae_scale_factor_temporal + 1,
|
| 909 |
-
height // self.vae_scale_factor_spatial,
|
| 910 |
-
width // self.vae_scale_factor_spatial,
|
| 911 |
-
)
|
| 912 |
-
|
| 913 |
-
if isinstance(generator, list) and len(generator) != batch_size:
|
| 914 |
-
raise ValueError(
|
| 915 |
-
f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
|
| 916 |
-
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
|
| 917 |
-
)
|
| 918 |
-
|
| 919 |
-
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
|
| 920 |
-
return latents
|
| 921 |
-
|
| 922 |
-
@property
|
| 923 |
-
def num_timesteps(self):
|
| 924 |
-
return self._num_timesteps
|
| 925 |
-
|
| 926 |
-
@property
|
| 927 |
-
def current_timestep(self):
|
| 928 |
-
return self._current_timestep
|
| 929 |
-
|
| 930 |
-
@property
|
| 931 |
-
def attention_kwargs(self):
|
| 932 |
-
return self._attention_kwargs
|
| 933 |
-
|
| 934 |
-
@property
|
| 935 |
-
def interrupt(self):
|
| 936 |
-
return self._interrupt
|
| 937 |
-
|
| 938 |
-
@torch.no_grad()
|
| 939 |
-
@replace_example_docstring(EXAMPLE_DOC_STRING)
|
| 940 |
-
def __call__(
|
| 941 |
-
self,
|
| 942 |
-
prompt: Union[str, List[str]] | None = None,
|
| 943 |
-
image=None,
|
| 944 |
-
negative_prompt: Optional[
|
| 945 |
-
Union[str, List[str]]
|
| 946 |
-
] = "text overlay, graphic overlay, watermark, logo, subtitles, timestamp, broadcast graphics, UI elements, random letters, frozen pose, rigid, static expression, jerky motion, mechanical motion, discontinuous motion, flat framing, depthless, dull lighting, monotone, crushed shadows, blown-out highlights, shifting background, fading background, poor continuity, identity drift, deformation, flickering, ghosting, smearing, duplication, mutated proportions, inconsistent clothing, flat colors, desaturated, tonally compressed, poor background separation, exposure shift, uneven brightness, color balance shift",
|
| 947 |
-
height: int = 736,
|
| 948 |
-
width: int = 1280,
|
| 949 |
-
num_frames: int = 121,
|
| 950 |
-
frame_rate: int = 24,
|
| 951 |
-
num_inference_steps: int = 50,
|
| 952 |
-
timesteps: List[int] | None = None,
|
| 953 |
-
use_linear_quadratic_schedule: bool = False,
|
| 954 |
-
linear_quadratic_emulating_steps: int = 250,
|
| 955 |
-
num_videos_per_prompt: Optional[int] = 1,
|
| 956 |
-
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
|
| 957 |
-
latents: Optional[torch.Tensor] = None,
|
| 958 |
-
prompt_embeds: Optional[torch.Tensor] = None,
|
| 959 |
-
pooled_prompt_embeds: Optional[torch.Tensor] = None,
|
| 960 |
-
prompt_attention_mask: Optional[torch.Tensor] = None,
|
| 961 |
-
negative_prompt_embeds: Optional[torch.Tensor] = None,
|
| 962 |
-
negative_pooled_prompt_embeds: Optional[torch.Tensor] = None,
|
| 963 |
-
negative_prompt_attention_mask: Optional[torch.Tensor] = None,
|
| 964 |
-
output_type: Optional[str] = "pil",
|
| 965 |
-
return_dict: bool = True,
|
| 966 |
-
attention_kwargs: Optional[Dict[str, Any]] = None,
|
| 967 |
-
callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None,
|
| 968 |
-
callback_on_step_end_tensor_inputs: List[str] = ["latents"],
|
| 969 |
-
max_sequence_length: int = 512,
|
| 970 |
-
use_attention_mask: bool = True,
|
| 971 |
-
vae_batch_size: int | None = None,
|
| 972 |
-
):
|
| 973 |
-
r"""
|
| 974 |
-
Function invoked when calling the pipeline for generation.
|
| 975 |
-
|
| 976 |
-
Args:
|
| 977 |
-
prompt (`str` or `List[str]`, *optional*):
|
| 978 |
-
The prompt or prompts to guide the image generation. If not defined, one has to pass `prompt_embeds`.
|
| 979 |
-
instead.
|
| 980 |
-
negative_prompt (`str` or `List[str]`, *optional*):
|
| 981 |
-
The prompt or prompts not to guide the image generation. If not defined, one has to pass
|
| 982 |
-
`negative_prompt_embeds` instead. Ignored when not using guidance.
|
| 983 |
-
height (`int`, defaults to `352`):
|
| 984 |
-
The height in pixels of the generated image.
|
| 985 |
-
width (`int`, defaults to `640`):
|
| 986 |
-
The width in pixels of the generated image.
|
| 987 |
-
num_frames (`int`, defaults to `65`):
|
| 988 |
-
The number of video frames to generate
|
| 989 |
-
frame_rate (`int`, defaults to `25`):
|
| 990 |
-
Frame rate for the output video.
|
| 991 |
-
num_inference_steps (`int`, *optional*, defaults to 50):
|
| 992 |
-
The number of denoising steps. More denoising steps usually lead to a higher quality video at the
|
| 993 |
-
expense of slower inference.
|
| 994 |
-
timesteps (`List[int]`, *optional*):
|
| 995 |
-
Custom timesteps to use for the denoising process.
|
| 996 |
-
use_linear_quadratic_schedule (`bool`, defaults to `True`):
|
| 997 |
-
Whether to use a linear-quadratic sigma schedule instead of the default linear schedule.
|
| 998 |
-
This schedule combines linear interpolation in the first half (slow denoising at high noise)
|
| 999 |
-
with quadratic interpolation in the second half (faster denoising toward clean image).
|
| 1000 |
-
Requires `num_inference_steps` to be even.
|
| 1001 |
-
linear_quadratic_emulating_steps (`int`, defaults to `250`):
|
| 1002 |
-
Controls the slope of linear interpolation in the first half of the linear-quadratic schedule.
|
| 1003 |
-
Higher values result in a gentler slope. Only used when `use_linear_quadratic_schedule=True`.
|
| 1004 |
-
num_videos_per_prompt (`int`, *optional*, defaults to 1):
|
| 1005 |
-
The number of videos to generate per prompt.
|
| 1006 |
-
generator (`torch.Generator` or `List[torch.Generator]`, *optional*):
|
| 1007 |
-
PyTorch Generator object(s) for deterministic generation.
|
| 1008 |
-
latents (`torch.Tensor`, *optional*):
|
| 1009 |
-
Pre-generated noisy latents.
|
| 1010 |
-
prompt_embeds (`torch.Tensor`, *optional*):
|
| 1011 |
-
Pre-generated text embeddings.
|
| 1012 |
-
pooled_prompt_embeds (`torch.FloatTensor`, *optional*):
|
| 1013 |
-
Pre-generated pooled text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting.
|
| 1014 |
-
If not provided, pooled text embeddings will be generated from `prompt` input argument.
|
| 1015 |
-
prompt_attention_mask (`torch.Tensor`, *optional*):
|
| 1016 |
-
Pre-generated attention mask for text embeddings.
|
| 1017 |
-
negative_prompt_embeds (`torch.FloatTensor`, *optional*):
|
| 1018 |
-
Pre-generated negative text embeddings.
|
| 1019 |
-
negative_pooled_prompt_embeds (`torch.FloatTensor`, *optional*):
|
| 1020 |
-
Pre-generated negative pooled text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt
|
| 1021 |
-
weighting. If not provided, pooled negative_prompt_embeds will be generated from `negative_prompt`
|
| 1022 |
-
input argument.
|
| 1023 |
-
negative_prompt_attention_mask (`torch.FloatTensor`, *optional*):
|
| 1024 |
-
Pre-generated attention mask for negative text embeddings.
|
| 1025 |
-
output_type (`str`, *optional*, defaults to `"pil"`):
|
| 1026 |
-
The output format ("pil" or "np").
|
| 1027 |
-
return_dict (`bool`, *optional*, defaults to `True`):
|
| 1028 |
-
Whether to return a `MotifVideoPipelineOutput`.
|
| 1029 |
-
attention_kwargs (`dict`, *optional*):
|
| 1030 |
-
Arguments passed to the attention processor.
|
| 1031 |
-
callback_on_step_end (`Callable`, *optional*):
|
| 1032 |
-
Callback function called at the end of each step.
|
| 1033 |
-
callback_on_step_end_tensor_inputs (`List`, *optional*):
|
| 1034 |
-
Tensors to include in the callback.
|
| 1035 |
-
max_sequence_length (`int` defaults to `512`):
|
| 1036 |
-
Maximum sequence length for the tokenizer.
|
| 1037 |
-
|
| 1038 |
-
Examples:
|
| 1039 |
-
|
| 1040 |
-
Returns:
|
| 1041 |
-
[`~pipelines.motif_video.MotifVideoPipelineOutput`] or `tuple`:
|
| 1042 |
-
If `return_dict` is `True`, returns [`~pipelines.motif_video.MotifVideoPipelineOutput`],
|
| 1043 |
-
otherwise returns a tuple where the first element is a list of generated video frames.
|
| 1044 |
-
"""
|
| 1045 |
-
|
| 1046 |
-
if isinstance(callback_on_step_end, (PipelineCallback, MultiPipelineCallbacks)):
|
| 1047 |
-
callback_on_step_end_tensor_inputs = callback_on_step_end.tensor_inputs
|
| 1048 |
-
|
| 1049 |
-
# 1. Define call parameters (batch_size needed for check_inputs)
|
| 1050 |
-
if prompt is not None and isinstance(prompt, str):
|
| 1051 |
-
batch_size = 1
|
| 1052 |
-
elif prompt is not None and isinstance(prompt, list):
|
| 1053 |
-
batch_size = len(prompt)
|
| 1054 |
-
else:
|
| 1055 |
-
batch_size = prompt_embeds.shape[0]
|
| 1056 |
-
|
| 1057 |
-
# 2. Check inputs. Raise error if not correct
|
| 1058 |
-
self.check_inputs(
|
| 1059 |
-
prompt=prompt,
|
| 1060 |
-
negative_prompt=negative_prompt,
|
| 1061 |
-
height=height,
|
| 1062 |
-
width=width,
|
| 1063 |
-
batch_size=batch_size,
|
| 1064 |
-
callback_on_step_end_tensor_inputs=callback_on_step_end_tensor_inputs,
|
| 1065 |
-
prompt_embeds=prompt_embeds,
|
| 1066 |
-
negative_prompt_embeds=negative_prompt_embeds,
|
| 1067 |
-
prompt_attention_mask=prompt_attention_mask,
|
| 1068 |
-
negative_prompt_attention_mask=negative_prompt_attention_mask,
|
| 1069 |
-
)
|
| 1070 |
-
|
| 1071 |
-
self._attention_kwargs = attention_kwargs
|
| 1072 |
-
self._interrupt = False
|
| 1073 |
-
self._current_timestep = None
|
| 1074 |
-
|
| 1075 |
-
# Auto-upgrade AdaptiveProjectedGuidance to VideoAdaptiveProjectedGuidance
|
| 1076 |
-
# for video generation. Video-aware APG normalizes per-frame [C,H,W] instead
|
| 1077 |
-
# of collapsing the temporal axis, preserving motion quality.
|
| 1078 |
-
if type(self.guider) is AdaptiveProjectedGuidance:
|
| 1079 |
-
self.guider = VideoAdaptiveProjectedGuidance(
|
| 1080 |
-
guidance_scale=self.guider.guidance_scale,
|
| 1081 |
-
adaptive_projected_guidance_rescale=self.guider.adaptive_projected_guidance_rescale,
|
| 1082 |
-
adaptive_projected_guidance_momentum=self.guider.adaptive_projected_guidance_momentum,
|
| 1083 |
-
eta=self.guider.eta,
|
| 1084 |
-
use_original_formulation=self.guider.use_original_formulation,
|
| 1085 |
-
)
|
| 1086 |
-
|
| 1087 |
-
device = self._execution_device
|
| 1088 |
-
|
| 1089 |
-
# 3. Prepare text embeddings
|
| 1090 |
-
prompt_embeds, pooled_prompt_embeds, prompt_attention_mask, pos_actual_len = self.encode_prompt(
|
| 1091 |
-
prompt=prompt,
|
| 1092 |
-
num_videos_per_prompt=num_videos_per_prompt,
|
| 1093 |
-
prompt_embeds=prompt_embeds,
|
| 1094 |
-
pooled_prompt_embeds=pooled_prompt_embeds,
|
| 1095 |
-
prompt_attention_mask=prompt_attention_mask,
|
| 1096 |
-
max_sequence_length=max_sequence_length,
|
| 1097 |
-
device=device,
|
| 1098 |
-
)
|
| 1099 |
-
|
| 1100 |
-
if not self.guider._enabled and pos_actual_len is not None:
|
| 1101 |
-
prompt_embeds = prompt_embeds[:, :pos_actual_len, :]
|
| 1102 |
-
prompt_attention_mask = None
|
| 1103 |
-
|
| 1104 |
-
if self.guider._enabled:
|
| 1105 |
-
negative_prompt = self._prepare_negative_prompt(negative_prompt, batch_size)
|
| 1106 |
-
(
|
| 1107 |
-
negative_prompt_embeds,
|
| 1108 |
-
negative_pooled_prompt_embeds,
|
| 1109 |
-
negative_prompt_attention_mask,
|
| 1110 |
-
neg_actual_len,
|
| 1111 |
-
) = self.encode_prompt(
|
| 1112 |
-
prompt=negative_prompt,
|
| 1113 |
-
num_videos_per_prompt=num_videos_per_prompt,
|
| 1114 |
-
prompt_embeds=negative_prompt_embeds,
|
| 1115 |
-
pooled_prompt_embeds=negative_pooled_prompt_embeds,
|
| 1116 |
-
prompt_attention_mask=negative_prompt_attention_mask,
|
| 1117 |
-
max_sequence_length=max_sequence_length,
|
| 1118 |
-
device=device,
|
| 1119 |
-
)
|
| 1120 |
-
|
| 1121 |
-
# Trim each to its own actual length — guider runs pos/neg in separate loop iterations,
|
| 1122 |
-
# so different seq lengths are fine. No padding embeddings attend without mask.
|
| 1123 |
-
if pos_actual_len is not None and neg_actual_len is not None:
|
| 1124 |
-
prompt_embeds = prompt_embeds[:, :pos_actual_len, :]
|
| 1125 |
-
negative_prompt_embeds = negative_prompt_embeds[:, :neg_actual_len, :]
|
| 1126 |
-
prompt_attention_mask = None
|
| 1127 |
-
negative_prompt_attention_mask = None
|
| 1128 |
-
|
| 1129 |
-
num_channels_latents = self.vae.config.z_dim
|
| 1130 |
-
latents = self.prepare_latents(
|
| 1131 |
-
batch_size * num_videos_per_prompt,
|
| 1132 |
-
num_channels_latents,
|
| 1133 |
-
height,
|
| 1134 |
-
width,
|
| 1135 |
-
num_frames,
|
| 1136 |
-
self.transformer.dtype,
|
| 1137 |
-
device,
|
| 1138 |
-
generator,
|
| 1139 |
-
latents,
|
| 1140 |
-
)
|
| 1141 |
-
|
| 1142 |
-
# 4.5 Preprocess image for I2V conditioning
|
| 1143 |
-
if image is not None:
|
| 1144 |
-
from PIL import Image as PILImage
|
| 1145 |
-
|
| 1146 |
-
if isinstance(image, PILImage.Image):
|
| 1147 |
-
image = image.convert("RGB").resize((width, height), PILImage.LANCZOS)
|
| 1148 |
-
image = torch.from_numpy(np.array(image)).permute(2, 0, 1).float() / 255.0
|
| 1149 |
-
image = image * 2.0 - 1.0 # [0,1] -> [-1,1]
|
| 1150 |
-
image = image.unsqueeze(0) # [1, C, H, W]
|
| 1151 |
-
# Handle [C, H, W] -> [1, C, H, W]
|
| 1152 |
-
if image.dim() == 3:
|
| 1153 |
-
image = image.unsqueeze(0)
|
| 1154 |
-
# [B, C, H, W] -> [B, 1, C, H, W] for video format
|
| 1155 |
-
if image.dim() == 4:
|
| 1156 |
-
image = image.unsqueeze(1)
|
| 1157 |
-
image = image.to(device=device, dtype=self.vae.dtype)
|
| 1158 |
-
|
| 1159 |
-
# 5. Prepare timesteps (including mu calculation)
|
| 1160 |
-
|
| 1161 |
-
# Recalculate latent dims based on VAE for mu calculation
|
| 1162 |
-
latent_height = height // self.vae_scale_factor_spatial
|
| 1163 |
-
latent_width = width // self.vae_scale_factor_spatial
|
| 1164 |
-
latent_num_frames = (num_frames - 1) // self.vae_scale_factor_temporal + 1
|
| 1165 |
-
|
| 1166 |
-
# Calculate sequence length based on *packed* dimensions if transformer uses packing
|
| 1167 |
-
# Packed dims: H/patch, W/patch, F/patch_t
|
| 1168 |
-
packed_latent_height = latent_height // self.transformer_spatial_patch_size
|
| 1169 |
-
packed_latent_width = latent_width // self.transformer_spatial_patch_size
|
| 1170 |
-
packed_latent_num_frames = latent_num_frames // self.transformer_temporal_patch_size
|
| 1171 |
-
video_sequence_length = packed_latent_num_frames * packed_latent_height * packed_latent_width
|
| 1172 |
-
|
| 1173 |
-
# Compute sigmas: use linear-quadratic schedule if enabled, otherwise default linear
|
| 1174 |
-
_is_flow_multistep = isinstance(
|
| 1175 |
-
self.scheduler,
|
| 1176 |
-
(
|
| 1177 |
-
DPMSolverMultistepScheduler,
|
| 1178 |
-
UniPCMultistepScheduler,
|
| 1179 |
-
FlowUniPCMultistepScheduler,
|
| 1180 |
-
),
|
| 1181 |
-
)
|
| 1182 |
-
|
| 1183 |
-
# Compute mu once, shared by both branches (required by FlowUniPCMultistepScheduler)
|
| 1184 |
-
mu = calculate_shift(
|
| 1185 |
-
video_sequence_length,
|
| 1186 |
-
self.scheduler.config.get("base_image_seq_len", 256),
|
| 1187 |
-
self.scheduler.config.get("max_image_seq_len", 4096),
|
| 1188 |
-
self.scheduler.config.get("base_shift", 0.5),
|
| 1189 |
-
self.scheduler.config.get("max_shift", 1.15),
|
| 1190 |
-
)
|
| 1191 |
-
|
| 1192 |
-
if _is_flow_multistep:
|
| 1193 |
-
# DPMSolver/UniPC manage their own sigma schedule via use_flow_sigmas + flow_shift.
|
| 1194 |
-
# Pass mu for dynamic shifting support (required by FlowUniPCMultistepScheduler).
|
| 1195 |
-
timesteps, num_inference_steps = retrieve_timesteps(
|
| 1196 |
-
self.scheduler,
|
| 1197 |
-
num_inference_steps,
|
| 1198 |
-
device,
|
| 1199 |
-
timesteps,
|
| 1200 |
-
mu=mu,
|
| 1201 |
-
)
|
| 1202 |
-
else:
|
| 1203 |
-
if use_linear_quadratic_schedule:
|
| 1204 |
-
# Linear-quadratic schedule computes sigmas internally in retrieve_timesteps
|
| 1205 |
-
sigmas = None
|
| 1206 |
-
else:
|
| 1207 |
-
sigmas = np.linspace(1.0, 1 / num_inference_steps, num_inference_steps)
|
| 1208 |
-
|
| 1209 |
-
timesteps, num_inference_steps = retrieve_timesteps(
|
| 1210 |
-
self.scheduler,
|
| 1211 |
-
num_inference_steps,
|
| 1212 |
-
device,
|
| 1213 |
-
timesteps,
|
| 1214 |
-
sigmas=sigmas,
|
| 1215 |
-
use_linear_quadratic_schedule=use_linear_quadratic_schedule,
|
| 1216 |
-
linear_quadratic_emulating_steps=linear_quadratic_emulating_steps,
|
| 1217 |
-
mu=mu,
|
| 1218 |
-
)
|
| 1219 |
-
|
| 1220 |
-
# Get conditioning tensors
|
| 1221 |
-
latent_condition, latent_mask, image_embeds = self._prepare_first_frame_conditioning(
|
| 1222 |
-
image,
|
| 1223 |
-
latents,
|
| 1224 |
-
use_conditioning=image is not None,
|
| 1225 |
-
generator=generator,
|
| 1226 |
-
)
|
| 1227 |
-
|
| 1228 |
-
num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0)
|
| 1229 |
-
self._num_timesteps = len(timesteps)
|
| 1230 |
-
|
| 1231 |
-
# 6. Denoising loop
|
| 1232 |
-
with self.progress_bar(total=num_inference_steps) as progress_bar:
|
| 1233 |
-
for i, t in enumerate(timesteps):
|
| 1234 |
-
if self.interrupt:
|
| 1235 |
-
continue
|
| 1236 |
-
|
| 1237 |
-
self._current_timestep = t
|
| 1238 |
-
|
| 1239 |
-
# Concatenate current latents with conditioning for this timestep
|
| 1240 |
-
# [latents | latent_condition | latent_mask]
|
| 1241 |
-
hidden_states = torch.cat([latents, latent_condition, latent_mask], dim=1)
|
| 1242 |
-
|
| 1243 |
-
# broadcast to batch dimension in a way that's compatible with ONNX/Core ML
|
| 1244 |
-
timestep = t.expand(latents.shape[0])
|
| 1245 |
-
|
| 1246 |
-
# Step 1: Collect model inputs needed for the guidance method
|
| 1247 |
-
# conditional inputs should always be first element in the tuple
|
| 1248 |
-
guider_inputs = {
|
| 1249 |
-
"encoder_hidden_states": (prompt_embeds, negative_prompt_embeds),
|
| 1250 |
-
}
|
| 1251 |
-
if use_attention_mask and prompt_attention_mask is not None:
|
| 1252 |
-
guider_inputs["encoder_attention_mask"] = (
|
| 1253 |
-
prompt_attention_mask,
|
| 1254 |
-
negative_prompt_attention_mask,
|
| 1255 |
-
)
|
| 1256 |
-
if self.transformer.config.pooled_projection_dim is not None:
|
| 1257 |
-
guider_inputs["pooled_projections"] = (
|
| 1258 |
-
pooled_prompt_embeds,
|
| 1259 |
-
negative_pooled_prompt_embeds,
|
| 1260 |
-
)
|
| 1261 |
-
if image_embeds is not None:
|
| 1262 |
-
guider_inputs["image_embeds"] = (image_embeds, image_embeds)
|
| 1263 |
-
|
| 1264 |
-
# Step 2: Update guider's internal state for this denoising step
|
| 1265 |
-
self.guider.set_state(step=i, num_inference_steps=num_inference_steps, timestep=t)
|
| 1266 |
-
# Sigma injection for guiders that support sigma-based gating
|
| 1267 |
-
# (Kynkäänniemi 2024). Must precede `prepare_inputs` because
|
| 1268 |
-
# `num_conditions` → `_is_cfg_enabled()` reads `_current_sigma`.
|
| 1269 |
-
# Duck-typed so diffusers-native guiders are unaffected; guard
|
| 1270 |
-
# on scheduler too since some schedulers don't expose `sigmas`.
|
| 1271 |
-
if hasattr(self.guider, "_current_sigma") and hasattr(self.scheduler, "sigmas"):
|
| 1272 |
-
self.guider._current_sigma = float(self.scheduler.sigmas[i])
|
| 1273 |
-
|
| 1274 |
-
# Step 3: Prepare batched model inputs based on the guidance method
|
| 1275 |
-
# The guider splits model inputs into separate batches for conditional/unconditional predictions.
|
| 1276 |
-
# For CFG with guider_inputs = {"encoder_hidden_states": (prompt_embeds, negative_prompt_embeds)}:
|
| 1277 |
-
# you will get a guider_state with two batches:
|
| 1278 |
-
# guider_state = [
|
| 1279 |
-
# {"encoder_hidden_states": prompt_embeds, "__guidance_identifier__": "pred_cond"}, # conditional batch
|
| 1280 |
-
# {"encoder_hidden_states": negative_prompt_embeds, "__guidance_identifier__": "pred_uncond"}, # unconditional batch
|
| 1281 |
-
# ]
|
| 1282 |
-
# Other guidance methods may return 1 batch (no guidance) or 3+ batches (e.g., PAG, APG).
|
| 1283 |
-
guider_state = self.guider.prepare_inputs(guider_inputs)
|
| 1284 |
-
|
| 1285 |
-
# Step 4: Run the denoiser for each batch
|
| 1286 |
-
# Each batch in guider_state represents a different conditioning (conditional, unconditional, etc.).
|
| 1287 |
-
# We run the model once per batch and store the noise prediction in guider_state_batch.noise_pred.
|
| 1288 |
-
for guider_state_batch in guider_state:
|
| 1289 |
-
self.guider.prepare_models(self.transformer)
|
| 1290 |
-
|
| 1291 |
-
# Extract conditioning kwargs for this batch (e.g., encoder_hidden_states)
|
| 1292 |
-
cond_kwargs = {
|
| 1293 |
-
input_name: getattr(guider_state_batch, input_name) for input_name in guider_inputs.keys()
|
| 1294 |
-
}
|
| 1295 |
-
|
| 1296 |
-
tread_disabled = getattr(self.guider, "_current_tread_disabled", False)
|
| 1297 |
-
|
| 1298 |
-
# Override TREAD selection ratio per batch if the guider provides one
|
| 1299 |
-
selection_ratio = getattr(self.guider, "_current_selection_ratio", None)
|
| 1300 |
-
tread_mixin = getattr(self.transformer, "_inference_tread_mixin", None)
|
| 1301 |
-
if (
|
| 1302 |
-
selection_ratio is not None
|
| 1303 |
-
and tread_mixin is not None
|
| 1304 |
-
and tread_mixin._tread_route is not None
|
| 1305 |
-
):
|
| 1306 |
-
tread_mixin._tread_route["sel"] = selection_ratio
|
| 1307 |
-
|
| 1308 |
-
# e.g. "pred_cond"/"pred_uncond"
|
| 1309 |
-
context_name = getattr(guider_state_batch, self.guider._identifier_key)
|
| 1310 |
-
with self.transformer.cache_context(context_name):
|
| 1311 |
-
# Run denoiser and store noise prediction in this batch
|
| 1312 |
-
|
| 1313 |
-
noise_pred = self.transformer(
|
| 1314 |
-
hidden_states=hidden_states,
|
| 1315 |
-
timestep=timestep,
|
| 1316 |
-
attention_kwargs=self.attention_kwargs,
|
| 1317 |
-
return_dict=False,
|
| 1318 |
-
tread_disabled=tread_disabled,
|
| 1319 |
-
**cond_kwargs,
|
| 1320 |
-
)[0].clone()
|
| 1321 |
-
|
| 1322 |
-
guider_state_batch.noise_pred = noise_pred
|
| 1323 |
-
# Cleanup model (e.g., remove hooks)
|
| 1324 |
-
self.guider.cleanup_models(self.transformer)
|
| 1325 |
-
|
| 1326 |
-
# Step 5: Combine predictions using the guidance method
|
| 1327 |
-
# The guider takes all noise predictions from guider_state and combines them according to the guidance algorithm.
|
| 1328 |
-
# Continuing the CFG example, the guider receives:
|
| 1329 |
-
# guider_state = [
|
| 1330 |
-
# {"encoder_hidden_states": prompt_embeds, "noise_pred": noise_pred_cond, "__guidance_identifier__": "pred_cond"}, # batch 0
|
| 1331 |
-
# {"encoder_hidden_states": negative_prompt_embeds, "noise_pred": noise_pred_uncond, "__guidance_identifier__": "pred_uncond"}, # batch 1
|
| 1332 |
-
# ]
|
| 1333 |
-
# And extracts predictions using the __guidance_identifier__:
|
| 1334 |
-
# pred_cond = guider_state[0]["noise_pred"] # extracts noise_pred_cond
|
| 1335 |
-
# pred_uncond = guider_state[1]["noise_pred"] # extracts noise_pred_uncond
|
| 1336 |
-
# Then applies CFG formula:
|
| 1337 |
-
# noise_pred = pred_uncond + guidance_scale * (pred_cond - pred_uncond)
|
| 1338 |
-
# Returns GuiderOutput(pred=noise_pred, pred_cond=pred_cond, pred_uncond=pred_uncond)
|
| 1339 |
-
noise_pred = self.guider(guider_state)[0]
|
| 1340 |
-
|
| 1341 |
-
# compute the previous noisy sample x_t -> x_t-1
|
| 1342 |
-
latents = self.scheduler.step(noise_pred, t, latents, return_dict=False)[0]
|
| 1343 |
-
|
| 1344 |
-
if callback_on_step_end is not None:
|
| 1345 |
-
callback_kwargs = {}
|
| 1346 |
-
for k in callback_on_step_end_tensor_inputs:
|
| 1347 |
-
callback_kwargs[k] = locals()[k]
|
| 1348 |
-
callback_outputs = callback_on_step_end(self, i, t, callback_kwargs)
|
| 1349 |
-
|
| 1350 |
-
latents = callback_outputs.pop("latents", latents)
|
| 1351 |
-
prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds)
|
| 1352 |
-
# Handle negative embeds if needed by callback
|
| 1353 |
-
if "negative_prompt_embeds" in callback_outputs:
|
| 1354 |
-
negative_prompt_embeds = callback_outputs.pop("negative_prompt_embeds")
|
| 1355 |
-
|
| 1356 |
-
# call the callback, if provided
|
| 1357 |
-
if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):
|
| 1358 |
-
progress_bar.update()
|
| 1359 |
-
|
| 1360 |
-
if XLA_AVAILABLE:
|
| 1361 |
-
xm.mark_step()
|
| 1362 |
-
|
| 1363 |
-
self._current_timestep = None
|
| 1364 |
-
|
| 1365 |
-
if output_type == "latent":
|
| 1366 |
-
video = latents
|
| 1367 |
-
else:
|
| 1368 |
-
latents = latents.to(self.vae.dtype)
|
| 1369 |
-
latents = self._denormalize_latents(latents, self.vae.config.latents_mean, self.vae.config.latents_std)
|
| 1370 |
-
if vae_batch_size is not None and latents.shape[0] > vae_batch_size:
|
| 1371 |
-
video_chunks = []
|
| 1372 |
-
for i in range(0, latents.shape[0], vae_batch_size):
|
| 1373 |
-
chunk = latents[i : i + vae_batch_size]
|
| 1374 |
-
video_chunks.append(self.vae.decode(chunk, return_dict=False)[0])
|
| 1375 |
-
video = torch.cat(video_chunks, dim=0)
|
| 1376 |
-
del video_chunks
|
| 1377 |
-
else:
|
| 1378 |
-
video = self.vae.decode(latents, return_dict=False)[0]
|
| 1379 |
-
video = self.video_processor.postprocess_video(video, output_type=output_type)
|
| 1380 |
-
|
| 1381 |
-
# Offload all models
|
| 1382 |
-
self.maybe_free_model_hooks()
|
| 1383 |
-
|
| 1384 |
-
if not return_dict:
|
| 1385 |
-
return (video,)
|
| 1386 |
-
|
| 1387 |
-
# Return updated output type
|
| 1388 |
-
return MotifVideoPipelineOutput(frames=video)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|