Codex commited on
Commit
b750161
·
1 Parent(s): c312a97

Show live training status and internal GPT logs

Browse files
Files changed (1) hide show
  1. app.py +219 -9
app.py CHANGED
@@ -15,6 +15,7 @@ import os
15
  import shutil
16
  import subprocess
17
  import sys
 
18
  import time
19
  from dataclasses import dataclass
20
  from datetime import datetime
@@ -66,7 +67,7 @@ MODEL_PATTERNS = [
66
  "*.safetensors",
67
  "*.model",
68
  ]
69
- UPSTREAM_PATCH_VERSION = "2026-05-25-worker0-v2"
70
 
71
  logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
72
  log = logging.getLogger(__name__)
@@ -102,6 +103,10 @@ class VersionContext:
102
  prep_live_log: Path
103
  sovits_live_log: Path
104
  gpt_live_log: Path
 
 
 
 
105
  sovits_ckpt_dir: Path
106
  gpt_log_dir: Path
107
  sovits_output_dir: Path
@@ -172,6 +177,10 @@ def get_version_context(version: str):
172
  prep_live_log=exp_dir / "_live_prepare.log",
173
  sovits_live_log=exp_dir / "_live_sovits.log",
174
  gpt_live_log=exp_dir / "_live_gpt.log",
 
 
 
 
175
  sovits_ckpt_dir=exp_dir / f"logs_s2_{version}",
176
  gpt_log_dir=exp_dir / f"logs_s1_{version}",
177
  sovits_output_dir=OUTPUT_ROOT / f"SoVITS_weights_{version}",
@@ -222,11 +231,102 @@ def clear_live_log(path: Path):
222
  path.write_text("", encoding="utf-8")
223
 
224
 
225
- def read_live_log(path: Path):
 
 
 
 
 
 
226
  if not path.exists():
227
  return ""
228
  lines = path.read_text(encoding="utf-8", errors="ignore").splitlines()
229
- return "\n".join(lines[-200:])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
 
231
 
232
  def push(logs, message, live_path: Path | None = None):
@@ -236,7 +336,7 @@ def push(logs, message, live_path: Path | None = None):
236
  return "\n".join(logs[-200:])
237
 
238
 
239
- def run_cmd(command, cwd=None, env=None):
240
  proc = subprocess.Popen(
241
  command,
242
  cwd=str(cwd) if cwd else None,
@@ -246,14 +346,82 @@ def run_cmd(command, cwd=None, env=None):
246
  text=True,
247
  bufsize=1,
248
  )
249
- yield f"$ {' '.join(command)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
  for raw in proc.stdout:
251
  line = raw.rstrip()
252
  if line:
 
 
 
253
  yield line
254
  code = proc.wait()
 
 
 
255
  if code != 0:
256
- raise RuntimeError(f"命令失败 (exit={code}): {' '.join(command)}")
257
 
258
 
259
  def python_script_command(script_path, *script_args, file_system_sharing=False):
@@ -387,8 +555,12 @@ def managed_single_paths(ctx: VersionContext):
387
  ("文本特征总表", ctx.text_path),
388
  ("语义特征总表", ctx.semantic_path),
389
  ("预处理实时日志", ctx.prep_live_log),
 
390
  ("SoVITS 实时日志", ctx.sovits_live_log),
 
391
  ("GPT 实时日志", ctx.gpt_live_log),
 
 
392
  ]
393
 
394
 
@@ -486,8 +658,12 @@ def allowed_delete_roots(ctx: VersionContext):
486
  ctx.text_path,
487
  ctx.semantic_path,
488
  ctx.prep_live_log,
 
489
  ctx.sovits_live_log,
 
490
  ctx.gpt_live_log,
 
 
491
  ]
492
 
493
 
@@ -525,6 +701,7 @@ def cleanup_preprocess(version=DEFAULT_VERSION, selected_path=None):
525
  ctx = get_version_context(version)
526
  reset_preprocess_outputs(ctx)
527
  clear_live_log(ctx.prep_live_log)
 
528
  status = f"已清理 {ctx.spec.version} 的预处理产物。"
529
  return manager_action_result(version, status, selected_path)
530
 
