ifroyou commited on
Commit
505f5e8
·
verified ·
1 Parent(s): f7d7cd1

P15B: repair active-adapter API and match Ref2VA geometry at 1024px

Browse files

Technical 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.

Files changed (2) hide show
  1. app.py +31 -16
  2. 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 = "multimodalart/qwen3vl-conditioner"
29
- # The packet's original conditioner SHA (20d92357...) is no longer the live Space HEAD. The current HEAD was
30
- # re-audited on 2026-08-12: /encode_ref2va is still present with the v02 media/kinds/canvas/num_frames contract.
31
- CONDITIONER_REVISION = "be67d10cd315e08efc4554d85c24751c62e566d5"
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
- # P15B uses xlarge (2x quota). With the 1024px reference cap, MM01 prices around 658 provider seconds and MM02
53
- # around 756 seconds under the published bound. A 900-second backend ceiling keeps one request's xlarge reservation
54
- # below a fresh 2,400-second user window while the client performs the stricter input-specific guard.
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
- active = list(getattr(transformer, "active_adapters", []) or [])
 
 
 
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 order after applying the real image-resolution cap."""
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 _prepare_reference_files(references)]
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
- references = collect(images, audio_path, video_path)
 
 
 
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(MIN_GPU_DURATION, min(MAX_GPU_DURATION, int(math.ceil(PLACEMENT_ALLOWANCE + encode + denoise + decode + 10 + LORA_ALLOWANCE))))
 
 
 
 
 
 
 
1230
  return {
1231
  "scene": scene,
1232
  "provider_reservation_seconds": provider,
1233
  "xlarge_quota_multiplier": 2,
1234
- "conditioner_allowance_seconds": 120,
1235
- "total_quota_reservation_seconds": provider * 2 + 120,
 
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 = [MiniMaxH3Ref2VASetupStep, MiniMaxH3Ref2VATextEncoderStep]
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
- MiniMaxH3Ref2VASetupStep,
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,