Spaces:
Running on Zero
Running on Zero
Update app.py
Browse files
app.py
CHANGED
|
@@ -33,6 +33,14 @@ PLACEMENT = os.environ.get("H3_PLACEMENT", "pack").lower()
|
|
| 33 |
ATTENTION = os.environ.get("H3_ATTENTION", "_native_cudnn").lower()
|
| 34 |
GPU_SIZE = os.environ.get("H3_GPU_SIZE", "xlarge")
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
# Must stay identical to the conditioner's table: the *label* goes over the wire, so a canvas that half does not
|
| 37 |
# know is rejected there and surfaces as a failure here. This is the workflow's "Target Dimension" node.
|
| 38 |
CANVASES = {
|
|
@@ -101,6 +109,7 @@ FILM = None
|
|
| 101 |
FILM_ERROR: str | None = None
|
| 102 |
LOAD_ERROR: str | None = None
|
| 103 |
LOADED_IN: float | None = None
|
|
|
|
| 104 |
|
| 105 |
|
| 106 |
def status() -> str:
|
|
@@ -113,8 +122,8 @@ def status() -> str:
|
|
| 113 |
film = "FILM **ready**" if FILM is not None else f"FILM **off** ({FILM_ERROR})"
|
| 114 |
return (
|
| 115 |
f"Ready · transformer + VAEs **bfloat16, unquantized** · placement `{PLACEMENT}` · attention "
|
| 116 |
-
f"`{ATTENTION}` · {h3_aoti.status()} · {film} · loaded in {LOADED_IN:.0f}s ·
|
| 117 |
-
f"`{CONDITIONER_SPACE}`"
|
| 118 |
)
|
| 119 |
|
| 120 |
|
|
@@ -125,7 +134,7 @@ def load_models() -> str | None:
|
|
| 125 |
touched. Both autoencoders carry `_keep_in_fp32_modules` over every module and stay float32: a bfloat16 audio
|
| 126 |
VAE decodes the soundtrack roughly 20 dB too quiet.
|
| 127 |
"""
|
| 128 |
-
global PIPE, FILM, FILM_ERROR, LOAD_ERROR, LOADED_IN
|
| 129 |
|
| 130 |
if PIPE is not None or LOAD_ERROR is not None:
|
| 131 |
return LOAD_ERROR
|
|
@@ -145,6 +154,21 @@ def load_models() -> str | None:
|
|
| 145 |
pipe.load_components(dtype=torch.bfloat16)
|
| 146 |
pipe.transformer.set_attention_backend(ATTENTION)
|
| 147 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
# Still startup, still free: an AoTI package carries no weights and opens its archive lazily inside the GPU
|
| 149 |
# worker.
|
| 150 |
import h3_aoti
|
|
@@ -278,6 +302,8 @@ def _generate(
|
|
| 278 |
sharpen,
|
| 279 |
multiplier,
|
| 280 |
seed,
|
|
|
|
|
|
|
| 281 |
):
|
| 282 |
"""The only thing on GPU time: the denoise loop, the two decoders and the workflow's post chain.
|
| 283 |
The mp4 is muxed here rather than in the caller: a `@spaces.GPU` return crosses a process boundary by pickling,
|
|
@@ -290,6 +316,13 @@ def _generate(
|
|
| 290 |
global FILM
|
| 291 |
|
| 292 |
booked = time.time()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 293 |
if PLACEMENT == "lazy":
|
| 294 |
PIPE.to("cuda")
|
| 295 |
elif PLACEMENT == "pack":
|
|
@@ -361,6 +394,8 @@ def generate(
|
|
| 361 |
interpolation=DEFAULT_INTERPOLATION,
|
| 362 |
seed=42,
|
| 363 |
upsample=False,
|
|
|
|
|
|
|
| 364 |
progress=gr.Progress(track_tqdm=True),
|
| 365 |
):
|
| 366 |
"""One request through the PlagueKind graph. Every parameter but the prompt carries the default its UI
|
|
@@ -414,6 +449,8 @@ def generate(
|
|
| 414 |
float(sharpen),
|
| 415 |
multiplier,
|
| 416 |
int(seed),
|
|
|
|
|
|
|
| 417 |
)
|
| 418 |
# The same call `spaces` will book the worker with, so the report can show the fit against the measurement.
|
| 419 |
booked_seconds = get_duration(*call)
|
|
@@ -429,7 +466,8 @@ def generate(
|
|
| 429 |
post.append(f"FILM {multiplier}x -> {fps} fps" if multiplier > 1 else f"{fps} fps")
|
| 430 |
report = (
|
| 431 |
f"`{width}x{height}`, {num_frames} frames ({num_frames / FPS:.3f} s) -> {out_frames} frames at {fps} fps · "
|
| 432 |
-
f"{int(steps)} steps of `{schedule_key}` · {' · '.join(post)} · seed {int(seed)}
|
|
|
|
| 433 |
f"conditioner {condition_seconds:.0f}s ({plan['num_text_tokens']} tokens"
|
| 434 |
f"{', upsampled' if refined else ''}) · denoise + decode {denoise_seconds:.0f}s "
|
| 435 |
f"({denoise_seconds / max(1, int(steps)):.1f} s/step) · post {post_seconds:.0f}s · "
|
|
@@ -537,6 +575,20 @@ with gr.Blocks(title="PlagueKind · MiniMax-H3") as demo:
|
|
| 537 |
value=DEFAULT_STEPS,
|
| 538 |
info="PlagueKind: 15-20 on the linear_quadratic grid.",
|
| 539 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 540 |
schedule = gr.Dropdown(
|
| 541 |
label="Sigma schedule",
|
| 542 |
choices=list(SCHEDULES),
|
|
@@ -583,6 +635,8 @@ with gr.Blocks(title="PlagueKind · MiniMax-H3") as demo:
|
|
| 583 |
interpolation,
|
| 584 |
seed,
|
| 585 |
upsample,
|
|
|
|
|
|
|
| 586 |
]
|
| 587 |
|
| 588 |
# Two blocks rather than one with empty keyframe cells: the positional order below matches `generate`'s
|
|
|
|
| 33 |
ATTENTION = os.environ.get("H3_ATTENTION", "_native_cudnn").lower()
|
| 34 |
GPU_SIZE = os.environ.get("H3_GPU_SIZE", "xlarge")
|
| 35 |
|
| 36 |
+
LORA_REPO = os.environ.get("H3_LORA_REPO", "dagloop5/LoRA")
|
| 37 |
+
LORA_FILES = {
|
| 38 |
+
"loraa": os.environ.get("H3_LORA_A_FILE", "Mylo_lora_epoch31.safetensors"),
|
| 39 |
+
"lorab": os.environ.get("H3_LORA_B_FILE", "VBVR_H3_attn_only.safetensors"),
|
| 40 |
+
}
|
| 41 |
+
DEFAULT_LORA_A_STRENGTH = 0.0
|
| 42 |
+
DEFAULT_LORA_B_STRENGTH = 0.0
|
| 43 |
+
|
| 44 |
# Must stay identical to the conditioner's table: the *label* goes over the wire, so a canvas that half does not
|
| 45 |
# know is rejected there and surfaces as a failure here. This is the workflow's "Target Dimension" node.
|
| 46 |
CANVASES = {
|
|
|
|
| 109 |
FILM_ERROR: str | None = None
|
| 110 |
LOAD_ERROR: str | None = None
|
| 111 |
LOADED_IN: float | None = None
|
| 112 |
+
LORA_STATUS: str | None = None
|
| 113 |
|
| 114 |
|
| 115 |
def status() -> str:
|
|
|
|
| 122 |
film = "FILM **ready**" if FILM is not None else f"FILM **off** ({FILM_ERROR})"
|
| 123 |
return (
|
| 124 |
f"Ready · transformer + VAEs **bfloat16, unquantized** · placement `{PLACEMENT}` · attention "
|
| 125 |
+
f"`{ATTENTION}` · {h3_aoti.status()} · {film} · {LORA_STATUS or 'no LoRA'} · loaded in {LOADED_IN:.0f}s · "
|
| 126 |
+
f"conditioner `{CONDITIONER_SPACE}`"
|
| 127 |
)
|
| 128 |
|
| 129 |
|
|
|
|
| 134 |
touched. Both autoencoders carry `_keep_in_fp32_modules` over every module and stay float32: a bfloat16 audio
|
| 135 |
VAE decodes the soundtrack roughly 20 dB too quiet.
|
| 136 |
"""
|
| 137 |
+
global PIPE, FILM, FILM_ERROR, LOAD_ERROR, LOADED_IN, LORA_STATUS
|
| 138 |
|
| 139 |
if PIPE is not None or LOAD_ERROR is not None:
|
| 140 |
return LOAD_ERROR
|
|
|
|
| 154 |
pipe.load_components(dtype=torch.bfloat16)
|
| 155 |
pipe.transformer.set_attention_backend(ATTENTION)
|
| 156 |
|
| 157 |
+
# Approach B: attach both LoRA adapters as PEFT layers on the transformer, inactive (weight 0) until a
|
| 158 |
+
# request asks for them. `load_lora_adapter` is the model-level loader (`PeftAdapterMixin`), used because
|
| 159 |
+
# `MiniMaxH3ModularPipeline` has no pipeline-level `load_lora_weights` of its own.
|
| 160 |
+
if LORA_REPO.lower() not in ("", "off", "none"):
|
| 161 |
+
try:
|
| 162 |
+
for name, filename in LORA_FILES.items():
|
| 163 |
+
pipe.transformer.load_lora_adapter(LORA_REPO, weight_name=filename, adapter_name=name)
|
| 164 |
+
pipe.transformer.set_adapters(list(LORA_FILES), weights=[0.0] * len(LORA_FILES))
|
| 165 |
+
LORA_STATUS = "LoRAs loaded: " + ", ".join(
|
| 166 |
+
f"`{name}` ({LORA_REPO}/{filename})" for name, filename in LORA_FILES.items()
|
| 167 |
+
)
|
| 168 |
+
except Exception as error:
|
| 169 |
+
LORA_STATUS = f"LoRA load failed: {type(error).__name__}: {error}"
|
| 170 |
+
print(f"[gen] {LORA_STATUS}", flush=True)
|
| 171 |
+
|
| 172 |
# Still startup, still free: an AoTI package carries no weights and opens its archive lazily inside the GPU
|
| 173 |
# worker.
|
| 174 |
import h3_aoti
|
|
|
|
| 302 |
sharpen,
|
| 303 |
multiplier,
|
| 304 |
seed,
|
| 305 |
+
lora_a_strength,
|
| 306 |
+
lora_b_strength,
|
| 307 |
):
|
| 308 |
"""The only thing on GPU time: the denoise loop, the two decoders and the workflow's post chain.
|
| 309 |
The mp4 is muxed here rather than in the caller: a `@spaces.GPU` return crosses a process boundary by pickling,
|
|
|
|
| 316 |
global FILM
|
| 317 |
|
| 318 |
booked = time.time()
|
| 319 |
+
|
| 320 |
+
# Approach B: blend the two resident LoRA adapters for this request. Cheap — `set_adapters` only updates each
|
| 321 |
+
# PEFT layer's active-adapter list and scale, no weight math — so it's safe to call on every request. Guarded
|
| 322 |
+
# in case loading failed or was disabled at startup, when the transformer carries no adapters at all.
|
| 323 |
+
if hasattr(PIPE.transformer, "peft_config"):
|
| 324 |
+
PIPE.transformer.set_adapters(list(LORA_FILES), weights=[float(lora_a_strength), float(lora_b_strength)])
|
| 325 |
+
|
| 326 |
if PLACEMENT == "lazy":
|
| 327 |
PIPE.to("cuda")
|
| 328 |
elif PLACEMENT == "pack":
|
|
|
|
| 394 |
interpolation=DEFAULT_INTERPOLATION,
|
| 395 |
seed=42,
|
| 396 |
upsample=False,
|
| 397 |
+
lora_a_strength=DEFAULT_LORA_A_STRENGTH,
|
| 398 |
+
lora_b_strength=DEFAULT_LORA_B_STRENGTH,
|
| 399 |
progress=gr.Progress(track_tqdm=True),
|
| 400 |
):
|
| 401 |
"""One request through the PlagueKind graph. Every parameter but the prompt carries the default its UI
|
|
|
|
| 449 |
float(sharpen),
|
| 450 |
multiplier,
|
| 451 |
int(seed),
|
| 452 |
+
float(lora_a_strength),
|
| 453 |
+
float(lora_b_strength),
|
| 454 |
)
|
| 455 |
# The same call `spaces` will book the worker with, so the report can show the fit against the measurement.
|
| 456 |
booked_seconds = get_duration(*call)
|
|
|
|
| 466 |
post.append(f"FILM {multiplier}x -> {fps} fps" if multiplier > 1 else f"{fps} fps")
|
| 467 |
report = (
|
| 468 |
f"`{width}x{height}`, {num_frames} frames ({num_frames / FPS:.3f} s) -> {out_frames} frames at {fps} fps · "
|
| 469 |
+
f"{int(steps)} steps of `{schedule_key}` · {' · '.join(post)} · seed {int(seed)} · "
|
| 470 |
+
f"LoRA A {float(lora_a_strength):.2f} / B {float(lora_b_strength):.2f}\n\n"
|
| 471 |
f"conditioner {condition_seconds:.0f}s ({plan['num_text_tokens']} tokens"
|
| 472 |
f"{', upsampled' if refined else ''}) · denoise + decode {denoise_seconds:.0f}s "
|
| 473 |
f"({denoise_seconds / max(1, int(steps)):.1f} s/step) · post {post_seconds:.0f}s · "
|
|
|
|
| 575 |
value=DEFAULT_STEPS,
|
| 576 |
info="PlagueKind: 15-20 on the linear_quadratic grid.",
|
| 577 |
)
|
| 578 |
+
lora_a_strength = gr.Slider(
|
| 579 |
+
label="Anthro Enhancer LoRA",
|
| 580 |
+
minimum=0.0,
|
| 581 |
+
maximum=2.0,
|
| 582 |
+
step=0.05,
|
| 583 |
+
value=DEFAULT_LORA_A_STRENGTH,
|
| 584 |
+
)
|
| 585 |
+
lora_b_strength = gr.Slider(
|
| 586 |
+
label="Reasoning Enhancer LoRA",
|
| 587 |
+
minimum=0.0,
|
| 588 |
+
maximum=2.0,
|
| 589 |
+
step=0.05,
|
| 590 |
+
value=DEFAULT_LORA_B_STRENGTH,
|
| 591 |
+
)
|
| 592 |
schedule = gr.Dropdown(
|
| 593 |
label="Sigma schedule",
|
| 594 |
choices=list(SCHEDULES),
|
|
|
|
| 635 |
interpolation,
|
| 636 |
seed,
|
| 637 |
upsample,
|
| 638 |
+
lora_a_strength,
|
| 639 |
+
lora_b_strength,
|
| 640 |
]
|
| 641 |
|
| 642 |
# Two blocks rather than one with empty keyframe cells: the positional order below matches `generate`'s
|