True frame-level streaming via engine.infer_stream (@spaces.GPU generator)
Browse files
app.py
CHANGED
|
@@ -19,7 +19,6 @@ import spaces
|
|
| 19 |
|
| 20 |
from vieneu import Vieneu
|
| 21 |
from vieneu_utils.core_utils import split_text_into_chunks
|
| 22 |
-
from vieneu_utils.phonemize_text import phonemize_text_with_emotions
|
| 23 |
|
| 24 |
# ── Load model once, on GPU (CUDA emulation makes this valid at startup) ───────
|
| 25 |
print("⏳ Loading VieNeu-TTS v3 Turbo (PyTorch / CUDA) ...")
|
|
@@ -178,54 +177,42 @@ def synthesize_conversation(script, silence_s, temperature, max_new_frames):
|
|
| 178 |
return (SR, full), info
|
| 179 |
|
| 180 |
|
| 181 |
-
# ──
|
| 182 |
-
#
|
| 183 |
-
#
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
@spaces.GPU(duration=_gpu_duration)
|
| 191 |
-
def _infer_chunk_gpu(phonemes, ref_codes, voice_token_id, temperature, top_k,
|
| 192 |
-
top_p, repetition_penalty, max_new_frames):
|
| 193 |
-
wav = tts.engine.infer(
|
| 194 |
-
text="", phonemes=phonemes, ref_codes=ref_codes,
|
| 195 |
-
emotion="natural", voice_token_id=voice_token_id,
|
| 196 |
-
temperature=float(temperature), top_k=int(top_k), top_p=float(top_p),
|
| 197 |
-
repetition_penalty=float(repetition_penalty), max_new_frames=int(max_new_frames),
|
| 198 |
-
)
|
| 199 |
-
return np.asarray(wav, dtype=np.float32)
|
| 200 |
-
|
| 201 |
-
|
| 202 |
def stream_synthesize(text, voice, ref_audio, temperature, top_k, top_p,
|
| 203 |
repetition_penalty, max_new_frames, max_chars):
|
| 204 |
text = (text or "").strip()
|
| 205 |
if not text:
|
| 206 |
raise gr.Error("Vui lòng nhập văn bản cần đọc.")
|
| 207 |
|
| 208 |
-
#
|
| 209 |
-
# cloning → encode the reference on the GPU a single time up front.
|
| 210 |
if ref_audio:
|
| 211 |
-
ref_codes, voice_token_id =
|
| 212 |
else:
|
| 213 |
ref_codes, voice_token_id = tts._resolve_v3_ref(voice, None, None)
|
| 214 |
|
| 215 |
-
# Smaller chunks = lower latency to first audio.
|
| 216 |
-
chunks = split_text_into_chunks(text, max_chars=min(int(max_chars), 140))
|
| 217 |
-
|
| 218 |
first = True
|
| 219 |
-
for chunk in
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
|
| 230 |
|
| 231 |
# ── UI ────────────────────────────────────────────────────────────────────────
|
|
@@ -294,7 +281,7 @@ with gr.Blocks(theme=theme, title="VieNeu-TTS v3 Turbo") as demo:
|
|
| 294 |
temperature_in = gr.Slider(0.1, 1.5, value=0.8, step=0.05, label="temperature")
|
| 295 |
top_p_in = gr.Slider(0.1, 1.0, value=0.95, step=0.01, label="top_p")
|
| 296 |
with gr.Row():
|
| 297 |
-
top_k_in = gr.Slider(1, 100, value=
|
| 298 |
rep_pen_in = gr.Slider(1.0, 2.0, value=1.2, step=0.05, label="repetition_penalty")
|
| 299 |
with gr.Row():
|
| 300 |
max_frames_in = gr.Slider(50, 1200, value=300, step=10, label="max_new_frames (mỗi đoạn)")
|
|
@@ -313,8 +300,9 @@ with gr.Blocks(theme=theme, title="VieNeu-TTS v3 Turbo") as demo:
|
|
| 313 |
stream_btn = gr.Button("⚡ Phát trực tiếp", variant="primary")
|
| 314 |
stop_btn = gr.Button("⏹ Dừng", variant="stop")
|
| 315 |
gr.Markdown(
|
| 316 |
-
"_Phát
|
| 317 |
-
"đầu tiên đôi khi Gradio nuốt mất
|
|
|
|
| 318 |
)
|
| 319 |
|
| 320 |
run_btn.click(
|
|
|
|
| 19 |
|
| 20 |
from vieneu import Vieneu
|
| 21 |
from vieneu_utils.core_utils import split_text_into_chunks
|
|
|
|
| 22 |
|
| 23 |
# ── Load model once, on GPU (CUDA emulation makes this valid at startup) ───────
|
| 24 |
print("⏳ Loading VieNeu-TTS v3 Turbo (PyTorch / CUDA) ...")
|
|
|
|
| 177 |
return (SR, full), info
|
| 178 |
|
| 179 |
|
| 180 |
+
# ── True frame-level streaming ────────────────────────────────────────────────
|
| 181 |
+
# The engine's ``infer_stream`` yields the waveform in small ~chunk_frames pieces
|
| 182 |
+
# (with an adaptive lead-in) as the model generates — that's what makes playback
|
| 183 |
+
# smooth, not chunky. On ZeroGPU the whole thing must hold the GPU, so this is a
|
| 184 |
+
# single ``@spaces.GPU`` *generator*: it keeps the GPU for the session and pushes
|
| 185 |
+
# each small piece out as it is produced. Long text is split by ``max_chars`` and
|
| 186 |
+
# each chunk is streamed in turn (so arbitrarily long input still works).
|
|
|
|
|
|
|
| 187 |
@spaces.GPU(duration=_gpu_duration)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
def stream_synthesize(text, voice, ref_audio, temperature, top_k, top_p,
|
| 189 |
repetition_penalty, max_new_frames, max_chars):
|
| 190 |
text = (text or "").strip()
|
| 191 |
if not text:
|
| 192 |
raise gr.Error("Vui lòng nhập văn bản cần đọc.")
|
| 193 |
|
| 194 |
+
# Preset → reserved token + cached codes; cloning → encode the reference once.
|
|
|
|
| 195 |
if ref_audio:
|
| 196 |
+
ref_codes, voice_token_id = tts.encode_reference(ref_audio), None
|
| 197 |
else:
|
| 198 |
ref_codes, voice_token_id = tts._resolve_v3_ref(voice, None, None)
|
| 199 |
|
|
|
|
|
|
|
|
|
|
| 200 |
first = True
|
| 201 |
+
for chunk in split_text_into_chunks(text, max_chars=int(max_chars)):
|
| 202 |
+
for wav in tts.engine.infer_stream(
|
| 203 |
+
text=chunk, ref_codes=ref_codes, voice_token_id=voice_token_id,
|
| 204 |
+
emotion="natural", temperature=float(temperature), top_k=int(top_k),
|
| 205 |
+
top_p=float(top_p), repetition_penalty=float(repetition_penalty),
|
| 206 |
+
max_new_frames=int(max_new_frames), chunk_frames=25,
|
| 207 |
+
):
|
| 208 |
+
wav = np.asarray(wav, dtype=np.float32)
|
| 209 |
+
if wav.size == 0:
|
| 210 |
+
continue
|
| 211 |
+
yield (SR, wav)
|
| 212 |
+
if first:
|
| 213 |
+
# Flush the first piece (Gradio streaming sometimes drops it).
|
| 214 |
+
first = False
|
| 215 |
+
yield (SR, np.zeros(1, dtype=np.float32))
|
| 216 |
|
| 217 |
|
| 218 |
# ── UI ────────────────────────────────────────────────────────────────────────
|
|
|
|
| 281 |
temperature_in = gr.Slider(0.1, 1.5, value=0.8, step=0.05, label="temperature")
|
| 282 |
top_p_in = gr.Slider(0.1, 1.0, value=0.95, step=0.01, label="top_p")
|
| 283 |
with gr.Row():
|
| 284 |
+
top_k_in = gr.Slider(1, 100, value=25, step=1, label="top_k")
|
| 285 |
rep_pen_in = gr.Slider(1.0, 2.0, value=1.2, step=0.05, label="repetition_penalty")
|
| 286 |
with gr.Row():
|
| 287 |
max_frames_in = gr.Slider(50, 1200, value=300, step=10, label="max_new_frames (mỗi đoạn)")
|
|
|
|
| 300 |
stream_btn = gr.Button("⚡ Phát trực tiếp", variant="primary")
|
| 301 |
stop_btn = gr.Button("⏹ Dừng", variant="stop")
|
| 302 |
gr.Markdown(
|
| 303 |
+
"_Phát theo thời gian thực, mượt từng đoạn ngắn ngay khi model sinh. "
|
| 304 |
+
"Lần bấm **Phát trực tiếp** đầu tiên đôi khi Gradio nuốt mất đoạn đầu "
|
| 305 |
+
"— bấm lại là được._"
|
| 306 |
)
|
| 307 |
|
| 308 |
run_btn.click(
|