Vansh Chugh commited on
Commit
bb25b43
·
1 Parent(s): 7b11b09

Drop gpu_lock: Gradio's default concurrency_limit=1 already serializes process_fn

Browse files
Files changed (1) hide show
  1. app.py +104 -115
app.py CHANGED
@@ -62,20 +62,11 @@ VARIANTS = {
62
  },
63
  }
64
 
65
- # Only one variant's full stack (text encoder + DiT + VAE) is kept on the GPU at a
66
- # time: the two variants need different Qwen2.5-Omni-7B layer-sampling depths, so
67
- # the encoder can't be shared as-is (QwenOmniThinkerExtractor bakes dit_depth into
68
- # which LLM layers it extracts at construction time). Switching variants evicts the
69
- # previous one and rebuilds from the local HF cache (no re-download).
70
  _active_name = None
71
  _active_entry = None # built on CPU at first; moved to GPU on first real use
72
  _loading = True
73
  _load_error = None
74
-
75
- # Only one request runs at a time. The scheduler object is shared and stateful,
76
- # so two requests running together would corrupt each other's results. This lock
77
- # also covers _active_name/_active_entry below, so no separate lock is needed.
78
- _gpu_lock = threading.Lock()
79
  _active_ready = False # has _active_entry been moved onto the GPU yet?
80
 
81
 
@@ -162,9 +153,8 @@ def get_variant(name: str):
162
  """Return the active variant's (model, extractor, vae, ...) tuple, ready to
163
  use on the GPU — building and/or activating it first if needed.
164
 
165
- Caller must hold _gpu_lock and be inside an @spaces.GPU call — that's both
166
- what makes the unlocked _active_* globals safe, and what makes it safe to
167
- touch CUDA in _activate_variant()."""
168
  global _active_name, _active_entry, _active_ready
169
  if name != _active_name:
170
  print(f"Switching variant: {_active_name!r} -> {name!r}")
