Spaces:
Running on Zero
Running on Zero
Update app.py
Browse files
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 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 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 |
-
|
| 277 |
1
|
| 278 |
for module in pipe.transformer.modules()
|
| 279 |
if isinstance(module, BaseTunerLayer) and name in module.lora_A
|
| 280 |
)
|
| 281 |
-
if
|
| 282 |
-
raise RuntimeError(
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
|
|
|
| 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 |
-
|
| 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 |
-
|
| 444 |
-
# PEFT layer's active-adapter list and scale, no weight math — so it's safe
|
| 445 |
-
#
|
| 446 |
-
|
| 447 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 · "
|