telecomadm1145 commited on
Commit
0ade193
·
verified ·
1 Parent(s): 33dd11e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +181 -20
app.py CHANGED
@@ -1,12 +1,11 @@
1
  # -*- coding: utf-8 -*-
2
-
3
  import numpy as np
4
  import torch
5
  import gradio as gr
6
- from huggingface_hub import hf_hub_download
7
- from transformers import PreTrainedTokenizerFast
8
- from qwen_tts import Qwen3TTSTokenizer
9
  from transformers import AutoModelForSeq2SeqLM, PreTrainedTokenizerFast
 
 
10
  REPO = "telecomadm1145/Kiseki-TTS-1.2"
11
  CODEC_REPO = "Qwen/Qwen3-TTS-Tokenizer-12Hz"
12
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
@@ -14,56 +13,218 @@ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
14
  print(f"Loading models onto device={DEVICE} ...")
15
  tok = PreTrainedTokenizerFast.from_pretrained(REPO, trust_remote_code=True)
16
  codec_tokenizer = Qwen3TTSTokenizer.from_pretrained(CODEC_REPO, device_map=DEVICE)
17
-
18
  model = AutoModelForSeq2SeqLM.from_pretrained(REPO, trust_remote_code=True).eval().to(DEVICE)
 
 
 
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
- def tts_generate(text, max_new_frames, temperature, top_k, repetition_penalty):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  if not text or not text.strip():
23
  raise gr.Error("Please enter some text to synthesize.")
24
 
 
25
  with torch.no_grad():
26
- ids = model.build_tts_input_ids(tok.encode(text), device=DEVICE)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  out = model.generate_audio(
28
  ids,
 
29
  max_new_frames=int(max_new_frames),
30
- temperature=float(temperature),
31
- top_k=int(top_k),
32
- repetition_penalty=float(repetition_penalty),
 
 
 
 
 
33
  )
 
34
  codes = out["audio_codes"][0][out["valid_mask"][0]]
 
 
35
  wavs, sr = codec_tokenizer.decode({"audio_codes": codes})
36
 
37
  wav = wavs[0]
38
  if isinstance(wav, torch.Tensor):
39
  wav = wav.detach().cpu().numpy()
40
- wav = np.asarray(wav, dtype=np.float32)
41
 
42
- return (sr, wav), "Generated."
 
 
43
 
44
 
 
45
  with gr.Blocks(title="Kiseki-TTS") as demo:
46
  gr.Markdown(
47
  "# Kiseki-TTS\n"
48
- f"Model: [`{REPO}`](https://huggingface.co/{REPO}) · Codec: `{CODEC_REPO}`"
49
  )
50
-
51
  with gr.Row():
52
  with gr.Column():
53
  text_in = gr.Textbox(label="Text", placeholder="うん、今日はいい天気です", lines=3)
54
- with gr.Accordion("Generation settings", open=False):
55
- max_frames = gr.Slider(50, 1000, value=250, step=10, label="max_new_frames")
56
- temperature = gr.Slider(0.1, 1.5, value=0.5, step=0.05, label="temperature")
57
- top_k = gr.Slider(1, 200, value=50, step=1, label="top_k")
58
- repetition_penalty = gr.Slider(1.0, 2.0, value=1.0, step=0.05, label="repetition_penalty")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  tts_btn = gr.Button("Generate", variant="primary")
 
60
  with gr.Column():
61
  audio_out = gr.Audio(label="Output audio", type="numpy")
62
  tts_status = gr.Markdown()
63
 
64
  tts_btn.click(
65
  tts_generate,
66
- inputs=[text_in, max_frames, temperature, top_k, repetition_penalty],
 
 
 
 
 
67
  outputs=[audio_out, tts_status],
68
  )
69
 
 
1
  # -*- coding: utf-8 -*-
2
+ import re
3
  import numpy as np
4
  import torch
5
  import gradio as gr
 
 
 
6
  from transformers import AutoModelForSeq2SeqLM, PreTrainedTokenizerFast
7
+ from qwen_tts import Qwen3TTSTokenizer
8
+
9
  REPO = "telecomadm1145/Kiseki-TTS-1.2"
