multimodalart HF Staff commited on
Commit
9c16554
·
verified ·
1 Parent(s): a0bf481

Forward the caller's ZeroGPU token to the conditioner so one request bills as one request

Browse files
Files changed (2) hide show
  1. README.md +13 -0
  2. app.py +43 -12
README.md CHANGED
@@ -158,6 +158,19 @@ one-time `PIPE.to("cuda")` is inside the first row's 339 s and does not reappear
158
  | `H3_GPU_DURATION` | `900` | Seconds per request; the pool applies a 1.5 duration factor. |
159
  | `H3_GPU_SIZE` | `xlarge` | ZeroGPU allocation size. `large` does not fit. |
160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  ## Secrets
162
 
163
  Nothing this Space loads is private any more: the weights are the public
 
158
  | `H3_GPU_DURATION` | `900` | Seconds per request; the pool applies a 1.5 duration factor. |
159
  | `H3_GPU_SIZE` | `xlarge` | ZeroGPU allocation size. `large` does not fit. |
160
 
161
+ ## Whose GPU quota pays
162
+
163
+ Two cards are booked per request: this Space's denoise loop and the conditioner's forward. ZeroGPU attributes a
164
+ booking to the `X-IP-Token` header of the request that triggered it, so this Space forwards the caller's token to the
165
+ conditioner (`gradio_client.Client(..., headers={"X-IP-Token": ...})`, from the `gr.Request` gradio injects — the UI
166
+ path and the `/generate` API path alike). One user's request then bills as one request across both halves, the way it
167
+ would if this were a single Space, and no org token is ever spent on it.
168
+
169
+ A caller the router cannot attribute — an unauthenticated API call — falls back to the conditioner's IP-based quota,
170
+ whose ceiling is 120 credits. An `xlarge` booking costs **twice** its seconds there, so the conditioner keeps its
171
+ reservation at 60 s (120 credits) for an encode; asking it to upsample a prompt books 120 s (240 credits) and needs a
172
+ forwarded token.
173
+
174
  ## Secrets
175
 
176
  Nothing this Space loads is private any more: the weights are the public
app.py CHANGED
@@ -65,7 +65,7 @@ PIPE = None
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,17 +170,47 @@ def _arm_decode_hooks(pipe):
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
- global CLIENT
176
- if CLIENT is None:
177
- from gradio_client import Client
178
 
179
- CLIENT = Client(CONDITIONER_SPACE) # public Space, no org token: the request runs on the caller side quota
180
- return CLIENT
 
 
 
181
 
 
 
 
182
 
183
- def encode_remote(prompt, image_path, last_image_path, canvas, num_frames, rewrite_prompt=False):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  """Ask the conditioner Space for `prompt_embeds` + `text_token_tags`. Off this Space's GPU time entirely.
185
 
186
  `rewrite_prompt` is the conditioner's prompt upsampling: it rewrites the request into MiniMax-H3's trained format
@@ -191,7 +221,7 @@ def encode_remote(prompt, image_path, last_image_path, canvas, num_frames, rewri
191
  from gradio_client import handle_file
192
  from safetensors import safe_open
193
 
194
- path, plan = conditioner().predict(
195
  prompt=prompt,
196
  image_path=handle_file(image_path) if image_path else None,
197
  last_image_path=handle_file(last_image_path) if last_image_path else None,
@@ -255,7 +285,7 @@ def _generate(prompt_embeds, text_token_tags, image, last_image, height, width,
255
  return state.get("videos")[0], state.get("audio")[0].cpu(), state.get("sampling_rate")
256
 
257
 
258
- 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)):
259
  """One request. `upsample` is appended last and defaults off, so an existing API client is untouched by it."""
260
  if LOAD_ERROR:
261
  raise gr.Error(LOAD_ERROR)
@@ -273,7 +303,8 @@ def generate(prompt, image_path=None, last_image_path=None, canvas=DEFAULT_CANVA
273
  progress(0.0, desc=f"Upsampling the prompt on {CONDITIONER_SPACE} ..." if upsample else f"Conditioning on {CONDITIONER_SPACE} ...")
274
  conditioned = time.time()
275
  prompt_embeds, text_token_tags, metadata, plan = encode_remote(
276
- prompt, image_path, last_image_path, canvas, num_frames, rewrite_prompt=upsample
 
277
  )
278
  condition_seconds = time.time() - conditioned
279
  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
+ CLIENTS: dict[str | None, object] = {}
69
 
70
 
71
  def status() -> str:
 
170
  module.decode = armed
171
 
172
 
173
+ def conditioner(ip_token: str | None = None):
174
+ """The other half, over the gradio API — booked against *the caller's* ZeroGPU quota, not this org's.
 
 
 