@@ -550,6 +727,9 @@ def cleanup_live_logs(version=DEFAULT_VERSION, selected_path=None):
550
  clear_live_log(ctx.prep_live_log)
551
  clear_live_log(ctx.sovits_live_log)
552
  clear_live_log(ctx.gpt_live_log)
 
 
 
553
  status = f"已清理 {ctx.spec.version} 的实时日志。"
554
  return manager_action_result(version, status, selected_path)
555
 
@@ -739,6 +919,16 @@ class Text2SemanticDataModule(LightningDataModule):
739
  """,
740
  encoding="utf-8",
741
  )
 
 
 
 
 
 
 
 
 
 
742
  patch_marker.write_text(f"{UPSTREAM_PATCH_VERSION}\n", encoding="utf-8")
743
 
744
 
@@ -1021,6 +1211,7 @@ def check_environment(version=DEFAULT_VERSION):
1021
  logs = []
1022
  ctx = get_version_context(version)
1023
  clear_live_log(ctx.prep_live_log)
 
1024
  try:
1025
  final = None
1026
  final = yield from setup_environment_steps(logs, version, ctx.prep_live_log)
@@ -1035,6 +1226,7 @@ def download_dataset(version=DEFAULT_VERSION):
1035
  logs = []
1036
  ctx = get_version_context(version)
1037
  clear_live_log(ctx.prep_live_log)
 
1038
  try:
1039
  final = None
1040
  final = yield from download_dataset_steps(logs, version, ctx.prep_live_log)
@@ -1049,6 +1241,7 @@ def prepare_data(version=DEFAULT_VERSION):
1049
  logs = []
1050
  ctx = get_version_context(version)
1051
  clear_live_log(ctx.prep_live_log)
 
1052
  try:
1053
  final = yield from prepare_data_steps(logs, version, ctx.prep_live_log)
1054
  yield push(logs, final, ctx.prep_live_log)
@@ -1061,6 +1254,7 @@ def start_training(version=DEFAULT_VERSION, epochs=2, batch_size=1, save_every_e
1061
  logs = []
1062
  ctx = get_version_context(version)
1063
  clear_live_log(ctx.sovits_live_log)
 
1064
  try:
1065
  yield push(logs, f"当前版本:{ctx.spec.version}({ctx.spec.stability})", ctx.sovits_live_log), None
1066
  for update in setup_environment_steps(logs, version, ctx.sovits_live_log):
@@ -1081,6 +1275,9 @@ def start_training(version=DEFAULT_VERSION, epochs=2, batch_size=1, save_every_e
1081
  ),
1082
  cwd=GPT_SOVITS_DIR,
1083
  env=env,
 
 
 
1084
  ):
1085
  yield push(logs, line, ctx.sovits_live_log), None
1086
  latest = latest_file(ctx.sovits_output_dir, ".pth")
@@ -1096,6 +1293,7 @@ def start_gpt_training(version=DEFAULT_VERSION, epochs=1, batch_size=1, save_eve
1096
  logs = []
1097
  ctx = get_version_context(version)
1098
  clear_live_log(ctx.gpt_live_log)
 
1099
  try:
1100
  yield push(logs, f"当前版本:{ctx.spec.version}({ctx.spec.stability})", ctx.gpt_live_log), None
1101
  for update in setup_environment_steps(logs, version, ctx.gpt_live_log):
@@ -1118,6 +1316,9 @@ def start_gpt_training(version=DEFAULT_VERSION, epochs=1, batch_size=1, save_eve
1118
  ),
1119
  cwd=GPT_SOVITS_DIR,
1120
  env=env,
 
 
 
1121
  ):
1122
  yield push(logs, line, ctx.gpt_live_log), None
1123
  latest = latest_file(ctx.gpt_output_dir, ".ckpt")
@@ -1136,9 +1337,13 @@ def refresh_outputs(version=DEFAULT_VERSION):
1136
  def live_logs(version=DEFAULT_VERSION):
1137
  ctx = get_version_context(version)
1138
  return (
1139
- read_live_log(ctx.prep_live_log),
1140
- read_live_log(ctx.sovits_live_log),
1141
- read_live_log(ctx.gpt_live_log),
 
 
 
 
1142
  )
1143
 
1144
 
@@ -1205,6 +1410,11 @@ def agent_status(version=DEFAULT_VERSION):
1205
  "sovits": [file_record(path) for path in sovits_files[:10]],
1206
  "gpt": [file_record(path) for path in gpt_files[:10]],
1207
  },
 
 
 
 
 
1208
  }
1209
 
1210
 
 
15
  import shutil
16
  import subprocess
17
  import sys
18
+ import threading
19
  import time
20
  from dataclasses import dataclass
21
  from datetime import datetime
 
67
  "*.safetensors",
68
  "*.model",
69
  ]
70
+ UPSTREAM_PATCH_VERSION = "2026-05-25-worker0-v3"
71
 
72
  logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
73
  log = logging.getLogger(__name__)
 
103
  prep_live_log: Path
104
  sovits_live_log: Path
105
  gpt_live_log: Path
106
+ prep_status_path: Path
107
+ sovits_status_path: Path
108
+ gpt_status_path: Path
109
+ gpt_train_log: Path
110
  sovits_ckpt_dir: Path
111
  gpt_log_dir: Path
112
  sovits_output_dir: Path
 
177
  prep_live_log=exp_dir / "_live_prepare.log",
178
  sovits_live_log=exp_dir / "_live_sovits.log",
179
  gpt_live_log=exp_dir / "_live_gpt.log",
180
+ prep_status_path=exp_dir / "_status_prepare.json",
181
+ sovits_status_path=exp_dir / "_status_sovits.json",
182
+ gpt_status_path=exp_dir / "_status_gpt.json",
183
+ gpt_train_log=exp_dir / "train.log",
184
  sovits_ckpt_dir=exp_dir / f"logs_s2_{version}",
185
  gpt_log_dir=exp_dir / f"logs_s1_{version}",
186
  sovits_output_dir=OUTPUT_ROOT / f"SoVITS_weights_{version}",
 
231
  path.write_text("", encoding="utf-8")
232
 
233
 
234
+ def clear_status_file(path: Path):
235
+ path.parent.mkdir(parents=True, exist_ok=True)
236
+ if path.exists():
237
+ path.unlink()
238
+
239
+
240
+ def read_text_tail(path: Path, max_lines=200):
241
  if not path.exists():
242
  return ""
243
  lines = path.read_text(encoding="utf-8", errors="ignore").splitlines()
244
+ return "\n".join(lines[-max_lines:])
245
+
246
+
247
+ def read_live_log(path: Path):
248
+ return read_text_tail(path, max_lines=200)
249
+
250
+
251
+ def write_status_file(path: Path, payload):
252
+ path.parent.mkdir(parents=True, exist_ok=True)
253
+ tmp_path = path.with_suffix(path.suffix + ".tmp")
254
+ tmp_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
255
+ tmp_path.replace(path)
256
+
257
+
258
+ def read_status_file(path: Path):
259
+ if not path.exists():
260
+ return None
261
+ try:
262
+ return json.loads(path.read_text(encoding="utf-8"))
263
+ except Exception:
264
+ return None
265
+
266
+
267
+ def now_iso():
268
+ return datetime.now().isoformat(timespec="seconds")
269
+
270
+
271
+ def process_metrics(pid: int, previous=None):
272
+ stat_path = Path(f"/proc/{pid}/stat")
273
+ statm_path = Path(f"/proc/{pid}/statm")
274
+ if not stat_path.exists() or not statm_path.exists():
275
+ return {"alive": False, "cpu_percent": 0.0, "rss_mb": 0.0, "cpu_time_seconds": 0.0}
276
+ stat_fields = stat_path.read_text(encoding="utf-8").split()
277
+ cpu_ticks = int(stat_fields[13]) + int(stat_fields[14])
278
+ clock_ticks = os.sysconf(os.sysconf_names["SC_CLK_TCK"])
279
+ cpu_time_seconds = cpu_ticks / float(clock_ticks)
280
+ rss_pages = int(statm_path.read_text(encoding="utf-8").split()[1])
281
+ page_size = os.sysconf("SC_PAGE_SIZE")
282
+ rss_mb = rss_pages * page_size / (1024 * 1024)
283
+ cpu_percent = 0.0
284
+ if previous is not None:
285
+ prev_ts, prev_cpu_seconds = previous
286
+ elapsed = max(time.time() - prev_ts, 1e-6)
287
+ cpu_percent = max(0.0, ((cpu_time_seconds - prev_cpu_seconds) / elapsed) * 100.0)
288
+ return {
289
+ "alive": True,
290
+ "cpu_percent": round(cpu_percent, 2),
291
+ "rss_mb": round(rss_mb, 2),
292
+ "cpu_time_seconds": round(cpu_time_seconds, 2),
293
+ }
294
+
295
+
296
+ def status_summary(status):
297
+ if not status:
298
+ return ""
299
+ parts = [
300
+ f"阶段: {status.get('state', 'unknown')}",
301
+ f"更新时间: {status.get('updated_at', '-')}",
302
+ ]
303
+ if status.get("pid") is not None:
304
+ parts.append(f"pid={status['pid']}")
305
+ if status.get("cpu_percent") is not None:
306
+ parts.append(f"cpu={status['cpu_percent']}%")
307
+ if status.get("rss_mb") is not None:
308
+ parts.append(f"rss={status['rss_mb']} MB")
309
+ if status.get("silent_for_seconds") is not None:
310
+ parts.append(f"静默={status['silent_for_seconds']}s")
311
+ if status.get("last_output_line"):
312
+ parts.append(f"最后输出: {status['last_output_line'][:160]}")
313
+ return "[状态]\n" + " | ".join(parts)
314
+
315
+
316
+ def combined_log_text(primary_path: Path, status_path: Path | None = None, extra_paths=None):
317
+ sections = []
318
+ status = read_status_file(status_path) if status_path else None
319
+ summary = status_summary(status)
320
+ if summary:
321
+ sections.append(summary)
322
+ primary = read_text_tail(primary_path, max_lines=200)
323
+ if primary:
324
+ sections.append(primary)
325
+ for label, path in extra_paths or []:
326
+ content = read_text_tail(path, max_lines=120)
327
+ if content:
328
+ sections.append(f"[{label}]\n{content}")
329
+ return "\n\n".join(section for section in sections if section)
330
 
331
 
332
  def push(logs, message, live_path: Path | None = None):
 
336
  return "\n".join(logs[-200:])
337
 
338
 
339
+ def run_cmd(command, cwd=None, env=None, status_path: Path | None = None, live_path: Path | None = None, status_label=None):
340
  proc = subprocess.Popen(
341
  command,
342
  cwd=str(cwd) if cwd else None,
 
346
  text=True,
347
  bufsize=1,
348
  )
349
+ command_text = " ".join(command)
350
+ shared_state = {
351
+ "started_at": now_iso(),
352
+ "last_output_at_ts": time.time(),
353
+ "last_output_line": f"$ {command_text}",
354
+ "last_heartbeat_at_ts": 0.0,
355
+ }
356
+ stop_event = threading.Event()
357
+
358
+ def write_runtime_status(state, exit_code=None):
359
+ if status_path is None:
360
+ return
361
+ payload = {
362
+ "label": status_label or "任务",
363
+ "state": state,
364
+ "pid": proc.pid,
365
+ "command": command_text,
366
+ "started_at": shared_state["started_at"],
367
+ "updated_at": now_iso(),
368
+ "last_output_at": datetime.fromtimestamp(shared_state["last_output_at_ts"]).isoformat(timespec="seconds"),
369
+ "last_output_line": shared_state["last_output_line"],
370
+ "silent_for_seconds": round(max(time.time() - shared_state["last_output_at_ts"], 0.0), 1),
371
+ "exit_code": exit_code,
372
+ }
373
+ payload.update(process_metrics(proc.pid))
374
+ write_status_file(status_path, payload)
375
+
376
+ def monitor_process():
377
+ previous = None
378
+ while not stop_event.is_set() and proc.poll() is None:
379
+ metrics = process_metrics(proc.pid, previous)
380
+ previous = (time.time(), metrics["cpu_time_seconds"])
381
+ if status_path is not None:
382
+ payload = {
383
+ "label": status_label or "任务",
384
+ "state": "running",
385
+ "pid": proc.pid,
386
+ "command": command_text,
387
+ "started_at": shared_state["started_at"],
388
+ "updated_at": now_iso(),
389
+ "last_output_at": datetime.fromtimestamp(shared_state["last_output_at_ts"]).isoformat(timespec="seconds"),
390
+ "last_output_line": shared_state["last_output_line"],
391
+ "silent_for_seconds": round(max(time.time() - shared_state["last_output_at_ts"], 0.0), 1),
392
+ "exit_code": None,
393
+ }
394
+ payload.update(metrics)
395
+ write_status_file(status_path, payload)
396
+ silent_for = time.time() - shared_state["last_output_at_ts"]
397
+ if live_path is not None and silent_for >= 30 and time.time() - shared_state["last_heartbeat_at_ts"] >= 30:
398
+ append_live_log(
399
+ live_path,
400
+ (
401
+ f"[状态] {status_label or '任务'} 仍在运行 | pid={proc.pid} | "
402
+ f"cpu={metrics['cpu_percent']}% | rss={metrics['rss_mb']} MB | 静默 {int(silent_for)}s"
403
+ ),
404
+ )
405
+ shared_state["last_heartbeat_at_ts"] = time.time()
406
+ stop_event.wait(10)
407
+
408
+ write_runtime_status("starting")
409
+ monitor_thread = threading.Thread(target=monitor_process, daemon=True)
410
+ monitor_thread.start()
411
+ yield f"$ {command_text}"
412
  for raw in proc.stdout:
413
  line = raw.rstrip()
414
  if line:
415
+ shared_state["last_output_at_ts"] = time.time()
416
+ shared_state["last_output_line"] = line
417
+ write_runtime_status("running")
418
  yield line
419
  code = proc.wait()
420
+ stop_event.set()
421
+ monitor_thread.join(timeout=1)
422
+ write_runtime_status("completed" if code == 0 else "failed", exit_code=code)
423
  if code != 0:
424
+ raise RuntimeError(f"命令失败 (exit={code}): {command_text}")
425
 
426
 
427
  def python_script_command(script_path, *script_args, file_system_sharing=False):
 
555
  ("文本特征总表", ctx.text_path),
556
  ("语义特征总表", ctx.semantic_path),
557
  ("预处理实时日志", ctx.prep_live_log),
558
+ ("预处理状态", ctx.prep_status_path),
559
  ("SoVITS 实时日志", ctx.sovits_live_log),
560
+ ("SoVITS 状态", ctx.sovits_status_path),
561
  ("GPT 实时日志", ctx.gpt_live_log),
562
+ ("GPT 状态", ctx.gpt_status_path),
563
+ ("GPT train.log", ctx.gpt_train_log),
564
  ]
565
 
566
 
 
658
  ctx.text_path,
659
  ctx.semantic_path,
660
  ctx.prep_live_log,
661
+ ctx.prep_status_path,
662
  ctx.sovits_live_log,
663
+ ctx.sovits_status_path,
664
  ctx.gpt_live_log,
665
+ ctx.gpt_status_path,
666
+ ctx.gpt_train_log,
667
  ]
668
 
669
 
 
701
  ctx = get_version_context(version)
702
  reset_preprocess_outputs(ctx)
703
  clear_live_log(ctx.prep_live_log)
704
+ clear_status_file(ctx.prep_status_path)
705
  status = f"已清理 {ctx.spec.version} 的预处理产物。"
706
  return manager_action_result(version, status, selected_path)
707
 
 
727
  clear_live_log(ctx.prep_live_log)
728
  clear_live_log(ctx.sovits_live_log)
729
  clear_live_log(ctx.gpt_live_log)
730
+ clear_status_file(ctx.prep_status_path)
731
+ clear_status_file(ctx.sovits_status_path)
732
+ clear_status_file(ctx.gpt_status_path)
733
  status = f"已清理 {ctx.spec.version} 的实时日志。"
734
  return manager_action_result(version, status, selected_path)
735
 
 
919
  """,
