Spaces:
Sleeping
Sleeping
P15B: repair active-adapter API and match Ref2VA geometry at 1024px
Browse filesTechnical retry repair only: same model, adapter, prompt, reference order, seed, steps, duration and generate_p15b route. Fix callable active_adapters and remove 2048px split-half mismatch.
- app.py +31 -16
- h3_split_blocks.py +25 -2
app.py
CHANGED
|
@@ -25,10 +25,10 @@ import gradio as gr
|
|
| 25 |
|
| 26 |
MODEL_REPO = "MiniMaxAI/MiniMax-H3"
|
| 27 |
MODEL_REVISION = "939557dc319dd91227e30195a763f272ba7f8765"
|
| 28 |
-
CONDITIONER_SPACE = "
|
| 29 |
-
#
|
| 30 |
-
#
|
| 31 |
-
CONDITIONER_REVISION = "
|
| 32 |
CONDITIONER_ENDPOINT = "/encode_ref2va"
|
| 33 |
P15B_ADAPTER_REPO = "Inner-Reflections/MiniMax-H3-Looping-Sketch-Anime"
|
| 34 |
P15B_ADAPTER_FILE = "minimax_h3_looping_sketch_anime_v1.safetensors"
|
|
@@ -49,10 +49,11 @@ GPU_SIZE = os.environ.get("H3_GPU_SIZE", "xlarge")
|
|
| 49 |
# Bounds on what `get_duration` may reserve. The pool reserves whatever number it is given, so a flat ceiling for every
|
| 50 |
# request is what makes an account hit "too many ZeroGPU credits allocated to running tasks".
|
| 51 |
MIN_GPU_DURATION = int(os.environ.get("H3_GPU_DURATION_MIN", "120"))
|
| 52 |
-
#
|
| 53 |
-
#
|
| 54 |
-
#
|
| 55 |
MAX_GPU_DURATION = 1100
|
|
|
|
| 56 |
|
| 57 |
# Must stay identical to the conditioner's table: the *label* goes over the wire, so a canvas that half does not know
|
| 58 |
# is rejected there and surfaces as a failure here.
|
|
@@ -231,7 +232,7 @@ def get_duration(
|
|
| 231 |
encode = 5 + reference_rows(references, num_frames) * 1e-3
|
| 232 |
decode = DECODE_BASE + DECODE_PER_DEFAULT_CANVAS * (height * width * num_frames) / DEFAULT_CANVAS_PIXELS
|
| 233 |
total = PLACEMENT_ALLOWANCE + encode + denoise + decode + 10 + LORA_ALLOWANCE * len(loras or ())
|
| 234 |
-
duration = max(MIN_GPU_DURATION, min(MAX_GPU_DURATION, int(total)))
|
| 235 |
print(f"[ref2va] S={sequence} -> reserving {duration}s ({denoise:.0f}s of denoise at {steps} steps)", flush=True)
|
| 236 |
return duration
|
| 237 |
|
|
@@ -642,7 +643,10 @@ def apply_loras(transformer, loras) -> list[str]:
|
|
| 642 |
|
| 643 |
transformer.set_adapters(names, scales)
|
| 644 |
if P15B_ADAPTER_NAME in names:
|
| 645 |
-
|
|
|
|
|
|
|
|
|
|
| 646 |
if P15B_ADAPTER_NAME not in active:
|
| 647 |
raise RuntimeError(
|
| 648 |
f"P15B_ADAPTER_NOT_ACTIVE: expected={P15B_ADAPTER_NAME} active={active} target=transformer_ref"
|
|
@@ -835,7 +839,7 @@ def _prepare_reference_files(references: list[tuple[str, str]]) -> list[tuple[st
|
|
| 835 |
|
| 836 |
|
| 837 |
def build_references(references: list[tuple[str, str]]):
|
| 838 |
-
"""Decode references in
|
| 839 |
from diffusers.modular_pipelines.minimax_h3 import (
|
| 840 |
MiniMaxH3AudioReference,
|
| 841 |
MiniMaxH3ImageReference,
|
|
@@ -843,7 +847,7 @@ def build_references(references: list[tuple[str, str]]):
|
|
| 843 |
)
|
| 844 |
|
| 845 |
classes = {"image": MiniMaxH3ImageReference, "video": MiniMaxH3VideoReference, "audio": MiniMaxH3AudioReference}
|
| 846 |
-
return [classes[kind].from_file(path) for kind, path in
|
| 847 |
|
| 848 |
|
| 849 |
def audio_bearing(references: list[tuple[str, str]]) -> list[tuple[str, float]]:
|
|
@@ -978,8 +982,11 @@ def generate(
|
|
| 978 |
|
| 979 |
from diffusers.utils import encode_video
|
| 980 |
|
| 981 |
-
images = [image_1, image_2, image_3, image_4, image_5, image_6, image_7, image_8, image_9]
|
| 982 |
-
|
|
|
|
|
|
|
|
|
|
| 983 |
check(prompt, references)
|
| 984 |
|
| 985 |
# `0` is "leave it to the references" over the wire, which MiniMax-H3 accepts when exactly one of them carries a
|
|
@@ -1226,13 +1233,21 @@ def _p15b_reservation(scene: str, image_paths: list[str], video_path: str) -> di
|
|
| 1226 |
denoise = int(math.ceil(P15B_STEPS * (STEP_LINEAR * sequence + STEP_QUADRATIC * sequence**2) * SAFETY))
|
| 1227 |
encode = 5 + ref_rows * 1e-3
|
| 1228 |
decode = DECODE_BASE + DECODE_PER_DEFAULT_CANVAS * (height * width * num_frames) / DEFAULT_CANVAS_PIXELS
|
| 1229 |
-
provider = max(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1230 |
return {
|
| 1231 |
"scene": scene,
|
| 1232 |
"provider_reservation_seconds": provider,
|
| 1233 |
"xlarge_quota_multiplier": 2,
|
| 1234 |
-
|
| 1235 |
-
"
|
|
|
|
| 1236 |
"text_token_bound": P15B_TEXT_TOKEN_BOUND,
|
| 1237 |
"sequence_rows_bound": sequence,
|
| 1238 |
"reference_rows": ref_rows,
|
|
|
|
| 25 |
|
| 26 |
MODEL_REPO = "MiniMaxAI/MiniMax-H3"
|
| 27 |
MODEL_REVISION = "939557dc319dd91227e30195a763f272ba7f8765"
|
| 28 |
+
CONDITIONER_SPACE = "ifroyou/minimax-h3-conditioner-p15b-1024"
|
| 29 |
+
# Source-faithful fork of multimodalart/qwen3vl-conditioner at be67d10c, pinned to the same MiniMax-H3 revision and
|
| 30 |
+
# /encode_ref2va schema. The only inference change is the packet-locked 1024px Ref2VA image short edge.
|
| 31 |
+
CONDITIONER_REVISION = "21e66c83442369d6ae3a7a50b020da039656db54"
|
| 32 |
CONDITIONER_ENDPOINT = "/encode_ref2va"
|
| 33 |
P15B_ADAPTER_REPO = "Inner-Reflections/MiniMax-H3-Looping-Sketch-Anime"
|
| 34 |
P15B_ADAPTER_FILE = "minimax_h3_looping_sketch_anime_v1.safetensors"
|
|
|
|
| 49 |
# Bounds on what `get_duration` may reserve. The pool reserves whatever number it is given, so a flat ceiling for every
|
| 50 |
# request is what makes an account hit "too many ZeroGPU credits allocated to running tasks".
|
| 51 |
MIN_GPU_DURATION = int(os.environ.get("H3_GPU_DURATION_MIN", "120"))
|
| 52 |
+
# `MAX_GPU_DURATION` remains the route-wide safety ceiling. P15B has a tighter, packet-specific ceiling because its
|
| 53 |
+
# 1024px references and frozen 4–5 second takes were analytically bounded below 950 seconds. Keeping this lower
|
| 54 |
+
# ceiling in the GPU decorator is what makes the client-side quota receipt enforceable rather than aspirational.
|
| 55 |
MAX_GPU_DURATION = 1100
|
| 56 |
+
P15B_MAX_GPU_DURATION = int(os.environ.get("P15B_MAX_GPU_DURATION", "944"))
|
| 57 |
|
| 58 |
# Must stay identical to the conditioner's table: the *label* goes over the wire, so a canvas that half does not know
|
| 59 |
# is rejected there and surfaces as a failure here.
|
|
|
|
| 232 |
encode = 5 + reference_rows(references, num_frames) * 1e-3
|
| 233 |
decode = DECODE_BASE + DECODE_PER_DEFAULT_CANVAS * (height * width * num_frames) / DEFAULT_CANVAS_PIXELS
|
| 234 |
total = PLACEMENT_ALLOWANCE + encode + denoise + decode + 10 + LORA_ALLOWANCE * len(loras or ())
|
| 235 |
+
duration = max(MIN_GPU_DURATION, min(MAX_GPU_DURATION, P15B_MAX_GPU_DURATION, int(total)))
|
| 236 |
print(f"[ref2va] S={sequence} -> reserving {duration}s ({denoise:.0f}s of denoise at {steps} steps)", flush=True)
|
| 237 |
return duration
|
| 238 |
|
|
|
|
| 643 |
|
| 644 |
transformer.set_adapters(names, scales)
|
| 645 |
if P15B_ADAPTER_NAME in names:
|
| 646 |
+
# Diffusers/PEFT exposes this as a property in some releases and a bound method in others. Calling only the
|
| 647 |
+
# latter avoids the deterministic TypeError that occurred after the real adapter had already been loaded.
|
| 648 |
+
active_value = getattr(transformer, "active_adapters", [])
|
| 649 |
+
active = list(active_value() if callable(active_value) else (active_value or []))
|
| 650 |
if P15B_ADAPTER_NAME not in active:
|
| 651 |
raise RuntimeError(
|
| 652 |
f"P15B_ADAPTER_NOT_ACTIVE: expected={P15B_ADAPTER_NAME} active={active} target=transformer_ref"
|
|
|
|
| 839 |
|
| 840 |
|
| 841 |
def build_references(references: list[tuple[str, str]]):
|
| 842 |
+
"""Decode the already-normalized references in the exact packed order."""
|
| 843 |
from diffusers.modular_pipelines.minimax_h3 import (
|
| 844 |
MiniMaxH3AudioReference,
|
| 845 |
MiniMaxH3ImageReference,
|
|
|
|
| 847 |
)
|
| 848 |
|
| 849 |
classes = {"image": MiniMaxH3ImageReference, "video": MiniMaxH3VideoReference, "audio": MiniMaxH3AudioReference}
|
| 850 |
+
return [classes[kind].from_file(path) for kind, path in references]
|
| 851 |
|
| 852 |
|
| 853 |
def audio_bearing(references: list[tuple[str, str]]) -> list[tuple[str, float]]:
|
|
|
|
| 982 |
|
| 983 |
from diffusers.utils import encode_video
|
| 984 |
|
| 985 |
+
images = [image_1, image_2, image_3, image_4, image_5, image_6, image_7, image_8, image_9]
|
| 986 |
+
# The same normalized files must go to both split halves. Previously the generator capped its copy but the
|
| 987 |
+
# conditioner received full-size endpoints and silently rebuilt 2048px vision blocks, so the actual sequence
|
| 988 |
+
# exceeded the P15B reservation before denoising began.
|
| 989 |
+
references = _prepare_reference_files(collect(images, audio_path, video_path))
|
| 990 |
check(prompt, references)
|
| 991 |
|
| 992 |
# `0` is "leave it to the references" over the wire, which MiniMax-H3 accepts when exactly one of them carries a
|
|
|
|
| 1233 |
denoise = int(math.ceil(P15B_STEPS * (STEP_LINEAR * sequence + STEP_QUADRATIC * sequence**2) * SAFETY))
|
| 1234 |
encode = 5 + ref_rows * 1e-3
|
| 1235 |
decode = DECODE_BASE + DECODE_PER_DEFAULT_CANVAS * (height * width * num_frames) / DEFAULT_CANVAS_PIXELS
|
| 1236 |
+
provider = max(
|
| 1237 |
+
MIN_GPU_DURATION,
|
| 1238 |
+
min(
|
| 1239 |
+
MAX_GPU_DURATION,
|
| 1240 |
+
P15B_MAX_GPU_DURATION,
|
| 1241 |
+
int(math.ceil(PLACEMENT_ALLOWANCE + encode + denoise + decode + 10 + LORA_ALLOWANCE)),
|
| 1242 |
+
),
|
| 1243 |
+
)
|
| 1244 |
return {
|
| 1245 |
"scene": scene,
|
| 1246 |
"provider_reservation_seconds": provider,
|
| 1247 |
"xlarge_quota_multiplier": 2,
|
| 1248 |
+
# The dedicated conditioner endpoint is hard-decorated at 45 xlarge seconds, hence at most 90 quota seconds.
|
| 1249 |
+
"conditioner_allowance_seconds": 90,
|
| 1250 |
+
"total_quota_reservation_seconds": provider * 2 + 90,
|
| 1251 |
"text_token_bound": P15B_TEXT_TOKEN_BOUND,
|
| 1252 |
"sequence_rows_bound": sequence,
|
| 1253 |
"reference_rows": ref_rows,
|
h3_split_blocks.py
CHANGED
|
@@ -31,6 +31,29 @@ from diffusers.modular_pipelines.modular_pipeline import SequentialPipelineBlock
|
|
| 31 |
from diffusers.modular_pipelines.modular_pipeline_utils import OutputParam
|
| 32 |
|
| 33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
def _wire_outputs(num_frames: bool = True) -> list[OutputParam]:
|
| 35 |
"""The wire format of the split. `num_frames` is declared by the `ref2va` half alone, whose setup resolves one."""
|
| 36 |
return [
|
|
@@ -101,7 +124,7 @@ class MiniMaxH3Ref2VAConditionerBlocks(SequentialPipelineBlocks):
|
|
| 101 |
"""
|
| 102 |
|
| 103 |
model_name = "minimax-h3"
|
| 104 |
-
block_classes = [
|
| 105 |
block_names = ["setup", "text_encoder"]
|
| 106 |
|
| 107 |
@property
|
|
@@ -126,7 +149,7 @@ class MiniMaxH3Ref2VAGeneratorBlocks(SequentialPipelineBlocks):
|
|
| 126 |
|
| 127 |
model_name = "minimax-h3"
|
| 128 |
block_classes = [
|
| 129 |
-
|
| 130 |
MiniMaxH3Ref2VAReferenceEncoderStep,
|
| 131 |
MiniMaxH3Ref2VACoreDenoiseStep,
|
| 132 |
MiniMaxH3AfterDenoiseStep,
|
|
|
|
| 31 |
from diffusers.modular_pipelines.modular_pipeline_utils import OutputParam
|
| 32 |
|
| 33 |
|
| 34 |
+
P15B_REFERENCE_IMAGE_SHORT_EDGE = 1024
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class P15BRef2VASetupStep(MiniMaxH3Ref2VASetupStep):
|
| 38 |
+
"""P15B's Ref2VA setup uses the packet-locked 1024px image short edge, never the upstream 2048px default."""
|
| 39 |
+
|
| 40 |
+
def __init__(
|
| 41 |
+
self,
|
| 42 |
+
max_images: int = 9,
|
| 43 |
+
max_videos: int = 3,
|
| 44 |
+
max_audios: int = 3,
|
| 45 |
+
max_references: int = 12,
|
| 46 |
+
reference_image_short_edge: int = P15B_REFERENCE_IMAGE_SHORT_EDGE,
|
| 47 |
+
):
|
| 48 |
+
super().__init__(
|
| 49 |
+
max_images=max_images,
|
| 50 |
+
max_videos=max_videos,
|
| 51 |
+
max_audios=max_audios,
|
| 52 |
+
max_references=max_references,
|
| 53 |
+
reference_image_short_edge=reference_image_short_edge,
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
def _wire_outputs(num_frames: bool = True) -> list[OutputParam]:
|
| 58 |
"""The wire format of the split. `num_frames` is declared by the `ref2va` half alone, whose setup resolves one."""
|
| 59 |
return [
|
|
|
|
| 124 |
"""
|
| 125 |
|
| 126 |
model_name = "minimax-h3"
|
| 127 |
+
block_classes = [P15BRef2VASetupStep, MiniMaxH3Ref2VATextEncoderStep]
|
| 128 |
block_names = ["setup", "text_encoder"]
|
| 129 |
|
| 130 |
@property
|
|
|
|
| 149 |
|
| 150 |
model_name = "minimax-h3"
|
| 151 |
block_classes = [
|
| 152 |
+
P15BRef2VASetupStep,
|
| 153 |
MiniMaxH3Ref2VAReferenceEncoderStep,
|
| 154 |
MiniMaxH3Ref2VACoreDenoiseStep,
|
| 155 |
MiniMaxH3AfterDenoiseStep,
|