DevEmmy commited on
Commit
868e5b5
·
1 Parent(s): 4bb4d57

Declare @spaces.GPU functions so ZeroGPU will start the Space

Browse files

ZeroGPU refuses to start a Space with no decorated function - 'No @spaces.GPU
function detected during startup' - which is why hardware.current stayed None
from the first build. Move the model calls into decorated functions, since
that is the only window in which a device is actually attached, and default
to cuda when the spaces package is importable.

Files changed (1) hide show
  1. app.py +36 -16
app.py CHANGED
@@ -36,9 +36,21 @@ SAMPLE_RATE = 16000
36
  CHUNK_LENGTH_S = float(os.environ.get("CHUNK_LENGTH_S", "30"))
37
  STRIDE_LENGTH_S = float(os.environ.get("STRIDE_LENGTH_S", "5"))
38
  MT_BATCH = int(os.environ.get("MT_BATCH", "16"))
39
- # "cpu" rather than the old -1: transformers 5 takes a device string. Set
40
- # ASR_DEVICE=cuda:0 if this ever runs on real GPU hardware.
41
- DEVICE = os.environ.get("ASR_DEVICE", "cpu")
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
  # Gradio owns the process on a Gradio-SDK Space, but it is a FastAPI app
44
  # underneath — so the REST routes below are the real interface and the little UI
@@ -160,6 +172,20 @@ def _words_from_chunks(chunks, audio_secs: float):
160
  return words
161
 
162
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  @app.post("/asr")
164
  async def asr(request: Request, x_service_token: str = Header(default=None)):
165
  _auth(x_service_token)
@@ -169,7 +195,6 @@ async def asr(request: Request, x_service_token: str = Header(default=None)):
169
 
170
  audio = _decode(raw)
171
  audio_secs = len(audio) / float(SAMPLE_RATE)
172
- pipe = _load("asr")
173
 
174
  # language/task are forced because a fine-tune sometimes ships a generation
175
  # config still defaulting to English transcription, which silently produces
@@ -177,16 +202,7 @@ async def asr(request: Request, x_service_token: str = Header(default=None)):
177
  gen = {"language": "ha", "task": "transcribe"}
178
 
179
  t0 = time.time()
180
- word_level = True
181
- try:
182
- out = pipe(audio.copy(), return_timestamps="word", generate_kwargs=gen)
183
- except Exception as e:
184
- # Word timings need alignment_heads in the generation config. Fine-tunes
185
- # frequently drop them; phrase-level timings still make usable cues.
186
- print(f"word timestamps unavailable ({type(e).__name__}: {e}) — phrase level",
187
- flush=True)
188
- word_level = False
189
- out = pipe(audio.copy(), return_timestamps=True, generate_kwargs=gen)
190
 
191
  words = _words_from_chunks(out.get("chunks"), audio_secs)
192
  print(f"asr {audio_secs:.1f}s audio -> {len(words)} words in {time.time() - t0:.1f}s "
@@ -205,6 +221,11 @@ class TranslateIn(BaseModel):
205
  texts: list[str]
206
 
207
 
 
 
 
 
 
208
  @app.post("/translate")
209
  def translate(body: TranslateIn, x_service_token: str = Header(default=None)):
210
  _auth(x_service_token)
@@ -212,13 +233,12 @@ def translate(body: TranslateIn, x_service_token: str = Header(default=None)):
212
  if not texts:
213
  return {"translations": []}
214
 
215
- pipe = _load("mt")
216
  # Blank inputs are held out and reinserted: MarianMT will happily emit a
217
  # hallucinated sentence for an empty string.
218
  idx = [i for i, t in enumerate(texts) if (t or "").strip()]
219
  out = [""] * len(texts)
220
  if idx:
221
- res = pipe([texts[i] for i in idx], batch_size=MT_BATCH, truncation=True)
222
  for i, r in zip(idx, res):
223
  out[i] = (r.get("translation_text") or "").strip()
224
  return {"translations": out}
 
36
  CHUNK_LENGTH_S = float(os.environ.get("CHUNK_LENGTH_S", "30"))
37
  STRIDE_LENGTH_S = float(os.environ.get("STRIDE_LENGTH_S", "5"))
38
  MT_BATCH = int(os.environ.get("MT_BATCH", "16"))
39
+ # ZeroGPU hands out a GPU only inside @spaces.GPU functions, and refuses to
40
+ # start a Space that declares none ("No @spaces.GPU function detected during
41
+ # startup"). Importing spaces also tells us we are on that hardware, so the
42
+ # device default follows it.
43
+ try:
44
+ import spaces
45
+ _GPU = spaces.GPU(duration=int(os.environ.get("GPU_DURATION", "120")))
46
+ _ON_ZEROGPU = True
47
+ except Exception: # local runs, or a plain CPU host
48
+ _ON_ZEROGPU = False
49
+
50
+ def _GPU(fn):
51
+ return fn
52
+
53
+ DEVICE = os.environ.get("ASR_DEVICE", "cuda" if _ON_ZEROGPU else "cpu")
54
 
55
  # Gradio owns the process on a Gradio-SDK Space, but it is a FastAPI app
56
  # underneath — so the REST routes below are the real interface and the little UI
 
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:
180
+ return pipe(audio.copy(), return_timestamps="word", generate_kwargs=gen), True
181
+ except Exception as e:
182
+ # Word timings need alignment_heads in the generation config. Fine-tunes
183
+ # frequently drop them; phrase-level timings still make usable cues.
184
+ print(f"word timestamps unavailable ({type(e).__name__}: {e}) — phrase level",
185
+ flush=True)
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)
 
195
 
196
  audio = _decode(raw)
197
  audio_secs = len(audio) / float(SAMPLE_RATE)
 
198
 
199
  # language/task are forced because a fine-tune sometimes ships a generation
200
  # config still defaulting to English transcription, which silently produces
 
202
  gen = {"language": "ha", "task": "transcribe"}
203
 
204
  t0 = time.time()
205
+ out, word_level = _asr_run(audio, gen)
 
 
 
 
 
 
 
 
 
206
 
207
  words = _words_from_chunks(out.get("chunks"), audio_secs)
208
  print(f"asr {audio_secs:.1f}s audio -> {len(words)} words in {time.time() - t0:.1f}s "
 
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)
 
233
  if not texts:
234
  return {"translations": []}
235
 
 
236
  # Blank inputs are held out and reinserted: MarianMT will happily emit a
237
  # hallucinated sentence for an empty string.
238
  idx = [i for i, t in enumerate(texts) if (t or "").strip()]
239
  out = [""] * len(texts)
240
  if idx:
241
+ res = _mt_run([texts[i] for i in idx])
242
  for i, r in zip(idx, res):
243
  out[i] = (r.get("translation_text") or "").strip()
244
  return {"translations": out}