920
  encoding="utf-8",
921
  )
922
+ s1_train = GPT_SOVITS_DIR / "GPT_SoVITS" / "s1_train.py"
923
+ s1_train_content = s1_train.read_text(encoding="utf-8")
924
+ trainer_anchor = " callbacks=[ckpt_callback],\n use_distributed_sampler=False,"
925
+ if "log_every_n_steps=1" not in s1_train_content and trainer_anchor in s1_train_content:
926
+ s1_train_content = s1_train_content.replace(
927
+ trainer_anchor,
928
+ " callbacks=[ckpt_callback],\n log_every_n_steps=1,\n use_distributed_sampler=False,",
929
+ 1,
930
+ )
931
+ s1_train.write_text(s1_train_content, encoding="utf-8")
932
  patch_marker.write_text(f"{UPSTREAM_PATCH_VERSION}\n", encoding="utf-8")
933
 
934
 
 
1211
  logs = []
1212
  ctx = get_version_context(version)
1213
  clear_live_log(ctx.prep_live_log)
1214
+ clear_status_file(ctx.prep_status_path)
1215
  try:
1216
  final = None
1217
  final = yield from setup_environment_steps(logs, version, ctx.prep_live_log)
 
1226
  logs = []
1227
  ctx = get_version_context(version)
