Spaces:
Running on Zero
Running on Zero
Harden unified audio generation
Browse files- README.md +1 -1
- app.py +35 -12
- audio_utils.py +4 -2
- tts_utils.py +12 -13
README.md
CHANGED
|
@@ -27,7 +27,7 @@ The hosted ZeroGPU runtime uses a prebuilt `mamba-ssm==2.3.2.post1` Blackwell wh
|
|
| 27 |
## Run locally
|
| 28 |
|
| 29 |
```bash
|
| 30 |
-
git clone https://huggingface.co/spaces/
|
| 31 |
cd Nemotron-Labs-Audex
|
| 32 |
bash setup_local.sh
|
| 33 |
bash run_local.sh
|
|
|
|
| 27 |
## Run locally
|
| 28 |
|
| 29 |
```bash
|
| 30 |
+
git clone https://huggingface.co/spaces/nvidia/Nemotron-Labs-Audex
|
| 31 |
cd Nemotron-Labs-Audex
|
| 32 |
bash setup_local.sh
|
| 33 |
bash run_local.sh
|
app.py
CHANGED
|
@@ -53,7 +53,7 @@ DEFAULT_WHEELS_DIR = APP_DIR / ("wheels" if IS_ZERO_GPU else ".local/wheels")
|
|
| 53 |
DEFAULT_BUILD_DIR = APP_DIR / ("_wheelout" if IS_ZERO_GPU else ".local/build")
|
| 54 |
WHEELS_DIR = Path(os.environ.get("AUDEX_WHEELS_DIR", str(DEFAULT_WHEELS_DIR)))
|
| 55 |
BUILD_DIR = Path(os.environ.get("AUDEX_BUILD_DIR", str(DEFAULT_BUILD_DIR)))
|
| 56 |
-
REPO_ID = os.environ.get("SPACE_ID", "
|
| 57 |
CAUSAL_CONV1D_VERSION = "1.6.2.post1"
|
| 58 |
MAMBA_SSM_VERSION = "2.3.2.post1"
|
| 59 |
CAUSAL_CONV1D_SPEC = f"causal-conv1d=={CAUSAL_CONV1D_VERSION}"
|
|
@@ -89,6 +89,9 @@ MAX_TOKEN_WARNING = (
|
|
| 89 |
"⚠️ Maximum new-token limit reached; this output is incomplete. "
|
| 90 |
"Increase Max new tokens and run again."
|
| 91 |
)
|
|
|
|
|
|
|
|
|
|
| 92 |
TEXT_VOCAB_SIZE = 131072
|
| 93 |
TEXT_SEED = 100
|
| 94 |
TEXT_SYSTEM_PROMPT = (
|
|
@@ -1098,6 +1101,7 @@ def _generate_tts(
|
|
| 1098 |
chunks: list[np.ndarray] = []
|
| 1099 |
first_token_seconds: float | None = None
|
| 1100 |
total_tokens = 0
|
|
|
|
| 1101 |
request_tag = hashlib.sha256(text.encode()).hexdigest()[:8]
|
| 1102 |
started_at = time.perf_counter()
|
| 1103 |
torch.cuda.reset_peak_memory_stats()
|
|
@@ -1110,6 +1114,7 @@ def _generate_tts(
|
|
| 1110 |
try:
|
| 1111 |
for segment_index, segment in enumerate(segments, start=1):
|
| 1112 |
segment_tokens = 0
|
|
|
|
| 1113 |
for event in stream_tts(
|
| 1114 |
model=runtime.model,
|
| 1115 |
tokenizer=runtime.tokenizer,
|
|
@@ -1122,6 +1127,8 @@ def _generate_tts(
|
|
| 1122 |
guidance_scale=guidance_scale,
|
| 1123 |
):
|
| 1124 |
segment_tokens = event.token_count
|
|
|
|
|
|
|
| 1125 |
if event.token_count and first_token_seconds is None:
|
| 1126 |
first_token_seconds = time.perf_counter() - started_at
|
| 1127 |
if event.pcm is not None:
|
|
@@ -1144,6 +1151,9 @@ def _generate_tts(
|
|
| 1144 |
)
|
| 1145 |
|
| 1146 |
total_tokens += segment_tokens
|
|
|
|
|
|
|
|
|
|
| 1147 |
if segment_index < len(segments):
|
| 1148 |
chunks.append(
|
| 1149 |
np.zeros(
|
|
@@ -1164,11 +1174,17 @@ def _generate_tts(
|
|
| 1164 |
f"[gpu] tts_request={request_tag} tts_tokens={total_tokens} "
|
| 1165 |
f"ttfc_seconds={first_token_seconds:.2f} "
|
| 1166 |
f"tokens_per_second={token_rate:.2f} audio_seconds={waveform.size / SAMPLE_RATE:.2f} "
|
| 1167 |
-
f"elapsed_seconds={elapsed_seconds:.2f} peak_memory_gib={peak_gib:.2f}"
|
|
|
|
| 1168 |
flush=True,
|
| 1169 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1170 |
yield (
|
| 1171 |
-
|
| 1172 |
wav_path,
|
| 1173 |
(
|
| 1174 |
player_value(sequence, token_count=total_tokens, done=True)
|
|
@@ -1191,11 +1207,13 @@ def _split_sentence_segments(text: str) -> list[str]:
|
|
| 1191 |
|
| 1192 |
|
| 1193 |
def _clean_transcription(text: str) -> str:
|
| 1194 |
-
|
| 1195 |
-
|
| 1196 |
-
|
| 1197 |
-
|
| 1198 |
-
|
|
|
|
|
|
|
| 1199 |
|
| 1200 |
|
| 1201 |
def _compose_s2s_input(response_prompt: str, transcript: str) -> str:
|
|
@@ -1267,7 +1285,7 @@ def _generate_s2s(
|
|
| 1267 |
raise gr.Error("Speech-to-speech response generation produced no final answer.")
|
| 1268 |
yield answer, thinking, gr.skip(), gr.skip()
|
| 1269 |
|
| 1270 |
-
for
|
| 1271 |
runtime,
|
| 1272 |
answer,
|
| 1273 |
S2S_TTS_MAX_NEW_TOKENS,
|
|
@@ -1278,7 +1296,12 @@ def _generate_s2s(
|
|
| 1278 |
token_limit=S2S_TTS_MAX_NEW_TOKENS,
|
| 1279 |
segment_sentences=True,
|
| 1280 |
):
|
| 1281 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1282 |
|
| 1283 |
|
| 1284 |
@spaces.GPU(duration=_estimate_text, size="xlarge")
|
|
@@ -1354,7 +1377,7 @@ def run_unified(
|
|
| 1354 |
guidance_scale: float,
|
| 1355 |
) -> Iterator[tuple[object, object, object, object]]:
|
| 1356 |
"""Run a selected Audex model for audio, text, or speech generation."""
|
| 1357 |
-
yield "", "",
|
| 1358 |
runtime = _get_runtime(model_name)
|
| 1359 |
settings = TASK_SPECS[task]
|
| 1360 |
if settings["modality"] == "s2s":
|
|
@@ -1400,7 +1423,7 @@ def run_unified(
|
|
| 1400 |
yield "Please provide an audio input.", "", gr.skip(), gr.skip()
|
| 1401 |
return
|
| 1402 |
|
| 1403 |
-
instruction =
|
| 1404 |
for answer, thinking in _generate(
|
| 1405 |
runtime,
|
| 1406 |
audio,
|
|
|
|
| 53 |
DEFAULT_BUILD_DIR = APP_DIR / ("_wheelout" if IS_ZERO_GPU else ".local/build")
|
| 54 |
WHEELS_DIR = Path(os.environ.get("AUDEX_WHEELS_DIR", str(DEFAULT_WHEELS_DIR)))
|
| 55 |
BUILD_DIR = Path(os.environ.get("AUDEX_BUILD_DIR", str(DEFAULT_BUILD_DIR)))
|
| 56 |
+
REPO_ID = os.environ.get("SPACE_ID", "nvidia/Nemotron-Labs-Audex")
|
| 57 |
CAUSAL_CONV1D_VERSION = "1.6.2.post1"
|
| 58 |
MAMBA_SSM_VERSION = "2.3.2.post1"
|
| 59 |
CAUSAL_CONV1D_SPEC = f"causal-conv1d=={CAUSAL_CONV1D_VERSION}"
|
|
|
|
| 89 |
"⚠️ Maximum new-token limit reached; this output is incomplete. "
|
| 90 |
"Increase Max new tokens and run again."
|
| 91 |
)
|
| 92 |
+
TTS_TOKEN_WARNING = (
|
| 93 |
+
"⚠️ Maximum speech-token limit reached; generated audio may be incomplete."
|
| 94 |
+
)
|
| 95 |
TEXT_VOCAB_SIZE = 131072
|
| 96 |
TEXT_SEED = 100
|
| 97 |
TEXT_SYSTEM_PROMPT = (
|
|
|
|
| 1101 |
chunks: list[np.ndarray] = []
|
| 1102 |
first_token_seconds: float | None = None
|
| 1103 |
total_tokens = 0
|
| 1104 |
+
truncated = False
|
| 1105 |
request_tag = hashlib.sha256(text.encode()).hexdigest()[:8]
|
| 1106 |
started_at = time.perf_counter()
|
| 1107 |
torch.cuda.reset_peak_memory_stats()
|
|
|
|
| 1114 |
try:
|
| 1115 |
for segment_index, segment in enumerate(segments, start=1):
|
| 1116 |
segment_tokens = 0
|
| 1117 |
+
segment_truncated = False
|
| 1118 |
for event in stream_tts(
|
| 1119 |
model=runtime.model,
|
| 1120 |
tokenizer=runtime.tokenizer,
|
|
|
|
| 1127 |
guidance_scale=guidance_scale,
|
| 1128 |
):
|
| 1129 |
segment_tokens = event.token_count
|
| 1130 |
+
if event.done:
|
| 1131 |
+
segment_truncated = event.truncated
|
| 1132 |
if event.token_count and first_token_seconds is None:
|
| 1133 |
first_token_seconds = time.perf_counter() - started_at
|
| 1134 |
if event.pcm is not None:
|
|
|
|
| 1151 |
)
|
| 1152 |
|
| 1153 |
total_tokens += segment_tokens
|
| 1154 |
+
if segment_truncated:
|
| 1155 |
+
truncated = True
|
| 1156 |
+
break
|
| 1157 |
if segment_index < len(segments):
|
| 1158 |
chunks.append(
|
| 1159 |
np.zeros(
|
|
|
|
| 1174 |
f"[gpu] tts_request={request_tag} tts_tokens={total_tokens} "
|
| 1175 |
f"ttfc_seconds={first_token_seconds:.2f} "
|
| 1176 |
f"tokens_per_second={token_rate:.2f} audio_seconds={waveform.size / SAMPLE_RATE:.2f} "
|
| 1177 |
+
f"elapsed_seconds={elapsed_seconds:.2f} peak_memory_gib={peak_gib:.2f} "
|
| 1178 |
+
f"truncated={truncated}",
|
| 1179 |
flush=True,
|
| 1180 |
)
|
| 1181 |
+
status = (
|
| 1182 |
+
f"{TTS_TOKEN_WARNING} · {waveform.size / SAMPLE_RATE:.1f}s audio"
|
| 1183 |
+
if truncated
|
| 1184 |
+
else f"Complete · {waveform.size / SAMPLE_RATE:.1f}s audio"
|
| 1185 |
+
)
|
| 1186 |
yield (
|
| 1187 |
+
status,
|
| 1188 |
wav_path,
|
| 1189 |
(
|
| 1190 |
player_value(sequence, token_count=total_tokens, done=True)
|
|
|
|
| 1207 |
|
| 1208 |
|
| 1209 |
def _clean_transcription(text: str) -> str:
|
| 1210 |
+
text = text.strip()
|
| 1211 |
+
quoted = re.fullmatch(
|
| 1212 |
+
r"(?is)(?:(?:the\s+)?(?:transcription|transcript)"
|
| 1213 |
+
r"(?:\s+(?:is|reads))?\s*:?\s*)?(['\"])(.*)\1[.!]?",
|
| 1214 |
+
text,
|
| 1215 |
+
)
|
| 1216 |
+
return quoted.group(2).strip() if quoted else text
|
| 1217 |
|
| 1218 |
|
| 1219 |
def _compose_s2s_input(response_prompt: str, transcript: str) -> str:
|
|
|
|
| 1285 |
raise gr.Error("Speech-to-speech response generation produced no final answer.")
|
| 1286 |
yield answer, thinking, gr.skip(), gr.skip()
|
| 1287 |
|
| 1288 |
+
for status, wav_path, player in _generate_tts(
|
| 1289 |
runtime,
|
| 1290 |
answer,
|
| 1291 |
S2S_TTS_MAX_NEW_TOKENS,
|
|
|
|
| 1296 |
token_limit=S2S_TTS_MAX_NEW_TOKENS,
|
| 1297 |
segment_sentences=True,
|
| 1298 |
):
|
| 1299 |
+
visible_answer = (
|
| 1300 |
+
f"{answer}\n\n{TTS_TOKEN_WARNING}"
|
| 1301 |
+
if TTS_TOKEN_WARNING in status
|
| 1302 |
+
else answer
|
| 1303 |
+
)
|
| 1304 |
+
yield visible_answer, thinking, wav_path or gr.skip(), player
|
| 1305 |
|
| 1306 |
|
| 1307 |
@spaces.GPU(duration=_estimate_text, size="xlarge")
|
|
|
|
| 1377 |
guidance_scale: float,
|
| 1378 |
) -> Iterator[tuple[object, object, object, object]]:
|
| 1379 |
"""Run a selected Audex model for audio, text, or speech generation."""
|
| 1380 |
+
yield "", "", None, gr.skip()
|
| 1381 |
runtime = _get_runtime(model_name)
|
| 1382 |
settings = TASK_SPECS[task]
|
| 1383 |
if settings["modality"] == "s2s":
|
|
|
|
| 1423 |
yield "Please provide an audio input.", "", gr.skip(), gr.skip()
|
| 1424 |
return
|
| 1425 |
|
| 1426 |
+
instruction = prompt.strip() or str(settings["template"])
|
| 1427 |
for answer, thinking in _generate(
|
| 1428 |
runtime,
|
| 1429 |
audio,
|
audio_utils.py
CHANGED
|
@@ -136,10 +136,12 @@ def build_prompt_template(
|
|
| 136 |
raise ValueError(f"Unknown prompt repetition mode: {prompt_repitition}")
|
| 137 |
if prompt_repitition == "repetition":
|
| 138 |
prompt = f"{prompt}\n{prompt}"
|
|
|
|
|
|
|
| 139 |
|
| 140 |
if reasoning:
|
| 141 |
-
return f"<|im_start|>user\n
|
| 142 |
-
return f"<|im_start|>user\n
|
| 143 |
|
| 144 |
|
| 145 |
def expand_sound_placeholder(prompt: str, num_embeddings: int) -> str:
|
|
|
|
| 136 |
raise ValueError(f"Unknown prompt repetition mode: {prompt_repitition}")
|
| 137 |
if prompt_repitition == "repetition":
|
| 138 |
prompt = f"{prompt}\n{prompt}"
|
| 139 |
+
if SOUND_PLACEHOLDER not in prompt:
|
| 140 |
+
prompt = f"{SOUND_PLACEHOLDER}\n{prompt}"
|
| 141 |
|
| 142 |
if reasoning:
|
| 143 |
+
return f"<|im_start|>user\n{prompt}<|im_end|>\n<|im_start|>assistant\n<think>\n"
|
| 144 |
+
return f"<|im_start|>user\n{prompt}<|im_end|>\n<|im_start|>assistant\n<think></think>"
|
| 145 |
|
| 146 |
|
| 147 |
def expand_sound_placeholder(prompt: str, num_embeddings: int) -> str:
|
tts_utils.py
CHANGED
|
@@ -41,6 +41,7 @@ class TTSStreamEvent:
|
|
| 41 |
token_count: int
|
| 42 |
elapsed_seconds: float
|
| 43 |
done: bool = False
|
|
|
|
| 44 |
|
| 45 |
|
| 46 |
class SpeechTokenLogitsProcessor(LogitsProcessor):
|
|
@@ -256,11 +257,11 @@ def stream_tts(
|
|
| 256 |
thread = Thread(target=generate, daemon=True)
|
| 257 |
thread.start()
|
| 258 |
token_count = 0
|
| 259 |
-
|
| 260 |
try:
|
| 261 |
for token_id in streamer:
|
| 262 |
if token_id == token_map.end or token_id == token_map.eos:
|
| 263 |
-
|
| 264 |
break
|
| 265 |
if not token_map.codec_start <= token_id <= token_map.codec_end:
|
| 266 |
raise RuntimeError(f"Unexpected token in TTS output: {token_id}")
|
|
@@ -279,24 +280,22 @@ def stream_tts(
|
|
| 279 |
token_count=token_count,
|
| 280 |
elapsed_seconds=time.perf_counter() - started_at,
|
| 281 |
)
|
| 282 |
-
else:
|
| 283 |
-
completed = True
|
| 284 |
|
| 285 |
if generation_error:
|
| 286 |
raise generation_error[0]
|
| 287 |
-
|
| 288 |
-
for _, pcm in session.flush():
|
| 289 |
-
yield TTSStreamEvent(
|
| 290 |
-
pcm=np.asarray(pcm, dtype=np.float32),
|
| 291 |
-
token_count=token_count,
|
| 292 |
-
elapsed_seconds=time.perf_counter() - started_at,
|
| 293 |
-
)
|
| 294 |
yield TTSStreamEvent(
|
| 295 |
-
pcm=
|
| 296 |
token_count=token_count,
|
| 297 |
elapsed_seconds=time.perf_counter() - started_at,
|
| 298 |
-
done=True,
|
| 299 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
finally:
|
| 301 |
cancel_event.set()
|
| 302 |
thread.join(timeout=10)
|
|
|
|
| 41 |
token_count: int
|
| 42 |
elapsed_seconds: float
|
| 43 |
done: bool = False
|
| 44 |
+
truncated: bool = False
|
| 45 |
|
| 46 |
|
| 47 |
class SpeechTokenLogitsProcessor(LogitsProcessor):
|
|
|
|
| 257 |
thread = Thread(target=generate, daemon=True)
|
| 258 |
thread.start()
|
| 259 |
token_count = 0
|
| 260 |
+
terminal_token_seen = False
|
| 261 |
try:
|
| 262 |
for token_id in streamer:
|
| 263 |
if token_id == token_map.end or token_id == token_map.eos:
|
| 264 |
+
terminal_token_seen = True
|
| 265 |
break
|
| 266 |
if not token_map.codec_start <= token_id <= token_map.codec_end:
|
| 267 |
raise RuntimeError(f"Unexpected token in TTS output: {token_id}")
|
|
|
|
| 280 |
token_count=token_count,
|
| 281 |
elapsed_seconds=time.perf_counter() - started_at,
|
| 282 |
)
|
|
|
|
|
|
|
| 283 |
|
| 284 |
if generation_error:
|
| 285 |
raise generation_error[0]
|
| 286 |
+
for _, pcm in session.flush():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
yield TTSStreamEvent(
|
| 288 |
+
pcm=np.asarray(pcm, dtype=np.float32),
|
| 289 |
token_count=token_count,
|
| 290 |
elapsed_seconds=time.perf_counter() - started_at,
|
|
|
|
| 291 |
)
|
| 292 |
+
yield TTSStreamEvent(
|
| 293 |
+
pcm=None,
|
| 294 |
+
token_count=token_count,
|
| 295 |
+
elapsed_seconds=time.perf_counter() - started_at,
|
| 296 |
+
done=True,
|
| 297 |
+
truncated=not terminal_token_seen,
|
| 298 |
+
)
|
| 299 |
finally:
|
| 300 |
cancel_event.set()
|
| 301 |
thread.join(timeout=10)
|