Cseti commited on
Commit
347f4da
·
verified ·
1 Parent(s): 31ca302

Drop Quality mode; distilled-only with new defaults (distill 0.7, 5s, crossview 1.5, cfg 1, 8 steps)

Browse files
Files changed (2) hide show
  1. README.md +16 -15
  2. app.py +26 -43
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 modes
35
 
36
- Two paths are available from the **Sampling mode** selector:
 
 
37
 
38
- | Mode | Setup | Steps | Guidance |
39
- |---|---|---|---|
40
- | **Fast (distilled)** | dev + [distilled speed LoRA](https://huggingface.co/Kijai/LTX2.3_comfy) (rank-dynamic resize) at strength 0.6 | 8 | cfg 1, distilled sigma schedule |
41
- | **Quality (dev)** | dev model alone | 30 (slider) | cfg 3 (slider), STG on |
 
 
 
42
 
43
- Fast mode mirrors the validated ComfyUI workflow for this LoRA family: the dev
44
- transformer with the distilled LoRA stacked on top, sampled at cfg 1 over the
45
- distilled sigma schedule. Quality mode is the original path and still honours the
46
- step and guidance sliders.
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
- - Fast mode generates a 3-second video in well under a minute; Quality mode takes ~2-4 minutes at 30 steps.
 
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.6
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 = 30
56
- DEFAULT_GUIDANCE = 3.0
57
- DEFAULT_LORA_SCALE = 1.0
 
 
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
- # Select the sampling path. Fast mode stacks the distill LoRA on the dev
156
- # transformer and samples at cfg 1 over the distilled sigma schedule (the
157
- # ComfyUI workflow's settings); quality mode runs the bare dev model.
158
- if mode == MODE_FAST:
159
- pipe.set_adapters(["crossview", "distill"], [lora_scale, float(distill_strength)])
160
- steps = len(DISTILLED_SIGMA_VALUES)
161
- sigmas = DISTILLED_SIGMA_VALUES
162
- cfg = 1.0
163
- stg = 0.0
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 (Fast mode only)",
276
  info="0.6 matches the ComfyUI workflow. Practitioners often "
277
- "run 0.25-0.5 — a stronger distill trades away motion. "
278
- "Ignored in Quality mode.",
279
  )
280
  duration_seconds = gr.Slider(
281
- minimum=1, maximum=5, value=3, step=0.5,
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", 3, 42, False, 1.0, 3.0, 30, "", MODE_FAST, 0.6],
324
- ["assets/ref_dining.mp4", "to the left", "higher", "further", 3, 123, False, 1.0, 3.0, 30, "", MODE_FAST, 0.6],
325
- ["assets/ref_scene01.mp4", "slightly to the left", "higher", "closer", 3, 42, False, 1.0, 3.0, 30, "", MODE_FAST, 0.6],
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, mode, distill_strength],
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, mode, distill_strength],
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