Forward the caller's ZeroGPU token to the conditioner so one request bills as one request
Browse files
README.md
CHANGED
|
@@ -181,6 +181,19 @@ on is cold and a cold one pays the lazy 72.16 GiB `PIPE.to("cuda")` inside its f
|
|
| 181 |
| `H3_PLACEMENT_ALLOWANCE` | `90` | Seconds of the reservation set aside for a cold worker's placement. |
|
| 182 |
| `H3_GPU_SIZE` | `xlarge` | ZeroGPU allocation size. `large` does not fit. |
|
| 183 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
## Secrets
|
| 185 |
|
| 186 |
Nothing this Space loads is private any more: the weights are the public
|
|
|
|
| 181 |
| `H3_PLACEMENT_ALLOWANCE` | `90` | Seconds of the reservation set aside for a cold worker's placement. |
|
| 182 |
| `H3_GPU_SIZE` | `xlarge` | ZeroGPU allocation size. `large` does not fit. |
|
| 183 |
|
| 184 |
+
## Whose GPU quota pays
|
| 185 |
+
|
| 186 |
+
Two cards are booked per request: this Space's denoise loop and the conditioner's forward. ZeroGPU attributes a
|
| 187 |
+
booking to the `X-IP-Token` header of the request that triggered it, so this Space forwards the caller's token to the
|
| 188 |
+
conditioner (`gradio_client.Client(..., headers={"X-IP-Token": ...})`, from the `gr.Request` gradio injects — the UI
|
| 189 |
+
path and the `/generate` API path alike). One user's request then bills as one request across both halves, the way it
|
| 190 |
+
would if this were a single Space, and no org token is ever spent on it.
|
| 191 |
+
|
| 192 |
+
A caller the router cannot attribute — an unauthenticated API call — falls back to the conditioner's IP-based quota,
|
| 193 |
+
whose ceiling is 120 credits. An `xlarge` booking costs **twice** its seconds there, so the conditioner keeps its
|
| 194 |
+
reservation at 60 s (120 credits) for an encode; asking it to upsample a prompt books 120 s (240 credits) and needs a
|
| 195 |
+
forwarded token.
|
| 196 |
+
|
| 197 |
## Secrets
|
| 198 |
|
| 199 |
Nothing this Space loads is private any more: the weights are the public
|
app.py
CHANGED
|
@@ -200,7 +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 |
|
| 205 |
|
| 206 |
def load_models() -> str | None:
|
|
@@ -306,14 +306,44 @@ def _arm_decode_hooks(pipe):
|
|
| 306 |
setattr(module, method, armed)
|
| 307 |
|
| 308 |
|
| 309 |
-
def conditioner():
|
| 310 |
-
"""The other half, over the gradio API
|
| 311 |
-
global CLIENT
|
| 312 |
-
if CLIENT is None:
|
| 313 |
-
from gradio_client import Client
|
| 314 |
|
| 315 |
-
|
| 316 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
|
| 318 |
|
| 319 |
def probe(path: str) -> tuple[float | None, float | None]:
|
|
@@ -412,7 +442,7 @@ def check(prompt: str, references: list[tuple[str, str]]) -> None:
|
|
| 412 |
)
|
| 413 |
|
| 414 |
|
| 415 |
-
def encode_remote(prompt, references, canvas, num_frames, rewrite_prompt=False):
|
| 416 |
"""Ask the conditioner Space for `prompt_embeds` + `text_token_tags`. Off this Space's GPU time entirely.
|
| 417 |
|
| 418 |
The references go over with the request: `ref2va`'s presentation puts a vision block in front of the prompt for
|
|
@@ -427,7 +457,7 @@ def encode_remote(prompt, references, canvas, num_frames, rewrite_prompt=False):
|
|
| 427 |
from gradio_client import handle_file
|
| 428 |
from safetensors import safe_open
|
| 429 |
|
| 430 |
-
path, plan = conditioner().predict(
|
| 431 |
prompt=prompt,
|
| 432 |
media=[handle_file(path) for _, path in references],
|
| 433 |
kinds=",".join(kind for kind, _ in references),
|
|
@@ -494,6 +524,7 @@ def generate(
|
|
| 494 |
seed=42,
|
| 495 |
upsample=False,
|
| 496 |
progress=gr.Progress(track_tqdm=True),
|
|
|
|
| 497 |
):
|
| 498 |
"""One request. `upsample` is appended last and defaults off, so an existing API client is untouched by it."""
|
| 499 |
if LOAD_ERROR:
|
|
@@ -516,7 +547,7 @@ def generate(
|
|
| 516 |
conditioned = time.time()
|
| 517 |
try:
|
| 518 |
prompt_embeds, text_token_tags, metadata, plan = encode_remote(
|
| 519 |
-
prompt, references, canvas, requested, rewrite_prompt=upsample
|
| 520 |
)
|
| 521 |
except gr.Error:
|
| 522 |
raise
|
|
|
|
| 200 |
PIPE = None
|
| 201 |
MANAGER = None
|
| 202 |
LOAD_ERROR: str | None = None
|
| 203 |
+
CLIENTS: dict[str | None, object] = {}
|
| 204 |
|
| 205 |
|
| 206 |
def load_models() -> str | None:
|
|
|
|
| 306 |
setattr(module, method, armed)
|
| 307 |
|
| 308 |
|
| 309 |
+
def conditioner(ip_token: str | None = None):
|
| 310 |
+
"""The other half, over the gradio API — booked against *the caller's* ZeroGPU quota, not this org's.
|
|
|
|
|
|
|
|
|
|
| 311 |
|
| 312 |
+
ZeroGPU attributes a booking to the `X-IP-Token` header of the request that triggered it
|
| 313 |
+
(`spaces/zero/client.py`), which the Spaces router puts on every browser request. That header is what pays for
|
| 314 |
+
this Space's own `@spaces.GPU` call, and forwarding it to the conditioner makes the same identity pay for the
|
| 315 |
+
conditioner's — the two halves of one user's request then bill as one request, the way they would if this were a
|
| 316 |
+
single Space.
|
| 317 |
+
|
| 318 |
+
Without it the conditioner falls back to an IP-based quota, whose ceiling is low enough that an `xlarge` booking
|
| 319 |
+
is refused outright ("The requested GPU duration (Ns) is larger than the maximum allowed"), so a call that does
|
| 320 |
+
not forward a token only works because the conditioner keeps its own reservation small.
|
| 321 |
+
|
| 322 |
+
Cached per token: building a `Client` costs a round trip to the Space config, and a token is per user session.
|
| 323 |
+
"""
|
| 324 |
+
from gradio_client import Client
|
| 325 |
+
|
| 326 |
+
if ip_token in CLIENTS:
|
| 327 |
+
return CLIENTS[ip_token]
|
| 328 |
+
# No org token: the request runs on the caller side quota, which is the point of forwarding theirs.
|
| 329 |
+
client = Client(CONDITIONER_SPACE, headers={"X-IP-Token": ip_token} if ip_token else None)
|
| 330 |
+
if len(CLIENTS) >= 32:
|
| 331 |
+
CLIENTS.pop(next(iter(CLIENTS)))
|
| 332 |
+
CLIENTS[ip_token] = client
|
| 333 |
+
return client
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
def ip_token_of(request) -> str | None:
|
| 337 |
+
"""The caller's ZeroGPU identity, as the Spaces router put it on this request.
|
| 338 |
+
|
| 339 |
+
Present on a browser request and on an API request the router could attribute; absent for a truly anonymous
|
| 340 |
+
caller, which then falls back to the conditioner's IP-based quota. Both the UI path and the `/generate` API path
|
| 341 |
+
reach this through the same `gr.Request` gradio injects for a parameter annotated with it.
|
| 342 |
+
"""
|
| 343 |
+
headers = getattr(request, "headers", None)
|
| 344 |
+
token = None if headers is None else headers.get("x-ip-token")
|
| 345 |
+
print(f"[ref2va] conditioner call {'forwards the caller ZeroGPU token' if token else 'is anonymous (IP quota)'}", flush=True)
|
| 346 |
+
return token
|
| 347 |
|
| 348 |
|
| 349 |
def probe(path: str) -> tuple[float | None, float | None]:
|
|
|
|
| 442 |
)
|
| 443 |
|
| 444 |
|
| 445 |
+
def encode_remote(prompt, references, canvas, num_frames, rewrite_prompt=False, ip_token=None):
|
| 446 |
"""Ask the conditioner Space for `prompt_embeds` + `text_token_tags`. Off this Space's GPU time entirely.
|
| 447 |
|
| 448 |
The references go over with the request: `ref2va`'s presentation puts a vision block in front of the prompt for
|
|
|
|
| 457 |
from gradio_client import handle_file
|
| 458 |
from safetensors import safe_open
|
| 459 |
|
| 460 |
+
path, plan = conditioner(ip_token).predict(
|
| 461 |
prompt=prompt,
|
| 462 |
media=[handle_file(path) for _, path in references],
|
| 463 |
kinds=",".join(kind for kind, _ in references),
|
|
|
|
| 524 |
seed=42,
|
| 525 |
upsample=False,
|
| 526 |
progress=gr.Progress(track_tqdm=True),
|
| 527 |
+
request: gr.Request | None = None,
|
| 528 |
):
|
| 529 |
"""One request. `upsample` is appended last and defaults off, so an existing API client is untouched by it."""
|
| 530 |
if LOAD_ERROR:
|
|
|
|
| 547 |
conditioned = time.time()
|
| 548 |
try:
|
| 549 |
prompt_embeds, text_token_tags, metadata, plan = encode_remote(
|
| 550 |
+
prompt, references, canvas, requested, rewrite_prompt=upsample, ip_token=ip_token_of(request)
|
| 551 |
)
|
| 552 |
except gr.Error:
|
| 553 |
raise
|