YanTianlong commited on
Commit
fdc45ee
·
1 Parent(s): 5ccb31e

Add GPU ComfyUI diagnostic tests

Browse files
Files changed (1) hide show
  1. app.py +323 -23
app.py CHANGED
@@ -1,40 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
 
2
  import spaces
3
  import torch
4
 
5
 
6
- @spaces.GPU(duration=60)
7
- def gpu_smoke_test():
8
- lines = ["VoiceGate GPU smoke test"]
 
 
 
 
 
 
 
 
 
 
 
9
  lines.append(f"torch={torch.__version__}")
10
  lines.append(f"cuda_available={torch.cuda.is_available()}")
11
  lines.append(f"cuda_device_count={torch.cuda.device_count()}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
- if not torch.cuda.is_available():
14
- return "\n".join(lines)
15
-
16
- device = torch.device("cuda:0")
17
- lines.append(f"device_name={torch.cuda.get_device_name(0)}")
18
- props = torch.cuda.get_device_properties(0)
19
- lines.append(f"total_memory_gb={props.total_memory / 1024**3:.2f}")
20
-
21
- tensor = torch.arange(16, device=device, dtype=torch.float32)
22
- result = (tensor * 2).sum().item()
23
- torch.cuda.synchronize()
24
- allocated = torch.cuda.memory_allocated(0) / 1024**2
25
- reserved = torch.cuda.memory_reserved(0) / 1024**2
26
- lines.append(f"tensor_result={result}")
27
- lines.append(f"memory_allocated_mb={allocated:.2f}")
28
- lines.append(f"memory_reserved_mb={reserved:.2f}")
 
29
  return "\n".join(lines)
30
 
31
 
32
  with gr.Blocks(title="VoiceGate") as demo:
33
  gr.Markdown("# VoiceGate")
34
- gr.Markdown("Hugging Face Space deployment scaffold for the VoiceGate ComfyUI workflow.")
35
- run = gr.Button("Run GPU smoke test")
36
- output = gr.Textbox(label="Status")
37
- run.click(fn=gpu_smoke_test, outputs=output)
 
 
 
 
 
 
38
 
39
 
40
  if __name__ == "__main__":
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import math
5
+ import subprocess
6
+ import sys
7
+ import time
8
+ import uuid
9
+ import wave
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
  import gradio as gr
14
+ import requests
15
  import spaces
16
  import torch
17
 
18
 
19
+ ROOT = Path(__file__).resolve().parent
20
+ COMFY_DIR = ROOT / "ComfyUI"
21
+ COMFY_INPUT_DIR = COMFY_DIR / "input"
22
+ COMFY_LOG = Path("/tmp/voicegate_comfy_gradio.log")
23
+ COMFY_URL = "http://127.0.0.1:8188"
24
+ COMFY_HOST = "127.0.0.1"
25
+ COMFY_PORT = "8188"
26
+
27
+ COMFY_PROCESS: subprocess.Popen | None = None
28
+ BOOTSTRAPPED = False
29
+
30
+
31
+ def gpu_status_lines() -> list[str]:
32
+ lines = ["VoiceGate GPU status"]
33
  lines.append(f"torch={torch.__version__}")
34
  lines.append(f"cuda_available={torch.cuda.is_available()}")
35
  lines.append(f"cuda_device_count={torch.cuda.device_count()}")
36
+ if torch.cuda.is_available():
37
+ props = torch.cuda.get_device_properties(0)
38
+ lines.append(f"device_name={torch.cuda.get_device_name(0)}")
39
+ lines.append(f"total_memory_gb={props.total_memory / 1024**3:.2f}")
40
+ return lines
41
+
42
+
43
+ def wait_for_comfy(timeout: float = 180) -> dict[str, Any]:
44
+ deadline = time.time() + timeout
45
+ last_error = ""
46
+ while time.time() < deadline:
47
+ try:
48
+ response = requests.get(f"{COMFY_URL}/system_stats", timeout=5)
49
+ if response.ok:
50
+ return response.json()
51
+ last_error = f"HTTP {response.status_code}: {response.text[:300]}"
52
+ except requests.RequestException as exc:
53
+ last_error = repr(exc)
54
+ time.sleep(2)
55
+ raise RuntimeError(f"ComfyUI did not become ready: {last_error}")
56
+
57
+
58
+ def run_bootstrap(lines: list[str]) -> None:
59
+ global BOOTSTRAPPED
60
+
61
+ if BOOTSTRAPPED and COMFY_DIR.exists():
62
+ lines.append("bootstrap=already_done")
63
+ return
64
+
65
+ started = time.time()
66
+ lines.append("bootstrap=starting")
67
+ command = [sys.executable, str(ROOT / "scripts" / "bootstrap_comfy.py")]
68
+ result = subprocess.run(
69
+ command,
70
+ cwd=ROOT,
71
+ text=True,
72
+ stdout=subprocess.PIPE,
73
+ stderr=subprocess.STDOUT,
74
+ timeout=900,
75
+ )
76
+ lines.append(f"bootstrap_returncode={result.returncode}")
77
+ lines.append(f"bootstrap_elapsed_sec={time.time() - started:.1f}")
78
+ if result.returncode != 0:
79
+ lines.append("bootstrap_tail:")
80
+ lines.extend(result.stdout.splitlines()[-80:])
81
+ raise RuntimeError("bootstrap_comfy.py failed")
82
+ BOOTSTRAPPED = True
83
+
84
+
85
+ def ensure_comfy(lines: list[str], *, timeout: float = 240) -> dict[str, Any]:
86
+ global COMFY_PROCESS
87
+
88
+ run_bootstrap(lines)
89
+
90
+ try:
91
+ stats = wait_for_comfy(timeout=5)
92
+ lines.append("comfy=already_running")
93
+ return stats
94
+ except RuntimeError:
95
+ pass
96
+
97
+ log = COMFY_LOG.open("ab")
98
+ command = [
99
+ sys.executable,
100
+ "main.py",
101
+ "--listen",
102
+ COMFY_HOST,
103
+ "--port",
104
+ COMFY_PORT,
105
+ ]
106
+ COMFY_PROCESS = subprocess.Popen(
107
+ command,
108
+ cwd=COMFY_DIR,
109
+ stdout=log,
110
+ stderr=subprocess.STDOUT,
111
+ )
112
+ lines.append(f"comfy_started_pid={COMFY_PROCESS.pid}")
113
+ try:
114
+ return wait_for_comfy(timeout=timeout)
115
+ except Exception:
116
+ lines.append("comfy_log_tail:")
117
+ if COMFY_LOG.exists():
118
+ lines.extend(COMFY_LOG.read_text(encoding="utf-8", errors="replace").splitlines()[-120:])
119
+ raise
120
+
121
+
122
+ def write_sine_wav(filename: str, *, seconds: float = 1.0, frequency: float = 440.0) -> str:
123
+ COMFY_INPUT_DIR.mkdir(parents=True, exist_ok=True)
124
+ path = COMFY_INPUT_DIR / filename
125
+ sample_rate = 16000
126
+ total = int(sample_rate * seconds)
127
+ amplitude = 0.2
128
+ with wave.open(str(path), "wb") as file:
129
+ file.setnchannels(1)
130
+ file.setsampwidth(2)
131
+ file.setframerate(sample_rate)
132
+ for index in range(total):
133
+ value = int(32767 * amplitude * math.sin(2 * math.pi * frequency * index / sample_rate))
134
+ file.writeframesraw(value.to_bytes(2, byteorder="little", signed=True))
135
+ return filename
136
+
137
+
138
+ def submit_prompt(workflow: dict[str, Any]) -> str:
139
+ response = requests.post(
140
+ f"{COMFY_URL}/prompt",
141
+ json={"prompt": workflow, "client_id": str(uuid.uuid4())},
142
+ timeout=120,
143
+ )
144
+ response.raise_for_status()
145
+ return response.json()["prompt_id"]
146
+
147
+
148
+ def wait_for_history(prompt_id: str, timeout: float = 1200) -> dict[str, Any]:
149
+ deadline = time.time() + timeout
150
+ while time.time() < deadline:
151
+ response = requests.get(f"{COMFY_URL}/history/{prompt_id}", timeout=30)
152
+ response.raise_for_status()
153
+ payload = response.json()
154
+ if prompt_id in payload:
155
+ return payload[prompt_id]
156
+ time.sleep(2)
157
+ raise TimeoutError(f"Timed out waiting for prompt {prompt_id}")
158
+
159
+
160
+ def history_summary(history: dict[str, Any]) -> list[str]:
161
+ lines = []
162
+ status = history.get("status", {})
163
+ lines.append(f"status_str={status.get('status_str')}")
164
+ lines.append(f"completed={status.get('completed')}")
165
+ messages = status.get("messages") or []
166
+ errors = [message for message in messages if isinstance(message, list) and message[0] == "execution_error"]
167
+ if errors:
168
+ lines.append("errors:")
169
+ lines.append(json.dumps(errors, ensure_ascii=False, indent=2)[:4000])
170
+
171
+ outputs = history.get("outputs", {})
172
+ output_files = []
173
+ for node_output in outputs.values():
174
+ for key in ("audio", "images", "gifs"):
175
+ for item in node_output.get(key, []) or []:
176
+ filename = item.get("filename")
177
+ subfolder = item.get("subfolder")
178
+ if subfolder:
179
+ output_files.append(f"{subfolder}/{filename}")
180
+ elif filename:
181
+ output_files.append(filename)
182
+ if output_files:
183
+ lines.append("outputs:")
184
+ lines.extend(output_files)
185
+ return lines
186
+
187
+
188
+ def melband_workflow(audio_filename: str, prefix: str) -> dict[str, Any]:
189
+ return {
190
+ "1": {
191
+ "class_type": "LoadAudio",
192
+ "inputs": {"audio": audio_filename, "audioUI": ""},
193
+ },
194
+ "2": {
195
+ "class_type": "MelBandRoFormerModelLoader",
196
+ "inputs": {"model_name": "MelBandRoFormer_comfy/MelBandRoformer_fp32.safetensors"},
197
+ },
198
+ "3": {
199
+ "class_type": "MelBandRoFormerSampler",
200
+ "inputs": {"model": ["2", 0], "audio": ["1", 0]},
201
+ },
202
+ "4": {
203
+ "class_type": "SaveAudioMP3",
204
+ "inputs": {
205
+ "filename_prefix": f"audio/{prefix}_vocals",
206
+ "quality": "V0",
207
+ "audioUI": "",
208
+ "audio": ["3", 0],
209
+ },
210
+ },
211
+ "5": {
212
+ "class_type": "SaveAudioMP3",
213
+ "inputs": {
214
+ "filename_prefix": f"audio/{prefix}_instruments",
215
+ "quality": "V0",
216
+ "audioUI": "",
217
+ "audio": ["3", 1],
218
+ },
219
+ },
220
+ }
221
+
222
+
223
+ def voxcpm_tts_workflow(prefix: str) -> dict[str, Any]:
224
+ return {
225
+ "1": {
226
+ "class_type": "RunningHub_VoxCPM_LoadModel",
227
+ "inputs": {"model_name": "VoxCPM2", "optimize": False, "lora_name": "None"},
228
+ },
229
+ "2": {
230
+ "class_type": "RunningHub_VoxCPM_Generate",
231
+ "inputs": {
232
+ "model": ["1", 0],
233
+ "control_instruction": "清晰自然的中文女声",
234
+ "text": "你好,VoiceGate GPU 语音合成测试。",
235
+ "cfg_value": 2.0,
236
+ "inference_steps": 4,
237
+ "seed": 20260605,
238
+ "ultimate_clone": False,
239
+ "reference_audio_text": "",
240
+ "normalize_text": False,
241
+ "denoise_reference": False,
242
+ "max_len": 512,
243
+ "retry_badcase": True,
244
+ },
245
+ },
246
+ "3": {
247
+ "class_type": "SaveAudioMP3",
248
+ "inputs": {
249
+ "filename_prefix": f"audio/{prefix}",
250
+ "quality": "V0",
251
+ "audioUI": "",
252
+ "audio": ["2", 0],
253
+ },
254
+ },
255
+ }
256
+
257
+
258
+ @spaces.GPU(duration=60)
259
+ def gpu_smoke_test() -> str:
260
+ lines = gpu_status_lines()
261
+ if torch.cuda.is_available():
262
+ tensor = torch.arange(16, device="cuda:0", dtype=torch.float32)
263
+ result = (tensor * 2).sum().item()
264
+ torch.cuda.synchronize()
265
+ lines.append(f"tensor_result={result}")
266
+ lines.append(f"memory_reserved_mb={torch.cuda.memory_reserved(0) / 1024**2:.2f}")
267
+ return "\n".join(lines)
268
+
269
+
270
+ @spaces.GPU(duration=900)
271
+ def comfy_runtime_test() -> str:
272
+ lines = gpu_status_lines()
273
+ started = time.time()
274
+ try:
275
+ stats = ensure_comfy(lines)
276
+ lines.append(f"comfy_ready=true")
277
+ lines.append(f"comfy_elapsed_sec={time.time() - started:.1f}")
278
+ lines.append("system_stats:")
279
+ lines.append(json.dumps(stats, ensure_ascii=False, indent=2)[:4000])
280
+ except Exception as exc:
281
+ lines.append(f"error={type(exc).__name__}: {exc}")
282
+ return "\n".join(lines)
283
+
284
+
285
+ @spaces.GPU(duration=1200)
286
+ def melband_gpu_test() -> str:
287
+ lines = gpu_status_lines()
288
+ started = time.time()
289
+ try:
290
+ ensure_comfy(lines)
291
+ audio_filename = write_sine_wav(f"voicegate_melband_{uuid.uuid4().hex[:8]}.wav")
292
+ prefix = f"melband_gpu_{uuid.uuid4().hex[:8]}"
293
+ prompt_id = submit_prompt(melband_workflow(audio_filename, prefix))
294
+ lines.append(f"prompt_id={prompt_id}")
295
+ history = wait_for_history(prompt_id)
296
+ lines.extend(history_summary(history))
297
+ lines.append(f"elapsed_sec={time.time() - started:.1f}")
298
+ except Exception as exc:
299
+ lines.append(f"error={type(exc).__name__}: {exc}")
300
+ if COMFY_LOG.exists():
301
+ lines.append("comfy_log_tail:")
302
+ lines.extend(COMFY_LOG.read_text(encoding="utf-8", errors="replace").splitlines()[-120:])
303
+ return "\n".join(lines)
304
+
305
 
306
+ @spaces.GPU(duration=1200)
307
+ def voxcpm_tts_gpu_test() -> str:
308
+ lines = gpu_status_lines()
309
+ started = time.time()
310
+ try:
311
+ ensure_comfy(lines)
312
+ prefix = f"voxcpm_tts_gpu_{uuid.uuid4().hex[:8]}"
313
+ prompt_id = submit_prompt(voxcpm_tts_workflow(prefix))
314
+ lines.append(f"prompt_id={prompt_id}")
315
+ history = wait_for_history(prompt_id, timeout=1200)
316
+ lines.extend(history_summary(history))
317
+ lines.append(f"elapsed_sec={time.time() - started:.1f}")
318
+ except Exception as exc:
319
+ lines.append(f"error={type(exc).__name__}: {exc}")
320
+ if COMFY_LOG.exists():
321
+ lines.append("comfy_log_tail:")
322
+ lines.extend(COMFY_LOG.read_text(encoding="utf-8", errors="replace").splitlines()[-160:])
323
  return "\n".join(lines)
324
 
325
 
326
  with gr.Blocks(title="VoiceGate") as demo:
327
  gr.Markdown("# VoiceGate")
328
+ with gr.Row():
329
+ gpu_run = gr.Button("GPU")
330
+ comfy_run = gr.Button("ComfyUI")
331
+ melband_run = gr.Button("MelBand")
332
+ voxcpm_run = gr.Button("VoxCPM TTS")
333
+ output = gr.Textbox(label="Status", lines=28)
334
+ gpu_run.click(fn=gpu_smoke_test, outputs=output)
335
+ comfy_run.click(fn=comfy_runtime_test, outputs=output)
336
+ melband_run.click(fn=melband_gpu_test, outputs=output)
337
+ voxcpm_run.click(fn=voxcpm_tts_gpu_test, outputs=output)
338
 
339
 
340
  if __name__ == "__main__":