Spaces:
Running on Zero
Running on Zero
Forward the caller's ZeroGPU token to the conditioner, plainly
Browse files
app.py
CHANGED
|
@@ -65,9 +65,8 @@ PIPE = None
|
|
| 65 |
MANAGER = None
|
| 66 |
LOAD_ERROR: str | None = None
|
| 67 |
LOADED_IN: float | None = None
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
CLIENTS: dict[tuple[str | None, str | None], object] = {}
|
| 71 |
|
| 72 |
|
| 73 |
def status() -> str:
|
|
@@ -172,89 +171,33 @@ def _arm_decode_hooks(pipe):
|
|
| 172 |
module.decode = armed
|
| 173 |
|
| 174 |
|
| 175 |
-
def conditioner(ip_token: str | None = None
|
| 176 |
-
"""
|
| 177 |
|
| 178 |
-
|
| 179 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
"""
|
| 181 |
from gradio_client import Client
|
| 182 |
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
client = Client(CONDITIONER_SPACE, headers={"X-IP-Token": ip_token} if ip_token else None)
|
| 189 |
-
if len(CLIENTS) >= 32:
|
| 190 |
-
CLIENTS.pop(next(iter(CLIENTS)))
|
| 191 |
-
CLIENTS[key] = client
|
| 192 |
-
return client
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
# What ZeroGPU says when an identity cannot pay for a booking: a proxy token its `/usage-approval` refused (`401`,
|
| 196 |
-
# surfaced as "Expired ZeroGPU proxy token"), a duration past what that identity may book, and an exhausted quota.
|
| 197 |
-
_UNPAYABLE = ("proxy token", "ZeroGPU quota", "larger than the maximum allowed", "GPU limit")
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
def call_conditioner(ip_token, **arguments):
|
| 201 |
-
"""One conditioner call, billed to **the requesting user**.
|
| 202 |
-
|
| 203 |
-
ZeroGPU attributes a booking to the `X-IP-Token` of the request that triggered it: `/schedule` hands that token to
|
| 204 |
-
the Spaces API's `/usage-approval` together with the duration and the calling pod's IP, and nothing else — there is
|
| 205 |
-
no Space identity in that call, so a *valid* token minted anywhere is honoured and the user's own quota pays for
|
| 206 |
-
both halves of their request. That is the whole reason this Space forwards the header instead of spending a token
|
| 207 |
-
of its own.
|
| 208 |
-
|
| 209 |
-
A token the Spaces API refuses (`401`) falls back to calling the conditioner with no token, which is billed to this
|
| 210 |
-
Space's pod IP off a small shared quota. That is a safety net, not the intended path: the log line
|
| 211 |
-
`conditioner call paid for by ...` records which one actually paid, so a Space that keeps falling back is visible.
|
| 212 |
-
"""
|
| 213 |
-
api_name = arguments.pop("api_name")
|
| 214 |
-
attempts = [("no token, on this Space's shared IP quota", {})]
|
| 215 |
-
if ip_token:
|
| 216 |
-
attempts.insert(0, ("the requesting user's own ZeroGPU token", {"ip_token": ip_token}))
|
| 217 |
-
|
| 218 |
-
for index, (label, identity) in enumerate(attempts):
|
| 219 |
-
try:
|
| 220 |
-
result = conditioner(**identity).predict(**arguments, api_name=api_name)
|
| 221 |
-
print(f"[{LOG_TAG}] conditioner call paid for by {label}", flush=True)
|
| 222 |
-
return result
|
| 223 |
-
except Exception as error:
|
| 224 |
-
if index == len(attempts) - 1 or not any(reason in str(error) for reason in _UNPAYABLE):
|
| 225 |
-
raise
|
| 226 |
-
print(f"[{LOG_TAG}] {label} was refused: {error}; falling back", flush=True)
|
| 227 |
-
CLIENTS.pop((identity.get("ip_token"), identity.get("hf_token")), None)
|
| 228 |
|
| 229 |
|
| 230 |
def ip_token_of(request) -> str | None:
|
| 231 |
"""The requesting user's ZeroGPU identity, as the Spaces router put it on this request.
|
| 232 |
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
the claims are what tell the two apart when a request ends up on the fallback quota. Both the UI path and the
|
| 236 |
-
`/generate` API path reach this through the same `gr.Request` gradio injects for a parameter annotated with it.
|
| 237 |
"""
|
| 238 |
headers = getattr(request, "headers", None)
|
| 239 |
-
|
| 240 |
-
if token is None:
|
| 241 |
-
print(f"[{LOG_TAG}] no X-IP-Token on this request; the conditioner call cannot be billed to the caller", flush=True)
|
| 242 |
-
return None
|
| 243 |
-
try:
|
| 244 |
-
import base64
|
| 245 |
-
import json
|
| 246 |
-
import time
|
| 247 |
-
|
| 248 |
-
payload = json.loads(base64.urlsafe_b64decode(f"{token.split('.')[1]}=="))
|
| 249 |
-
left = payload.get("exp", 0) - time.time()
|
| 250 |
-
print(
|
| 251 |
-
f"[{LOG_TAG}] X-IP-Token present: {left:.0f}s to expiry, claims "
|
| 252 |
-
f"{ {k: v for k, v in payload.items() if k in ('exp', 'iat', 'sub', 'aud', 'error', 'user')} }",
|
| 253 |
-
flush=True,
|
| 254 |
-
)
|
| 255 |
-
except Exception as error: # a token that cannot be read is still worth forwarding; ZeroGPU is the judge
|
| 256 |
-
print(f"[{LOG_TAG}] X-IP-Token present but unreadable ({type(error).__name__}: {error})", flush=True)
|
| 257 |
-
return token
|
| 258 |
|
| 259 |
def encode_remote(prompt, image_path, last_image_path, canvas, num_frames, rewrite_prompt=False, ip_token=None):
|
| 260 |
"""Ask the conditioner Space for `prompt_embeds` + `text_token_tags`. Off this Space's GPU time entirely.
|
|
@@ -267,8 +210,7 @@ def encode_remote(prompt, image_path, last_image_path, canvas, num_frames, rewri
|
|
| 267 |
from gradio_client import handle_file
|
| 268 |
from safetensors import safe_open
|
| 269 |
|
| 270 |
-
path, plan =
|
| 271 |
-
ip_token,
|
| 272 |
prompt=prompt,
|
| 273 |
image_path=handle_file(image_path) if image_path else None,
|
| 274 |
last_image_path=handle_file(last_image_path) if last_image_path else None,
|
|
|
|
| 65 |
MANAGER = None
|
| 66 |
LOAD_ERROR: str | None = None
|
| 67 |
LOADED_IN: float | None = None
|
| 68 |
+
# One `gradio_client.Client` per forwarded token; see `conditioner`.
|
| 69 |
+
CLIENTS: dict[str | None, object] = {}
|
|
|
|
| 70 |
|
| 71 |
|
| 72 |
def status() -> str:
|
|
|
|
| 171 |
module.decode = armed
|
| 172 |
|
| 173 |
|
| 174 |
+
def conditioner(ip_token: str | None = None):
|
| 175 |
+
"""The other half, over the gradio API, billed to the requesting user.
|
| 176 |
|
| 177 |
+
ZeroGPU attributes a booking to the `X-IP-Token` of the request that triggered it: `/schedule` hands that token to
|
| 178 |
+
the Spaces API together with the duration and the calling pod's IP and nothing else — there is no Space identity in
|
| 179 |
+
the decision — so forwarding the caller's header makes the user's own quota pay for both halves of their request,
|
| 180 |
+
the way it would if this were a single Space.
|
| 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 |
from gradio_client import Client
|
| 185 |
|
| 186 |
+
if ip_token not in CLIENTS:
|
| 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 |
def ip_token_of(request) -> str | None:
|
| 194 |
"""The requesting user's ZeroGPU identity, as the Spaces router put it on this request.
|
| 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, ip_token=None):
|
| 203 |
"""Ask the conditioner Space for `prompt_embeds` + `text_token_tags`. Off this Space's GPU time entirely.
|
|
|
|
| 210 |
from gradio_client import handle_file
|
| 211 |
from safetensors import safe_open
|
| 212 |
|
| 213 |
+
path, plan = conditioner(ip_token).predict(
|
|
|
|
| 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,
|