1228
  clear_live_log(ctx.prep_live_log)
1229
+ clear_status_file(ctx.prep_status_path)
1230
  try:
1231
  final = None
1232
  final = yield from download_dataset_steps(logs, version, ctx.prep_live_log)
 
1241
  logs = []
1242
  ctx = get_version_context(version)
1243
  clear_live_log(ctx.prep_live_log)
1244
+ clear_status_file(ctx.prep_status_path)
1245
  try:
1246
  final = yield from prepare_data_steps(logs, version, ctx.prep_live_log)
1247
  yield push(logs, final, ctx.prep_live_log)
 
1254
  logs = []
1255
  ctx = get_version_context(version)
1256
  clear_live_log(ctx.sovits_live_log)
1257
+ clear_status_file(ctx.sovits_status_path)
1258
  try:
1259
  yield push(logs, f"当前版本:{ctx.spec.version}({ctx.spec.stability})", ctx.sovits_live_log), None
1260
  for update in setup_environment_steps(logs, version, ctx.sovits_live_log):
 
1275
  ),
1276
  cwd=GPT_SOVITS_DIR,
1277
  env=env,
1278
+ status_path=ctx.sovits_status_path,
1279
+ live_path=ctx.sovits_live_log,
1280
+ status_label=f"{ctx.spec.version} SoVITS",
1281
  ):
