Spaces:
Paused
Paused
init: LingBot-Video MoE 30B-A3B ZeroGPU demo
Browse files- .gitattributes +2 -0
- README.md +27 -8
- app.py +456 -0
- examples/t2i_bottle.txt +1 -0
- examples/t2v_bubbles.txt +1 -0
- examples/t2v_robot_desk.txt +1 -0
- examples/ti2v_1.png +3 -0
- examples/ti2v_1.txt +1 -0
- examples/ti2v_5.png +3 -0
- examples/ti2v_5.txt +1 -0
- lingbot_video/__init__.py +36 -0
- lingbot_video/default_negative_prompt.json +57 -0
- lingbot_video/default_negative_prompt_image.json +41 -0
- lingbot_video/moe_pack_kernels.py +142 -0
- lingbot_video/moe_restore_kernels.py +92 -0
- lingbot_video/pipeline_lingbot_video.py +591 -0
- lingbot_video/pipeline_lingbot_video_i2v.py +352 -0
- lingbot_video/scheduling_flow_unipc.py +796 -0
- lingbot_video/sglang_moe_shim.py +297 -0
- lingbot_video/transformer_lingbot_video.py +1312 -0
- lingbot_video/utils.py +287 -0
- requirements.txt +11 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
examples/ti2v_1.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
examples/ti2v_5.png filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -1,13 +1,32 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 6.
|
| 8 |
-
python_version: '3.12'
|
| 9 |
app_file: app.py
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: LingBot-Video MoE 30B-A3B
|
| 3 |
+
emoji: 🎬
|
| 4 |
+
colorFrom: indigo
|
| 5 |
+
colorTo: purple
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 6.10.0
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
+
license: apache-2.0
|
| 10 |
+
short_description: Embodied MoE video generation — T2V, I2V, T2I
|
| 11 |
+
models:
|
| 12 |
+
- robbyant/lingbot-video-moe-30b-a3b
|
| 13 |
+
startup_duration_timeout: 1h
|
| 14 |
---
|
| 15 |
|
| 16 |
+
# LingBot-Video MoE 30B-A3B — ZeroGPU demo
|
| 17 |
+
|
| 18 |
+
Text-to-video, image-to-video and text-to-image with
|
| 19 |
+
[LingBot-Video MoE 30B-A3B](https://huggingface.co/robbyant/lingbot-video-moe-30b-a3b),
|
| 20 |
+
the first open-source large-scale MoE video generation model dedicated to embodied intelligence
|
| 21 |
+
(30B parameters, ~3B active per token).
|
| 22 |
+
|
| 23 |
+
This demo runs the base MoE DiT (the refiner stage is skipped to fit ZeroGPU) with:
|
| 24 |
+
|
| 25 |
+
- bf16 transformer + text encoder (Qwen3-VL), fp32 Wan VAE
|
| 26 |
+
- 480p resolution buckets, up to 5 s at 24 fps
|
| 27 |
+
- sequential CFG with SDPA flash attention
|
| 28 |
+
- `torch._grouped_mm` MoE expert dispatch
|
| 29 |
+
|
| 30 |
+
The model was trained on detailed structured captions — long, specific prompts (or JSON captions
|
| 31 |
+
from the [LingBot rewriter](https://huggingface.co/robbyant/lingbot-video-rewriter-lora)) work
|
| 32 |
+
much better than short casual ones.
|
app.py
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
os.environ.setdefault("HF_HOME", "/tmp/hf_home")
|
| 4 |
+
os.environ.setdefault("HF_MODULES_CACHE", "/tmp/hf_modules")
|
| 5 |
+
os.environ.setdefault("MPLCONFIGDIR", "/tmp/matplotlib")
|
| 6 |
+
# LingBot-Video runtime knobs (read at import / first forward).
|
| 7 |
+
os.environ.setdefault("LINGBOT_MOE_EXPERT_BACKEND", "grouped_mm")
|
| 8 |
+
os.environ.setdefault("LINGBOT_MOE_PAD_BACKEND", "vectorized")
|
| 9 |
+
os.environ.setdefault("DIFFUSERS_ATTN_BACKEND", "_native_flash")
|
| 10 |
+
|
| 11 |
+
import spaces # noqa: E402 — must be imported before torch
|
| 12 |
+
|
| 13 |
+
import random # noqa: E402
|
| 14 |
+
import time # noqa: E402
|
| 15 |
+
from pathlib import Path # noqa: E402
|
| 16 |
+
|
| 17 |
+
import gradio as gr # noqa: E402
|
| 18 |
+
import numpy as np # noqa: E402
|
| 19 |
+
import torch # noqa: E402
|
| 20 |
+
from PIL import Image # noqa: E402
|
| 21 |
+
from diffusers import AutoencoderKLWan # noqa: E402
|
| 22 |
+
from diffusers.utils import export_to_video # noqa: E402
|
| 23 |
+
from huggingface_hub import snapshot_download # noqa: E402
|
| 24 |
+
from transformers import AutoProcessor, Qwen3VLForConditionalGeneration # noqa: E402
|
| 25 |
+
|
| 26 |
+
from lingbot_video import ( # noqa: E402
|
| 27 |
+
FlowUniPCMultistepScheduler,
|
| 28 |
+
LingBotVideoImageToVideoPipeline,
|
| 29 |
+
LingBotVideoPipeline,
|
| 30 |
+
LingBotVideoTransformer3DModel,
|
| 31 |
+
)
|
| 32 |
+
from lingbot_video.pipeline_lingbot_video import ( # noqa: E402
|
| 33 |
+
DEFAULT_NEGATIVE_PROMPT,
|
| 34 |
+
DEFAULT_NEGATIVE_PROMPT_IMAGE,
|
| 35 |
+
)
|
| 36 |
+
from lingbot_video.utils import num_frames_from_duration # noqa: E402
|
| 37 |
+
|
| 38 |
+
MODEL_ID = "robbyant/lingbot-video-moe-30b-a3b"
|
| 39 |
+
FPS = 24
|
| 40 |
+
MAX_SEED = 2**31 - 1
|
| 41 |
+
MAX_GPU_SECONDS = 300
|
| 42 |
+
|
| 43 |
+
# (height, width), multiples of 16 — official 480p buckets.
|
| 44 |
+
VIDEO_SIZES = {
|
| 45 |
+
"832 × 480 (16:9)": (480, 832),
|
| 46 |
+
"480 × 832 (9:16)": (832, 480),
|
| 47 |
+
"640 × 480 (4:3)": (480, 640),
|
| 48 |
+
"480 × 480 (1:1)": (480, 480),
|
| 49 |
+
}
|
| 50 |
+
IMAGE_SIZES = {
|
| 51 |
+
"832 × 480 (16:9)": (480, 832),
|
| 52 |
+
"480 × 832 (9:16)": (832, 480),
|
| 53 |
+
"480 × 480 (1:1)": (480, 480),
|
| 54 |
+
"1280 × 736 (16:9, 720p)": (736, 1280),
|
| 55 |
+
"736 × 1280 (9:16, 720p)": (1280, 736),
|
| 56 |
+
"1088 × 1088 (1:1, 1080p)": (1088, 1088),
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
EXAMPLES_DIR = Path(__file__).parent / "examples"
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def _read_example(name: str) -> str:
|
| 63 |
+
return (EXAMPLES_DIR / name).read_text(encoding="utf-8").strip()
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
print(f"[startup] downloading {MODEL_ID} (refiner excluded) ...", flush=True)
|
| 67 |
+
t0 = time.perf_counter()
|
| 68 |
+
model_dir = snapshot_download(
|
| 69 |
+
MODEL_ID,
|
| 70 |
+
ignore_patterns=["refiner/*", "*.bak_lingbot_video_diffusers"],
|
| 71 |
+
)
|
| 72 |
+
print(f"[startup] snapshot ready in {time.perf_counter() - t0:.1f}s", flush=True)
|
| 73 |
+
|
| 74 |
+
print(f"[startup] torch {torch.__version__}, grouped_mm available: {hasattr(torch, '_grouped_mm')}", flush=True)
|
| 75 |
+
|
| 76 |
+
t0 = time.perf_counter()
|
| 77 |
+
transformer = LingBotVideoTransformer3DModel.from_pretrained(
|
| 78 |
+
model_dir, subfolder="transformer", torch_dtype=torch.bfloat16
|
| 79 |
+
)
|
| 80 |
+
text_encoder = Qwen3VLForConditionalGeneration.from_pretrained(
|
| 81 |
+
model_dir, subfolder="text_encoder", dtype=torch.bfloat16, attn_implementation="sdpa"
|
| 82 |
+
)
|
| 83 |
+
processor = AutoProcessor.from_pretrained(model_dir, subfolder="processor")
|
| 84 |
+
vae = AutoencoderKLWan.from_pretrained(model_dir, subfolder="vae", torch_dtype=torch.float32)
|
| 85 |
+
print(f"[startup] components loaded in {time.perf_counter() - t0:.1f}s", flush=True)
|
| 86 |
+
|
| 87 |
+
t0 = time.perf_counter()
|
| 88 |
+
pipe = LingBotVideoPipeline(
|
| 89 |
+
transformer=transformer,
|
| 90 |
+
vae=vae,
|
| 91 |
+
text_encoder=text_encoder,
|
| 92 |
+
processor=processor,
|
| 93 |
+
scheduler=FlowUniPCMultistepScheduler.from_pretrained(model_dir, subfolder="scheduler"),
|
| 94 |
+
).to("cuda")
|
| 95 |
+
pipe_i2v = LingBotVideoImageToVideoPipeline(
|
| 96 |
+
transformer=transformer,
|
| 97 |
+
vae=vae,
|
| 98 |
+
text_encoder=text_encoder,
|
| 99 |
+
processor=processor,
|
| 100 |
+
scheduler=FlowUniPCMultistepScheduler.from_pretrained(model_dir, subfolder="scheduler"),
|
| 101 |
+
)
|
| 102 |
+
print(f"[startup] pipelines on cuda in {time.perf_counter() - t0:.1f}s", flush=True)
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def _resolve_seed(seed: float, randomize: bool) -> int:
|
| 106 |
+
if randomize:
|
| 107 |
+
return random.randint(0, MAX_SEED)
|
| 108 |
+
return int(seed)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def _latent_tokens(height: int, width: int, num_frames: int) -> int:
|
| 112 |
+
latent_frames = (num_frames - 1) // 4 + 1
|
| 113 |
+
return latent_frames * (height // 16) * (width // 16)
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def _step_seconds(height: int, width: int, num_frames: int) -> float:
|
| 117 |
+
# Empirical per-step cost model (CFG included): quadratic attention term +
|
| 118 |
+
# linear MoE/MLP term, normalized to the 81-frame 480x832 token count.
|
| 119 |
+
r = _latent_tokens(height, width, num_frames) / 32760.0
|
| 120 |
+
return 9.0 * r * r + 6.0 * r
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def _estimate_video_seconds(size_label: str, duration_s: float, steps: int, sizes=VIDEO_SIZES) -> int:
|
| 124 |
+
height, width = sizes[size_label]
|
| 125 |
+
num_frames = num_frames_from_duration(duration_s, FPS)
|
| 126 |
+
return int(40.0 + steps * _step_seconds(height, width, num_frames))
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def _estimate_image_seconds(size_label: str, steps: int) -> int:
|
| 130 |
+
height, width = IMAGE_SIZES[size_label]
|
| 131 |
+
return int(30.0 + steps * max(_step_seconds(height, width, 1), 0.35))
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def _check_budget(estimate: int) -> None:
|
| 135 |
+
if estimate > MAX_GPU_SECONDS:
|
| 136 |
+
raise gr.Error(
|
| 137 |
+
f"These settings need ~{estimate}s of GPU time (max {MAX_GPU_SECONDS}s). "
|
| 138 |
+
"Reduce the video duration, steps, or resolution."
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def _export_video(frames: np.ndarray) -> str:
|
| 143 |
+
path = f"/tmp/lingbot_{int(time.time() * 1000)}.mp4"
|
| 144 |
+
export_to_video(frames, path, fps=FPS)
|
| 145 |
+
return path
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def _t2v_duration(prompt, size_label, duration_s, steps, *args, **kwargs) -> int:
|
| 149 |
+
return min(MAX_GPU_SECONDS, _estimate_video_seconds(size_label, duration_s, int(steps)) + 20)
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
@spaces.GPU(duration=_t2v_duration, size="xlarge")
|
| 153 |
+
def generate_t2v(
|
| 154 |
+
prompt,
|
| 155 |
+
size_label,
|
| 156 |
+
duration_s,
|
| 157 |
+
steps,
|
| 158 |
+
guidance_scale,
|
| 159 |
+
shift,
|
| 160 |
+
negative_prompt,
|
| 161 |
+
seed,
|
| 162 |
+
randomize_seed,
|
| 163 |
+
progress=gr.Progress(track_tqdm=True),
|
| 164 |
+
):
|
| 165 |
+
if not prompt or not prompt.strip():
|
| 166 |
+
raise gr.Error("Please enter a prompt.")
|
| 167 |
+
estimate = _estimate_video_seconds(size_label, duration_s, int(steps))
|
| 168 |
+
_check_budget(estimate)
|
| 169 |
+
height, width = VIDEO_SIZES[size_label]
|
| 170 |
+
num_frames = num_frames_from_duration(duration_s, FPS)
|
| 171 |
+
seed = _resolve_seed(seed, randomize_seed)
|
| 172 |
+
t0 = time.perf_counter()
|
| 173 |
+
output = pipe(
|
| 174 |
+
prompt=prompt.strip(),
|
| 175 |
+
negative_prompt=negative_prompt.strip() or DEFAULT_NEGATIVE_PROMPT,
|
| 176 |
+
height=height,
|
| 177 |
+
width=width,
|
| 178 |
+
num_frames=num_frames,
|
| 179 |
+
num_inference_steps=int(steps),
|
| 180 |
+
guidance_scale=float(guidance_scale),
|
| 181 |
+
shift=float(shift),
|
| 182 |
+
generator=torch.Generator().manual_seed(seed),
|
| 183 |
+
)
|
| 184 |
+
torch.cuda.synchronize()
|
| 185 |
+
elapsed = time.perf_counter() - t0
|
| 186 |
+
print(
|
| 187 |
+
f"[timing] t2v {width}x{height} f={num_frames} steps={int(steps)} "
|
| 188 |
+
f"tokens={_latent_tokens(height, width, num_frames)} took {elapsed:.1f}s "
|
| 189 |
+
f"(estimated {estimate}s)",
|
| 190 |
+
flush=True,
|
| 191 |
+
)
|
| 192 |
+
return _export_video(output.frames[0]), seed
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
def _i2v_duration(image, prompt, size_label, duration_s, steps, *args, **kwargs) -> int:
|
| 196 |
+
return min(MAX_GPU_SECONDS, _estimate_video_seconds(size_label, duration_s, int(steps)) + 35)
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
@spaces.GPU(duration=_i2v_duration, size="xlarge")
|
| 200 |
+
def generate_i2v(
|
| 201 |
+
image,
|
| 202 |
+
prompt,
|
| 203 |
+
size_label,
|
| 204 |
+
duration_s,
|
| 205 |
+
steps,
|
| 206 |
+
guidance_scale,
|
| 207 |
+
shift,
|
| 208 |
+
negative_prompt,
|
| 209 |
+
seed,
|
| 210 |
+
randomize_seed,
|
| 211 |
+
progress=gr.Progress(track_tqdm=True),
|
| 212 |
+
):
|
| 213 |
+
if image is None:
|
| 214 |
+
raise gr.Error("Please upload a first-frame image.")
|
| 215 |
+
if not prompt or not prompt.strip():
|
| 216 |
+
raise gr.Error("Please enter a prompt.")
|
| 217 |
+
estimate = _estimate_video_seconds(size_label, duration_s, int(steps)) + 15
|
| 218 |
+
_check_budget(estimate)
|
| 219 |
+
height, width = VIDEO_SIZES[size_label]
|
| 220 |
+
num_frames = num_frames_from_duration(duration_s, FPS)
|
| 221 |
+
seed = _resolve_seed(seed, randomize_seed)
|
| 222 |
+
t0 = time.perf_counter()
|
| 223 |
+
output = pipe_i2v(
|
| 224 |
+
prompt=prompt.strip(),
|
| 225 |
+
image=image,
|
| 226 |
+
negative_prompt=negative_prompt.strip() or DEFAULT_NEGATIVE_PROMPT,
|
| 227 |
+
height=height,
|
| 228 |
+
width=width,
|
| 229 |
+
num_frames=num_frames,
|
| 230 |
+
num_inference_steps=int(steps),
|
| 231 |
+
guidance_scale=float(guidance_scale),
|
| 232 |
+
shift=float(shift),
|
| 233 |
+
generator=torch.Generator().manual_seed(seed),
|
| 234 |
+
)
|
| 235 |
+
torch.cuda.synchronize()
|
| 236 |
+
elapsed = time.perf_counter() - t0
|
| 237 |
+
print(
|
| 238 |
+
f"[timing] i2v {width}x{height} f={num_frames} steps={int(steps)} took {elapsed:.1f}s "
|
| 239 |
+
f"(estimated {estimate}s)",
|
| 240 |
+
flush=True,
|
| 241 |
+
)
|
| 242 |
+
return _export_video(output.frames[0]), seed
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def _t2i_duration(prompt, size_label, steps, *args, **kwargs) -> int:
|
| 246 |
+
return min(MAX_GPU_SECONDS, _estimate_image_seconds(size_label, int(steps)) + 20)
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
@spaces.GPU(duration=_t2i_duration, size="xlarge")
|
| 250 |
+
def generate_t2i(
|
| 251 |
+
prompt,
|
| 252 |
+
size_label,
|
| 253 |
+
steps,
|
| 254 |
+
guidance_scale,
|
| 255 |
+
shift,
|
| 256 |
+
negative_prompt,
|
| 257 |
+
seed,
|
| 258 |
+
randomize_seed,
|
| 259 |
+
progress=gr.Progress(track_tqdm=True),
|
| 260 |
+
):
|
| 261 |
+
if not prompt or not prompt.strip():
|
| 262 |
+
raise gr.Error("Please enter a prompt.")
|
| 263 |
+
height, width = IMAGE_SIZES[size_label]
|
| 264 |
+
seed = _resolve_seed(seed, randomize_seed)
|
| 265 |
+
t0 = time.perf_counter()
|
| 266 |
+
output = pipe(
|
| 267 |
+
prompt=prompt.strip(),
|
| 268 |
+
negative_prompt=negative_prompt.strip() or DEFAULT_NEGATIVE_PROMPT_IMAGE,
|
| 269 |
+
height=height,
|
| 270 |
+
width=width,
|
| 271 |
+
num_frames=1,
|
| 272 |
+
num_inference_steps=int(steps),
|
| 273 |
+
guidance_scale=float(guidance_scale),
|
| 274 |
+
shift=float(shift),
|
| 275 |
+
generator=torch.Generator().manual_seed(seed),
|
| 276 |
+
)
|
| 277 |
+
torch.cuda.synchronize()
|
| 278 |
+
elapsed = time.perf_counter() - t0
|
| 279 |
+
print(f"[timing] t2i {width}x{height} steps={int(steps)} took {elapsed:.1f}s", flush=True)
|
| 280 |
+
frame = output.frames[0][0]
|
| 281 |
+
image = Image.fromarray((np.clip(frame, 0, 1) * 255).astype(np.uint8))
|
| 282 |
+
return image, seed
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
DESCRIPTION = """
|
| 286 |
+
# LingBot-Video MoE 30B-A3B
|
| 287 |
+
|
| 288 |
+
First open-source large-scale MoE video generation model dedicated to **embodied intelligence** — 30B parameters,
|
| 289 |
+
3B active per token. Text-to-video, image-to-video and text-to-image with a single model.
|
| 290 |
+
|
| 291 |
+
[Model card](https://huggingface.co/robbyant/lingbot-video-moe-30b-a3b) ·
|
| 292 |
+
[GitHub](https://github.com/Robbyant/lingbot-video) ·
|
| 293 |
+
[Project page](https://technology.robbyant.com/lingbot-video)
|
| 294 |
+
|
| 295 |
+
> 💡 The model was trained on **detailed structured captions**: long, specific prompts (or the JSON captions produced
|
| 296 |
+
> by the [LingBot rewriter](https://huggingface.co/robbyant/lingbot-video-rewriter-lora)) work much better than short ones.
|
| 297 |
+
> This demo runs the base MoE model (no refiner) at 480p on ZeroGPU.
|
| 298 |
+
"""
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
def _seed_row():
|
| 302 |
+
with gr.Row():
|
| 303 |
+
seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=42)
|
| 304 |
+
randomize = gr.Checkbox(label="Randomize seed", value=True)
|
| 305 |
+
return seed, randomize
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
def _advanced(negative_default: str, steps_value: int):
|
| 309 |
+
with gr.Accordion("Advanced settings", open=False):
|
| 310 |
+
steps = gr.Slider(label="Inference steps", minimum=4, maximum=40, step=1, value=steps_value)
|
| 311 |
+
guidance = gr.Slider(label="Guidance scale", minimum=1.0, maximum=10.0, step=0.5, value=3.0)
|
| 312 |
+
shift = gr.Slider(label="Flow shift", minimum=1.0, maximum=9.0, step=0.5, value=3.0)
|
| 313 |
+
negative = gr.Textbox(label="Negative prompt", value=negative_default, lines=3)
|
| 314 |
+
seed, randomize = _seed_row()
|
| 315 |
+
return steps, guidance, shift, negative, seed, randomize
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
with gr.Blocks(title="LingBot-Video MoE 30B-A3B") as demo:
|
| 319 |
+
gr.Markdown(DESCRIPTION)
|
| 320 |
+
|
| 321 |
+
with gr.Tab("Text → Video"):
|
| 322 |
+
with gr.Row():
|
| 323 |
+
with gr.Column():
|
| 324 |
+
t2v_prompt = gr.Textbox(
|
| 325 |
+
label="Prompt",
|
| 326 |
+
placeholder="Describe the scene in detail (plain text or a LingBot JSON caption)...",
|
| 327 |
+
lines=5,
|
| 328 |
+
max_lines=12,
|
| 329 |
+
)
|
| 330 |
+
with gr.Row():
|
| 331 |
+
t2v_size = gr.Dropdown(
|
| 332 |
+
label="Resolution", choices=list(VIDEO_SIZES), value="832 × 480 (16:9)"
|
| 333 |
+
)
|
| 334 |
+
t2v_dur = gr.Slider(
|
| 335 |
+
label="Video duration (s)", minimum=1.0, maximum=5.0, step=0.5, value=2.0
|
| 336 |
+
)
|
| 337 |
+
t2v_steps, t2v_guidance, t2v_shift, t2v_negative, t2v_seed, t2v_rand = _advanced(
|
| 338 |
+
DEFAULT_NEGATIVE_PROMPT, 24
|
| 339 |
+
)
|
| 340 |
+
t2v_btn = gr.Button("Generate video", variant="primary")
|
| 341 |
+
with gr.Column():
|
| 342 |
+
t2v_out = gr.Video(label="Generated video", autoplay=True)
|
| 343 |
+
t2v_seed_out = gr.Number(label="Seed used", interactive=False)
|
| 344 |
+
|
| 345 |
+
t2v_inputs = [
|
| 346 |
+
t2v_prompt, t2v_size, t2v_dur, t2v_steps, t2v_guidance, t2v_shift,
|
| 347 |
+
t2v_negative, t2v_seed, t2v_rand,
|
| 348 |
+
]
|
| 349 |
+
t2v_btn.click(generate_t2v, inputs=t2v_inputs, outputs=[t2v_out, t2v_seed_out],
|
| 350 |
+
concurrency_id="gpu", concurrency_limit=1)
|
| 351 |
+
gr.Examples(
|
| 352 |
+
examples=[
|
| 353 |
+
[_read_example("t2v_bubbles.txt"), "832 × 480 (16:9)", 2.0, 24, 3.0, 3.0,
|
| 354 |
+
DEFAULT_NEGATIVE_PROMPT, 42, False],
|
| 355 |
+
[_read_example("t2v_robot_desk.txt"), "832 × 480 (16:9)", 2.0, 24, 3.0, 3.0,
|
| 356 |
+
DEFAULT_NEGATIVE_PROMPT, 42, False],
|
| 357 |
+
["A corgi puppy runs through shallow water on a beach at golden hour. Slow motion, "
|
| 358 |
+
"splashes glistening in warm sunlight, shallow depth of field, cinematic color grading. "
|
| 359 |
+
"The camera tracks alongside the puppy at a low angle close to the water surface.",
|
| 360 |
+
"832 × 480 (16:9)", 2.0, 24, 3.0, 3.0, DEFAULT_NEGATIVE_PROMPT, 42, False],
|
| 361 |
+
],
|
| 362 |
+
fn=generate_t2v,
|
| 363 |
+
inputs=t2v_inputs,
|
| 364 |
+
outputs=[t2v_out, t2v_seed_out],
|
| 365 |
+
cache_examples=True,
|
| 366 |
+
cache_mode="lazy",
|
| 367 |
+
)
|
| 368 |
+
|
| 369 |
+
with gr.Tab("Image → Video"):
|
| 370 |
+
with gr.Row():
|
| 371 |
+
with gr.Column():
|
| 372 |
+
i2v_image = gr.Image(label="First frame", type="pil")
|
| 373 |
+
i2v_prompt = gr.Textbox(
|
| 374 |
+
label="Prompt",
|
| 375 |
+
placeholder="Describe how the scene should evolve...",
|
| 376 |
+
lines=4,
|
| 377 |
+
max_lines=12,
|
| 378 |
+
)
|
| 379 |
+
with gr.Row():
|
| 380 |
+
i2v_size = gr.Dropdown(
|
| 381 |
+
label="Resolution", choices=list(VIDEO_SIZES), value="832 × 480 (16:9)"
|
| 382 |
+
)
|
| 383 |
+
i2v_dur = gr.Slider(
|
| 384 |
+
label="Video duration (s)", minimum=1.0, maximum=5.0, step=0.5, value=2.0
|
| 385 |
+
)
|
| 386 |
+
i2v_steps, i2v_guidance, i2v_shift, i2v_negative, i2v_seed, i2v_rand = _advanced(
|
| 387 |
+
DEFAULT_NEGATIVE_PROMPT, 24
|
| 388 |
+
)
|
| 389 |
+
i2v_btn = gr.Button("Generate video", variant="primary")
|
| 390 |
+
with gr.Column():
|
| 391 |
+
i2v_out = gr.Video(label="Generated video", autoplay=True)
|
| 392 |
+
i2v_seed_out = gr.Number(label="Seed used", interactive=False)
|
| 393 |
+
|
| 394 |
+
i2v_inputs = [
|
| 395 |
+
i2v_image, i2v_prompt, i2v_size, i2v_dur, i2v_steps, i2v_guidance, i2v_shift,
|
| 396 |
+
i2v_negative, i2v_seed, i2v_rand,
|
| 397 |
+
]
|
| 398 |
+
i2v_btn.click(generate_i2v, inputs=i2v_inputs, outputs=[i2v_out, i2v_seed_out],
|
| 399 |
+
concurrency_id="gpu", concurrency_limit=1)
|
| 400 |
+
gr.Examples(
|
| 401 |
+
examples=[
|
| 402 |
+
[str(EXAMPLES_DIR / "ti2v_1.png"), _read_example("ti2v_1.txt"),
|
| 403 |
+
"832 × 480 (16:9)", 2.0, 24, 3.0, 3.0, DEFAULT_NEGATIVE_PROMPT, 42, False],
|
| 404 |
+
[str(EXAMPLES_DIR / "ti2v_5.png"), _read_example("ti2v_5.txt"),
|
| 405 |
+
"832 × 480 (16:9)", 2.0, 24, 3.0, 3.0, DEFAULT_NEGATIVE_PROMPT, 42, False],
|
| 406 |
+
],
|
| 407 |
+
fn=generate_i2v,
|
| 408 |
+
inputs=i2v_inputs,
|
| 409 |
+
outputs=[i2v_out, i2v_seed_out],
|
| 410 |
+
cache_examples=True,
|
| 411 |
+
cache_mode="lazy",
|
| 412 |
+
)
|
| 413 |
+
|
| 414 |
+
with gr.Tab("Text → Image"):
|
| 415 |
+
with gr.Row():
|
| 416 |
+
with gr.Column():
|
| 417 |
+
t2i_prompt = gr.Textbox(
|
| 418 |
+
label="Prompt",
|
| 419 |
+
placeholder="Describe the image in detail...",
|
| 420 |
+
lines=5,
|
| 421 |
+
max_lines=12,
|
| 422 |
+
)
|
| 423 |
+
t2i_size = gr.Dropdown(
|
| 424 |
+
label="Resolution", choices=list(IMAGE_SIZES), value="1280 × 736 (16:9, 720p)"
|
| 425 |
+
)
|
| 426 |
+
t2i_steps, t2i_guidance, t2i_shift, t2i_negative, t2i_seed, t2i_rand = _advanced(
|
| 427 |
+
DEFAULT_NEGATIVE_PROMPT_IMAGE, 30
|
| 428 |
+
)
|
| 429 |
+
t2i_btn = gr.Button("Generate image", variant="primary")
|
| 430 |
+
with gr.Column():
|
| 431 |
+
t2i_out = gr.Image(label="Generated image")
|
| 432 |
+
t2i_seed_out = gr.Number(label="Seed used", interactive=False)
|
| 433 |
+
|
| 434 |
+
t2i_inputs = [
|
| 435 |
+
t2i_prompt, t2i_size, t2i_steps, t2i_guidance, t2i_shift,
|
| 436 |
+
t2i_negative, t2i_seed, t2i_rand,
|
| 437 |
+
]
|
| 438 |
+
t2i_btn.click(generate_t2i, inputs=t2i_inputs, outputs=[t2i_out, t2i_seed_out],
|
| 439 |
+
concurrency_id="gpu", concurrency_limit=1)
|
| 440 |
+
gr.Examples(
|
| 441 |
+
examples=[
|
| 442 |
+
[_read_example("t2i_bottle.txt"), "1280 × 736 (16:9, 720p)", 30, 3.0, 3.0,
|
| 443 |
+
DEFAULT_NEGATIVE_PROMPT_IMAGE, 42, False],
|
| 444 |
+
["A humanoid robot chef flipping a pancake in a bright modern kitchen, "
|
| 445 |
+
"photorealistic, dramatic side lighting, steam rising from the pan, "
|
| 446 |
+
"shallow depth of field, 85mm lens.",
|
| 447 |
+
"1280 × 736 (16:9, 720p)", 30, 3.0, 3.0, DEFAULT_NEGATIVE_PROMPT_IMAGE, 42, False],
|
| 448 |
+
],
|
| 449 |
+
fn=generate_t2i,
|
| 450 |
+
inputs=t2i_inputs,
|
| 451 |
+
outputs=[t2i_out, t2i_seed_out],
|
| 452 |
+
cache_examples=True,
|
| 453 |
+
cache_mode="lazy",
|
| 454 |
+
)
|
| 455 |
+
|
| 456 |
+
demo.queue(max_size=30).launch()
|
examples/t2i_bottle.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"comprehensive_description":"A clear glass bottle filled with water sits on a light-colored wooden table, acting as a lens to focus sunlight. The bottle is positioned slightly to the left of the center, with its base resting on the table. Bright, direct sunlight from the upper right creates a brilliant, starburst-like lens flare on the table's surface, casting a warm, golden glow. The background is a soft, out-of-focus blur of green foliage and bright light, suggesting an outdoor garden or patio setting. The overall atmosphere is warm, serene, and summery, emphasizing the interplay between light and water.","camera_info":{"color":"Warm","frame_size":"Extreme Close Up","shot_type_angle":"Low angle","lens_size":"Long Lens","composition":"Left heavy","lighting":"Hard light","lighting_type":"Daylight"},"world_knowledge":[],"prominent_elements":[{"name":"glass bottle","description":"A clear, cylindrical glass bottle with a narrow neck and a flat base, partially filled with water.","location":"center-left","relative_size":"medium","shape_and_color":"Cylindrical with a tapered neck; transparent and clear","texture":"smooth, glossy","appearance_details":"The bottle contains clear water that reflects the surrounding light. The glass surface shows subtle highlights and reflections from the sun.","relationship":"Sits on the wooden table and acts as the focal point for the sunlight, creating a lens flare.","orientation":"upright","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"wooden table","description":"A flat surface made of light-colored wood with visible grain and horizontal planks.","location":"spanning the bottom half of the frame","relative_size":"large","shape_and_color":"Rectangular planks; light tan and beige","texture":"matte, grainy","appearance_details":"The surface is illuminated by a bright, star-shaped lens flare caused by the bottle focusing the sunlight.","relationship":"Provides the base upon which the bottle rests.","orientation":"horizontal","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"lens flare","description":"A bright, star-shaped burst of light created by sunlight passing through the water in the bottle.","location":"center, on the table surface","relative_size":"medium","shape_and_color":"Star-shaped; bright white and golden yellow","texture":"ethereal, glowing","appearance_details":"The flare has multiple sharp rays extending outwards from the point where the bottle meets the table.","relationship":"Originates from the interaction of sunlight and the water inside the glass bottle.","orientation":"radiating","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"blurred foliage","description":"A soft-focus background consisting of green leaves and bright light spots.","location":"top half of the frame","relative_size":"large","shape_and_color":"Amorphous shapes; various shades of green and bright white","texture":"soft, blurry","appearance_details":"The background is heavily out of focus, creating a bokeh effect that emphasizes the sharpness of the bottle in the foreground.","relationship":"Provides a natural, outdoor context for the scene.","orientation":"upright","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":"","is_cluster":true,"number_of_objects":"numerous"}]}
|
examples/t2v_bubbles.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"comprehensive_description":{"scene_content_description":"A young child with short brown hair is outdoors on a bright, sunny day, playing with bubbles. The child is wearing a colorful striped shirt with horizontal bands of red, yellow, green, and blue. They are holding a small bottle of bubble solution and a wand, dipping the wand into the bottle and then blowing to create a stream of shimmering, iridescent bubbles. The background is a soft-focus outdoor setting with greenery and a hint of a building, creating a warm and joyful atmosphere. The lighting is bright and natural, highlighting the child's focused expression and the delicate, translucent nature of the bubbles.","camera_movement_description":"The camera is essentially stationary throughout the video, maintaining a medium close-up shot of the child from an eye-level angle. There is a very slight handheld tremor, but no intentional panning, tilting, or zooming occurs."},"camera_info":{"color":"Saturated","frame_size":"Medium Close Up","shot_type_angle":"Low angle","lens_size":"Long Lens","composition":"Center","lighting":"Hard light","lighting_type":"Daylight"},"world_knowledge":[],"prominent_elements":[{"name":"young child","description":"A young child with short brown hair and a joyful expression, focused on playing with bubbles.","actions":[{"timestamp":"[0.0s - 1.5s]","action":"Dips the bubble wand into the solution bottle and lifts it out."},{"timestamp":"[1.5s - 2.5s]","action":"Brings the wand to their lips and blows to create bubbles."},{"timestamp":"[2.5s - 5.0s]","action":"Watches the bubbles float away, smiling slightly."}],"location":"Center of the frame","relative_size":"dominant","shape_and_color":"Human form; wearing a multi-colored striped shirt (red, yellow, green, blue).","texture":"Soft skin, fine hair, fabric texture of the shirt.","appearance_details":"Short brown hair, bright eyes, colorful horizontal stripes on the shirt.","relationship":"Holding the bubble wand and bottle, interacting with the bubbles.","orientation":"Facing forward and slightly to the right.","pose":"Standing or sitting upright, arms raised to hold the bubble wand.","expression":"Focused and happy.","clothing":"A short-sleeved shirt with horizontal stripes in red, yellow, green, and blue.","gender":"Male","skin_tone_and_texture":"Fair skin with a smooth texture."},{"name":"bubble wand and bottle","description":"A small plastic bottle containing bubble solution and a wand with a circular loop.","actions":[{"timestamp":"[0.0s - 1.5s]","action":"The wand is dipped into the bottle and then lifted out."},{"timestamp":"[1.5s - 2.5s]","action":"The wand is held at the child's mouth as bubbles are blown."}],"location":"Lower center of the frame, held by the child.","relative_size":"small","shape_and_color":"Cylindrical bottle with a blue cap; circular wand loop.","texture":"Smooth plastic.","appearance_details":"The bottle is partially filled with clear liquid; the wand has a thin handle.","relationship":"Held by the child's hands.","orientation":"Vertical bottle, horizontal wand loop.","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"bubbles","description":"A cluster of small, iridescent, translucent bubbles floating in the air.","actions":[{"timestamp":"[1.5s - 5.0s]","action":"The bubbles are blown from the wand and float upwards and towards the right side of the frame."}],"location":"Upper center and right side of the frame.","relative_size":"medium","shape_and_color":"Spherical and iridescent with rainbow-like reflections.","texture":"Glossy and translucent.","appearance_details":"Shimmering surfaces that catch the sunlight.","relationship":"Created by the child's blowing action.","orientation":"Floating in various directions.","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":"","is_cluster":true,"number_of_objects":"many"}]}
|
examples/t2v_robot_desk.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"comprehensive_description":{"scene_content_description":"The video presents a first-person perspective of a workspace, likely a desk, viewed from a fixed, slightly elevated angle. The environment is well-lit with neutral, even lighting, creating a clear and focused atmosphere. The desk surface is a light grey color. On the left side of the desk, there is a black game controller with a glowing blue light bar, resting on a black mousepad. In the center of the desk lies a pair of black over-ear headphones. To the right, there is an open, empty black box with a white interior. In the foreground, two robotic arms are visible. The left robotic arm, featuring a black and silver body with a two-fingered gripper, remains completely stationary throughout the video. The right robotic arm, similar in design, is the active subject. It begins by moving forward and to the left, positioning its gripper over the game controller. It then grasps the controller, lifts it off the desk, and transports it to the right, moving it over the open box. Finally, the right robotic arm lowers the controller into the box and releases it. The headphones and the left robotic arm remain undisturbed during this entire sequence.","camera_movement_description":""},"camera_info":{"color":"Cyan","frame_size":"Wide","shot_type_angle":"High angle","lens_size":"Ultra Wide / Fisheye","composition":"Balanced","lighting":"Soft light","lighting_type":"Artificial light"},"world_knowledge":[],"prominent_elements":[{"name":"right robotic arm","description":"A mechanical arm with a black and silver body, equipped with a two-fingered gripper and visible wiring.","actions":[{"timestamp":"[0.0s - 1.5s]","action":"Moves forward and to the left towards the game controller."},{"timestamp":"[1.5s - 2.5s]","action":"Grasps the game controller and lifts it upward."},{"timestamp":"[2.5s - 4.0s]","action":"Moves to the right, carrying the game controller over the open box."},{"timestamp":"[4.0s - 5.0s]","action":"Lowers the game controller into the box and releases it."}],"location":"Originates from the bottom right, moves to the center, then to the right.","relative_size":"large","shape_and_color":"Cylindrical and angular segments, black and silver.","texture":"Metallic and matte plastic.","appearance_details":"Visible joints, wiring, and a two-fingered gripper mechanism.","relationship":"Interacts directly with the game controller.","orientation":"Extends forward and slightly upward from the bottom right.","pose":"","expression":"","clothing":"","is_cluster":false,"number_of_objects":""},{"name":"left robotic arm","description":"A mechanical arm with a black and silver body, equipped with a two-fingered gripper.","actions":[{"timestamp":"[0.0s - 5.0s]","action":"Remains stationary."}],"location":"Bottom left corner of the frame.","relative_size":"large","shape_and_color":"Cylindrical and angular segments, black and silver.","texture":"Metallic and matte plastic.","appearance_details":"Visible joints and a two-fingered gripper mechanism.","relationship":"Positioned opposite the right robotic arm, not interacting with other objects.","orientation":"Extends forward and slightly upward from the bottom left.","pose":"","expression":"","clothing":"","is_cluster":false,"number_of_objects":""},{"name":"game controller","description":"A standard video game controller with joysticks, buttons, and a glowing light bar.","actions":[{"timestamp":"[0.0s - 1.5s]","action":"Rests stationary on the desk."},{"timestamp":"[1.5s - 2.5s]","action":"Is grasped and lifted upward by the right robotic arm."},{"timestamp":"[2.5s - 4.0s]","action":"Is moved to the right by the right robotic arm."},{"timestamp":"[4.0s - 5.0s]","action":"Is lowered into the open box and released."}],"location":"Initially on the left side of the desk, moved to the right side inside the box.","relative_size":"medium","shape_and_color":"Contoured shape, black with a blue light bar.","texture":"Matte plastic.","appearance_details":"Two joysticks, directional pad, action buttons, and a glowing blue light bar in the center.","relationship":"Initially on the desk, then grasped and moved by the right robotic arm, finally placed in the open box.","orientation":"Horizontal on the desk, then tilted while being carried.","pose":"","expression":"","clothing":"","is_cluster":false,"number_of_objects":""},{"name":"headphones","description":"A pair of over-ear headphones with a headband and ear cups.","actions":[{"timestamp":"[0.0s - 5.0s]","action":"Remains stationary on the desk."}],"location":"Center of the desk.","relative_size":"medium","shape_and_color":"Curved headband with circular ear cups, black.","texture":"Matte plastic and soft ear cushions.","appearance_details":"Visible ear cushions and a headband.","relationship":"Rests on the desk, untouched by the robotic arms.","orientation":"Lying flat on the desk.","pose":"","expression":"","clothing":"","is_cluster":false,"number_of_objects":""},{"name":"open box","description":"A rectangular box with the lid open, revealing a white interior.","actions":[{"timestamp":"[0.0s - 5.0s]","action":"Remains stationary on the desk."}],"location":"Right side of the desk.","relative_size":"medium","shape_and_color":"Rectangular, black exterior with a white interior.","texture":"Smooth cardboard or plastic.","appearance_details":"Open lid, empty interior.","relationship":"Serves as the receptacle for the game controller.","orientation":"Horizontal on the desk.","pose":"","expression":"","clothing":"","is_cluster":false,"number_of_objects":""}]}
|
examples/ti2v_1.png
ADDED
|
Git LFS Details
|
examples/ti2v_1.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"comprehensive_description":{"scene_content_description":"A high-end, cinematic still-life advertisement featuring a thick-bottomed, transparent whiskey glass resting on a dark wooden table. The glass contains amber-colored liquid, and a single transparent ice cube is suspended in mid-air directly above the rim, poised to fall. The background is dark and out of focus, creating a shallow depth of field that isolates the subject. The atmosphere is sophisticated, moody, and luxurious, emphasizing the clarity of the glass and the rich color of the whiskey.","camera_movement_description":"The camera remains in a stable close-up perspective throughout the duration. There is no significant panning, tilting, or zooming. Instead, the camera exhibits a very subtle, smooth, slow-motion breathing effect, maintaining a steady focus on the glass and the falling ice."},"camera_info":{"color":"Warm","frame_size":"Close Up","shot_type_angle":"Eye level","lens_size":"Macro","composition":"Center","lighting":"Soft light","lighting_type":"Studio lighting"},"world_knowledge":[],"prominent_elements":[{"name":"transparent ice cube","description":"A single, clear, roughly cubic block of ice","actions":[{"timestamp":"[0.0s - 0.8s]","action":"falls from above the rim, rotating slightly as it descends"},{"timestamp":"[0.8s - 1.6s]","action":"strikes the surface of the liquid, creating a circular depression and initial splash"},{"timestamp":"[1.6s - 2.8s]","action":"partially submerges, generating splashes and bubbles"},{"timestamp":"[2.8s - 4.0s]","action":"bobs slightly up and down and rotates within the liquid"},{"timestamp":"[4.0s - 5.0s]","action":"floats gently in the liquid, slowly coming to a stop"}],"location":"initially in the upper center of the frame, then moves into the center of the glass","relative_size":"medium","shape_and_color":"geometric cube, transparent with internal refractions","texture":"crystalline and solid","appearance_details":"visible internal air bubbles, cracks, and sharp edges that catch the light","relationship":"the dynamic object interacting with the liquid","orientation":"slightly tilted","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"whiskey glass","description":"A heavy, short tumbler with a thick, textured base","actions":[{"timestamp":"[0.0s - 5.0s]","action":""}],"location":"center of the frame, resting on the table","relative_size":"large","shape_and_color":"cylindrical glass, transparent","texture":"smooth and reflective","appearance_details":"thick, grid-patterned base and clear walls that refract the amber liquid inside","relationship":"the container for the liquid and ice","orientation":"upright","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"amber liquid","description":"A rich, golden-brown fluid filling the lower half of the glass","actions":[{"timestamp":"[0.0s - 0.8s]","action":"remains relatively still with a flat surface"},{"timestamp":"[0.8s - 1.6s]","action":"surface is pushed down by the ice, forming a circular wave"},{"timestamp":"[1.6s - 2.8s]","action":"creates splashes and fine droplets as the ice enters"},{"timestamp":"[2.8s - 4.0s]","action":"exhibits ripples that bounce off the glass walls and cross-ripples"},{"timestamp":"[4.0s - 5.0s]","action":"surface gradually calms down, becoming smooth again"}],"location":"inside the glass","relative_size":"large","shape_and_color":"fluid volume, deep amber","texture":"viscous and glossy","appearance_details":"surface tension and the way light passes through it","relationship":"contained within the glass","orientation":"horizontal","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"dark wooden table","description":"A polished, dark wood surface","actions":[{"timestamp":"[0.0s - 5.0s]","action":""}],"location":"foreground and bottom of the frame","relative_size":"large","shape_and_color":"flat surface, dark brown/black","texture":"smooth with a slight sheen","appearance_details":"wood grain and reflections of the glass and light","relationship":"the supporting surface for the glass","orientation":"horizontal","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":""}]}
|
examples/ti2v_5.png
ADDED
|
Git LFS Details
|
examples/ti2v_5.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"comprehensive_description":{"scene_content_description":"A sleek, vibrant red sports car is captured in a high-speed motion sequence. The car begins by emerging from a dark, shadowed cave entrance, its glossy surface reflecting the intense light of the desert environment. As it accelerates, it kicks up a significant amount of dust and gravel from the sandy ground. The car then bursts into the bright, sun-drenched exterior, where the background reveals a vast, arid landscape with distant mountains under a clear sky. The lighting is dramatic, with high-contrast shadows inside the cave and brilliant, almost blinding sunlight outside, creating a sense of power and speed.","camera_movement_description":"The camera performs a dynamic tracking shot, moving parallel to the car's side. It starts with a close-up side profile and gradually pulls back and pans to the right, following the car's trajectory. As the car exits the cave, the camera angle shifts slightly upward and pulls back further to capture the car's front and side as it speeds into the open desert. The movement is smooth and fluid, maintaining a consistent distance from the subject."},"camera_info":{"color":"Warm","frame_size":"Wide","shot_type_angle":"Low angle","lens_size":"Wide","composition":"Balanced","lighting":"Hard light","lighting_type":"Daylight"},"world_knowledge":[],"prominent_elements":[{"name":"red sports car","description":"A modern, high-performance coupe with a glossy red finish and black accents","actions":[{"timestamp":"[0.0s - 4.0s]","action":"moves right while emerging from a dark cave"},{"timestamp":"[4.0s - 6.0s]","action":"accelerates and exits the cave into bright sunlight"},{"timestamp":"[6.0s - 8.0s]","action":"speeds right across the desert landscape"},{"timestamp":"[8.0s - 10.0s]","action":"continues moving right with the camera tracking its side"}],"location":"center of the frame, moving from left to right","relative_size":"dominant","shape_and_color":"aerodynamic coupe shape, vibrant red with black wheels and trim","texture":"glossy and metallic","appearance_details":"features a low-slung profile, large black multi-spoke wheels, and a visible driver inside","relationship":"the main subject of the video, interacting with the cave and desert environment","orientation":"facing right","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"cave entrance","description":"A dark, rocky opening in a cliff face","actions":[{"timestamp":"[0.0s - 4.0s]","action":""}],"location":"left side of the frame","relative_size":"large","shape_and_color":"irregular rocky shape, dark brown and black","texture":"rough and rocky","appearance_details":"deep shadows inside the opening, contrasting with the bright exterior","relationship":"the starting point for the car's movement","orientation":"upright","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":""},{"name":"desert landscape","description":"A vast, arid environment with sandy ground and distant mountains","actions":[{"timestamp":"[0.0s - 10.0s]","action":""}],"location":"background and foreground","relative_size":"large","shape_and_color":"sandy beige ground, light blue sky, and brown mountains","texture":"sandy and dusty","appearance_details":"bright sunlight creates a hazy, high-contrast atmosphere","relationship":"the setting for the car's high-speed run","orientation":"horizontal","pose":"","expression":"","clothing":"","gender":"","skin_tone_and_texture":""}]}
|
lingbot_video/__init__.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import importlib
|
| 4 |
+
from typing import Any
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
_EXPORTS = {
|
| 8 |
+
"FlowUniPCMultistepScheduler": (
|
| 9 |
+
"lingbot_video.scheduling_flow_unipc",
|
| 10 |
+
"FlowUniPCMultistepScheduler",
|
| 11 |
+
),
|
| 12 |
+
"LingBotVideoImageToVideoPipeline": (
|
| 13 |
+
"lingbot_video.pipeline_lingbot_video_i2v",
|
| 14 |
+
"LingBotVideoImageToVideoPipeline",
|
| 15 |
+
),
|
| 16 |
+
"LingBotVideoPipeline": (
|
| 17 |
+
"lingbot_video.pipeline_lingbot_video",
|
| 18 |
+
"LingBotVideoPipeline",
|
| 19 |
+
),
|
| 20 |
+
"LingBotVideoTransformer3DModel": (
|
| 21 |
+
"lingbot_video.transformer_lingbot_video",
|
| 22 |
+
"LingBotVideoTransformer3DModel",
|
| 23 |
+
),
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
__all__ = sorted(_EXPORTS)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def __getattr__(name: str) -> Any:
|
| 30 |
+
try:
|
| 31 |
+
module_name, attr_name = _EXPORTS[name]
|
| 32 |
+
except KeyError as exc:
|
| 33 |
+
raise AttributeError(name) from exc
|
| 34 |
+
value = getattr(importlib.import_module(module_name), attr_name)
|
| 35 |
+
globals()[name] = value
|
| 36 |
+
return value
|
lingbot_video/default_negative_prompt.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"universal_negative": {
|
| 3 |
+
"visual_quality": [
|
| 4 |
+
"low quality",
|
| 5 |
+
"worst quality",
|
| 6 |
+
"blurry",
|
| 7 |
+
"pixelated",
|
| 8 |
+
"jpeg artifacts",
|
| 9 |
+
"low resolution",
|
| 10 |
+
"unstable color",
|
| 11 |
+
"color flicker",
|
| 12 |
+
"underexposed",
|
| 13 |
+
"overexposed",
|
| 14 |
+
"invisible subject",
|
| 15 |
+
"subject hidden in darkness"
|
| 16 |
+
],
|
| 17 |
+
"artistic_style": [
|
| 18 |
+
"painting",
|
| 19 |
+
"illustration",
|
| 20 |
+
"drawing",
|
| 21 |
+
"cartoon",
|
| 22 |
+
"3d render",
|
| 23 |
+
"cgi",
|
| 24 |
+
"sketch",
|
| 25 |
+
"digital art"
|
| 26 |
+
],
|
| 27 |
+
"composition_and_content": [
|
| 28 |
+
"text",
|
| 29 |
+
"watermark",
|
| 30 |
+
"signature",
|
| 31 |
+
"logo",
|
| 32 |
+
"subtitles",
|
| 33 |
+
"pillarboxed",
|
| 34 |
+
"side bars",
|
| 35 |
+
"portrait image in landscape frame"
|
| 36 |
+
],
|
| 37 |
+
"temporal_and_motion_stability": [
|
| 38 |
+
"flickering",
|
| 39 |
+
"jittery",
|
| 40 |
+
"motion blur",
|
| 41 |
+
"temporal inconsistency",
|
| 42 |
+
"warping",
|
| 43 |
+
"morphing",
|
| 44 |
+
"incoherent motion",
|
| 45 |
+
"unnatural movement",
|
| 46 |
+
"static object with sudden jump",
|
| 47 |
+
"frame-to-frame inconsistency"
|
| 48 |
+
],
|
| 49 |
+
"material_and_structure": [
|
| 50 |
+
"plastic-like glass",
|
| 51 |
+
"unrealistic texture",
|
| 52 |
+
"deformed bottle",
|
| 53 |
+
"liquid freezing improperly",
|
| 54 |
+
"distorted reflections"
|
| 55 |
+
]
|
| 56 |
+
}
|
| 57 |
+
}
|
lingbot_video/default_negative_prompt_image.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"universal_negative": {
|
| 3 |
+
"visual_quality": [
|
| 4 |
+
"low quality",
|
| 5 |
+
"worst quality",
|
| 6 |
+
"blurry",
|
| 7 |
+
"pixelated",
|
| 8 |
+
"jpeg artifacts",
|
| 9 |
+
"low resolution",
|
| 10 |
+
"underexposed",
|
| 11 |
+
"overexposed",
|
| 12 |
+
"invisible subject",
|
| 13 |
+
"subject hidden in darkness"
|
| 14 |
+
],
|
| 15 |
+
"artistic_style": [
|
| 16 |
+
"painting",
|
| 17 |
+
"illustration",
|
| 18 |
+
"drawing",
|
| 19 |
+
"cartoon",
|
| 20 |
+
"3d render",
|
| 21 |
+
"cgi",
|
| 22 |
+
"sketch",
|
| 23 |
+
"digital art"
|
| 24 |
+
],
|
| 25 |
+
"composition_and_content": [
|
| 26 |
+
"text",
|
| 27 |
+
"watermark",
|
| 28 |
+
"signature",
|
| 29 |
+
"logo",
|
| 30 |
+
"pillarboxed",
|
| 31 |
+
"side bars",
|
| 32 |
+
"portrait image in landscape frame"
|
| 33 |
+
],
|
| 34 |
+
"material_and_structure": [
|
| 35 |
+
"plastic-like glass",
|
| 36 |
+
"unrealistic texture",
|
| 37 |
+
"deformed bottle",
|
| 38 |
+
"distorted reflections"
|
| 39 |
+
]
|
| 40 |
+
}
|
| 41 |
+
}
|
lingbot_video/moe_pack_kernels.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
try:
|
| 6 |
+
import triton
|
| 7 |
+
import triton.language as tl
|
| 8 |
+
except Exception: # pragma: no cover - Triton is optional outside GPU deployments.
|
| 9 |
+
triton = None
|
| 10 |
+
tl = None
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
if triton is not None and tl is not None:
|
| 14 |
+
@triton.jit
|
| 15 |
+
def _moe_route_count_slots_kernel(
|
| 16 |
+
flat_indices,
|
| 17 |
+
counts,
|
| 18 |
+
route_slots,
|
| 19 |
+
num_routes: tl.constexpr,
|
| 20 |
+
block_m: tl.constexpr,
|
| 21 |
+
):
|
| 22 |
+
offsets = tl.program_id(0) * block_m + tl.arange(0, block_m)
|
| 23 |
+
mask = offsets < num_routes
|
| 24 |
+
experts = tl.load(flat_indices + offsets, mask=mask, other=0)
|
| 25 |
+
slots = tl.atomic_add(counts + experts, 1, sem="relaxed", mask=mask)
|
| 26 |
+
tl.store(route_slots + offsets, slots, mask=mask)
|
| 27 |
+
|
| 28 |
+
@triton.jit
|
| 29 |
+
def _moe_pack_tokens_kernel(
|
| 30 |
+
tokens,
|
| 31 |
+
flat_scores,
|
| 32 |
+
flat_indices,
|
| 33 |
+
offsets,
|
| 34 |
+
route_slots,
|
| 35 |
+
permuted_tokens,
|
| 36 |
+
sorted_positions,
|
| 37 |
+
sorted_scores,
|
| 38 |
+
hidden_size: tl.constexpr,
|
| 39 |
+
top_k: tl.constexpr,
|
| 40 |
+
block_h: tl.constexpr,
|
| 41 |
+
):
|
| 42 |
+
route_idx = tl.program_id(0)
|
| 43 |
+
hidden_block = tl.program_id(1)
|
| 44 |
+
offsets_h = hidden_block * block_h + tl.arange(0, block_h)
|
| 45 |
+
hidden_mask = offsets_h < hidden_size
|
| 46 |
+
|
| 47 |
+
expert_idx = tl.load(flat_indices + route_idx)
|
| 48 |
+
slot = tl.load(route_slots + route_idx)
|
| 49 |
+
expert_offset = tl.load(offsets + expert_idx)
|
| 50 |
+
dest_idx = expert_offset + slot
|
| 51 |
+
token_idx = route_idx // top_k
|
| 52 |
+
|
| 53 |
+
values = tl.load(
|
| 54 |
+
tokens + token_idx * hidden_size + offsets_h,
|
| 55 |
+
mask=hidden_mask,
|
| 56 |
+
other=0.0,
|
| 57 |
+
)
|
| 58 |
+
tl.store(
|
| 59 |
+
permuted_tokens + dest_idx * hidden_size + offsets_h,
|
| 60 |
+
values,
|
| 61 |
+
mask=hidden_mask,
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
first_hidden_block = hidden_block == 0
|
| 65 |
+
tl.store(sorted_positions + dest_idx, route_idx, mask=first_hidden_block)
|
| 66 |
+
score = tl.load(flat_scores + route_idx, mask=first_hidden_block, other=0.0)
|
| 67 |
+
tl.store(sorted_scores + dest_idx, score, mask=first_hidden_block)
|
| 68 |
+
else:
|
| 69 |
+
_moe_route_count_slots_kernel = None
|
| 70 |
+
_moe_pack_tokens_kernel = None
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def reorder_tokens_triton_pack(
|
| 74 |
+
tokens: torch.Tensor,
|
| 75 |
+
top_scores: torch.Tensor,
|
| 76 |
+
top_indices: torch.Tensor,
|
| 77 |
+
num_experts: int,
|
| 78 |
+
block_h: int = 64,
|
| 79 |
+
block_m: int = 256,
|
| 80 |
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, int, int]:
|
| 81 |
+
if _moe_route_count_slots_kernel is None or _moe_pack_tokens_kernel is None:
|
| 82 |
+
raise RuntimeError("LINGBOT_MOE_REORDER_BACKEND=triton_pack requires Triton")
|
| 83 |
+
if tokens.device.type != "cuda":
|
| 84 |
+
raise RuntimeError("LINGBOT_MOE_REORDER_BACKEND=triton_pack requires CUDA tensors")
|
| 85 |
+
if tokens.ndim != 2:
|
| 86 |
+
raise ValueError(f"Expected 2D tokens, got {tokens.ndim}D")
|
| 87 |
+
if top_scores.shape != top_indices.shape:
|
| 88 |
+
raise ValueError("top_scores and top_indices must have the same shape")
|
| 89 |
+
if top_indices.ndim != 2:
|
| 90 |
+
raise ValueError(f"Expected 2D top_indices, got {top_indices.ndim}D")
|
| 91 |
+
|
| 92 |
+
num_tokens = tokens.shape[0]
|
| 93 |
+
hidden_size = tokens.shape[1]
|
| 94 |
+
top_k = top_indices.shape[1]
|
| 95 |
+
num_routes = top_indices.numel()
|
| 96 |
+
counts = torch.zeros(num_experts, dtype=torch.int32, device=tokens.device)
|
| 97 |
+
|
| 98 |
+
if num_routes == 0:
|
| 99 |
+
return (
|
| 100 |
+
tokens.new_empty((0, hidden_size)),
|
| 101 |
+
counts,
|
| 102 |
+
torch.empty(0, dtype=torch.int64, device=tokens.device),
|
| 103 |
+
top_scores.new_empty((0,)),
|
| 104 |
+
num_tokens,
|
| 105 |
+
top_k,
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
tokens = tokens.contiguous()
|
| 109 |
+
flat_scores = top_scores.contiguous().reshape(-1)
|
| 110 |
+
flat_indices = top_indices.contiguous().to(torch.int32).reshape(-1)
|
| 111 |
+
route_slots = torch.empty(num_routes, dtype=torch.int32, device=tokens.device)
|
| 112 |
+
|
| 113 |
+
_moe_route_count_slots_kernel[(triton.cdiv(num_routes, block_m),)](
|
| 114 |
+
flat_indices,
|
| 115 |
+
counts,
|
| 116 |
+
route_slots,
|
| 117 |
+
num_routes,
|
| 118 |
+
block_m,
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
counts_i64 = counts.to(torch.int64)
|
| 122 |
+
offsets = torch.empty(num_experts + 1, dtype=torch.int64, device=tokens.device)
|
| 123 |
+
offsets[0] = 0
|
| 124 |
+
offsets[1:] = torch.cumsum(counts_i64, dim=0)
|
| 125 |
+
|
| 126 |
+
permuted_tokens = torch.empty((num_routes, hidden_size), dtype=tokens.dtype, device=tokens.device)
|
| 127 |
+
sorted_positions = torch.empty(num_routes, dtype=torch.int64, device=tokens.device)
|
| 128 |
+
sorted_scores = torch.empty(num_routes, dtype=top_scores.dtype, device=tokens.device)
|
| 129 |
+
_moe_pack_tokens_kernel[(num_routes, triton.cdiv(hidden_size, block_h))](
|
| 130 |
+
tokens,
|
| 131 |
+
flat_scores,
|
| 132 |
+
flat_indices,
|
| 133 |
+
offsets,
|
| 134 |
+
route_slots,
|
| 135 |
+
permuted_tokens,
|
| 136 |
+
sorted_positions,
|
| 137 |
+
sorted_scores,
|
| 138 |
+
hidden_size,
|
| 139 |
+
top_k,
|
| 140 |
+
block_h,
|
| 141 |
+
)
|
| 142 |
+
return permuted_tokens, counts, sorted_positions, sorted_scores, num_tokens, top_k
|
lingbot_video/moe_restore_kernels.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
try:
|
| 6 |
+
import triton
|
| 7 |
+
import triton.language as tl
|
| 8 |
+
except Exception: # pragma: no cover - Triton is optional outside GPU deployments.
|
| 9 |
+
triton = None
|
| 10 |
+
tl = None
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
if triton is not None and tl is not None:
|
| 14 |
+
@triton.jit
|
| 15 |
+
def _moe_restore_weighted_sum_kernel(
|
| 16 |
+
expert_output,
|
| 17 |
+
sorted_scores,
|
| 18 |
+
route_for_position,
|
| 19 |
+
output,
|
| 20 |
+
hidden_size: tl.constexpr,
|
| 21 |
+
top_k: tl.constexpr,
|
| 22 |
+
block_h: tl.constexpr,
|
| 23 |
+
):
|
| 24 |
+
token_idx = tl.program_id(0)
|
| 25 |
+
hidden_block = tl.program_id(1)
|
| 26 |
+
offsets_h = hidden_block * block_h + tl.arange(0, block_h)
|
| 27 |
+
hidden_mask = offsets_h < hidden_size
|
| 28 |
+
acc = tl.zeros((block_h,), dtype=tl.float32)
|
| 29 |
+
|
| 30 |
+
for route_slot in tl.range(0, top_k):
|
| 31 |
+
route_pos = token_idx * top_k + route_slot
|
| 32 |
+
route_idx = tl.load(route_for_position + route_pos)
|
| 33 |
+
active = route_idx >= 0
|
| 34 |
+
safe_route_idx = tl.maximum(route_idx, 0)
|
| 35 |
+
values = tl.load(
|
| 36 |
+
expert_output + safe_route_idx * hidden_size + offsets_h,
|
| 37 |
+
mask=active & hidden_mask,
|
| 38 |
+
other=0.0,
|
| 39 |
+
).to(tl.float32)
|
| 40 |
+
score = tl.load(sorted_scores + safe_route_idx, mask=active, other=0.0).to(tl.float32)
|
| 41 |
+
acc += values * score
|
| 42 |
+
|
| 43 |
+
tl.store(output + token_idx * hidden_size + offsets_h, acc, mask=hidden_mask)
|
| 44 |
+
else:
|
| 45 |
+
_moe_restore_weighted_sum_kernel = None
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def restore_tokens_triton(
|
| 49 |
+
expert_output: torch.Tensor,
|
| 50 |
+
sorted_positions: torch.Tensor,
|
| 51 |
+
sorted_scores: torch.Tensor,
|
| 52 |
+
num_tokens: int,
|
| 53 |
+
top_k: int,
|
| 54 |
+
block_h: int = 64,
|
| 55 |
+
) -> torch.Tensor:
|
| 56 |
+
if _moe_restore_weighted_sum_kernel is None:
|
| 57 |
+
raise RuntimeError("LINGBOT_MOE_RESTORE_BACKEND=triton requires Triton")
|
| 58 |
+
if expert_output.ndim != 2:
|
| 59 |
+
raise ValueError(f"Expected 2D expert_output, got {expert_output.ndim}D")
|
| 60 |
+
if sorted_positions.numel() != sorted_scores.numel():
|
| 61 |
+
raise ValueError("sorted_positions and sorted_scores must have the same length")
|
| 62 |
+
if sorted_positions.numel() == 0:
|
| 63 |
+
return expert_output.new_zeros((num_tokens, expert_output.shape[-1]))
|
| 64 |
+
|
| 65 |
+
hidden_size = expert_output.shape[-1]
|
| 66 |
+
route_for_position = torch.full(
|
| 67 |
+
(num_tokens * top_k,),
|
| 68 |
+
-1,
|
| 69 |
+
dtype=torch.int32,
|
| 70 |
+
device=expert_output.device,
|
| 71 |
+
)
|
| 72 |
+
route_for_position[sorted_positions] = torch.arange(
|
| 73 |
+
sorted_positions.numel(),
|
| 74 |
+
dtype=torch.int32,
|
| 75 |
+
device=expert_output.device,
|
| 76 |
+
)
|
| 77 |
+
output = torch.empty(
|
| 78 |
+
(num_tokens, hidden_size),
|
| 79 |
+
dtype=expert_output.dtype,
|
| 80 |
+
device=expert_output.device,
|
| 81 |
+
)
|
| 82 |
+
grid = (num_tokens, triton.cdiv(hidden_size, block_h))
|
| 83 |
+
_moe_restore_weighted_sum_kernel[grid](
|
| 84 |
+
expert_output.contiguous(),
|
| 85 |
+
sorted_scores.contiguous(),
|
| 86 |
+
route_for_position,
|
| 87 |
+
output,
|
| 88 |
+
hidden_size,
|
| 89 |
+
top_k,
|
| 90 |
+
block_h,
|
| 91 |
+
)
|
| 92 |
+
return output
|
lingbot_video/pipeline_lingbot_video.py
ADDED
|
@@ -0,0 +1,591 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from contextlib import nullcontext
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
import torch
|
| 9 |
+
import torch.distributed as dist
|
| 10 |
+
from torchvision.transforms.functional import normalize as normalize_image_tensor
|
| 11 |
+
|
| 12 |
+
from diffusers import DiffusionPipeline
|
| 13 |
+
from diffusers.utils import BaseOutput
|
| 14 |
+
from diffusers.utils.torch_utils import randn_tensor
|
| 15 |
+
|
| 16 |
+
from .utils import (
|
| 17 |
+
LOW_NOISE_TAIL_V1_DEFAULT_STEPS,
|
| 18 |
+
batch_cfg_prompt_inputs,
|
| 19 |
+
compute_refiner_sigmas,
|
| 20 |
+
)
|
| 21 |
+
from .scheduling_flow_unipc import FlowUniPCMultistepScheduler
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
TOKEN_LENGTH = 37698
|
| 25 |
+
HIDDEN_STATE_SKIP_LAYER = 0
|
| 26 |
+
|
| 27 |
+
PROMPT_TEMPLATE = (
|
| 28 |
+
"<|im_start|>system\nGiven a user input that may include a text prompt alone, "
|
| 29 |
+
"a text prompt with an image reference, or a text prompt with a video reference "
|
| 30 |
+
"or a video reference alone, generate an \"Enhanced prompt\" that provides detailed "
|
| 31 |
+
"visual descriptions suitable for video generation. Evaluate the level of detail "
|
| 32 |
+
"in the user's input: if it is simple, enrich it by adding specifics about colors, "
|
| 33 |
+
"shapes, sizes, textures, lighting, motion dynamics, camera movement, temporal "
|
| 34 |
+
"progression, and spatial relationships to create vivid, concrete, and temporally "
|
| 35 |
+
"coherent scenes to create vivid and concrete scenes. Please generate only the "
|
| 36 |
+
"enhanced description for the prompt below and avoid including any additional "
|
| 37 |
+
"commentary or evaluations:<|im_end|>\n<|im_start|>user\n{}<|im_end|>\n"
|
| 38 |
+
"<|im_start|>assistant\n"
|
| 39 |
+
)
|
| 40 |
+
IMG_PROMPT_TEMPLATE = "<|vision_start|><|image_pad|><|vision_end|>"
|
| 41 |
+
VIDEO_PROMPT_TEMPLATE = "<|vision_start|><|video_pad|><|vision_end|>"
|
| 42 |
+
|
| 43 |
+
DEFAULT_NEGATIVE_PROMPT = (
|
| 44 |
+
'{"universal_negative": {"visual_quality": ["low quality", "worst quality", "blurry", "pixelated", "jpeg artifacts", "low resolution", "unstable color", "color flicker", "underexposed", "overexposed", "invisible subject", "subject hidden in darkness"], "artistic_style": ["painting", "illustration", "drawing", "cartoon", "3d render", "cgi", "sketch", "digital art"], "composition_and_content": ["text", "watermark", "signature", "logo", "subtitles", "pillarboxed", "side bars", "portrait image in landscape frame"], "temporal_and_motion_stability": ["flickering", "jittery", "motion blur", "temporal inconsistency", "warping", "morphing", "incoherent motion", "unnatural movement", "static object with sudden jump", "frame-to-frame inconsistency"], "material_and_structure": ["plastic-like glass", "unrealistic texture", "deformed bottle", "liquid freezing improperly", "distorted reflections"]}}'
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
# Still-image default (t2i): drops the whole temporal/motion block and the video-only
|
| 48 |
+
# codec/temporal terms that cannot apply to a single frame.
|
| 49 |
+
DEFAULT_NEGATIVE_PROMPT_IMAGE = (
|
| 50 |
+
'{"universal_negative": {"visual_quality": ["low quality", "worst quality", "blurry", "pixelated", "jpeg artifacts", "low resolution", "underexposed", "overexposed", "invisible subject", "subject hidden in darkness"], "artistic_style": ["painting", "illustration", "drawing", "cartoon", "3d render", "cgi", "sketch", "digital art"], "composition_and_content": ["text", "watermark", "signature", "logo", "pillarboxed", "side bars", "portrait image in landscape frame"], "material_and_structure": ["plastic-like glass", "unrealistic texture", "deformed bottle", "distorted reflections"]}}'
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
@dataclass
|
| 55 |
+
class LingBotVideoPipelineOutput(BaseOutput):
|
| 56 |
+
frames: Union[List[np.ndarray], torch.Tensor]
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def _module_dtype(module: torch.nn.Module) -> torch.dtype:
|
| 60 |
+
try:
|
| 61 |
+
return next(module.parameters()).dtype
|
| 62 |
+
except StopIteration:
|
| 63 |
+
return torch.float32
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def _transformer_timestep(timestep: torch.Tensor, transformer_dtype: torch.dtype) -> torch.Tensor:
|
| 67 |
+
sigma = timestep.float() / 1000.0
|
| 68 |
+
if transformer_dtype in {torch.bfloat16, torch.float16}:
|
| 69 |
+
sigma = sigma.to(transformer_dtype)
|
| 70 |
+
return (sigma * 1000.0).float()
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def _transformer_autocast(device: torch.device, transformer_dtype: torch.dtype):
|
| 74 |
+
if device.type != "cuda" or transformer_dtype not in {torch.bfloat16, torch.float16}:
|
| 75 |
+
return nullcontext()
|
| 76 |
+
return torch.autocast(device_type="cuda", dtype=transformer_dtype)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def _module_device(module: torch.nn.Module) -> torch.device:
|
| 80 |
+
try:
|
| 81 |
+
return next(module.parameters()).device
|
| 82 |
+
except StopIteration:
|
| 83 |
+
return torch.device("cpu")
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def _group_global_rank(group: Optional[Any], group_rank: int) -> int:
|
| 87 |
+
if group is None:
|
| 88 |
+
return group_rank
|
| 89 |
+
get_global_rank = getattr(dist, "get_global_rank", None)
|
| 90 |
+
if get_global_rank is None:
|
| 91 |
+
return group_rank
|
| 92 |
+
return int(get_global_rank(group, group_rank))
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
class LingBotVideoPipeline(DiffusionPipeline):
|
| 96 |
+
"""Minimal LingBotVideo t2v/t2i pipeline.
|
| 97 |
+
|
| 98 |
+
Standard CFG runs as two independent transformer forwards unless batched CFG
|
| 99 |
+
or CFG parallelism is explicitly requested.
|
| 100 |
+
"""
|
| 101 |
+
|
| 102 |
+
model_cpu_offload_seq = "text_encoder->transformer->vae"
|
| 103 |
+
|
| 104 |
+
def __init__(self, transformer, vae, text_encoder, processor, scheduler):
|
| 105 |
+
super().__init__()
|
| 106 |
+
if (
|
| 107 |
+
scheduler is not None
|
| 108 |
+
and scheduler.__class__.__name__ != FlowUniPCMultistepScheduler.__name__
|
| 109 |
+
):
|
| 110 |
+
raise TypeError(
|
| 111 |
+
"LingBotVideoPipeline requires vendored FlowUniPCMultistepScheduler; "
|
| 112 |
+
f"got {scheduler.__class__.__name__}."
|
| 113 |
+
)
|
| 114 |
+
self.register_modules(
|
| 115 |
+
transformer=transformer,
|
| 116 |
+
vae=vae,
|
| 117 |
+
text_encoder=text_encoder,
|
| 118 |
+
processor=processor,
|
| 119 |
+
scheduler=scheduler,
|
| 120 |
+
)
|
| 121 |
+
self.vae_scale_factor_temporal = 4
|
| 122 |
+
self.vae_scale_factor_spatial = 8
|
| 123 |
+
self.token_length = TOKEN_LENGTH
|
| 124 |
+
self.hidden_state_skip_layer = HIDDEN_STATE_SKIP_LAYER
|
| 125 |
+
self.prompt_template = PROMPT_TEMPLATE
|
| 126 |
+
self.img_prompt_template = IMG_PROMPT_TEMPLATE
|
| 127 |
+
self.video_prompt_template = VIDEO_PROMPT_TEMPLATE
|
| 128 |
+
self._crop_start: Optional[int] = None
|
| 129 |
+
|
| 130 |
+
@staticmethod
|
| 131 |
+
def check_inputs(height: int, width: int, num_frames: int) -> None:
|
| 132 |
+
if num_frames != 1 and (num_frames - 1) % 4 != 0:
|
| 133 |
+
raise ValueError(f"`num_frames` must be 1 or 4n+1, got {num_frames}.")
|
| 134 |
+
if height % 16 != 0 or width % 16 != 0:
|
| 135 |
+
raise ValueError(f"`height` and `width` must be multiples of 16, got {height}x{width}.")
|
| 136 |
+
|
| 137 |
+
@staticmethod
|
| 138 |
+
def _apply_inpainting(latents: torch.Tensor, cond_latent: torch.Tensor) -> torch.Tensor:
|
| 139 |
+
cond_t = cond_latent.shape[2]
|
| 140 |
+
latents[:, :, :cond_t] = cond_latent.float()
|
| 141 |
+
return latents
|
| 142 |
+
|
| 143 |
+
@staticmethod
|
| 144 |
+
def apply_text_to_template(text: str, template: str = PROMPT_TEMPLATE) -> str:
|
| 145 |
+
return template.format(text)
|
| 146 |
+
|
| 147 |
+
def _compute_crop_start(self) -> int:
|
| 148 |
+
if self._crop_start is None:
|
| 149 |
+
marker = "<|USER_INPUT_MARKER|>"
|
| 150 |
+
marked = self.prompt_template.format(marker)
|
| 151 |
+
marker_pos = marked.find(marker)
|
| 152 |
+
if marker_pos < 0:
|
| 153 |
+
self._crop_start = 0
|
| 154 |
+
else:
|
| 155 |
+
prefix = self.processor(
|
| 156 |
+
text=marked[:marker_pos],
|
| 157 |
+
images=None,
|
| 158 |
+
videos=None,
|
| 159 |
+
return_tensors="pt",
|
| 160 |
+
)
|
| 161 |
+
self._crop_start = int(prefix["input_ids"].shape[1])
|
| 162 |
+
return self._crop_start
|
| 163 |
+
|
| 164 |
+
def _build_prompt_inputs(
|
| 165 |
+
self,
|
| 166 |
+
prompt: Union[str, List[str]],
|
| 167 |
+
images: Optional[Any] = None,
|
| 168 |
+
videos: Optional[Any] = None,
|
| 169 |
+
video_metadata: Optional[Any] = None,
|
| 170 |
+
video_kwargs: Optional[Dict[str, Any]] = None,
|
| 171 |
+
):
|
| 172 |
+
if isinstance(prompt, str):
|
| 173 |
+
prompts = [prompt]
|
| 174 |
+
else:
|
| 175 |
+
prompts = list(prompt)
|
| 176 |
+
|
| 177 |
+
visual_template = ""
|
| 178 |
+
if images is not None:
|
| 179 |
+
visual_template = self.img_prompt_template
|
| 180 |
+
elif videos is not None:
|
| 181 |
+
visual_template = self.video_prompt_template
|
| 182 |
+
|
| 183 |
+
texts = [
|
| 184 |
+
self.apply_text_to_template(visual_template + text, self.prompt_template)
|
| 185 |
+
for text in prompts
|
| 186 |
+
]
|
| 187 |
+
kwargs = dict(video_kwargs or {})
|
| 188 |
+
return self.processor(
|
| 189 |
+
text=texts,
|
| 190 |
+
images=images,
|
| 191 |
+
videos=videos,
|
| 192 |
+
video_metadata=video_metadata,
|
| 193 |
+
do_resize=False,
|
| 194 |
+
truncation=True,
|
| 195 |
+
max_length=self.token_length,
|
| 196 |
+
padding="longest",
|
| 197 |
+
return_tensors="pt",
|
| 198 |
+
**kwargs,
|
| 199 |
+
)
|
| 200 |
+
|
| 201 |
+
@torch.no_grad()
|
| 202 |
+
def encode_prompt(
|
| 203 |
+
self,
|
| 204 |
+
prompt: Union[str, List[str]],
|
| 205 |
+
*,
|
| 206 |
+
images: Optional[Any] = None,
|
| 207 |
+
videos: Optional[Any] = None,
|
| 208 |
+
video_metadata: Optional[Any] = None,
|
| 209 |
+
video_kwargs: Optional[Dict[str, Any]] = None,
|
| 210 |
+
device: Optional[Union[str, torch.device]] = None,
|
| 211 |
+
return_inputs: bool = False,
|
| 212 |
+
):
|
| 213 |
+
if self.text_encoder is None or self.processor is None:
|
| 214 |
+
raise ValueError("`text_encoder` and `processor` are required for encode_prompt().")
|
| 215 |
+
|
| 216 |
+
device = torch.device(device) if device is not None else self._execution_device
|
| 217 |
+
inputs = self._build_prompt_inputs(
|
| 218 |
+
prompt,
|
| 219 |
+
images=images,
|
| 220 |
+
videos=videos,
|
| 221 |
+
video_metadata=video_metadata,
|
| 222 |
+
video_kwargs=video_kwargs,
|
| 223 |
+
)
|
| 224 |
+
inputs = inputs.to(device)
|
| 225 |
+
outputs = self.text_encoder(
|
| 226 |
+
**inputs,
|
| 227 |
+
output_hidden_states=self.hidden_state_skip_layer is not None,
|
| 228 |
+
)
|
| 229 |
+
if self.hidden_state_skip_layer is not None:
|
| 230 |
+
prompt_embeds = outputs.hidden_states[-(self.hidden_state_skip_layer + 1)]
|
| 231 |
+
else:
|
| 232 |
+
prompt_embeds = outputs.last_hidden_state
|
| 233 |
+
|
| 234 |
+
prompt_mask = inputs["attention_mask"]
|
| 235 |
+
crop_start = self._compute_crop_start()
|
| 236 |
+
if crop_start > 0:
|
| 237 |
+
prompt_embeds = prompt_embeds[:, crop_start:]
|
| 238 |
+
prompt_mask = prompt_mask[:, crop_start:]
|
| 239 |
+
|
| 240 |
+
# Batch=1 can drop right padding before DiT inference.
|
| 241 |
+
if prompt_embeds.shape[0] == 1:
|
| 242 |
+
true_len = int(prompt_mask[0].sum().item())
|
| 243 |
+
prompt_embeds = prompt_embeds[:, :true_len]
|
| 244 |
+
prompt_mask = prompt_mask[:, :true_len]
|
| 245 |
+
|
| 246 |
+
if return_inputs:
|
| 247 |
+
return prompt_embeds, prompt_mask, inputs
|
| 248 |
+
return prompt_embeds, prompt_mask
|
| 249 |
+
|
| 250 |
+
def prepare_latents(
|
| 251 |
+
self,
|
| 252 |
+
num_frames: int,
|
| 253 |
+
height: int,
|
| 254 |
+
width: int,
|
| 255 |
+
generator: Optional[torch.Generator],
|
| 256 |
+
latents: Optional[torch.Tensor],
|
| 257 |
+
device: torch.device,
|
| 258 |
+
) -> torch.Tensor:
|
| 259 |
+
latent_frames = (num_frames - 1) // self.vae_scale_factor_temporal + 1
|
| 260 |
+
latent_height = height // self.vae_scale_factor_spatial
|
| 261 |
+
latent_width = width // self.vae_scale_factor_spatial
|
| 262 |
+
shape = (
|
| 263 |
+
1,
|
| 264 |
+
self.transformer.config.in_channels,
|
| 265 |
+
latent_frames,
|
| 266 |
+
latent_height,
|
| 267 |
+
latent_width,
|
| 268 |
+
)
|
| 269 |
+
if latents is None:
|
| 270 |
+
return randn_tensor(shape, generator=generator, device=device, dtype=torch.float32)
|
| 271 |
+
if tuple(latents.shape) != shape:
|
| 272 |
+
raise ValueError(f"`latents` shape must be {shape}, got {tuple(latents.shape)}.")
|
| 273 |
+
return latents.to(device=device, dtype=torch.float32)
|
| 274 |
+
|
| 275 |
+
def _dit_latent_to_vae(self, latents: torch.Tensor) -> torch.Tensor:
|
| 276 |
+
mean = torch.tensor(self.vae.config.latents_mean, device=latents.device, dtype=torch.float32)
|
| 277 |
+
std_inv = 1.0 / torch.tensor(
|
| 278 |
+
self.vae.config.latents_std, device=latents.device, dtype=torch.float32
|
| 279 |
+
)
|
| 280 |
+
mean = mean.view(1, -1, 1, 1, 1)
|
| 281 |
+
std_inv = std_inv.view(1, -1, 1, 1, 1)
|
| 282 |
+
return latents.float() / std_inv + mean
|
| 283 |
+
|
| 284 |
+
def _vae_latent_to_dit(self, latents: torch.Tensor) -> torch.Tensor:
|
| 285 |
+
mean = torch.tensor(self.vae.config.latents_mean, device=latents.device, dtype=torch.float32)
|
| 286 |
+
std_inv = 1.0 / torch.tensor(
|
| 287 |
+
self.vae.config.latents_std, device=latents.device, dtype=torch.float32
|
| 288 |
+
)
|
| 289 |
+
mean = mean.view(1, -1, 1, 1, 1)
|
| 290 |
+
std_inv = std_inv.view(1, -1, 1, 1, 1)
|
| 291 |
+
return (latents.float() - mean) * std_inv
|
| 292 |
+
|
| 293 |
+
@torch.no_grad()
|
| 294 |
+
def encode_video_latent(
|
| 295 |
+
self,
|
| 296 |
+
video: torch.Tensor,
|
| 297 |
+
generator: Optional[torch.Generator] = None,
|
| 298 |
+
) -> torch.Tensor:
|
| 299 |
+
if self.vae is None:
|
| 300 |
+
raise ValueError("`vae` is required to encode video latents.")
|
| 301 |
+
vae_device = _module_device(self.vae)
|
| 302 |
+
video = video.to(device=vae_device, dtype=torch.float32)
|
| 303 |
+
bsz, channels, frames, height, width = video.shape
|
| 304 |
+
flat_video = video.permute(0, 2, 1, 3, 4).reshape(bsz * frames, channels, height, width)
|
| 305 |
+
norm_flat_video = normalize_image_tensor(
|
| 306 |
+
flat_video,
|
| 307 |
+
[0.5, 0.5, 0.5],
|
| 308 |
+
[0.5, 0.5, 0.5],
|
| 309 |
+
inplace=False,
|
| 310 |
+
)
|
| 311 |
+
norm_video = (
|
| 312 |
+
norm_flat_video.reshape(bsz, frames, channels, height, width)
|
| 313 |
+
.permute(0, 2, 1, 3, 4)
|
| 314 |
+
.contiguous()
|
| 315 |
+
)
|
| 316 |
+
with torch.autocast(
|
| 317 |
+
"cuda",
|
| 318 |
+
dtype=torch.bfloat16,
|
| 319 |
+
enabled=vae_device.type == "cuda",
|
| 320 |
+
):
|
| 321 |
+
encoded = self.vae.encode(norm_video)
|
| 322 |
+
if hasattr(encoded, "latent_dist"):
|
| 323 |
+
latents = encoded.latent_dist.sample(generator)
|
| 324 |
+
else:
|
| 325 |
+
latents = encoded[0] if isinstance(encoded, tuple) else encoded
|
| 326 |
+
return self._vae_latent_to_dit(latents).to(latents)
|
| 327 |
+
|
| 328 |
+
@torch.no_grad()
|
| 329 |
+
def _decode_latents(
|
| 330 |
+
self,
|
| 331 |
+
latents: torch.Tensor,
|
| 332 |
+
) -> List[np.ndarray]:
|
| 333 |
+
vae_device = _module_device(self.vae)
|
| 334 |
+
vae_dtype = _module_dtype(self.vae)
|
| 335 |
+
vae_latents = self._dit_latent_to_vae(latents).to(device=vae_device, dtype=torch.float32)
|
| 336 |
+
if vae_latents.ndim == 5:
|
| 337 |
+
vae_latents = vae_latents.contiguous(memory_format=torch.channels_last_3d)
|
| 338 |
+
autocast_dtype = (
|
| 339 |
+
vae_dtype
|
| 340 |
+
if vae_device.type == "cuda" and vae_dtype in {torch.bfloat16, torch.float16}
|
| 341 |
+
else None
|
| 342 |
+
)
|
| 343 |
+
with torch.autocast(
|
| 344 |
+
"cuda",
|
| 345 |
+
dtype=autocast_dtype or torch.bfloat16,
|
| 346 |
+
enabled=autocast_dtype is not None,
|
| 347 |
+
):
|
| 348 |
+
decoded = self.vae.decode(vae_latents)
|
| 349 |
+
frames = decoded[0] if isinstance(decoded, tuple) else decoded.sample
|
| 350 |
+
frames = frames.float().clamp_(-1, 1)
|
| 351 |
+
frames = (frames + 1.0) / 2.0
|
| 352 |
+
frames = frames.permute(0, 2, 3, 4, 1).cpu().numpy()
|
| 353 |
+
return [video for video in frames]
|
| 354 |
+
|
| 355 |
+
@torch.no_grad()
|
| 356 |
+
def __call__(
|
| 357 |
+
self,
|
| 358 |
+
prompt: str,
|
| 359 |
+
negative_prompt: str = DEFAULT_NEGATIVE_PROMPT,
|
| 360 |
+
height: int = 480,
|
| 361 |
+
width: int = 480,
|
| 362 |
+
num_frames: int = 81,
|
| 363 |
+
num_inference_steps: int = 40,
|
| 364 |
+
guidance_scale: float = 6.0,
|
| 365 |
+
shift: float = 3.0,
|
| 366 |
+
generator: Optional[torch.Generator] = None,
|
| 367 |
+
latents: Optional[torch.Tensor] = None,
|
| 368 |
+
cond_latent: Optional[torch.Tensor] = None,
|
| 369 |
+
prompt_embeds: Optional[torch.Tensor] = None,
|
| 370 |
+
prompt_mask: Optional[torch.Tensor] = None,
|
| 371 |
+
negative_prompt_embeds: Optional[torch.Tensor] = None,
|
| 372 |
+
negative_prompt_mask: Optional[torch.Tensor] = None,
|
| 373 |
+
output_type: str = "np",
|
| 374 |
+
cfg_parallel_group: Optional[Any] = None,
|
| 375 |
+
batch_cfg: bool = False,
|
| 376 |
+
null_cond_clone_zero: bool = False,
|
| 377 |
+
t_thresh: Optional[float] = None,
|
| 378 |
+
refiner_sigma_tail_steps: int = LOW_NOISE_TAIL_V1_DEFAULT_STEPS,
|
| 379 |
+
offload_vae_during_denoise: bool = False,
|
| 380 |
+
return_dict: bool = True,
|
| 381 |
+
) -> Union[LingBotVideoPipelineOutput, Tuple[Union[List[np.ndarray], torch.Tensor]]]:
|
| 382 |
+
self.check_inputs(height, width, num_frames)
|
| 383 |
+
if self.transformer is None or self.scheduler is None:
|
| 384 |
+
raise ValueError("`transformer` and `scheduler` are required for generation.")
|
| 385 |
+
|
| 386 |
+
device = self._execution_device
|
| 387 |
+
do_cfg = guidance_scale > 1.0
|
| 388 |
+
requested_batch_cfg = bool(batch_cfg)
|
| 389 |
+
effective_batch_cfg = requested_batch_cfg
|
| 390 |
+
batch_cfg_fallback_reason = None
|
| 391 |
+
self._last_batch_cfg_requested = requested_batch_cfg
|
| 392 |
+
self._last_effective_batch_cfg = bool(effective_batch_cfg and do_cfg)
|
| 393 |
+
self._last_batch_cfg_fallback_reason = batch_cfg_fallback_reason
|
| 394 |
+
|
| 395 |
+
cfg_parallel = cfg_parallel_group is not None
|
| 396 |
+
if cfg_parallel and effective_batch_cfg:
|
| 397 |
+
raise ValueError("`cfg_parallel_group` and `batch_cfg` are mutually exclusive.")
|
| 398 |
+
cfg_parallel_rank = 0
|
| 399 |
+
cfg_parallel_world_size = 1
|
| 400 |
+
if cfg_parallel:
|
| 401 |
+
if not dist.is_available() or not dist.is_initialized():
|
| 402 |
+
raise ValueError("`cfg_parallel_group` requires an initialized process group.")
|
| 403 |
+
if not do_cfg:
|
| 404 |
+
raise ValueError("CFG parallel requires `guidance_scale > 1.0`.")
|
| 405 |
+
cfg_parallel_rank = dist.get_rank(cfg_parallel_group)
|
| 406 |
+
cfg_parallel_world_size = dist.get_world_size(cfg_parallel_group)
|
| 407 |
+
if cfg_parallel_world_size != 2:
|
| 408 |
+
raise ValueError(
|
| 409 |
+
f"CFG parallel currently requires exactly 2 ranks, got {cfg_parallel_world_size}."
|
| 410 |
+
)
|
| 411 |
+
|
| 412 |
+
if prompt_embeds is not None:
|
| 413 |
+
if prompt_mask is None:
|
| 414 |
+
raise ValueError("`prompt_mask` is required when `prompt_embeds` is provided.")
|
| 415 |
+
prompt_embeds = prompt_embeds.to(device=device)
|
| 416 |
+
prompt_mask = prompt_mask.to(device=device)
|
| 417 |
+
if negative_prompt_embeds is not None:
|
| 418 |
+
if negative_prompt_mask is None:
|
| 419 |
+
raise ValueError(
|
| 420 |
+
"`negative_prompt_mask` is required when `negative_prompt_embeds` is provided."
|
| 421 |
+
)
|
| 422 |
+
negative_prompt_embeds = negative_prompt_embeds.to(device=device)
|
| 423 |
+
negative_prompt_mask = negative_prompt_mask.to(device=device)
|
| 424 |
+
|
| 425 |
+
if cfg_parallel and cfg_parallel_rank == 1:
|
| 426 |
+
if negative_prompt_embeds is not None:
|
| 427 |
+
negative_embeds, negative_mask = negative_prompt_embeds, negative_prompt_mask
|
| 428 |
+
else:
|
| 429 |
+
negative_embeds, negative_mask = self.encode_prompt(negative_prompt, device=device)
|
| 430 |
+
prompt_embeds = prompt_mask = None
|
| 431 |
+
else:
|
| 432 |
+
if prompt_embeds is None:
|
| 433 |
+
prompt_embeds, prompt_mask = self.encode_prompt(prompt, device=device)
|
| 434 |
+
if do_cfg and not cfg_parallel:
|
| 435 |
+
if null_cond_clone_zero:
|
| 436 |
+
negative_embeds = torch.zeros_like(prompt_embeds)
|
| 437 |
+
negative_mask = prompt_mask.clone()
|
| 438 |
+
elif negative_prompt_embeds is not None:
|
| 439 |
+
negative_embeds, negative_mask = negative_prompt_embeds, negative_prompt_mask
|
| 440 |
+
else:
|
| 441 |
+
negative_embeds, negative_mask = self.encode_prompt(negative_prompt, device=device)
|
| 442 |
+
|
| 443 |
+
latents = self.prepare_latents(num_frames, height, width, generator, latents, device)
|
| 444 |
+
# Clean temporal-prefix condition (e.g. the ti2v refiner's first-frame
|
| 445 |
+
# latent): written into the latent before sampling and after every
|
| 446 |
+
# scheduler step, so the fixed frames stay clean while the rest denoise
|
| 447 |
+
# against them through attention.
|
| 448 |
+
if cond_latent is not None:
|
| 449 |
+
cond_latent = cond_latent.to(device=device, dtype=torch.float32)
|
| 450 |
+
latents = self._apply_inpainting(latents, cond_latent)
|
| 451 |
+
sigmas = compute_refiner_sigmas(
|
| 452 |
+
sigma_max=float(self.scheduler.sigma_max),
|
| 453 |
+
sigma_min=float(self.scheduler.sigma_min),
|
| 454 |
+
num_inference_steps=num_inference_steps,
|
| 455 |
+
shift=shift,
|
| 456 |
+
t_thresh=t_thresh,
|
| 457 |
+
tail_steps=refiner_sigma_tail_steps,
|
| 458 |
+
)
|
| 459 |
+
if sigmas is None:
|
| 460 |
+
self.scheduler.set_timesteps(num_inference_steps, device=device, shift=shift)
|
| 461 |
+
else:
|
| 462 |
+
self.scheduler.set_timesteps(
|
| 463 |
+
int(sigmas.shape[0]),
|
| 464 |
+
device=device,
|
| 465 |
+
sigmas=sigmas,
|
| 466 |
+
shift=1.0,
|
| 467 |
+
)
|
| 468 |
+
transformer_dtype = _module_dtype(self.transformer)
|
| 469 |
+
vae_restore_device: Optional[torch.device] = None
|
| 470 |
+
vae_offloaded = False
|
| 471 |
+
if offload_vae_during_denoise and output_type == "np" and self.vae is not None:
|
| 472 |
+
vae_device = _module_device(self.vae)
|
| 473 |
+
if vae_device.type == "cuda":
|
| 474 |
+
self.vae.to("cpu")
|
| 475 |
+
torch.cuda.empty_cache()
|
| 476 |
+
vae_restore_device = vae_device
|
| 477 |
+
vae_offloaded = True
|
| 478 |
+
cfg_latent_src = _group_global_rank(cfg_parallel_group, 0)
|
| 479 |
+
cfg_uncond_src = _group_global_rank(cfg_parallel_group, 1)
|
| 480 |
+
for i, timestep in enumerate(self.progress_bar(self.scheduler.timesteps)):
|
| 481 |
+
if cfg_parallel:
|
| 482 |
+
dist.broadcast(latents, src=cfg_latent_src, group=cfg_parallel_group)
|
| 483 |
+
timestep_batch = _transformer_timestep(timestep, transformer_dtype).expand(1).to(device)
|
| 484 |
+
latent_model_input = latents
|
| 485 |
+
if cfg_parallel:
|
| 486 |
+
if cfg_parallel_rank == 0:
|
| 487 |
+
branch_embeds = prompt_embeds
|
| 488 |
+
branch_mask = prompt_mask
|
| 489 |
+
branch_name = "transformer.cond"
|
| 490 |
+
else:
|
| 491 |
+
branch_embeds = negative_embeds
|
| 492 |
+
branch_mask = negative_mask
|
| 493 |
+
branch_name = "transformer.uncond"
|
| 494 |
+
branch_model_input = branch_embeds.to(transformer_dtype)
|
| 495 |
+
with _transformer_autocast(device, transformer_dtype):
|
| 496 |
+
branch_noise_pred = self.transformer(
|
| 497 |
+
latent_model_input,
|
| 498 |
+
timestep_batch,
|
| 499 |
+
branch_model_input,
|
| 500 |
+
encoder_attention_mask=branch_mask,
|
| 501 |
+
return_dict=False,
|
| 502 |
+
)[0].float()
|
| 503 |
+
if cfg_parallel_rank == 0:
|
| 504 |
+
noise_pred = branch_noise_pred
|
| 505 |
+
noise_pred_uncond = torch.empty_like(noise_pred)
|
| 506 |
+
else:
|
| 507 |
+
noise_pred_uncond = branch_noise_pred
|
| 508 |
+
dist.broadcast(noise_pred_uncond, src=cfg_uncond_src, group=cfg_parallel_group)
|
| 509 |
+
if cfg_parallel_rank != 0:
|
| 510 |
+
continue
|
| 511 |
+
noise_pred = noise_pred_uncond + guidance_scale * (noise_pred - noise_pred_uncond)
|
| 512 |
+
else:
|
| 513 |
+
prompt_model_input = prompt_embeds.to(transformer_dtype)
|
| 514 |
+
if do_cfg and effective_batch_cfg:
|
| 515 |
+
negative_model_input = negative_embeds.to(transformer_dtype)
|
| 516 |
+
cfg_embeds, cfg_mask = batch_cfg_prompt_inputs(
|
| 517 |
+
prompt_model_input,
|
| 518 |
+
prompt_mask,
|
| 519 |
+
negative_model_input,
|
| 520 |
+
negative_mask,
|
| 521 |
+
null_cond_clone_zero=False,
|
| 522 |
+
)
|
| 523 |
+
cfg_latents = torch.cat([latent_model_input, latent_model_input], dim=0)
|
| 524 |
+
cfg_timesteps = torch.cat([timestep_batch, timestep_batch], dim=0)
|
| 525 |
+
with _transformer_autocast(device, transformer_dtype):
|
| 526 |
+
noise_batched = self.transformer(
|
| 527 |
+
cfg_latents,
|
| 528 |
+
cfg_timesteps,
|
| 529 |
+
cfg_embeds,
|
| 530 |
+
encoder_attention_mask=cfg_mask,
|
| 531 |
+
return_dict=False,
|
| 532 |
+
)[0].float()
|
| 533 |
+
noise_pred, noise_pred_uncond = noise_batched.chunk(2, dim=0)
|
| 534 |
+
noise_pred = noise_pred_uncond + guidance_scale * (
|
| 535 |
+
noise_pred - noise_pred_uncond
|
| 536 |
+
)
|
| 537 |
+
else:
|
| 538 |
+
with _transformer_autocast(device, transformer_dtype):
|
| 539 |
+
noise_pred = self.transformer(
|
| 540 |
+
latent_model_input,
|
| 541 |
+
timestep_batch,
|
| 542 |
+
prompt_model_input,
|
| 543 |
+
encoder_attention_mask=prompt_mask,
|
| 544 |
+
return_dict=False,
|
| 545 |
+
)[0].float()
|
| 546 |
+
|
| 547 |
+
if do_cfg and not effective_batch_cfg:
|
| 548 |
+
negative_model_input = negative_embeds.to(transformer_dtype)
|
| 549 |
+
with _transformer_autocast(device, transformer_dtype):
|
| 550 |
+
noise_pred_uncond = self.transformer(
|
| 551 |
+
latent_model_input,
|
| 552 |
+
timestep_batch,
|
| 553 |
+
negative_model_input,
|
| 554 |
+
encoder_attention_mask=negative_mask,
|
| 555 |
+
return_dict=False,
|
| 556 |
+
)[0].float()
|
| 557 |
+
noise_pred = noise_pred_uncond + guidance_scale * (noise_pred - noise_pred_uncond)
|
| 558 |
+
|
| 559 |
+
latents = self.scheduler.step(
|
| 560 |
+
noise_pred,
|
| 561 |
+
timestep,
|
| 562 |
+
latents,
|
| 563 |
+
return_dict=False,
|
| 564 |
+
generator=generator,
|
| 565 |
+
)[0]
|
| 566 |
+
if cond_latent is not None:
|
| 567 |
+
latents = self._apply_inpainting(latents, cond_latent)
|
| 568 |
+
|
| 569 |
+
if cfg_parallel:
|
| 570 |
+
dist.barrier(group=cfg_parallel_group)
|
| 571 |
+
if cfg_parallel_rank != 0:
|
| 572 |
+
frames = latents if output_type == "latent" else []
|
| 573 |
+
self.maybe_free_model_hooks()
|
| 574 |
+
if not return_dict:
|
| 575 |
+
return (frames,)
|
| 576 |
+
return LingBotVideoPipelineOutput(frames=frames)
|
| 577 |
+
|
| 578 |
+
if output_type == "latent":
|
| 579 |
+
frames = latents
|
| 580 |
+
elif output_type == "np":
|
| 581 |
+
if vae_offloaded and vae_restore_device is not None:
|
| 582 |
+
self.vae.to(device=vae_restore_device)
|
| 583 |
+
torch.cuda.empty_cache()
|
| 584 |
+
frames = self._decode_latents(latents)
|
| 585 |
+
else:
|
| 586 |
+
raise ValueError(f"Unsupported output_type: {output_type}")
|
| 587 |
+
|
| 588 |
+
self.maybe_free_model_hooks()
|
| 589 |
+
if not return_dict:
|
| 590 |
+
return (frames,)
|
| 591 |
+
return LingBotVideoPipelineOutput(frames=frames)
|
lingbot_video/pipeline_lingbot_video_i2v.py
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
from typing import Any, Optional, Tuple, Union
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
import torch
|
| 8 |
+
import torch.distributed as dist
|
| 9 |
+
import torch.nn.functional as F
|
| 10 |
+
from PIL import Image
|
| 11 |
+
from diffusers.utils.torch_utils import randn_tensor
|
| 12 |
+
|
| 13 |
+
from .pipeline_lingbot_video import (
|
| 14 |
+
DEFAULT_NEGATIVE_PROMPT,
|
| 15 |
+
LingBotVideoPipeline,
|
| 16 |
+
LingBotVideoPipelineOutput,
|
| 17 |
+
_group_global_rank,
|
| 18 |
+
_module_device,
|
| 19 |
+
_module_dtype,
|
| 20 |
+
_transformer_autocast,
|
| 21 |
+
_transformer_timestep,
|
| 22 |
+
)
|
| 23 |
+
from .utils import batch_cfg_prompt_inputs
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
IMAGE_MIN_TOKEN_NUM = 4
|
| 27 |
+
IMAGE_MAX_TOKEN_NUM = 16384
|
| 28 |
+
MAX_RATIO = 200
|
| 29 |
+
SPATIAL_MERGE_SIZE = 2
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _round_by_factor(number: float, factor: int) -> int:
|
| 33 |
+
return round(number / factor) * factor
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def _ceil_by_factor(number: float, factor: int) -> int:
|
| 37 |
+
return math.ceil(number / factor) * factor
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _floor_by_factor(number: float, factor: int) -> int:
|
| 41 |
+
return math.floor(number / factor) * factor
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def smart_resize(
|
| 45 |
+
height: int,
|
| 46 |
+
width: int,
|
| 47 |
+
factor: int,
|
| 48 |
+
min_pixels: Optional[int] = None,
|
| 49 |
+
max_pixels: Optional[int] = None,
|
| 50 |
+
) -> Tuple[int, int]:
|
| 51 |
+
max_pixels = max_pixels if max_pixels is not None else IMAGE_MAX_TOKEN_NUM * factor**2
|
| 52 |
+
min_pixels = min_pixels if min_pixels is not None else IMAGE_MIN_TOKEN_NUM * factor**2
|
| 53 |
+
if max_pixels < min_pixels:
|
| 54 |
+
raise ValueError("max_pixels must be greater than or equal to min_pixels.")
|
| 55 |
+
if max(height, width) / min(height, width) > MAX_RATIO:
|
| 56 |
+
raise ValueError(f"absolute aspect ratio must be smaller than {MAX_RATIO}.")
|
| 57 |
+
|
| 58 |
+
resized_height = max(factor, _round_by_factor(height, factor))
|
| 59 |
+
resized_width = max(factor, _round_by_factor(width, factor))
|
| 60 |
+
if resized_height * resized_width > max_pixels:
|
| 61 |
+
beta = math.sqrt((height * width) / max_pixels)
|
| 62 |
+
resized_height = _floor_by_factor(height / beta, factor)
|
| 63 |
+
resized_width = _floor_by_factor(width / beta, factor)
|
| 64 |
+
elif resized_height * resized_width < min_pixels:
|
| 65 |
+
beta = math.sqrt(min_pixels / (height * width))
|
| 66 |
+
resized_height = _ceil_by_factor(height * beta, factor)
|
| 67 |
+
resized_width = _ceil_by_factor(width * beta, factor)
|
| 68 |
+
return resized_height, resized_width
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def _pixel_tensor_to_pil(pixel: torch.Tensor) -> Image.Image:
|
| 72 |
+
"""Match torchvision.transforms.ToPILImage for a float CHW image in [0, 1]."""
|
| 73 |
+
frame = pixel[0, :, 0].detach().cpu().clamp(0, 1)
|
| 74 |
+
array = frame.permute(1, 2, 0).mul(255).byte().numpy()
|
| 75 |
+
return Image.fromarray(array, mode="RGB")
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class LingBotVideoImageToVideoPipeline(LingBotVideoPipeline):
|
| 79 |
+
"""LingBotVideo ti2v pipeline.
|
| 80 |
+
|
| 81 |
+
The condition frame is used twice: as visual input for Qwen3-VL and as a
|
| 82 |
+
clean latent that is written into the beginning of the diffusion latent
|
| 83 |
+
before sampling and after every scheduler step.
|
| 84 |
+
"""
|
| 85 |
+
|
| 86 |
+
def preprocess_image(self, image: Image.Image, height: int, width: int) -> torch.Tensor:
|
| 87 |
+
if image is None:
|
| 88 |
+
raise ValueError("`image` is required when `image_tensor` is not provided.")
|
| 89 |
+
raw = torch.from_numpy(np.array(image.convert("RGB"))).permute(2, 0, 1).unsqueeze(0).contiguous()
|
| 90 |
+
old_h, old_w = raw.shape[-2:]
|
| 91 |
+
scale = max(height / old_h, width / old_w)
|
| 92 |
+
new_h = max(math.ceil(old_h * scale), height)
|
| 93 |
+
new_w = max(math.ceil(old_w * scale), width)
|
| 94 |
+
resized = F.interpolate(raw, size=(new_h, new_w), mode="bilinear", align_corners=False)
|
| 95 |
+
top = int(round((new_h - height) / 2.0))
|
| 96 |
+
left = int(round((new_w - width) / 2.0))
|
| 97 |
+
cropped = resized[:, :, top : top + height, left : left + width].float() / 255.0
|
| 98 |
+
return cropped.unsqueeze(2)
|
| 99 |
+
|
| 100 |
+
def _vision_patch_size(self) -> int:
|
| 101 |
+
for obj in (
|
| 102 |
+
getattr(getattr(self.text_encoder, "config", None), "vision_config", None),
|
| 103 |
+
getattr(getattr(self.processor, "image_processor", None), "config", None),
|
| 104 |
+
getattr(self.processor, "image_processor", None),
|
| 105 |
+
):
|
| 106 |
+
patch = getattr(obj, "patch_size", None)
|
| 107 |
+
if patch is not None:
|
| 108 |
+
return int(patch)
|
| 109 |
+
return 16
|
| 110 |
+
|
| 111 |
+
def _vlm_image(self, pixel: torch.Tensor) -> Image.Image:
|
| 112 |
+
image = _pixel_tensor_to_pil(pixel)
|
| 113 |
+
patch_factor = self._vision_patch_size() * SPATIAL_MERGE_SIZE
|
| 114 |
+
width, height = image.size
|
| 115 |
+
resized_height, resized_width = smart_resize(height, width, factor=patch_factor)
|
| 116 |
+
return image.resize((resized_width, resized_height))
|
| 117 |
+
|
| 118 |
+
@torch.no_grad()
|
| 119 |
+
def encode_image_latent(
|
| 120 |
+
self,
|
| 121 |
+
pixel: torch.Tensor,
|
| 122 |
+
generator: Optional[torch.Generator] = None,
|
| 123 |
+
) -> torch.Tensor:
|
| 124 |
+
if self.vae is None:
|
| 125 |
+
raise ValueError("`vae` is required to encode image latents.")
|
| 126 |
+
device = _module_device(self.vae)
|
| 127 |
+
pixel = pixel.to(device=device, dtype=torch.float32)
|
| 128 |
+
norm_pixel = (pixel - 0.5) / 0.5
|
| 129 |
+
with torch.autocast("cuda", dtype=torch.bfloat16, enabled=device.type == "cuda"):
|
| 130 |
+
latents = self.vae.encode(norm_pixel).latent_dist.sample(generator)
|
| 131 |
+
|
| 132 |
+
mean = torch.tensor(self.vae.config.latents_mean, device=latents.device, dtype=torch.float32)
|
| 133 |
+
std_inv = 1.0 / torch.tensor(
|
| 134 |
+
self.vae.config.latents_std, device=latents.device, dtype=torch.float32
|
| 135 |
+
)
|
| 136 |
+
mean = mean.view(1, -1, 1, 1, 1)
|
| 137 |
+
std_inv = std_inv.view(1, -1, 1, 1, 1)
|
| 138 |
+
return (latents.float() - mean) * std_inv
|
| 139 |
+
|
| 140 |
+
@torch.no_grad()
|
| 141 |
+
def __call__(
|
| 142 |
+
self,
|
| 143 |
+
prompt: str,
|
| 144 |
+
image: Optional[Image.Image] = None,
|
| 145 |
+
image_tensor: Optional[torch.Tensor] = None,
|
| 146 |
+
cond_latent: Optional[torch.Tensor] = None,
|
| 147 |
+
negative_prompt: str = DEFAULT_NEGATIVE_PROMPT,
|
| 148 |
+
height: int = 480,
|
| 149 |
+
width: int = 480,
|
| 150 |
+
num_frames: int = 81,
|
| 151 |
+
num_inference_steps: int = 40,
|
| 152 |
+
guidance_scale: float = 6.0,
|
| 153 |
+
shift: float = 3.0,
|
| 154 |
+
generator: Optional[torch.Generator] = None,
|
| 155 |
+
latents: Optional[torch.Tensor] = None,
|
| 156 |
+
prompt_embeds: Optional[torch.Tensor] = None,
|
| 157 |
+
prompt_mask: Optional[torch.Tensor] = None,
|
| 158 |
+
negative_prompt_embeds: Optional[torch.Tensor] = None,
|
| 159 |
+
negative_prompt_mask: Optional[torch.Tensor] = None,
|
| 160 |
+
output_type: str = "np",
|
| 161 |
+
cfg_parallel_group: Optional[Any] = None,
|
| 162 |
+
batch_cfg: bool = False,
|
| 163 |
+
null_cond_clone_zero: bool = False,
|
| 164 |
+
return_dict: bool = True,
|
| 165 |
+
) -> Union[LingBotVideoPipelineOutput, Tuple[Union[list, torch.Tensor]]]:
|
| 166 |
+
self.check_inputs(height, width, num_frames)
|
| 167 |
+
if self.transformer is None or self.scheduler is None:
|
| 168 |
+
raise ValueError("`transformer` and `scheduler` are required for generation.")
|
| 169 |
+
|
| 170 |
+
device = self._execution_device
|
| 171 |
+
do_cfg = guidance_scale > 1.0
|
| 172 |
+
requested_batch_cfg = bool(batch_cfg)
|
| 173 |
+
effective_batch_cfg = requested_batch_cfg and do_cfg
|
| 174 |
+
self._last_batch_cfg_requested = requested_batch_cfg
|
| 175 |
+
self._last_effective_batch_cfg = effective_batch_cfg
|
| 176 |
+
self._last_batch_cfg_fallback_reason = None
|
| 177 |
+
|
| 178 |
+
cfg_parallel = cfg_parallel_group is not None
|
| 179 |
+
if cfg_parallel and effective_batch_cfg:
|
| 180 |
+
raise ValueError("`cfg_parallel_group` and `batch_cfg` are mutually exclusive.")
|
| 181 |
+
cfg_parallel_rank = 0
|
| 182 |
+
cfg_parallel_world_size = 1
|
| 183 |
+
if cfg_parallel:
|
| 184 |
+
if not dist.is_available() or not dist.is_initialized():
|
| 185 |
+
raise ValueError("`cfg_parallel_group` requires an initialized process group.")
|
| 186 |
+
if not do_cfg:
|
| 187 |
+
raise ValueError("CFG parallel requires `guidance_scale > 1.0`.")
|
| 188 |
+
cfg_parallel_rank = dist.get_rank(cfg_parallel_group)
|
| 189 |
+
cfg_parallel_world_size = dist.get_world_size(cfg_parallel_group)
|
| 190 |
+
if cfg_parallel_world_size != 2:
|
| 191 |
+
raise ValueError(
|
| 192 |
+
f"CFG parallel currently requires exactly 2 ranks, got {cfg_parallel_world_size}."
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
pixel = image_tensor if image_tensor is not None else self.preprocess_image(image, height, width)
|
| 196 |
+
pixel = pixel.to(device=device, dtype=torch.float32)
|
| 197 |
+
vlm_image = self._vlm_image(pixel)
|
| 198 |
+
|
| 199 |
+
if prompt_embeds is not None:
|
| 200 |
+
if prompt_mask is None:
|
| 201 |
+
raise ValueError("`prompt_mask` is required when `prompt_embeds` is provided.")
|
| 202 |
+
prompt_embeds = prompt_embeds.to(device=device)
|
| 203 |
+
prompt_mask = prompt_mask.to(device=device)
|
| 204 |
+
if negative_prompt_embeds is not None:
|
| 205 |
+
if negative_prompt_mask is None:
|
| 206 |
+
raise ValueError(
|
| 207 |
+
"`negative_prompt_mask` is required when `negative_prompt_embeds` is provided."
|
| 208 |
+
)
|
| 209 |
+
negative_prompt_embeds = negative_prompt_embeds.to(device=device)
|
| 210 |
+
negative_prompt_mask = negative_prompt_mask.to(device=device)
|
| 211 |
+
|
| 212 |
+
if cfg_parallel and cfg_parallel_rank == 1:
|
| 213 |
+
if negative_prompt_embeds is not None:
|
| 214 |
+
negative_embeds, negative_mask = negative_prompt_embeds, negative_prompt_mask
|
| 215 |
+
else:
|
| 216 |
+
negative_embeds, negative_mask = self.encode_prompt(
|
| 217 |
+
negative_prompt, images=[vlm_image], device=device
|
| 218 |
+
)
|
| 219 |
+
prompt_embeds = prompt_mask = None
|
| 220 |
+
else:
|
| 221 |
+
if prompt_embeds is None:
|
| 222 |
+
prompt_embeds, prompt_mask = self.encode_prompt(prompt, images=[vlm_image], device=device)
|
| 223 |
+
if do_cfg and not cfg_parallel:
|
| 224 |
+
if null_cond_clone_zero:
|
| 225 |
+
negative_embeds = torch.zeros_like(prompt_embeds)
|
| 226 |
+
negative_mask = prompt_mask.clone()
|
| 227 |
+
elif negative_prompt_embeds is not None:
|
| 228 |
+
negative_embeds, negative_mask = negative_prompt_embeds, negative_prompt_mask
|
| 229 |
+
else:
|
| 230 |
+
negative_embeds, negative_mask = self.encode_prompt(
|
| 231 |
+
negative_prompt, images=[vlm_image], device=device
|
| 232 |
+
)
|
| 233 |
+
|
| 234 |
+
if cond_latent is None:
|
| 235 |
+
cond_latent = self.encode_image_latent(pixel, generator=generator)
|
| 236 |
+
cond_latent = cond_latent.to(device=device, dtype=torch.float32)
|
| 237 |
+
|
| 238 |
+
latents = self.prepare_latents(num_frames, height, width, generator, latents, device)
|
| 239 |
+
latents = self._apply_inpainting(latents, cond_latent)
|
| 240 |
+
self.scheduler.set_timesteps(num_inference_steps, device=device, shift=shift)
|
| 241 |
+
transformer_dtype = _module_dtype(self.transformer)
|
| 242 |
+
cfg_latent_src = _group_global_rank(cfg_parallel_group, 0)
|
| 243 |
+
cfg_uncond_src = _group_global_rank(cfg_parallel_group, 1)
|
| 244 |
+
|
| 245 |
+
for i, timestep in enumerate(self.progress_bar(self.scheduler.timesteps)):
|
| 246 |
+
if cfg_parallel:
|
| 247 |
+
dist.broadcast(latents, src=cfg_latent_src, group=cfg_parallel_group)
|
| 248 |
+
timestep_batch = _transformer_timestep(timestep, transformer_dtype).expand(1).to(device)
|
| 249 |
+
latent_model_input = latents
|
| 250 |
+
if cfg_parallel:
|
| 251 |
+
if cfg_parallel_rank == 0:
|
| 252 |
+
branch_embeds = prompt_embeds
|
| 253 |
+
branch_mask = prompt_mask
|
| 254 |
+
else:
|
| 255 |
+
branch_embeds = negative_embeds
|
| 256 |
+
branch_mask = negative_mask
|
| 257 |
+
branch_model_input = branch_embeds.to(transformer_dtype)
|
| 258 |
+
with _transformer_autocast(device, transformer_dtype):
|
| 259 |
+
branch_noise_pred = self.transformer(
|
| 260 |
+
latent_model_input,
|
| 261 |
+
timestep_batch,
|
| 262 |
+
branch_model_input,
|
| 263 |
+
encoder_attention_mask=branch_mask,
|
| 264 |
+
return_dict=False,
|
| 265 |
+
)[0].float()
|
| 266 |
+
if cfg_parallel_rank == 0:
|
| 267 |
+
noise_pred = branch_noise_pred
|
| 268 |
+
noise_pred_uncond = torch.empty_like(noise_pred)
|
| 269 |
+
else:
|
| 270 |
+
noise_pred_uncond = branch_noise_pred
|
| 271 |
+
dist.broadcast(noise_pred_uncond, src=cfg_uncond_src, group=cfg_parallel_group)
|
| 272 |
+
if cfg_parallel_rank != 0:
|
| 273 |
+
continue
|
| 274 |
+
noise_pred = noise_pred_uncond + guidance_scale * (noise_pred - noise_pred_uncond)
|
| 275 |
+
else:
|
| 276 |
+
prompt_model_input = prompt_embeds.to(transformer_dtype)
|
| 277 |
+
if do_cfg and effective_batch_cfg:
|
| 278 |
+
negative_model_input = negative_embeds.to(transformer_dtype)
|
| 279 |
+
cfg_embeds, cfg_mask = batch_cfg_prompt_inputs(
|
| 280 |
+
prompt_model_input,
|
| 281 |
+
prompt_mask,
|
| 282 |
+
negative_model_input,
|
| 283 |
+
negative_mask,
|
| 284 |
+
null_cond_clone_zero=null_cond_clone_zero,
|
| 285 |
+
)
|
| 286 |
+
cfg_latents = torch.cat([latent_model_input, latent_model_input], dim=0)
|
| 287 |
+
cfg_timesteps = torch.cat([timestep_batch, timestep_batch], dim=0)
|
| 288 |
+
with _transformer_autocast(device, transformer_dtype):
|
| 289 |
+
noise_batched = self.transformer(
|
| 290 |
+
cfg_latents,
|
| 291 |
+
cfg_timesteps,
|
| 292 |
+
cfg_embeds,
|
| 293 |
+
encoder_attention_mask=cfg_mask,
|
| 294 |
+
return_dict=False,
|
| 295 |
+
)[0].float()
|
| 296 |
+
noise_pred, noise_pred_uncond = noise_batched.chunk(2, dim=0)
|
| 297 |
+
noise_pred = noise_pred_uncond + guidance_scale * (
|
| 298 |
+
noise_pred - noise_pred_uncond
|
| 299 |
+
)
|
| 300 |
+
else:
|
| 301 |
+
with _transformer_autocast(device, transformer_dtype):
|
| 302 |
+
noise_pred = self.transformer(
|
| 303 |
+
latent_model_input,
|
| 304 |
+
timestep_batch,
|
| 305 |
+
prompt_model_input,
|
| 306 |
+
encoder_attention_mask=prompt_mask,
|
| 307 |
+
return_dict=False,
|
| 308 |
+
)[0].float()
|
| 309 |
+
|
| 310 |
+
if do_cfg:
|
| 311 |
+
negative_model_input = negative_embeds.to(transformer_dtype)
|
| 312 |
+
with _transformer_autocast(device, transformer_dtype):
|
| 313 |
+
noise_pred_uncond = self.transformer(
|
| 314 |
+
latent_model_input,
|
| 315 |
+
timestep_batch,
|
| 316 |
+
negative_model_input,
|
| 317 |
+
encoder_attention_mask=negative_mask,
|
| 318 |
+
return_dict=False,
|
| 319 |
+
)[0].float()
|
| 320 |
+
noise_pred = noise_pred_uncond + guidance_scale * (
|
| 321 |
+
noise_pred - noise_pred_uncond
|
| 322 |
+
)
|
| 323 |
+
|
| 324 |
+
latents = self.scheduler.step(
|
| 325 |
+
noise_pred,
|
| 326 |
+
timestep,
|
| 327 |
+
latents,
|
| 328 |
+
return_dict=False,
|
| 329 |
+
generator=generator,
|
| 330 |
+
)[0]
|
| 331 |
+
latents = self._apply_inpainting(latents, cond_latent)
|
| 332 |
+
|
| 333 |
+
if cfg_parallel:
|
| 334 |
+
dist.barrier(group=cfg_parallel_group)
|
| 335 |
+
if cfg_parallel_rank != 0:
|
| 336 |
+
frames = latents if output_type == "latent" else []
|
| 337 |
+
self.maybe_free_model_hooks()
|
| 338 |
+
if not return_dict:
|
| 339 |
+
return (frames,)
|
| 340 |
+
return LingBotVideoPipelineOutput(frames=frames)
|
| 341 |
+
|
| 342 |
+
if output_type == "latent":
|
| 343 |
+
frames = latents
|
| 344 |
+
elif output_type == "np":
|
| 345 |
+
frames = self._decode_latents(latents)
|
| 346 |
+
else:
|
| 347 |
+
raise ValueError(f"Unsupported output_type: {output_type}")
|
| 348 |
+
|
| 349 |
+
self.maybe_free_model_hooks()
|
| 350 |
+
if not return_dict:
|
| 351 |
+
return (frames,)
|
| 352 |
+
return LingBotVideoPipelineOutput(frames=frames)
|
lingbot_video/scheduling_flow_unipc.py
ADDED
|
@@ -0,0 +1,796 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
from typing import List, Optional, Tuple, Union
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 7 |
+
from diffusers.schedulers.scheduling_utils import (KarrasDiffusionSchedulers,
|
| 8 |
+
SchedulerMixin,
|
| 9 |
+
SchedulerOutput)
|
| 10 |
+
from diffusers.utils import deprecate, is_scipy_available
|
| 11 |
+
|
| 12 |
+
if is_scipy_available():
|
| 13 |
+
import scipy.stats
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class FlowUniPCMultistepScheduler(SchedulerMixin, ConfigMixin):
|
| 17 |
+
"""
|
| 18 |
+
`UniPCMultistepScheduler` is a training-free framework designed for the fast sampling of diffusion models.
|
| 19 |
+
|
| 20 |
+
This model inherits from [`SchedulerMixin`] and [`ConfigMixin`]. Check the superclass documentation for the generic
|
| 21 |
+
methods the library implements for all schedulers such as loading and saving.
|
| 22 |
+
|
| 23 |
+
Args:
|
| 24 |
+
num_train_timesteps (`int`, defaults to 1000):
|
| 25 |
+
The number of diffusion steps to train the model.
|
| 26 |
+
solver_order (`int`, default `2`):
|
| 27 |
+
The UniPC order which can be any positive integer. The effective order of accuracy is `solver_order + 1`
|
| 28 |
+
due to the UniC. It is recommended to use `solver_order=2` for guided sampling, and `solver_order=3` for
|
| 29 |
+
unconditional sampling.
|
| 30 |
+
prediction_type (`str`, defaults to "flow_prediction"):
|
| 31 |
+
Prediction type of the scheduler function; must be `flow_prediction` for this scheduler, which predicts
|
| 32 |
+
the flow of the diffusion process.
|
| 33 |
+
thresholding (`bool`, defaults to `False`):
|
| 34 |
+
Whether to use the "dynamic thresholding" method. This is unsuitable for latent-space diffusion models such
|
| 35 |
+
as Stable Diffusion.
|
| 36 |
+
dynamic_thresholding_ratio (`float`, defaults to 0.995):
|
| 37 |
+
The ratio for the dynamic thresholding method. Valid only when `thresholding=True`.
|
| 38 |
+
sample_max_value (`float`, defaults to 1.0):
|
| 39 |
+
The threshold value for dynamic thresholding. Valid only when `thresholding=True` and `predict_x0=True`.
|
| 40 |
+
predict_x0 (`bool`, defaults to `True`):
|
| 41 |
+
Whether to use the updating algorithm on the predicted x0.
|
| 42 |
+
solver_type (`str`, default `bh2`):
|
| 43 |
+
Solver type for UniPC. It is recommended to use `bh1` for unconditional sampling when steps < 10, and `bh2`
|
| 44 |
+
otherwise.
|
| 45 |
+
lower_order_final (`bool`, default `True`):
|
| 46 |
+
Whether to use lower-order solvers in the final steps. Only valid for < 15 inference steps. This can
|
| 47 |
+
stabilize the sampling of DPMSolver for steps < 15, especially for steps <= 10.
|
| 48 |
+
disable_corrector (`list`, default `[]`):
|
| 49 |
+
Decides which step to disable the corrector to mitigate the misalignment between `epsilon_theta(x_t, c)`
|
| 50 |
+
and `epsilon_theta(x_t^c, c)` which can influence convergence for a large guidance scale. Corrector is
|
| 51 |
+
usually disabled during the first few steps.
|
| 52 |
+
solver_p (`SchedulerMixin`, default `None`):
|
| 53 |
+
Any other scheduler that if specified, the algorithm becomes `solver_p + UniC`.
|
| 54 |
+
use_karras_sigmas (`bool`, *optional*, defaults to `False`):
|
| 55 |
+
Whether to use Karras sigmas for step sizes in the noise schedule during the sampling process. If `True`,
|
| 56 |
+
the sigmas are determined according to a sequence of noise levels {σi}.
|
| 57 |
+
use_exponential_sigmas (`bool`, *optional*, defaults to `False`):
|
| 58 |
+
Whether to use exponential sigmas for step sizes in the noise schedule during the sampling process.
|
| 59 |
+
timestep_spacing (`str`, defaults to `"linspace"`):
|
| 60 |
+
The way the timesteps should be scaled. Refer to Table 2 of the [Common Diffusion Noise Schedules and
|
| 61 |
+
Sample Steps are Flawed](https://huggingface.co/papers/2305.08891) for more information.
|
| 62 |
+
steps_offset (`int`, defaults to 0):
|
| 63 |
+
An offset added to the inference steps, as required by some model families.
|
| 64 |
+
final_sigmas_type (`str`, defaults to `"zero"`):
|
| 65 |
+
The final `sigma` value for the noise schedule during the sampling process. If `"sigma_min"`, the final
|
| 66 |
+
sigma is the same as the last sigma in the training schedule. If `zero`, the final sigma is set to 0.
|
| 67 |
+
"""
|
| 68 |
+
|
| 69 |
+
_compatibles = [e.name for e in KarrasDiffusionSchedulers]
|
| 70 |
+
order = 1
|
| 71 |
+
|
| 72 |
+
@register_to_config
|
| 73 |
+
def __init__(
|
| 74 |
+
self,
|
| 75 |
+
num_train_timesteps: int = 1000,
|
| 76 |
+
solver_order: int = 2,
|
| 77 |
+
prediction_type: str = "flow_prediction",
|
| 78 |
+
shift: Optional[float] = 1.0,
|
| 79 |
+
use_dynamic_shifting=False,
|
| 80 |
+
thresholding: bool = False,
|
| 81 |
+
dynamic_thresholding_ratio: float = 0.995,
|
| 82 |
+
sample_max_value: float = 1.0,
|
| 83 |
+
predict_x0: bool = True,
|
| 84 |
+
solver_type: str = "bh2",
|
| 85 |
+
lower_order_final: bool = True,
|
| 86 |
+
disable_corrector: List[int] = [],
|
| 87 |
+
solver_p: SchedulerMixin = None,
|
| 88 |
+
timestep_spacing: str = "linspace",
|
| 89 |
+
steps_offset: int = 0,
|
| 90 |
+
final_sigmas_type: Optional[str] = "zero", # "zero", "sigma_min"
|
| 91 |
+
):
|
| 92 |
+
|
| 93 |
+
if solver_type not in ["bh1", "bh2"]:
|
| 94 |
+
if solver_type in ["midpoint", "heun", "logrho"]:
|
| 95 |
+
self.register_to_config(solver_type="bh2")
|
| 96 |
+
else:
|
| 97 |
+
raise NotImplementedError(
|
| 98 |
+
f"{solver_type} is not implemented for {self.__class__}")
|
| 99 |
+
|
| 100 |
+
self.predict_x0 = predict_x0
|
| 101 |
+
# setable values
|
| 102 |
+
self.num_inference_steps = None
|
| 103 |
+
alphas = np.linspace(1, 1 / num_train_timesteps,
|
| 104 |
+
num_train_timesteps)[::-1].copy()
|
| 105 |
+
sigmas = 1.0 - alphas
|
| 106 |
+
sigmas = torch.from_numpy(sigmas).to(dtype=torch.float32)
|
| 107 |
+
|
| 108 |
+
if not use_dynamic_shifting:
|
| 109 |
+
# when use_dynamic_shifting is True, we apply the timestep shifting on the fly based on the image resolution
|
| 110 |
+
sigmas = shift * sigmas / (1 +
|
| 111 |
+
(shift - 1) * sigmas) # pyright: ignore
|
| 112 |
+
|
| 113 |
+
self.sigmas = sigmas
|
| 114 |
+
self.timesteps = sigmas * num_train_timesteps
|
| 115 |
+
|
| 116 |
+
self.model_outputs = [None] * solver_order
|
| 117 |
+
self.timestep_list = [None] * solver_order
|
| 118 |
+
self.lower_order_nums = 0
|
| 119 |
+
self.disable_corrector = disable_corrector
|
| 120 |
+
self.solver_p = solver_p
|
| 121 |
+
self.last_sample = None
|
| 122 |
+
self._step_index = None
|
| 123 |
+
self._begin_index = None
|
| 124 |
+
|
| 125 |
+
self.sigmas = self.sigmas.to(
|
| 126 |
+
"cpu") # to avoid too much CPU/GPU communication
|
| 127 |
+
self.sigma_min = self.sigmas[-1].item()
|
| 128 |
+
self.sigma_max = self.sigmas[0].item()
|
| 129 |
+
|
| 130 |
+
@property
|
| 131 |
+
def step_index(self):
|
| 132 |
+
"""
|
| 133 |
+
The index counter for current timestep. It will increase 1 after each scheduler step.
|
| 134 |
+
"""
|
| 135 |
+
return self._step_index
|
| 136 |
+
|
| 137 |
+
@property
|
| 138 |
+
def begin_index(self):
|
| 139 |
+
"""
|
| 140 |
+
The index for the first timestep. It should be set from pipeline with `set_begin_index` method.
|
| 141 |
+
"""
|
| 142 |
+
return self._begin_index
|
| 143 |
+
|
| 144 |
+
# Copied from diffusers.schedulers.scheduling_dpmsolver_multistep.DPMSolverMultistepScheduler.set_begin_index
|
| 145 |
+
def set_begin_index(self, begin_index: int = 0):
|
| 146 |
+
"""
|
| 147 |
+
Sets the begin index for the scheduler. This function should be run from pipeline before the inference.
|
| 148 |
+
|
| 149 |
+
Args:
|
| 150 |
+
begin_index (`int`):
|
| 151 |
+
The begin index for the scheduler.
|
| 152 |
+
"""
|
| 153 |
+
self._begin_index = begin_index
|
| 154 |
+
|
| 155 |
+
# Modified from diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler.set_timesteps
|
| 156 |
+
def set_timesteps(
|
| 157 |
+
self,
|
| 158 |
+
num_inference_steps: Union[int, None] = None,
|
| 159 |
+
device: Union[str, torch.device] = None,
|
| 160 |
+
sigmas: Optional[List[float]] = None,
|
| 161 |
+
mu: Optional[Union[float, None]] = None,
|
| 162 |
+
shift: Optional[Union[float, None]] = None,
|
| 163 |
+
):
|
| 164 |
+
"""
|
| 165 |
+
Sets the discrete timesteps used for the diffusion chain (to be run before inference).
|
| 166 |
+
Args:
|
| 167 |
+
num_inference_steps (`int`):
|
| 168 |
+
Total number of the spacing of the time steps.
|
| 169 |
+
device (`str` or `torch.device`, *optional*):
|
| 170 |
+
The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.
|
| 171 |
+
"""
|
| 172 |
+
|
| 173 |
+
if self.config.use_dynamic_shifting and mu is None:
|
| 174 |
+
raise ValueError(
|
| 175 |
+
" you have to pass a value for `mu` when `use_dynamic_shifting` is set to be `True`"
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
if sigmas is None:
|
| 179 |
+
sigmas = np.linspace(self.sigma_max, self.sigma_min,
|
| 180 |
+
num_inference_steps +
|
| 181 |
+
1).copy()[:-1] # pyright: ignore
|
| 182 |
+
|
| 183 |
+
if self.config.use_dynamic_shifting:
|
| 184 |
+
sigmas = self.time_shift(mu, 1.0, sigmas) # pyright: ignore
|
| 185 |
+
else:
|
| 186 |
+
if shift is None:
|
| 187 |
+
shift = self.config.shift
|
| 188 |
+
sigmas = shift * sigmas / (1 +
|
| 189 |
+
(shift - 1) * sigmas) # pyright: ignore
|
| 190 |
+
|
| 191 |
+
if self.config.final_sigmas_type == "sigma_min":
|
| 192 |
+
sigma_last = ((1 - self.alphas_cumprod[0]) /
|
| 193 |
+
self.alphas_cumprod[0])**0.5
|
| 194 |
+
elif self.config.final_sigmas_type == "zero":
|
| 195 |
+
sigma_last = 0
|
| 196 |
+
else:
|
| 197 |
+
raise ValueError(
|
| 198 |
+
f"`final_sigmas_type` must be one of 'zero', or 'sigma_min', but got {self.config.final_sigmas_type}"
|
| 199 |
+
)
|
| 200 |
+
|
| 201 |
+
timesteps = sigmas * self.config.num_train_timesteps
|
| 202 |
+
sigmas = np.concatenate([sigmas, [sigma_last]
|
| 203 |
+
]).astype(np.float32) # pyright: ignore
|
| 204 |
+
|
| 205 |
+
self.sigmas = torch.from_numpy(sigmas)
|
| 206 |
+
self.timesteps = torch.from_numpy(timesteps).to(
|
| 207 |
+
device=device, dtype=torch.int64)
|
| 208 |
+
|
| 209 |
+
self.num_inference_steps = len(timesteps)
|
| 210 |
+
|
| 211 |
+
self.model_outputs = [
|
| 212 |
+
None,
|
| 213 |
+
] * self.config.solver_order
|
| 214 |
+
self.lower_order_nums = 0
|
| 215 |
+
self.last_sample = None
|
| 216 |
+
if self.solver_p:
|
| 217 |
+
self.solver_p.set_timesteps(self.num_inference_steps, device=device)
|
| 218 |
+
|
| 219 |
+
# add an index counter for schedulers that allow duplicated timesteps
|
| 220 |
+
self._step_index = None
|
| 221 |
+
self._begin_index = None
|
| 222 |
+
self.sigmas = self.sigmas.to(
|
| 223 |
+
"cpu") # to avoid too much CPU/GPU communication
|
| 224 |
+
|
| 225 |
+
# Copied from diffusers.schedulers.scheduling_ddpm.DDPMScheduler._threshold_sample
|
| 226 |
+
def _threshold_sample(self, sample: torch.Tensor) -> torch.Tensor:
|
| 227 |
+
"""
|
| 228 |
+
"Dynamic thresholding: At each sampling step we set s to a certain percentile absolute pixel value in xt0 (the
|
| 229 |
+
prediction of x_0 at timestep t), and if s > 1, then we threshold xt0 to the range [-s, s] and then divide by
|
| 230 |
+
s. Dynamic thresholding pushes saturated pixels (those near -1 and 1) inwards, thereby actively preventing
|
| 231 |
+
pixels from saturation at each step. We find that dynamic thresholding results in significantly better
|
| 232 |
+
photorealism as well as better image-text alignment, especially when using very large guidance weights."
|
| 233 |
+
|
| 234 |
+
https://arxiv.org/abs/2205.11487
|
| 235 |
+
"""
|
| 236 |
+
dtype = sample.dtype
|
| 237 |
+
batch_size, channels, *remaining_dims = sample.shape
|
| 238 |
+
|
| 239 |
+
if dtype not in (torch.float32, torch.float64):
|
| 240 |
+
sample = sample.float(
|
| 241 |
+
) # upcast for quantile calculation, and clamp not implemented for cpu half
|
| 242 |
+
|
| 243 |
+
# Flatten sample for doing quantile calculation along each image
|
| 244 |
+
sample = sample.reshape(batch_size, channels * np.prod(remaining_dims))
|
| 245 |
+
|
| 246 |
+
abs_sample = sample.abs() # "a certain percentile absolute pixel value"
|
| 247 |
+
|
| 248 |
+
s = torch.quantile(
|
| 249 |
+
abs_sample, self.config.dynamic_thresholding_ratio, dim=1)
|
| 250 |
+
s = torch.clamp(
|
| 251 |
+
s, min=1, max=self.config.sample_max_value
|
| 252 |
+
) # When clamped to min=1, equivalent to standard clipping to [-1, 1]
|
| 253 |
+
s = s.unsqueeze(
|
| 254 |
+
1) # (batch_size, 1) because clamp will broadcast along dim=0
|
| 255 |
+
sample = torch.clamp(
|
| 256 |
+
sample, -s, s
|
| 257 |
+
) / s # "we threshold xt0 to the range [-s, s] and then divide by s"
|
| 258 |
+
|
| 259 |
+
sample = sample.reshape(batch_size, channels, *remaining_dims)
|
| 260 |
+
sample = sample.to(dtype)
|
| 261 |
+
|
| 262 |
+
return sample
|
| 263 |
+
|
| 264 |
+
# Copied from diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler._sigma_to_t
|
| 265 |
+
def _sigma_to_t(self, sigma):
|
| 266 |
+
return sigma * self.config.num_train_timesteps
|
| 267 |
+
|
| 268 |
+
def _sigma_to_alpha_sigma_t(self, sigma):
|
| 269 |
+
return 1 - sigma, sigma
|
| 270 |
+
|
| 271 |
+
# Copied from diffusers.schedulers.scheduling_flow_match_euler_discrete.set_timesteps
|
| 272 |
+
def time_shift(self, mu: float, sigma: float, t: torch.Tensor):
|
| 273 |
+
return math.exp(mu) / (math.exp(mu) + (1 / t - 1)**sigma)
|
| 274 |
+
|
| 275 |
+
def convert_model_output(
|
| 276 |
+
self,
|
| 277 |
+
model_output: torch.Tensor,
|
| 278 |
+
*args,
|
| 279 |
+
sample: torch.Tensor = None,
|
| 280 |
+
**kwargs,
|
| 281 |
+
) -> torch.Tensor:
|
| 282 |
+
r"""
|
| 283 |
+
Convert the model output to the corresponding type the UniPC algorithm needs.
|
| 284 |
+
|
| 285 |
+
Args:
|
| 286 |
+
model_output (`torch.Tensor`):
|
| 287 |
+
The direct output from the learned diffusion model.
|
| 288 |
+
timestep (`int`):
|
| 289 |
+
The current discrete timestep in the diffusion chain.
|
| 290 |
+
sample (`torch.Tensor`):
|
| 291 |
+
A current instance of a sample created by the diffusion process.
|
| 292 |
+
|
| 293 |
+
Returns:
|
| 294 |
+
`torch.Tensor`:
|
| 295 |
+
The converted model output.
|
| 296 |
+
"""
|
| 297 |
+
timestep = args[0] if len(args) > 0 else kwargs.pop("timestep", None)
|
| 298 |
+
if sample is None:
|
| 299 |
+
if len(args) > 1:
|
| 300 |
+
sample = args[1]
|
| 301 |
+
else:
|
| 302 |
+
raise ValueError(
|
| 303 |
+
"missing `sample` as a required keyward argument")
|
| 304 |
+
if timestep is not None:
|
| 305 |
+
deprecate(
|
| 306 |
+
"timesteps",
|
| 307 |
+
"1.0.0",
|
| 308 |
+
"Passing `timesteps` is deprecated and has no effect as model output conversion is now handled via an internal counter `self.step_index`",
|
| 309 |
+
)
|
| 310 |
+
|
| 311 |
+
sigma = self.sigmas[self.step_index]
|
| 312 |
+
alpha_t, sigma_t = self._sigma_to_alpha_sigma_t(sigma)
|
| 313 |
+
|
| 314 |
+
if self.predict_x0:
|
| 315 |
+
if self.config.prediction_type == "flow_prediction":
|
| 316 |
+
sigma_t = self.sigmas[self.step_index]
|
| 317 |
+
x0_pred = sample - sigma_t * model_output
|
| 318 |
+
else:
|
| 319 |
+
raise ValueError(
|
| 320 |
+
f"prediction_type given as {self.config.prediction_type} must be one of `epsilon`, `sample`,"
|
| 321 |
+
" `v_prediction` or `flow_prediction` for the UniPCMultistepScheduler."
|
| 322 |
+
)
|
| 323 |
+
|
| 324 |
+
if self.config.thresholding:
|
| 325 |
+
x0_pred = self._threshold_sample(x0_pred)
|
| 326 |
+
|
| 327 |
+
return x0_pred
|
| 328 |
+
else:
|
| 329 |
+
if self.config.prediction_type == "flow_prediction":
|
| 330 |
+
sigma_t = self.sigmas[self.step_index]
|
| 331 |
+
epsilon = sample - (1 - sigma_t) * model_output
|
| 332 |
+
else:
|
| 333 |
+
raise ValueError(
|
| 334 |
+
f"prediction_type given as {self.config.prediction_type} must be one of `epsilon`, `sample`,"
|
| 335 |
+
" `v_prediction` or `flow_prediction` for the UniPCMultistepScheduler."
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
if self.config.thresholding:
|
| 339 |
+
sigma_t = self.sigmas[self.step_index]
|
| 340 |
+
x0_pred = sample - sigma_t * model_output
|
| 341 |
+
x0_pred = self._threshold_sample(x0_pred)
|
| 342 |
+
epsilon = model_output + x0_pred
|
| 343 |
+
|
| 344 |
+
return epsilon
|
| 345 |
+
|
| 346 |
+
def multistep_uni_p_bh_update(
|
| 347 |
+
self,
|
| 348 |
+
model_output: torch.Tensor,
|
| 349 |
+
*args,
|
| 350 |
+
sample: torch.Tensor = None,
|
| 351 |
+
order: int = None, # pyright: ignore
|
| 352 |
+
**kwargs,
|
| 353 |
+
) -> torch.Tensor:
|
| 354 |
+
"""
|
| 355 |
+
One step for the UniP (B(h) version). Alternatively, `self.solver_p` is used if is specified.
|
| 356 |
+
|
| 357 |
+
Args:
|
| 358 |
+
model_output (`torch.Tensor`):
|
| 359 |
+
The direct output from the learned diffusion model at the current timestep.
|
| 360 |
+
prev_timestep (`int`):
|
| 361 |
+
The previous discrete timestep in the diffusion chain.
|
| 362 |
+
sample (`torch.Tensor`):
|
| 363 |
+
A current instance of a sample created by the diffusion process.
|
| 364 |
+
order (`int`):
|
| 365 |
+
The order of UniP at this timestep (corresponds to the *p* in UniPC-p).
|
| 366 |
+
|
| 367 |
+
Returns:
|
| 368 |
+
`torch.Tensor`:
|
| 369 |
+
The sample tensor at the previous timestep.
|
| 370 |
+
"""
|
| 371 |
+
prev_timestep = args[0] if len(args) > 0 else kwargs.pop(
|
| 372 |
+
"prev_timestep", None)
|
| 373 |
+
if sample is None:
|
| 374 |
+
if len(args) > 1:
|
| 375 |
+
sample = args[1]
|
| 376 |
+
else:
|
| 377 |
+
raise ValueError(
|
| 378 |
+
" missing `sample` as a required keyward argument")
|
| 379 |
+
if order is None:
|
| 380 |
+
if len(args) > 2:
|
| 381 |
+
order = args[2]
|
| 382 |
+
else:
|
| 383 |
+
raise ValueError(
|
| 384 |
+
" missing `order` as a required keyward argument")
|
| 385 |
+
if prev_timestep is not None:
|
| 386 |
+
deprecate(
|
| 387 |
+
"prev_timestep",
|
| 388 |
+
"1.0.0",
|
| 389 |
+
"Passing `prev_timestep` is deprecated and has no effect as model output conversion is now handled via an internal counter `self.step_index`",
|
| 390 |
+
)
|
| 391 |
+
model_output_list = self.model_outputs
|
| 392 |
+
|
| 393 |
+
s0 = self.timestep_list[-1]
|
| 394 |
+
m0 = model_output_list[-1]
|
| 395 |
+
x = sample
|
| 396 |
+
|
| 397 |
+
if self.solver_p:
|
| 398 |
+
x_t = self.solver_p.step(model_output, s0, x).prev_sample
|
| 399 |
+
return x_t
|
| 400 |
+
|
| 401 |
+
sigma_t, sigma_s0 = self.sigmas[self.step_index + 1], self.sigmas[
|
| 402 |
+
self.step_index] # pyright: ignore
|
| 403 |
+
alpha_t, sigma_t = self._sigma_to_alpha_sigma_t(sigma_t)
|
| 404 |
+
alpha_s0, sigma_s0 = self._sigma_to_alpha_sigma_t(sigma_s0)
|
| 405 |
+
|
| 406 |
+
lambda_t = torch.log(alpha_t) - torch.log(sigma_t)
|
| 407 |
+
lambda_s0 = torch.log(alpha_s0) - torch.log(sigma_s0)
|
| 408 |
+
|
| 409 |
+
h = lambda_t - lambda_s0
|
| 410 |
+
device = sample.device
|
| 411 |
+
|
| 412 |
+
rks = []
|
| 413 |
+
D1s = []
|
| 414 |
+
for i in range(1, order):
|
| 415 |
+
si = self.step_index - i # pyright: ignore
|
| 416 |
+
mi = model_output_list[-(i + 1)]
|
| 417 |
+
alpha_si, sigma_si = self._sigma_to_alpha_sigma_t(self.sigmas[si])
|
| 418 |
+
lambda_si = torch.log(alpha_si) - torch.log(sigma_si)
|
| 419 |
+
rk = (lambda_si - lambda_s0) / h
|
| 420 |
+
rks.append(rk)
|
| 421 |
+
D1s.append((mi - m0) / rk) # pyright: ignore
|
| 422 |
+
|
| 423 |
+
rks.append(1.0)
|
| 424 |
+
rks = torch.tensor(rks, device=device)
|
| 425 |
+
|
| 426 |
+
R = []
|
| 427 |
+
b = []
|
| 428 |
+
|
| 429 |
+
hh = -h if self.predict_x0 else h
|
| 430 |
+
h_phi_1 = torch.expm1(hh) # h\phi_1(h) = e^h - 1
|
| 431 |
+
h_phi_k = h_phi_1 / hh - 1
|
| 432 |
+
|
| 433 |
+
factorial_i = 1
|
| 434 |
+
|
| 435 |
+
if self.config.solver_type == "bh1":
|
| 436 |
+
B_h = hh
|
| 437 |
+
elif self.config.solver_type == "bh2":
|
| 438 |
+
B_h = torch.expm1(hh)
|
| 439 |
+
else:
|
| 440 |
+
raise NotImplementedError()
|
| 441 |
+
|
| 442 |
+
for i in range(1, order + 1):
|
| 443 |
+
R.append(torch.pow(rks, i - 1))
|
| 444 |
+
b.append(h_phi_k * factorial_i / B_h)
|
| 445 |
+
factorial_i *= i + 1
|
| 446 |
+
h_phi_k = h_phi_k / hh - 1 / factorial_i
|
| 447 |
+
|
| 448 |
+
R = torch.stack(R)
|
| 449 |
+
b = torch.tensor(b, device=device)
|
| 450 |
+
|
| 451 |
+
if len(D1s) > 0:
|
| 452 |
+
D1s = torch.stack(D1s, dim=1) # (B, K)
|
| 453 |
+
# for order 2, we use a simplified version
|
| 454 |
+
if order == 2:
|
| 455 |
+
rhos_p = torch.tensor([0.5], dtype=x.dtype, device=device)
|
| 456 |
+
else:
|
| 457 |
+
rhos_p = torch.linalg.solve(R[:-1, :-1],
|
| 458 |
+
b[:-1]).to(device).to(x.dtype)
|
| 459 |
+
else:
|
| 460 |
+
D1s = None
|
| 461 |
+
|
| 462 |
+
if self.predict_x0:
|
| 463 |
+
x_t_ = sigma_t / sigma_s0 * x - alpha_t * h_phi_1 * m0
|
| 464 |
+
if D1s is not None:
|
| 465 |
+
pred_res = torch.einsum("k,bkc...->bc...", rhos_p,
|
| 466 |
+
D1s) # pyright: ignore
|
| 467 |
+
else:
|
| 468 |
+
pred_res = 0
|
| 469 |
+
x_t = x_t_ - alpha_t * B_h * pred_res
|
| 470 |
+
else:
|
| 471 |
+
x_t_ = alpha_t / alpha_s0 * x - sigma_t * h_phi_1 * m0
|
| 472 |
+
if D1s is not None:
|
| 473 |
+
pred_res = torch.einsum("k,bkc...->bc...", rhos_p,
|
| 474 |
+
D1s) # pyright: ignore
|
| 475 |
+
else:
|
| 476 |
+
pred_res = 0
|
| 477 |
+
x_t = x_t_ - sigma_t * B_h * pred_res
|
| 478 |
+
|
| 479 |
+
x_t = x_t.to(x.dtype)
|
| 480 |
+
return x_t
|
| 481 |
+
|
| 482 |
+
def multistep_uni_c_bh_update(
|
| 483 |
+
self,
|
| 484 |
+
this_model_output: torch.Tensor,
|
| 485 |
+
*args,
|
| 486 |
+
last_sample: torch.Tensor = None,
|
| 487 |
+
this_sample: torch.Tensor = None,
|
| 488 |
+
order: int = None, # pyright: ignore
|
| 489 |
+
**kwargs,
|
| 490 |
+
) -> torch.Tensor:
|
| 491 |
+
"""
|
| 492 |
+
One step for the UniC (B(h) version).
|
| 493 |
+
|
| 494 |
+
Args:
|
| 495 |
+
this_model_output (`torch.Tensor`):
|
| 496 |
+
The model outputs at `x_t`.
|
| 497 |
+
this_timestep (`int`):
|
| 498 |
+
The current timestep `t`.
|
| 499 |
+
last_sample (`torch.Tensor`):
|
| 500 |
+
The generated sample before the last predictor `x_{t-1}`.
|
| 501 |
+
this_sample (`torch.Tensor`):
|
| 502 |
+
The generated sample after the last predictor `x_{t}`.
|
| 503 |
+
order (`int`):
|
| 504 |
+
The `p` of UniC-p at this step. The effective order of accuracy should be `order + 1`.
|
| 505 |
+
|
| 506 |
+
Returns:
|
| 507 |
+
`torch.Tensor`:
|
| 508 |
+
The corrected sample tensor at the current timestep.
|
| 509 |
+
"""
|
| 510 |
+
this_timestep = args[0] if len(args) > 0 else kwargs.pop(
|
| 511 |
+
"this_timestep", None)
|
| 512 |
+
if last_sample is None:
|
| 513 |
+
if len(args) > 1:
|
| 514 |
+
last_sample = args[1]
|
| 515 |
+
else:
|
| 516 |
+
raise ValueError(
|
| 517 |
+
" missing`last_sample` as a required keyward argument")
|
| 518 |
+
if this_sample is None:
|
| 519 |
+
if len(args) > 2:
|
| 520 |
+
this_sample = args[2]
|
| 521 |
+
else:
|
| 522 |
+
raise ValueError(
|
| 523 |
+
" missing`this_sample` as a required keyward argument")
|
| 524 |
+
if order is None:
|
| 525 |
+
if len(args) > 3:
|
| 526 |
+
order = args[3]
|
| 527 |
+
else:
|
| 528 |
+
raise ValueError(
|
| 529 |
+
" missing`order` as a required keyward argument")
|
| 530 |
+
if this_timestep is not None:
|
| 531 |
+
deprecate(
|
| 532 |
+
"this_timestep",
|
| 533 |
+
"1.0.0",
|
| 534 |
+
"Passing `this_timestep` is deprecated and has no effect as model output conversion is now handled via an internal counter `self.step_index`",
|
| 535 |
+
)
|
| 536 |
+
|
| 537 |
+
model_output_list = self.model_outputs
|
| 538 |
+
|
| 539 |
+
m0 = model_output_list[-1]
|
| 540 |
+
x = last_sample
|
| 541 |
+
x_t = this_sample
|
| 542 |
+
model_t = this_model_output
|
| 543 |
+
|
| 544 |
+
sigma_t, sigma_s0 = self.sigmas[self.step_index], self.sigmas[
|
| 545 |
+
self.step_index - 1] # pyright: ignore
|
| 546 |
+
alpha_t, sigma_t = self._sigma_to_alpha_sigma_t(sigma_t)
|
| 547 |
+
alpha_s0, sigma_s0 = self._sigma_to_alpha_sigma_t(sigma_s0)
|
| 548 |
+
|
| 549 |
+
lambda_t = torch.log(alpha_t) - torch.log(sigma_t)
|
| 550 |
+
lambda_s0 = torch.log(alpha_s0) - torch.log(sigma_s0)
|
| 551 |
+
|
| 552 |
+
h = lambda_t - lambda_s0
|
| 553 |
+
device = this_sample.device
|
| 554 |
+
|
| 555 |
+
rks = []
|
| 556 |
+
D1s = []
|
| 557 |
+
for i in range(1, order):
|
| 558 |
+
si = self.step_index - (i + 1) # pyright: ignore
|
| 559 |
+
mi = model_output_list[-(i + 1)]
|
| 560 |
+
alpha_si, sigma_si = self._sigma_to_alpha_sigma_t(self.sigmas[si])
|
| 561 |
+
lambda_si = torch.log(alpha_si) - torch.log(sigma_si)
|
| 562 |
+
rk = (lambda_si - lambda_s0) / h
|
| 563 |
+
rks.append(rk)
|
| 564 |
+
D1s.append((mi - m0) / rk) # pyright: ignore
|
| 565 |
+
|
| 566 |
+
rks.append(1.0)
|
| 567 |
+
rks = torch.tensor(rks, device=device)
|
| 568 |
+
|
| 569 |
+
R = []
|
| 570 |
+
b = []
|
| 571 |
+
|
| 572 |
+
hh = -h if self.predict_x0 else h
|
| 573 |
+
h_phi_1 = torch.expm1(hh) # h\phi_1(h) = e^h - 1
|
| 574 |
+
h_phi_k = h_phi_1 / hh - 1
|
| 575 |
+
|
| 576 |
+
factorial_i = 1
|
| 577 |
+
|
| 578 |
+
if self.config.solver_type == "bh1":
|
| 579 |
+
B_h = hh
|
| 580 |
+
elif self.config.solver_type == "bh2":
|
| 581 |
+
B_h = torch.expm1(hh)
|
| 582 |
+
else:
|
| 583 |
+
raise NotImplementedError()
|
| 584 |
+
|
| 585 |
+
for i in range(1, order + 1):
|
| 586 |
+
R.append(torch.pow(rks, i - 1))
|
| 587 |
+
b.append(h_phi_k * factorial_i / B_h)
|
| 588 |
+
factorial_i *= i + 1
|
| 589 |
+
h_phi_k = h_phi_k / hh - 1 / factorial_i
|
| 590 |
+
|
| 591 |
+
R = torch.stack(R)
|
| 592 |
+
b = torch.tensor(b, device=device)
|
| 593 |
+
|
| 594 |
+
if len(D1s) > 0:
|
| 595 |
+
D1s = torch.stack(D1s, dim=1)
|
| 596 |
+
else:
|
| 597 |
+
D1s = None
|
| 598 |
+
|
| 599 |
+
# for order 1, we use a simplified version
|
| 600 |
+
if order == 1:
|
| 601 |
+
rhos_c = torch.tensor([0.5], dtype=x.dtype, device=device)
|
| 602 |
+
else:
|
| 603 |
+
rhos_c = torch.linalg.solve(R, b).to(device).to(x.dtype)
|
| 604 |
+
|
| 605 |
+
if self.predict_x0:
|
| 606 |
+
x_t_ = sigma_t / sigma_s0 * x - alpha_t * h_phi_1 * m0
|
| 607 |
+
if D1s is not None:
|
| 608 |
+
corr_res = torch.einsum("k,bkc...->bc...", rhos_c[:-1], D1s)
|
| 609 |
+
else:
|
| 610 |
+
corr_res = 0
|
| 611 |
+
D1_t = model_t - m0
|
| 612 |
+
x_t = x_t_ - alpha_t * B_h * (corr_res + rhos_c[-1] * D1_t)
|
| 613 |
+
else:
|
| 614 |
+
x_t_ = alpha_t / alpha_s0 * x - sigma_t * h_phi_1 * m0
|
| 615 |
+
if D1s is not None:
|
| 616 |
+
corr_res = torch.einsum("k,bkc...->bc...", rhos_c[:-1], D1s)
|
| 617 |
+
else:
|
| 618 |
+
corr_res = 0
|
| 619 |
+
D1_t = model_t - m0
|
| 620 |
+
x_t = x_t_ - sigma_t * B_h * (corr_res + rhos_c[-1] * D1_t)
|
| 621 |
+
x_t = x_t.to(x.dtype)
|
| 622 |
+
return x_t
|
| 623 |
+
|
| 624 |
+
def index_for_timestep(self, timestep, schedule_timesteps=None):
|
| 625 |
+
if schedule_timesteps is None:
|
| 626 |
+
schedule_timesteps = self.timesteps
|
| 627 |
+
|
| 628 |
+
indices = (schedule_timesteps == timestep).nonzero()
|
| 629 |
+
|
| 630 |
+
# The sigma index that is taken for the **very** first `step`
|
| 631 |
+
# is always the second index (or the last index if there is only 1)
|
| 632 |
+
# This way we can ensure we don't accidentally skip a sigma in
|
| 633 |
+
# case we start in the middle of the denoising schedule (e.g. for image-to-image)
|
| 634 |
+
pos = 1 if len(indices) > 1 else 0
|
| 635 |
+
|
| 636 |
+
return indices[pos].item()
|
| 637 |
+
|
| 638 |
+
# Copied from diffusers.schedulers.scheduling_dpmsolver_multistep.DPMSolverMultistepScheduler._init_step_index
|
| 639 |
+
def _init_step_index(self, timestep):
|
| 640 |
+
"""
|
| 641 |
+
Initialize the step_index counter for the scheduler.
|
| 642 |
+
"""
|
| 643 |
+
|
| 644 |
+
if self.begin_index is None:
|
| 645 |
+
if isinstance(timestep, torch.Tensor):
|
| 646 |
+
timestep = timestep.to(self.timesteps.device)
|
| 647 |
+
self._step_index = self.index_for_timestep(timestep)
|
| 648 |
+
else:
|
| 649 |
+
self._step_index = self._begin_index
|
| 650 |
+
|
| 651 |
+
def step(self,
|
| 652 |
+
model_output: torch.Tensor,
|
| 653 |
+
timestep: Union[int, torch.Tensor],
|
| 654 |
+
sample: torch.Tensor,
|
| 655 |
+
return_dict: bool = True,
|
| 656 |
+
generator=None) -> Union[SchedulerOutput, Tuple]:
|
| 657 |
+
"""
|
| 658 |
+
Predict the sample from the previous timestep by reversing the SDE. This function propagates the sample with
|
| 659 |
+
the multistep UniPC.
|
| 660 |
+
|
| 661 |
+
Args:
|
| 662 |
+
model_output (`torch.Tensor`):
|
| 663 |
+
The direct output from learned diffusion model.
|
| 664 |
+
timestep (`int`):
|
| 665 |
+
The current discrete timestep in the diffusion chain.
|
| 666 |
+
sample (`torch.Tensor`):
|
| 667 |
+
A current instance of a sample created by the diffusion process.
|
| 668 |
+
return_dict (`bool`):
|
| 669 |
+
Whether or not to return a [`~schedulers.scheduling_utils.SchedulerOutput`] or `tuple`.
|
| 670 |
+
|
| 671 |
+
Returns:
|
| 672 |
+
[`~schedulers.scheduling_utils.SchedulerOutput`] or `tuple`:
|
| 673 |
+
If return_dict is `True`, [`~schedulers.scheduling_utils.SchedulerOutput`] is returned, otherwise a
|
| 674 |
+
tuple is returned where the first element is the sample tensor.
|
| 675 |
+
|
| 676 |
+
"""
|
| 677 |
+
if self.num_inference_steps is None:
|
| 678 |
+
raise ValueError(
|
| 679 |
+
"Number of inference steps is 'None', you need to run 'set_timesteps' after creating the scheduler"
|
| 680 |
+
)
|
| 681 |
+
|
| 682 |
+
if self.step_index is None:
|
| 683 |
+
self._init_step_index(timestep)
|
| 684 |
+
|
| 685 |
+
use_corrector = (
|
| 686 |
+
self.step_index > 0 and
|
| 687 |
+
self.step_index - 1 not in self.disable_corrector and
|
| 688 |
+
self.last_sample is not None # pyright: ignore
|
| 689 |
+
)
|
| 690 |
+
|
| 691 |
+
model_output_convert = self.convert_model_output(
|
| 692 |
+
model_output, sample=sample)
|
| 693 |
+
if use_corrector:
|
| 694 |
+
sample = self.multistep_uni_c_bh_update(
|
| 695 |
+
this_model_output=model_output_convert,
|
| 696 |
+
last_sample=self.last_sample,
|
| 697 |
+
this_sample=sample,
|
| 698 |
+
order=self.this_order,
|
| 699 |
+
)
|
| 700 |
+
|
| 701 |
+
for i in range(self.config.solver_order - 1):
|
| 702 |
+
self.model_outputs[i] = self.model_outputs[i + 1]
|
| 703 |
+
self.timestep_list[i] = self.timestep_list[i + 1]
|
| 704 |
+
|
| 705 |
+
self.model_outputs[-1] = model_output_convert
|
| 706 |
+
self.timestep_list[-1] = timestep # pyright: ignore
|
| 707 |
+
|
| 708 |
+
if self.config.lower_order_final:
|
| 709 |
+
this_order = min(self.config.solver_order,
|
| 710 |
+
len(self.timesteps) -
|
| 711 |
+
self.step_index) # pyright: ignore
|
| 712 |
+
else:
|
| 713 |
+
this_order = self.config.solver_order
|
| 714 |
+
|
| 715 |
+
self.this_order = min(this_order,
|
| 716 |
+
self.lower_order_nums + 1) # warmup for multistep
|
| 717 |
+
assert self.this_order > 0
|
| 718 |
+
|
| 719 |
+
self.last_sample = sample
|
| 720 |
+
prev_sample = self.multistep_uni_p_bh_update(
|
| 721 |
+
model_output=model_output, # pass the original non-converted model output, in case solver-p is used
|
| 722 |
+
sample=sample,
|
| 723 |
+
order=self.this_order,
|
| 724 |
+
)
|
| 725 |
+
|
| 726 |
+
if self.lower_order_nums < self.config.solver_order:
|
| 727 |
+
self.lower_order_nums += 1
|
| 728 |
+
|
| 729 |
+
# upon completion increase step index by one
|
| 730 |
+
self._step_index += 1 # pyright: ignore
|
| 731 |
+
|
| 732 |
+
if not return_dict:
|
| 733 |
+
return (prev_sample,)
|
| 734 |
+
|
| 735 |
+
return SchedulerOutput(prev_sample=prev_sample)
|
| 736 |
+
|
| 737 |
+
def scale_model_input(self, sample: torch.Tensor, *args,
|
| 738 |
+
**kwargs) -> torch.Tensor:
|
| 739 |
+
"""
|
| 740 |
+
Ensures interchangeability with schedulers that need to scale the denoising model input depending on the
|
| 741 |
+
current timestep.
|
| 742 |
+
|
| 743 |
+
Args:
|
| 744 |
+
sample (`torch.Tensor`):
|
| 745 |
+
The input sample.
|
| 746 |
+
|
| 747 |
+
Returns:
|
| 748 |
+
`torch.Tensor`:
|
| 749 |
+
A scaled input sample.
|
| 750 |
+
"""
|
| 751 |
+
return sample
|
| 752 |
+
|
| 753 |
+
# Copied from diffusers.schedulers.scheduling_dpmsolver_multistep.DPMSolverMultistepScheduler.add_noise
|
| 754 |
+
def add_noise(
|
| 755 |
+
self,
|
| 756 |
+
original_samples: torch.Tensor,
|
| 757 |
+
noise: torch.Tensor,
|
| 758 |
+
timesteps: torch.IntTensor,
|
| 759 |
+
) -> torch.Tensor:
|
| 760 |
+
# Make sure sigmas and timesteps have the same device and dtype as original_samples
|
| 761 |
+
sigmas = self.sigmas.to(
|
| 762 |
+
device=original_samples.device, dtype=original_samples.dtype)
|
| 763 |
+
if original_samples.device.type == "mps" and torch.is_floating_point(
|
| 764 |
+
timesteps):
|
| 765 |
+
# mps does not support float64
|
| 766 |
+
schedule_timesteps = self.timesteps.to(
|
| 767 |
+
original_samples.device, dtype=torch.float32)
|
| 768 |
+
timesteps = timesteps.to(
|
| 769 |
+
original_samples.device, dtype=torch.float32)
|
| 770 |
+
else:
|
| 771 |
+
schedule_timesteps = self.timesteps.to(original_samples.device)
|
| 772 |
+
timesteps = timesteps.to(original_samples.device)
|
| 773 |
+
|
| 774 |
+
# begin_index is None when the scheduler is used for training or pipeline does not implement set_begin_index
|
| 775 |
+
if self.begin_index is None:
|
| 776 |
+
step_indices = [
|
| 777 |
+
self.index_for_timestep(t, schedule_timesteps)
|
| 778 |
+
for t in timesteps
|
| 779 |
+
]
|
| 780 |
+
elif self.step_index is not None:
|
| 781 |
+
# add_noise is called after first denoising step (for inpainting)
|
| 782 |
+
step_indices = [self.step_index] * timesteps.shape[0]
|
| 783 |
+
else:
|
| 784 |
+
# add noise is called before first denoising step to create initial latent(img2img)
|
| 785 |
+
step_indices = [self.begin_index] * timesteps.shape[0]
|
| 786 |
+
|
| 787 |
+
sigma = sigmas[step_indices].flatten()
|
| 788 |
+
while len(sigma.shape) < len(original_samples.shape):
|
| 789 |
+
sigma = sigma.unsqueeze(-1)
|
| 790 |
+
|
| 791 |
+
alpha_t, sigma_t = self._sigma_to_alpha_sigma_t(sigma)
|
| 792 |
+
noisy_samples = alpha_t * original_samples + sigma_t * noise
|
| 793 |
+
return noisy_samples
|
| 794 |
+
|
| 795 |
+
def __len__(self):
|
| 796 |
+
return self.config.num_train_timesteps
|
lingbot_video/sglang_moe_shim.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import importlib.util
|
| 4 |
+
import os
|
| 5 |
+
import sys
|
| 6 |
+
from contextlib import contextmanager
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
+
from types import ModuleType, SimpleNamespace
|
| 9 |
+
from typing import NamedTuple, Optional
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
import torch.nn.functional as F
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@dataclass
|
| 16 |
+
class LightSglangMoeRunnerConfig:
|
| 17 |
+
num_experts: Optional[int] = None
|
| 18 |
+
num_local_experts: Optional[int] = None
|
| 19 |
+
hidden_size: Optional[int] = None
|
| 20 |
+
intermediate_size_per_partition: Optional[int] = None
|
| 21 |
+
layer_id: Optional[int] = None
|
| 22 |
+
top_k: Optional[int] = None
|
| 23 |
+
num_fused_shared_experts: Optional[int] = None
|
| 24 |
+
params_dtype: Optional[torch.dtype] = None
|
| 25 |
+
routing_method_type: Optional[object] = None
|
| 26 |
+
activation: str = "silu"
|
| 27 |
+
is_gated: bool = True
|
| 28 |
+
apply_router_weight_on_input: bool = False
|
| 29 |
+
inplace: bool = False
|
| 30 |
+
no_combine: bool = False
|
| 31 |
+
routed_scaling_factor: Optional[float] = None
|
| 32 |
+
gemm1_alpha: Optional[float] = None
|
| 33 |
+
gemm1_clamp_limit: Optional[float] = None
|
| 34 |
+
swiglu_limit: Optional[float] = None
|
| 35 |
+
gate_up_interleaved: bool = False
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class LightSglangStandardTopKOutput(NamedTuple):
|
| 39 |
+
topk_weights: torch.Tensor
|
| 40 |
+
topk_ids: torch.Tensor
|
| 41 |
+
router_logits: torch.Tensor
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
SGLANG_MOE_SERVER_ARGS = SimpleNamespace(
|
| 45 |
+
enable_deterministic_inference=False,
|
| 46 |
+
enable_fused_moe_sum_all_reduce=False,
|
| 47 |
+
)
|
| 48 |
+
FP8_E4M3_MAX = 448.0
|
| 49 |
+
_SERVER_ARGS_READY = False
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def sglang_env_flag(name: str, default: bool = False) -> bool:
|
| 53 |
+
raw = os.environ.get(name)
|
| 54 |
+
if raw is None:
|
| 55 |
+
return default
|
| 56 |
+
return raw.lower() in {"1", "true", "yes", "on"}
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class SglangEnvFlag:
|
| 60 |
+
def __init__(self, name: str, default: bool = False):
|
| 61 |
+
self.name = name
|
| 62 |
+
self.default = default
|
| 63 |
+
|
| 64 |
+
def get(self) -> bool:
|
| 65 |
+
return sglang_env_flag(self.name, self.default)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def ensure_module(name: str, package_path: str | None = None) -> ModuleType:
|
| 69 |
+
module = sys.modules.get(name)
|
| 70 |
+
if module is None:
|
| 71 |
+
module = ModuleType(name)
|
| 72 |
+
sys.modules[name] = module
|
| 73 |
+
if package_path is not None:
|
| 74 |
+
module.__path__ = [package_path]
|
| 75 |
+
return module
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def fp8_scale_from_amax(amax: torch.Tensor) -> torch.Tensor:
|
| 79 |
+
return torch.clamp(amax.float() / FP8_E4M3_MAX, min=1e-12)
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def quantize_to_fp8_e4m3fn(input: torch.Tensor, scale: torch.Tensor) -> torch.Tensor:
|
| 83 |
+
return torch.clamp(input.float() / scale, -FP8_E4M3_MAX, FP8_E4M3_MAX).to(
|
| 84 |
+
torch.float8_e4m3fn
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def raise_unsupported_sglang_quantization(*args, **kwargs):
|
| 89 |
+
raise RuntimeError("This LingBotVideo SGLang MoE shim only enables FP8 W8A8 quantization")
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def sglang_scaled_fp8_quant(
|
| 93 |
+
input: torch.Tensor,
|
| 94 |
+
scale: Optional[torch.Tensor] = None,
|
| 95 |
+
num_token_padding: Optional[int] = None,
|
| 96 |
+
use_per_token_if_dynamic: bool = False,
|
| 97 |
+
):
|
| 98 |
+
if input.ndim != 2:
|
| 99 |
+
raise ValueError(f"Expected 2D input tensor, got {input.ndim}D")
|
| 100 |
+
rows = input.shape[0]
|
| 101 |
+
output_rows = max(int(num_token_padding or 0), rows)
|
| 102 |
+
output = torch.empty((output_rows, input.shape[1]), device=input.device, dtype=torch.float8_e4m3fn)
|
| 103 |
+
input_contiguous = input.contiguous()
|
| 104 |
+
if scale is None:
|
| 105 |
+
if use_per_token_if_dynamic:
|
| 106 |
+
scale = fp8_scale_from_amax(input_contiguous.float().abs().amax(dim=1, keepdim=True))
|
| 107 |
+
if output_rows > rows:
|
| 108 |
+
padded_scale = torch.ones((output_rows, 1), device=input.device, dtype=torch.float32)
|
| 109 |
+
padded_scale[:rows] = scale
|
| 110 |
+
scale = padded_scale
|
| 111 |
+
else:
|
| 112 |
+
scale = fp8_scale_from_amax(input_contiguous.float().abs().amax()).reshape(1)
|
| 113 |
+
quant_scale = scale[:rows] if scale.ndim == 2 else scale
|
| 114 |
+
output[:rows] = quantize_to_fp8_e4m3fn(input_contiguous, quant_scale)
|
| 115 |
+
if output_rows > rows:
|
| 116 |
+
output[rows:] = torch.zeros(
|
| 117 |
+
(output_rows - rows, input.shape[1]), device=input.device, dtype=torch.float8_e4m3fn
|
| 118 |
+
)
|
| 119 |
+
return output, scale.contiguous()
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def sglang_per_token_group_quant_fp8(
|
| 123 |
+
x: torch.Tensor,
|
| 124 |
+
group_size: int,
|
| 125 |
+
eps: float = 1e-10,
|
| 126 |
+
column_major_scales: bool = False,
|
| 127 |
+
scale_tma_aligned: bool = False,
|
| 128 |
+
scale_ue8m0: bool = False,
|
| 129 |
+
fuse_silu_and_mul: bool = False,
|
| 130 |
+
masked_m: Optional[torch.Tensor] = None,
|
| 131 |
+
enable_v2: Optional[bool] = None,
|
| 132 |
+
):
|
| 133 |
+
if fuse_silu_and_mul:
|
| 134 |
+
half = x.shape[-1] // 2
|
| 135 |
+
x = (F.silu(x[..., :half]) * x[..., half:]).contiguous()
|
| 136 |
+
if x.shape[-1] % group_size != 0:
|
| 137 |
+
raise ValueError("The last dimension must be divisible by group_size")
|
| 138 |
+
x_view = x.contiguous().view(*x.shape[:-1], x.shape[-1] // group_size, group_size)
|
| 139 |
+
scales = fp8_scale_from_amax(x_view.float().abs().amax(dim=-1)).clamp_min(eps)
|
| 140 |
+
x_q = quantize_to_fp8_e4m3fn(x_view, scales.unsqueeze(-1)).view(x.shape)
|
| 141 |
+
if column_major_scales:
|
| 142 |
+
scales = scales.transpose(0, 1).contiguous()
|
| 143 |
+
return x_q.contiguous(), scales.contiguous()
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def sglang_silu_and_mul(x: torch.Tensor, out: torch.Tensor | None = None, *args, **kwargs):
|
| 147 |
+
half = x.shape[-1] // 2
|
| 148 |
+
result = F.silu(x[..., :half]) * x[..., half:]
|
| 149 |
+
if out is not None:
|
| 150 |
+
out.copy_(result)
|
| 151 |
+
return out
|
| 152 |
+
return result
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def sglang_gelu_and_mul(x: torch.Tensor, out: torch.Tensor | None = None, *args, **kwargs):
|
| 156 |
+
half = x.shape[-1] // 2
|
| 157 |
+
result = F.gelu(x[..., :half]) * x[..., half:]
|
| 158 |
+
if out is not None:
|
| 159 |
+
out.copy_(result)
|
| 160 |
+
return out
|
| 161 |
+
return result
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
@contextmanager
|
| 165 |
+
def null_sglang_config_override(config):
|
| 166 |
+
yield
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
def install_sglang_moe_import_shims() -> None:
|
| 170 |
+
try:
|
| 171 |
+
spec = importlib.util.find_spec("sglang")
|
| 172 |
+
except Exception:
|
| 173 |
+
spec = None
|
| 174 |
+
if spec is None or not spec.submodule_search_locations:
|
| 175 |
+
return
|
| 176 |
+
|
| 177 |
+
package_dir = next(iter(spec.submodule_search_locations))
|
| 178 |
+
srt_dir = os.path.join(package_dir, "srt")
|
| 179 |
+
layers_dir = os.path.join(srt_dir, "layers")
|
| 180 |
+
moe_dir = os.path.join(layers_dir, "moe")
|
| 181 |
+
moe_runner_dir = os.path.join(moe_dir, "moe_runner")
|
| 182 |
+
triton_utils_dir = os.path.join(moe_runner_dir, "triton_utils")
|
| 183 |
+
jit_kernel_dir = os.path.join(package_dir, "jit_kernel")
|
| 184 |
+
|
| 185 |
+
ensure_module("sglang", package_dir)
|
| 186 |
+
ensure_module("sglang.srt", srt_dir)
|
| 187 |
+
ensure_module("sglang.srt.layers", layers_dir)
|
| 188 |
+
ensure_module("sglang.srt.layers.moe", moe_dir)
|
| 189 |
+
moe_runner_module = ensure_module("sglang.srt.layers.moe.moe_runner", moe_runner_dir)
|
| 190 |
+
moe_runner_module.MoeRunnerConfig = LightSglangMoeRunnerConfig
|
| 191 |
+
triton_utils_module = ensure_module(
|
| 192 |
+
"sglang.srt.layers.moe.moe_runner.triton_utils", triton_utils_dir
|
| 193 |
+
)
|
| 194 |
+
triton_utils_module.get_config = lambda: None
|
| 195 |
+
triton_utils_module.override_config = null_sglang_config_override
|
| 196 |
+
ensure_module("sglang.jit_kernel", jit_kernel_dir)
|
| 197 |
+
activation_module = ensure_module("sglang.jit_kernel.activation")
|
| 198 |
+
activation_module.silu_and_mul = sglang_silu_and_mul
|
| 199 |
+
activation_module.gelu_and_mul = sglang_gelu_and_mul
|
| 200 |
+
|
| 201 |
+
server_args_module = ensure_module("sglang.srt.server_args")
|
| 202 |
+
server_args_module.get_global_server_args = lambda: SGLANG_MOE_SERVER_ARGS
|
| 203 |
+
server_args_module.set_global_server_args_for_scheduler = lambda args: None
|
| 204 |
+
|
| 205 |
+
batch_module = ensure_module("sglang.srt.batch_invariant_ops")
|
| 206 |
+
batch_module.is_batch_invariant_mode_enabled = lambda: False
|
| 207 |
+
|
| 208 |
+
environ_module = ensure_module("sglang.srt.environ")
|
| 209 |
+
environ_module.envs = SimpleNamespace(
|
| 210 |
+
SGLANG_OPT_SWIGLU_CLAMP_FUSION=SglangEnvFlag("SGLANG_OPT_SWIGLU_CLAMP_FUSION"),
|
| 211 |
+
SGLANG_EXPERIMENTAL_LORA_OPTI=SglangEnvFlag("SGLANG_EXPERIMENTAL_LORA_OPTI"),
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
utils_module = ensure_module("sglang.srt.utils")
|
| 215 |
+
utils_module.cpu_has_amx_support = lambda: False
|
| 216 |
+
utils_module.get_bool_env_var = sglang_env_flag
|
| 217 |
+
utils_module.is_cpu = lambda: not torch.cuda.is_available()
|
| 218 |
+
utils_module.is_cuda = torch.cuda.is_available
|
| 219 |
+
utils_module.is_hip = lambda: False
|
| 220 |
+
utils_module.is_musa = lambda: False
|
| 221 |
+
utils_module.is_xpu = lambda: False
|
| 222 |
+
utils_module.use_intel_xpu_backend = lambda: False
|
| 223 |
+
utils_module.get_device_name = lambda: torch.cuda.get_device_name() if torch.cuda.is_available() else "cpu"
|
| 224 |
+
utils_module.is_sm90_supported = (
|
| 225 |
+
lambda: torch.cuda.is_available() and torch.cuda.get_device_capability()[0] >= 9
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
custom_op_module = ensure_module("sglang.srt.utils.custom_op")
|
| 229 |
+
custom_op_module.register_custom_op = lambda *args, **kwargs: (lambda fn: fn)
|
| 230 |
+
|
| 231 |
+
moe_utils_module = ensure_module("sglang.srt.layers.moe.utils")
|
| 232 |
+
moe_utils_module.get_moe_padding_size = lambda is_aiter_moe: 0
|
| 233 |
+
|
| 234 |
+
fp8_module = ensure_module("sglang.srt.layers.quantization.fp8_kernel")
|
| 235 |
+
fp8_module.per_token_group_quant_fp8 = sglang_per_token_group_quant_fp8
|
| 236 |
+
fp8_module.scaled_fp8_quant = sglang_scaled_fp8_quant
|
| 237 |
+
fp8_module.sglang_per_token_group_quant_fp8 = sglang_per_token_group_quant_fp8
|
| 238 |
+
|
| 239 |
+
int8_module = ensure_module("sglang.srt.layers.quantization.int8_kernel")
|
| 240 |
+
int8_module.per_token_group_quant_int8 = raise_unsupported_sglang_quantization
|
| 241 |
+
int8_module.per_token_quant_int8 = raise_unsupported_sglang_quantization
|
| 242 |
+
int8_module.sglang_per_token_group_quant_int8 = raise_unsupported_sglang_quantization
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
try:
|
| 246 |
+
from sglang.srt.layers.moe.moe_runner.triton_utils.fused_moe import (
|
| 247 |
+
fused_experts as sglang_fused_experts,
|
| 248 |
+
)
|
| 249 |
+
except Exception as direct_exc: # pragma: no cover - optional deployment dependency
|
| 250 |
+
install_sglang_moe_import_shims()
|
| 251 |
+
try:
|
| 252 |
+
from sglang.srt.layers.moe.moe_runner.triton_utils.fused_moe import (
|
| 253 |
+
fused_experts as sglang_fused_experts,
|
| 254 |
+
)
|
| 255 |
+
except Exception as shim_exc: # pragma: no cover - optional deployment dependency
|
| 256 |
+
sglang_fused_experts = None
|
| 257 |
+
SGLANG_MOE_IMPORT_ERROR = shim_exc
|
| 258 |
+
else:
|
| 259 |
+
SGLANG_MOE_IMPORT_ERROR = None
|
| 260 |
+
else:
|
| 261 |
+
SGLANG_MOE_IMPORT_ERROR = None
|
| 262 |
+
|
| 263 |
+
try:
|
| 264 |
+
from sglang.srt.server_args import (
|
| 265 |
+
get_global_server_args as sglang_get_global_server_args,
|
| 266 |
+
set_global_server_args_for_scheduler as sglang_set_global_server_args_for_scheduler,
|
| 267 |
+
)
|
| 268 |
+
except Exception: # pragma: no cover - optional deployment dependency
|
| 269 |
+
sglang_get_global_server_args = None
|
| 270 |
+
sglang_set_global_server_args_for_scheduler = None
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def _ensure_moe_server_args_attrs(args) -> None:
|
| 274 |
+
if not hasattr(args, "enable_deterministic_inference"):
|
| 275 |
+
args.enable_deterministic_inference = False
|
| 276 |
+
if not hasattr(args, "enable_fused_moe_sum_all_reduce"):
|
| 277 |
+
args.enable_fused_moe_sum_all_reduce = False
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
def ensure_sglang_moe_ready() -> None:
|
| 281 |
+
global _SERVER_ARGS_READY
|
| 282 |
+
if sglang_fused_experts is None:
|
| 283 |
+
raise RuntimeError(
|
| 284 |
+
"LINGBOT_MOE_EXPERT_BACKEND=sglang_triton requires SGLang MoE runtime"
|
| 285 |
+
) from SGLANG_MOE_IMPORT_ERROR
|
| 286 |
+
if _SERVER_ARGS_READY:
|
| 287 |
+
return
|
| 288 |
+
_ensure_moe_server_args_attrs(SGLANG_MOE_SERVER_ARGS)
|
| 289 |
+
if sglang_get_global_server_args is not None:
|
| 290 |
+
try:
|
| 291 |
+
server_args = sglang_get_global_server_args()
|
| 292 |
+
except Exception:
|
| 293 |
+
server_args = SGLANG_MOE_SERVER_ARGS
|
| 294 |
+
if sglang_set_global_server_args_for_scheduler is not None:
|
| 295 |
+
sglang_set_global_server_args_for_scheduler(server_args)
|
| 296 |
+
_ensure_moe_server_args_attrs(server_args)
|
| 297 |
+
_SERVER_ARGS_READY = True
|
lingbot_video/transformer_lingbot_video.py
ADDED
|
@@ -0,0 +1,1312 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import os
|
| 3 |
+
from typing import Optional, Tuple
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
import torch.distributed as dist
|
| 9 |
+
|
| 10 |
+
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 11 |
+
from diffusers.models.attention_dispatch import dispatch_attention_fn
|
| 12 |
+
from diffusers.models._modeling_parallel import ContextParallelInput, ContextParallelOutput
|
| 13 |
+
from diffusers.models.embeddings import TimestepEmbedding, Timesteps
|
| 14 |
+
from diffusers.models.modeling_outputs import Transformer2DModelOutput
|
| 15 |
+
from diffusers.models.modeling_utils import ModelMixin
|
| 16 |
+
|
| 17 |
+
try:
|
| 18 |
+
from flash_attn_interface import flash_attn_varlen_func as flash_attn_varlen_func_v3
|
| 19 |
+
except Exception: # pragma: no cover - optional CUDA kernel.
|
| 20 |
+
flash_attn_varlen_func_v3 = None
|
| 21 |
+
|
| 22 |
+
try:
|
| 23 |
+
from .moe_pack_kernels import reorder_tokens_triton_pack
|
| 24 |
+
from .moe_restore_kernels import restore_tokens_triton
|
| 25 |
+
from .sglang_moe_shim import (
|
| 26 |
+
LightSglangMoeRunnerConfig,
|
| 27 |
+
LightSglangStandardTopKOutput,
|
| 28 |
+
ensure_sglang_moe_ready,
|
| 29 |
+
fp8_scale_from_amax,
|
| 30 |
+
quantize_to_fp8_e4m3fn,
|
| 31 |
+
sglang_fused_experts,
|
| 32 |
+
)
|
| 33 |
+
except ImportError: # pragma: no cover - allows direct file loading in diagnostics.
|
| 34 |
+
from moe_pack_kernels import reorder_tokens_triton_pack
|
| 35 |
+
from moe_restore_kernels import restore_tokens_triton
|
| 36 |
+
from sglang_moe_shim import (
|
| 37 |
+
LightSglangMoeRunnerConfig,
|
| 38 |
+
LightSglangStandardTopKOutput,
|
| 39 |
+
ensure_sglang_moe_ready,
|
| 40 |
+
fp8_scale_from_amax,
|
| 41 |
+
quantize_to_fp8_e4m3fn,
|
| 42 |
+
sglang_fused_experts,
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
LINGBOT_VIDEO_FP32_MODULES = (
|
| 47 |
+
"time_embedder",
|
| 48 |
+
"time_modulation",
|
| 49 |
+
"scale_shift_table",
|
| 50 |
+
"norm",
|
| 51 |
+
"norm1",
|
| 52 |
+
"norm2",
|
| 53 |
+
"norm_q",
|
| 54 |
+
"norm_k",
|
| 55 |
+
"norm_post_attn",
|
| 56 |
+
"norm_post_ffn",
|
| 57 |
+
"norm_out",
|
| 58 |
+
"norm_out_modulation",
|
| 59 |
+
"router",
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def should_keep_in_fp32(name: str) -> bool:
|
| 64 |
+
return any(module_name in name.split(".") for module_name in LINGBOT_VIDEO_FP32_MODULES)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def _moe_expert_backend() -> str:
|
| 68 |
+
return os.environ.get("LINGBOT_MOE_EXPERT_BACKEND", "grouped_mm").lower().strip()
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def _moe_pad_backend() -> str:
|
| 72 |
+
return os.environ.get("LINGBOT_MOE_PAD_BACKEND", "loop").lower().strip()
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def _moe_reorder_backend() -> str:
|
| 76 |
+
return os.environ.get("LINGBOT_MOE_REORDER_BACKEND", "sort").lower().strip()
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def _moe_restore_backend() -> str:
|
| 80 |
+
return os.environ.get("LINGBOT_MOE_RESTORE_BACKEND", "scatter").lower().strip()
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def _all_to_all_split_cat(
|
| 84 |
+
local_input: torch.Tensor,
|
| 85 |
+
scatter_dim: int,
|
| 86 |
+
gather_dim: int,
|
| 87 |
+
group: dist.ProcessGroup,
|
| 88 |
+
) -> torch.Tensor:
|
| 89 |
+
world_size = dist.get_world_size(group)
|
| 90 |
+
input_list = [
|
| 91 |
+
tensor.contiguous()
|
| 92 |
+
for tensor in torch.tensor_split(local_input, world_size, scatter_dim)
|
| 93 |
+
]
|
| 94 |
+
output_list = [torch.empty_like(input_list[0]) for _ in range(world_size)]
|
| 95 |
+
dist.all_to_all(output_list, input_list, group=group)
|
| 96 |
+
return torch.cat(output_list, dim=gather_dim).contiguous()
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
class LingBotVideoRMSNorm(nn.Module):
|
| 100 |
+
"""RMSNorm with fp32 accumulation."""
|
| 101 |
+
|
| 102 |
+
def __init__(self, dim: int, eps: float = 1e-6):
|
| 103 |
+
super().__init__()
|
| 104 |
+
self.weight = nn.Parameter(torch.ones(dim))
|
| 105 |
+
self.variance_epsilon = eps
|
| 106 |
+
|
| 107 |
+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
| 108 |
+
input_dtype = hidden_states.dtype
|
| 109 |
+
hidden_states = hidden_states.to(torch.float32)
|
| 110 |
+
variance = hidden_states.pow(2).mean(-1, keepdim=True)
|
| 111 |
+
hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
|
| 112 |
+
return (self.weight * hidden_states).to(input_dtype)
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def apply_rotary_emb(x: torch.Tensor, freqs_cis: torch.Tensor) -> torch.Tensor:
|
| 116 |
+
"""Apply complex RoPE to `(B, S, H, D)` attention tensors."""
|
| 117 |
+
with torch.amp.autocast("cuda", enabled=False):
|
| 118 |
+
x_c = torch.view_as_complex(x.float().reshape(*x.shape[:-1], -1, 2))
|
| 119 |
+
out = torch.view_as_real(x_c * freqs_cis.unsqueeze(2)).flatten(3)
|
| 120 |
+
return out.type_as(x)
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
class LingBotVideoRotaryEmbedding(nn.Module):
|
| 124 |
+
"""Complex64 RoPE table indexed by position ids."""
|
| 125 |
+
|
| 126 |
+
def __init__(self, axes_dims: Tuple[int, ...], axes_lens: Tuple[int, ...], theta: float):
|
| 127 |
+
super().__init__()
|
| 128 |
+
self.axes_dims = tuple(axes_dims)
|
| 129 |
+
self.axes_lens = list(axes_lens)
|
| 130 |
+
self.theta = theta
|
| 131 |
+
self.freqs_cis = None
|
| 132 |
+
|
| 133 |
+
@staticmethod
|
| 134 |
+
def precompute_freqs_cis(dim: Tuple[int, ...], end: Tuple[int, ...], theta: float):
|
| 135 |
+
freqs_cis = []
|
| 136 |
+
for d, e in zip(dim, end):
|
| 137 |
+
freqs = 1.0 / (
|
| 138 |
+
theta ** (torch.arange(0, d, 2, dtype=torch.float64, device="cpu") / d)
|
| 139 |
+
)
|
| 140 |
+
timestep = torch.arange(e, device=freqs.device, dtype=torch.float64)
|
| 141 |
+
freqs = torch.outer(timestep, freqs).float()
|
| 142 |
+
freqs_cis.append(torch.polar(torch.ones_like(freqs), freqs).to(torch.complex64))
|
| 143 |
+
return freqs_cis
|
| 144 |
+
|
| 145 |
+
def forward(self, position_ids: torch.Tensor) -> torch.Tensor:
|
| 146 |
+
# position_ids: (S, 3) int → (S, head_dim/2) complex64
|
| 147 |
+
device = position_ids.device
|
| 148 |
+
max_vals = position_ids.max(dim=0).values.tolist()
|
| 149 |
+
needs_rebuild = self.freqs_cis is None or any(m >= l for m, l in zip(max_vals, self.axes_lens))
|
| 150 |
+
if needs_rebuild:
|
| 151 |
+
for i in range(len(self.axes_lens)):
|
| 152 |
+
if max_vals[i] >= self.axes_lens[i]:
|
| 153 |
+
self.axes_lens[i] = int(max_vals[i] * 1.5) + 1
|
| 154 |
+
self.freqs_cis = self.precompute_freqs_cis(
|
| 155 |
+
self.axes_dims, tuple(self.axes_lens), theta=self.theta
|
| 156 |
+
)
|
| 157 |
+
self.freqs_cis = [freqs_cis.to(device) for freqs_cis in self.freqs_cis]
|
| 158 |
+
elif self.freqs_cis[0].device != device:
|
| 159 |
+
self.freqs_cis = [freqs_cis.to(device) for freqs_cis in self.freqs_cis]
|
| 160 |
+
|
| 161 |
+
return torch.cat([self.freqs_cis[i][position_ids[:, i]] for i in range(len(self.axes_dims))], dim=-1)
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def make_joint_position_ids(
|
| 165 |
+
text_len: int, grid_t: int, grid_h: int, grid_w: int, device: torch.device
|
| 166 |
+
) -> torch.Tensor:
|
| 167 |
+
"""3D positions in [video; text] order. Text t-axis is 1..text_len; video t-axis starts at text_len+1.
|
| 168 |
+
|
| 169 |
+
Matches patchify_and_embed: cap start (1,0,0); vision start (cap_len+1,0,0);
|
| 170 |
+
freqs ordered with x first and cap second (same order as cat_interleave).
|
| 171 |
+
"""
|
| 172 |
+
tt = torch.arange(grid_t, device=device, dtype=torch.int32) + (text_len + 1)
|
| 173 |
+
hh = torch.arange(grid_h, device=device, dtype=torch.int32)
|
| 174 |
+
ww = torch.arange(grid_w, device=device, dtype=torch.int32)
|
| 175 |
+
grid = torch.stack(torch.meshgrid(tt, hh, ww, indexing="ij"), dim=-1).flatten(0, 2)
|
| 176 |
+
text_t = torch.arange(text_len, device=device, dtype=torch.int32) + 1
|
| 177 |
+
text_pos = torch.stack(
|
| 178 |
+
[text_t, torch.zeros_like(text_t), torch.zeros_like(text_t)], dim=-1
|
| 179 |
+
)
|
| 180 |
+
return torch.cat([grid, text_pos], dim=0) # (Nx + L, 3)
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def _cat_interleave(
|
| 184 |
+
a: torch.Tensor,
|
| 185 |
+
len_a: list[int],
|
| 186 |
+
b: torch.Tensor,
|
| 187 |
+
len_b: list[int],
|
| 188 |
+
) -> torch.Tensor:
|
| 189 |
+
a_split = torch.split(a, len_a, dim=1)
|
| 190 |
+
b_split = torch.split(b, len_b, dim=1)
|
| 191 |
+
blocks: list[torch.Tensor] = []
|
| 192 |
+
for x_part, text_part in zip(a_split, b_split):
|
| 193 |
+
blocks.extend([x_part, text_part])
|
| 194 |
+
return torch.cat(blocks, dim=1)
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
class LingBotVideoTextEmbedder(nn.Module):
|
| 198 |
+
"""Matches CondProjection: RMSNorm(text_dim, eps=1e-6 fixed) -> Linear-SiLU-Linear."""
|
| 199 |
+
|
| 200 |
+
def __init__(self, text_dim: int, hidden_size: int):
|
| 201 |
+
super().__init__()
|
| 202 |
+
self.norm = LingBotVideoRMSNorm(text_dim, eps=1e-6)
|
| 203 |
+
self.linear_1 = nn.Linear(text_dim, hidden_size, bias=True)
|
| 204 |
+
self.linear_2 = nn.Linear(hidden_size, hidden_size, bias=True)
|
| 205 |
+
|
| 206 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 207 |
+
x = self.norm(x)
|
| 208 |
+
return self.linear_2(F.silu(self.linear_1(x)))
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
class LingBotVideoAttention(nn.Module):
|
| 212 |
+
def __init__(self, hidden_size, num_heads, norm_eps, qkv_bias, out_bias):
|
| 213 |
+
super().__init__()
|
| 214 |
+
self.num_heads = num_heads
|
| 215 |
+
self.head_dim = hidden_size // num_heads
|
| 216 |
+
self.to_q = nn.Linear(hidden_size, hidden_size, bias=qkv_bias)
|
| 217 |
+
self.to_k = nn.Linear(hidden_size, hidden_size, bias=qkv_bias)
|
| 218 |
+
self.to_v = nn.Linear(hidden_size, hidden_size, bias=qkv_bias)
|
| 219 |
+
self.norm_q = LingBotVideoRMSNorm(self.head_dim, norm_eps)
|
| 220 |
+
self.norm_k = LingBotVideoRMSNorm(self.head_dim, norm_eps)
|
| 221 |
+
self.to_out = nn.Linear(hidden_size, hidden_size, bias=out_bias)
|
| 222 |
+
|
| 223 |
+
def forward(
|
| 224 |
+
self,
|
| 225 |
+
x,
|
| 226 |
+
rotary_emb,
|
| 227 |
+
attention_mask=None,
|
| 228 |
+
packed_indices: Optional[dict[str, torch.Tensor]] = None,
|
| 229 |
+
parallel_config=None,
|
| 230 |
+
):
|
| 231 |
+
B, S, _ = x.shape
|
| 232 |
+
if os.environ.get("LINGBOT_FUSED_QKV_LINEAR") == "1":
|
| 233 |
+
weight = torch.cat(
|
| 234 |
+
(self.to_q.weight, self.to_k.weight, self.to_v.weight),
|
| 235 |
+
dim=0,
|
| 236 |
+
)
|
| 237 |
+
bias = None
|
| 238 |
+
if self.to_q.bias is not None:
|
| 239 |
+
bias = torch.cat(
|
| 240 |
+
(self.to_q.bias, self.to_k.bias, self.to_v.bias),
|
| 241 |
+
dim=0,
|
| 242 |
+
)
|
| 243 |
+
qkv = F.linear(x, weight, bias)
|
| 244 |
+
q, k, v = qkv.view(B, S, 3, self.num_heads, self.head_dim).unbind(2)
|
| 245 |
+
else:
|
| 246 |
+
q = self.to_q(x).unflatten(2, (self.num_heads, self.head_dim))
|
| 247 |
+
k = self.to_k(x).unflatten(2, (self.num_heads, self.head_dim))
|
| 248 |
+
v = self.to_v(x).unflatten(2, (self.num_heads, self.head_dim))
|
| 249 |
+
q = apply_rotary_emb(self.norm_q(q), rotary_emb)
|
| 250 |
+
k = apply_rotary_emb(self.norm_k(k), rotary_emb)
|
| 251 |
+
# dispatch_attention_fn expects (B, S, H, D) in and out (same as the diffusers Wan processor)
|
| 252 |
+
if packed_indices is None:
|
| 253 |
+
out = dispatch_attention_fn(
|
| 254 |
+
q,
|
| 255 |
+
k,
|
| 256 |
+
v,
|
| 257 |
+
attn_mask=attention_mask,
|
| 258 |
+
parallel_config=parallel_config,
|
| 259 |
+
)
|
| 260 |
+
else:
|
| 261 |
+
if flash_attn_varlen_func_v3 is None:
|
| 262 |
+
raise RuntimeError("flash_attn_interface.flash_attn_varlen_func is required.")
|
| 263 |
+
if parallel_config is None:
|
| 264 |
+
result = flash_attn_varlen_func_v3(
|
| 265 |
+
q=q.reshape(-1, self.num_heads, self.head_dim),
|
| 266 |
+
k=k.reshape(-1, self.num_heads, self.head_dim),
|
| 267 |
+
v=v.reshape(-1, self.num_heads, self.head_dim),
|
| 268 |
+
cu_seqlens_q=packed_indices["cu_seqlens_kv"],
|
| 269 |
+
cu_seqlens_k=packed_indices["cu_seqlens_kv"],
|
| 270 |
+
max_seqlen_q=packed_indices["max_seqlen_in_batch_kv"],
|
| 271 |
+
max_seqlen_k=packed_indices["max_seqlen_in_batch_kv"],
|
| 272 |
+
causal=False,
|
| 273 |
+
)
|
| 274 |
+
out = result[0] if isinstance(result, tuple) else result
|
| 275 |
+
out = out.reshape(B, S, self.num_heads, self.head_dim)
|
| 276 |
+
else:
|
| 277 |
+
group = parallel_config.context_parallel_config._ulysses_mesh.get_group()
|
| 278 |
+
world_size = dist.get_world_size(group)
|
| 279 |
+
local_heads = self.num_heads // world_size
|
| 280 |
+
q_global = _all_to_all_split_cat(
|
| 281 |
+
q.reshape(B, S, self.num_heads * self.head_dim),
|
| 282 |
+
scatter_dim=2,
|
| 283 |
+
gather_dim=1,
|
| 284 |
+
group=group,
|
| 285 |
+
).view(B, S * world_size, local_heads, self.head_dim)
|
| 286 |
+
k_global = _all_to_all_split_cat(
|
| 287 |
+
k.reshape(B, S, self.num_heads * self.head_dim),
|
| 288 |
+
scatter_dim=2,
|
| 289 |
+
gather_dim=1,
|
| 290 |
+
group=group,
|
| 291 |
+
).view(B, S * world_size, local_heads, self.head_dim)
|
| 292 |
+
v_global = _all_to_all_split_cat(
|
| 293 |
+
v.reshape(B, S, self.num_heads * self.head_dim),
|
| 294 |
+
scatter_dim=2,
|
| 295 |
+
gather_dim=1,
|
| 296 |
+
group=group,
|
| 297 |
+
).view(B, S * world_size, local_heads, self.head_dim)
|
| 298 |
+
q_flat = q_global.reshape(-1, local_heads, self.head_dim)
|
| 299 |
+
k_flat = k_global.reshape(-1, local_heads, self.head_dim)
|
| 300 |
+
v_flat = v_global.reshape(-1, local_heads, self.head_dim)
|
| 301 |
+
result = flash_attn_varlen_func_v3(
|
| 302 |
+
q=q_flat,
|
| 303 |
+
k=k_flat,
|
| 304 |
+
v=v_flat,
|
| 305 |
+
cu_seqlens_q=packed_indices["cu_seqlens_kv"],
|
| 306 |
+
cu_seqlens_k=packed_indices["cu_seqlens_kv"],
|
| 307 |
+
max_seqlen_q=packed_indices["max_seqlen_in_batch_kv"],
|
| 308 |
+
max_seqlen_k=packed_indices["max_seqlen_in_batch_kv"],
|
| 309 |
+
causal=False,
|
| 310 |
+
)
|
| 311 |
+
out_global = result[0] if isinstance(result, tuple) else result
|
| 312 |
+
out_global = out_global.reshape(B, S * world_size, local_heads * self.head_dim)
|
| 313 |
+
out = _all_to_all_split_cat(
|
| 314 |
+
out_global,
|
| 315 |
+
scatter_dim=1,
|
| 316 |
+
gather_dim=2,
|
| 317 |
+
group=group,
|
| 318 |
+
).view(B, S, self.num_heads, self.head_dim)
|
| 319 |
+
return self.to_out(out.flatten(2, 3).type_as(x))
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
class LingBotVideoMLP(nn.Module):
|
| 323 |
+
def __init__(self, hidden_size, intermediate_size):
|
| 324 |
+
super().__init__()
|
| 325 |
+
self.gate_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
|
| 326 |
+
self.up_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
|
| 327 |
+
self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)
|
| 328 |
+
|
| 329 |
+
def forward(self, x):
|
| 330 |
+
return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
class LingBotVideoRouter(nn.Module):
|
| 334 |
+
"""Matches the TokenChoiceTopKRouter inference path (no capacity/jitter/load stats).
|
| 335 |
+
|
| 336 |
+
The asymmetry must be preserved: selection uses the bias-added score, while gating
|
| 337 |
+
weights gather the bias-free score.
|
| 338 |
+
"""
|
| 339 |
+
|
| 340 |
+
def __init__(self, hidden_size, num_experts, top_k, score_func, norm_topk_prob,
|
| 341 |
+
n_group, topk_group, route_scale):
|
| 342 |
+
super().__init__()
|
| 343 |
+
self.num_experts = num_experts
|
| 344 |
+
self.top_k = top_k
|
| 345 |
+
self.score_func = score_func
|
| 346 |
+
self.norm_topk_prob = norm_topk_prob
|
| 347 |
+
self.n_group = n_group
|
| 348 |
+
self.topk_group = topk_group
|
| 349 |
+
self.route_scale = route_scale
|
| 350 |
+
self.weight = nn.Parameter(torch.empty(num_experts, hidden_size))
|
| 351 |
+
self.register_buffer("e_score_correction_bias", torch.zeros(num_experts), persistent=True)
|
| 352 |
+
|
| 353 |
+
def _group_limited_topk(self, scores_for_choice):
|
| 354 |
+
seq_len = scores_for_choice.shape[0]
|
| 355 |
+
experts_per_group = self.num_experts // self.n_group
|
| 356 |
+
grouped = scores_for_choice.view(seq_len, self.n_group, experts_per_group)
|
| 357 |
+
group_scores = grouped.topk(2, dim=-1)[0].sum(dim=-1)
|
| 358 |
+
group_idx = torch.topk(group_scores, k=self.topk_group, dim=-1, sorted=False)[1]
|
| 359 |
+
group_mask = torch.zeros_like(group_scores)
|
| 360 |
+
group_mask.scatter_(1, group_idx, 1)
|
| 361 |
+
score_mask = (
|
| 362 |
+
group_mask.unsqueeze(-1)
|
| 363 |
+
.expand(seq_len, self.n_group, experts_per_group)
|
| 364 |
+
.reshape(seq_len, -1)
|
| 365 |
+
)
|
| 366 |
+
masked = scores_for_choice.masked_fill(~score_mask.bool(), float("-inf"))
|
| 367 |
+
return torch.topk(masked, k=self.top_k, dim=-1, sorted=False)[1]
|
| 368 |
+
|
| 369 |
+
def forward(self, tokens: torch.Tensor):
|
| 370 |
+
with torch.amp.autocast(tokens.device.type, enabled=False):
|
| 371 |
+
logits = F.linear(tokens.float(), self.weight.float())
|
| 372 |
+
if self.score_func == "softmax":
|
| 373 |
+
scores = F.softmax(logits, dim=-1)
|
| 374 |
+
else:
|
| 375 |
+
scores = logits.sigmoid()
|
| 376 |
+
scores_for_choice = scores + self.e_score_correction_bias.unsqueeze(0)
|
| 377 |
+
if self.n_group is not None and self.n_group > 1:
|
| 378 |
+
top_indices = self._group_limited_topk(scores_for_choice)
|
| 379 |
+
else:
|
| 380 |
+
top_indices = torch.topk(scores_for_choice, k=self.top_k, dim=-1, sorted=False)[1]
|
| 381 |
+
top_scores = scores.gather(1, top_indices)
|
| 382 |
+
if self.top_k > 1 and self.norm_topk_prob:
|
| 383 |
+
top_scores = top_scores / (top_scores.sum(dim=-1, keepdim=True) + 1e-20)
|
| 384 |
+
top_scores = top_scores * self.route_scale
|
| 385 |
+
return top_indices, top_scores.to(tokens.dtype), logits, scores, scores_for_choice
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
class LingBotVideoGroupedExperts(nn.Module):
|
| 389 |
+
"""Weight layout matches GroupedExperts: w1 [E,I,H], w2 [E,H,I], w3 [E,I,H]. Eager per-expert compute."""
|
| 390 |
+
|
| 391 |
+
def __init__(self, num_experts, hidden_size, intermediate_size):
|
| 392 |
+
super().__init__()
|
| 393 |
+
self.num_experts = num_experts
|
| 394 |
+
self.w1 = nn.Parameter(torch.empty(num_experts, intermediate_size, hidden_size))
|
| 395 |
+
self.w2 = nn.Parameter(torch.empty(num_experts, hidden_size, intermediate_size))
|
| 396 |
+
self.w3 = nn.Parameter(torch.empty(num_experts, intermediate_size, hidden_size))
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
def _round_up_to_multiple(value: int, multiple: int) -> int:
|
| 400 |
+
return ((value + multiple - 1) // multiple) * multiple
|
| 401 |
+
|
| 402 |
+
|
| 403 |
+
class LingBotVideoSparseMoeBlock(nn.Module):
|
| 404 |
+
def __init__(self, hidden_size, intermediate_size, num_experts, top_k,
|
| 405 |
+
moe_intermediate_size, score_func, norm_topk_prob, n_group, topk_group,
|
| 406 |
+
routed_scaling_factor, n_shared_experts):
|
| 407 |
+
super().__init__()
|
| 408 |
+
self.hidden_size = hidden_size
|
| 409 |
+
self.num_experts = num_experts
|
| 410 |
+
self.router = LingBotVideoRouter(
|
| 411 |
+
hidden_size, num_experts, top_k, score_func, norm_topk_prob,
|
| 412 |
+
n_group, topk_group, routed_scaling_factor,
|
| 413 |
+
)
|
| 414 |
+
self.experts = LingBotVideoGroupedExperts(num_experts, hidden_size, moe_intermediate_size)
|
| 415 |
+
self._sglang_w13_cache: Optional[torch.Tensor] = None
|
| 416 |
+
self._sglang_w13_cache_key = None
|
| 417 |
+
self._sglang_fp8_cache = None
|
| 418 |
+
self._sglang_fp8_cache_key = None
|
| 419 |
+
self.shared_experts = None
|
| 420 |
+
if n_shared_experts is not None and n_shared_experts > 0:
|
| 421 |
+
self.shared_experts = LingBotVideoMLP(
|
| 422 |
+
hidden_size, moe_intermediate_size * n_shared_experts
|
| 423 |
+
)
|
| 424 |
+
|
| 425 |
+
@staticmethod
|
| 426 |
+
def _reorder_tokens(tokens: torch.Tensor, top_scores: torch.Tensor, top_indices: torch.Tensor, num_experts: int):
|
| 427 |
+
backend = _moe_reorder_backend()
|
| 428 |
+
if backend in {"triton_pack", "pack", "triton"}:
|
| 429 |
+
return reorder_tokens_triton_pack(tokens, top_scores, top_indices, num_experts)
|
| 430 |
+
if backend not in {"sort", "argsort", "default"}:
|
| 431 |
+
raise ValueError(
|
| 432 |
+
f"Unsupported LINGBOT_MOE_REORDER_BACKEND={backend!r}; "
|
| 433 |
+
"expected sort or triton_pack"
|
| 434 |
+
)
|
| 435 |
+
num_tokens = tokens.shape[0]
|
| 436 |
+
top_k = top_indices.shape[1]
|
| 437 |
+
flat_scores = top_scores.reshape(-1)
|
| 438 |
+
flat_indices = top_indices.reshape(-1)
|
| 439 |
+
active_positions = torch.where(flat_scores != 0)[0]
|
| 440 |
+
active_experts = flat_indices[active_positions]
|
| 441 |
+
|
| 442 |
+
counts = torch.zeros(num_experts, device=tokens.device, dtype=torch.int64)
|
| 443 |
+
counts.scatter_add_(0, active_experts, torch.ones_like(active_experts, dtype=torch.int64))
|
| 444 |
+
|
| 445 |
+
sort_order = torch.argsort(active_experts, stable=True)
|
| 446 |
+
sorted_positions = active_positions[sort_order]
|
| 447 |
+
sorted_scores = flat_scores[sorted_positions]
|
| 448 |
+
original_token_idx = sorted_positions // top_k
|
| 449 |
+
permuted_tokens = tokens[original_token_idx]
|
| 450 |
+
return permuted_tokens, counts, sorted_positions, sorted_scores, num_tokens, top_k
|
| 451 |
+
|
| 452 |
+
@staticmethod
|
| 453 |
+
def _pad_grouped_tokens_loop(tokens: torch.Tensor, counts: torch.Tensor, align: int = 8):
|
| 454 |
+
num_tokens = tokens.shape[0]
|
| 455 |
+
num_experts = int(counts.shape[0])
|
| 456 |
+
max_len = _round_up_to_multiple(num_tokens + num_experts * align, align)
|
| 457 |
+
counts_i64 = counts.to(torch.int64)
|
| 458 |
+
total_per_expert = torch.clamp_min(counts_i64, align)
|
| 459 |
+
aligned_counts = (
|
| 460 |
+
(total_per_expert + align - 1) // align * align
|
| 461 |
+
).to(torch.int32)
|
| 462 |
+
write_offsets = torch.cumsum(aligned_counts, dim=0) - aligned_counts
|
| 463 |
+
start_indices = torch.cumsum(counts_i64, dim=0) - counts_i64
|
| 464 |
+
|
| 465 |
+
fill_value = num_tokens
|
| 466 |
+
permuted_indices = torch.full(
|
| 467 |
+
(max_len,), fill_value, dtype=torch.int64, device=tokens.device
|
| 468 |
+
)
|
| 469 |
+
for expert_idx in range(num_experts):
|
| 470 |
+
length = int(counts_i64[expert_idx].item())
|
| 471 |
+
if length == 0:
|
| 472 |
+
continue
|
| 473 |
+
write_start = int(write_offsets[expert_idx].item())
|
| 474 |
+
start = int(start_indices[expert_idx].item())
|
| 475 |
+
permuted_indices[write_start:write_start + length] = torch.arange(
|
| 476 |
+
start, start + length, device=tokens.device, dtype=torch.int64
|
| 477 |
+
)
|
| 478 |
+
|
| 479 |
+
tokens_with_pad = torch.vstack((tokens, tokens.new_zeros((tokens.shape[-1],))))
|
| 480 |
+
input_shape = tokens_with_pad.shape
|
| 481 |
+
return input_shape, tokens_with_pad[permuted_indices], permuted_indices, aligned_counts
|
| 482 |
+
|
| 483 |
+
@staticmethod
|
| 484 |
+
def _pad_grouped_tokens_vectorized(tokens: torch.Tensor, counts: torch.Tensor, align: int = 8):
|
| 485 |
+
num_tokens = tokens.shape[0]
|
| 486 |
+
num_experts = int(counts.shape[0])
|
| 487 |
+
max_len = _round_up_to_multiple(num_tokens + num_experts * align, align)
|
| 488 |
+
counts_i64 = counts.to(torch.int64)
|
| 489 |
+
total_per_expert = torch.clamp_min(counts_i64, align)
|
| 490 |
+
aligned_counts_i64 = (total_per_expert + align - 1) // align * align
|
| 491 |
+
write_offsets = torch.cumsum(aligned_counts_i64, dim=0) - aligned_counts_i64
|
| 492 |
+
end_offsets = torch.cumsum(aligned_counts_i64, dim=0)
|
| 493 |
+
start_indices = torch.cumsum(counts_i64, dim=0) - counts_i64
|
| 494 |
+
|
| 495 |
+
slots = torch.arange(max_len, dtype=torch.int64, device=tokens.device)
|
| 496 |
+
expert_idx = torch.bucketize(slots, end_offsets, right=True)
|
| 497 |
+
valid_expert = expert_idx < num_experts
|
| 498 |
+
safe_expert_idx = expert_idx.clamp(max=num_experts - 1)
|
| 499 |
+
local_idx = slots - write_offsets[safe_expert_idx]
|
| 500 |
+
source_idx = start_indices[safe_expert_idx] + local_idx
|
| 501 |
+
valid = valid_expert & (local_idx < counts_i64[safe_expert_idx])
|
| 502 |
+
fill = torch.full_like(source_idx, num_tokens)
|
| 503 |
+
permuted_indices = torch.where(valid, source_idx, fill)
|
| 504 |
+
|
| 505 |
+
tokens_with_pad = torch.vstack((tokens, tokens.new_zeros((tokens.shape[-1],))))
|
| 506 |
+
input_shape = tokens_with_pad.shape
|
| 507 |
+
return (
|
| 508 |
+
input_shape,
|
| 509 |
+
tokens_with_pad[permuted_indices],
|
| 510 |
+
permuted_indices,
|
| 511 |
+
aligned_counts_i64.to(torch.int32),
|
| 512 |
+
)
|
| 513 |
+
|
| 514 |
+
@staticmethod
|
| 515 |
+
def _pad_grouped_tokens(tokens: torch.Tensor, counts: torch.Tensor, align: int = 8):
|
| 516 |
+
backend = _moe_pad_backend()
|
| 517 |
+
if backend in {"loop", "default"}:
|
| 518 |
+
return LingBotVideoSparseMoeBlock._pad_grouped_tokens_loop(tokens, counts, align)
|
| 519 |
+
if backend in {"vectorized", "torch"}:
|
| 520 |
+
return LingBotVideoSparseMoeBlock._pad_grouped_tokens_vectorized(tokens, counts, align)
|
| 521 |
+
raise ValueError(
|
| 522 |
+
f"Unsupported LINGBOT_MOE_PAD_BACKEND={backend!r}; expected loop or vectorized"
|
| 523 |
+
)
|
| 524 |
+
|
| 525 |
+
@staticmethod
|
| 526 |
+
def _unpad_grouped_tokens(output: torch.Tensor, input_shape: torch.Size, permuted_indices: torch.Tensor):
|
| 527 |
+
unpermuted = output.new_empty(input_shape)
|
| 528 |
+
unpermuted[permuted_indices, :] = output
|
| 529 |
+
return unpermuted[:-1]
|
| 530 |
+
|
| 531 |
+
def _run_grouped_experts(self, tokens: torch.Tensor, counts: torch.Tensor) -> torch.Tensor:
|
| 532 |
+
if not hasattr(torch, "_grouped_mm"):
|
| 533 |
+
return self._run_experts_for_loop(tokens, counts)
|
| 534 |
+
input_shape, padded_tokens, permuted_indices, aligned_counts = self._pad_grouped_tokens(tokens, counts)
|
| 535 |
+
offsets = torch.cumsum(aligned_counts, dim=0, dtype=torch.int32)
|
| 536 |
+
h = F.silu(
|
| 537 |
+
torch._grouped_mm(
|
| 538 |
+
padded_tokens.bfloat16(),
|
| 539 |
+
self.experts.w1.bfloat16().transpose(-2, -1),
|
| 540 |
+
offs=offsets,
|
| 541 |
+
)
|
| 542 |
+
)
|
| 543 |
+
h = h * torch._grouped_mm(
|
| 544 |
+
padded_tokens.bfloat16(),
|
| 545 |
+
self.experts.w3.bfloat16().transpose(-2, -1),
|
| 546 |
+
offs=offsets,
|
| 547 |
+
)
|
| 548 |
+
out = torch._grouped_mm(
|
| 549 |
+
h,
|
| 550 |
+
self.experts.w2.bfloat16().transpose(-2, -1),
|
| 551 |
+
offs=offsets,
|
| 552 |
+
).type_as(padded_tokens)
|
| 553 |
+
return self._unpad_grouped_tokens(out, input_shape, permuted_indices)
|
| 554 |
+
|
| 555 |
+
def _run_experts_for_loop(self, tokens: torch.Tensor, counts: torch.Tensor) -> torch.Tensor:
|
| 556 |
+
count_list = counts.tolist()
|
| 557 |
+
splits = torch.split(tokens, count_list, dim=0)
|
| 558 |
+
outputs = []
|
| 559 |
+
for expert_idx, expert_tokens in enumerate(splits):
|
| 560 |
+
if expert_tokens.numel() == 0:
|
| 561 |
+
continue
|
| 562 |
+
h = F.silu(expert_tokens @ self.experts.w1[expert_idx].transpose(-2, -1))
|
| 563 |
+
h = h * (expert_tokens @ self.experts.w3[expert_idx].transpose(-2, -1))
|
| 564 |
+
h = h @ self.experts.w2[expert_idx].transpose(-2, -1)
|
| 565 |
+
outputs.append(h)
|
| 566 |
+
if not outputs:
|
| 567 |
+
return tokens.new_zeros(tokens.shape)
|
| 568 |
+
return torch.cat(outputs, dim=0)
|
| 569 |
+
|
| 570 |
+
def _get_sglang_w13(self) -> torch.Tensor:
|
| 571 |
+
key = (
|
| 572 |
+
self.experts.w1.data_ptr(),
|
| 573 |
+
self.experts.w3.data_ptr(),
|
| 574 |
+
self.experts.w1.device,
|
| 575 |
+
self.experts.w3.device,
|
| 576 |
+
self.experts.w1.dtype,
|
| 577 |
+
self.experts.w3.dtype,
|
| 578 |
+
)
|
| 579 |
+
if self._sglang_w13_cache is None or self._sglang_w13_cache_key != key:
|
| 580 |
+
self._sglang_w13_cache = torch.cat(
|
| 581 |
+
(self.experts.w1.bfloat16(), self.experts.w3.bfloat16()), dim=1
|
| 582 |
+
).contiguous()
|
| 583 |
+
self._sglang_w13_cache_key = key
|
| 584 |
+
return self._sglang_w13_cache
|
| 585 |
+
|
| 586 |
+
@staticmethod
|
| 587 |
+
def _quantize_fp8_weight_per_expert(weight: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 588 |
+
weight_float = weight.float()
|
| 589 |
+
scale = fp8_scale_from_amax(weight_float.abs().amax(dim=(1, 2)))
|
| 590 |
+
quantized = quantize_to_fp8_e4m3fn(weight_float, scale[:, None, None]).contiguous()
|
| 591 |
+
return quantized, scale.contiguous()
|
| 592 |
+
|
| 593 |
+
def _get_sglang_fp8_weights(self) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 594 |
+
key = (
|
| 595 |
+
self.experts.w1.data_ptr(),
|
| 596 |
+
self.experts.w2.data_ptr(),
|
| 597 |
+
self.experts.w3.data_ptr(),
|
| 598 |
+
self.experts.w1.device,
|
| 599 |
+
self.experts.w2.device,
|
| 600 |
+
self.experts.w3.device,
|
| 601 |
+
self.experts.w1.dtype,
|
| 602 |
+
self.experts.w2.dtype,
|
| 603 |
+
self.experts.w3.dtype,
|
| 604 |
+
)
|
| 605 |
+
if self._sglang_fp8_cache is None or self._sglang_fp8_cache_key != key:
|
| 606 |
+
w13 = torch.cat((self.experts.w1.float(), self.experts.w3.float()), dim=1).contiguous()
|
| 607 |
+
w13_fp8, w13_scale = self._quantize_fp8_weight_per_expert(w13)
|
| 608 |
+
w2_fp8, w2_scale = self._quantize_fp8_weight_per_expert(self.experts.w2)
|
| 609 |
+
self._sglang_fp8_cache = (w13_fp8, w2_fp8, w13_scale, w2_scale)
|
| 610 |
+
self._sglang_fp8_cache_key = key
|
| 611 |
+
return self._sglang_fp8_cache
|
| 612 |
+
|
| 613 |
+
def _run_sglang_triton_experts(
|
| 614 |
+
self,
|
| 615 |
+
tokens: torch.Tensor,
|
| 616 |
+
top_scores: torch.Tensor,
|
| 617 |
+
top_indices: torch.Tensor,
|
| 618 |
+
) -> torch.Tensor:
|
| 619 |
+
ensure_sglang_moe_ready()
|
| 620 |
+
topk_output = LightSglangStandardTopKOutput(
|
| 621 |
+
top_scores.float(),
|
| 622 |
+
top_indices.to(torch.int32),
|
| 623 |
+
torch.empty(0, device=tokens.device),
|
| 624 |
+
)
|
| 625 |
+
runner_config = LightSglangMoeRunnerConfig(
|
| 626 |
+
num_experts=self.num_experts,
|
| 627 |
+
num_local_experts=self.num_experts,
|
| 628 |
+
activation="silu",
|
| 629 |
+
is_gated=True,
|
| 630 |
+
inplace=False,
|
| 631 |
+
)
|
| 632 |
+
return sglang_fused_experts(
|
| 633 |
+
tokens.contiguous().bfloat16(),
|
| 634 |
+
self._get_sglang_w13(),
|
| 635 |
+
self.experts.w2.bfloat16().contiguous(),
|
| 636 |
+
topk_output,
|
| 637 |
+
runner_config,
|
| 638 |
+
).type_as(tokens)
|
| 639 |
+
|
| 640 |
+
def _run_sglang_triton_fp8_experts(
|
| 641 |
+
self,
|
| 642 |
+
tokens: torch.Tensor,
|
| 643 |
+
top_scores: torch.Tensor,
|
| 644 |
+
top_indices: torch.Tensor,
|
| 645 |
+
) -> torch.Tensor:
|
| 646 |
+
ensure_sglang_moe_ready()
|
| 647 |
+
topk_output = LightSglangStandardTopKOutput(
|
| 648 |
+
top_scores.float(),
|
| 649 |
+
top_indices.to(torch.int32),
|
| 650 |
+
torch.empty(0, device=tokens.device),
|
| 651 |
+
)
|
| 652 |
+
runner_config = LightSglangMoeRunnerConfig(
|
| 653 |
+
num_experts=self.num_experts,
|
| 654 |
+
num_local_experts=self.num_experts,
|
| 655 |
+
activation="silu",
|
| 656 |
+
is_gated=True,
|
| 657 |
+
inplace=False,
|
| 658 |
+
)
|
| 659 |
+
w13_fp8, w2_fp8, w13_scale, w2_scale = self._get_sglang_fp8_weights()
|
| 660 |
+
return sglang_fused_experts(
|
| 661 |
+
tokens.contiguous().bfloat16(),
|
| 662 |
+
w13_fp8,
|
| 663 |
+
w2_fp8,
|
| 664 |
+
topk_output,
|
| 665 |
+
runner_config,
|
| 666 |
+
use_fp8_w8a8=True,
|
| 667 |
+
w1_scale=w13_scale,
|
| 668 |
+
w2_scale=w2_scale,
|
| 669 |
+
).type_as(tokens)
|
| 670 |
+
|
| 671 |
+
def _run_selected_experts(
|
| 672 |
+
self,
|
| 673 |
+
tokens: torch.Tensor,
|
| 674 |
+
top_scores: torch.Tensor,
|
| 675 |
+
top_indices: torch.Tensor,
|
| 676 |
+
) -> torch.Tensor:
|
| 677 |
+
backend = _moe_expert_backend()
|
| 678 |
+
if backend in {"grouped_mm", "torch_grouped_mm", "default"}:
|
| 679 |
+
(
|
| 680 |
+
permuted_tokens,
|
| 681 |
+
counts,
|
| 682 |
+
sorted_positions,
|
| 683 |
+
sorted_scores,
|
| 684 |
+
num_tokens,
|
| 685 |
+
top_k,
|
| 686 |
+
) = self._reorder_tokens(tokens, top_scores, top_indices, self.router.num_experts)
|
| 687 |
+
expert_output = self._run_grouped_experts(permuted_tokens, counts)
|
| 688 |
+
return self._restore_tokens(
|
| 689 |
+
expert_output,
|
| 690 |
+
sorted_positions,
|
| 691 |
+
sorted_scores,
|
| 692 |
+
num_tokens,
|
| 693 |
+
top_k,
|
| 694 |
+
)
|
| 695 |
+
if backend in {"sglang_triton", "triton", "sglang"}:
|
| 696 |
+
return self._run_sglang_triton_experts(tokens, top_scores, top_indices)
|
| 697 |
+
if backend in {"sglang_triton_fp8", "triton_fp8", "sglang_fp8"}:
|
| 698 |
+
return self._run_sglang_triton_fp8_experts(tokens, top_scores, top_indices)
|
| 699 |
+
raise ValueError(
|
| 700 |
+
f"Unsupported LINGBOT_MOE_EXPERT_BACKEND={backend!r}; "
|
| 701 |
+
"expected grouped_mm, sglang_triton, or sglang_triton_fp8"
|
| 702 |
+
)
|
| 703 |
+
|
| 704 |
+
@staticmethod
|
| 705 |
+
def _restore_tokens(
|
| 706 |
+
expert_output: torch.Tensor,
|
| 707 |
+
sorted_positions: torch.Tensor,
|
| 708 |
+
sorted_scores: torch.Tensor,
|
| 709 |
+
num_tokens: int,
|
| 710 |
+
top_k: int,
|
| 711 |
+
) -> torch.Tensor:
|
| 712 |
+
backend = _moe_restore_backend()
|
| 713 |
+
if backend in {"triton", "triton_fused", "fused"}:
|
| 714 |
+
return LingBotVideoSparseMoeBlock._restore_tokens_triton(
|
| 715 |
+
expert_output,
|
| 716 |
+
sorted_positions,
|
| 717 |
+
sorted_scores,
|
| 718 |
+
num_tokens,
|
| 719 |
+
top_k,
|
| 720 |
+
)
|
| 721 |
+
if backend in {"index_add", "index_add_", "scatter_add"}:
|
| 722 |
+
return LingBotVideoSparseMoeBlock._restore_tokens_index_add(
|
| 723 |
+
expert_output,
|
| 724 |
+
sorted_positions,
|
| 725 |
+
sorted_scores,
|
| 726 |
+
num_tokens,
|
| 727 |
+
top_k,
|
| 728 |
+
)
|
| 729 |
+
if backend in {"weighted_scatter", "weighted", "fast_scatter"}:
|
| 730 |
+
return LingBotVideoSparseMoeBlock._restore_tokens_weighted_scatter(
|
| 731 |
+
expert_output,
|
| 732 |
+
sorted_positions,
|
| 733 |
+
sorted_scores,
|
| 734 |
+
num_tokens,
|
| 735 |
+
top_k,
|
| 736 |
+
)
|
| 737 |
+
if backend in {"chunked", "chunked_scatter", "scatter_chunked"}:
|
| 738 |
+
return LingBotVideoSparseMoeBlock._restore_tokens_chunked_scatter(
|
| 739 |
+
expert_output,
|
| 740 |
+
sorted_positions,
|
| 741 |
+
sorted_scores,
|
| 742 |
+
num_tokens,
|
| 743 |
+
top_k,
|
| 744 |
+
)
|
| 745 |
+
if backend not in {"scatter", "default"}:
|
| 746 |
+
raise ValueError(
|
| 747 |
+
f"Unsupported LINGBOT_MOE_RESTORE_BACKEND={backend!r}; "
|
| 748 |
+
"expected scatter, chunked_scatter, weighted_scatter, index_add, or triton"
|
| 749 |
+
)
|
| 750 |
+
dim = expert_output.shape[-1]
|
| 751 |
+
unsorted = torch.zeros(
|
| 752 |
+
(num_tokens * top_k, dim),
|
| 753 |
+
dtype=expert_output.dtype,
|
| 754 |
+
device=expert_output.device,
|
| 755 |
+
)
|
| 756 |
+
unsorted[sorted_positions] = expert_output
|
| 757 |
+
unsorted = unsorted.reshape(num_tokens, top_k, dim)
|
| 758 |
+
|
| 759 |
+
scores_unsorted = torch.zeros(
|
| 760 |
+
num_tokens * top_k,
|
| 761 |
+
dtype=sorted_scores.dtype,
|
| 762 |
+
device=sorted_scores.device,
|
| 763 |
+
)
|
| 764 |
+
scores_unsorted[sorted_positions] = sorted_scores
|
| 765 |
+
scores_unsorted = scores_unsorted.reshape(num_tokens, top_k, 1)
|
| 766 |
+
return (unsorted.float() * scores_unsorted).sum(dim=1).to(expert_output.dtype)
|
| 767 |
+
|
| 768 |
+
@staticmethod
|
| 769 |
+
def _restore_tokens_chunked_scatter(
|
| 770 |
+
expert_output: torch.Tensor,
|
| 771 |
+
sorted_positions: torch.Tensor,
|
| 772 |
+
sorted_scores: torch.Tensor,
|
| 773 |
+
num_tokens: int,
|
| 774 |
+
top_k: int,
|
| 775 |
+
) -> torch.Tensor:
|
| 776 |
+
dim = expert_output.shape[-1]
|
| 777 |
+
chunk_size = int(os.environ.get("LINGBOT_MOE_RESTORE_CHUNK_SIZE", "128"))
|
| 778 |
+
if chunk_size <= 0:
|
| 779 |
+
raise ValueError("LINGBOT_MOE_RESTORE_CHUNK_SIZE must be positive")
|
| 780 |
+
|
| 781 |
+
scores_unsorted = torch.zeros(
|
| 782 |
+
num_tokens * top_k,
|
| 783 |
+
dtype=sorted_scores.dtype,
|
| 784 |
+
device=sorted_scores.device,
|
| 785 |
+
)
|
| 786 |
+
scores_unsorted[sorted_positions] = sorted_scores
|
| 787 |
+
scores_unsorted = scores_unsorted.reshape(num_tokens, top_k, 1)
|
| 788 |
+
output = expert_output.new_empty((num_tokens, dim))
|
| 789 |
+
for start in range(0, dim, chunk_size):
|
| 790 |
+
end = min(start + chunk_size, dim)
|
| 791 |
+
unsorted = torch.zeros(
|
| 792 |
+
(num_tokens * top_k, end - start),
|
| 793 |
+
dtype=expert_output.dtype,
|
| 794 |
+
device=expert_output.device,
|
| 795 |
+
)
|
| 796 |
+
unsorted[sorted_positions] = expert_output[:, start:end]
|
| 797 |
+
unsorted = unsorted.reshape(num_tokens, top_k, end - start)
|
| 798 |
+
output[:, start:end] = (unsorted.float() * scores_unsorted).sum(dim=1).to(
|
| 799 |
+
expert_output.dtype
|
| 800 |
+
)
|
| 801 |
+
return output
|
| 802 |
+
|
| 803 |
+
@staticmethod
|
| 804 |
+
def _restore_tokens_triton(
|
| 805 |
+
expert_output: torch.Tensor,
|
| 806 |
+
sorted_positions: torch.Tensor,
|
| 807 |
+
sorted_scores: torch.Tensor,
|
| 808 |
+
num_tokens: int,
|
| 809 |
+
top_k: int,
|
| 810 |
+
) -> torch.Tensor:
|
| 811 |
+
return restore_tokens_triton(
|
| 812 |
+
expert_output,
|
| 813 |
+
sorted_positions,
|
| 814 |
+
sorted_scores,
|
| 815 |
+
num_tokens,
|
| 816 |
+
top_k,
|
| 817 |
+
)
|
| 818 |
+
|
| 819 |
+
@staticmethod
|
| 820 |
+
def _restore_tokens_weighted_scatter(
|
| 821 |
+
expert_output: torch.Tensor,
|
| 822 |
+
sorted_positions: torch.Tensor,
|
| 823 |
+
sorted_scores: torch.Tensor,
|
| 824 |
+
num_tokens: int,
|
| 825 |
+
top_k: int,
|
| 826 |
+
) -> torch.Tensor:
|
| 827 |
+
weighted = (expert_output * sorted_scores[:, None].to(expert_output.dtype)).to(expert_output.dtype)
|
| 828 |
+
unsorted = torch.zeros(
|
| 829 |
+
(num_tokens * top_k, expert_output.shape[-1]),
|
| 830 |
+
dtype=expert_output.dtype,
|
| 831 |
+
device=expert_output.device,
|
| 832 |
+
)
|
| 833 |
+
unsorted[sorted_positions] = weighted
|
| 834 |
+
return unsorted.reshape(num_tokens, top_k, expert_output.shape[-1]).sum(dim=1)
|
| 835 |
+
|
| 836 |
+
@staticmethod
|
| 837 |
+
def _restore_tokens_index_add(
|
| 838 |
+
expert_output: torch.Tensor,
|
| 839 |
+
sorted_positions: torch.Tensor,
|
| 840 |
+
sorted_scores: torch.Tensor,
|
| 841 |
+
num_tokens: int,
|
| 842 |
+
top_k: int,
|
| 843 |
+
) -> torch.Tensor:
|
| 844 |
+
token_indices = torch.div(sorted_positions, top_k, rounding_mode="floor")
|
| 845 |
+
weighted = expert_output.float() * sorted_scores[:, None].float()
|
| 846 |
+
out = torch.zeros(
|
| 847 |
+
(num_tokens, expert_output.shape[-1]),
|
| 848 |
+
dtype=torch.float32,
|
| 849 |
+
device=expert_output.device,
|
| 850 |
+
)
|
| 851 |
+
out.index_add_(0, token_indices, weighted)
|
| 852 |
+
return out.to(expert_output.dtype)
|
| 853 |
+
|
| 854 |
+
def forward(self, hidden_states: torch.Tensor, padding_mask: Optional[torch.Tensor] = None):
|
| 855 |
+
# hidden_states: (B, S, H); padding_mask: (B*S,) with 1=valid (only needed when B>1)
|
| 856 |
+
B = hidden_states.shape[0]
|
| 857 |
+
tokens = hidden_states.view(-1, self.hidden_size)
|
| 858 |
+
top_indices, top_scores, logits, scores, scores_for_choice = self.router(tokens)
|
| 859 |
+
del logits, scores, scores_for_choice
|
| 860 |
+
if padding_mask is not None:
|
| 861 |
+
pm = padding_mask.unsqueeze(-1).to(top_scores.dtype)
|
| 862 |
+
top_scores = top_scores * pm
|
| 863 |
+
top_scores = top_scores / (top_scores.sum(dim=-1, keepdim=True) + 1e-9)
|
| 864 |
+
top_scores = top_scores * self.router.route_scale
|
| 865 |
+
|
| 866 |
+
out = self._run_selected_experts(tokens, top_scores, top_indices)
|
| 867 |
+
|
| 868 |
+
out = out.view(B, -1, self.hidden_size)
|
| 869 |
+
if self.shared_experts is not None:
|
| 870 |
+
shared_output = self.shared_experts(hidden_states)
|
| 871 |
+
out = out + shared_output
|
| 872 |
+
return out
|
| 873 |
+
|
| 874 |
+
|
| 875 |
+
class LingBotVideoBlock(nn.Module):
|
| 876 |
+
def __init__(
|
| 877 |
+
self,
|
| 878 |
+
hidden_size,
|
| 879 |
+
num_attention_heads,
|
| 880 |
+
intermediate_size,
|
| 881 |
+
norm_eps,
|
| 882 |
+
qkv_bias,
|
| 883 |
+
out_bias,
|
| 884 |
+
num_experts,
|
| 885 |
+
num_experts_per_tok,
|
| 886 |
+
moe_intermediate_size,
|
| 887 |
+
decoder_sparse_step,
|
| 888 |
+
mlp_only_layers,
|
| 889 |
+
n_shared_experts,
|
| 890 |
+
score_func,
|
| 891 |
+
norm_topk_prob,
|
| 892 |
+
n_group,
|
| 893 |
+
topk_group,
|
| 894 |
+
routed_scaling_factor,
|
| 895 |
+
layer_idx: int,
|
| 896 |
+
):
|
| 897 |
+
super().__init__()
|
| 898 |
+
self.layer_idx = layer_idx
|
| 899 |
+
h = hidden_size
|
| 900 |
+
self.scale_shift_table = nn.Parameter(torch.zeros(1, 6 * h))
|
| 901 |
+
self.norm1 = LingBotVideoRMSNorm(h, norm_eps)
|
| 902 |
+
self.attn = LingBotVideoAttention(
|
| 903 |
+
h, num_attention_heads, norm_eps, qkv_bias, out_bias
|
| 904 |
+
)
|
| 905 |
+
self.norm_post_attn = LingBotVideoRMSNorm(h, norm_eps)
|
| 906 |
+
self.norm2 = LingBotVideoRMSNorm(h, norm_eps)
|
| 907 |
+
# Sparsity decision matches MoEBlock: mlp_only_layers + decoder_sparse_step + num_experts
|
| 908 |
+
if layer_idx not in mlp_only_layers and (
|
| 909 |
+
num_experts > 0 and (layer_idx + 1) % decoder_sparse_step == 0
|
| 910 |
+
):
|
| 911 |
+
self.ffn = LingBotVideoSparseMoeBlock(
|
| 912 |
+
h, intermediate_size, num_experts, num_experts_per_tok,
|
| 913 |
+
moe_intermediate_size, score_func, norm_topk_prob,
|
| 914 |
+
n_group, topk_group, routed_scaling_factor,
|
| 915 |
+
n_shared_experts,
|
| 916 |
+
)
|
| 917 |
+
else:
|
| 918 |
+
self.ffn = LingBotVideoMLP(h, intermediate_size)
|
| 919 |
+
self.norm_post_ffn = LingBotVideoRMSNorm(h, norm_eps)
|
| 920 |
+
|
| 921 |
+
def forward(
|
| 922 |
+
self,
|
| 923 |
+
x,
|
| 924 |
+
temb6,
|
| 925 |
+
rotary_emb,
|
| 926 |
+
attention_mask=None,
|
| 927 |
+
moe_padding_mask=None,
|
| 928 |
+
packed_indices: Optional[dict[str, torch.Tensor]] = None,
|
| 929 |
+
parallel_config=None,
|
| 930 |
+
):
|
| 931 |
+
expected_tokens = x.shape[0] * x.shape[1]
|
| 932 |
+
if temb6.ndim != 2 or temb6.shape[0] != expected_tokens:
|
| 933 |
+
raise ValueError(
|
| 934 |
+
"LingBotVideoBlock expects token-level temb6 with shape "
|
| 935 |
+
f"(B*S, 6D); got {tuple(temb6.shape)} for hidden states {tuple(x.shape)}."
|
| 936 |
+
)
|
| 937 |
+
# AdaLN mod: dense and MoE both keep scale_shift_table fp32 (master
|
| 938 |
+
# moe/models.py:80 dropped the accidental `.to(dtype=c.dtype)` cast).
|
| 939 |
+
mod = temb6.view(x.shape[0], x.shape[1], -1) + self.scale_shift_table.unsqueeze(0)
|
| 940 |
+
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = mod.chunk(6, dim=-1)
|
| 941 |
+
gate_msa, gate_mlp = gate_msa.tanh(), gate_mlp.tanh()
|
| 942 |
+
scale_msa, scale_mlp = 1.0 + scale_msa, 1.0 + scale_mlp
|
| 943 |
+
|
| 944 |
+
# AdaLN modulation / norms run in fp32 (sensitive path); cast to the bulk
|
| 945 |
+
# compute dtype only at the bf16 Linear boundary. This replaces the old
|
| 946 |
+
# ambient autocast, which rounded Linear inputs to bf16 at the same point.
|
| 947 |
+
bulk_dtype = self.attn.to_q.weight.dtype
|
| 948 |
+
attn_in = (self.norm1(x) * scale_msa + shift_msa).to(bulk_dtype)
|
| 949 |
+
attn_out = self.attn(
|
| 950 |
+
attn_in,
|
| 951 |
+
rotary_emb,
|
| 952 |
+
attention_mask,
|
| 953 |
+
packed_indices=packed_indices,
|
| 954 |
+
parallel_config=parallel_config,
|
| 955 |
+
)
|
| 956 |
+
x = x + (gate_msa * self.norm_post_attn(attn_out)).to(x.dtype)
|
| 957 |
+
|
| 958 |
+
ffn_in = (self.norm2(x) * scale_mlp + shift_mlp).to(bulk_dtype)
|
| 959 |
+
if isinstance(self.ffn, LingBotVideoSparseMoeBlock):
|
| 960 |
+
ffn_out = self.ffn(ffn_in, padding_mask=moe_padding_mask)
|
| 961 |
+
else:
|
| 962 |
+
ffn_out = self.ffn(ffn_in)
|
| 963 |
+
ffn_normed = self.norm_post_ffn(ffn_out)
|
| 964 |
+
x = x + (gate_mlp * ffn_normed).to(x.dtype)
|
| 965 |
+
return x
|
| 966 |
+
|
| 967 |
+
|
| 968 |
+
class LingBotVideoTransformer3DModel(ModelMixin, ConfigMixin):
|
| 969 |
+
_supports_gradient_checkpointing = False
|
| 970 |
+
_no_split_modules = ["LingBotVideoBlock"]
|
| 971 |
+
_keep_in_fp32_modules = list(LINGBOT_VIDEO_FP32_MODULES)
|
| 972 |
+
|
| 973 |
+
def to(self, *args, **kwargs):
|
| 974 |
+
device, dtype, non_blocking, _ = torch._C._nn._parse_to(*args, **kwargs)
|
| 975 |
+
if dtype is None or dtype == torch.float32:
|
| 976 |
+
return super().to(*args, **kwargs)
|
| 977 |
+
|
| 978 |
+
dtype_is_floating = torch.is_floating_point(torch.empty((), dtype=dtype))
|
| 979 |
+
if not dtype_is_floating:
|
| 980 |
+
return super().to(*args, **kwargs)
|
| 981 |
+
|
| 982 |
+
if device is not None:
|
| 983 |
+
super().to(device=device, non_blocking=non_blocking)
|
| 984 |
+
|
| 985 |
+
for name, param in self.named_parameters():
|
| 986 |
+
if not torch.is_floating_point(param):
|
| 987 |
+
continue
|
| 988 |
+
target_dtype = torch.float32 if should_keep_in_fp32(name) else dtype
|
| 989 |
+
param.data = param.data.to(dtype=target_dtype, non_blocking=non_blocking)
|
| 990 |
+
if param.grad is not None:
|
| 991 |
+
param.grad.data = param.grad.data.to(dtype=target_dtype, non_blocking=non_blocking)
|
| 992 |
+
|
| 993 |
+
for name, buffer in self.named_buffers():
|
| 994 |
+
if not torch.is_floating_point(buffer):
|
| 995 |
+
continue
|
| 996 |
+
target_dtype = torch.float32 if should_keep_in_fp32(name) else dtype
|
| 997 |
+
buffer.data = buffer.data.to(dtype=target_dtype, non_blocking=non_blocking)
|
| 998 |
+
|
| 999 |
+
return self
|
| 1000 |
+
|
| 1001 |
+
@register_to_config
|
| 1002 |
+
def __init__(
|
| 1003 |
+
self,
|
| 1004 |
+
patch_size: Tuple[int, int, int] = (1, 2, 2),
|
| 1005 |
+
in_channels: int = 16,
|
| 1006 |
+
out_channels: int = 16,
|
| 1007 |
+
hidden_size: int = 2048,
|
| 1008 |
+
num_attention_heads: int = 16,
|
| 1009 |
+
depth: int = 24,
|
| 1010 |
+
intermediate_size: int = 6144,
|
| 1011 |
+
text_dim: int = 2560,
|
| 1012 |
+
freq_dim: int = 256,
|
| 1013 |
+
norm_eps: float = 1e-6,
|
| 1014 |
+
rope_theta: float = 256.0,
|
| 1015 |
+
axes_dims: Tuple[int, int, int] = (32, 48, 48),
|
| 1016 |
+
axes_lens: Tuple[int, int, int] = (8192, 1024, 1024),
|
| 1017 |
+
qkv_bias: bool = False,
|
| 1018 |
+
out_bias: bool = True,
|
| 1019 |
+
patch_embed_bias: bool = True,
|
| 1020 |
+
timestep_mlp_bias: bool = True,
|
| 1021 |
+
num_experts: int = 0,
|
| 1022 |
+
num_experts_per_tok: int = 8,
|
| 1023 |
+
moe_intermediate_size: int = 512,
|
| 1024 |
+
decoder_sparse_step: int = 1,
|
| 1025 |
+
mlp_only_layers: Tuple[int, ...] = (),
|
| 1026 |
+
n_shared_experts: Optional[int] = None,
|
| 1027 |
+
score_func: str = "sigmoid",
|
| 1028 |
+
norm_topk_prob: bool = True,
|
| 1029 |
+
n_group: Optional[int] = None,
|
| 1030 |
+
topk_group: Optional[int] = None,
|
| 1031 |
+
routed_scaling_factor: float = 1.0,
|
| 1032 |
+
):
|
| 1033 |
+
super().__init__()
|
| 1034 |
+
head_dim = hidden_size // num_attention_heads
|
| 1035 |
+
assert head_dim == sum(axes_dims), f"head_dim {head_dim} != sum(axes_dims) {sum(axes_dims)}"
|
| 1036 |
+
mlp_only_layers = tuple(mlp_only_layers)
|
| 1037 |
+
|
| 1038 |
+
self.patch_embedder = nn.Linear(
|
| 1039 |
+
in_channels * math.prod(patch_size), hidden_size, bias=patch_embed_bias
|
| 1040 |
+
)
|
| 1041 |
+
self.time_proj = Timesteps(freq_dim, flip_sin_to_cos=True, downscale_freq_shift=0)
|
| 1042 |
+
self.time_embedder = TimestepEmbedding(
|
| 1043 |
+
freq_dim, hidden_size, act_fn="silu", sample_proj_bias=timestep_mlp_bias
|
| 1044 |
+
)
|
| 1045 |
+
self.time_modulation = nn.Sequential(nn.SiLU(), nn.Linear(hidden_size, 6 * hidden_size))
|
| 1046 |
+
self.text_embedder = LingBotVideoTextEmbedder(text_dim, hidden_size)
|
| 1047 |
+
self.rope = LingBotVideoRotaryEmbedding(axes_dims, axes_lens, rope_theta)
|
| 1048 |
+
self.blocks = nn.ModuleList(
|
| 1049 |
+
[
|
| 1050 |
+
LingBotVideoBlock(
|
| 1051 |
+
hidden_size=hidden_size,
|
| 1052 |
+
num_attention_heads=num_attention_heads,
|
| 1053 |
+
intermediate_size=intermediate_size,
|
| 1054 |
+
norm_eps=norm_eps,
|
| 1055 |
+
qkv_bias=qkv_bias,
|
| 1056 |
+
out_bias=out_bias,
|
| 1057 |
+
num_experts=num_experts,
|
| 1058 |
+
num_experts_per_tok=num_experts_per_tok,
|
| 1059 |
+
moe_intermediate_size=moe_intermediate_size,
|
| 1060 |
+
decoder_sparse_step=decoder_sparse_step,
|
| 1061 |
+
mlp_only_layers=mlp_only_layers,
|
| 1062 |
+
n_shared_experts=n_shared_experts,
|
| 1063 |
+
score_func=score_func,
|
| 1064 |
+
norm_topk_prob=norm_topk_prob,
|
| 1065 |
+
n_group=n_group,
|
| 1066 |
+
topk_group=topk_group,
|
| 1067 |
+
routed_scaling_factor=routed_scaling_factor,
|
| 1068 |
+
layer_idx=i,
|
| 1069 |
+
)
|
| 1070 |
+
for i in range(depth)
|
| 1071 |
+
]
|
| 1072 |
+
)
|
| 1073 |
+
self.norm_out = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=norm_eps)
|
| 1074 |
+
self.norm_out_modulation = nn.Sequential(nn.SiLU(), nn.Linear(hidden_size, 2 * hidden_size))
|
| 1075 |
+
self.proj_out = nn.Linear(hidden_size, math.prod(patch_size) * out_channels)
|
| 1076 |
+
self.cp_joint = nn.Identity()
|
| 1077 |
+
self.cp_rotary = nn.Identity()
|
| 1078 |
+
self.cp_temb_input = nn.Identity()
|
| 1079 |
+
self.cp_temb6 = nn.Identity()
|
| 1080 |
+
self.cp_out = nn.Identity()
|
| 1081 |
+
self._cp_plan = {
|
| 1082 |
+
"cp_joint": {
|
| 1083 |
+
"input": ContextParallelInput(split_dim=1, expected_dims=3),
|
| 1084 |
+
},
|
| 1085 |
+
"cp_rotary": {
|
| 1086 |
+
"input": ContextParallelInput(split_dim=1, expected_dims=3),
|
| 1087 |
+
},
|
| 1088 |
+
"cp_temb_input": {
|
| 1089 |
+
"input": ContextParallelInput(split_dim=1, expected_dims=3),
|
| 1090 |
+
},
|
| 1091 |
+
"cp_temb6": {
|
| 1092 |
+
"input": ContextParallelInput(split_dim=1, expected_dims=3),
|
| 1093 |
+
},
|
| 1094 |
+
"cp_out": ContextParallelOutput(gather_dim=1, expected_dims=3),
|
| 1095 |
+
}
|
| 1096 |
+
|
| 1097 |
+
def forward(
|
| 1098 |
+
self,
|
| 1099 |
+
hidden_states: torch.Tensor, # (B, C, T, H, W)
|
| 1100 |
+
timestep: torch.Tensor, # (B,) ∈ [0, 1000](= sigma*1000)
|
| 1101 |
+
encoder_hidden_states: torch.Tensor, # (B, L, text_dim)
|
| 1102 |
+
encoder_attention_mask: Optional[torch.Tensor] = None, # (B, L) 1=valid
|
| 1103 |
+
return_dict: bool = True,
|
| 1104 |
+
):
|
| 1105 |
+
B, C, T, H, W = hidden_states.shape
|
| 1106 |
+
pF, pH, pW = self.config.patch_size
|
| 1107 |
+
gt, gh, gw = T // pF, H // pH, W // pW
|
| 1108 |
+
n_video = gt * gh * gw
|
| 1109 |
+
L = encoder_hidden_states.shape[1]
|
| 1110 |
+
device = hidden_states.device
|
| 1111 |
+
if encoder_attention_mask is not None:
|
| 1112 |
+
text_lens = encoder_attention_mask.sum(dim=-1).long()
|
| 1113 |
+
else:
|
| 1114 |
+
text_lens = torch.full((B,), L, dtype=torch.long, device=device)
|
| 1115 |
+
text_lens_list = [int(v) for v in text_lens.detach().cpu().tolist()]
|
| 1116 |
+
packed_batch = B > 1
|
| 1117 |
+
|
| 1118 |
+
# patchify: token order (f h w), feature order (pf ph pw c) -- matches patchify_and_embed
|
| 1119 |
+
patch_tokens = hidden_states.reshape(B, C, gt, pF, gh, pH, gw, pW)
|
| 1120 |
+
patch_tokens = patch_tokens.permute(0, 2, 4, 6, 3, 5, 7, 1).reshape(
|
| 1121 |
+
B,
|
| 1122 |
+
n_video,
|
| 1123 |
+
pF * pH * pW * C,
|
| 1124 |
+
)
|
| 1125 |
+
if packed_batch:
|
| 1126 |
+
packed_patch_tokens = patch_tokens.reshape(1, B * n_video, -1)
|
| 1127 |
+
x = torch.cat(
|
| 1128 |
+
[self.patch_embedder(patch_tokens[i : i + 1]) for i in range(B)],
|
| 1129 |
+
dim=1,
|
| 1130 |
+
)
|
| 1131 |
+
else:
|
| 1132 |
+
x = self.patch_embedder(patch_tokens)
|
| 1133 |
+
|
| 1134 |
+
if packed_batch:
|
| 1135 |
+
text_parts = [
|
| 1136 |
+
self.text_embedder(encoder_hidden_states[i : i + 1, : text_lens_list[i], :])
|
| 1137 |
+
for i in range(B)
|
| 1138 |
+
]
|
| 1139 |
+
text = torch.cat(text_parts, dim=1)
|
| 1140 |
+
joint = _cat_interleave(
|
| 1141 |
+
x,
|
| 1142 |
+
[n_video] * B,
|
| 1143 |
+
text,
|
| 1144 |
+
text_lens_list,
|
| 1145 |
+
)
|
| 1146 |
+
else:
|
| 1147 |
+
text = self.text_embedder(encoder_hidden_states)
|
| 1148 |
+
joint = torch.cat([x, text], dim=1) # [video; text]
|
| 1149 |
+
joint_seq_len = joint.shape[1]
|
| 1150 |
+
|
| 1151 |
+
# Per-sample RoPE: video t-axis start = real text length of this sample + 1
|
| 1152 |
+
rotary_parts = [
|
| 1153 |
+
self.rope(make_joint_position_ids(text_lens_list[i], gt, gh, gw, device))
|
| 1154 |
+
for i in range(B)
|
| 1155 |
+
]
|
| 1156 |
+
if packed_batch:
|
| 1157 |
+
rotary = torch.cat(rotary_parts, dim=0).unsqueeze(0)
|
| 1158 |
+
else:
|
| 1159 |
+
rotary = torch.stack(rotary_parts, dim=0) # (B, S, head_dim/2) complex64
|
| 1160 |
+
|
| 1161 |
+
parallel_config = getattr(self, "_parallel_config", None)
|
| 1162 |
+
use_packed_attention = parallel_config is not None
|
| 1163 |
+
|
| 1164 |
+
attention_mask = None
|
| 1165 |
+
moe_padding_mask = None
|
| 1166 |
+
packed_indices = None
|
| 1167 |
+
has_padding = encoder_attention_mask is not None and bool((text_lens < L).any())
|
| 1168 |
+
if packed_batch or use_packed_attention:
|
| 1169 |
+
sample_seq_lens = [n_video + text_len for text_len in text_lens_list]
|
| 1170 |
+
cu_seqlens = torch.zeros(B + 1, device=device, dtype=torch.int32)
|
| 1171 |
+
cu_seqlens[1:] = torch.cumsum(
|
| 1172 |
+
torch.tensor(sample_seq_lens, device=device, dtype=torch.int32),
|
| 1173 |
+
dim=0,
|
| 1174 |
+
)
|
| 1175 |
+
packed_indices = {
|
| 1176 |
+
"cu_seqlens_kv": cu_seqlens,
|
| 1177 |
+
"max_seqlen_in_batch_kv": max(sample_seq_lens),
|
| 1178 |
+
}
|
| 1179 |
+
has_padding = False
|
| 1180 |
+
if has_padding:
|
| 1181 |
+
key_mask = torch.cat(
|
| 1182 |
+
[torch.ones(B, n_video, dtype=torch.bool, device=device),
|
| 1183 |
+
encoder_attention_mask.bool()],
|
| 1184 |
+
dim=1,
|
| 1185 |
+
)
|
| 1186 |
+
attention_mask = key_mask[:, None, None, :] # (B,1,1,S) → SDPA broadcast
|
| 1187 |
+
moe_padding_mask = key_mask.reshape(-1).float() # (B*S,)
|
| 1188 |
+
packed_cp = packed_indices is not None and parallel_config is not None
|
| 1189 |
+
padding_size = 0
|
| 1190 |
+
if packed_cp:
|
| 1191 |
+
cp_config = parallel_config.context_parallel_config
|
| 1192 |
+
cp_world_size = int(getattr(cp_config, "ulysses_degree", getattr(cp_config, "_world_size", 1)))
|
| 1193 |
+
padding_size = (cp_world_size - (joint_seq_len % cp_world_size)) % cp_world_size
|
| 1194 |
+
if padding_size:
|
| 1195 |
+
joint = torch.cat(
|
| 1196 |
+
[
|
| 1197 |
+
joint,
|
| 1198 |
+
torch.zeros(
|
| 1199 |
+
joint.shape[0],
|
| 1200 |
+
padding_size,
|
| 1201 |
+
joint.shape[2],
|
| 1202 |
+
device=joint.device,
|
| 1203 |
+
dtype=joint.dtype,
|
| 1204 |
+
),
|
| 1205 |
+
],
|
| 1206 |
+
dim=1,
|
| 1207 |
+
)
|
| 1208 |
+
rotary = torch.cat(
|
| 1209 |
+
[
|
| 1210 |
+
rotary,
|
| 1211 |
+
torch.zeros(
|
| 1212 |
+
rotary.shape[0],
|
| 1213 |
+
padding_size,
|
| 1214 |
+
rotary.shape[2],
|
| 1215 |
+
device=rotary.device,
|
| 1216 |
+
dtype=rotary.dtype,
|
| 1217 |
+
),
|
| 1218 |
+
],
|
| 1219 |
+
dim=1,
|
| 1220 |
+
)
|
| 1221 |
+
if packed_indices is None:
|
| 1222 |
+
raise RuntimeError("packed_indices must be initialized for packed context parallel.")
|
| 1223 |
+
packed_indices["cu_seqlens_kv"] = torch.cat(
|
| 1224 |
+
[
|
| 1225 |
+
packed_indices["cu_seqlens_kv"],
|
| 1226 |
+
packed_indices["cu_seqlens_kv"][-1:] + padding_size,
|
| 1227 |
+
],
|
| 1228 |
+
dim=0,
|
| 1229 |
+
)
|
| 1230 |
+
packed_indices["max_seqlen_in_batch_kv"] = max(
|
| 1231 |
+
int(packed_indices["max_seqlen_in_batch_kv"]),
|
| 1232 |
+
int(padding_size),
|
| 1233 |
+
)
|
| 1234 |
+
joint_seq_len = joint.shape[1]
|
| 1235 |
+
|
| 1236 |
+
timestep_for_embed = timestep.float()
|
| 1237 |
+
timestep_proj = self.time_proj(timestep_for_embed)
|
| 1238 |
+
t_emb = self.time_embedder(timestep_proj) # (B, D)
|
| 1239 |
+
if packed_batch:
|
| 1240 |
+
temb_input = torch.cat(
|
| 1241 |
+
[
|
| 1242 |
+
t_emb[i : i + 1].unsqueeze(1).expand(1, n_video + text_lens_list[i], -1)
|
| 1243 |
+
for i in range(B)
|
| 1244 |
+
],
|
| 1245 |
+
dim=1,
|
| 1246 |
+
)
|
| 1247 |
+
if padding_size:
|
| 1248 |
+
temb_input = torch.cat(
|
| 1249 |
+
[
|
| 1250 |
+
temb_input,
|
| 1251 |
+
torch.zeros(
|
| 1252 |
+
temb_input.shape[0],
|
| 1253 |
+
padding_size,
|
| 1254 |
+
temb_input.shape[2],
|
| 1255 |
+
device=temb_input.device,
|
| 1256 |
+
dtype=temb_input.dtype,
|
| 1257 |
+
),
|
| 1258 |
+
],
|
| 1259 |
+
dim=1,
|
| 1260 |
+
)
|
| 1261 |
+
temb6 = self.time_modulation(temb_input.reshape(joint_seq_len, -1))
|
| 1262 |
+
temb6 = temb6.reshape(1, joint_seq_len, -1)
|
| 1263 |
+
else:
|
| 1264 |
+
temb_input = t_emb.unsqueeze(1).expand(B, joint_seq_len, -1) # (B, S, D)
|
| 1265 |
+
temb6 = self.time_modulation(temb_input.reshape(B * joint_seq_len, -1))
|
| 1266 |
+
temb6 = temb6.reshape(B, joint_seq_len, -1) # (B, S, 6D)
|
| 1267 |
+
|
| 1268 |
+
joint = self.cp_joint(joint)
|
| 1269 |
+
rotary = self.cp_rotary(rotary)
|
| 1270 |
+
if packed_cp:
|
| 1271 |
+
temb_input = self.cp_temb_input(temb_input)
|
| 1272 |
+
temb6 = self.cp_temb6(temb6)
|
| 1273 |
+
temb6 = temb6.reshape(temb6.shape[0] * temb6.shape[1], -1)
|
| 1274 |
+
|
| 1275 |
+
for block in self.blocks:
|
| 1276 |
+
joint = block(
|
| 1277 |
+
joint,
|
| 1278 |
+
temb6,
|
| 1279 |
+
rotary,
|
| 1280 |
+
attention_mask,
|
| 1281 |
+
moe_padding_mask,
|
| 1282 |
+
packed_indices=packed_indices,
|
| 1283 |
+
parallel_config=parallel_config,
|
| 1284 |
+
)
|
| 1285 |
+
if not packed_cp:
|
| 1286 |
+
joint = self.cp_out(joint)
|
| 1287 |
+
|
| 1288 |
+
final_mod = self.norm_out_modulation(temb_input.reshape(joint.shape[0] * joint.shape[1], -1))
|
| 1289 |
+
shift, scale = final_mod.reshape(joint.shape[0], joint.shape[1], -1).chunk(2, dim=-1)
|
| 1290 |
+
final_hidden = self.norm_out(joint) * (1.0 + scale) + shift
|
| 1291 |
+
projected = self.proj_out(final_hidden.to(self.proj_out.weight.dtype))
|
| 1292 |
+
if packed_cp:
|
| 1293 |
+
projected = self.cp_out(projected)
|
| 1294 |
+
if padding_size:
|
| 1295 |
+
projected = projected[:, :-padding_size, :]
|
| 1296 |
+
if packed_batch:
|
| 1297 |
+
split_lengths: list[int] = []
|
| 1298 |
+
for text_len in text_lens_list:
|
| 1299 |
+
split_lengths.extend([n_video, text_len])
|
| 1300 |
+
parts = torch.split(projected, split_lengths, dim=1)
|
| 1301 |
+
x = torch.cat(parts[::2], dim=1).reshape(B, n_video, -1)
|
| 1302 |
+
else:
|
| 1303 |
+
x = projected[:, :n_video]
|
| 1304 |
+
|
| 1305 |
+
# unpatchify (matches the rearrange in postprocess)
|
| 1306 |
+
Cout = self.config.out_channels
|
| 1307 |
+
x = x.reshape(B, gt, gh, gw, pF, pH, pW, Cout)
|
| 1308 |
+
x = x.permute(0, 7, 1, 4, 2, 5, 3, 6).reshape(B, Cout, T, H, W)
|
| 1309 |
+
|
| 1310 |
+
if not return_dict:
|
| 1311 |
+
return (x,)
|
| 1312 |
+
return Transformer2DModelOutput(sample=x)
|
lingbot_video/utils.py
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from typing import Any, Sequence
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
import torch
|
| 9 |
+
import torch.nn.functional as F
|
| 10 |
+
from PIL import Image
|
| 11 |
+
|
| 12 |
+
LOW_NOISE_TAIL_V1_NAME = "low_noise_tail_v1"
|
| 13 |
+
LOW_NOISE_TAIL_V1_DEFAULT_STEPS = 2
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def num_frames_from_duration(duration: float, fps: int) -> int:
|
| 17 |
+
frame_count = int(float(duration) * int(fps))
|
| 18 |
+
return ((frame_count - 1) // 4 + 1) * 4 + 1
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def validate_refiner_sigmas(
|
| 22 |
+
sigmas: Sequence[float] | np.ndarray,
|
| 23 |
+
t_thresh: float | None = None,
|
| 24 |
+
) -> np.ndarray:
|
| 25 |
+
arr = np.asarray(list(sigmas), dtype=np.float64)
|
| 26 |
+
if arr.ndim != 1 or arr.size == 0:
|
| 27 |
+
raise ValueError("refiner sigma schedule must be a non-empty 1D list")
|
| 28 |
+
if not np.all(np.isfinite(arr)):
|
| 29 |
+
raise ValueError("refiner sigma schedule contains non-finite values")
|
| 30 |
+
if np.any(arr < 0.0) or np.any(arr > 1.0):
|
| 31 |
+
raise ValueError(f"refiner sigma schedule values must be in [0, 1], got {arr.tolist()}")
|
| 32 |
+
if arr.size > 1 and not np.all(np.diff(arr) < 0.0):
|
| 33 |
+
raise ValueError(f"refiner sigma schedule must be strictly descending, got {arr.tolist()}")
|
| 34 |
+
if t_thresh is not None and abs(float(arr[0]) - float(t_thresh)) > 1e-6:
|
| 35 |
+
raise ValueError(f"refiner sigma schedule must start at t_thresh={float(t_thresh)}, got {float(arr[0])}")
|
| 36 |
+
return arr
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def compute_refiner_sigmas(
|
| 40 |
+
*,
|
| 41 |
+
sigma_max: float,
|
| 42 |
+
sigma_min: float,
|
| 43 |
+
num_inference_steps: int,
|
| 44 |
+
shift: float,
|
| 45 |
+
t_thresh: float | None,
|
| 46 |
+
tail_steps: int = 0,
|
| 47 |
+
) -> np.ndarray | None:
|
| 48 |
+
if t_thresh is None:
|
| 49 |
+
return None
|
| 50 |
+
t_value = float(t_thresh)
|
| 51 |
+
if not (0.0 < t_value <= 1.0):
|
| 52 |
+
raise ValueError(f"refiner t_thresh must lie in (0, 1], got {t_value}")
|
| 53 |
+
steps = int(num_inference_steps)
|
| 54 |
+
if steps < 1:
|
| 55 |
+
raise ValueError(f"num_inference_steps must be >= 1, got {steps}")
|
| 56 |
+
tail = int(tail_steps or 0)
|
| 57 |
+
if tail < 0:
|
| 58 |
+
raise ValueError(f"refiner_sigma_tail_steps must be >= 0, got {tail}")
|
| 59 |
+
|
| 60 |
+
base = np.linspace(float(sigma_max), float(sigma_min), steps + 1).copy()[:-1]
|
| 61 |
+
shift_value = float(shift)
|
| 62 |
+
shifted = shift_value * base / (1.0 + (shift_value - 1.0) * base)
|
| 63 |
+
eps = 1e-6
|
| 64 |
+
sigmas = shifted[shifted <= t_value + eps]
|
| 65 |
+
if sigmas.size == 0 or abs(float(sigmas[0]) - t_value) > eps:
|
| 66 |
+
sigmas = np.concatenate([[t_value], sigmas])
|
| 67 |
+
if tail > 0:
|
| 68 |
+
start = float(sigmas[-1])
|
| 69 |
+
stop = min(float(sigma_min), start)
|
| 70 |
+
extra = np.linspace(start, stop, tail + 2, dtype=np.float64)[1:-1]
|
| 71 |
+
sigmas = np.concatenate([sigmas, extra])
|
| 72 |
+
return validate_refiner_sigmas(sigmas, t_value).astype(np.float32)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def prepare_refiner_latent(
|
| 76 |
+
x_up: torch.Tensor,
|
| 77 |
+
noise: torch.Tensor,
|
| 78 |
+
t_thresh: float | torch.Tensor,
|
| 79 |
+
) -> torch.Tensor:
|
| 80 |
+
if not torch.is_tensor(t_thresh):
|
| 81 |
+
t_thresh = torch.tensor(float(t_thresh), device=x_up.device, dtype=x_up.dtype)
|
| 82 |
+
while t_thresh.ndim < x_up.ndim:
|
| 83 |
+
t_thresh = t_thresh.view(*t_thresh.shape, *([1] * (x_up.ndim - t_thresh.ndim)))
|
| 84 |
+
return (1.0 - t_thresh) * x_up + t_thresh * noise
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def _pad_prompt_embeds(
|
| 88 |
+
embeds: torch.Tensor,
|
| 89 |
+
mask: torch.Tensor,
|
| 90 |
+
target_length: int,
|
| 91 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 92 |
+
if embeds.ndim != 3:
|
| 93 |
+
raise ValueError(f"prompt embeds must be rank-3, got {tuple(embeds.shape)}")
|
| 94 |
+
if mask.ndim != 2:
|
| 95 |
+
raise ValueError(f"prompt mask must be rank-2, got {tuple(mask.shape)}")
|
| 96 |
+
if embeds.shape[:2] != mask.shape:
|
| 97 |
+
raise ValueError(
|
| 98 |
+
f"prompt embeds/mask shape mismatch: {tuple(embeds.shape)} vs {tuple(mask.shape)}"
|
| 99 |
+
)
|
| 100 |
+
if embeds.shape[0] != 1:
|
| 101 |
+
raise ValueError(f"batched CFG helper expects batch=1 inputs, got {embeds.shape[0]}")
|
| 102 |
+
if embeds.shape[1] > target_length:
|
| 103 |
+
raise ValueError(f"cannot pad length {embeds.shape[1]} down to {target_length}")
|
| 104 |
+
pad_len = target_length - embeds.shape[1]
|
| 105 |
+
if pad_len == 0:
|
| 106 |
+
return embeds, mask
|
| 107 |
+
embed_pad = torch.zeros(
|
| 108 |
+
embeds.shape[0],
|
| 109 |
+
pad_len,
|
| 110 |
+
embeds.shape[2],
|
| 111 |
+
dtype=embeds.dtype,
|
| 112 |
+
device=embeds.device,
|
| 113 |
+
)
|
| 114 |
+
mask_pad = torch.zeros(mask.shape[0], pad_len, dtype=mask.dtype, device=mask.device)
|
| 115 |
+
return torch.cat([embeds, embed_pad], dim=1), torch.cat([mask, mask_pad], dim=1)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def batch_cfg_prompt_inputs(
|
| 119 |
+
prompt_embeds: torch.Tensor,
|
| 120 |
+
prompt_mask: torch.Tensor,
|
| 121 |
+
negative_embeds: torch.Tensor,
|
| 122 |
+
negative_mask: torch.Tensor,
|
| 123 |
+
*,
|
| 124 |
+
null_cond_clone_zero: bool = False,
|
| 125 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 126 |
+
if null_cond_clone_zero:
|
| 127 |
+
zero_negative = torch.zeros_like(prompt_embeds)
|
| 128 |
+
return (
|
| 129 |
+
torch.cat([prompt_embeds, zero_negative], dim=0),
|
| 130 |
+
torch.cat([prompt_mask, prompt_mask.clone()], dim=0),
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
target_length = max(int(prompt_embeds.shape[1]), int(negative_embeds.shape[1]))
|
| 134 |
+
prompt_padded, prompt_mask_padded = _pad_prompt_embeds(
|
| 135 |
+
prompt_embeds, prompt_mask, target_length
|
| 136 |
+
)
|
| 137 |
+
negative_padded, negative_mask_padded = _pad_prompt_embeds(
|
| 138 |
+
negative_embeds, negative_mask, target_length
|
| 139 |
+
)
|
| 140 |
+
return (
|
| 141 |
+
torch.cat([prompt_padded, negative_padded], dim=0),
|
| 142 |
+
torch.cat([prompt_mask_padded, negative_mask_padded], dim=0),
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def caption_from_sample(sample: dict[str, Any]) -> str:
|
| 147 |
+
if "caption" in sample:
|
| 148 |
+
caption = sample["caption"]
|
| 149 |
+
else:
|
| 150 |
+
runtime_keys = {
|
| 151 |
+
"duration",
|
| 152 |
+
"fps",
|
| 153 |
+
"height",
|
| 154 |
+
"width",
|
| 155 |
+
"num_frames",
|
| 156 |
+
"resolution",
|
| 157 |
+
"ratio",
|
| 158 |
+
}
|
| 159 |
+
caption = {key: value for key, value in sample.items() if key not in runtime_keys}
|
| 160 |
+
if isinstance(caption, (dict, list)):
|
| 161 |
+
return json.dumps(caption, ensure_ascii=False, separators=(",", ":"))
|
| 162 |
+
return str(caption)
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def compute_training_frame_budget(
|
| 166 |
+
num_source_frames: int,
|
| 167 |
+
source_fps: float,
|
| 168 |
+
sample_fps: int = 24,
|
| 169 |
+
vae_tc: int = 4,
|
| 170 |
+
) -> tuple[int, float, int]:
|
| 171 |
+
if num_source_frames <= 0:
|
| 172 |
+
return 1, 0.0, 1
|
| 173 |
+
if source_fps > sample_fps:
|
| 174 |
+
raw_val = int(num_source_frames / source_fps * sample_fps)
|
| 175 |
+
else:
|
| 176 |
+
raw_val = int(num_source_frames)
|
| 177 |
+
sample_frame = ((raw_val - 1) // vae_tc) * vae_tc + 1
|
| 178 |
+
sample_frame = max(sample_frame, 1)
|
| 179 |
+
vae_fps = sample_frame / num_source_frames * float(source_fps)
|
| 180 |
+
t_vae = (sample_frame - 1) // vae_tc + 1
|
| 181 |
+
return int(sample_frame), float(vae_fps), int(t_vae)
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
def compute_training_aligned_indices(
|
| 185 |
+
num_source_frames: int,
|
| 186 |
+
sample_frame: int,
|
| 187 |
+
) -> np.ndarray:
|
| 188 |
+
if sample_frame <= 0:
|
| 189 |
+
return np.zeros(0, dtype=int)
|
| 190 |
+
if num_source_frames <= 0:
|
| 191 |
+
return np.zeros(sample_frame, dtype=int)
|
| 192 |
+
if num_source_frames >= sample_frame:
|
| 193 |
+
return np.linspace(0, num_source_frames - 1, sample_frame, dtype=int)
|
| 194 |
+
head = np.arange(num_source_frames, dtype=int)
|
| 195 |
+
pad = np.full(sample_frame - num_source_frames, num_source_frames - 1, dtype=int)
|
| 196 |
+
return np.concatenate([head, pad])
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def resize_video_tensor(video: torch.Tensor, height: int, width: int) -> torch.Tensor:
|
| 200 |
+
if video.ndim != 5:
|
| 201 |
+
raise ValueError(f"video tensor must have shape [B,C,T,H,W], got {tuple(video.shape)}")
|
| 202 |
+
bsz, channels, frames, _height, _width = video.shape
|
| 203 |
+
flat = video.permute(0, 2, 1, 3, 4).reshape(bsz * frames, channels, _height, _width)
|
| 204 |
+
resized = F.interpolate(flat, size=(height, width), mode="bicubic", align_corners=False)
|
| 205 |
+
resized = resized.clamp(0.0, 1.0)
|
| 206 |
+
return resized.reshape(bsz, frames, channels, height, width).permute(0, 2, 1, 3, 4).contiguous()
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def load_refiner_video_tensor(
|
| 210 |
+
path: str | Path,
|
| 211 |
+
height: int,
|
| 212 |
+
width: int,
|
| 213 |
+
*,
|
| 214 |
+
sample_fps: int = 24,
|
| 215 |
+
vae_tc: int = 4,
|
| 216 |
+
max_frames: int | None = None,
|
| 217 |
+
) -> tuple[torch.Tensor, dict[str, Any]]:
|
| 218 |
+
from decord import VideoReader, cpu
|
| 219 |
+
|
| 220 |
+
vr = VideoReader(str(path), ctx=cpu(0))
|
| 221 |
+
total = len(vr)
|
| 222 |
+
if total <= 0:
|
| 223 |
+
raise ValueError(f"Video has no frames: {path}")
|
| 224 |
+
src_fps = float(vr.get_avg_fps())
|
| 225 |
+
sample_frame, vae_fps, t_vae = compute_training_frame_budget(
|
| 226 |
+
total,
|
| 227 |
+
src_fps,
|
| 228 |
+
sample_fps=sample_fps,
|
| 229 |
+
vae_tc=vae_tc,
|
| 230 |
+
)
|
| 231 |
+
sample_frame_uncapped = int(sample_frame)
|
| 232 |
+
truncated = False
|
| 233 |
+
if max_frames is not None and sample_frame > int(max_frames):
|
| 234 |
+
sample_frame = int(max_frames)
|
| 235 |
+
truncated = True
|
| 236 |
+
vae_fps = float(sample_frame) / max(total, 1) * src_fps
|
| 237 |
+
t_vae = (sample_frame - 1) // vae_tc + 1
|
| 238 |
+
indices = compute_training_aligned_indices(total, sample_frame)
|
| 239 |
+
frames = torch.from_numpy(vr.get_batch(indices).asnumpy()).permute(0, 3, 1, 2).float()
|
| 240 |
+
frames = frames / 255.0
|
| 241 |
+
video = frames.permute(1, 0, 2, 3).unsqueeze(0).contiguous()
|
| 242 |
+
video = resize_video_tensor(video, height=height, width=width)
|
| 243 |
+
meta = {
|
| 244 |
+
"src_fps": float(src_fps),
|
| 245 |
+
"sample_frame": int(sample_frame),
|
| 246 |
+
"sample_frame_uncapped": int(sample_frame_uncapped),
|
| 247 |
+
"max_frames": None if max_frames is None else int(max_frames),
|
| 248 |
+
"truncated_by_max_frames": bool(truncated),
|
| 249 |
+
"vae_fps": float(vae_fps),
|
| 250 |
+
"t_vae": int(t_vae),
|
| 251 |
+
"num_source_frames": int(total),
|
| 252 |
+
"align_to_training": True,
|
| 253 |
+
}
|
| 254 |
+
return video, meta
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
def load_first_frame_condition_tensor(
|
| 258 |
+
path: str | Path,
|
| 259 |
+
target_height: int,
|
| 260 |
+
target_width: int,
|
| 261 |
+
geometry_height: int,
|
| 262 |
+
geometry_width: int,
|
| 263 |
+
) -> torch.Tensor:
|
| 264 |
+
"""Load the clean first frame center-cropped to the lowres video's aspect.
|
| 265 |
+
|
| 266 |
+
The refiner latent grid is encoded from the lowres video, so the injected
|
| 267 |
+
frame-0 condition must share that video's geometry, not the raw image's.
|
| 268 |
+
"""
|
| 269 |
+
image = Image.open(path).convert("RGB")
|
| 270 |
+
image_width, image_height = image.size
|
| 271 |
+
geometry_aspect = float(geometry_width) / float(geometry_height)
|
| 272 |
+
image_aspect = float(image_width) / float(image_height)
|
| 273 |
+
if image_aspect > geometry_aspect:
|
| 274 |
+
crop_height = image_height
|
| 275 |
+
crop_width = max(1, int(round(crop_height * geometry_aspect)))
|
| 276 |
+
left = int(round((image_width - crop_width) / 2.0))
|
| 277 |
+
top = 0
|
| 278 |
+
else:
|
| 279 |
+
crop_width = image_width
|
| 280 |
+
crop_height = max(1, int(round(crop_width / geometry_aspect)))
|
| 281 |
+
left = 0
|
| 282 |
+
top = int(round((image_height - crop_height) / 2.0))
|
| 283 |
+
crop = image.crop((left, top, left + crop_width, top + crop_height))
|
| 284 |
+
crop = crop.resize((target_width, target_height), resample=Image.BICUBIC)
|
| 285 |
+
arr = np.asarray(crop, dtype=np.float32) / 255.0
|
| 286 |
+
frame = torch.from_numpy(arr).permute(2, 0, 1).unsqueeze(0).contiguous()
|
| 287 |
+
return frame.permute(1, 0, 2, 3).unsqueeze(0)
|
requirements.txt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
spaces>=0.41
|
| 2 |
+
diffusers==0.39.0
|
| 3 |
+
transformers==5.8.1
|
| 4 |
+
accelerate>=1.10
|
| 5 |
+
safetensors>=0.4.5
|
| 6 |
+
torchvision
|
| 7 |
+
imageio>=2.35.0
|
| 8 |
+
imageio-ffmpeg>=0.5.1
|
| 9 |
+
numpy>=1.26
|
| 10 |
+
pillow>=10.4.0
|
| 11 |
+
scipy>=1.11
|