dagloop5 commited on
Commit
915963a
·
verified ·
1 Parent(s): 306f6c8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +46 -31
app.py CHANGED
@@ -40,6 +40,12 @@ LORA_FILES = {
40
  "loraa": os.environ.get("H3_LORA_A_FILE", "Mylo_lora_epoch31.safetensors"),
41
  "lorab": os.environ.get("H3_LORA_B_FILE", "VBVR_H3_attn_only.safetensors"),
42
  }
 
 
 
 
 
 
43
  DEFAULT_LORA_A_STRENGTH = 0.0
44
  DEFAULT_LORA_B_STRENGTH = 0.0
45
  # Some `diffusion_model.blocks.*` checkpoints store SwiGLU's fc1 gate/value halves in the opposite order
@@ -172,6 +178,7 @@ FILM_ERROR: str | None = None
172
  LOAD_ERROR: str | None = None
173
  LOADED_IN: float | None = None
174
  LORA_STATUS: str | None = None
 
175
 
176
 
177
  def status() -> str:
@@ -260,12 +267,12 @@ def load_models() -> str | None:
260
  from peft.tuners.tuners_utils import BaseTunerLayer
261
  from safetensors import safe_open
262
 
263
- try:
264
- # Snapshot once, before either adapter attaches and wraps the target Linears — see the
265
- # docstring on `_convert_diffusion_model_lora` for why this can't be read fresh per-file.
266
- base_shapes = {k: tuple(v.shape) for k, v in pipe.transformer.state_dict().items()}
267
- counts = {}
268
- for name, filename in LORA_FILES.items():
269
  path = hf_hub_download(LORA_REPO, filename)
270
  with safe_open(path, framework="pt") as handle:
271
  raw = {k: handle.get_tensor(k) for k in handle.keys()}
@@ -273,24 +280,25 @@ def load_models() -> str | None:
273
  pipe.transformer.load_lora_adapter(converted, adapter_name=name, prefix=None)
274
  # `load_lora_adapter` warns-and-continues on a zero-key match instead of raising, so count
275
  # matched layers ourselves and fail loudly if a file attached nothing.
276
- counts[name] = sum(
277
  1
278
  for module in pipe.transformer.modules()
279
  if isinstance(module, BaseTunerLayer) and name in module.lora_A
280
  )
281
- if counts[name] == 0:
282
- raise RuntimeError(
283
- f"'{filename}' converted but matched 0 target modules on "
284
- f"MiniMaxH3Transformer3DModel the rename map needs another look."
285
- )
286
- pipe.transformer.set_adapters(list(LORA_FILES), weights=[0.0] * len(LORA_FILES))
287
- LORA_STATUS = "LoRAs loaded: " + ", ".join(
288
- f"`{name}` ({LORA_REPO}/{filename}, {counts[name]} layers)"
289
- for name, filename in LORA_FILES.items()
290
- )
291
- except Exception as error:
292
- LORA_STATUS = f"LoRA load failed: {type(error).__name__}: {error}"
293
- print(f"[gen] {LORA_STATUS}", flush=True)
 
294
 
295
  # Still startup, still free: an AoTI package carries no weights and opens its archive lazily inside the GPU
296
  # worker.
@@ -425,8 +433,7 @@ def _generate(
425
  sharpen,
426
  multiplier,
427
  seed,
428
- lora_a_strength,
429
- lora_b_strength,
430
  ):