10
  CODEC_REPO = "Qwen/Qwen3-TTS-Tokenizer-12Hz"
11
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
 
13
  print(f"Loading models onto device={DEVICE} ...")
14
  tok = PreTrainedTokenizerFast.from_pretrained(REPO, trust_remote_code=True)
15
  codec_tokenizer = Qwen3TTSTokenizer.from_pretrained(CODEC_REPO, device_map=DEVICE)
 
16
  model = AutoModelForSeq2SeqLM.from_pretrained(REPO, trust_remote_code=True).eval().to(DEVICE)
17
+ NUM_Q = model.config.num_q
18
+ FPS = 12 # Qwen3-TTS-Tokenizer-12Hz
19
+
20
 
21
+ # ---------------------------------------------------------------- 分层参数解析
22
+ def parse_per_layer(spec, Q, default, cast=float):
23
+ """
24
+ 支持:
25
+ "" -> default
26
+ "0.7" -> 全层 0.7
27
+ "0.9,0.7,0.6" -> 逐层(不足按最后一个补齐)
28
+ "default:0.7, 0:0.9, 1-3:0.8"
29
+ """
30
+ if spec is None or not str(spec).strip():
31
+ return default
32
+ s = str(spec).strip()
33
+ if ":" in s:
34
+ out = [cast(default)] * Q
35
+ for item in re.split(r"[,\s;]+", s):
36
+ if not item or ":" not in item:
37
+ continue
38
+ k, v = item.split(":", 1)
39
+ k, v = k.strip(), cast(float(v))
40
+ if k in ("default", "*", "all"):
41
+ out = [v] * Q
42
+ elif "-" in k:
43
+ a, b = k.split("-")
44
+ for i in range(int(a), min(int(b), Q - 1) + 1):
45
+ out[i] = v
46
+ else:
47
+ i = int(k)
48
+ if 0 <= i < Q:
49
+ out[i] = v
50
+ return out
51
+ vals = [cast(float(p)) for p in re.split(r"[,\s;]+", s) if p]
52
+ return vals if len(vals) > 1 else vals[0]
53
 
54
+
55
+ # ---------------------------------------------------------------- 参考音频编码
56
+ def encode_reference_audio(path):
57
+ """返回 (T, Q) 的 LongTensor。"""
58
+ if not path:
59
+ return None
60
+ res = None
61
+ for call in (lambda: codec_tokenizer.encode(path),
62
+ lambda: codec_tokenizer.encode([path]),
63
+ lambda: codec_tokenizer(path)):
64
+ try:
65
+ res = call()
66
+ break
67
+ except Exception:
68
+ continue
69
+ if res is None:
70
+ raise gr.Error("参考音频编码失败:codec tokenizer 不支持该输入形式。")
71
+
72
+ if isinstance(res, dict):
73
+ codes = res.get("audio_codes", res.get("codes", next(iter(res.values()))))
74
+ elif isinstance(res, (list, tuple)):
75
+ codes = res[0]
76
+ else:
77
+ codes = res
78
+ if isinstance(codes, (list, tuple)):
79
+ codes = codes[0]
80
+ codes = torch.as_tensor(codes)
81
+ while codes.dim() > 2:
82
+ codes = codes[0]
83
+ if codes.dim() != 2:
84
+ raise gr.Error(f"参考音频 codes 维度异常: {tuple(codes.shape)}")
85
+ # 统一成 (T, Q)
86
+ if codes.shape[-1] != NUM_Q and codes.shape[0] == NUM_Q:
87
+ codes = codes.transpose(0, 1)
88
+ if codes.shape[-1] != NUM_Q:
89
+ raise gr.Error(f"codebook 数不匹配: 模型 num_q={NUM_Q}, 音频 {tuple(codes.shape)}")
90
+ return codes.long()
91
+
92
+
93
+ # ---------------------------------------------------------------- 主流程
94
+ def tts_generate(text, ref_audio, ref_text, prompt_seconds,
95
+ max_new_frames, min_new_frames,
96
+ temperature, top_k, top_p, repetition_penalty, rep_window,
97
+ temp_spec, topk_spec, topp_spec, rep_spec,
98
+ cfg_scale, cfg_scale_q0, cfg_spec, cfg_rescale, neg_text,
99
+ seed):
100
  if not text or not text.strip():
