bann commited on
Commit
feee32b
·
1 Parent(s): a2f22d5

feat(turbo): add native 4-step and 8-step LightX2V distill Turbo LoRA acceleration

Browse files
Files changed (1) hide show
  1. app.py +97 -10
app.py CHANGED
@@ -1,4 +1,4 @@
1
- """Wan 2.2 Space with ZeroGPU, Multi-LoRA (Civitai + HF), Presets, Trigger Words, and 300-Hours Optimizations."""
2
 
3
  from __future__ import annotations
4
 
@@ -33,6 +33,35 @@ LOCAL_LORAS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "lora
33
  LORA_MAX_BYTES = 2 * 1024**3
34
  DEFAULT_FPS = 16
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  # =========================================================================
37
  # Base NSFW Adapters (Unlocks adult generation on Wan Base Models)
38
  # =========================================================================
@@ -312,6 +341,31 @@ def _download_lora_url(url: str, civitai_token: str = "") -> tuple[str, str]:
312
  raise ValueError("Too many redirects downloading LoRA.")
313
 
314
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  def resolve_single_lora(
316
  preset_type: str, custom_url: str, hf_repo: str, hf_file: str, local_file: str, strength: float, civitai_token: str = ""
317
  ) -> tuple[str | None, str, float]:
@@ -447,7 +501,7 @@ def _fit_keyframe(image_input, target_width: int, target_height: int) -> Image.I
447
 
448
  def get_duration(*args, **kwargs):
449
  try:
450
- steps = kwargs.get("steps", args[6] if len(args) > 6 else 20)
451
  return max(180, min(MAX_GPU_DURATION, int(steps) * 8 + 60))
452
  except Exception:
453
  return 240
