Spaces:
Running on Zero
Running on Zero
Let gradio_client forward the caller's ZeroGPU token itself instead of pinning one by hand
Browse files
README.md
CHANGED
|
@@ -160,22 +160,19 @@ one-time `PIPE.to("cuda")` is inside the first row's 339 s and does not reappear
|
|
| 160 |
|
| 161 |
## Whose GPU quota pays
|
| 162 |
|
| 163 |
-
Two cards are booked per request
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
The conditioner is sized so that even the fallback is legal: an unattributed caller may book at most 120 credits at a
|
| 177 |
-
time and an `xlarge` booking costs **twice** its seconds (`_gpu_size_units`), so the conditioner books the encode
|
| 178 |
-
(45 s) and a prompt upsample (60 s) as **two separate calls**, where one combined booking would be refused outright.
|
| 179 |
|
| 180 |
## Secrets
|
| 181 |
|
|
|
|
| 160 |
|
| 161 |
## Whose GPU quota pays
|
| 162 |
|
| 163 |
+
Two cards are booked per request — this Space's denoise loop and the conditioner's forward — and both are billed to the
|
| 164 |
+
**requesting user**, with nothing in this repository arranging it. `gradio_client` attaches the caller's own
|
| 165 |
+
`x-ip-token` to every outgoing call by itself, reading it off gradio's `LocalContext` inside the event listener
|
| 166 |
+
(`Client.send_data` -> `add_zero_gpu_headers`), and ZeroGPU's `/schedule` charges the booking to whatever that token
|
| 167 |
+
identifies. Forwarding the header by hand is not needed and is actively worse: a cached `Client` would pin one stale
|
| 168 |
+
token, which ZeroGPU refuses with `Expired ZeroGPU proxy token`.
|
| 169 |
+
|
| 170 |
+
A caller with no token to forward — a `gradio_client` script rather than a browser — leaves the conditioner's booking
|
| 171 |
+
attributed to this Space's pod IP and its small shared quota. That path is why the conditioner books small: an
|
| 172 |
+
unattributed caller may book at most 120 credits at a time and an `xlarge` booking costs **twice** its seconds
|
| 173 |
+
(`_gpu_size_units`), so the conditioner books the encode (45 s) and a prompt upsample (60 s) as two separate calls,
|
| 174 |
+
where one combined booking of the old 300 s would be — and was — refused outright with `The requested GPU duration
|
| 175 |
+
(600s) is larger than the maximum allowed`.
|
|
|
|
|
|
|
|
|
|
| 176 |
|
| 177 |
## Secrets
|
| 178 |
|
app.py
CHANGED
|
@@ -65,8 +65,7 @@ PIPE = None
|
|
| 65 |
MANAGER = None
|
| 66 |
LOAD_ERROR: str | None = None
|
| 67 |
LOADED_IN: float | None = None
|
| 68 |
-
|
| 69 |
-
CLIENTS: dict[str | None, object] = {}
|
| 70 |
|
| 71 |
|
| 72 |
def status() -> str:
|
|
@@ -171,35 +170,23 @@ def _arm_decode_hooks(pipe):
|
|
| 171 |
module.decode = armed
|
| 172 |
|
| 173 |
|
| 174 |
-
def conditioner(
|
| 175 |
-
"""The other half, over the gradio API
|
| 176 |
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
the
|
| 181 |
-
|
| 182 |
-
Cached per token: building a `Client` costs a round trip to the Space config, and a token is per user session.
|
| 183 |
"""
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
if len(CLIENTS) >= 32:
|
| 188 |
-
CLIENTS.pop(next(iter(CLIENTS)))
|
| 189 |
-
CLIENTS[ip_token] = Client(CONDITIONER_SPACE, headers={"X-IP-Token": ip_token} if ip_token else None)
|
| 190 |
-
return CLIENTS[ip_token]
|
| 191 |
-
|
| 192 |
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
The UI path and the `/generate` API path both reach this through the `gr.Request` gradio injects for a parameter
|
| 197 |
-
annotated with it.
|
| 198 |
-
"""
|
| 199 |
-
headers = getattr(request, "headers", None)
|
| 200 |
-
return None if headers is None else headers.get("x-ip-token")
|
| 201 |
|
| 202 |
-
def encode_remote(prompt, image_path, last_image_path, canvas, num_frames, rewrite_prompt=False
|
| 203 |
"""Ask the conditioner Space for `prompt_embeds` + `text_token_tags`. Off this Space's GPU time entirely.
|
| 204 |
|
| 205 |
`rewrite_prompt` is the conditioner's prompt upsampling: it rewrites the request into MiniMax-H3's trained format
|
|
@@ -210,7 +197,7 @@ def encode_remote(prompt, image_path, last_image_path, canvas, num_frames, rewri
|
|
| 210 |
from gradio_client import handle_file
|
| 211 |
from safetensors import safe_open
|
| 212 |
|
| 213 |
-
path, plan = conditioner(
|
| 214 |
prompt=prompt,
|
| 215 |
image_path=handle_file(image_path) if image_path else None,
|
| 216 |
last_image_path=handle_file(last_image_path) if last_image_path else None,
|
|
@@ -274,7 +261,7 @@ def _generate(prompt_embeds, text_token_tags, image, last_image, height, width,
|
|
| 274 |
return state.get("videos")[0], state.get("audio")[0].cpu(), state.get("sampling_rate")
|
| 275 |
|
| 276 |
|
| 277 |
-
def generate(prompt, image_path=None, last_image_path=None, canvas=DEFAULT_CANVAS, duration=5, steps=28, seed=42, upsample=False, progress=gr.Progress(track_tqdm=True)
|
| 278 |
"""One request. `upsample` is appended last and defaults off, so an existing API client is untouched by it."""
|
| 279 |
if LOAD_ERROR:
|
| 280 |
raise gr.Error(LOAD_ERROR)
|
|
@@ -292,8 +279,7 @@ def generate(prompt, image_path=None, last_image_path=None, canvas=DEFAULT_CANVA
|
|
| 292 |
progress(0.0, desc=f"Upsampling the prompt on {CONDITIONER_SPACE} ..." if upsample else f"Conditioning on {CONDITIONER_SPACE} ...")
|
| 293 |
conditioned = time.time()
|
| 294 |
prompt_embeds, text_token_tags, metadata, plan = encode_remote(
|
| 295 |
-
prompt, image_path, last_image_path, canvas, num_frames, rewrite_prompt=upsample
|
| 296 |
-
ip_token=ip_token_of(request),
|
| 297 |
)
|
| 298 |
condition_seconds = time.time() - conditioned
|
| 299 |
height, width, num_frames = (int(metadata[key]) for key in ("height", "width", "num_frames"))
|
|
|
|
| 65 |
MANAGER = None
|
| 66 |
LOAD_ERROR: str | None = None
|
| 67 |
LOADED_IN: float | None = None
|
| 68 |
+
CLIENT = None
|
|
|
|
| 69 |
|
| 70 |
|
| 71 |
def status() -> str:
|
|
|
|
| 170 |
module.decode = armed
|
| 171 |
|
| 172 |
|
| 173 |
+
def conditioner():
|
| 174 |
+
"""The other half, over the gradio API. Cached — building a `Client` costs a round trip to the Space config.
|
| 175 |
|
| 176 |
+
No token is passed and none has to be: `gradio_client` attaches the caller's own ZeroGPU token itself, per call,
|
| 177 |
+
by reading the `x-ip-token` of the request being served off gradio's `LocalContext` (`Client.send_data` ->
|
| 178 |
+
`add_zero_gpu_headers`). So calling this from inside an event listener — which is the only place it is called —
|
| 179 |
+
bills the conditioner's booking to the user who asked for the video, exactly as this Space's own booking is, and
|
| 180 |
+
forwarding the header by hand would only pin a stale token onto a cached client.
|
|
|
|
| 181 |
"""
|
| 182 |
+
global CLIENT
|
| 183 |
+
if CLIENT is None:
|
| 184 |
+
from gradio_client import Client
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
|
| 186 |
+
CLIENT = Client(CONDITIONER_SPACE)
|
| 187 |
+
return CLIENT
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
|
| 189 |
+
def encode_remote(prompt, image_path, last_image_path, canvas, num_frames, rewrite_prompt=False):
|
| 190 |
"""Ask the conditioner Space for `prompt_embeds` + `text_token_tags`. Off this Space's GPU time entirely.
|
| 191 |
|
| 192 |
`rewrite_prompt` is the conditioner's prompt upsampling: it rewrites the request into MiniMax-H3's trained format
|
|
|
|
| 197 |
from gradio_client import handle_file
|
| 198 |
from safetensors import safe_open
|
| 199 |
|
| 200 |
+
path, plan = conditioner().predict(
|
| 201 |
prompt=prompt,
|
| 202 |
image_path=handle_file(image_path) if image_path else None,
|
| 203 |
last_image_path=handle_file(last_image_path) if last_image_path else None,
|
|
|
|
| 261 |
return state.get("videos")[0], state.get("audio")[0].cpu(), state.get("sampling_rate")
|
| 262 |
|
| 263 |
|
| 264 |
+
def generate(prompt, image_path=None, last_image_path=None, canvas=DEFAULT_CANVAS, duration=5, steps=28, seed=42, upsample=False, progress=gr.Progress(track_tqdm=True)):
|
| 265 |
"""One request. `upsample` is appended last and defaults off, so an existing API client is untouched by it."""
|
| 266 |
if LOAD_ERROR:
|
| 267 |
raise gr.Error(LOAD_ERROR)
|
|
|
|
| 279 |
progress(0.0, desc=f"Upsampling the prompt on {CONDITIONER_SPACE} ..." if upsample else f"Conditioning on {CONDITIONER_SPACE} ...")
|
| 280 |
conditioned = time.time()
|
| 281 |
prompt_embeds, text_token_tags, metadata, plan = encode_remote(
|
| 282 |
+
prompt, image_path, last_image_path, canvas, num_frames, rewrite_prompt=upsample
|
|
|
|
| 283 |
)
|
| 284 |
condition_seconds = time.time() - conditioned
|
| 285 |
height, width, num_frames = (int(metadata[key]) for key in ("height", "width", "num_frames"))
|