YanTianlong commited on
Commit
b39ef30
·
1 Parent(s): 53b61f6

Add ASR diagnostic workflow and deployment guide

Browse files
app.py CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
2
 
3
  import json
4
  import math
 
5
  import subprocess
6
  import sys
7
  import time
@@ -196,6 +197,17 @@ def history_summary(history: dict[str, Any]) -> list[str]:
196
  if output_files:
197
  lines.append("outputs:")
198
  lines.extend(output_files)
 
 
 
 
 
 
 
 
 
 
 
199
  return lines
200
 
201
 
@@ -269,6 +281,66 @@ def voxcpm_tts_workflow(prefix: str) -> dict[str, Any]:
269
  }
270
 
271
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  def prepare_runtime() -> str:
273
  global PREPARE_PROCESS
274
 
@@ -278,7 +350,7 @@ def prepare_runtime() -> str:
278
  return "\n".join(lines)
279
  BOOTSTRAP_LOG.parent.mkdir(parents=True, exist_ok=True)
280
  log = BOOTSTRAP_LOG.open("ab")
281
- command = [sys.executable, str(ROOT / "scripts" / "bootstrap_comfy.py")]
282
  PREPARE_PROCESS = subprocess.Popen(
283
  command,
284
  cwd=ROOT,
@@ -379,8 +451,33 @@ def voxcpm_tts_gpu_test() -> str:
379
  return "\n".join(lines)
380
 
381
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
  with gr.Blocks(title="VoiceGate") as demo:
383
  gr.Markdown("# VoiceGate")
 
384
  with gr.Row():
385
  prepare_run = gr.Button("Prepare")
386
  prepare_status_run = gr.Button("Prepare Status")
@@ -388,6 +485,7 @@ with gr.Blocks(title="VoiceGate") as demo:
388
  comfy_run = gr.Button("ComfyUI")
389
  melband_run = gr.Button("MelBand")
390
  voxcpm_run = gr.Button("VoxCPM TTS")
 
391
  output = gr.Textbox(label="Status", lines=28)
392
  prepare_run.click(fn=prepare_runtime, outputs=output)
393
  prepare_status_run.click(fn=prepare_status, outputs=output)
@@ -395,6 +493,7 @@ with gr.Blocks(title="VoiceGate") as demo:
395
  comfy_run.click(fn=comfy_runtime_test, outputs=output)
396
  melband_run.click(fn=melband_gpu_test, outputs=output)
397
  voxcpm_run.click(fn=voxcpm_tts_gpu_test, outputs=output)
 
398
 
399
 
400
  if __name__ == "__main__":
 
2
 
3
  import json
4
  import math
5
+ import shutil
6
  import subprocess
7
  import sys
8
  import time
 
197
  if output_files:
198
  lines.append("outputs:")
199
  lines.extend(output_files)
200
+ text_outputs = []
201
+ for node_output in outputs.values():
202
+ for key in ("text", "string"):
203
+ values = node_output.get(key, []) or []
204
+ if isinstance(values, str):
205
+ values = [values]
206
+ text_outputs.extend(str(value) for value in values)
207
+ if text_outputs:
208
+ lines.append("text_outputs:")
209
+ for value in text_outputs:
210
+ lines.append(value[:2000])
211
  return lines
212
 
213
 
 
281
  }
282
 
283
 