@@ -529,6 +583,7 @@ def generate_video(
529
  prompt: str,
530
  negative_prompt: str,
531
  input_image: Image.Image | str | None,
 
532
  nsfw_base_choice: str,
533
  nsfw_base_strength: float,
534
  canvas: str,
@@ -571,6 +626,12 @@ def generate_video(
571
  lora_configs = []
572
  active_labels = []
573
 
 
 
 
 
 
 
574
  # 1. Base NSFW Adapter
575
  if nsfw_base_choice in NSFW_BASE_ADAPTERS and nsfw_base_strength > 0:
576
  base_spec = NSFW_BASE_ADAPTERS[nsfw_base_choice]
@@ -664,6 +725,11 @@ custom_css = """
664
  font-weight: 600;
665
  margin-right: 6px;
666
  }
 
 
 
 
 
667
  .badge-zerogpu {
668
  background: rgba(16, 185, 129, 0.2);
669
  color: #34d399;
@@ -711,17 +777,18 @@ all_preset_choices = (
711
  + (["Local File (loras/ folder)"] if get_local_loras() else [])
712
  )
713
 
714
- with gr.Blocks(css=custom_css, title="Wan 2.2 Video Studio") as app:
715
  gr.HTML(
716
  """
717
  <div class="header-card">
718
  <div style="margin-bottom: 12px;">
 
719
  <span class="badge badge-zerogpu">⚡ ZeroGPU</span>
720
  <span class="badge badge-model">🎬 Wan 2.2 (MoE Diffusers)</span>
721
  <span class="badge badge-multilora">🧩 Base NSFW + Multi-LoRA Engine</span>
722
  </div>
723
- <h1>Wan 2.2 AI Video Studio</h1>
724
- <p>Image-to-Video (I2V) & Text-to-Video (T2V) com Arquitetura MoE Wan 2.2, Adaptador NSFW Base e Multi-LoRA.</p>
725
  </div>
726
  """
727
  )
@@ -758,6 +825,14 @@ with gr.Blocks(css=custom_css, title="Wan 2.2 Video Studio") as app:
758
  hmnsfw_example_btn = gr.Button("💡 Inserir Exemplo Completo de Prompt HMNSFW", elem_classes=["trigger-btn"])
759
 
760
  with gr.Tabs():
 
 
 
 
 
 
 
 
761
  with gr.TabItem("🔞 Adaptador Base NSFW (Essencial)"):
762
  gr.Markdown("Como o modelo base do Wan é treinado sem conteúdo explícito, este adaptador é carregado como base para desbloquear anatomia sem gastar seus slots de LoRA!")
763
  with gr.Row():
@@ -883,11 +958,11 @@ with gr.Blocks(css=custom_css, title="Wan 2.2 Video Studio") as app:
883
  )
884
  steps = gr.Slider(
885
  label="Passos de Inferência (Steps)",
886
- minimum=4,
887
  maximum=40,
888
  step=1,
889
- value=20,
890
- info="O guia recomenda 15-25 passos para qualidade ideal.",
891
  )
892
 
893
  with gr.Row():
@@ -896,7 +971,8 @@ with gr.Blocks(css=custom_css, title="Wan 2.2 Video Studio") as app:
896
  minimum=1.0,
897
  maximum=10.0,
898
  step=0.5,
899
- value=5.0,
 
900
  )
901
  seed = gr.Number(label="Seed", value=42, precision=0)
902
  randomize_seed = gr.Checkbox(label="🎲 Randomizar Seed", value=True)
@@ -908,13 +984,23 @@ with gr.Blocks(css=custom_css, title="Wan 2.2 Video Studio") as app:
908
  type="password",
909
  )
910
 
911
- generate_btn = gr.Button("🚀 Gerar Vídeo Wan 2.2", variant="primary", elem_classes=["generate-btn"])
912
 
913
  with gr.Column(scale=6):
914
  output_video = gr.Video(label="Vídeo Gerado", autoplay=True, loop=True)
915
  output_report = gr.Markdown(label="Detalhes da Geração", value="Pronto para renderizar vídeo.")
916
 
917
  # Event handlers
 
 
 
 
 
 
 
 
 
 
918
  def on_lora1_change(preset_val):
919
  spec = LORA_PRESETS.get(preset_val, {})
920
  triggers = spec.get("trigger_words", "")
@@ -991,6 +1077,7 @@ with gr.Blocks(css=custom_css, title="Wan 2.2 Video Studio") as app:
991
  prompt,
992
  negative_prompt,
993
  input_image,
 
994
  nsfw_base_choice,
995
  nsfw_base_strength,
996
  canvas,
 
1
+ """Wan 2.2 Space with ZeroGPU, Turbo 4-8 Step Distill, Multi-LoRA (Civitai + HF), Presets, Trigger Words, and 300-Hours Optimizations."""
2
 
3
  from __future__ import annotations
4
 
 
33
  LORA_MAX_BYTES = 2 * 1024**3
34
  DEFAULT_FPS = 16
35
 
36
+ # =========================================================================
37
+ # Turbo Distillation LoRAs (4-Step & 8-Step Acceleration)
38
+ # =========================================================================
39
+ TURBO_PRESETS = {
40
+ "⚡ Turbo 4-Passos (LightX2V Distill - 4 Steps Ultra Rápido)": {
41
+ "wan2.2_i2v": ("lightx2v/Wan2.2-Distill-Loras", "wan2.2_i2v_A14b_high_noise_lora_rank64_lightx2v_4step_1022.safetensors"),
42
+ "wan2.2_t2v": ("lightx2v/Wan2.2-Distill-Loras", "wan2.2_t2v_A14b_high_noise_lora_rank64_lightx2v_4step_1217.safetensors"),
43
+ "wan2.1_i2v": ("lightx2v/Wan2.1-Distill-Loras", "wan2.1_i2v_lora_rank64_lightx2v_4step.safetensors"),
44
+ "wan2.1_t2v": ("lightx2v/Wan2.1-Distill-Loras", "wan2.1_t2v_14b_lora_rank64_lightx2v_4step.safetensors"),
45
+ "default_steps": 4,
46
+ "default_cfg": 1.5,
47
+ "description": "Destilação 4-passos LightX2V. Renderiza o vídeo em ~15-25 segundos mantendo alta qualidade!",
48
+ },
49
+ "⚡ Turbo 8-Passos (8 Steps Balanceado)": {
50
+ "wan2.2_i2v": ("lightx2v/Wan2.2-Distill-Loras", "wan2.2_i2v_A14b_low_noise_lora_rank64_lightx2v_4step_1022.safetensors"),
51
+ "wan2.2_t2v": ("lightx2v/Wan2.2-Distill-Loras", "wan2.2_t2v_A14b_low_noise_lora_rank64_lightx2v_4step_1217.safetensors"),
52
+ "wan2.1_i2v": ("lightx2v/Wan2.1-Distill-Loras", "wan2.1_i2v_lora_rank64_lightx2v_4step.safetensors"),
53
+ "wan2.1_t2v": ("lightx2v/Wan2.1-Distill-Loras", "wan2.1_t2v_14b_lora_rank64_lightx2v_4step.safetensors"),
54
+ "default_steps": 8,
55
+ "default_cfg": 2.0,
56
+ "description": "Destilação 8-passos para equilíbrio perfeito entre velocidade e detalhes.",
57
+ },
58
+ "🚫 Desativado (Modo Normal - 15 a 25 Passos)": {
59
+ "default_steps": 20,
60
+ "default_cfg": 5.0,
61
+ "description": "Modo padrão sem aceleração Turbo (requer 15-25 passos).",
62
+ },
63
+ }
64
+
65
  # =========================================================================
66
  # Base NSFW Adapters (Unlocks adult generation on Wan Base Models)
67
  # =========================================================================
 
341
  raise ValueError("Too many redirects downloading LoRA.")
342
 
343
 
344
+ def resolve_turbo_lora(turbo_choice: str, model_repo: str) -> tuple[str | None, float]:
345
+ """Resolves and downloads the appropriate 4-step or 8-step distillation LoRA."""
346
+ if turbo_choice not in TURBO_PRESETS or "Desativado" in turbo_choice:
347
+ return None, 0.0
348
+
349
+ spec = TURBO_PRESETS[turbo_choice]
350
+ is_i2v = "I2V" in model_repo or "i2v" in model_repo or "TI2V" in model_repo
351
+ is_wan22 = "Wan2.2" in model_repo or "wan2.2" in model_repo
352
+
353
+ if is_wan22:
354
+ key = "wan2.2_i2v" if is_i2v else "wan2.2_t2v"
355
+ else:
356
+ key = "wan2.1_i2v" if is_i2v else "wan2.1_t2v"
357
+
358
+ if key not in spec:
359
+ return None, 0.0
360
+
361
+ repo_id, filename = spec[key]
362
+ from huggingface_hub import hf_hub_download
363
+ local_dir = _lora_cache_directory(f"hf://{repo_id}/{filename}")
364
+ path = hf_hub_download(repo_id=repo_id, filename=filename, token=False, local_dir=local_dir)
365
+ os.utime(local_dir, None)
366
+ return path, 1.0
367
+
368
+
369
  def resolve_single_lora(
370
  preset_type: str, custom_url: str, hf_repo: str, hf_file: str, local_file: str, strength: float, civitai_token: str = ""
371
  ) -> tuple[str | None, str, float]:
 
501
 
502
  def get_duration(*args, **kwargs):
503
  try:
504
+ steps = kwargs.get("steps", args[6] if len(args) > 6 else 4)
505
  return max(180, min(MAX_GPU_DURATION, int(steps) * 8 + 60))
506
  except Exception:
507
  return 240
 
583
  prompt: str,
584
  negative_prompt: str,
585
  input_image: Image.Image | str | None,
586
+ turbo_choice: str,
587
  nsfw_base_choice: str,
588
  nsfw_base_strength: float,
589
  canvas: str,
 
626
  lora_configs = []
627
  active_labels = []
628
 
629
+ # 0. Turbo LoRA (4-Step or 8-Step Distill)
630
+ t_path, t_scale = resolve_turbo_lora(turbo_choice, model_choice)
631
+ if t_path and t_scale > 0:
632
+ lora_configs.append((t_path, t_scale))
633
+ active_labels.append(f"⚡ Turbo: {turbo_choice.split('(')[0].strip()}")
634
+
635
  # 1. Base NSFW Adapter
636
  if nsfw_base_choice in NSFW_BASE_ADAPTERS and nsfw_base_strength > 0:
637
  base_spec = NSFW_BASE_ADAPTERS[nsfw_base_choice]
 
725
  font-weight: 600;
726
  margin-right: 6px;
727
  }
728
+ .badge-turbo {
729
+ background: rgba(245, 158, 11, 0.2);
730
+ color: #fbbf24;
731
+ border: 1px solid rgba(245, 158, 11, 0.4);
732
+ }
733
  .badge-zerogpu {
734
  background: rgba(16, 185, 129, 0.2);
735
  color: #34d399;
 
777
  + (["Local File (loras/ folder)"] if get_local_loras() else [])
778
  )
779
 
780
+ with gr.Blocks(css=custom_css, title="Wan 2.2 Turbo Video Studio") as app:
781
  gr.HTML(
782
  """
783
  <div class="header-card">
784
  <div style="margin-bottom: 12px;">
785
+ <span class="badge badge-turbo">⚡ Turbo 4-Step Distill</span>
786
  <span class="badge badge-zerogpu">⚡ ZeroGPU</span>
787
  <span class="badge badge-model">🎬 Wan 2.2 (MoE Diffusers)</span>
788
  <span class="badge badge-multilora">🧩 Base NSFW + Multi-LoRA Engine</span>
789
  </div>
790
+ <h1>Wan 2.2 Turbo Video Studio</h1>
791
+ <p>Aceleração Turbo de 4 a 8 Passos com Arquitetura Wan 2.2 MoE, Adaptador Base NSFW e Multi-LoRA.</p>
792
  </div>
793
  """
794
  )
 
825
  hmnsfw_example_btn = gr.Button("💡 Inserir Exemplo Completo de Prompt HMNSFW", elem_classes=["trigger-btn"])
826
 
827
  with gr.Tabs():
828
+ with gr.TabItem("⚡ Aceleração Turbo (4-8 Passos)"):
829
+ gr.Markdown("Acelera a geração em até **8x** aplicando destilação LightX2V. Permite renderizar vídeos em ~15 segundos!")
830
+ turbo_choice = gr.Dropdown(
831
+ label="Modo Turbo",
832
+ choices=list(TURBO_PRESETS.keys()),
833
+ value="⚡ Turbo 4-Passos (LightX2V Distill - 4 Steps Ultra Rápido)",
834
+ )
835
+
836
  with gr.TabItem("🔞 Adaptador Base NSFW (Essencial)"):
837
  gr.Markdown("Como o modelo base do Wan é treinado sem conteúdo explícito, este adaptador é carregado como base para desbloquear anatomia sem gastar seus slots de LoRA!")
838
  with gr.Row():
 
958
  )
959
  steps = gr.Slider(
960
  label="Passos de Inferência (Steps)",
961
+ minimum=2,
962
  maximum=40,
963
  step=1,
964
+ value=4,
965
+ info="Com Turbo 4-Passos ativado, 4 passos fornecem render ultrarrápido.",
966
  )
967
 
968
  with gr.Row():
 
971
  minimum=1.0,
972
  maximum=10.0,
973
  step=0.5,
974
+ value=1.5,
975
+ info="No modo Turbo, recomenda-se CFG baixo (1.0 a 2.0).",
976
  )
977
  seed = gr.Number(label="Seed", value=42, precision=0)
978
  randomize_seed = gr.Checkbox(label="🎲 Randomizar Seed", value=True)
 
984
  type="password",
985
  )
986
 
987
+ generate_btn = gr.Button("🚀 Gerar Vídeo Wan 2.2 Turbo", variant="primary", elem_classes=["generate-btn"])
988
 
989
  with gr.Column(scale=6):
990
  output_video = gr.Video(label="Vídeo Gerado", autoplay=True, loop=True)
991
  output_report = gr.Markdown(label="Detalhes da Geração", value="Pronto para renderizar vídeo.")
992
 
993
  # Event handlers
994
+ def on_turbo_change(turbo_val):
995
+ spec = TURBO_PRESETS.get(turbo_val, {})
996
+ return spec.get("default_steps", 4), spec.get("default_cfg", 1.5)
997
+
998
+ turbo_choice.change(
999
+ fn=on_turbo_change,
1000
+ inputs=[turbo_choice],
1001
+ outputs=[steps, guidance_scale],
1002
+ )
1003
+
1004
  def on_lora1_change(preset_val):
1005
  spec = LORA_PRESETS.get(preset_val, {})
1006
  triggers = spec.get("trigger_words", "")
 
1077
  prompt,
1078
  negative_prompt,
1079
  input_image,
1080
+ turbo_choice,
1081
  nsfw_base_choice,
1082
  nsfw_base_strength,
1083
  canvas,