akhaliq HF Staff commited on
Commit
4512613
·
1 Parent(s): e35aff5

Switch to the recommended ckpt850 EMA LoRA; default steps back to 4

Browse files

ckpt850 is the final checkpoint of the round and sharp at 4 steps per
the updated card, so the UI/API default drops 8 -> 4. Also adds
H3_LORA_STRENGTH (default 1.0), the card's sharpness/artifact dial,
applied at fold time and in the per-request toggle.

Files changed (4) hide show
  1. README.md +4 -3
  2. app.py +2 -2
  3. h3_lora.py +10 -5
  4. index.html +2 -2
README.md CHANGED
@@ -47,12 +47,12 @@ callers get the same treatment the old upload event gave.
47
  ## 4-step Turbo LoRA
48
 
49
  The transformer runs with [`larryvrh/MiniMax-H3-Turbo-Lora`](https://huggingface.co/larryvrh/MiniMax-H3-Turbo-Lora)
50
- folded into its bf16 weights at startup (`h3_lora.py`), so the default is **8 sampling steps** instead of 28 (the
51
  card's comfort zone at the current checkpoint; 4 is the design point but softer). The fold mirrors the diffusers key
52
  conversion exactly (fused-QKV thirds, the `SwiGLU` gate/value swap, the shared AdaLN row layout) and happens before
53
  the AoTI package is patched in, so compiled blocks carry the update too. The low-rank factors stay resident, so the
54
  **Turbo LoRA checkbox** (or the `use_lora` API parameter) folds/unfolds `lora_B @ lora_A` in place per request —
55
- off gives the base model at whatever step count you pick. `H3_LORA` selects the file (`off` disables entirely).
56
 
57
  ## AoTI-compiled blocks
58
 
@@ -176,8 +176,9 @@ one-time `PIPE.to("cuda")` is inside the first row's 339 s and does not reappear
176
  | `H3_ATTENTION` | `_native_cudnn` | cuDNN's fused kernel, 10–20% faster than the SDPA default and needs nothing installed. flash-attention 3 is sm90-only and this pool is sm120. |
177
  | `H3_GPU_DURATION` | `900` | Seconds per request; the pool applies a 1.5 duration factor. |
178
  | `H3_GPU_SIZE` | `xlarge` | ZeroGPU allocation size. `large` does not fit. |
179
- | `H3_LORA` | `minimax_h3_turbo_4step_ckpt500.safetensors` | Turbo LoRA file folded into the transformer at startup. `off` disables. |
180
  | `H3_LORA_REPO` | `larryvrh/MiniMax-H3-Turbo-Lora` | Hub repo the LoRA is fetched from. |
 
181
 
182
  ## Whose GPU quota pays
183
 
 
47
  ## 4-step Turbo LoRA
48
 
49
  The transformer runs with [`larryvrh/MiniMax-H3-Turbo-Lora`](https://huggingface.co/larryvrh/MiniMax-H3-Turbo-Lora)
50
+ folded into its bf16 weights at startup (`h3_lora.py`), so the default is **4 sampling steps** instead of 28
51
  card's comfort zone at the current checkpoint; 4 is the design point but softer). The fold mirrors the diffusers key
52
  conversion exactly (fused-QKV thirds, the `SwiGLU` gate/value swap, the shared AdaLN row layout) and happens before
53
  the AoTI package is patched in, so compiled blocks carry the update too. The low-rank factors stay resident, so the
54
  **Turbo LoRA checkbox** (or the `use_lora` API parameter) folds/unfolds `lora_B @ lora_A` in place per request —
55
+ off gives the base model at whatever step count you pick. `H3_LORA` selects the file (`off` disables entirely), `H3_LORA_STRENGTH` scales the update (the card's sharpness/artifact dial: >1 against smear, <1 against over-sharp grain).
56
 
57
  ## AoTI-compiled blocks
58
 
 
176
  | `H3_ATTENTION` | `_native_cudnn` | cuDNN's fused kernel, 10–20% faster than the SDPA default and needs nothing installed. flash-attention 3 is sm90-only and this pool is sm120. |
177
  | `H3_GPU_DURATION` | `900` | Seconds per request; the pool applies a 1.5 duration factor. |
178
  | `H3_GPU_SIZE` | `xlarge` | ZeroGPU allocation size. `large` does not fit. |
179
+ | `H3_LORA` | `minimax_h3_turbo_4step_ema_ckpt850.safetensors` | Turbo LoRA file folded into the transformer at startup. `off` disables. |
180
  | `H3_LORA_REPO` | `larryvrh/MiniMax-H3-Turbo-Lora` | Hub repo the LoRA is fetched from. |
181
+ | `H3_LORA_STRENGTH` | `1.0` | Scales the folded LoRA delta (sharpness/artifact trade-off). |
182
 
183
  ## Whose GPU quota pays
184
 
app.py CHANGED
@@ -302,7 +302,7 @@ def _fit_keyframe(image_path, current_canvas):
302
  return image_path, label
303
 
304
 
305
- def generate(prompt, image_path=None, last_image_path=None, canvas=DEFAULT_CANVAS, duration=5, steps=8, seed=42, upsample=False, use_lora=True, ip_token=None):
306
  """One request. `upsample` is last and defaults off, so a positional API client that predates it is unaffected."""
307
  if LOAD_ERROR:
308
  raise Exception(LOAD_ERROR)
@@ -379,7 +379,7 @@ app = Server(title="MiniMax-H3 Studio")
379
 
380
  @app.api(name="generate")
381
  def _generate_api(prompt: str, image_path: FileData | None = None, last_image_path: FileData | None = None,
382
- canvas: str = DEFAULT_CANVAS, duration: float = 5, steps: int = 8, seed: float = 42,
383
  upsample: bool = False, use_lora: bool = True, request: Request = None) -> tuple[FileData, str, str]:
384
  """Generate a video with a synchronized soundtrack. Returns (video, report, refined prompt)."""
385
  # `request` is injected by the event system, not an API input; its x-ip-token bills the conditioner to the caller.
 
302
  return image_path, label
303
 
304
 
305
+ def generate(prompt, image_path=None, last_image_path=None, canvas=DEFAULT_CANVAS, duration=5, steps=4, seed=42, upsample=False, use_lora=True, ip_token=None):
306
  """One request. `upsample` is last and defaults off, so a positional API client that predates it is unaffected."""
307
  if LOAD_ERROR:
308
  raise Exception(LOAD_ERROR)
 
379
 
380
  @app.api(name="generate")
381
  def _generate_api(prompt: str, image_path: FileData | None = None, last_image_path: FileData | None = None,
382
+ canvas: str = DEFAULT_CANVAS, duration: float = 5, steps: int = 4, seed: float = 42,
383
  upsample: bool = False, use_lora: bool = True, request: Request = None) -> tuple[FileData, str, str]:
384
  """Generate a video with a synchronized soundtrack. Returns (video, report, refined prompt)."""
385
  # `request` is injected by the event system, not an API input; its x-ip-token bills the conditioner to the caller.
h3_lora.py CHANGED
@@ -27,8 +27,11 @@ import os
27
  import torch
28
 
29
  LORA_REPO = os.environ.get("H3_LORA_REPO", "larryvrh/MiniMax-H3-Turbo-Lora")
30
- # The recommended default per the model card: ckpt500, non-EMA (newest, sharpest). `off` disables.
31
- LORA_FILE = os.environ.get("H3_LORA", "minimax_h3_turbo_4step_ckpt500.safetensors")
 
 
 
32
 
33
 
34
  def _delta_targets(name: str, delta: torch.Tensor, inner_dim: int) -> list[tuple[str, torch.Tensor]]:
@@ -79,7 +82,7 @@ def apply_lora(transformer) -> str | None:
79
  for name in bases:
80
  a = lora[f"{name}.lora_A.weight"].float()
81
  b = lora[f"{name}.lora_B.weight"].float()
82
- delta = b @ a # alpha == rank, so the scale is 1
83
  targets = _delta_targets(name, delta, inner_dim)
84
  for key, converted in targets:
85
  param = params.get(key)
@@ -94,12 +97,14 @@ def apply_lora(transformer) -> str | None:
94
  transformer._lora_state = {
95
  "enabled": True,
96
  "inner_dim": inner_dim,
 
97
  "entries": [
98
  (name, lora[f"{name}.lora_A.weight"], lora[f"{name}.lora_B.weight"])
99
  for name in entries
100
  ],
101
  }
102
- return f"LoRA `{LORA_REPO}/{LORA_FILE}` folded into {folded} weights ({len(bases)} modules)"
 
103
 
104
 
105
  def set_enabled(transformer, enabled: bool) -> bool:
@@ -109,7 +114,7 @@ def set_enabled(transformer, enabled: bool) -> bool:
109
  return state["enabled"] if state else False
110
  import torch
111
 
112
- sign = 1.0 if enabled else -1.0
113
  params = dict(transformer.named_parameters())
114
  for name, a, b in state["entries"]:
115
  delta = b.to(torch.float32) @ a.to(torch.float32)
 
27
  import torch
28
 
29
  LORA_REPO = os.environ.get("H3_LORA_REPO", "larryvrh/MiniMax-H3-Turbo-Lora")
30
+ # The recommended default per the model card: ckpt850 EMA (final checkpoint of the round, sharp at 4 steps).
31
+ # `off` disables.
32
+ LORA_FILE = os.environ.get("H3_LORA", "minimax_h3_turbo_4step_ema_ckpt850.safetensors")
33
+ # The card's sharpness/artifact dial: >1 against blurry ghosting/smear, <1 against over-sharp grain.
34
+ LORA_STRENGTH = float(os.environ.get("H3_LORA_STRENGTH", "1.0"))
35
 
36
 
37
  def _delta_targets(name: str, delta: torch.Tensor, inner_dim: int) -> list[tuple[str, torch.Tensor]]:
 
82
  for name in bases:
83
  a = lora[f"{name}.lora_A.weight"].float()
84
  b = lora[f"{name}.lora_B.weight"].float()
85
+ delta = (b @ a) * LORA_STRENGTH # alpha == rank, so the base scale is 1
86
  targets = _delta_targets(name, delta, inner_dim)
87
  for key, converted in targets:
88
  param = params.get(key)
 
97
  transformer._lora_state = {
98
  "enabled": True,
99
  "inner_dim": inner_dim,
100
+ "strength": LORA_STRENGTH,
101
  "entries": [
102
  (name, lora[f"{name}.lora_A.weight"], lora[f"{name}.lora_B.weight"])
103
  for name in entries
104
  ],
105
  }
106
+ strength = "" if LORA_STRENGTH == 1.0 else f" @ strength {LORA_STRENGTH}"
107
+ return f"LoRA `{LORA_REPO}/{LORA_FILE}`{strength} folded into {folded} weights ({len(bases)} modules)"
108
 
109
 
110
  def set_enabled(transformer, enabled: bool) -> bool:
 
114
  return state["enabled"] if state else False
115
  import torch
116
 
117
+ sign = (1.0 if enabled else -1.0) * state.get("strength", 1.0)
118
  params = dict(transformer.named_parameters())
119
  for name, a, b in state["entries"]:
120
  delta = b.to(torch.float32) @ a.to(torch.float32)
index.html CHANGED
@@ -211,8 +211,8 @@
211
  <input type="range" id="duration" min="2" max="14" step="1" value="5">
212
  </div>
213
  <div>
214
- <div class="slider-row"><span>Steps</span><output id="steps-out">8</output></div>
215
- <input type="range" id="steps" min="2" max="40" step="1" value="8">
216
  </div>
217
  <div>
218
  <div class="deck-label">Seed</div>
 
211
  <input type="range" id="duration" min="2" max="14" step="1" value="5">
212
  </div>
213
  <div>
214
+ <div class="slider-row"><span>Steps</span><output id="steps-out">4</output></div>
215
+ <input type="range" id="steps" min="2" max="40" step="1" value="4">
216
  </div>
217
  <div>
218
  <div class="deck-label">Seed</div>