Codex commited on
Commit
444221d
·
1 Parent(s): 239a98e

Persist and poll live training logs

Browse files
Files changed (1) hide show
  1. app.py +134 -63
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 push(logs, message):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- wait_logged = False
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
- if not wait_logged:
629
- yield push(logs, f"{ctx.spec.version} 预处理中,等待已有任务完成...")
630
- wait_logged = True
 
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
- *refresh_outputs(version),
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(refresh_outputs, inputs=[version_select], outputs=refresh_outputs_targets, api_name="refresh_outputs")
 
 
 
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(refresh_outputs, inputs=[version_select], outputs=refresh_outputs_targets, api_name=False).then(
948
- agent_status,
 
 
 
 
 
 
949
  inputs=[version_select],
950
- outputs=agent_status_out,
951
  api_name=False,
952
  )
953
- sovits_event.then(refresh_outputs, inputs=[version_select], outputs=refresh_outputs_targets, api_name=False).then(
954
- agent_status,
955
  inputs=[version_select],
956
- outputs=agent_status_out,
957
  api_name=False,
958
  )
959
- gpt_event.then(refresh_outputs, inputs=[version_select], outputs=refresh_outputs_targets, api_name=False).then(
960
- agent_status,
961
  inputs=[version_select],
962
- outputs=agent_status_out,
963
  api_name=False,
 
 
964
  )
965
  demo.load(
966
  load_dashboard,
967
- outputs=[version_note, *refresh_outputs_targets, agent_status_out],
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