small-talk-tts / app.py
GauravGosain's picture
Qwen3-TTS VoiceDesign on ZeroGPU (base qwen-tts, no CUDA graphs)
5817981 verified
Raw
History Blame Contribute Delete
3.31 kB
"""Small Talk · Voice Design — Qwen3-TTS VoiceDesign on ZeroGPU.
Designs a speaking voice from a natural-language description (gender, age, pitch,
timbre, attitude, pace, accent…). This is the same model the podcast uses locally
(`Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign`), run here via the base `qwen_tts` —
NOT `faster_qwen3_tts`, because that package's CUDA-graph speedup can't survive
ZeroGPU's per-request GPU reclaim. (faster_qwen3_tts → dedicated GPU / Modal.)
Qwen3-TTS has NO emotion/markup tags — expressiveness comes entirely from the
`instruct` description, so write rich, structured voice descriptions.
`import spaces` MUST come before torch so ZeroGPU can patch CUDA.
"""
import spaces
import gradio as gr
import numpy as np
import torch
from qwen_tts import Qwen3TTSModel
MODEL_ID = "Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign"
tts = Qwen3TTSModel.from_pretrained(
MODEL_ID,
device_map="cuda",
dtype=torch.bfloat16,
attn_implementation="sdpa", # no flash-attn needed
)
EXAMPLE_INSTRUCT = (
"A dry, witty man in his fifties with a deep, smooth, slightly weathered "
"baritone. Sardonic, understated and unflappable, with a sarcastic edge. "
"Speaks slowly and deliberately, with deadpan timing and the faint amusement "
"of someone who has seen it all."
)
EXAMPLE_TEXT = (
"Charming? It's a robot reading sine waves off a tensor. But sure — let's "
"anthropomorphize the linear algebra."
)
LANGUAGES = ["English", "Chinese", "Spanish", "French", "German", "Italian",
"Japanese", "Korean", "Portuguese", "Russian", "Auto"]
@spaces.GPU(duration=120)
def design(text, instruct, language, temperature, top_p, repetition_penalty):
text = (text or "").strip()
if not text:
raise gr.Error("Enter some text to speak.")
with torch.inference_mode():
wavs, sr = tts.generate_voice_design(
text=text,
instruct=(instruct or "").strip(),
language=language or "English",
do_sample=True,
temperature=float(temperature),
top_p=float(top_p),
repetition_penalty=float(repetition_penalty),
)
audio = np.asarray(wavs[0], dtype=np.float32).reshape(-1)
return (int(sr), audio)
demo = gr.Interface(
fn=design,
inputs=[
gr.Textbox(label="Text to speak", value=EXAMPLE_TEXT, lines=3),
gr.Textbox(label="Voice design — natural-language description",
value=EXAMPLE_INSTRUCT, lines=5),
gr.Dropdown(LANGUAGES, value="English", label="Language"),
gr.Slider(0.1, 1.5, value=0.95, step=0.05, label="Temperature"),
gr.Slider(0.1, 1.0, value=0.92, step=0.02, label="Top-p"),
gr.Slider(1.0, 1.5, value=1.1, step=0.05, label="Repetition penalty"),
],
outputs=gr.Audio(label="Designed voice", type="numpy"),
title="Small Talk · Qwen3-TTS Voice Design",
description=(
"Design a speaking voice from a description — the voice engine behind the "
"Small Talk robot podcast. No emotion tags: put all the expressiveness "
"(age, pitch, timbre, attitude, pace, accent) into the description. "
"Callable as an API by the podcast backend."
),
)
if __name__ == "__main__":
demo.queue(max_size=12).launch()