ChenPinZhen commited on
Commit
949b499
·
verified ·
1 Parent(s): 5bb0aa4

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +74 -22
app.py CHANGED
@@ -2,37 +2,87 @@
2
  """Hugging Face Space(Gradio SDK)的進入點。
3
 
4
  這個專案本身沒有用 Gradio,介面是自己的 index.html 加 Python 內建的 http.server。
5
- Space 的 Gradio SDK 其實只做兩件事:跑 app.py,然後把 7860 埠反向代理出去。
6
- 所以這裡只要把我們自己的伺服器綁在 7860 上就行,不需要包成 Gradio 元件
7
  (包成 Gradio 反而會失去鋼琴捲軸、試聽合成器那些自己寫的東西)。
8
 
9
- 用 Docker SDK 的話請改用 Dockerfile,那份比較單純。
 
10
  """
11
  import importlib.util
12
  import os
13
  import subprocess
14
  import sys
15
 
 
 
16
 
17
- # ZeroGPU 硬體開機時會掃描程式裡有沒有被 @spaces.GPU 裝飾的函式,掃不到就直接判定
18
- # 啟動失敗(Runtime error: No @spaces.GPU function detected during startup),
19
- # 跟程式寫得對不對無關。
20
- #
21
- # 這個專案從頭到尾跑 CPU:Basic Pitch 走的是 ONNX,沒有任何一段需要 GPU。
22
- # 但 Space 建立後若沒有 PRO 就不能把硬體降回免費的 cpu-basic,所以留一個空的
23
- # 函式給它掃,讓 ZeroGPU 上的 Space 也能正常啟動。這個函式永遠不會被呼叫。
24
- #
25
- # cpu-basic 的映像檔裡沒有 spaces 這個套件,import 會失敗,整段跳過就好。
26
- try:
27
- import spaces
28
 
29
- @spaces.GPU
30
- def _zerogpu_probe():
31
- return None
32
 
33
- print('[app] 偵測到 ZeroGPU 環境,已註冊佔位函式(實際運算仍在 CPU)', flush=True)
34
- except Exception:
35
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
 
38
  def ensure_basic_pitch():
@@ -68,11 +118,13 @@ def ensure_basic_pitch():
68
 
69
 
70
  def main():
 
 
 
71
  ensure_basic_pitch()
72
 
73
  # HF 的代理固定打 7860,而且一定要綁 0.0.0.0;綁 127.0.0.1 從外面連不進來。
74
- # GRADIO_SERVER_PORT 是 Gradio SDK 會設的,拿它當第二順位比較保險。
75
- port = os.environ.get('PORT') or os.environ.get('GRADIO_SERVER_PORT') or '7860'
76
  os.environ['PORT'] = port
77
  os.environ['HOST'] = '0.0.0.0'
78
  os.environ['NO_BROWSER'] = '1'
 
2
  """Hugging Face Space(Gradio SDK)的進入點。
3
 
4
  這個專案本身沒有用 Gradio,介面是自己的 index.html 加 Python 內建的 http.server。
5
+ Space 的 Gradio SDK 只做兩件事:跑 app.py,然後把 7860 埠反向代理出去,
6
+ 所以把自己的伺服器綁在 7860 上就能用,不需要包成 Gradio 元件
7
  (包成 Gradio 反而會失去鋼琴捲軸、試聽合成器那些自己寫的東西)。
8
 
9
+ 只有 ZeroGPU 硬體多一道手續,見 satisfy_zerogpu()。
10
+ 用 Docker SDK 的話請改用 Dockerfile,那份最單純。
11
  """
12
  import importlib.util
13
  import os
14
  import subprocess
15
  import sys
16
 