101
  raise gr.Error("Please enter some text to synthesize.")
102
 
103
+ info = []
104
  with torch.no_grad():
105
+ # ---- 引导音频 ----
106
+ prompt_codes = None
107
+ if ref_audio:
108
+ prompt_codes = encode_reference_audio(ref_audio)
109
+ cap = int(float(prompt_seconds) * FPS)
110
+ if cap > 0 and prompt_codes.shape[0] > cap:
111
+ prompt_codes = prompt_codes[:cap]
112
+ prompt_codes = prompt_codes.to(DEVICE)
113
+ info.append(f"prompt frames = {prompt_codes.shape[0]} "
114
+ f"({prompt_codes.shape[0] / FPS:.2f}s)")
115
+
116
+ # ---- encoder 输入 ----
117
+ prompt_text_ids = tok.encode(ref_text) if (ref_audio and ref_text and ref_text.strip()) else None
118
+ ids = model.build_tts_input_ids(tok.encode(text),
119
+ prompt_text_ids=prompt_text_ids,
120
+ device=DEVICE)
121
+
122
+ neg_ids = None
123
+ if neg_text and neg_text.strip():
124
+ neg_ids = model.build_tts_input_ids(tok.encode(neg_text), device=DEVICE)
125
+
126
+ # ---- 分层采样参数 ----
127
+ temps = parse_per_layer(temp_spec, NUM_Q, float(temperature))
128
+ topks = parse_per_layer(topk_spec, NUM_Q, int(top_k), cast=int)
129
+ topps = parse_per_layer(topp_spec, NUM_Q, float(top_p))
130
+ reps = parse_per_layer(rep_spec, NUM_Q, float(repetition_penalty))
131
+ cfgs = parse_per_layer(cfg_spec, NUM_Q, float(cfg_scale))
132
+
133
  out = model.generate_audio(
134
  ids,
135
+ prompt_codes=prompt_codes,
136
  max_new_frames=int(max_new_frames),
137
+ min_new_frames=int(min_new_frames),
138
+ temperature=temps, top_k=topks, top_p=topps,
139
+ repetition_penalty=reps, repetition_window=int(rep_window),
140
+ cfg_scale=cfgs,
141
+ cfg_scale_q0=(None if cfg_scale_q0 <= 0 else float(cfg_scale_q0)),
142
+ cfg_rescale=float(cfg_rescale),
143
+ negative_input_ids=neg_ids,
144
+ seed=(None if int(seed) < 0 else int(seed)),
145
  )
146
+
147
  codes = out["audio_codes"][0][out["valid_mask"][0]]
148
+ if codes.shape[0] == 0:
149
+ raise gr.Error("模型立刻输出了 EOS,试着提高 min_new_frames 或降低 CFG。")
150
  wavs, sr = codec_tokenizer.decode({"audio_codes": codes})
151
 
152
  wav = wavs[0]
153
  if isinstance(wav, torch.Tensor):
154
  wav = wav.detach().cpu().numpy()
155
+ wav = np.asarray(wav, dtype=np.float32).squeeze()
156
 
157
+ info.append(f"generated frames = {codes.shape[0]} ({codes.shape[0] / FPS:.2f}s)")
158
+ info.append(f"temp={temps} | top_k={topks} | cfg={cfgs}")
159
+ return (sr, wav), " · ".join(info)
160
 
161
 
162
+ # ---------------------------------------------------------------- UI
163
  with gr.Blocks(title="Kiseki-TTS") as demo:
164
  gr.Markdown(
165
  "# Kiseki-TTS\n"
166
+ f"Model: [`{REPO}`](https://huggingface.co/{REPO}) · Codec: `{CODEC_REPO}` · num_q={NUM_Q}"
167
  )
 
168
  with gr.Row():
169
  with gr.Column():
170
  text_in = gr.Textbox(label="Text", placeholder="うん、今日はいい天気です", lines=3)