1282
  yield push(logs, line, ctx.sovits_live_log), None
1283
  latest = latest_file(ctx.sovits_output_dir, ".pth")
 
1293
  logs = []
1294
  ctx = get_version_context(version)
1295
  clear_live_log(ctx.gpt_live_log)
1296
+ clear_status_file(ctx.gpt_status_path)
1297
  try:
1298
  yield push(logs, f"当前版本:{ctx.spec.version}({ctx.spec.stability})", ctx.gpt_live_log), None
1299
  for update in setup_environment_steps(logs, version, ctx.gpt_live_log):
 
1316
  ),
1317
  cwd=GPT_SOVITS_DIR,
1318
  env=env,
1319
+ status_path=ctx.gpt_status_path,
1320
+ live_path=ctx.gpt_live_log,
1321
+ status_label=f"{ctx.spec.version} GPT",
1322
  ):
1323
  yield push(logs, line, ctx.gpt_live_log), None
1324
  latest = latest_file(ctx.gpt_output_dir, ".ckpt")
 
1337
  def live_logs(version=DEFAULT_VERSION):
1338
  ctx = get_version_context(version)
1339
  return (
1340
+ combined_log_text(ctx.prep_live_log, ctx.prep_status_path),
1341
+ combined_log_text(ctx.sovits_live_log, ctx.sovits_status_path),
1342
+ combined_log_text(
1343
+ ctx.gpt_live_log,
1344
+ ctx.gpt_status_path,
1345
+ extra_paths=[("train.log", ctx.gpt_train_log)],
1346
+ ),
1347
  )
1348
 
1349
 
 
1410
  "sovits": [file_record(path) for path in sovits_files[:10]],
1411
  "gpt": [file_record(path) for path in gpt_files[:10]],
1412
  },
1413
+ "tasks": {
1414
+ "prepare": read_status_file(ctx.prep_status_path),
1415
+ "sovits": read_status_file(ctx.sovits_status_path),
1416
+ "gpt": read_status_file(ctx.gpt_status_path),
1417
+ },
1418
  }
1419
 
1420