@@ -214,108 +204,107 @@ def process_fn(
214
  with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
215
  out_path = f.name
216
 
217
- with _gpu_lock:
218
- model, omni_extractor, audio_vae, vae_scale_factor, vae_sample_rate, gen_target_frames, scheduler = get_variant(model_variant)
219
-
220
- if mode == "Generate":
221
- # Text-to-audio/speech, no input audio involved. sound_type picks which of
222
- # the model's trained prompt templates to build (see README task table) —
223
- # voice/background only apply to the Speech templates, unused otherwise.
224
- if sound_type == "Sound Effect":
225
- tagged_prompt = f"[Audio] {prompt}"
226
- elif sound_type == "Speech":
227
- tagged_prompt = f'[Speech] A {voice.lower()} voice saying "{prompt}"'
228
- else: # "Speech + Background"
229
- tagged_prompt = f'[Speech] A {voice.lower()} voice saying "{prompt}" [Audio] {background}'
230
-
231
- latents = sample_latents(
232
- model, scheduler, omni_extractor, [tagged_prompt],
233
- num_inference_steps=steps, guidance_scale=guidance, device=DEVICE,
234
- target_frames=gen_target_frames,
235
- )
236
- latents = latents * (1.0 / vae_scale_factor)
237
- decode_and_save(audio_vae, latents, [duration], [out_path], sample_rate=vae_sample_rate)
238
-
239
- elif mode == "Edit":
240
- # Source audio + instruction. Mask is all-ones: the whole clip is editable,
241
- # source_latents just gives the model something to condition on.
242
- # sound_type again picks [Audio] vs [Speech] as the edit's sub-tag; "Speech
243
- # + Background" isn't a real edit template, so it also falls back to [Audio].
244
- if not input_audio_path:
245
- raise gr.Error("Edit mode requires an input audio track.")
246
- edit_target = "Speech" if sound_type == "Speech" else "Audio"
247
- tgt_wav_len = int(MAX_AUDIO_DURATION * vae_sample_rate)
248
- src_wav = load_source_audio(input_audio_path, target_sr=vae_sample_rate,
249
- target_length=tgt_wav_len, device=DEVICE)
250
- src_latent = audio_vae.wrapped_encode(src_wav) * vae_scale_factor
251
- mask_wav = make_edit_mask(src_wav.shape[-1], DEVICE)
252
- mask_lat = downsample_mask(mask_wav, src_latent.shape[-1])
253
-
254
- latents = sample_latents(
255
- model, scheduler, omni_extractor, [f"[Edit] [{edit_target}] {prompt}"],
256
- source_latents=src_latent, masks=mask_lat,
257
- num_inference_steps=steps, guidance_scale=guidance, device=DEVICE,
258
- )
259
- latents = latents * (1.0 / vae_scale_factor)
260
- edit_duration = src_wav.shape[-1] / vae_sample_rate
261
- decode_and_save(audio_vae, latents, [edit_duration], [out_path], sample_rate=vae_sample_rate)
262
-
263
- elif mode == "Clone Voice":
264
- # Reference clip + text to speak in that voice.
265
- if not input_audio_path:
266
- raise gr.Error("Clone Voice mode requires a reference audio track.")
267
-
268
- # 3.0s matches REF_DURATION, the reference-clip length the model was trained with.
269
- ref_wav = load_ref_audio(input_audio_path, target_sr=vae_sample_rate,
270
- max_ref_duration=3.0, device=DEVICE)
271
- ref_audio_duration = ref_wav.shape[-1] / vae_sample_rate
272
- if ref_audio_duration + 1.0 > duration: # 1.0s floor so some cloned speech always fits
273
- raise gr.Error(f"Reference clip ({ref_audio_duration:.1f}s) leaves under 1s for "
274
- f"the cloned speech at Duration={duration:.1f}s. Increase Duration.")
275
-
276
- # Get the ref transcript (typed one wins over auto-transcription), then
277
- # combine it with what the user wants said next into one prompt.
278
- # Exclude load_ref_audio's trailing silence pad (tail_pad_s=0.1) so Whisper
279
- # doesn't hallucinate tokens over silence.
280
- speech_samples = max(ref_wav.shape[-1] - int(0.1 * vae_sample_rate), 1)
281
- resolved_ref_text = ref_text.strip() or transcribe_ref_audio(
282
- ref_wav[..., :speech_samples], sr=vae_sample_rate,
283
- )
284
- combined_text = join_ref_target_text(resolved_ref_text, prompt)
285
- full_prompt = f"[Speech with voice] {combined_text}"
286
-
287
- # Build one waveform [ref audio | silence] and encode it as a single clip —
288
- # the model generates the target portion conditioned on the ref portion.
289
- total_wav_len = int(duration * vae_sample_rate)
290
- ref_wav_1d = ref_wav.squeeze(0) if ref_wav.dim() == 2 else ref_wav
291
- ref_wav_len = min(ref_wav_1d.shape[-1], total_wav_len)
292
- source_wav = torch.zeros(1, total_wav_len, device=DEVICE)
293
- source_wav[:, :ref_wav_len] = ref_wav_1d[:ref_wav_len]
294
- source_latent = audio_vae.wrapped_encode(source_wav) * vae_scale_factor
295
-
296
- mask_wav = torch.zeros(1, 1, total_wav_len, device=DEVICE)
297
- mask_wav[:, :, :ref_wav_len] = 2.0 # 2 = reference region, 0 = target (see sample_latents docstring)
298
- mask_latent = torch.nn.functional.interpolate(
299
- mask_wav, size=source_latent.shape[-1]
300
- ).to(torch.bfloat16)
301
-
302
- latents = sample_latents(
303
- model, scheduler, omni_extractor, [full_prompt],
304
- source_latents=source_latent, masks=mask_latent,
305
- num_inference_steps=steps, guidance_scale=guidance, device=DEVICE,
306
- )
307
- latents_full = latents * (1.0 / vae_scale_factor)
308
-
309
- # Decode the full ref+target latent, then crop out just the target
310
- # the decoder needs the ref portion as context to decode cleanly.
311
- ref_samples = int(ref_audio_duration * vae_sample_rate)
312
- decode_and_save_full(
313
- audio_vae, latents_full, ref_samples,
314
- [duration - ref_audio_duration], [out_path], sample_rate=vae_sample_rate,
315
- )
316
-
317
- else:
318
- raise gr.Error(f"Unknown mode: {mode}")
319
 
320
  return out_path
321
 
 
62
  },
