akhaliq HF Staff commited on
Commit
7db57ff
·
1 Parent(s): e6d78bd

Pass the caller's x-ip-token to the conditioner explicitly

Browse files

LocalContext-based token forwarding is unreliable in Server mode, so the
conditioner's ZeroGPU booking fell back to this Space's pod IP and its
shared quota ('quota exceeded' despite the caller having quota). Extract
the header from the injected Request and hand it to a per-request
gradio_client, per the gradio ZeroGPU docs.

Files changed (1) hide show
  1. app.py +22 -9
app.py CHANGED
@@ -12,7 +12,7 @@ from functools import cache
12
  # startup rather than on GPU time.
13
  import spaces
14
  from fastapi.responses import HTMLResponse
15
- from gradio import Server
16
  from gradio.data_classes import FileData
17
 
18
  MODEL_REPO = os.environ.get("H3_MODEL_REPO", "MiniMaxAI/MiniMax-H3")
@@ -177,20 +177,31 @@ def _arm_decode_hooks(pipe):
177
 
178
  @cache
179
  def conditioner():
180
- """The other half, over the gradio API. `gradio_client` attaches the caller's own ZeroGPU token per call, so the
181
- conditioner's booking is billed to whoever asked for the video."""
182
  from gradio_client import Client
183
 
184
  return Client(CONDITIONER_SPACE)
185
 
186
 
187
- def encode_remote(prompt, image_path, last_image_path, canvas, num_frames, rewrite_prompt=False):
 
 
 
 
 
 
 
 
 
 
 
188
  """`/encode` on the conditioner Space: a safetensors file holding `prompt_embeds` + `text_token_tags`, with the
189
  resolved `height` / `width` / `num_frames` in its metadata, plus the plan. `canvas` is the label."""
190
  from gradio_client import handle_file
191
  from safetensors import safe_open
192
 
193
- path, plan = conditioner().predict(
194
  prompt=prompt,
195
  image_path=handle_file(image_path) if image_path else None,
196
  last_image_path=handle_file(last_image_path) if last_image_path else None,
@@ -286,7 +297,7 @@ def _fit_keyframe(image_path, current_canvas):
286
  return image_path, label
287
 
288
 
289
- def generate(prompt, image_path=None, last_image_path=None, canvas=DEFAULT_CANVAS, duration=5, steps=4, seed=42, upsample=False):
290
  """One request. `upsample` is last and defaults off, so a positional API client that predates it is unaffected."""
291
  if LOAD_ERROR:
292
  raise Exception(LOAD_ERROR)
@@ -312,7 +323,7 @@ def generate(prompt, image_path=None, last_image_path=None, canvas=DEFAULT_CANVA
312
 
313
  conditioned = time.time()
314
  prompt_embeds, text_token_tags, metadata, plan = encode_remote(
315
- prompt, first, last, canvas, num_frames, rewrite_prompt=upsample
316
  )
317
  condition_seconds = time.time() - conditioned
318
  height, width, num_frames = (int(metadata[key]) for key in ("height", "width", "num_frames"))
@@ -362,9 +373,11 @@ app = Server(title="MiniMax-H3 Studio")
362
  @app.api(name="generate")
363
  def _generate_api(prompt: str, image_path: FileData | None = None, last_image_path: FileData | None = None,
364
  canvas: str = DEFAULT_CANVAS, duration: float = 5, steps: int = 4, seed: float = 42,
365
- upsample: bool = False) -> tuple[FileData, str, str]:
366
  """Generate a video with a synchronized soundtrack. Returns (video, report, refined prompt)."""
367
- return generate(prompt, image_path, last_image_path, canvas, duration, steps, seed, upsample)
 
 
368
 
369
 
370
  @app.get("/status")
 
12
  # startup rather than on GPU time.
13
  import spaces
14
  from fastapi.responses import HTMLResponse
15
+ from gradio import Request, Server
16
  from gradio.data_classes import FileData
17
 
18
  MODEL_REPO = os.environ.get("H3_MODEL_REPO", "MiniMaxAI/MiniMax-H3")
 
177
 
178
  @cache
179
  def conditioner():
180
+ """The other half, over the gradio API. Used only when the caller's token could not be extracted; the booking is
181
+ then billed to this Space's pod IP and its small shared quota."""
182
  from gradio_client import Client
183
 
184
  return Client(CONDITIONER_SPACE)
185
 
186
 
187
+ def conditioner_client(ip_token):
188
+ """A conditioner client billed to the caller. `LocalContext`-based token forwarding is not reliable in Server
189
+ mode, so the `x-ip-token` header is extracted from the incoming request and passed explicitly (per the gradio
190
+ ZeroGPU docs); a per-request Client is cheap next to a 45s encode."""
191
+ if not ip_token:
192
+ return conditioner()
193
+ from gradio_client import Client
194
+
195
+ return Client(CONDITIONER_SPACE, headers={"x-ip-token": ip_token})
196
+
197
+
198
+ def encode_remote(prompt, image_path, last_image_path, canvas, num_frames, rewrite_prompt=False, ip_token=None):
199
  """`/encode` on the conditioner Space: a safetensors file holding `prompt_embeds` + `text_token_tags`, with the
200
  resolved `height` / `width` / `num_frames` in its metadata, plus the plan. `canvas` is the label."""
201
  from gradio_client import handle_file
202
  from safetensors import safe_open
203
 
204
+ path, plan = conditioner_client(ip_token).predict(
205
  prompt=prompt,
206
  image_path=handle_file(image_path) if image_path else None,
207
  last_image_path=handle_file(last_image_path) if last_image_path else None,
 
297
  return image_path, label
298
 
299
 
300
+ def generate(prompt, image_path=None, last_image_path=None, canvas=DEFAULT_CANVAS, duration=5, steps=4, seed=42, upsample=False, ip_token=None):
301
  """One request. `upsample` is last and defaults off, so a positional API client that predates it is unaffected."""
302
  if LOAD_ERROR:
303
  raise Exception(LOAD_ERROR)
 
323
 
324
  conditioned = time.time()
325
  prompt_embeds, text_token_tags, metadata, plan = encode_remote(
326
+ prompt, first, last, canvas, num_frames, rewrite_prompt=upsample, ip_token=ip_token
327
  )
328
  condition_seconds = time.time() - conditioned
329
  height, width, num_frames = (int(metadata[key]) for key in ("height", "width", "num_frames"))
 
373
  @app.api(name="generate")
374
  def _generate_api(prompt: str, image_path: FileData | None = None, last_image_path: FileData | None = None,
375
  canvas: str = DEFAULT_CANVAS, duration: float = 5, steps: int = 4, seed: float = 42,
376
+ upsample: bool = False, request: Request = None) -> tuple[FileData, str, str]:
377
  """Generate a video with a synchronized soundtrack. Returns (video, report, refined prompt)."""
378
+ # `request` is injected by the event system, not an API input; its x-ip-token bills the conditioner to the caller.
379
+ ip_token = request.headers.get("x-ip-token") if request is not None else None
380
+ return generate(prompt, image_path, last_image_path, canvas, duration, steps, seed, upsample, ip_token=ip_token)
381
 
382
 
383
  @app.get("/status")