Spaces:
Running on Zero
Running on Zero
Fall back to CPU when the GPU path fails, and surface tracebacks
Browse files/translate 500d in 5s with mt.error still null, so the failure was in the
ZeroGPU call rather than model loading. Try the decorated function, then
retry the same work on CPU instead of failing the request. Reading Space
logs needs a token, so unhandled errors now come back in the response body.
app.py
CHANGED
|
@@ -58,6 +58,23 @@ DEVICE = os.environ.get("ASR_DEVICE", "cuda" if _ON_ZEROGPU else "cpu")
|
|
| 58 |
# at a glance whether the models have finished loading).
|
| 59 |
app = FastAPI()
|
| 60 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
# --------------------------------------------------------------- MODEL LOAD ---
|
| 62 |
# Loading takes minutes on a cold Space (weights download + CPU init). Doing it
|
| 63 |
# at import time would make the Space fail its health check and restart-loop, so
|
|
@@ -172,8 +189,24 @@ def _words_from_chunks(chunks, audio_secs: float):
|
|
| 172 |
return words
|
| 173 |
|
| 174 |
|
| 175 |
-
|
| 176 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
"""Decoding, on the GPU when ZeroGPU has granted one."""
|
| 178 |
pipe = _load("asr")
|
| 179 |
try:
|
|
@@ -186,6 +219,19 @@ def _asr_run(audio, gen):
|
|
| 186 |
return pipe(audio.copy(), return_timestamps=True, generate_kwargs=gen), False
|
| 187 |
|
| 188 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
@app.post("/asr")
|
| 190 |
async def asr(request: Request, x_service_token: str = Header(default=None)):
|
| 191 |
_auth(x_service_token)
|
|
@@ -221,11 +267,23 @@ class TranslateIn(BaseModel):
|
|
| 221 |
texts: list[str]
|
| 222 |
|
| 223 |
|
| 224 |
-
|
| 225 |
-
def _mt_run(texts):
|
| 226 |
return _load("mt")(texts, batch_size=MT_BATCH, truncation=True)
|
| 227 |
|
| 228 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
@app.post("/translate")
|
| 230 |
def translate(body: TranslateIn, x_service_token: str = Header(default=None)):
|
| 231 |
_auth(x_service_token)
|
|
|
|
| 58 |
# at a glance whether the models have finished loading).
|
| 59 |
app = FastAPI()
|
| 60 |
|
| 61 |
+
|
| 62 |
+
@app.exception_handler(Exception)
|
| 63 |
+
def _unhandled(request, exc):
|
| 64 |
+
"""
|
| 65 |
+
Return the traceback in the response body. Reading a Space's logs needs an
|
| 66 |
+
HF token, so an opaque 500 is genuinely hard to diagnose from the client
|
| 67 |
+
that is calling this. Nothing here handles user data worth hiding.
|
| 68 |
+
"""
|
| 69 |
+
import traceback
|
| 70 |
+
from fastapi.responses import JSONResponse
|
| 71 |
+
tb = traceback.format_exc()
|
| 72 |
+
print(tb, flush=True)
|
| 73 |
+
return JSONResponse(status_code=500,
|
| 74 |
+
content={"error": f"{type(exc).__name__}: {exc}",
|
| 75 |
+
"traceback": tb.splitlines()[-12:],
|
| 76 |
+
"device": DEVICE})
|
| 77 |
+
|
| 78 |
# --------------------------------------------------------------- MODEL LOAD ---
|
| 79 |
# Loading takes minutes on a cold Space (weights download + CPU init). Doing it
|
| 80 |
# at import time would make the Space fail its health check and restart-loop, so
|
|
|
|
| 189 |
return words
|
| 190 |
|
| 191 |
|
| 192 |
+
def _device_fallback() -> bool:
|
| 193 |
+
"""
|
| 194 |
+
Drop to CPU and force a reload. ZeroGPU only grants a device inside its
|
| 195 |
+
decorated functions, and the grant can fail for reasons that have nothing
|
| 196 |
+
to do with the model — quota, a worker thread, a cold pool. Falling back is
|
| 197 |
+
slower but keeps the service answering instead of 500ing.
|
| 198 |
+
"""
|
| 199 |
+
global DEVICE
|
| 200 |
+
if DEVICE == "cpu":
|
| 201 |
+
return False
|
| 202 |
+
print(f"GPU path failed on {DEVICE} — reloading on CPU", flush=True)
|
| 203 |
+
DEVICE = "cpu"
|
| 204 |
+
_models["asr"] = _models["mt"] = None
|
| 205 |
+
_errors["asr"] = _errors["mt"] = None
|
| 206 |
+
return True
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def _asr_impl(audio, gen):
|
| 210 |
"""Decoding, on the GPU when ZeroGPU has granted one."""
|
| 211 |
pipe = _load("asr")
|
| 212 |
try:
|
|
|
|
| 219 |
return pipe(audio.copy(), return_timestamps=True, generate_kwargs=gen), False
|
| 220 |
|
| 221 |
|
| 222 |
+
_asr_gpu = _GPU(_asr_impl)
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def _asr_run(audio, gen):
|
| 226 |
+
try:
|
| 227 |
+
return _asr_gpu(audio, gen)
|
| 228 |
+
except Exception as e:
|
| 229 |
+
print(f"asr on {DEVICE} failed ({type(e).__name__}: {e})", flush=True)
|
| 230 |
+
if not _device_fallback():
|
| 231 |
+
raise
|
| 232 |
+
return _asr_impl(audio, gen)
|
| 233 |
+
|
| 234 |
+
|
| 235 |
@app.post("/asr")
|
| 236 |
async def asr(request: Request, x_service_token: str = Header(default=None)):
|
| 237 |
_auth(x_service_token)
|
|
|
|
| 267 |
texts: list[str]
|
| 268 |
|
| 269 |
|
| 270 |
+
def _mt_impl(texts):
|
|
|
|
| 271 |
return _load("mt")(texts, batch_size=MT_BATCH, truncation=True)
|
| 272 |
|
| 273 |
|
| 274 |
+
_mt_gpu = _GPU(_mt_impl)
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def _mt_run(texts):
|
| 278 |
+
try:
|
| 279 |
+
return _mt_gpu(texts)
|
| 280 |
+
except Exception as e:
|
| 281 |
+
print(f"mt on {DEVICE} failed ({type(e).__name__}: {e})", flush=True)
|
| 282 |
+
if not _device_fallback():
|
| 283 |
+
raise
|
| 284 |
+
return _mt_impl(texts)
|
| 285 |
+
|
| 286 |
+
|
| 287 |
@app.post("/translate")
|
| 288 |
def translate(body: TranslateIn, x_service_token: str = Header(default=None)):
|
| 289 |
_auth(x_service_token)
|