63
  }
64
 
65
+ # Only one variant is kept on the GPU at a time; switching evicts the other.
 
 
 
 
66
  _active_name = None
67
  _active_entry = None # built on CPU at first; moved to GPU on first real use
68
  _loading = True
69
  _load_error = None
 
 
 
 
 
70
  _active_ready = False # has _active_entry been moved onto the GPU yet?
71
 
72
 
 
153
  """Return the active variant's (model, extractor, vae, ...) tuple, ready to
154
  use on the GPU — building and/or activating it first if needed.
155
 
156
+ Must be called from inside process_fn (i.e. inside @spaces.GPU) — that's
157
+ what makes it safe to touch CUDA in _activate_variant()."""
 
158
  global _active_name, _active_entry, _active_ready
159
  if name != _active_name:
160
  print(f"Switching variant: {_active_name!r} -> {name!r}")
 
204
  with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
205
  out_path = f.name
206
 
207
+ model, omni_extractor, audio_vae, vae_scale_factor, vae_sample_rate, gen_target_frames, scheduler = get_variant(model_variant)
208
+
209
+ if mode == "Generate":
210
+ # Text-to-audio/speech, no input audio involved. sound_type picks which of
211
+ # the model's trained prompt templates to build (see README task table) —
212
+ # voice/background only apply to the Speech templates, unused otherwise.
213
+ if sound_type == "Sound Effect":
214
+ tagged_prompt = f"[Audio] {prompt}"
215
+ elif sound_type == "Speech":
216
+ tagged_prompt = f'[Speech] A {voice.lower()} voice saying "{prompt}"'
217
+ else: # "Speech + Background"
218
+ tagged_prompt = f'[Speech] A {voice.lower()} voice saying "{prompt}" [Audio] {background}'
219
+
220
+ latents = sample_latents(
221
+ model, scheduler, omni_extractor, [tagged_prompt],
222
+ num_inference_steps=steps, guidance_scale=guidance, device=DEVICE,
223
+ target_frames=gen_target_frames,
224
+ )
225
+ latents = latents * (1.0 / vae_scale_factor)
226
+ decode_and_save(audio_vae, latents, [duration], [out_path], sample_rate=vae_sample_rate)
227
+
228
+ elif mode == "Edit":
229
+ # Source audio + instruction. Mask is all-ones: the whole clip is editable,
230
+ # source_latents just gives the model something to condition on.
231
+ # sound_type again picks [Audio] vs [Speech] as the edit's sub-tag; "Speech
232
+ # + Background" isn't a real edit template, so it also falls back to [Audio].
233
+ if not input_audio_path:
234
+ raise gr.Error("Edit mode requires an input audio track.")
235
+ edit_target = "Speech" if sound_type == "Speech" else "Audio"
236
+ tgt_wav_len = int(MAX_AUDIO_DURATION * vae_sample_rate)
237
+ src_wav = load_source_audio(input_audio_path, target_sr=vae_sample_rate,
238
+ target_length=tgt_wav_len, device=DEVICE)
239
+ src_latent = audio_vae.wrapped_encode(src_wav) * vae_scale_factor
240
+ mask_wav = make_edit_mask(src_wav.shape[-1], DEVICE)
241
+ mask_lat = downsample_mask(mask_wav, src_latent.shape[-1])
242
+
243
+ latents = sample_latents(
244
+ model, scheduler, omni_extractor, [f"[Edit] [{edit_target}] {prompt}"],
245
+ source_latents=src_latent, masks=mask_lat,
246
+ num_inference_steps=steps, guidance_scale=guidance, device=DEVICE,
247
+ )
248
+ latents = latents * (1.0 / vae_scale_factor)
249
+ edit_duration = src_wav.shape[-1] / vae_sample_rate
250
+ decode_and_save(audio_vae, latents, [edit_duration], [out_path], sample_rate=vae_sample_rate)
251
+
252
+ elif mode == "Clone Voice":
253
+ # Reference clip + text to speak in that voice.
254
+ if not input_audio_path:
255
+ raise gr.Error("Clone Voice mode requires a reference audio track.")
256
+
257
+ # 3.0s matches REF_DURATION, the reference-clip length the model was trained with.
258
+ ref_wav = load_ref_audio(input_audio_path, target_sr=vae_sample_rate,
259
+ max_ref_duration=3.0, device=DEVICE)
260
+ ref_audio_duration = ref_wav.shape[-1] / vae_sample_rate
261
+ if ref_audio_duration + 1.0 > duration: # 1.0s floor so some cloned speech always fits
262
+ raise gr.Error(f"Reference clip ({ref_audio_duration:.1f}s) leaves under 1s for "
263
+ f"the cloned speech at Duration={duration:.1f}s. Increase Duration.")
264
+
265
+ # Get the ref transcript (typed one wins over auto-transcription), then
266
+ # combine it with what the user wants said next into one prompt.
267
+ # Exclude load_ref_audio's trailing silence pad (tail_pad_s=0.1) so Whisper
268
+ # doesn't hallucinate tokens over silence.
269
+ speech_samples = max(ref_wav.shape[-1] - int(0.1 * vae_sample_rate), 1)
270
+ resolved_ref_text = ref_text.strip() or transcribe_ref_audio(
271
+ ref_wav[..., :speech_samples], sr=vae_sample_rate,
272
+ )
273
+ combined_text = join_ref_target_text(resolved_ref_text, prompt)
274
+ full_prompt = f"[Speech with voice] {combined_text}"
275
+
276
+ # Build one waveform [ref audio | silence] and encode it as a single clip —
277
+ # the model generates the target portion conditioned on the ref portion.
278
+ total_wav_len = int(duration * vae_sample_rate)
279
+ ref_wav_1d = ref_wav.squeeze(0) if ref_wav.dim() == 2 else ref_wav
280
+ ref_wav_len = min(ref_wav_1d.shape[-1], total_wav_len)
281
+ source_wav = torch.zeros(1, total_wav_len, device=DEVICE)
282
+ source_wav[:, :ref_wav_len] = ref_wav_1d[:ref_wav_len]
283
+ source_latent = audio_vae.wrapped_encode(source_wav) * vae_scale_factor
284
+
285
+ mask_wav = torch.zeros(1, 1, total_wav_len, device=DEVICE)
286
+ mask_wav[:, :, :ref_wav_len] = 2.0 # 2 = reference region, 0 = target (see sample_latents docstring)
287
+ mask_latent = torch.nn.functional.interpolate(
288
+ mask_wav, size=source_latent.shape[-1]
289
+ ).to(torch.bfloat16)
290
+
291
+ latents = sample_latents(
292
+ model, scheduler, omni_extractor, [full_prompt],
293
+ source_latents=source_latent, masks=mask_latent,
294
+ num_inference_steps=steps, guidance_scale=guidance, device=DEVICE,
295
+ )
296
+ latents_full = latents * (1.0 / vae_scale_factor)
297
+
298
+ # Decode the full ref+target latent, then crop out just the target —
299
+ # the decoder needs the ref portion as context to decode cleanly.
300
+ ref_samples = int(ref_audio_duration * vae_sample_rate)
301
+ decode_and_save_full(
302
+ audio_vae, latents_full, ref_samples,
303
+ [duration - ref_audio_duration], [out_path], sample_rate=vae_sample_rate,
304
+ )
305
+
306
+ else:
307
+ raise gr.Error(f"Unknown mode: {mode}")
 
308
 
309
  return out_path
310