431
  """The only thing on GPU time: the denoise loop, the two decoders and the workflow's post chain.
432
  The mp4 is muxed here rather than in the caller: a `@spaces.GPU` return crosses a process boundary by pickling,
@@ -440,11 +447,14 @@ def _generate(
440
 
441
  booked = time.time()
442
 
443
- # Approach B: blend the two resident LoRA adapters for this request. Cheap — `set_adapters` only updates each
444
- # PEFT layer's active-adapter list and scale, no weight math — so it's safe to call on every request. Guarded
445
- # in case loading failed or was disabled at startup, when the transformer carries no adapters at all.
446
- if hasattr(PIPE.transformer, "peft_config"):
447
- PIPE.transformer.set_adapters(list(LORA_FILES), weights=[float(lora_a_strength), float(lora_b_strength)])
 
 
 
448
 
449
  if PLACEMENT == "lazy":
450
  PIPE.to("cuda")
@@ -558,6 +568,10 @@ def generate(
558
  # prepares exactly this way.
559
  return ImageOps.exif_transpose(Image.open(path)).convert("RGB") if path else None
560
 
 
 
 
 
561
  progress(0.1, desc=f"Denoising {int(steps)} steps at {width}x{height}, {num_frames} frames ...")
562
  call = (
563
  prompt_embeds,
@@ -572,8 +586,7 @@ def generate(
572
  float(sharpen),
573
  multiplier,
574
  int(seed),
575
- float(lora_a_strength),
576
- float(lora_b_strength),
577
  )
578
  # The same call `spaces` will book the worker with, so the report can show the fit against the measurement.
579
  booked_seconds = get_duration(*call)
@@ -587,10 +600,12 @@ def generate(
587
 
588
  post = [f"RCAS {float(sharpen):.2f}" if float(sharpen) > 0 else "no sharpening"]
589
  post.append(f"FILM {multiplier}x -> {fps} fps" if multiplier > 1 else f"{fps} fps")
 
 
 
590
  report = (
591
  f"`{width}x{height}`, {num_frames} frames ({num_frames / FPS:.3f} s) -> {out_frames} frames at {fps} fps · "
592
- f"{int(steps)} steps of `{schedule_key}` · {' · '.join(post)} · seed {int(seed)} · "
593
- f"LoRA A {float(lora_a_strength):.2f} / B {float(lora_b_strength):.2f}\n\n"
594
  f"conditioner {condition_seconds:.0f}s ({plan['num_text_tokens']} tokens"
595
  f"{', upsampled' if refined else ''}) · denoise + decode {denoise_seconds:.0f}s "
596
  f"({denoise_seconds / max(1, int(steps)):.1f} s/step) · post {post_seconds:.0f}s · "
 
40
  "loraa": os.environ.get("H3_LORA_A_FILE", "Mylo_lora_epoch31.safetensors"),
41
  "lorab": os.environ.get("H3_LORA_B_FILE", "VBVR_H3_attn_only.safetensors"),
42
  }
43
+ # Display names, keyed the same as LORA_FILES — used in the UI slider labels, the per-request report line, and
44
+ # the status line's failure list. Keep these two dicts' keys in sync when adding a LoRA.
45
+ LORA_LABELS = {
46
+ "loraa": "Anthro Enhancer",
47
+ "lorab": "Reasoning Enhancer",
48
+ }
49
  DEFAULT_LORA_A_STRENGTH = 0.0
50
  DEFAULT_LORA_B_STRENGTH = 0.0
51
  # Some `diffusion_model.blocks.*` checkpoints store SwiGLU's fc1 gate/value halves in the opposite order
 
178
  LOAD_ERROR: str | None = None
179
  LOADED_IN: float | None = None
180
  LORA_STATUS: str | None = None
181
+ LOADED_LORAS: set[str] = set()
182
 
183
 
184
  def status() -> str:
 
267
  from peft.tuners.tuners_utils import BaseTunerLayer
268
  from safetensors import safe_open
269
 
270
+ # Snapshot once, before any adapter attaches and wraps the target Linears — see the docstring on
271
+ # `_convert_diffusion_model_lora` for why this can't be read fresh per-file.
272
+ base_shapes = {k: tuple(v.shape) for k, v in pipe.transformer.state_dict().items()}
273
+ failures = []
274
+ for name, filename in LORA_FILES.items():
275
+ try:
276
  path = hf_hub_download(LORA_REPO, filename)
277
  with safe_open(path, framework="pt") as handle:
278
  raw = {k: handle.get_tensor(k) for k in handle.keys()}
 
280
  pipe.transformer.load_lora_adapter(converted, adapter_name=name, prefix=None)
281
  # `load_lora_adapter` warns-and-continues on a zero-key match instead of raising, so count
282
  # matched layers ourselves and fail loudly if a file attached nothing.
283
+ matched = sum(
284
  1
285
  for module in pipe.transformer.modules()
286
  if isinstance(module, BaseTunerLayer) and name in module.lora_A
287
  )
288
+ if matched == 0:
289
+ raise RuntimeError(f"'{filename}' converted but matched 0 target modules")
290
+ LOADED_LORAS.add(name)
291
+ except Exception as error:
292
+ failures.append(f"`{LORA_LABELS.get(name, name)}` ({type(error).__name__}: {error})")
293
+ print(
294
+ f"[gen] LoRA '{name}' ({filename}) failed to load: {type(error).__name__}: {error}",
295
+ flush=True,
296
+ )
297
+
298
+ if LOADED_LORAS:
299
+ pipe.transformer.set_adapters(list(LOADED_LORAS), weights=[0.0] * len(LOADED_LORAS))
300
+ LORA_STATUS = "All LoRAs loaded" if not failures else "LoRA issues: " + "; ".join(failures)
301
+ print(f"[gen] {LORA_STATUS}", flush=True)
302
 
303
  # Still startup, still free: an AoTI package carries no weights and opens its archive lazily inside the GPU
304
  # worker.
 
433
  sharpen,
434
  multiplier,
435
  seed,
436
+ lora_strengths,
 
437
  ):
438
  """The only thing on GPU time: the denoise loop, the two decoders and the workflow's post chain.