284
+ def copy_audio_to_comfy_input(audio_path: str | Path, prefix: str) -> str:
285
+ source = Path(audio_path)
286
+ if not source.exists():
287
+ raise FileNotFoundError(f"Uploaded audio does not exist: {source}")
288
+ suffix = source.suffix or ".wav"
289
+ filename = f"{prefix}_{uuid.uuid4().hex[:8]}{suffix}"
290
+ COMFY_INPUT_DIR.mkdir(parents=True, exist_ok=True)
291
+ shutil.copyfile(source, COMFY_INPUT_DIR / filename)
292
+ return filename
293
+
294
+
295
+ def asr_workflow(audio_filename: str, prefix: str) -> dict[str, Any]:
296
+ return {
297
+ "1": {
298
+ "class_type": "LoadAudio",
299
+ "inputs": {"audio": audio_filename, "audioUI": ""},
300
+ },
301
+ "2": {
302
+ "class_type": "VoiceBridgeASRLoader",
303
+ "inputs": {
304
+ "repo_id": "Qwen/Qwen3-ASR-1.7B",
305
+ "source": "HuggingFace",
306
+ "precision": "bf16",
307
+ "attention": "sdpa",
308
+ "max_new_tokens": 256,
309
+ "forced_aligner": "Qwen/Qwen3-ForcedAligner-0.6B",
310
+ "local_model_path_asr": "",
311
+ "local_model_path_fa": "",
312
+ },
313
+ },
314
+ "3": {
315
+ "class_type": "VoiceBridgeASRTranscribe",
316
+ "inputs": {
317
+ "model_key": ["2", 0],
318
+ "audio": ["1", 0],
319
+ "language": "auto",
320
+ "context": "",
321
+ "return_timestamps": True,
322
+ },
323
+ },
324
+ "4": {
325
+ "class_type": "GenerateSRT",
326
+ "inputs": {
327
+ "forced_aligns": ["3", 0],
328
+ "text": ["3", 1],
329
+ "language": ["3", 2],
330
+ "save_srt": True,
331
+ "filename_prefix": f"VoiceBridge/{prefix}",
332
+ },
333
+ },
334
+ "5": {
335
+ "class_type": "easy showAnything",
336
+ "inputs": {
337
+ "text": "",
338
+ "anything": ["4", 0],
339
+ },
340
+ },
341
+ }
342
+
343
+
344
  def prepare_runtime() -> str:
345
  global PREPARE_PROCESS
346
 
 
350
  return "\n".join(lines)
351
  BOOTSTRAP_LOG.parent.mkdir(parents=True, exist_ok=True)
352
  log = BOOTSTRAP_LOG.open("ab")
