Spaces:
Sleeping
Sleeping
lease ladder 60/30/10 via free probes; 120s per session is too expensive
Browse files
README.md
CHANGED
|
@@ -34,5 +34,5 @@ DiT + streaming VAE pipeline, served on ZeroGPU.
|
|
| 34 |
not the first user session.
|
| 35 |
- `torch.compile` is inert in ZeroGPU forks; each fork instead pays a short warmup
|
| 36 |
(CUDA-graph capture, KV fill), so the first 2-3 chunks are slower.
|
| 37 |
-
- A session runs up to ~
|
| 38 |
-
|
|
|
|
| 34 |
not the first user session.
|
| 35 |
- `torch.compile` is inert in ZeroGPU forks; each fork instead pays a short warmup
|
| 36 |
(CUDA-graph capture, KV fill), so the first 2-3 chunks are slower.
|
| 37 |
+
- A session runs up to ~60s of GPU time. Free ZeroGPU quota is limited — this is a
|
| 38 |
+
small-scale demo.
|
app.py
CHANGED
|
@@ -81,8 +81,8 @@ warnings.filterwarnings("ignore", message="ZeroGPU: Cannot get Gradio app Queue
|
|
| 81 |
import spaces
|
| 82 |
|
| 83 |
CKPT_ROOT = Path(os.environ.get("JOYOMNI_CKPT_ROOT", str(_DATA / "checkpoints")))
|
| 84 |
-
#
|
| 85 |
-
SESSION_DURATION = int(os.environ.get("JOYOMNI_SPACES_DURATION", "
|
| 86 |
|
| 87 |
|
| 88 |
# Prebuilt sm_120 wheel: pip runs before repo files exist on Spaces — install here.
|
|
@@ -723,12 +723,12 @@ async def ws(websocket: WebSocket):
|
|
| 723 |
_visitor = SimpleNamespace(headers=_hdrs)
|
| 724 |
pockets = [("visitor", _visitor), ("app-pool", None)]
|
| 725 |
|
| 726 |
-
def _probe(req):
|
| 727 |
token, _ = _get_token_and_payload(_get_headers(req))
|
| 728 |
res, _meta = api_client().schedule(
|
| 729 |
cgroup_path=zutils.self_cgroup_device_path(),
|
| 730 |
token=token, token_version=2,
|
| 731 |
-
duration_seconds=
|
| 732 |
return res
|
| 733 |
|
| 734 |
def _run(name, req, duration):
|
|
@@ -748,37 +748,38 @@ async def ws(websocket: WebSocket):
|
|
| 748 |
|
| 749 |
try:
|
| 750 |
t0 = time.time()
|
| 751 |
-
|
| 752 |
last_quota = None
|
| 753 |
while not stop.is_set():
|
| 754 |
-
live = [(n, r) for n, r in pockets if n
|
| 755 |
if not live:
|
| 756 |
raise RuntimeError(last_quota or "GPU quota exceeded")
|
| 757 |
name, req = live[0]
|
| 758 |
-
|
|
|
|
| 759 |
if isinstance(res, ScheduleResponse):
|
| 760 |
release(res.allowToken, allow_404=True)
|
| 761 |
try:
|
| 762 |
-
_run(name, req,
|
| 763 |
break
|
| 764 |
except Exception as exc: # noqa: BLE001
|
| 765 |
if got_gpu:
|
| 766 |
raise
|
| 767 |
emsg = str(exc)
|
| 768 |
-
print(f"[ws] {name} lease failed: {emsg!r}", flush=True)
|
| 769 |
if "quota" in emsg.lower() or "exceeded" in emsg.lower():
|
| 770 |
-
|
| 771 |
last_quota = emsg
|
| 772 |
continue
|
| 773 |
if isinstance(res, QuotaInfos):
|
| 774 |
-
|
| 775 |
if name == "visitor":
|
| 776 |
last_quota = (f"You have exceeded your GPU quota "
|
| 777 |
-
f"({
|
| 778 |
f"Try again in {res.wait}.")
|
| 779 |
continue
|
| 780 |
if res is httpx.codes.UNAUTHORIZED:
|
| 781 |
-
|
| 782 |
continue
|
| 783 |
close = getattr(res, "close", None)
|
| 784 |
if close:
|
|
|
|
| 81 |
import spaces
|
| 82 |
|
| 83 |
CKPT_ROOT = Path(os.environ.get("JOYOMNI_CKPT_ROOT", str(_DATA / "checkpoints")))
|
| 84 |
+
# 60s = 120s billed on xlarge.
|
| 85 |
+
SESSION_DURATION = int(os.environ.get("JOYOMNI_SPACES_DURATION", "60"))
|
| 86 |
|
| 87 |
|
| 88 |
# Prebuilt sm_120 wheel: pip runs before repo files exist on Spaces — install here.
|
|
|
|
| 723 |
_visitor = SimpleNamespace(headers=_hdrs)
|
| 724 |
pockets = [("visitor", _visitor), ("app-pool", None)]
|
| 725 |
|
| 726 |
+
def _probe(req, duration):
|
| 727 |
token, _ = _get_token_and_payload(_get_headers(req))
|
| 728 |
res, _meta = api_client().schedule(
|
| 729 |
cgroup_path=zutils.self_cgroup_device_path(),
|
| 730 |
token=token, token_version=2,
|
| 731 |
+
duration_seconds=duration, enable_queue=False, gpu_size="xlarge")
|
| 732 |
return res
|
| 733 |
|
| 734 |
def _run(name, req, duration):
|
|
|
|
| 748 |
|
| 749 |
try:
|
| 750 |
t0 = time.time()
|
| 751 |
+
durations = {n: list(dict.fromkeys((SESSION_DURATION, 30, 10))) for n, _ in pockets}
|
| 752 |
last_quota = None
|
| 753 |
while not stop.is_set():
|
| 754 |
+
live = [(n, r) for n, r in pockets if durations[n]]
|
| 755 |
if not live:
|
| 756 |
raise RuntimeError(last_quota or "GPU quota exceeded")
|
| 757 |
name, req = live[0]
|
| 758 |
+
dur = durations[name][0]
|
| 759 |
+
res = _probe(req, dur)
|
| 760 |
if isinstance(res, ScheduleResponse):
|
| 761 |
release(res.allowToken, allow_404=True)
|
| 762 |
try:
|
| 763 |
+
_run(name, req, dur)
|
| 764 |
break
|
| 765 |
except Exception as exc: # noqa: BLE001
|
| 766 |
if got_gpu:
|
| 767 |
raise
|
| 768 |
emsg = str(exc)
|
| 769 |
+
print(f"[ws] {name}@{dur}s lease failed: {emsg!r}", flush=True)
|
| 770 |
if "quota" in emsg.lower() or "exceeded" in emsg.lower():
|
| 771 |
+
durations[name].pop(0)
|
| 772 |
last_quota = emsg
|
| 773 |
continue
|
| 774 |
if isinstance(res, QuotaInfos):
|
| 775 |
+
durations[name].pop(0)
|
| 776 |
if name == "visitor":
|
| 777 |
last_quota = (f"You have exceeded your GPU quota "
|
| 778 |
+
f"({dur * 2}s requested vs. {res.left}s left). "
|
| 779 |
f"Try again in {res.wait}.")
|
| 780 |
continue
|
| 781 |
if res is httpx.codes.UNAUTHORIZED:
|
| 782 |
+
durations[name].clear()
|
| 783 |
continue
|
| 784 |
close = getattr(res, "close", None)
|
| 785 |
if close:
|