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
|
@@ -183,22 +183,19 @@ on is cold and a cold one pays the lazy 72.16 GiB `PIPE.to("cuda")` inside its f
|
|
| 183 |
|
| 184 |
## Whose GPU quota pays
|
| 185 |
|
| 186 |
-
Two cards are booked per request
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
The conditioner is sized so that even the fallback is legal: an unattributed caller may book at most 120 credits at a
|
| 200 |
-
time and an `xlarge` booking costs **twice** its seconds (`_gpu_size_units`), so the conditioner books the encode
|
| 201 |
-
(45 s) and a prompt upsample (60 s) as **two separate calls**, where one combined booking would be refused outright.
|
| 202 |
|
| 203 |
## Secrets
|
| 204 |
|
|
|
|
| 183 |
|
| 184 |
## Whose GPU quota pays
|
| 185 |
|
| 186 |
+
Two cards are booked per request — this Space's denoise loop and the conditioner's forward — and both are billed to the
|
| 187 |
+
**requesting user**, with nothing in this repository arranging it. `gradio_client` attaches the caller's own
|
| 188 |
+
`x-ip-token` to every outgoing call by itself, reading it off gradio's `LocalContext` inside the event listener
|
| 189 |
+
(`Client.send_data` -> `add_zero_gpu_headers`), and ZeroGPU's `/schedule` charges the booking to whatever that token
|
| 190 |
+
identifies. Forwarding the header by hand is not needed and is actively worse: a cached `Client` would pin one stale
|
| 191 |
+
token, which ZeroGPU refuses with `Expired ZeroGPU proxy token`.
|
| 192 |
+
|
| 193 |
+
A caller with no token to forward — a `gradio_client` script rather than a browser — leaves the conditioner's booking
|
| 194 |
+
attributed to this Space's pod IP and its small shared quota. That path is why the conditioner books small: an
|
| 195 |
+
unattributed caller may book at most 120 credits at a time and an `xlarge` booking costs **twice** its seconds
|
| 196 |
+
(`_gpu_size_units`), so the conditioner books the encode (45 s) and a prompt upsample (60 s) as two separate calls,
|
| 197 |
+
where one combined booking of the old 300 s would be — and was — refused outright with `The requested GPU duration
|
| 198 |
+
(600s) is larger than the maximum allowed`.
|
|
|
|
|
|
|
|
|
|
| 199 |
|
| 200 |
## Secrets
|
| 201 |
|
app.py
CHANGED
|
@@ -200,8 +200,7 @@ def get_duration(prompt_embeds, text_token_tags, references, height, width, num_
|
|
| 200 |
PIPE = None
|
| 201 |
MANAGER = None
|
| 202 |
LOAD_ERROR: str | None = None
|
| 203 |
-
|
| 204 |
-
CLIENTS: dict[str | None, object] = {}
|
| 205 |
|
| 206 |
|
| 207 |
def load_models() -> str | None:
|
|
@@ -307,33 +306,21 @@ def _arm_decode_hooks(pipe):
|
|
| 307 |
setattr(module, method, armed)
|
| 308 |
|
| 309 |
|
| 310 |
-
def conditioner(
|
| 311 |
-
"""The other half, over the gradio API
|
| 312 |
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
the
|
| 317 |
-
|
| 318 |
-
Cached per token: building a `Client` costs a round trip to the Space config, and a token is per user session.
|
| 319 |
"""
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
if len(CLIENTS) >= 32:
|
| 324 |
-
CLIENTS.pop(next(iter(CLIENTS)))
|
| 325 |
-
CLIENTS[ip_token] = Client(CONDITIONER_SPACE, headers={"X-IP-Token": ip_token} if ip_token else None)
|
| 326 |
-
return CLIENTS[ip_token]
|
| 327 |
-
|
| 328 |
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
The UI path and the `/generate` API path both reach this through the `gr.Request` gradio injects for a parameter
|
| 333 |
-
annotated with it.
|
| 334 |
-
"""
|
| 335 |
-
headers = getattr(request, "headers", None)
|
| 336 |
-
return None if headers is None else headers.get("x-ip-token")
|
| 337 |
|
| 338 |
def probe(path: str) -> tuple[float | None, float | None]:
|
| 339 |
"""`(video seconds, audio seconds)` of a media file, either being `None` when the stream is absent."""
|
|
@@ -431,7 +418,7 @@ def check(prompt: str, references: list[tuple[str, str]]) -> None:
|
|
| 431 |
)
|
| 432 |
|
| 433 |
|
| 434 |
-
def encode_remote(prompt, references, canvas, num_frames, rewrite_prompt=False
|
| 435 |
"""Ask the conditioner Space for `prompt_embeds` + `text_token_tags`. Off this Space's GPU time entirely.
|
| 436 |
|
| 437 |
The references go over with the request: `ref2va`'s presentation puts a vision block in front of the prompt for
|
|
@@ -446,7 +433,7 @@ def encode_remote(prompt, references, canvas, num_frames, rewrite_prompt=False,
|
|
| 446 |
from gradio_client import handle_file
|
| 447 |
from safetensors import safe_open
|
| 448 |
|
| 449 |
-
path, plan = conditioner(
|
| 450 |
prompt=prompt,
|
| 451 |
media=[handle_file(path) for _, path in references],
|
| 452 |
kinds=",".join(kind for kind, _ in references),
|
|
@@ -513,7 +500,6 @@ def generate(
|
|
| 513 |
seed=42,
|
| 514 |
upsample=False,
|
| 515 |
progress=gr.Progress(track_tqdm=True),
|
| 516 |
-
request: gr.Request | None = None,
|
| 517 |
):
|
| 518 |
"""One request. `upsample` is appended last and defaults off, so an existing API client is untouched by it."""
|
| 519 |
if LOAD_ERROR:
|
|
@@ -536,7 +522,7 @@ def generate(
|
|
| 536 |
conditioned = time.time()
|
| 537 |
try:
|
| 538 |
prompt_embeds, text_token_tags, metadata, plan = encode_remote(
|
| 539 |
-
prompt, references, canvas, requested, rewrite_prompt=upsample
|
| 540 |
)
|
| 541 |
except gr.Error:
|
| 542 |
raise
|
|
|
|
| 200 |
PIPE = None
|
| 201 |
MANAGER = None
|
| 202 |
LOAD_ERROR: str | None = None
|
| 203 |
+
CLIENT = None
|
|
|
|
| 204 |
|
| 205 |
|
| 206 |
def load_models() -> str | None:
|
|
|
|
| 306 |
setattr(module, method, armed)
|
| 307 |
|
| 308 |
|
| 309 |
+
def conditioner():
|
| 310 |
+
"""The other half, over the gradio API. Cached — building a `Client` costs a round trip to the Space config.
|
| 311 |
|
| 312 |
+
No token is passed and none has to be: `gradio_client` attaches the caller's own ZeroGPU token itself, per call,
|
| 313 |
+
by reading the `x-ip-token` of the request being served off gradio's `LocalContext` (`Client.send_data` ->
|
| 314 |
+
`add_zero_gpu_headers`). So calling this from inside an event listener — which is the only place it is called —
|
| 315 |
+
bills the conditioner's booking to the user who asked for the video, exactly as this Space's own booking is, and
|
| 316 |
+
forwarding the header by hand would only pin a stale token onto a cached client.
|
|
|
|
| 317 |
"""
|
| 318 |
+
global CLIENT
|
| 319 |
+
if CLIENT is None:
|
| 320 |
+
from gradio_client import Client
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 321 |
|
| 322 |
+
CLIENT = Client(CONDITIONER_SPACE)
|
| 323 |
+
return CLIENT
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
|
| 325 |
def probe(path: str) -> tuple[float | None, float | None]:
|
| 326 |
"""`(video seconds, audio seconds)` of a media file, either being `None` when the stream is absent."""
|
|
|
|
| 418 |
)
|
| 419 |
|
| 420 |
|
| 421 |
+
def encode_remote(prompt, references, canvas, num_frames, rewrite_prompt=False):
|
| 422 |
"""Ask the conditioner Space for `prompt_embeds` + `text_token_tags`. Off this Space's GPU time entirely.
|
| 423 |
|
| 424 |
The references go over with the request: `ref2va`'s presentation puts a vision block in front of the prompt for
|
|
|
|
| 433 |
from gradio_client import handle_file
|
| 434 |
from safetensors import safe_open
|
| 435 |
|
| 436 |
+
path, plan = conditioner().predict(
|
| 437 |
prompt=prompt,
|
| 438 |
media=[handle_file(path) for _, path in references],
|
| 439 |
kinds=",".join(kind for kind, _ in references),
|
|
|
|
| 500 |
seed=42,
|
| 501 |
upsample=False,
|
| 502 |
progress=gr.Progress(track_tqdm=True),
|
|
|
|
| 503 |
):
|
| 504 |
"""One request. `upsample` is appended last and defaults off, so an existing API client is untouched by it."""
|
| 505 |
if LOAD_ERROR:
|
|
|
|
| 522 |
conditioned = time.time()
|
| 523 |
try:
|
| 524 |
prompt_embeds, text_token_tags, metadata, plan = encode_remote(
|
| 525 |
+
prompt, references, canvas, requested, rewrite_prompt=upsample
|
| 526 |
)
|
| 527 |
except gr.Error:
|
| 528 |
raise
|