multimodalart HF Staff commited on
Commit
9ac597f
·
verified ·
1 Parent(s): 9a812e9

Forward the caller's ZeroGPU token to the conditioner, plainly

Browse files
Files changed (1) hide show
  1. app.py +19 -77
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
- LOG_TAG = "gen"
69
- # One `gradio_client.Client` per ZeroGPU identity the conditioner is called with; see `call_conditioner`.
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, hf_token: str | None = None):
176
- """A client for the other half, bound to one ZeroGPU identity.
177
 
178
- Cached per identity: building a `Client` costs a round trip to the Space config, and a forwarded token is per
179
- user session.
 
 
 
 
180
  """
181
  from gradio_client import Client
182
 
183
- key = (ip_token, hf_token)
184
- if key in CLIENTS:
185
- return CLIENTS[key]
186
- # No token of this Space's own: the booking is meant to be the caller's, and an unattributed call is the
187
- # fallback rather than a second identity to spend.
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
- Logged, decoded, on every request never the token itself, only what it claims. ZeroGPU refuses a token its
234
- `/usage-approval` considers expired, and that refusal is indistinguishable from a missing one in the outcome, so
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
- token = None if headers is None else headers.get("x-ip-token")
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 = call_conditioner(
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,