Spaces:
Running on Zero
Running on Zero
Pay for the conditioner call with the first ZeroGPU identity that can: the caller's token, this Space's HF_TOKEN, then none
Browse files
README.md
CHANGED
|
@@ -161,27 +161,30 @@ one-time `PIPE.to("cuda")` is inside the first row's 339 s and does not reappear
|
|
| 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
|
| 165 |
-
conditioner (`gradio_client.Client(..., headers={"X-IP-Token": ...})`, off the `gr.Request` gradio injects — the UI
|
| 166 |
-
path and the `/generate` API path alike). When ZeroGPU honours it, one user's request bills as one request across both
|
| 167 |
-
halves and no org token is spent on it.
|
| 168 |
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
conditioner
|
| 176 |
-
|
|
|
|
|
|
|
|
|
|
| 177 |
|
| 178 |
## Secrets
|
| 179 |
|
| 180 |
-
|
| 181 |
-
[`MiniMaxAI/MiniMax-H3`](https://huggingface.co/MiniMaxAI/MiniMax-H3)
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
this org's. No `HF_TOKEN` is required.
|
| 185 |
|
| 186 |
## Where diffusers comes from
|
| 187 |
|
|
|
|
| 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 identity of the request that triggered it, so the conditioner call tries three in order:
|
|
|
|
|
|
|
|
|
|
| 165 |
|
| 166 |
+
1. **the caller's own `X-IP-Token`**, forwarded off the `gr.Request` gradio injects (the UI path and the `/generate`
|
| 167 |
+
API path alike). The request then bills as one request across both halves and costs this org nothing. Best effort:
|
| 168 |
+
ZeroGPU answers `401` for a proxy token it will not honour — which is what a token minted for *this* Space looks
|
| 169 |
+
like arriving at another one — and `spaces` surfaces that as `Expired ZeroGPU proxy token`.
|
| 170 |
+
2. **this Space's `HF_TOKEN`**, which charges the account that owns the Space and has a real quota. This is what
|
| 171 |
+
carries the Space in practice.
|
| 172 |
+
3. **no token**, an IP-based free quota shared by everything calling out of this Space's egress IP. A last resort.
|
| 173 |
|
| 174 |
+
Any of ZeroGPU's "this identity cannot pay" answers — a refused proxy token, a duration past what the identity may
|
| 175 |
+
book, an exhausted quota — moves on to the next identity instead of failing the request, and the log line
|
| 176 |
+
`conditioner call paid for by ...` records which one paid.
|
| 177 |
+
|
| 178 |
+
The conditioner is sized so that even (3) is legal: an unattributed caller may book at most 120 credits at a time and
|
| 179 |
+
an `xlarge` booking costs **twice** its seconds, so the conditioner books the encode (45 s) and a prompt upsample
|
| 180 |
+
(60 s) as **two separate calls**, where one combined booking would be refused outright.
|
| 181 |
|
| 182 |
## Secrets
|
| 183 |
|
| 184 |
+
`HF_TOKEN` — used for exactly one thing: paying for the conditioner call when the caller's own ZeroGPU token cannot
|
| 185 |
+
(see above). Everything this Space downloads is public: the [`MiniMaxAI/MiniMax-H3`](https://huggingface.co/MiniMaxAI/MiniMax-H3)
|
| 186 |
+
checkpoint and the [`multimodalart/minimax-h3-aoti`](https://huggingface.co/multimodalart/minimax-h3-aoti) packages.
|
| 187 |
+
Without it, a call the caller cannot pay for falls back to a shared IP quota worth a couple of requests a day.
|
|
|
|
| 188 |
|
| 189 |
## Where diffusers comes from
|
| 190 |
|
app.py
CHANGED
|
@@ -24,6 +24,9 @@ ATTENTION = os.environ.get("H3_ATTENTION", "_native_cudnn").lower()
|
|
| 24 |
GPU_DURATION = int(os.environ.get("H3_GPU_DURATION", "900"))
|
| 25 |
GPU_SIZE = os.environ.get("H3_GPU_SIZE", "xlarge")
|
| 26 |
ON_SPACES = bool(os.environ.get("SPACE_ID"))
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
CANVASES = {
|
| 29 |
# 16:9
|
|
@@ -65,7 +68,9 @@ PIPE = None
|
|
| 65 |
MANAGER = None
|
| 66 |
LOAD_ERROR: str | None = None
|
| 67 |
LOADED_IN: float | None = None
|
| 68 |
-
|
|
|
|
|
|
|
| 69 |
|
| 70 |
|
| 71 |
def status() -> str:
|
|
@@ -170,33 +175,70 @@ def _arm_decode_hooks(pipe):
|
|
| 170 |
module.decode = armed
|
| 171 |
|
| 172 |
|
| 173 |
-
def conditioner(ip_token: str | None = None):
|
| 174 |
-
"""
|
| 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 |
-
|
| 183 |
-
|
| 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 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
client = Client(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
if len(CLIENTS) >= 32:
|
| 195 |
CLIENTS.pop(next(iter(CLIENTS)))
|
| 196 |
-
CLIENTS[
|
| 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 |
|
|
@@ -210,30 +252,6 @@ def ip_token_of(request) -> str | None:
|
|
| 210 |
return token
|
| 211 |
|
| 212 |
|
| 213 |
-
LOG_TAG = "gen"
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
def call_conditioner(ip_token, **arguments):
|
| 217 |
-
"""One conditioner call, on the caller's ZeroGPU identity when that is accepted and anonymously when it is not.
|
| 218 |
-
|
| 219 |
-
Forwarding is best effort. ZeroGPU's `/schedule` answers `401` for a proxy token it will not honour — which is
|
| 220 |
-
what a token minted for *this* Space looks like when it arrives at the conditioner — and `spaces` surfaces that as
|
| 221 |
-
`Expired ZeroGPU proxy token`. So the forwarded call is tried first, and a rejected token falls back to no token
|
| 222 |
-
at all rather than failing the request. The conditioner's own bookings are sized to fit the unattributed ceiling,
|
| 223 |
-
so the fallback is a working path and not a degraded one.
|
| 224 |
-
"""
|
| 225 |
-
api_name = arguments.pop("api_name")
|
| 226 |
-
if ip_token is not None:
|
| 227 |
-
try:
|
| 228 |
-
return conditioner(ip_token).predict(**arguments, api_name=api_name)
|
| 229 |
-
except Exception as error:
|
| 230 |
-
if "proxy token" not in str(error):
|
| 231 |
-
raise
|
| 232 |
-
print(f"[{LOG_TAG}] the forwarded ZeroGPU token was refused ({error}); retrying anonymously", flush=True)
|
| 233 |
-
CLIENTS.pop(ip_token, None)
|
| 234 |
-
return conditioner(None).predict(**arguments, api_name=api_name)
|
| 235 |
-
|
| 236 |
-
|
| 237 |
def encode_remote(prompt, image_path, last_image_path, canvas, num_frames, rewrite_prompt=False, ip_token=None):
|
| 238 |
"""Ask the conditioner Space for `prompt_embeds` + `text_token_tags`. Off this Space's GPU time entirely.
|
| 239 |
|
|
|
|
| 24 |
GPU_DURATION = int(os.environ.get("H3_GPU_DURATION", "900"))
|
| 25 |
GPU_SIZE = os.environ.get("H3_GPU_SIZE", "xlarge")
|
| 26 |
ON_SPACES = bool(os.environ.get("SPACE_ID"))
|
| 27 |
+
# Used for one thing only: paying for the conditioner call when the caller's own token cannot. Everything
|
| 28 |
+
# this Space downloads — the checkpoint and the AoTI packages — is public.
|
| 29 |
+
HF_TOKEN = os.environ.get("HF_TOKEN")
|
| 30 |
|
| 31 |
CANVASES = {
|
| 32 |
# 16:9
|
|
|
|
| 68 |
MANAGER = None
|
| 69 |
LOAD_ERROR: str | None = None
|
| 70 |
LOADED_IN: float | None = None
|
| 71 |
+
LOG_TAG = "gen"
|
| 72 |
+
# One `gradio_client.Client` per ZeroGPU identity the conditioner is called with; see `call_conditioner`.
|
| 73 |
+
CLIENTS: dict[tuple[str | None, str | None], object] = {}
|
| 74 |
|
| 75 |
|
| 76 |
def status() -> str:
|
|
|
|
| 175 |
module.decode = armed
|
| 176 |
|
| 177 |
|
| 178 |
+
def conditioner(ip_token: str | None = None, hf_token: str | None = None):
|
| 179 |
+
"""A client for the other half, bound to one ZeroGPU identity.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
|
| 181 |
+
Cached per identity: building a `Client` costs a round trip to the Space config, and a forwarded token is per
|
| 182 |
+
user session.
|
|
|
|
|
|
|
|
|
|
| 183 |
"""
|
| 184 |
from gradio_client import Client
|
| 185 |
|
| 186 |
+
key = (ip_token, hf_token)
|
| 187 |
+
if key in CLIENTS:
|
| 188 |
+
return CLIENTS[key]
|
| 189 |
+
client = Client(
|
| 190 |
+
CONDITIONER_SPACE,
|
| 191 |
+
token=hf_token,
|
| 192 |
+
headers={"X-IP-Token": ip_token} if ip_token else None,
|
| 193 |
+
)
|
| 194 |
if len(CLIENTS) >= 32:
|
| 195 |
CLIENTS.pop(next(iter(CLIENTS)))
|
| 196 |
+
CLIENTS[key] = client
|
| 197 |
return client
|
| 198 |
|
| 199 |
|
| 200 |
+
# What ZeroGPU says when an identity cannot pay for the booking, in any of its forms: a proxy token it will not
|
| 201 |
+
# honour (`401`, surfaced as "Expired ZeroGPU proxy token"), a duration past what that identity may book, and an
|
| 202 |
+
# exhausted quota. All three mean "try the next identity" rather than "fail the request".
|
| 203 |
+
_UNPAYABLE = ("proxy token", "ZeroGPU quota", "larger than the maximum allowed", "GPU limit")
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
def call_conditioner(ip_token, **arguments):
|
| 207 |
+
"""One conditioner call, against the first ZeroGPU identity that can pay for it.
|
| 208 |
+
|
| 209 |
+
In order of preference:
|
| 210 |
+
|
| 211 |
+
1. **the caller's own forwarded `X-IP-Token`** — the request bills as one request across both halves and costs
|
| 212 |
+
this org nothing. Best effort: a proxy token minted for this Space is not necessarily honoured when it
|
| 213 |
+
arrives at another one, and ZeroGPU answers `401` when it is not.
|
| 214 |
+
2. **this Space's `HF_TOKEN`** — the booking is charged to the account that owns the Space, which has a real
|
| 215 |
+
quota. This is what carries the Space in practice.
|
| 216 |
+
3. **no token at all** — an IP-based free quota, shared by everything calling out of this Space's egress IP and
|
| 217 |
+
worth a couple of requests a day. A last resort, not a design.
|
| 218 |
+
|
| 219 |
+
The conditioner's own bookings are sized to fit even (3): it books an encode and a prompt upsample as two calls
|
| 220 |
+
of 45 s and 60 s, because an unattributed caller may book at most 60 s of `xlarge` at a time.
|
| 221 |
+
"""
|
| 222 |
+
api_name = arguments.pop("api_name")
|
| 223 |
+
attempts = []
|
| 224 |
+
if ip_token:
|
| 225 |
+
attempts.append(("the caller's forwarded ZeroGPU token", {"ip_token": ip_token}))
|
| 226 |
+
if HF_TOKEN:
|
| 227 |
+
attempts.append(("this Space's HF_TOKEN", {"hf_token": HF_TOKEN}))
|
| 228 |
+
attempts.append(("no token, on an IP quota", {}))
|
| 229 |
+
|
| 230 |
+
for index, (label, identity) in enumerate(attempts):
|
| 231 |
+
try:
|
| 232 |
+
result = conditioner(**identity).predict(**arguments, api_name=api_name)
|
| 233 |
+
print(f"[{LOG_TAG}] conditioner call paid for by {label}", flush=True)
|
| 234 |
+
return result
|
| 235 |
+
except Exception as error:
|
| 236 |
+
if index == len(attempts) - 1 or not any(reason in str(error) for reason in _UNPAYABLE):
|
| 237 |
+
raise
|
| 238 |
+
print(f"[{LOG_TAG}] {label}: {error}; trying the next identity", flush=True)
|
| 239 |
+
CLIENTS.pop((identity.get("ip_token"), identity.get("hf_token")), None)
|
| 240 |
+
|
| 241 |
+
|
| 242 |
def ip_token_of(request) -> str | None:
|
| 243 |
"""The caller's ZeroGPU identity, as the Spaces router put it on this request.
|
| 244 |
|
|
|
|
| 252 |
return token
|
| 253 |
|
| 254 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
def encode_remote(prompt, image_path, last_image_path, canvas, num_frames, rewrite_prompt=False, ip_token=None):
|
| 256 |
"""Ask the conditioner Space for `prompt_embeds` + `text_token_tags`. Off this Space's GPU time entirely.
|
| 257 |
|