17
+ # ZeroGPU 握手用的內部埠。只綁 127.0.0.1,不對外。
18
+ HANDSHAKE_PORT = int(os.environ.get('ZEROGPU_HANDSHAKE_PORT') or 7861)
19
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
+ def satisfy_zerogpu():
22
+ """在 ZeroGPU 硬體上完成啟動握手。
 
23
 
24
+ 免費帳號的 Gradio Space 只能用 ZeroGPU(Docker SDK 要付費,cpu-basic 要 PRO),
25
+ 而 ZeroGPU 啟動不成功就整個 Space 掛掉,錯誤是:
26
+ No @spaces.GPU function detected during startup
27
+
28
+ 這個錯誤的訊息會誤導人。實際機制在 spaces/zero/__init__.py:
29
+
30
+ def startup():
31
+ ...
32
+ if len(decorator.decorated_cache) == 0:
33
+ return
34
+ client.startup_report()
35
+
36
+ gradio.one_launch(startup)
37
+
38
+ 而 one_launch 是把 gr.Blocks.launch 換掉,等你呼叫 launch() 時才執行 startup()。
39
+ 也就是說,光有 @spaces.GPU 函式沒有用——只要程式從頭到尾沒有呼叫過
40
+ demo.launch(),那份 startup_report 就永遠不會送出,監督程序等不到就判定失敗。
41
+
42
+ 所以這裡開一個最小的 Gradio app 綁在 127.0.0.1 的內部埠上,純粹為了觸發那一次
43
+ 握手。它不對外、沒有人會看到,被裝飾的函式也永遠不會被呼叫——這個專案完全跑
44
+ CPU(Basic Pitch 走 ONNX)。對外的 7860 仍然是我們自己的伺服器。
45
+
46
+ 非 ZeroGPU 的環境(cpu-basic、Docker、本機)沒有 SPACES_ZERO_GPU 這個環境變數,
47
+ 整段直接跳過。
48
+ """
49
+ if not os.environ.get('SPACES_ZERO_GPU'):
50
+ return
51
+
52
+ try:
53
+ # spaces 必須在任何會初始化 CUDA 的套件之前 import,否則它自己會拋錯。
54
+ # 這也是 satisfy_zerogpu() 要排在 import server 之前的原因。
55
+ import spaces
56
+ import gradio as gr
57
+ except ImportError as e:
58
+ print('[app] ZeroGPU 環境但缺少套件(%s),跳過握手' % e, flush=True)
59
+ return
60
+
61
+ @spaces.GPU(duration=1)
62
+ def _unused():
63
+ # 只是為了讓 decorated_cache 不是空的。永遠不會被呼叫。
64
+ return 'ok'
65
+
66
+ with gr.Blocks() as demo:
67
+ gr.Markdown('這個頁面只是 ZeroGPU 的啟動握手,實際介面在 Space 的網址上。')
68
+ out = gr.Textbox(visible=False)
69
+ gr.Button('noop', visible=False).click(_unused, outputs=out)
70
+
71
+ try:
72
+ demo.launch(
73
+ server_name='127.0.0.1',
74
+ server_port=HANDSHAKE_PORT,
75
+ prevent_thread_lock=True, # 立刻返回,不要卡住主流程
76
+ quiet=True,
77
+ show_api=False,
78
+ share=False,
79
+ inbrowser=False,
80
+ )
81
+ print('[app] ZeroGPU 握手完成(內部埠 %d)' % HANDSHAKE_PORT, flush=True)
82
+ except Exception as e:
83
+ # 握手失敗的話 Space 大概還是會被判定啟動失敗,但至少日誌看得到原因,
84
+ # 不要在這裡把整個程式帶走。
85
+ print('[app] ZeroGPU 握手失敗:%r' % (e,), flush=True)
86
 
87
 
88
  def ensure_basic_pitch():
 
118
 
119
 
120
  def main():
121
+ # 順序有意義:握手要在 import server(會連帶 import librosa、numpy)之前做完,
122
+ # spaces 套件要求自己排在任何 CUDA 相關套件前面。
123
+ satisfy_zerogpu()
124
  ensure_basic_pitch()
125
 
126
  # HF 的代理固定打 7860,而且一定要綁 0.0.0.0;綁 127.0.0.1 從外面連不進來。
127
+ port = os.environ.get('PORT') or '7860'
 
128
  os.environ['PORT'] = port
129
  os.environ['HOST'] = '0.0.0.0'
130
  os.environ['NO_BROWSER'] = '1'