duyduytran98 commited on
Commit
de39e5e
·
1 Parent(s): 744e6a2

Add application file

Browse files
Files changed (2) hide show
  1. app.py +315 -0
  2. requirements.txt +9 -0
app.py ADDED
@@ -0,0 +1,315 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import subprocess
3
+ import sys
4
+
5
+ # Disable torch.compile / dynamo before any torch import
6
+ os.environ["TORCH_COMPILE_DISABLE"] = "1"
7
+ os.environ["TORCHDYNAMO_DISABLE"] = "1"
8
+
9
+ # Install xformers for memory-efficient attention
10
+ subprocess.run([sys.executable, "-m", "pip", "install", "xformers==0.0.32.post2", "--no-build-isolation"], check=False)
11
+
12
+ # Clone LTX-2 repo and install packages
13
+ LTX_REPO_URL = "https://github.com/Lightricks/LTX-2.git"
14
+ LTX_REPO_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "LTX-2")
15
+
16
+ if not os.path.exists(LTX_REPO_DIR):
17
+ print(f"Cloning {LTX_REPO_URL}...")
18
+ subprocess.run(["git", "clone", "--depth", "1", LTX_REPO_URL, LTX_REPO_DIR], check=True)
19
+
20
+ print("Installing ltx-core and ltx-pipelines from cloned repo...")
21
+ subprocess.run(
22
+ [sys.executable, "-m", "pip", "install", "--force-reinstall", "--no-deps", "-e",
23
+ os.path.join(LTX_REPO_DIR, "packages", "ltx-core"),
24
+ "-e", os.path.join(LTX_REPO_DIR, "packages", "ltx-pipelines")],
25
+ check=True,
26
+ )
27
+
28
+ sys.path.insert(0, os.path.join(LTX_REPO_DIR, "packages", "ltx-pipelines", "src"))
29
+ sys.path.insert(0, os.path.join(LTX_REPO_DIR, "packages", "ltx-core", "src"))
30
+
31
+ import logging
32
+ import random
33
+ import tempfile
34
+ from pathlib import Path
35
+
36
+ import torch
37
+
38
+ torch._dynamo.config.suppress_errors = True
39
+ torch._dynamo.config.disable = True
40
+
41
+ import spaces
42
+ import gradio as gr
43
+ import numpy as np
44
+ from huggingface_hub import hf_hub_download, snapshot_download
45
+
46
+ from ltx_core.model.video_vae import TilingConfig, get_video_chunks_number
47
+ from ltx_core.quantization import QuantizationPolicy
48
+ from ltx_pipelines.distilled import DistilledPipeline
49
+ from ltx_pipelines.utils.args import ImageConditioningInput
50
+ from ltx_pipelines.utils.media_io import encode_video
51
+
52
+ # Force-patch xformers attention into the LTX attention module.
53
+ from ltx_core.model.transformer import attention as _attn_mod
54
+
55
+ print(f"[ATTN] Before patch: memory_efficient_attention={_attn_mod.memory_efficient_attention}")
56
+ try:
57
+ from xformers.ops import memory_efficient_attention as _mea
58
+
59
+ _attn_mod.memory_efficient_attention = _mea
60
+ print(f"[ATTN] After patch: memory_efficient_attention={_attn_mod.memory_efficient_attention}")
61
+ except Exception as e:
62
+ print(f"[ATTN] xformers patch FAILED: {type(e).__name__}: {e}")
63
+
64
+ logging.getLogger().setLevel(logging.INFO)
65
+
66
+ MAX_SEED = np.iinfo(np.int32).max
67
+ DEFAULT_PROMPT = (
68
+ "An astronaut hatches from a fragile egg on the surface of the Moon, "
69
+ "the shell cracking and peeling apart in gentle low-gravity motion. "
70
+ "Fine lunar dust lifts and drifts outward with each movement, floating "
71
+ "in slow arcs before settling back onto the ground."
72
+ )
73
+ DEFAULT_FRAME_RATE = 24.0
74
+
75
+ # Resolution presets: (width, height)
76
+ RESOLUTIONS = {
77
+ "high": {"16:9": (1536, 1024), "9:16": (1024, 1536), "1:1": (1024, 1024)},
78
+ "low": {"16:9": (768, 512), "9:16": (512, 768), "1:1": (768, 768)},
79
+ }
80
+
81
+ # Model repos
82
+ LTX_MODEL_REPO = "Lightricks/LTX-2.3"
83
+ LTX_MODEL_REPO_FP8 = "Lightricks/LTX-2.3-fp8"
84
+ GEMMA_REPO = "google/gemma-3-12b-it-qat-q4_0-unquantized"
85
+
86
+ # Download model checkpoints
87
+ print("=" * 80)
88
+ print("Downloading LTX-2.3 distilled model + Gemma...")
89
+ print("=" * 80)
90
+
91
+ checkpoint_path = hf_hub_download(repo_id=LTX_MODEL_REPO_FP8, filename="ltx-2.3-22b-distilled-fp8.safetensors")
92
+ spatial_upsampler_path = hf_hub_download(repo_id=LTX_MODEL_REPO, filename="ltx-2.3-spatial-upscaler-x2-1.1.safetensors")
93
+ gemma_root = snapshot_download(repo_id=GEMMA_REPO)
94
+
95
+ print(f"Checkpoint: {checkpoint_path}")
96
+ print(f"Spatial upsampler: {spatial_upsampler_path}")
97
+ print(f"Gemma root: {gemma_root}")
98
+
99
+ # Initialize pipeline WITH text encoder
100
+ pipeline = DistilledPipeline(
101
+ distilled_checkpoint_path=checkpoint_path,
102
+ spatial_upsampler_path=spatial_upsampler_path,
103
+ gemma_root=gemma_root,
104
+ loras=[],
105
+ quantization=QuantizationPolicy.fp8_cast(),
106
+ )
107
+
108
+ # Preload all models for ZeroGPU tensor packing.
109
+ print("Preloading all models (including Gemma)...")
110
+ ledger = pipeline.model_ledger
111
+ _transformer = ledger.transformer()
112
+ _video_encoder = ledger.video_encoder()
113
+ _video_decoder = ledger.video_decoder()
114
+ _audio_decoder = ledger.audio_decoder()
115
+ _vocoder = ledger.vocoder()
116
+ _spatial_upsampler = ledger.spatial_upsampler()
117
+ _text_encoder = ledger.text_encoder()
118
+ _embeddings_processor = ledger.gemma_embeddings_processor()
119
+
120
+ ledger.transformer = lambda: _transformer
121
+ ledger.video_encoder = lambda: _video_encoder
122
+ ledger.video_decoder = lambda: _video_decoder
123
+ ledger.audio_decoder = lambda: _audio_decoder
124
+ ledger.vocoder = lambda: _vocoder
125
+ ledger.spatial_upsampler = lambda: _spatial_upsampler
126
+ ledger.text_encoder = lambda: _text_encoder
127
+ ledger.gemma_embeddings_processor = lambda: _embeddings_processor
128
+ print("All models preloaded (including Gemma text encoder)!")
129
+
130
+ print("=" * 80)
131
+ print("Pipeline ready!")
132
+ print("=" * 80)
133
+
134
+
135
+ def log_memory(tag: str):
136
+ if torch.cuda.is_available():
137
+ allocated = torch.cuda.memory_allocated() / 1024 ** 3
138
+ peak = torch.cuda.max_memory_allocated() / 1024 ** 3
139
+ free, total = torch.cuda.mem_get_info()
140
+ print(
141
+ f"[VRAM {tag}] allocated={allocated:.2f}GB peak={peak:.2f}GB free={free / 1024 ** 3:.2f}GB total={total / 1024 ** 3:.2f}GB")
142
+
143
+
144
+ def detect_aspect_ratio(image) -> str:
145
+ """Detect the closest aspect ratio (16:9, 9:16, or 1:1) from an image."""
146
+ if image is None:
147
+ return "16:9"
148
+ if hasattr(image, "size"):
149
+ w, h = image.size
150
+ elif hasattr(image, "shape"):
151
+ h, w = image.shape[:2]
152
+ else:
153
+ return "16:9"
154
+ ratio = w / h
155
+ candidates = {"16:9": 16 / 9, "9:16": 9 / 16, "1:1": 1.0}
156
+ return min(candidates, key=lambda k: abs(ratio - candidates[k]))
157
+
158
+
159
+ def on_image_upload(image, high_res):
160
+ """Auto-set resolution when image is uploaded."""
161
+ aspect = detect_aspect_ratio(image)
162
+ tier = "high" if high_res else "low"
163
+ w, h = RESOLUTIONS[tier][aspect]
164
+ return gr.update(value=w), gr.update(value=h)
165
+
166
+
167
+ def on_highres_toggle(image, high_res):
168
+ """Update resolution when high-res toggle changes."""
169
+ aspect = detect_aspect_ratio(image)
170
+ tier = "high" if high_res else "low"
171
+ w, h = RESOLUTIONS[tier][aspect]
172
+ return gr.update(value=w), gr.update(value=h)
173
+
174
+
175
+ @spaces.GPU(duration=75)
176
+ @torch.inference_mode()
177
+ def generate_video(
178
+ input_image,
179
+ prompt: str,
180
+ duration: float,
181
+ enhance_prompt: bool = True,
182
+ seed: int = 42,
183
+ randomize_seed: bool = True,
184
+ height: int = 1024,
185
+ width: int = 1536,
186
+ progress=gr.Progress(track_tqdm=True),
187
+ ):
188
+ try:
189
+ torch.cuda.reset_peak_memory_stats()
190
+ log_memory("start")
191
+
192
+ current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
193
+
194
+ frame_rate = DEFAULT_FRAME_RATE
195
+ num_frames = int(duration * frame_rate) + 1
196
+ num_frames = ((num_frames - 1 + 7) // 8) * 8 + 1
197
+
198
+ print(f"Generating: {height}x{width}, {num_frames} frames ({duration}s), seed={current_seed}")
199
+
200
+ images = []
201
+ if input_image is not None:
202
+ output_dir = Path("outputs")
203
+ output_dir.mkdir(exist_ok=True)
204
+ temp_image_path = output_dir / f"temp_input_{current_seed}.jpg"
205
+ if hasattr(input_image, "save"):
206
+ input_image.save(temp_image_path)
207
+ else:
208
+ temp_image_path = Path(input_image)
209
+ images = [ImageConditioningInput(path=str(temp_image_path), frame_idx=0, strength=1.0)]
210
+
211
+ tiling_config = TilingConfig.default()
212
+ video_chunks_number = get_video_chunks_number(num_frames, tiling_config)
213
+
214
+ log_memory("before pipeline call")
215
+
216
+ video, audio = pipeline(
217
+ prompt=prompt,
218
+ seed=current_seed,
219
+ height=int(height),
220
+ width=int(width),
221
+ num_frames=num_frames,
222
+ frame_rate=frame_rate,
223
+ images=images,
224
+ tiling_config=tiling_config,
225
+ enhance_prompt=enhance_prompt,
226
+ )
227
+
228
+ log_memory("after pipeline call")
229
+
230
+ output_path = tempfile.mktemp(suffix=".mp4")
231
+ encode_video(
232
+ video=video,
233
+ fps=frame_rate,
234
+ audio=audio,
235
+ output_path=output_path,
236
+ video_chunks_number=video_chunks_number,
237
+ )
238
+
239
+ log_memory("after encode_video")
240
+ return str(output_path), current_seed
241
+
242
+ except Exception as e:
243
+ import traceback
244
+ log_memory("on error")
245
+ print(f"Error: {str(e)}\n{traceback.format_exc()}")
246
+ return None, current_seed
247
+
248
+
249
+ with gr.Blocks(title="LTX-2.3 Distilled") as demo:
250
+ gr.Markdown("# LTX-2.3 Distilled (22B): Fast Audio-Video Generation")
251
+ gr.Markdown(
252
+ "Fast and high quality video + audio generation "
253
+ "[[model]](https://huggingface.co/Lightricks/LTX-2.3) "
254
+ "[[code]](https://github.com/Lightricks/LTX-2)"
255
+ )
256
+
257
+ with gr.Row():
258
+ with gr.Column():
259
+ input_image = gr.Image(label="Input Image (Optional)", type="pil")
260
+ prompt = gr.Textbox(
261
+ label="Prompt",
262
+ info="for best results - make it as elaborate as possible",
263
+ value="Make this image come alive with cinematic motion, smooth animation",
264
+ lines=3,
265
+ placeholder="Describe the motion and animation you want...",
266
+ )
267
+
268
+ with gr.Row():
269
+ duration = gr.Slider(label="Duration (seconds)", minimum=1.0, maximum=10.0, value=3.0, step=0.1)
270
+ with gr.Column():
271
+ enhance_prompt = gr.Checkbox(label="Enhance Prompt", value=False)
272
+ high_res = gr.Checkbox(label="High Resolution", value=True)
273
+
274
+ generate_btn = gr.Button("Generate Video", variant="primary", size="lg")
275
+
276
+ with gr.Accordion("Advanced Settings", open=False):
277
+ seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, value=10, step=1)
278
+ randomize_seed = gr.Checkbox(label="Randomize Seed", value=True)
279
+ with gr.Row():
280
+ width = gr.Number(label="Width", value=1536, precision=0)
281
+ height = gr.Number(label="Height", value=1024, precision=0)
282
+
283
+ with gr.Column():
284
+ output_video = gr.Video(label="Generated Video", autoplay=True)
285
+
286
+ # Auto-detect aspect ratio from uploaded image and set resolution
287
+ input_image.change(
288
+ fn=on_image_upload,
289
+ inputs=[input_image, high_res],
290
+ outputs=[width, height],
291
+ )
292
+
293
+ # Update resolution when high-res toggle changes
294
+ high_res.change(
295
+ fn=on_highres_toggle,
296
+ inputs=[input_image, high_res],
297
+ outputs=[width, height],
298
+ )
299
+
300
+ generate_btn.click(
301
+ fn=generate_video,
302
+ inputs=[
303
+ input_image, prompt, duration, enhance_prompt,
304
+ seed, randomize_seed, height, width,
305
+ ],
306
+ outputs=[output_video, seed],
307
+ )
308
+
309
+ css = """
310
+ .fillable{max-width: 1200px !important}
311
+ .progress-text {color: white}
312
+ """
313
+
314
+ if __name__ == "__main__":
315
+ demo.launch(theme=gr.themes.Citrus(), css=css)
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ transformers==4.57.6
2
+ accelerate
3
+ torch==2.8.0
4
+ einops
5
+ scipy
6
+ av
7
+ scikit-image>=0.25.2
8
+ flashpack==0.1.2
9
+ torchaudio==2.8.0