Codex commited on
Commit ·
444221d
1
Parent(s): 239a98e
Persist and poll live training logs
Browse files
app.py
CHANGED
|
@@ -98,6 +98,9 @@ class VersionContext:
|
|
| 98 |
exp_dir: Path
|
| 99 |
text_path: Path
|
| 100 |
semantic_path: Path
|
|
|
|
|
|
|
|
|
|
| 101 |
sovits_ckpt_dir: Path
|
| 102 |
gpt_log_dir: Path
|
| 103 |
sovits_output_dir: Path
|
|
@@ -165,6 +168,9 @@ def get_version_context(version: str):
|
|
| 165 |
exp_dir=exp_dir,
|
| 166 |
text_path=exp_dir / "2-name2text.txt",
|
| 167 |
semantic_path=exp_dir / "6-name2semantic.tsv",
|
|
|
|
|
|
|
|
|
|
| 168 |
sovits_ckpt_dir=exp_dir / f"logs_s2_{version}",
|
| 169 |
gpt_log_dir=exp_dir / f"logs_s1_{version}",
|
| 170 |
sovits_output_dir=OUTPUT_ROOT / f"SoVITS_weights_{version}",
|
|
@@ -204,8 +210,28 @@ def hf_kwargs():
|
|
| 204 |
return {"token": HF_TOKEN} if HF_TOKEN else {}
|
| 205 |
|
| 206 |
|
| 207 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
logs.append(message)
|
|
|
|
|
|
|
| 209 |
return "\n".join(logs[-200:])
|
| 210 |
|
| 211 |
|
|
@@ -569,37 +595,38 @@ def create_gpt_config(ctx: VersionContext, epochs, batch_size, save_every_epoch)
|
|
| 569 |
return tmp_config
|
| 570 |
|
| 571 |
|
| 572 |
-
def setup_environment_steps(logs, version):
|
| 573 |
ctx = get_version_context(version)
|
| 574 |
ensure_dirs()
|
| 575 |
-
yield push(logs, f"目标版本:{ctx.spec.version}({ctx.spec.stability})")
|
| 576 |
if (GPT_SOVITS_DIR / "webui.py").exists():
|
| 577 |
-
yield push(logs, "GPT-SoVITS 仓库已存在,跳过克隆。")
|
| 578 |
else:
|
| 579 |
-
yield push(logs, "克隆 GPT-SoVITS 仓库...")
|
| 580 |
ensure_upstream_repo()
|
| 581 |
-
yield push(logs, "✅ GPT-SoVITS 仓库已就绪。")
|
| 582 |
patch_upstream_repo()
|
| 583 |
-
yield push(logs, "✅ 已应用 Space 兼容补丁。")
|
| 584 |
if not has_transformers_model(BERT_DIR):
|
| 585 |
-
yield push(logs, "下载中文 BERT 特征模型...")
|
| 586 |
if not has_transformers_model(CNHUBERT_DIR):
|
| 587 |
-
yield push(logs, "下载 CN-HuBERT 特征模型...")
|
| 588 |
if not ctx.pretrained_s1.exists() or not ctx.pretrained_s2g.exists():
|
| 589 |
-
yield push(logs, f"下载 GPT-SoVITS {ctx.spec.version} 底模...")
|
| 590 |
if ctx.sv_pretrained and not ctx.sv_pretrained.exists():
|
| 591 |
-
yield push(logs, f"下载 {ctx.spec.version} 的 speaker embedding 底模...")
|
| 592 |
ensure_base_assets(version)
|
| 593 |
yield push(
|
| 594 |
logs,
|
| 595 |
f"✅ 环境就绪:GPT-SoVITS 仓库、中文特征模型和 {ctx.spec.version} 底模均已准备完成。",
|
|
|
|
| 596 |
)
|
| 597 |
|
| 598 |
|
| 599 |
-
def download_dataset_steps(logs, version):
|
| 600 |
ensure_dirs()
|
| 601 |
-
yield from setup_environment_steps(logs, version)
|
| 602 |
-
yield push(logs, "下载 Daniya 数据集...")
|
| 603 |
snapshot_download(
|
| 604 |
repo_id=DATASET_REPO,
|
| 605 |
repo_type="dataset",
|
|
@@ -611,34 +638,37 @@ def download_dataset_steps(logs, version):
|
|
| 611 |
yield push(
|
| 612 |
logs,
|
| 613 |
f"✅ 数据集已下载:音频 {audio_count} 个,metadata {len(rows)} 条。",
|
|
|
|
| 614 |
)
|
| 615 |
|
| 616 |
|
| 617 |
-
def prepare_data_steps(logs, version):
|
| 618 |
ctx = get_version_context(version)
|
| 619 |
ensure_dirs()
|
| 620 |
lock_path = ctx.exp_dir / ".preprocess.lock"
|
| 621 |
-
|
| 622 |
with lock_path.open("a+", encoding="utf-8") as lock_handle:
|
| 623 |
while True:
|
| 624 |
try:
|
| 625 |
fcntl.flock(lock_handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
| 626 |
break
|
| 627 |
except BlockingIOError:
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
|
|
|
|
| 631 |
time.sleep(2)
|
| 632 |
try:
|
| 633 |
if dataset_prepared(ctx):
|
| 634 |
return f"✅ 预处理已就绪({ctx.spec.version}),无需重复执行。"
|
| 635 |
-
yield from download_dataset_steps(logs, version)
|
| 636 |
reset_preprocess_outputs(ctx)
|
| 637 |
sample_count, audio_count, unlisted = build_manifest(ctx)
|
| 638 |
ensure_preprocess_dirs(ctx)
|
| 639 |
yield push(
|
| 640 |
logs,
|
| 641 |
f"训练清单已生成:metadata 可用样本 {sample_count} 条,音频总数 {audio_count} 个,未标注音频 {len(unlisted)} 个。",
|
|
|
|
| 642 |
)
|
| 643 |
env = build_process_env(ctx)
|
| 644 |
for line in run_cmd(
|
|
@@ -646,33 +676,33 @@ def prepare_data_steps(logs, version):
|
|
| 646 |
cwd=GPT_SOVITS_DIR,
|
| 647 |
env=env,
|
| 648 |
):
|
| 649 |
-
yield push(logs, line)
|
| 650 |
part_text = ctx.exp_dir / "2-name2text-0.txt"
|
| 651 |
if not part_text.exists():
|
| 652 |
raise RuntimeError("文本特征提取完成后未生成 2-name2text-0.txt")
|
| 653 |
part_text.replace(ctx.text_path)
|
| 654 |
-
yield push(logs, "✅ 文本分词与 BERT 特征提取完成。")
|
| 655 |
for line in run_cmd(
|
| 656 |
[sys.executable, "-s", "GPT_SoVITS/prepare_datasets/2-get-hubert-wav32k.py"],
|
| 657 |
cwd=GPT_SOVITS_DIR,
|
| 658 |
env=env,
|
| 659 |
):
|
| 660 |
-
yield push(logs, line)
|
| 661 |
-
yield push(logs, "✅ CN-HuBERT 特征与 32k wav 已生成。")
|
| 662 |
if ctx.spec.uses_sv:
|
| 663 |
for line in run_cmd(
|
| 664 |
[sys.executable, "-s", "GPT_SoVITS/prepare_datasets/2-get-sv.py"],
|
| 665 |
cwd=GPT_SOVITS_DIR,
|
| 666 |
env=env,
|
| 667 |
):
|
| 668 |
-
yield push(logs, line)
|
| 669 |
-
yield push(logs, "✅ speaker embedding 特征已生成。")
|
| 670 |
for line in run_cmd(
|
| 671 |
[sys.executable, "-s", "GPT_SoVITS/prepare_datasets/3-get-semantic.py"],
|
| 672 |
cwd=GPT_SOVITS_DIR,
|
| 673 |
env=env,
|
| 674 |
):
|
| 675 |
-
yield push(logs, line)
|
| 676 |
part_semantic = ctx.exp_dir / "6-name2semantic-0.tsv"
|
| 677 |
if not part_semantic.exists():
|
| 678 |
raise RuntimeError("语义 token 提取完成后未生成 6-name2semantic-0.tsv")
|
|
@@ -682,7 +712,7 @@ def prepare_data_steps(logs, version):
|
|
| 682 |
encoding="utf-8",
|
| 683 |
)
|
| 684 |
part_semantic.unlink()
|
| 685 |
-
yield push(logs, "✅ 语义 token 提取完成。")
|
| 686 |
return f"✅ 预处理完成({ctx.spec.version}),可用于训练的样本 {sample_count} 条。"
|
| 687 |
finally:
|
| 688 |
fcntl.flock(lock_handle.fileno(), fcntl.LOCK_UN)
|
|
@@ -690,98 +720,123 @@ def prepare_data_steps(logs, version):
|
|
| 690 |
|
| 691 |
def check_environment(version=DEFAULT_VERSION):
|
| 692 |
logs = []
|
|
|
|
|
|
|
| 693 |
try:
|
| 694 |
final = None
|
| 695 |
-
final = yield from setup_environment_steps(logs, version)
|
| 696 |
if final:
|
| 697 |
yield final
|
| 698 |
except Exception as exc:
|
| 699 |
log.exception("check_environment")
|
| 700 |
-
yield push(logs, f"❌ 环境准备失败: {exc}")
|
| 701 |
|
| 702 |
|
| 703 |
def download_dataset(version=DEFAULT_VERSION):
|
| 704 |
logs = []
|
|
|
|
|
|
|
| 705 |
try:
|
| 706 |
final = None
|
| 707 |
-
final = yield from download_dataset_steps(logs, version)
|
| 708 |
if final:
|
| 709 |
yield final
|
| 710 |
except Exception as exc:
|
| 711 |
log.exception("download_dataset")
|
| 712 |
-
yield push(logs, f"❌ 数据集下载失败: {exc}")
|
| 713 |
|
| 714 |
|
| 715 |
def prepare_data(version=DEFAULT_VERSION):
|
| 716 |
logs = []
|
|
|
|
|
|
|
| 717 |
try:
|
| 718 |
-
final = yield from prepare_data_steps(logs, version)
|
| 719 |
-
yield push(logs, final)
|
| 720 |
except Exception as exc:
|
| 721 |
log.exception("prepare_data")
|
| 722 |
-
yield push(logs, f"❌ 预处理失败: {exc}")
|
| 723 |
|
| 724 |
|
| 725 |
def start_training(version=DEFAULT_VERSION, epochs=2, batch_size=1, save_every_epoch=1, lr=0.0001):
|
| 726 |
logs = []
|
| 727 |
ctx = get_version_context(version)
|
|
|
|
| 728 |
try:
|
| 729 |
-
yield push(logs, f"当前版本:{ctx.spec.version}({ctx.spec.stability})"), None
|
| 730 |
if not dataset_prepared(ctx):
|
| 731 |
-
yield push(logs, f"{ctx.spec.version} 缺少预处理产物,开始自动补齐..."), None
|
| 732 |
-
for update in prepare_data_steps(logs, version):
|
| 733 |
yield update, None
|
| 734 |
config_path = create_sovits_config(ctx, epochs, batch_size, save_every_epoch, lr)
|
| 735 |
env = build_process_env(ctx)
|
| 736 |
-
yield push(logs, f"开始 SoVITS 训练({ctx.spec.version})..."), None
|
| 737 |
for line in run_cmd(
|
| 738 |
[sys.executable, "-s", "GPT_SoVITS/s2_train.py", "--config", str(config_path)],
|
| 739 |
cwd=GPT_SOVITS_DIR,
|
| 740 |
env=env,
|
| 741 |
):
|
| 742 |
-
yield push(logs, line), None
|
| 743 |
latest = latest_file(ctx.sovits_output_dir, ".pth")
|
| 744 |
if not latest:
|
| 745 |
raise RuntimeError("训练结束后没有找到导出的 SoVITS 权重文件")
|
| 746 |
-
yield push(logs, f"✅ SoVITS 训练完成,最新权重:{latest}"), latest
|
| 747 |
except Exception as exc:
|
| 748 |
log.exception("start_training")
|
| 749 |
-
yield push(logs, f"❌ SoVITS 训练失败: {exc}"), None
|
| 750 |
|
| 751 |
|
| 752 |
def start_gpt_training(version=DEFAULT_VERSION, epochs=1, batch_size=1, save_every_epoch=1):
|
| 753 |
logs = []
|
| 754 |
ctx = get_version_context(version)
|
|
|
|
| 755 |
try:
|
| 756 |
-
yield push(logs, f"当前版本:{ctx.spec.version}({ctx.spec.stability})"), None
|
| 757 |
if not dataset_prepared(ctx):
|
| 758 |
-
yield push(logs, f"{ctx.spec.version} 缺少预处理产物,开始自动补齐..."), None
|
| 759 |
-
for update in prepare_data_steps(logs, version):
|
| 760 |
yield update, None
|
| 761 |
reset_gpt_training_outputs(ctx)
|
| 762 |
-
yield push(logs, "已清理旧 GPT 断点与导出文件,避免恢复到不兼容 checkpoint。"), None
|
| 763 |
config_path = create_gpt_config(ctx, epochs, batch_size, save_every_epoch)
|
| 764 |
env = build_process_env(ctx)
|
| 765 |
-
yield push(logs, f"开始 GPT 训练({ctx.spec.version})..."), None
|
| 766 |
for line in run_cmd(
|
| 767 |
[sys.executable, "-s", "GPT_SoVITS/s1_train.py", "--config_file", str(config_path)],
|
| 768 |
cwd=GPT_SOVITS_DIR,
|
| 769 |
env=env,
|
| 770 |
):
|
| 771 |
-
yield push(logs, line), None
|
| 772 |
latest = latest_file(ctx.gpt_output_dir, ".ckpt")
|
| 773 |
if not latest:
|
| 774 |
raise RuntimeError("训练结束后没有找到导出的 GPT 权重文件")
|
| 775 |
-
yield push(logs, f"✅ GPT 训练完成,最新权重:{latest}"), latest
|
| 776 |
except Exception as exc:
|
| 777 |
log.exception("start_gpt_training")
|
| 778 |
-
yield push(logs, f"❌ GPT 训练失败: {exc}"), None
|
| 779 |
|
| 780 |
|
| 781 |
def refresh_outputs(version=DEFAULT_VERSION):
|
| 782 |
return artifacts_summary(version)
|
| 783 |
|
| 784 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 785 |
def version_markdown(version=DEFAULT_VERSION):
|
| 786 |
spec = get_version_spec(version)
|
| 787 |
return (
|
|
@@ -844,8 +899,7 @@ def load_dashboard():
|
|
| 844 |
version = DEFAULT_VERSION
|
| 845 |
return (
|
| 846 |
version_markdown(version),
|
| 847 |
-
*
|
| 848 |
-
agent_status(version),
|
| 849 |
)
|
| 850 |
|
| 851 |
|
|
@@ -859,6 +913,7 @@ def create_ui():
|
|
| 859 |
"打开页面会自动加载当前输出。训练完成后,最新模型会直接出现在下载框里,"
|
| 860 |
"下面的“输出与目录”也会自动刷新,避免找不到文件。"
|
| 861 |
)
|
|
|
|
| 862 |
version_select = gr.Dropdown(
|
| 863 |
choices=list(SUPPORTED_VERSIONS),
|
| 864 |
value=DEFAULT_VERSION,
|
|
@@ -924,8 +979,13 @@ def create_ui():
|
|
| 924 |
)
|
| 925 |
agent_status_out = gr.JSON(label="Agent 状态", value=agent_status(DEFAULT_VERSION))
|
| 926 |
agent_status_btn = gr.Button("刷新 Agent 状态", variant="secondary")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 927 |
|
| 928 |
refresh_outputs_targets = [refresh_text, output_dirs, refresh_sovits, refresh_gpt, all_sovits, all_gpt]
|
|
|
|
| 929 |
env_btn.click(check_environment, inputs=[version_select], outputs=env_out, api_name="check_environment")
|
| 930 |
dataset_btn.click(download_dataset, inputs=[version_select], outputs=dataset_out, api_name="download_dataset")
|
| 931 |
prep_btn.click(prepare_data, inputs=[version_select], outputs=prep_out, api_name="prepare_data")
|
|
@@ -941,30 +1001,41 @@ def create_ui():
|
|
| 941 |
outputs=[gpt_log, gpt_file],
|
| 942 |
api_name="start_gpt_training",
|
| 943 |
)
|
| 944 |
-
refresh_btn.click(
|
|
|
|
|
|
|
|
|
|
| 945 |
agent_status_btn.click(agent_status, inputs=[version_select], outputs=agent_status_out, api_name="agent_status")
|
| 946 |
version_select.change(version_markdown, inputs=[version_select], outputs=version_note, api_name=False)
|
| 947 |
-
version_select.change(
|
| 948 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 949 |
inputs=[version_select],
|
| 950 |
-
outputs=
|
| 951 |
api_name=False,
|
| 952 |
)
|
| 953 |
-
|
| 954 |
-
|
| 955 |
inputs=[version_select],
|
| 956 |
-
outputs=
|
| 957 |
api_name=False,
|
| 958 |
)
|
| 959 |
-
|
| 960 |
-
|
| 961 |
inputs=[version_select],
|
| 962 |
-
outputs=
|
| 963 |
api_name=False,
|
|
|
|
|
|
|
| 964 |
)
|
| 965 |
demo.load(
|
| 966 |
load_dashboard,
|
| 967 |
-
outputs=[version_note, *
|
| 968 |
api_name=False,
|
| 969 |
)
|
| 970 |
|
|
|
|
| 98 |
exp_dir: Path
|
| 99 |
text_path: Path
|
| 100 |
semantic_path: Path
|
| 101 |
+
prep_live_log: Path
|
| 102 |
+
sovits_live_log: Path
|
| 103 |
+
gpt_live_log: Path
|
| 104 |
sovits_ckpt_dir: Path
|
| 105 |
gpt_log_dir: Path
|
| 106 |
sovits_output_dir: Path
|
|
|
|
| 168 |
exp_dir=exp_dir,
|
| 169 |
text_path=exp_dir / "2-name2text.txt",
|
| 170 |
semantic_path=exp_dir / "6-name2semantic.tsv",
|
| 171 |
+
prep_live_log=exp_dir / "_live_prepare.log",
|
| 172 |
+
sovits_live_log=exp_dir / "_live_sovits.log",
|
| 173 |
+
gpt_live_log=exp_dir / "_live_gpt.log",
|
| 174 |
sovits_ckpt_dir=exp_dir / f"logs_s2_{version}",
|
| 175 |
gpt_log_dir=exp_dir / f"logs_s1_{version}",
|
| 176 |
sovits_output_dir=OUTPUT_ROOT / f"SoVITS_weights_{version}",
|
|
|
|
| 210 |
return {"token": HF_TOKEN} if HF_TOKEN else {}
|
| 211 |
|
| 212 |
|
| 213 |
+
def append_live_log(path: Path, message: str):
|
| 214 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 215 |
+
with path.open("a", encoding="utf-8") as handle:
|
| 216 |
+
handle.write(message + "\n")
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def clear_live_log(path: Path):
|
| 220 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 221 |
+
path.write_text("", encoding="utf-8")
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
def read_live_log(path: Path):
|
| 225 |
+
if not path.exists():
|
| 226 |
+
return ""
|
| 227 |
+
lines = path.read_text(encoding="utf-8", errors="ignore").splitlines()
|
| 228 |
+
return "\n".join(lines[-200:])
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
def push(logs, message, live_path: Path | None = None):
|
| 232 |
logs.append(message)
|
| 233 |
+
if live_path is not None:
|
| 234 |
+
append_live_log(live_path, message)
|
| 235 |
return "\n".join(logs[-200:])
|
| 236 |
|
| 237 |
|
|
|
|
| 595 |
return tmp_config
|
| 596 |
|
| 597 |
|
| 598 |
+
def setup_environment_steps(logs, version, live_path: Path | None = None):
|
| 599 |
ctx = get_version_context(version)
|
| 600 |
ensure_dirs()
|
| 601 |
+
yield push(logs, f"目标版本:{ctx.spec.version}({ctx.spec.stability})", live_path)
|
| 602 |
if (GPT_SOVITS_DIR / "webui.py").exists():
|
| 603 |
+
yield push(logs, "GPT-SoVITS 仓库已存在,跳过克隆。", live_path)
|
| 604 |
else:
|
| 605 |
+
yield push(logs, "克隆 GPT-SoVITS 仓库...", live_path)
|
| 606 |
ensure_upstream_repo()
|
| 607 |
+
yield push(logs, "✅ GPT-SoVITS 仓库已就绪。", live_path)
|
| 608 |
patch_upstream_repo()
|
| 609 |
+
yield push(logs, "✅ 已应用 Space 兼容补丁。", live_path)
|
| 610 |
if not has_transformers_model(BERT_DIR):
|
| 611 |
+
yield push(logs, "下载中文 BERT 特征模型...", live_path)
|
| 612 |
if not has_transformers_model(CNHUBERT_DIR):
|
| 613 |
+
yield push(logs, "下载 CN-HuBERT 特征模型...", live_path)
|
| 614 |
if not ctx.pretrained_s1.exists() or not ctx.pretrained_s2g.exists():
|
| 615 |
+
yield push(logs, f"下载 GPT-SoVITS {ctx.spec.version} 底模...", live_path)
|
| 616 |
if ctx.sv_pretrained and not ctx.sv_pretrained.exists():
|
| 617 |
+
yield push(logs, f"下载 {ctx.spec.version} 的 speaker embedding 底模...", live_path)
|
| 618 |
ensure_base_assets(version)
|
| 619 |
yield push(
|
| 620 |
logs,
|
| 621 |
f"✅ 环境就绪:GPT-SoVITS 仓库、中文特征模型和 {ctx.spec.version} 底模均已准备完成。",
|
| 622 |
+
live_path,
|
| 623 |
)
|
| 624 |
|
| 625 |
|
| 626 |
+
def download_dataset_steps(logs, version, live_path: Path | None = None):
|
| 627 |
ensure_dirs()
|
| 628 |
+
yield from setup_environment_steps(logs, version, live_path)
|
| 629 |
+
yield push(logs, "下载 Daniya 数据集...", live_path)
|
| 630 |
snapshot_download(
|
| 631 |
repo_id=DATASET_REPO,
|
| 632 |
repo_type="dataset",
|
|
|
|
| 638 |
yield push(
|
| 639 |
logs,
|
| 640 |
f"✅ 数据集已下载:音频 {audio_count} 个,metadata {len(rows)} 条。",
|
| 641 |
+
live_path,
|
| 642 |
)
|
| 643 |
|
| 644 |
|
| 645 |
+
def prepare_data_steps(logs, version, live_path: Path | None = None):
|
| 646 |
ctx = get_version_context(version)
|
| 647 |
ensure_dirs()
|
| 648 |
lock_path = ctx.exp_dir / ".preprocess.lock"
|
| 649 |
+
wait_notice_at = 0.0
|
| 650 |
with lock_path.open("a+", encoding="utf-8") as lock_handle:
|
| 651 |
while True:
|
| 652 |
try:
|
| 653 |
fcntl.flock(lock_handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
| 654 |
break
|
| 655 |
except BlockingIOError:
|
| 656 |
+
now = time.time()
|
| 657 |
+
if now >= wait_notice_at:
|
| 658 |
+
yield push(logs, f"{ctx.spec.version} 预处理中,等待已有任务完成...", live_path)
|
| 659 |
+
wait_notice_at = now + 10
|
| 660 |
time.sleep(2)
|
| 661 |
try:
|
| 662 |
if dataset_prepared(ctx):
|
| 663 |
return f"✅ 预处理已就绪({ctx.spec.version}),无需重复执行。"
|
| 664 |
+
yield from download_dataset_steps(logs, version, live_path)
|
| 665 |
reset_preprocess_outputs(ctx)
|
| 666 |
sample_count, audio_count, unlisted = build_manifest(ctx)
|
| 667 |
ensure_preprocess_dirs(ctx)
|
| 668 |
yield push(
|
| 669 |
logs,
|
| 670 |
f"训练清单已生成:metadata 可用样本 {sample_count} 条,音频总数 {audio_count} 个,未标注音频 {len(unlisted)} 个。",
|
| 671 |
+
live_path,
|
| 672 |
)
|
| 673 |
env = build_process_env(ctx)
|
| 674 |
for line in run_cmd(
|
|
|
|
| 676 |
cwd=GPT_SOVITS_DIR,
|
| 677 |
env=env,
|
| 678 |
):
|
| 679 |
+
yield push(logs, line, live_path)
|
| 680 |
part_text = ctx.exp_dir / "2-name2text-0.txt"
|
| 681 |
if not part_text.exists():
|
| 682 |
raise RuntimeError("文本特征提取完成后未生成 2-name2text-0.txt")
|
| 683 |
part_text.replace(ctx.text_path)
|
| 684 |
+
yield push(logs, "✅ 文本分词与 BERT 特征提取完成。", live_path)
|
| 685 |
for line in run_cmd(
|
| 686 |
[sys.executable, "-s", "GPT_SoVITS/prepare_datasets/2-get-hubert-wav32k.py"],
|
| 687 |
cwd=GPT_SOVITS_DIR,
|
| 688 |
env=env,
|
| 689 |
):
|
| 690 |
+
yield push(logs, line, live_path)
|
| 691 |
+
yield push(logs, "✅ CN-HuBERT 特征与 32k wav 已生成。", live_path)
|
| 692 |
if ctx.spec.uses_sv:
|
| 693 |
for line in run_cmd(
|
| 694 |
[sys.executable, "-s", "GPT_SoVITS/prepare_datasets/2-get-sv.py"],
|
| 695 |
cwd=GPT_SOVITS_DIR,
|
| 696 |
env=env,
|
| 697 |
):
|
| 698 |
+
yield push(logs, line, live_path)
|
| 699 |
+
yield push(logs, "✅ speaker embedding 特征已生成。", live_path)
|
| 700 |
for line in run_cmd(
|
| 701 |
[sys.executable, "-s", "GPT_SoVITS/prepare_datasets/3-get-semantic.py"],
|
| 702 |
cwd=GPT_SOVITS_DIR,
|
| 703 |
env=env,
|
| 704 |
):
|
| 705 |
+
yield push(logs, line, live_path)
|
| 706 |
part_semantic = ctx.exp_dir / "6-name2semantic-0.tsv"
|
| 707 |
if not part_semantic.exists():
|
| 708 |
raise RuntimeError("语义 token 提取完成后未生成 6-name2semantic-0.tsv")
|
|
|
|
| 712 |
encoding="utf-8",
|
| 713 |
)
|
| 714 |
part_semantic.unlink()
|
| 715 |
+
yield push(logs, "✅ 语义 token 提取完成。", live_path)
|
| 716 |
return f"✅ 预处理完成({ctx.spec.version}),可用于训练的样本 {sample_count} 条。"
|
| 717 |
finally:
|
| 718 |
fcntl.flock(lock_handle.fileno(), fcntl.LOCK_UN)
|
|
|
|
| 720 |
|
| 721 |
def check_environment(version=DEFAULT_VERSION):
|
| 722 |
logs = []
|
| 723 |
+
ctx = get_version_context(version)
|
| 724 |
+
clear_live_log(ctx.prep_live_log)
|
| 725 |
try:
|
| 726 |
final = None
|
| 727 |
+
final = yield from setup_environment_steps(logs, version, ctx.prep_live_log)
|
| 728 |
if final:
|
| 729 |
yield final
|
| 730 |
except Exception as exc:
|
| 731 |
log.exception("check_environment")
|
| 732 |
+
yield push(logs, f"❌ 环境准备失败: {exc}", ctx.prep_live_log)
|
| 733 |
|
| 734 |
|
| 735 |
def download_dataset(version=DEFAULT_VERSION):
|
| 736 |
logs = []
|
| 737 |
+
ctx = get_version_context(version)
|
| 738 |
+
clear_live_log(ctx.prep_live_log)
|
| 739 |
try:
|
| 740 |
final = None
|
| 741 |
+
final = yield from download_dataset_steps(logs, version, ctx.prep_live_log)
|
| 742 |
if final:
|
| 743 |
yield final
|
| 744 |
except Exception as exc:
|
| 745 |
log.exception("download_dataset")
|
| 746 |
+
yield push(logs, f"❌ 数据集下载失败: {exc}", ctx.prep_live_log)
|
| 747 |
|
| 748 |
|
| 749 |
def prepare_data(version=DEFAULT_VERSION):
|
| 750 |
logs = []
|
| 751 |
+
ctx = get_version_context(version)
|
| 752 |
+
clear_live_log(ctx.prep_live_log)
|
| 753 |
try:
|
| 754 |
+
final = yield from prepare_data_steps(logs, version, ctx.prep_live_log)
|
| 755 |
+
yield push(logs, final, ctx.prep_live_log)
|
| 756 |
except Exception as exc:
|
| 757 |
log.exception("prepare_data")
|
| 758 |
+
yield push(logs, f"❌ 预处理失败: {exc}", ctx.prep_live_log)
|
| 759 |
|
| 760 |
|
| 761 |
def start_training(version=DEFAULT_VERSION, epochs=2, batch_size=1, save_every_epoch=1, lr=0.0001):
|
| 762 |
logs = []
|
| 763 |
ctx = get_version_context(version)
|
| 764 |
+
clear_live_log(ctx.sovits_live_log)
|
| 765 |
try:
|
| 766 |
+
yield push(logs, f"当前版本:{ctx.spec.version}({ctx.spec.stability})", ctx.sovits_live_log), None
|
| 767 |
if not dataset_prepared(ctx):
|
| 768 |
+
yield push(logs, f"{ctx.spec.version} 缺少预处理产物,开始自动补齐...", ctx.sovits_live_log), None
|
| 769 |
+
for update in prepare_data_steps(logs, version, ctx.sovits_live_log):
|
| 770 |
yield update, None
|
| 771 |
config_path = create_sovits_config(ctx, epochs, batch_size, save_every_epoch, lr)
|
| 772 |
env = build_process_env(ctx)
|
| 773 |
+
yield push(logs, f"开始 SoVITS 训练({ctx.spec.version})...", ctx.sovits_live_log), None
|
| 774 |
for line in run_cmd(
|
| 775 |
[sys.executable, "-s", "GPT_SoVITS/s2_train.py", "--config", str(config_path)],
|
| 776 |
cwd=GPT_SOVITS_DIR,
|
| 777 |
env=env,
|
| 778 |
):
|
| 779 |
+
yield push(logs, line, ctx.sovits_live_log), None
|
| 780 |
latest = latest_file(ctx.sovits_output_dir, ".pth")
|
| 781 |
if not latest:
|
| 782 |
raise RuntimeError("训练结束后没有找到导出的 SoVITS 权重文件")
|
| 783 |
+
yield push(logs, f"✅ SoVITS 训练完成,最新权重:{latest}", ctx.sovits_live_log), latest
|
| 784 |
except Exception as exc:
|
| 785 |
log.exception("start_training")
|
| 786 |
+
yield push(logs, f"❌ SoVITS 训练失败: {exc}", ctx.sovits_live_log), None
|
| 787 |
|
| 788 |
|
| 789 |
def start_gpt_training(version=DEFAULT_VERSION, epochs=1, batch_size=1, save_every_epoch=1):
|
| 790 |
logs = []
|
| 791 |
ctx = get_version_context(version)
|
| 792 |
+
clear_live_log(ctx.gpt_live_log)
|
| 793 |
try:
|
| 794 |
+
yield push(logs, f"当前版本:{ctx.spec.version}({ctx.spec.stability})", ctx.gpt_live_log), None
|
| 795 |
if not dataset_prepared(ctx):
|
| 796 |
+
yield push(logs, f"{ctx.spec.version} 缺少预处理产物,开始自动补齐...", ctx.gpt_live_log), None
|
| 797 |
+
for update in prepare_data_steps(logs, version, ctx.gpt_live_log):
|
| 798 |
yield update, None
|
| 799 |
reset_gpt_training_outputs(ctx)
|
| 800 |
+
yield push(logs, "已清理旧 GPT 断点与导出文件,避免恢复到不兼容 checkpoint。", ctx.gpt_live_log), None
|
| 801 |
config_path = create_gpt_config(ctx, epochs, batch_size, save_every_epoch)
|
| 802 |
env = build_process_env(ctx)
|
| 803 |
+
yield push(logs, f"开始 GPT 训练({ctx.spec.version})...", ctx.gpt_live_log), None
|
| 804 |
for line in run_cmd(
|
| 805 |
[sys.executable, "-s", "GPT_SoVITS/s1_train.py", "--config_file", str(config_path)],
|
| 806 |
cwd=GPT_SOVITS_DIR,
|
| 807 |
env=env,
|
| 808 |
):
|
| 809 |
+
yield push(logs, line, ctx.gpt_live_log), None
|
| 810 |
latest = latest_file(ctx.gpt_output_dir, ".ckpt")
|
| 811 |
if not latest:
|
| 812 |
raise RuntimeError("训练结束后没有找到导出的 GPT 权重文件")
|
| 813 |
+
yield push(logs, f"✅ GPT 训练完成,最新权重:{latest}", ctx.gpt_live_log), latest
|
| 814 |
except Exception as exc:
|
| 815 |
log.exception("start_gpt_training")
|
| 816 |
+
yield push(logs, f"❌ GPT 训练失败: {exc}", ctx.gpt_live_log), None
|
| 817 |
|
| 818 |
|
| 819 |
def refresh_outputs(version=DEFAULT_VERSION):
|
| 820 |
return artifacts_summary(version)
|
| 821 |
|
| 822 |
|
| 823 |
+
def live_logs(version=DEFAULT_VERSION):
|
| 824 |
+
ctx = get_version_context(version)
|
| 825 |
+
return (
|
| 826 |
+
read_live_log(ctx.prep_live_log),
|
| 827 |
+
read_live_log(ctx.sovits_live_log),
|
| 828 |
+
read_live_log(ctx.gpt_live_log),
|
| 829 |
+
)
|
| 830 |
+
|
| 831 |
+
|
| 832 |
+
def sync_live_state(version=DEFAULT_VERSION):
|
| 833 |
+
return (
|
| 834 |
+
*live_logs(version),
|
| 835 |
+
*refresh_outputs(version),
|
| 836 |
+
agent_status(version),
|
| 837 |
+
)
|
| 838 |
+
|
| 839 |
+
|
| 840 |
def version_markdown(version=DEFAULT_VERSION):
|
| 841 |
spec = get_version_spec(version)
|
| 842 |
return (
|
|
|
|
| 899 |
version = DEFAULT_VERSION
|
| 900 |
return (
|
| 901 |
version_markdown(version),
|
| 902 |
+
*sync_live_state(version),
|
|
|
|
| 903 |
)
|
| 904 |
|
| 905 |
|
|
|
|
| 913 |
"打开页面会自动加载当前输出。训练完成后,最新模型会直接出现在下载框里,"
|
| 914 |
"下面的“输出与目录”也会自动刷新,避免找不到文件。"
|
| 915 |
)
|
| 916 |
+
gr.Markdown("日志会同步写入 `/data`,页面每 2 秒自动轮询;即使刷新页面,也会把当前训练日志重新拉回来。")
|
| 917 |
version_select = gr.Dropdown(
|
| 918 |
choices=list(SUPPORTED_VERSIONS),
|
| 919 |
value=DEFAULT_VERSION,
|
|
|
|
| 979 |
)
|
| 980 |
agent_status_out = gr.JSON(label="Agent 状态", value=agent_status(DEFAULT_VERSION))
|
| 981 |
agent_status_btn = gr.Button("刷新 Agent 状态", variant="secondary")
|
| 982 |
+
sync_timer = gr.Timer(value=2, active=True)
|
| 983 |
+
refresh_api_btn = gr.Button(visible=False)
|
| 984 |
+
live_logs_api_btn = gr.Button(visible=False)
|
| 985 |
+
sync_state_api_btn = gr.Button(visible=False)
|
| 986 |
|
| 987 |
refresh_outputs_targets = [refresh_text, output_dirs, refresh_sovits, refresh_gpt, all_sovits, all_gpt]
|
| 988 |
+
sync_targets = [prep_out, sovits_log, gpt_log, *refresh_outputs_targets, agent_status_out]
|
| 989 |
env_btn.click(check_environment, inputs=[version_select], outputs=env_out, api_name="check_environment")
|
| 990 |
dataset_btn.click(download_dataset, inputs=[version_select], outputs=dataset_out, api_name="download_dataset")
|
| 991 |
prep_btn.click(prepare_data, inputs=[version_select], outputs=prep_out, api_name="prepare_data")
|
|
|
|
| 1001 |
outputs=[gpt_log, gpt_file],
|
| 1002 |
api_name="start_gpt_training",
|
| 1003 |
)
|
| 1004 |
+
refresh_btn.click(sync_live_state, inputs=[version_select], outputs=sync_targets, api_name=False)
|
| 1005 |
+
refresh_api_btn.click(refresh_outputs, inputs=[version_select], outputs=refresh_outputs_targets, api_name="refresh_outputs")
|
| 1006 |
+
live_logs_api_btn.click(live_logs, inputs=[version_select], outputs=[prep_out, sovits_log, gpt_log], api_name="live_logs")
|
| 1007 |
+
sync_state_api_btn.click(sync_live_state, inputs=[version_select], outputs=sync_targets, api_name="sync_live_state")
|
| 1008 |
agent_status_btn.click(agent_status, inputs=[version_select], outputs=agent_status_out, api_name="agent_status")
|
| 1009 |
version_select.change(version_markdown, inputs=[version_select], outputs=version_note, api_name=False)
|
| 1010 |
+
version_select.change(
|
| 1011 |
+
sync_live_state,
|
| 1012 |
+
inputs=[version_select],
|
| 1013 |
+
outputs=sync_targets,
|
| 1014 |
+
api_name=False,
|
| 1015 |
+
)
|
| 1016 |
+
sovits_event.then(
|
| 1017 |
+
sync_live_state,
|
| 1018 |
inputs=[version_select],
|
| 1019 |
+
outputs=sync_targets,
|
| 1020 |
api_name=False,
|
| 1021 |
)
|
| 1022 |
+
gpt_event.then(
|
| 1023 |
+
sync_live_state,
|
| 1024 |
inputs=[version_select],
|
| 1025 |
+
outputs=sync_targets,
|
| 1026 |
api_name=False,
|
| 1027 |
)
|
| 1028 |
+
sync_timer.tick(
|
| 1029 |
+
sync_live_state,
|
| 1030 |
inputs=[version_select],
|
| 1031 |
+
outputs=sync_targets,
|
| 1032 |
api_name=False,
|
| 1033 |
+
queue=False,
|
| 1034 |
+
show_progress="hidden",
|
| 1035 |
)
|
| 1036 |
demo.load(
|
| 1037 |
load_dashboard,
|
| 1038 |
+
outputs=[version_note, *sync_targets],
|
| 1039 |
api_name=False,
|
| 1040 |
)
|
| 1041 |
|