Spaces:
Running on Zero
Running on Zero
Drop Quality mode; distilled-only with new defaults (distill 0.7, 5s, crossview 1.5, cfg 1, 8 steps)
Browse files
README.md
CHANGED
|
@@ -31,23 +31,24 @@ Upload a reference video and pick a new camera angle (azimuth, elevation, distan
|
|
| 31 |
- **Pipeline**: `LTX2InContextPipeline` with `LTX2ReferenceCondition` for in-context video conditioning
|
| 32 |
- **LoRA**: applied via standard `pipe.load_lora_weights()` + `set_adapters()`
|
| 33 |
|
| 34 |
-
## Sampling
|
| 35 |
|
| 36 |
-
|
|
|
|
|
|
|
| 37 |
|
| 38 |
-
|
|
| 39 |
-
|---|---|
|
| 40 |
-
|
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
**Distill LoRA strength** is adjustable under Advanced (Fast mode only). `0.6`
|
| 49 |
-
matches the ComfyUI workflow; lower values (0.25-0.5) preserve more motion at the
|
| 50 |
-
cost of needing more steps.
|
| 51 |
|
| 52 |
> The distilled LoRA used here is rank-dynamic, and the diffusers LTX-2 loader
|
| 53 |
> discards per-layer alphas (`network_alphas=None`), which would otherwise scale
|
|
@@ -69,4 +70,4 @@ Use the dropdowns in the UI to build a valid prompt — the app constructs it au
|
|
| 69 |
|
| 70 |
- **Small angle changes work best.** For larger viewpoint shifts, chain multiple small steps (feed the generated view back in as the new reference).
|
| 71 |
- **LoRA strength** of 1.0–1.5 is recommended. Higher values produce a stronger viewpoint shift.
|
| 72 |
-
-
|
|
|
|
| 31 |
- **Pipeline**: `LTX2InContextPipeline` with `LTX2ReferenceCondition` for in-context video conditioning
|
| 32 |
- **LoRA**: applied via standard `pipe.load_lora_weights()` + `set_adapters()`
|
| 33 |
|
| 34 |
+
## Sampling
|
| 35 |
|
| 36 |
+
Distilled sampling, mirroring the validated ComfyUI workflow for this LoRA family:
|
| 37 |
+
the dev transformer with the [distilled speed LoRA](https://huggingface.co/Kijai/LTX2.3_comfy)
|
| 38 |
+
(rank-dynamic resize) stacked on top.
|
| 39 |
|
| 40 |
+
| Control | Default |
|
| 41 |
+
|---|---|
|
| 42 |
+
| Duration | 5 s |
|
| 43 |
+
| CrossView LoRA strength | 1.5 |
|
| 44 |
+
| Distill LoRA strength | 0.7 |
|
| 45 |
+
| Inference steps | 8 |
|
| 46 |
+
| Guidance scale | 1 |
|
| 47 |
|
| 48 |
+
All five are adjustable under **Advanced**. At 8 steps the distilled sigma
|
| 49 |
+
schedule is used; any other step count falls back to the scheduler's own spacing.
|
| 50 |
+
Practitioners often run the distill LoRA at 0.25-0.5 — a stronger distill trades
|
| 51 |
+
away motion, and a weaker one usually wants more steps.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
> The distilled LoRA used here is rank-dynamic, and the diffusers LTX-2 loader
|
| 54 |
> discards per-layer alphas (`network_alphas=None`), which would otherwise scale
|
|
|
|
| 70 |
|
| 71 |
- **Small angle changes work best.** For larger viewpoint shifts, chain multiple small steps (feed the generated view back in as the new reference).
|
| 72 |
- **LoRA strength** of 1.0–1.5 is recommended. Higher values produce a stronger viewpoint shift.
|
| 73 |
+
- Generation takes well under a minute at the default 8 steps.
|
app.py
CHANGED
|
@@ -27,12 +27,7 @@ DISTILL_LORA_REPO = "Kijai/LTX2.3_comfy"
|
|
| 27 |
DISTILL_LORA_WEIGHT_NAME = (
|
| 28 |
"loras/ltx-2.3-22b-distilled-1.1_lora-dynamic_fro09_avg_rank_111_bf16.safetensors"
|
| 29 |
)
|
| 30 |
-
DISTILL_LORA_SCALE = 0.
|
| 31 |
-
|
| 32 |
-
MODE_FAST = "Fast (distilled, 8 steps)"
|
| 33 |
-
MODE_QUALITY = "Quality (dev, 30 steps)"
|
| 34 |
-
MODE_CHOICES = [MODE_FAST, MODE_QUALITY]
|
| 35 |
-
DEFAULT_MODE = MODE_FAST
|
| 36 |
|
| 37 |
# Camera vocabulary (from captions_all_63.txt)
|
| 38 |
AZIMUTH_CHOICES = [
|
|
@@ -52,9 +47,11 @@ DEFAULT_WIDTH = 768
|
|
| 52 |
DEFAULT_HEIGHT = 512
|
| 53 |
DEFAULT_NUM_FRAMES = 81
|
| 54 |
DEFAULT_FPS = 24
|
| 55 |
-
DEFAULT_STEPS =
|
| 56 |
-
DEFAULT_GUIDANCE =
|
| 57 |
-
DEFAULT_LORA_SCALE = 1.
|
|
|
|
|
|
|
| 58 |
|
| 59 |
def _normalize_dynamic_rank_lora(state_dict):
|
| 60 |
"""Pre-compensate for the diffusers LTX-2 loader dropping per-layer alphas.
|
|
@@ -129,7 +126,6 @@ def generate(
|
|
| 129 |
guidance_scale,
|
| 130 |
num_steps,
|
| 131 |
negative_prompt,
|
| 132 |
-
mode=DEFAULT_MODE,
|
| 133 |
distill_strength=DISTILL_LORA_SCALE,
|
| 134 |
progress=gr.Progress(track_tqdm=True),
|
| 135 |
):
|
|
@@ -152,21 +148,15 @@ def generate(
|
|
| 152 |
ref_frames = load_video(reference_video)
|
| 153 |
ref_cond = LTX2ReferenceCondition(frames=ref_frames, strength=1.0)
|
| 154 |
|
| 155 |
-
#
|
| 156 |
-
#
|
| 157 |
-
#
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
else:
|
| 165 |
-
pipe.set_adapters("crossview", lora_scale)
|
| 166 |
-
steps = int(num_steps)
|
| 167 |
-
sigmas = None
|
| 168 |
-
cfg = guidance_scale
|
| 169 |
-
stg = 1.0
|
| 170 |
|
| 171 |
# Run inference (return_dict=False gives (video, audio) tuple)
|
| 172 |
video, audio = pipe(
|
|
@@ -260,25 +250,16 @@ with gr.Blocks(title="LTX CrossView-Prompt - Video Camera Control by Prompt", cs
|
|
| 260 |
info="Camera distance to subject",
|
| 261 |
)
|
| 262 |
|
| 263 |
-
mode = gr.Radio(
|
| 264 |
-
choices=MODE_CHOICES,
|
| 265 |
-
value=DEFAULT_MODE,
|
| 266 |
-
label="Sampling mode",
|
| 267 |
-
info="Fast stacks the distilled speed LoRA on the dev model "
|
| 268 |
-
"(8 steps, cfg 1). Quality runs the dev model alone and "
|
| 269 |
-
"honours the step / guidance sliders below.",
|
| 270 |
-
)
|
| 271 |
-
|
| 272 |
with gr.Accordion("Advanced", open=False):
|
| 273 |
distill_strength = gr.Slider(
|
| 274 |
minimum=0.0, maximum=1.0, value=DISTILL_LORA_SCALE, step=0.05,
|
| 275 |
-
label="Distill LoRA strength
|
| 276 |
info="0.6 matches the ComfyUI workflow. Practitioners often "
|
| 277 |
-
"run 0.25-0.5 — a stronger distill trades away motion
|
| 278 |
-
"
|
| 279 |
)
|
| 280 |
duration_seconds = gr.Slider(
|
| 281 |
-
minimum=1, maximum=5, value=
|
| 282 |
label="Duration (seconds)",
|
| 283 |
)
|
| 284 |
lora_scale = gr.Slider(
|
|
@@ -293,6 +274,8 @@ with gr.Blocks(title="LTX CrossView-Prompt - Video Camera Control by Prompt", cs
|
|
| 293 |
num_steps = gr.Slider(
|
| 294 |
minimum=8, maximum=50, value=DEFAULT_STEPS, step=1,
|
| 295 |
label="Inference steps",
|
|
|
|
|
|
|
| 296 |
)
|
| 297 |
negative_prompt = gr.Textbox(
|
| 298 |
value="",
|
|
@@ -320,13 +303,13 @@ with gr.Blocks(title="LTX CrossView-Prompt - Video Camera Control by Prompt", cs
|
|
| 320 |
gr.Markdown("---\n### 📋 Examples")
|
| 321 |
gr.Markdown("Click an example to populate the inputs, then click **Generate New View**.")
|
| 322 |
examples = [
|
| 323 |
-
["assets/ref_dining.mp4", "to the right", "lower", "closer",
|
| 324 |
-
["assets/ref_dining.mp4", "to the left", "higher", "further",
|
| 325 |
-
["assets/ref_scene01.mp4", "slightly to the left", "higher", "closer",
|
| 326 |
]
|
| 327 |
gr.Examples(
|
| 328 |
examples=examples,
|
| 329 |
-
inputs=[reference_video, azimuth, elevation, distance, duration_seconds, seed, randomize_seed, lora_scale, guidance_scale, num_steps, negative_prompt,
|
| 330 |
outputs=[output_video, used_seed, used_prompt],
|
| 331 |
fn=generate,
|
| 332 |
cache_examples=True,
|
|
@@ -336,7 +319,7 @@ with gr.Blocks(title="LTX CrossView-Prompt - Video Camera Control by Prompt", cs
|
|
| 336 |
# Wire up
|
| 337 |
generate_btn.click(
|
| 338 |
fn=generate,
|
| 339 |
-
inputs=[reference_video, azimuth, elevation, distance, duration_seconds, seed, randomize_seed, lora_scale, guidance_scale, num_steps, negative_prompt,
|
| 340 |
outputs=[output_video, used_seed, used_prompt],
|
| 341 |
)
|
| 342 |
|
|
|
|
| 27 |
DISTILL_LORA_WEIGHT_NAME = (
|
| 28 |
"loras/ltx-2.3-22b-distilled-1.1_lora-dynamic_fro09_avg_rank_111_bf16.safetensors"
|
| 29 |
)
|
| 30 |
+
DISTILL_LORA_SCALE = 0.7
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
# Camera vocabulary (from captions_all_63.txt)
|
| 33 |
AZIMUTH_CHOICES = [
|
|
|
|
| 47 |
DEFAULT_HEIGHT = 512
|
| 48 |
DEFAULT_NUM_FRAMES = 81
|
| 49 |
DEFAULT_FPS = 24
|
| 50 |
+
DEFAULT_STEPS = len(DISTILLED_SIGMA_VALUES) # 8
|
| 51 |
+
DEFAULT_GUIDANCE = 1.0
|
| 52 |
+
DEFAULT_LORA_SCALE = 1.5
|
| 53 |
+
DEFAULT_DURATION = 5.0
|
| 54 |
+
|
| 55 |
|
| 56 |
def _normalize_dynamic_rank_lora(state_dict):
|
| 57 |
"""Pre-compensate for the diffusers LTX-2 loader dropping per-layer alphas.
|
|
|
|
| 126 |
guidance_scale,
|
| 127 |
num_steps,
|
| 128 |
negative_prompt,
|
|
|
|
| 129 |
distill_strength=DISTILL_LORA_SCALE,
|
| 130 |
progress=gr.Progress(track_tqdm=True),
|
| 131 |
):
|
|
|
|
| 148 |
ref_frames = load_video(reference_video)
|
| 149 |
ref_cond = LTX2ReferenceCondition(frames=ref_frames, strength=1.0)
|
| 150 |
|
| 151 |
+
# Distilled sampling: the distill LoRA stacked on the dev transformer, as in
|
| 152 |
+
# the validated ComfyUI workflow. DISTILLED_SIGMA_VALUES is an 8-value
|
| 153 |
+
# schedule, so it only applies at its native step count; any other step count
|
| 154 |
+
# falls back to the scheduler's own spacing.
|
| 155 |
+
pipe.set_adapters(["crossview", "distill"], [lora_scale, float(distill_strength)])
|
| 156 |
+
steps = int(num_steps)
|
| 157 |
+
sigmas = DISTILLED_SIGMA_VALUES if steps == len(DISTILLED_SIGMA_VALUES) else None
|
| 158 |
+
cfg = guidance_scale
|
| 159 |
+
stg = 0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
|
| 161 |
# Run inference (return_dict=False gives (video, audio) tuple)
|
| 162 |
video, audio = pipe(
|
|
|
|
| 250 |
info="Camera distance to subject",
|
| 251 |
)
|
| 252 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
with gr.Accordion("Advanced", open=False):
|
| 254 |
distill_strength = gr.Slider(
|
| 255 |
minimum=0.0, maximum=1.0, value=DISTILL_LORA_SCALE, step=0.05,
|
| 256 |
+
label="Distill LoRA strength",
|
| 257 |
info="0.6 matches the ComfyUI workflow. Practitioners often "
|
| 258 |
+
"run 0.25-0.5 — a stronger distill trades away motion, "
|
| 259 |
+
"and a weaker one usually wants more steps.",
|
| 260 |
)
|
| 261 |
duration_seconds = gr.Slider(
|
| 262 |
+
minimum=1, maximum=5, value=DEFAULT_DURATION, step=0.5,
|
| 263 |
label="Duration (seconds)",
|
| 264 |
)
|
| 265 |
lora_scale = gr.Slider(
|
|
|
|
| 274 |
num_steps = gr.Slider(
|
| 275 |
minimum=8, maximum=50, value=DEFAULT_STEPS, step=1,
|
| 276 |
label="Inference steps",
|
| 277 |
+
info="8 uses the distilled sigma schedule. Any other value "
|
| 278 |
+
"falls back to the scheduler's default spacing.",
|
| 279 |
)
|
| 280 |
negative_prompt = gr.Textbox(
|
| 281 |
value="",
|
|
|
|
| 303 |
gr.Markdown("---\n### 📋 Examples")
|
| 304 |
gr.Markdown("Click an example to populate the inputs, then click **Generate New View**.")
|
| 305 |
examples = [
|
| 306 |
+
["assets/ref_dining.mp4", "to the right", "lower", "closer", 5, 42, False, 1.5, 1.0, 8, "", 0.7],
|
| 307 |
+
["assets/ref_dining.mp4", "to the left", "higher", "further", 5, 123, False, 1.5, 1.0, 8, "", 0.7],
|
| 308 |
+
["assets/ref_scene01.mp4", "slightly to the left", "higher", "closer", 5, 42, False, 1.5, 1.0, 8, "", 0.7],
|
| 309 |
]
|
| 310 |
gr.Examples(
|
| 311 |
examples=examples,
|
| 312 |
+
inputs=[reference_video, azimuth, elevation, distance, duration_seconds, seed, randomize_seed, lora_scale, guidance_scale, num_steps, negative_prompt, distill_strength],
|
| 313 |
outputs=[output_video, used_seed, used_prompt],
|
| 314 |
fn=generate,
|
| 315 |
cache_examples=True,
|
|
|
|
| 319 |
# Wire up
|
| 320 |
generate_btn.click(
|
| 321 |
fn=generate,
|
| 322 |
+
inputs=[reference_video, azimuth, elevation, distance, duration_seconds, seed, randomize_seed, lora_scale, guidance_scale, num_steps, negative_prompt, distill_strength],
|
| 323 |
outputs=[output_video, used_seed, used_prompt],
|
| 324 |
)
|
| 325 |
|