From 2dfe91afbb2061dabac95e609971a4410be74437 Mon Sep 17 00:00:00 2001 From: XucroYuri Date: Tue, 7 Jul 2026 13:04:09 +0800 Subject: [PATCH] =?UTF-8?q?refactor(webui):=20=E9=80=89=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E5=90=8D=E8=87=AA=E5=8A=A8=E5=8C=B9=E9=85=8D=E6=9D=83=E9=87=8D?= =?UTF-8?q?(=E7=9B=AE=E6=A0=87epoch=208/15),=20=E7=A7=BB=E9=99=A4=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=B9=E9=85=8D=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按产品逻辑重构模型切换流程: 1. 选中模型名(实验名) -> 自动设置 GPT/SoVITS 下拉默认值, 无需点按钮 2. GPT 目标 epoch=8, SoVITS 目标 epoch=15 (对应训练WebUI默认total_epoch) 3. 用户仍可通过下拉框手动更换 GPT/SoVITS 权重 实现: - 新增 _extract_epoch_from_weight: 从文件名提取epoch (GPT: -e10, SoVITS: _e12_s180) - 新增 _pick_weight_by_epoch: 从权重列表选epoch最接近目标的(平手取较小epoch) - 新增 auto_match_weights_for_model: 封装扫描+匹配, 返回 gr.Dropdown 更新 - on_model_name_change 合并自动匹配: outputs 从 [样本,预览] 扩展为 [样本,预览,GPT下拉,SoVITS下拉], 选中模型名即联动设置权重 - 移除「自动匹配权重」按钮组件及 click 绑定 (不再需要手动触发) - 删除 on_auto_select_weights (逻辑并入 on_model_name_change) 验证(真实proplus数据): 选「光头TTS新-20260611」-> GPT 自动选 e10(可选5/10/15../50, |10-8|最近), SoVITS 自动选 e16(可选4/8/12/16/20/24, |16-15|最近), 样本下拉40个含emotion标签。组件数64->63。 --- GPT_SoVITS/inference_webui.py | 79 ++++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 20 deletions(-) diff --git a/GPT_SoVITS/inference_webui.py b/GPT_SoVITS/inference_webui.py index 5971b486..261724d9 100644 --- a/GPT_SoVITS/inference_webui.py +++ b/GPT_SoVITS/inference_webui.py @@ -1293,12 +1293,17 @@ def _read_emotion_map_for_webui(model_name: str) -> dict[str, str]: def on_model_name_change(model_name: str): - """模型名变化时,加载该模型的训练样本列表到下拉框与预览播放器。""" + """模型名变化时:加载训练样本列表 + 自动匹配最佳 GPT/SoVITS 权重。 + + 返回: [样本下拉框, 样本预览, GPT下拉框, SoVITS下拉框] + GPT/SoVITS 自动选中 epoch 最接近推荐值(8/15)的权重, 用户仍可手动改。 + """ model_name = _coerce_single(model_name) if not model_name: - return gr.Dropdown(choices=[], value=""), gr.Audio(value=None) + return gr.Dropdown(choices=[], value=""), gr.Audio(value=None), gr.Dropdown(), gr.Dropdown() from config import exp_root + # 1. 加载训练样本 logs_dir = Path(exp_root) / model_name wav_dir = logs_dir / "5-wav32k" samples: list[tuple[str, str]] = [] @@ -1318,7 +1323,9 @@ def on_model_name_change(model_name: str): choices = [s[0] for s in samples] value = samples[0][0] if samples else "" audio = samples[0][1] if samples else None - return gr.Dropdown(choices=choices, value=value), gr.Audio(value=audio) + # 2. 自动匹配 GPT/SoVITS 权重(epoch 最接近 8/15) + gpt_dd, sovits_dd = auto_match_weights_for_model(model_name) + return gr.Dropdown(choices=choices, value=value), gr.Audio(value=audio), gpt_dd, sovits_dd def on_ref_sample_change(sample_label: str, model_name: str): @@ -1381,21 +1388,59 @@ def _scan_model_weights_for_webui() -> dict[str, dict[str, list[str]]]: return grouped -def on_auto_select_weights(model_name: str): - """根据模型名自动匹配并选择最佳 GPT/SoVITS 权重。 +def _extract_epoch_from_weight(weight_path: str, kind: str) -> int | None: + """从权重文件名提取 epoch 数字。 - 仅返回 Dropdown 的 value;真正的权重切换由已绑定的 - GPT_dropdown.change / SoVITS_dropdown.change 在 value 变化时自动触发。 - (change_sovits_weights 是生成器,手动消费会丢失对其它组件的更新,故不直接调用。) + kind='gpt': '-e10.ckpt' -> 10 + kind='sovits': '_e12_s180.pth' -> 12 + """ + name = Path(weight_path).name + pattern = r"-e(\d+)" if kind == "gpt" else r"_e(\d+)_s\d+" + m = re.search(pattern, name, flags=re.IGNORECASE) + return int(m.group(1)) if m else None + + +def _pick_weight_by_epoch(weights: list[str], target_epoch: int) -> str | None: + """从权重列表中选 epoch 最接近 target_epoch 的(平手取较小 epoch)。 + + 匹配训练 WebUI 的推荐: GPT 目标 8 (total_epoch 默认 8), SoVITS 目标 15 + (total_epoch 默认 15)。无法提取 epoch 时退化为列表第一个。 + """ + if not weights: + return None + best = None + best_diff = None + for w in weights: + # kind 由文件扩展名推断 + kind = "gpt" if w.endswith(".ckpt") else "sovits" + ep = _extract_epoch_from_weight(w, kind) + if ep is None: + continue + diff = abs(ep - target_epoch) + if best_diff is None or diff < best_diff or (diff == best_diff and ep < (best_ep or 0)): + best = w + best_diff = diff + best_ep = ep + return best or weights[0] + + +# GPT/SoVITS 自动匹配权重的目标 epoch(对应训练 WebUI 的默认 total_epoch) +_GPT_TARGET_EPOCH = 8 +_SOVITS_TARGET_EPOCH = 15 + + +def auto_match_weights_for_model(model_name: str): + """选中模型名时自动匹配最佳 GPT/SoVITS 权重(按目标 epoch 最近匹配)。 + + 返回 (gpt_dropdown_update, sovits_dropdown_update);真正的权重切换由已绑定的 + GPT_dropdown.change / SoVITS_dropdown.change 在 value 变化时自动触发。 """ - model_name = _coerce_single(model_name) if not model_name: return gr.Dropdown(), gr.Dropdown() weights = _scan_model_weights_for_webui() model_weights = weights.get(model_name, {"gpt": [], "sovits": []}) - # 选择最高 epoch 的权重(按字符串排序后取末尾) - gpt_best = sorted(model_weights["gpt"])[-1] if model_weights["gpt"] else None - sovits_best = sorted(model_weights["sovits"])[-1] if model_weights["sovits"] else None + gpt_best = _pick_weight_by_epoch(model_weights["gpt"], _GPT_TARGET_EPOCH) + sovits_best = _pick_weight_by_epoch(model_weights["sovits"], _SOVITS_TARGET_EPOCH) return gr.Dropdown(value=gpt_best), gr.Dropdown(value=sovits_best) @@ -1424,8 +1469,7 @@ with gr.Blocks(title="GPT-SoVITS WebUI", analytics_enabled=False, js=js, css=css interactive=True, scale=14, ) - refresh_button = gr.Button(i18n("刷新模型路径"), variant="primary", scale=7) - auto_select_weights_btn = gr.Button(i18n("自动匹配权重"), variant="secondary", scale=7) + refresh_button = gr.Button(i18n("刷新模型路径"), variant="primary", scale=14) refresh_button.click(fn=change_choices, inputs=[], outputs=[SoVITS_dropdown, GPT_dropdown]) # ===== 新增:训练角色选择区域(独立 Group,按操作流程纵向排列) ===== with gr.Group(): @@ -1650,7 +1694,7 @@ with gr.Blocks(title="GPT-SoVITS WebUI", analytics_enabled=False, js=js, css=css model_name_dropdown.change( fn=on_model_name_change, inputs=[model_name_dropdown], - outputs=[ref_sample_dropdown, ref_sample_player], + outputs=[ref_sample_dropdown, ref_sample_player, GPT_dropdown, SoVITS_dropdown], ) ref_sample_dropdown.change( fn=on_ref_sample_change, @@ -1662,11 +1706,6 @@ with gr.Blocks(title="GPT-SoVITS WebUI", analytics_enabled=False, js=js, css=css inputs=[ref_sample_dropdown, model_name_dropdown], outputs=[inp_ref, prompt_text, ref_sample_dropdown, ref_emotion_text], ) - auto_select_weights_btn.click( - fn=on_auto_select_weights, - inputs=[model_name_dropdown], - outputs=[GPT_dropdown, SoVITS_dropdown], - ) # 页面加载时自动扫描模型列表 app.load(fn=refresh_model_dropdown, inputs=[], outputs=[model_name_dropdown])