175
 
176
+ ZeroGPU attributes a booking to the `X-IP-Token` header of the request that triggered it
177
+ (`spaces/zero/client.py`), which the Spaces router puts on every browser request. That header is what pays for
178
+ this Space's own `@spaces.GPU` call, and forwarding it to the conditioner makes the same identity pay for the
179
+ conditioner's — the two halves of one user's request then bill as one request, the way they would if this were a
180
+ single Space.
181
 
182
+ Without it the conditioner falls back to an IP-based quota, whose ceiling is low enough that an `xlarge` booking
183
+ is refused outright ("The requested GPU duration (Ns) is larger than the maximum allowed"), so a call that does
184
+ not forward a token only works because the conditioner keeps its own reservation small.
185
 
186
+ Cached per token: building a `Client` costs a round trip to the Space config, and a token is per user session.
187
+ """
188
+ from gradio_client import Client
189
+
190
+ if ip_token in CLIENTS:
191
+ return CLIENTS[ip_token]
192
+ # No org token: the request runs on the caller side quota, which is the point of forwarding theirs.
193
+ client = Client(CONDITIONER_SPACE, headers={"X-IP-Token": ip_token} if ip_token else None)
194
+ if len(CLIENTS) >= 32:
195
+ CLIENTS.pop(next(iter(CLIENTS)))
196
+ CLIENTS[ip_token] = client
197
+ return client
198
+
199
+
200
+ def ip_token_of(request) -> str | None:
201
+ """The caller's ZeroGPU identity, as the Spaces router put it on this request.
202
+
203
+ Present on a browser request and on an API request the router could attribute; absent for a truly anonymous
204
+ caller, which then falls back to the conditioner's IP-based quota. Both the UI path and the `/generate` API path
205
+ reach this through the same `gr.Request` gradio injects for a parameter annotated with it.
206
+ """
207
+ headers = getattr(request, "headers", None)
208
+ token = None if headers is None else headers.get("x-ip-token")
209
+ print(f"[gen] conditioner call {'forwards the caller ZeroGPU token' if token else 'is anonymous (IP quota)'}", flush=True)
210
+ return token
211
+
212
+
213
+ def encode_remote(prompt, image_path, last_image_path, canvas, num_frames, rewrite_prompt=False, ip_token=None):
214
  """Ask the conditioner Space for `prompt_embeds` + `text_token_tags`. Off this Space's GPU time entirely.
215
 
216
  `rewrite_prompt` is the conditioner's prompt upsampling: it rewrites the request into MiniMax-H3's trained format
 
221
  from gradio_client import handle_file
222
  from safetensors import safe_open
223
 
224
+ path, plan = conditioner(ip_token).predict(
225
  prompt=prompt,
226
  image_path=handle_file(image_path) if image_path else None,
227
  last_image_path=handle_file(last_image_path) if last_image_path else None,
 
285
  return state.get("videos")[0], state.get("audio")[0].cpu(), state.get("sampling_rate")
286
 
287
 
288
+ 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), request: gr.Request | None = None):
289
  """One request. `upsample` is appended last and defaults off, so an existing API client is untouched by it."""
290
  if LOAD_ERROR:
291
  raise gr.Error(LOAD_ERROR)
 
303
  progress(0.0, desc=f"Upsampling the prompt on {CONDITIONER_SPACE} ..." if upsample else f"Conditioning on {CONDITIONER_SPACE} ...")
304
  conditioned = time.time()
305
  prompt_embeds, text_token_tags, metadata, plan = encode_remote(
306
+ prompt, image_path, last_image_path, canvas, num_frames, rewrite_prompt=upsample,
307
+ ip_token=ip_token_of(request),
308
  )
309
  condition_seconds = time.time() - conditioned
310
  height, width, num_frames = (int(metadata[key]) for key in ("height", "width", "num_frames"))