439
  The mp4 is muxed here rather than in the caller: a `@spaces.GPU` return crosses a process boundary by pickling,
 
447
 
448
  booked = time.time()
449
 
450
+ # Approach B: blend whichever resident LoRA adapters actually loaded, for this request. Cheap —
451
+ # `set_adapters` only updates each PEFT layer's active-adapter list and scale, no weight math — so it's safe
452
+ # to call on every request. Filtered to `LOADED_LORAS`: a slider for a LoRA that failed at startup has no
453
+ # adapter behind it, and `set_adapters` would raise if asked to activate a name that was never attached.
454
+ if LOADED_LORAS:
455
+ active = {name: strength for name, strength in lora_strengths.items() if name in LOADED_LORAS}
456
+ if active:
457
+ PIPE.transformer.set_adapters(list(active), weights=list(active.values()))
458
 
459
  if PLACEMENT == "lazy":
460
  PIPE.to("cuda")
 
568
  # prepares exactly this way.
569
  return ImageOps.exif_transpose(Image.open(path)).convert("RGB") if path else None
570
 
571
+ # Every UI LoRA slider gets packed into one dict here — this is the only place a new LoRA's slider value
572
+ # needs wiring in; `_generate`, `set_adapters`, and the report line below are all keyed off this dict.
573
+ lora_strengths = {"loraa": float(lora_a_strength), "lorab": float(lora_b_strength)}
574
+
575
  progress(0.1, desc=f"Denoising {int(steps)} steps at {width}x{height}, {num_frames} frames ...")
576
  call = (
577
  prompt_embeds,
 
586
  float(sharpen),
587
  multiplier,
588
  int(seed),
589
+ lora_strengths,
 
590
  )
591
  # The same call `spaces` will book the worker with, so the report can show the fit against the measurement.
592
  booked_seconds = get_duration(*call)
 
600
 
601
  post = [f"RCAS {float(sharpen):.2f}" if float(sharpen) > 0 else "no sharpening"]
602
  post.append(f"FILM {multiplier}x -> {fps} fps" if multiplier > 1 else f"{fps} fps")
603
+ lora_text = " / ".join(
604
+ f"{LORA_LABELS.get(name, name)} {strength:.2f}" for name, strength in lora_strengths.items()
605
+ )
606
  report = (
607
  f"`{width}x{height}`, {num_frames} frames ({num_frames / FPS:.3f} s) -> {out_frames} frames at {fps} fps · "
608
+ f"{int(steps)} steps of `{schedule_key}` · {' · '.join(post)} · seed {int(seed)} · {lora_text}\n\n"
 
609
  f"conditioner {condition_seconds:.0f}s ({plan['num_text_tokens']} tokens"
610
  f"{', upsampled' if refined else ''}) · denoise + decode {denoise_seconds:.0f}s "
611
  f"({denoise_seconds / max(1, int(steps)):.1f} s/step) · post {post_seconds:.0f}s · "