171
+
172
+ with gr.Accordion("🎤 引导音频 (voice prompt / 音色克隆)", open=True):
173
+ ref_audio = gr.Audio(label="Reference audio", type="filepath")
174
+ ref_text = gr.Textbox(
175
+ label="Reference transcript(强烈建议填写,与参考音频内容一致)",
176
+ lines=2)
177
+ prompt_seconds = gr.Slider(0, 30, value=10, step=0.5,
178
+ label="prompt 最长秒数 (0=不限)")
179
+
180
+ with gr.Accordion("🎚 CFG (classifier-free guidance)", open=False):
181
+ cfg_scale = gr.Slider(1.0, 5.0, value=1.0, step=0.05,
182
+ label="cfg_scale(1.0=关闭;建议 1.3~2.0)")
183
+ cfg_scale_q0 = gr.Slider(0.0, 5.0, value=0.0, step=0.05,
184
+ label="cfg_scale_q0(0=沿用上面)")
185
+ cfg_spec = gr.Textbox(label="逐层 cfg_scale(覆盖上面,如 `0:2.0, 1-15:1.2`)")
186
+ cfg_rescale = gr.Slider(0.0, 1.0, value=0.0, step=0.05,
187
+ label="cfg_rescale(std 回缩,抑制过锐)")
188
+ neg_text = gr.Textbox(label="Negative text(留空=丢弃全部文本做无条件分支)")
189
+
190
+ with gr.Accordion("⚙️ 采样参数(全局)", open=False):
191
+ max_frames = gr.Slider(50, 2000, value=250, step=10, label="max_new_frames")
192
+ min_frames = gr.Slider(0, 500, value=0, step=5, label="min_new_frames(抑制早停)")
193
+ temperature = gr.Slider(0.0, 1.5, value=0.5, step=0.05, label="temperature")
194
+ top_k = gr.Slider(0, 2048, value=50, step=1, label="top_k (0=off)")
195
+ top_p = gr.Slider(0.1, 1.0, value=0.95, step=0.01, label="top_p")
196
+ repetition_penalty = gr.Slider(1.0, 2.0, value=1.0, step=0.05,
197
+ label="repetition_penalty")
198
+ rep_window = gr.Slider(0, 200, value=0, step=5,
199
+ label="repetition_window(0=全历史)")
200
+ seed = gr.Number(value=-1, precision=0, label="seed (-1 = random)")
201
+
202
+ with gr.Accordion("🧬 分层采样(按 RVQ 层覆盖全局值)", open=False):
203
+ gr.Markdown(
204
+ "格式:`0.7` / `0.9,0.7,0.6,...`(不足按最后一个补齐)/ "
205
+ "`default:0.7, 0:0.9, 1-3:0.8`。留空表示用全局值。\n"
206
+ "经验:q0 决定内容与韵律(温度略高、top_k 大),q1+ 只是残差细节(低温甚至 0=贪心)。"
207
+ )
208
+ temp_spec = gr.Textbox(label="temperature / layer",
209
+ placeholder="default:0.4, 0:0.8")
210
+ topk_spec = gr.Textbox(label="top_k / layer", placeholder="default:20, 0:60")
211
+ topp_spec = gr.Textbox(label="top_p / layer", placeholder="default:0.9, 0:0.95")
212
+ rep_spec = gr.Textbox(label="repetition_penalty / layer", placeholder="0:1.15")
213
+
214
  tts_btn = gr.Button("Generate", variant="primary")
215
+
216
  with gr.Column():
217
  audio_out = gr.Audio(label="Output audio", type="numpy")
218
  tts_status = gr.Markdown()
219
 
220
  tts_btn.click(
221
  tts_generate,
222
+ inputs=[text_in, ref_audio, ref_text, prompt_seconds,
223
+ max_frames, min_frames,
224
+ temperature, top_k, top_p, repetition_penalty, rep_window,
225
+ temp_spec, topk_spec, topp_spec, rep_spec,
226
+ cfg_scale, cfg_scale_q0, cfg_spec, cfg_rescale, neg_text,
227
+ seed],
228
  outputs=[audio_out, tts_status],
229
  )
230