DevEmmy commited on
Commit
80a7b2e
·
1 Parent(s): 868e5b5

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.

Files changed (1) hide show
  1. app.py +62 -4
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
- @_GPU
176
- def _asr_run(audio, gen):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- @_GPU
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)