Spaces:
Running on Zero
Running on Zero
Forward the caller's ZeroGPU token to the conditioner, plainly
Browse files
app.py
CHANGED
|
@@ -200,9 +200,8 @@ 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 |
-
|
| 205 |
-
CLIENTS: dict[tuple[str | None, str | None], object] = {}
|
| 206 |
|
| 207 |
|
| 208 |
def load_models() -> str | None:
|
|
@@ -308,89 +307,33 @@ def _arm_decode_hooks(pipe):
|
|
| 308 |
setattr(module, method, armed)
|
| 309 |
|
| 310 |
|
| 311 |
-
def conditioner(ip_token: str | None = None
|
| 312 |
-
"""
|
| 313 |
|
| 314 |
-
|
| 315 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
"""
|
| 317 |
from gradio_client import Client
|
| 318 |
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
client = Client(CONDITIONER_SPACE, headers={"X-IP-Token": ip_token} if ip_token else None)
|
| 325 |
-
if len(CLIENTS) >= 32:
|
| 326 |
-
CLIENTS.pop(next(iter(CLIENTS)))
|
| 327 |
-
CLIENTS[key] = client
|
| 328 |
-
return client
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
# What ZeroGPU says when an identity cannot pay for a booking: a proxy token its `/usage-approval` refused (`401`,
|
| 332 |
-
# surfaced as "Expired ZeroGPU proxy token"), a duration past what that identity may book, and an exhausted quota.
|
| 333 |
-
_UNPAYABLE = ("proxy token", "ZeroGPU quota", "larger than the maximum allowed", "GPU limit")
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
def call_conditioner(ip_token, **arguments):
|
| 337 |
-
"""One conditioner call, billed to **the requesting user**.
|
| 338 |
-
|
| 339 |
-
ZeroGPU attributes a booking to the `X-IP-Token` of the request that triggered it: `/schedule` hands that token to
|
| 340 |
-
the Spaces API's `/usage-approval` together with the duration and the calling pod's IP, and nothing else — there is
|
| 341 |
-
no Space identity in that call, so a *valid* token minted anywhere is honoured and the user's own quota pays for
|
| 342 |
-
both halves of their request. That is the whole reason this Space forwards the header instead of spending a token
|
| 343 |
-
of its own.
|
| 344 |
-
|
| 345 |
-
A token the Spaces API refuses (`401`) falls back to calling the conditioner with no token, which is billed to this
|
| 346 |
-
Space's pod IP off a small shared quota. That is a safety net, not the intended path: the log line
|
| 347 |
-
`conditioner call paid for by ...` records which one actually paid, so a Space that keeps falling back is visible.
|
| 348 |
-
"""
|
| 349 |
-
api_name = arguments.pop("api_name")
|
| 350 |
-
attempts = [("no token, on this Space's shared IP quota", {})]
|
| 351 |
-
if ip_token:
|
| 352 |
-
attempts.insert(0, ("the requesting user's own ZeroGPU token", {"ip_token": ip_token}))
|
| 353 |
-
|
| 354 |
-
for index, (label, identity) in enumerate(attempts):
|
| 355 |
-
try:
|
| 356 |
-
result = conditioner(**identity).predict(**arguments, api_name=api_name)
|
| 357 |
-
print(f"[{LOG_TAG}] conditioner call paid for by {label}", flush=True)
|
| 358 |
-
return result
|
| 359 |
-
except Exception as error:
|
| 360 |
-
if index == len(attempts) - 1 or not any(reason in str(error) for reason in _UNPAYABLE):
|
| 361 |
-
raise
|
| 362 |
-
print(f"[{LOG_TAG}] {label} was refused: {error}; falling back", flush=True)
|
| 363 |
-
CLIENTS.pop((identity.get("ip_token"), identity.get("hf_token")), None)
|
| 364 |
|
| 365 |
|
| 366 |
def ip_token_of(request) -> str | None:
|
| 367 |
"""The requesting user's ZeroGPU identity, as the Spaces router put it on this request.
|
| 368 |
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
the claims are what tell the two apart when a request ends up on the fallback quota. Both the UI path and the
|
| 372 |
-
`/generate` API path reach this through the same `gr.Request` gradio injects for a parameter annotated with it.
|
| 373 |
"""
|
| 374 |
headers = getattr(request, "headers", None)
|
| 375 |
-
|
| 376 |
-
if token is None:
|
| 377 |
-
print(f"[{LOG_TAG}] no X-IP-Token on this request; the conditioner call cannot be billed to the caller", flush=True)
|
| 378 |
-
return None
|
| 379 |
-
try:
|
| 380 |
-
import base64
|
| 381 |
-
import json
|
| 382 |
-
import time
|
| 383 |
-
|
| 384 |
-
payload = json.loads(base64.urlsafe_b64decode(f"{token.split('.')[1]}=="))
|
| 385 |
-
left = payload.get("exp", 0) - time.time()
|
| 386 |
-
print(
|
| 387 |
-
f"[{LOG_TAG}] X-IP-Token present: {left:.0f}s to expiry, claims "
|
| 388 |
-
f"{ {k: v for k, v in payload.items() if k in ('exp', 'iat', 'sub', 'aud', 'error', 'user')} }",
|
| 389 |
-
flush=True,
|
| 390 |
-
)
|
| 391 |
-
except Exception as error: # a token that cannot be read is still worth forwarding; ZeroGPU is the judge
|
| 392 |
-
print(f"[{LOG_TAG}] X-IP-Token present but unreadable ({type(error).__name__}: {error})", flush=True)
|
| 393 |
-
return token
|
| 394 |
|
| 395 |
def probe(path: str) -> tuple[float | None, float | None]:
|
| 396 |
"""`(video seconds, audio seconds)` of a media file, either being `None` when the stream is absent."""
|
|
@@ -503,8 +446,7 @@ def encode_remote(prompt, references, canvas, num_frames, rewrite_prompt=False,
|
|
| 503 |
from gradio_client import handle_file
|
| 504 |
from safetensors import safe_open
|
| 505 |
|
| 506 |
-
path, plan =
|
| 507 |
-
ip_token,
|
| 508 |
prompt=prompt,
|
| 509 |
media=[handle_file(path) for _, path in references],
|
| 510 |
kinds=",".join(kind for kind, _ in references),
|
|
|
|
| 200 |
PIPE = None
|
| 201 |
MANAGER = None
|
| 202 |
LOAD_ERROR: str | None = None
|
| 203 |
+
# One `gradio_client.Client` per forwarded token; see `conditioner`.
|
| 204 |
+
CLIENTS: dict[str | None, object] = {}
|
|
|
|
| 205 |
|
| 206 |
|
| 207 |
def load_models() -> str | None:
|
|
|
|
| 307 |
setattr(module, method, armed)
|
| 308 |
|
| 309 |
|
| 310 |
+
def conditioner(ip_token: str | None = None):
|
| 311 |
+
"""The other half, over the gradio API, billed to the requesting user.
|
| 312 |
|
| 313 |
+
ZeroGPU attributes a booking to the `X-IP-Token` of the request that triggered it: `/schedule` hands that token to
|
| 314 |
+
the Spaces API together with the duration and the calling pod's IP and nothing else — there is no Space identity in
|
| 315 |
+
the decision — so forwarding the caller's header makes the user's own quota pay for both halves of their request,
|
| 316 |
+
the way it would if this were a single Space.
|
| 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 |
from gradio_client import Client
|
| 321 |
|
| 322 |
+
if ip_token not in CLIENTS:
|
| 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 |
def ip_token_of(request) -> str | None:
|
| 330 |
"""The requesting user's ZeroGPU identity, as the Spaces router put it on this request.
|
| 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."""
|
|
|
|
| 446 |
from gradio_client import handle_file
|
| 447 |
from safetensors import safe_open
|
| 448 |
|
| 449 |
+
path, plan = conditioner(ip_token).predict(
|
|
|
|
| 450 |
prompt=prompt,
|
| 451 |
media=[handle_file(path) for _, path in references],
|
| 452 |
kinds=",".join(kind for kind, _ in references),
|