353
+ command = [sys.executable, str(ROOT / "scripts" / "bootstrap_comfy.py"), "--with-models"]
354
  PREPARE_PROCESS = subprocess.Popen(
355
  command,
356
  cwd=ROOT,
 
451
  return "\n".join(lines)
452
 
453
 
454
+ @spaces.GPU(duration=1800)
455
+ def asr_gpu_test(audio_path: str | None) -> str:
456
+ lines = gpu_status_lines()
457
+ started = time.time()
458
+ try:
459
+ if not audio_path:
460
+ raise ValueError("Please upload an audio file before running ASR.")
461
+ ensure_comfy(lines)
462
+ prefix = f"asr_gpu_{uuid.uuid4().hex[:8]}"
463
+ audio_filename = copy_audio_to_comfy_input(audio_path, prefix)
464
+ lines.append(f"input_audio={audio_filename}")
465
+ prompt_id = submit_prompt(asr_workflow(audio_filename, prefix))
466
+ lines.append(f"prompt_id={prompt_id}")
467
+ history = wait_for_history(prompt_id, timeout=1800)
468
+ lines.extend(history_summary(history))
469
+ lines.append(f"elapsed_sec={time.time() - started:.1f}")
470
+ except Exception as exc:
471
+ lines.append(f"error={type(exc).__name__}: {exc}")
472
+ if COMFY_LOG.exists():
473
+ lines.append("comfy_log_tail:")
474
+ lines.extend(COMFY_LOG.read_text(encoding="utf-8", errors="replace").splitlines()[-180:])
475
+ return "\n".join(lines)
476
+
477
+
478
  with gr.Blocks(title="VoiceGate") as demo:
479
  gr.Markdown("# VoiceGate")
480
+ asr_audio = gr.Audio(label="ASR test audio", type="filepath")
481
  with gr.Row():
482
  prepare_run = gr.Button("Prepare")
483
  prepare_status_run = gr.Button("Prepare Status")
 
485
  comfy_run = gr.Button("ComfyUI")
486
  melband_run = gr.Button("MelBand")
487
  voxcpm_run = gr.Button("VoxCPM TTS")
488
+ asr_run = gr.Button("ASR")
489
  output = gr.Textbox(label="Status", lines=28)
490
  prepare_run.click(fn=prepare_runtime, outputs=output)
491
  prepare_status_run.click(fn=prepare_status, outputs=output)
 
493
  comfy_run.click(fn=comfy_runtime_test, outputs=output)
494
  melband_run.click(fn=melband_gpu_test, outputs=output)
495
  voxcpm_run.click(fn=voxcpm_tts_gpu_test, outputs=output)
496
+ asr_run.click(fn=asr_gpu_test, inputs=asr_audio, outputs=output)
497
 
498
 
499
  if __name__ == "__main__":
docs/hf-space-comfyui-step-by-step.zh.md ADDED
@@ -0,0 +1,372 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 从零部署 ComfyUI Workflow 到 Hugging Face Space 的操作指南
2
+
3
+ 本文是 VoiceGate 部署过程沉淀出来的通用指南。目标是:从一个本地
4
+ ComfyUI workflow 出发,快速构建一个 Hugging Face Space,用 Gradio 做
5
+ 前端,用 ComfyUI 做后端,并尽量避开已经踩过的坑。
6
+
7
+ ## 1. 先确定部署形态
8
+
9
+ 推荐形态:
10
+
11
+ - Space SDK:Gradio。
12
+ - GPU:ZeroGPU,适合按需调用。
13
+ - ComfyUI:不要直接提交整个 ComfyUI 目录到 git。
14
+ - Custom nodes:用 bootstrap 脚本按 pinned commit 拉取。
15
+ - 大模型:放在 Space persistent storage,例如 `/data/project_models`。
16
+ - Secrets:只放 Hugging Face Space Secrets,不写进代码或 workflow JSON。
17
+
18
+ 不推荐:
19
+
20
+ - 把模型权重提交到 Space git。
21
+ - 把 API key 写进 workflow。
22
+ - 依赖 Dev Mode 里的手动修改作为最终状态。
23
+ - 假设 SSH 能看到 ZeroGPU CUDA。
24
+
25
+ ## 2. 创建 Space
26
+
27
+ 在 Hugging Face 创建 Space:
28
+
29
+ - SDK 选择 `Gradio`。
30
+ - Hardware 选择 ZeroGPU,例如 `zero-a10g`。
31
+ - 如果需要持久化模型,开启 persistent storage。
32
+ - 如果 Space 是组织名下项目,确认 token 有该组织的 Space 写权限。
33
+
34
+ 本地登录:
35
+
36
+ ```powershell
37
+ huggingface-cli login
38
+ huggingface-cli whoami
39
+ ```
40
+
41
+ 添加 Space git remote:
42
+
43
+ ```powershell
44
+ git remote add space https://huggingface.co/spaces/<org>/<space-name>
45
+ git push space main
46
+ ```
47
+
48
+ ## 3. 推荐目录结构
49
+
50
+ ```text
51
+ project-hf/
52
+ |-- app.py
53
+ |-- README.md
54
+ |-- requirements.txt
55
+ |-- packages.txt
56
+ |-- scripts/
57
+ | |-- bootstrap_comfy.py
58
+ | |-- run_comfy.py
59
+ | `-- workflow_client.py
60
+ |-- workflows/
61
+ | |-- workflow_api.json
62
+ | `-- workflow_ui.json
63
+ |-- docs/
64
+ | |-- deployment-plan.md
65
+ | |-- dependency-inventory.md
66
+ | |-- ssh-runbook.md
67
+ | |-- todo.md
68
+ | `-- work-log.md
69
+ `-- assets/
70
+ ```
71
+
72
+ 本地可以保留上游项目 checkout,例如 `VoiceGate/`,但要加入 `.gitignore`。
73
+ 它只作为参考,不作为 Space runtime 内容。
74
+
75
+ ## 4. 先做 workflow 和依赖盘点
76
+
77
+ 从 ComfyUI 导出两个文件:
78
+
79
+ - API workflow:用于 `/prompt` 调用。
80
+ - UI workflow:用于人工参考节点布局。
81
+
82
+ 检查 API workflow:
83
+
84
+ ```powershell
85
+ python -m json.tool workflows\workflow_api.json > $null
86
+ ```
87
+
88
+ 列出所有 `class_type`:
89
+
90
+ ```powershell
91
+ python -c "import json; w=json.load(open('workflows/workflow_api.json',encoding='utf-8')); print('\n'.join(sorted({v['class_type'] for v in w.values()})))"
92
+ ```
93
+
94
+ 为每个 custom node 找到:
95
+
96
+ - GitHub repo URL。
97
+ - 可用 commit。
98
+ - requirements.txt。
99
+ - 额外系统包。
100
+ - 模型路径约定。
101
+
102
+ 把这些写进 `docs/dependency-inventory.md`。
103
+
104
+ ## 5. 写 bootstrap 脚本
105
+
106
+ `scripts/bootstrap_comfy.py` 应该是幂等的:
107
+
108
+ - 如果 ComfyUI 不存在,clone。
109
+ - 如果 custom node 不存在,clone。
110
+ - 每个 repo checkout 到固定 commit。
111
+ - 安装 ComfyUI requirements。
112
+ - 安装 custom node requirements。
113
+ - 创建模型目录和 symlink。
114
+ - 可选下载模型。
115
+
116
+ 模型不要默认每次下载。建议做成:
117
+
118
+ ```bash
119
+ python scripts/bootstrap_comfy.py
120
+ python scripts/bootstrap_comfy.py --with-models
121
+ ```
122
+
123
+ 持久化模型路径建议:
124
+
125
+ ```text
126
+ /data/project_models
127
+ ```
128
+
129
+ 再 symlink 到 ComfyUI 需要的位置,例如:
130
+
131
+ ```text
132
+ ComfyUI/models/voxcpm/VoxCPM2
133
+ -> /data/project_models/voxcpm/VoxCPM2
134
+ ```
135
+
136
+ ## 6. 写最小 Gradio 入口
137
+
138
+ ZeroGPU Space 启动时必须能发现至少一个 `@spaces.GPU` 函数,否则可能报:
139
+
140
+ ```text
141
+ No @spaces.GPU function detected during startup
142
+ ```
143
+
144
+ 最小形式:
145
+
146
+ ```python
147
+ import gradio as gr
148
+ import spaces
149
+ import torch
150
+
151
+ @spaces.GPU(duration=60)
152
+ def gpu_smoke_test():
153
+ return f"cuda_available={torch.cuda.is_available()}"
154
+
155
+ with gr.Blocks() as demo:
156
+ button = gr.Button("GPU")
157
+ output = gr.Textbox()
158
+ button.click(gpu_smoke_test, outputs=output)
159
+
160
+ demo.launch()
161
+ ```
162
+
163
+ 先推送这个版本,确认 Space 能运行。
164
+
165
+ ## 7. 正确理解 ZeroGPU、SSH、Dev Mode
166
+
167
+ 关键结论:
168
+
169
+ - SSH 只适合诊断容器文件和 CPU 模式。
170
+ - SSH 里通常看不到 ZeroGPU CUDA。
171
+ - 真正的 GPU 工作必须从 Gradio 的 `@spaces.GPU(...)` 函数触发。
172
+ - Dev Mode 开启后,运行容器可能停留在旧 commit。
173
+
174
+ SSH 只在 Dev Mode 开启时可用:
175
+
176
+ ```powershell
177
+ ssh -i "$env:USERPROFILE\.ssh\<key_name>" <space-user>@ssh.hf.space
178
+ ```
179
+
180
+ 如果 Space 一直不更新到最新 commit:
181
+
182
+ - 关闭 Dev Mode。
183
+ - push 一个新 commit 或 factory reboot。
184
+ - 用 HF API 确认 runtime sha。
185
+
186
+ ## 8. 不要在 Gradio 请求里同步做重 bootstrap
187
+
188
+ 踩过的坑:
189
+
190
+ ```text
191
+ Gradio click -> bootstrap_comfy.py -> clone -> pip install -> start ComfyUI
192
+ ```
193
+
194
+ 这种请求可能在几分钟后被外层 queue 中断,只看到:
195
+
196
+ ```text
197
+ event: error
198
+ data: {"error": null}
199
+ ```
200
+
201
+ 推荐:
202
+
203
+ - `Prepare` 按钮:非 GPU,启动后台 bootstrap 进程。
204
+ - `Prepare Status` 按钮:读取 `/tmp/bootstrap.log`。
205
+ - GPU 按钮:只启动 ComfyUI、���交 workflow、等待结果。
206
+
207
+ 这样不会把 ZeroGPU 时间浪费在安装依赖上,也更容易看到真实错误。
208
+
209
+ ## 9. 启动 ComfyUI 并通过 API 测试
210
+
211
+ ComfyUI 启动命令:
212
+
213
+ ```bash
214
+ python main.py --listen 127.0.0.1 --port 8188
215
+ ```
216
+
217
+ 先测:
218
+
219
+ ```text
220
+ GET /system_stats
221
+ ```
222
+
223
+ 再测最小 workflow:
224
+
225
+ ```text
226
+ LoadAudio -> SaveAudioMP3
227
+ ```
228
+
229
+ 常用 API:
230
+
231
+ - `POST /upload/image`:上传 input 文件,音频也可用这个入口。
232
+ - `POST /prompt`:提交 API workflow。
233
+ - `GET /history/{prompt_id}`:取执行结果。
234
+
235
+ 音频上传要注意二进制安全。不要用会破坏字节流的 PowerShell 文本管道。
236
+ 上传后最好校验 sha256 或至少确认 `file` 识别为音频。
237
+
238
+ ## 10. 分阶段测试 workflow
239
+
240
+ 不要一上来跑完整 workflow。推荐顺序:
241
+
242
+ 1. GPU smoke test:确认 CUDA 可用。
243
+ 2. ComfyUI runtime test:确认 `@spaces.GPU -> ComfyUI -> /system_stats`。
244
+ 3. Load/Save audio:确认 ComfyUI API 和音频文件路径。
245
+ 4. LLM-only:确认 secret 和外部 API。
246
+ 5. MelBand-only:确认模型加载和音频输出。
247
+ 6. TTS-only:确认 TTS 模型能进 GPU。
248
+ 7. ASR-only:确认 ASR 模型、aligner、SRT 输出。
249
+ 8. SRT split/merge。
250
+ 9. 完整短音频 workflow。
251
+ 10. 最后再做正式 Gradio UI。
252
+
253
+ 每一步都要记录:
254
+
255
+ - 输入文件。
256
+ - prompt id。
257
+ - status_str。
258
+ - outputs。
259
+ - elapsed_sec。
260
+ - 错误日志尾部。
261
+
262
+ ## 11. Secrets 的安全配置
263
+
264
+ API key 放在 HF Space Secrets,例如:
265
+
266
+ ```text
267
+ DEEPSEEK_API_KEY
268
+ ```
269
+
270
+ 代码里只读环境变量:
271
+
272
+ ```python
273
+ import os
274
+ api_key = os.environ.get("DEEPSEEK_API_KEY")
275
+ ```
276
+
277
+ 不要打印 key。检查时只输出:
278
+
279
+ - 是否存在。
280
+ - 长度。
281
+ - 前后缀也尽量不要输出。
282
+
283
+ ## 12. 常见坑和解决办法
284
+
285
+ ### `No @spaces.GPU function detected`
286
+
287
+ 原因:ZeroGPU Space 启动时没有发现 `@spaces.GPU`。
288
+
289
+ 解决:在 `app.py` 保留至少一个 GPU 函数。
290
+
291
+ ### SSH 里 `torch.cuda.is_available()` 是 False
292
+
293
+ 这是正常的。SSH 不代表 ZeroGPU 执行环境。
294
+
295
+ 解决:从 Gradio `@spaces.GPU` 函数里测 CUDA。
296
+
297
+ ### Dev Mode 里代码不是最新
298
+
299
+ Dev Mode 容器可能停留在旧 commit。
300
+
301
+ 解决:关闭 Dev Mode,重新 rebuild,或 push 新 commit。
302
+
303
+ ### Custom node import failed
304
+
305
+ 先看 ComfyUI 启动日志,通常是缺 Python 包或系统包。
306
+
307
+ VoiceGate 遇到过:
308
+
309
+ ```text
310
+ SoX could not be found
311
+ ModuleNotFoundError: sounddevice
312
+ OSError: PortAudio library not found
313
+ ModuleNotFoundError: easydict
314
+ ModuleNotFoundError: pytorch_lightning
315
+ ```
316
+
317
+ 对应修复:
318
+
319
+ ```text
320
+ packages.txt: sox, libportaudio2, portaudio19-dev
321
+ requirements.txt: sounddevice, easydict, pytorch-lightning
322
+ ```
323
+
324
+ ### Runtime pip install 改变 torch 版本
325
+
326
+ 如果 Gradio 进程已经 import torch,再在运行时 pip install/upgrade torch,
327
+ Gradio 进程和后续子进程可能看到不同 torch 版本。
328
+
329
+ 解决:生产版本尽量把重依赖放进 build 阶段,并显式 pin 版本。
330
+
331
+ ### 模型下载 CLI 不稳定
332
+
333
+ `huggingface-cli download` 或 `hf download` 可能因为版本兼容问题失败。
334
+
335
+ 解决:用 `huggingface_hub` 的 Python API 下载模型。
336
+
337
+ ### TTS/ASR 冷启动很慢
338
+
339
+ 大模型第一次加载会非常慢。VoiceGate 的 VoxCPM2 TTS-only 冷启动约 766 秒。
340
+
341
+ 解决方向:
342
+
343
+ - 缩短测试文本。
344
+ - 避免每个 GPU 调用重复启动 ComfyUI。
345
+ - 尽量复用模型进程。
346
+ - 先做子流程验证,再优化 UX。
347
+
348
+ ## 13. 最终正式化建议
349
+
350
+ 诊断阶段可以把很多按钮放在 `app.py` 里。正式阶段建议拆分:
351
+
352
+ ```text
353
+ app.py
354
+ scripts/bootstrap_comfy.py
355
+ voicegate_runtime/
356
+ |-- comfy_server.py
357
+ |-- workflows.py
358
+ |-- files.py
359
+ `-- ui.py
360
+ ```
361
+
362
+ 正式 Gradio UI 至少提供:
363
+
364
+ - 音频上传。
365
+ - 目标语言。
366
+ - 运行状态。
367
+ - 生成音频下载。
368
+ - SRT 下载。
369
+ - 错误日志摘要。
370
+ - 文件类型和时长限制。
371
+
372
+ 先限制短音频,等完整链路稳定后再支持视频和更长输入。
docs/work-log.md CHANGED
@@ -19,6 +19,32 @@ and how they were resolved.
19
  Do not commit API keys, model weights, uploaded media, generated outputs, or the
20
  local `VoiceGate/` upstream checkout.
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  ## Repository Setup Completed
23
 
24
  - Created and pushed the Space wrapper repository.
@@ -161,6 +187,18 @@ def placeholder():
161
  ...
162
  ```
163
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  ### SSH Does Not Expose ZeroGPU CUDA
165
 
166
  Starting ComfyUI normally through SSH failed with:
@@ -181,6 +219,52 @@ CPU diagnostic command:
181
  python scripts/run_comfy.py --cpu
182
  ```
183
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  ## Dependency Pitfalls and Fixes
185
 
186
  `ComfyUI_AudioTools` initially failed to import.
 
19
  Do not commit API keys, model weights, uploaded media, generated outputs, or the
20
  local `VoiceGate/` upstream checkout.
21
 
22
+ ## Executive Summary
23
+
24
+ The Space is no longer just a blank scaffold. It can now run Gradio, invoke
25
+ ZeroGPU, prepare a ComfyUI runtime, start ComfyUI from a GPU-backed Gradio
26
+ function, and submit several segmented ComfyUI workflows.
27
+
28
+ Confirmed working:
29
+
30
+ - Hugging Face Space git push and normal rebuild flow.
31
+ - Dev Mode SSH for CPU/container diagnostics.
32
+ - ZeroGPU invocation from Gradio through `@spaces.GPU`.
33
+ - ComfyUI startup from inside a `@spaces.GPU` function.
34
+ - ComfyUI API calls from the Gradio process.
35
+ - DeepSeek-compatible LLM node with the Space secret.
36
+ - MelBand RoFormer smoke tests in CPU mode and ZeroGPU mode.
37
+ - VoxCPM2 TTS-only smoke test in ZeroGPU mode.
38
+ - Persistent model storage for VoxCPM2 and MelBand under `/data`.
39
+
40
+ Not yet confirmed at the start of 2026-06-06:
41
+
42
+ - VoiceBridge ASR-only workflow.
43
+ - Qwen ASR and Qwen forced aligner model download/cache behavior in the Space.
44
+ - SRT split -> VoxCPM -> SRT merge.
45
+ - Full short-audio VoiceGate workflow.
46
+ - Final user-facing Gradio upload/download UI.
47
+
48
  ## Repository Setup Completed
49
 
50
  - Created and pushed the Space wrapper repository.
 
187
  ...
188
  ```
189
 
190
+ Later this placeholder was replaced by real diagnostic functions:
191
+
192
+ ```python
193
+ @spaces.GPU(duration=60)
194
+ def gpu_smoke_test():
195
+ ...
196
+
197
+ @spaces.GPU(duration=900)
198
+ def comfy_runtime_test():
199
+ ...
200
+ ```
201
+
202
  ### SSH Does Not Expose ZeroGPU CUDA
203
 
204
  Starting ComfyUI normally through SSH failed with:
 
219
  python scripts/run_comfy.py --cpu
220
  ```
221
 
222
+ ### Gradio Request Timeout During Bootstrap
223
+
224
+ Long bootstrap work should not run synchronously inside a Gradio request. The
225
+ first attempt did this:
226
+
227
+ ```text
228
+ Gradio click -> bootstrap_comfy.py -> clone repos -> pip install -> start ComfyUI
229
+ ```
230
+
231
+ The request was interrupted by Gradio/ZeroGPU's outer queue after roughly 2.5
232
+ minutes and returned:
233
+
234
+ ```text
235
+ event: error
236
+ data: {"error": null}
237
+ ```
238
+
239
+ Fix:
240
+
241
+ - Add a non-GPU `Prepare` action that starts `scripts/bootstrap_comfy.py` as a
242
+ background process.
243
+ - Add `Prepare Status` to poll `/tmp/voicegate_bootstrap.log`.
244
+ - Keep GPU actions focused on starting ComfyUI and running actual CUDA work.
245
+
246
+ This avoids wasting ZeroGPU time on clone/install steps and prevents the request
247
+ from being killed before diagnostics can return useful logs.
248
+
249
+ ### Runtime Pip Install Pitfall
250
+
251
+ The background bootstrap installed a large dependency set and upgraded the
252
+ on-disk Torch package. The already-running Gradio process continued to report:
253
+
254
+ ```text
255
+ torch=2.11.0+cu130
256
+ ```
257
+
258
+ while the ComfyUI subprocess started afterwards reported:
259
+
260
+ ```text
261
+ pytorch_version=2.12.0+cu130
262
+ ```
263
+
264
+ This is workable for diagnostics, but final production should avoid heavy
265
+ runtime `pip install` where possible. Prefer moving stable dependencies into
266
+ Space build-time requirements or explicitly controlling pins.
267
+
268
  ## Dependency Pitfalls and Fixes
269
 
270
  `ComfyUI_AudioTools` initially failed to import.
scripts/bootstrap_comfy.py CHANGED
@@ -95,11 +95,13 @@ CUSTOM_NODE_REPOS = [
95
  MODEL_LINKS = {
96
  "voxcpm2": COMFY_DIR / "models" / "voxcpm" / "VoxCPM2",
97
  "melband": COMFY_DIR / "models" / "diffusion_models" / "MelBandRoFormer_comfy",
 
98
  }
99
 
100
  MODEL_DIRS = {
101
  "voxcpm2": Path("voxcpm") / "VoxCPM2",
102
  "melband": Path("diffusion_models") / "MelBandRoFormer_comfy",
 
103
  }
104
 
105
 
@@ -197,6 +199,11 @@ def download_models(dry_run: bool = False) -> None:
197
  "+ hf_hub_download "
198
  f"Kijai/MelBandRoFormer_comfy/MelBandRoformer_fp32.safetensors -> {model_target('melband')}"
199
  )
 
 
 
 
 
200
  return
201
 
202
  from huggingface_hub import hf_hub_download, snapshot_download
@@ -212,6 +219,16 @@ def download_models(dry_run: bool = False) -> None:
212
  local_dir=model_target("melband"),
213
  token=token,
214
  )
 
 
 
 
 
 
 
 
 
 
215
 
216
 
217
  def print_summary() -> None:
 
95
  MODEL_LINKS = {
96
  "voxcpm2": COMFY_DIR / "models" / "voxcpm" / "VoxCPM2",
97
  "melband": COMFY_DIR / "models" / "diffusion_models" / "MelBandRoFormer_comfy",
98
+ "qwen3_asr": COMFY_DIR / "models" / "Qwen3-ASR",
99
  }
100
 
101
  MODEL_DIRS = {
102
  "voxcpm2": Path("voxcpm") / "VoxCPM2",
103
  "melband": Path("diffusion_models") / "MelBandRoFormer_comfy",
104
+ "qwen3_asr": Path("Qwen3-ASR"),
105
  }
106
 
107
 
 
199
  "+ hf_hub_download "
200
  f"Kijai/MelBandRoFormer_comfy/MelBandRoformer_fp32.safetensors -> {model_target('melband')}"
201
  )
202
+ print(f"+ snapshot_download Qwen/Qwen3-ASR-1.7B -> {model_target('qwen3_asr') / 'Qwen3-ASR-1.7B'}")
203
+ print(
204
+ "+ snapshot_download "
205
+ f"Qwen/Qwen3-ForcedAligner-0.6B -> {model_target('qwen3_asr') / 'Qwen3-ForcedAligner-0.6B'}"
206
+ )
207
  return
208
 
209
  from huggingface_hub import hf_hub_download, snapshot_download
 
219
  local_dir=model_target("melband"),
220
  token=token,
221
  )
222
+ snapshot_download(
223
+ repo_id="Qwen/Qwen3-ASR-1.7B",
224
+ local_dir=model_target("qwen3_asr") / "Qwen3-ASR-1.7B",
225
+ token=token,
226
+ )
227
+ snapshot_download(
228
+ repo_id="Qwen/Qwen3-ForcedAligner-0.6B",
229
+ local_dir=model_target("qwen3_asr") / "Qwen3-ForcedAligner-0.6B",
230
+ token=token,
231
+ )
232
 
233
 
234
  def print_summary() -> None: