EternalFuture commited on
Commit
8b7ec64
·
1 Parent(s): 388ff02

ZeroGPU 声明时长固定为 60s(预占 90s),按实测 0.19s/s 更新文档

Browse files
Files changed (3) hide show
  1. README.md +6 -4
  2. app.py +7 -24
  3. config.yaml +9 -7
README.md CHANGED
@@ -88,7 +88,8 @@ voices:
88
  | 字段 | 默认 | 说明 |
89
  | --- | --- | --- |
90
  | `output_sr` | 44100 | 输出采样率(三个分离模型都是 44100) |
91
- | `max_gpu_seconds` | 180 | 单次请求声明的 ZeroGPU 时长上限。报 `ZeroGPU illegal duration` 就调小(如 120),PRO 号长歌可调大到 300 |
 
92
  | `mix_peak` | 0.97 | 合并轨峰值归一化目标 |
93
  | `pitch_algo` | rmvpe+ | 音高提取算法,`rmvpe+` 质量最好 |
94
  | `model_cache_dir` | `/tmp/pymss_models` | 分离模型 + 音色下载缓存(临时盘) |
@@ -100,9 +101,10 @@ voices:
100
  - **`infer_rvc_python` 是 vendored 的,不在 `requirements.txt` 里**:它死锁 `torchcrepe==0.0.20` → `librosa==0.9.1`,而 `pymss` 要求 `librosa>=0.10.2`,两者不相容、pip 无解。所以整个包放在 `vendor/infer_rvc_python/`,由 `app.py` 用 `sys.path` 引入,它的运行期依赖在 `requirements.txt` 里显式列出。**别把 `vendor/` 加进 `.gitignore`,也别再往 `requirements.txt` 里写 `infer_rvc_python`**。
101
  - **构建阶段要现编译 pyworld**:Linux 上 pyworld 官方没有 wheel(只有 Windows wheel + sdist),只能从源码编译(需要 gcc / Cython),所以首次构建会稍慢,这一步报错就是编译环境问题。
102
  - **冷启动会下载约 3.3 GB 模型**:容器临时盘每次重启都会被清空,Space 睡醒后会重新下载,第一次访问会慢几分钟。介意的话可以在 Space 设置里加购 persistent storage,或把 `vocal_split` 换成体积更小的模型。
103
- - **ZeroGPU 是「先预占配额,再看实际耗时」**:`@spaces.GPU` 的 `duration` 在调用前就按声明值预占当天额度,**剩余额度 < 声明值会直接被拒**(`quota exceeded`),与实际跑多久无关。
104
- - **硬件系数 1.5**:SDK 把声明值乘上硬件系数再交给调度器(`spaces/zero/configs.json` 里 RTX PRO 6000 Blackwell = 1.5,H200 = 1.0)。这个系数同时也是函数真正可用的运行时间。`app.py` 已按 `gpu_duration_factor` 换算,所以 `max_gpu_seconds` 就是最终出现在 `requested vs. left` 里的数字 —— 别被这个 1.5 倍绕晕。
105
- - **额度差距很大**:免费号每天 **300s**,PRO 号每天 **2400s**。本项目一首三分钟的歌大约预占 180s,所以**免费号基本只能跑一首,想正常用必须上 PRO**。想省额度就先拿 30~60s 的短片段跑通流程。
 
106
  - **首次调用偏慢**:RVC 音色模型是在第一次请求时才真正加载进显存的(`infer_rvc_python` 的惰性加载),之后会复用。
107
  - **并发**:页面请求会串行执行(内部有锁),避免多个任务互抢显存和中间文件。
108
  - 仅供学习与研究使用,请勿用于侵权或未授权的声音克隆。
 
88
  | 字段 | 默认 | 说明 |
89
  | --- | --- | --- |
90
  | `output_sr` | 44100 | 输出采样率(三个分离模型都是 44100) |
91
+ | `gpu_declared_seconds` | 60 | 固定声明给 `@spaces.GPU` 的秒数,实际预占 = 该值 × `gpu_duration_factor`(= 90s)。它同时是运行时限,长音频要往上调 |
92
+ | `gpu_duration_factor` | 1.5 | ZeroGPU 硬件系数(`spaces/zero/configs.json`),换硬件时会变 |
93
  | `mix_peak` | 0.97 | 合并轨峰值归一化目标 |
94
  | `pitch_algo` | rmvpe+ | 音高提取算法,`rmvpe+` 质量最好 |
95
  | `model_cache_dir` | `/tmp/pymss_models` | 分离模型 + 音色下载缓存(临时盘) |
 
101
  - **`infer_rvc_python` 是 vendored 的,不在 `requirements.txt` 里**:它死锁 `torchcrepe==0.0.20` → `librosa==0.9.1`,而 `pymss` 要求 `librosa>=0.10.2`,两者不相容、pip 无解。所以整个包放在 `vendor/infer_rvc_python/`,由 `app.py` 用 `sys.path` 引入,它的运行期依赖在 `requirements.txt` 里显式列出。**别把 `vendor/` 加进 `.gitignore`,也别再往 `requirements.txt` 里写 `infer_rvc_python`**。
102
  - **构建阶段要现编译 pyworld**:Linux 上 pyworld 官方没有 wheel(只有 Windows wheel + sdist),只能从源码编译(需要 gcc / Cython),所以首次构建会稍慢,这一步报错就是编译环境问题。
103
  - **冷启动会下载约 3.3 GB 模型**:容器临时盘每次重启都会被清空,Space 睡醒后会重新下载,第一次访问会慢几分钟。介意的话可以在 Space 设置里加购 persistent storage,或把 `vocal_split` 换成体积更小的模型。
104
+ - **ZeroGPU 的额度按「实际耗时」扣,不是按声明值扣**。实测:204.1s 音频实际耗时约 38~40s,配额计量显示 `0.7/5 min`(≈42s);多次测量基本与音频长度成固定比例,约 **0.19 秒 GPU 时间 / 秒音频**,跟音频是无损还是低品质无关(解码开销相比模型推理可忽略)。
105
+ - **但调度门槛按「声明值」预检查**:`剩余配额 < 声明值 × 硬件系数` 会直接被拒(`quota exceeded`),与实际跑多久无关。所以声明值固定在 60(→ 预占 90s)最省事。
106
+ - **硬件系数 1.5**:SDK 把声明值乘上硬件系数再交给调度器(`spaces/zero/configs.json` 里 RTX PRO 6000 Blackwell = 1.5,H200 = 1.0);这个系数同时也是函数真正可用的运行时间 —— 声明 60 意味着约 90s 跑道。
107
+ - **声明值同时是运行时限,别为省额度调太小**:一旦实际跑超,ZeroGPU 会 abort 任务(`GPU task aborted`),配额照样扣、结果也拿不到。60(90s 跑道)按实测速度够跑约 7 分钟以内的音频;更长的歌按 `0.19s / 秒音频` 往上调 `gpu_declared_seconds`。
108
  - **首次调用偏慢**:RVC 音色模型是在第一次请求时才真正加载进显存的(`infer_rvc_python` 的惰性加载),之后会复用。
109
  - **并发**:页面请求会串行执行(内部有锁),避免多个任务互抢显存和中间文件。
110
  - 仅供学习与研究使用,请勿用于侵权或未授权的声音克隆。
app.py CHANGED
@@ -55,11 +55,9 @@ RT_CFG = CFG.get("runtime") or {}
55
  VOICES_CFG = [v for v in (CFG.get("voices") or []) if v.get("repo_id")]
56
 
57
  OUT_SR = int(RT_CFG.get("output_sr", 44100))
58
- MAX_GPU_SECONDS = int(RT_CFG.get("max_gpu_seconds", 180))
59
  # ZeroGPU 的 SDK 会把声明值乘上硬件系数再交给调度器(spaces/zero/configs.json 里
60
  # RTX PRO 6000 Blackwell 是 1.5),这个系数同时也是函数真正能跑的时间。
61
- # 所以配置里的秒数按「函数可用运行时间」理解,对外声明时除以系数,
62
- # 免得报错里的 requested 数字和配置对不上。
63
  GPU_DURATION_FACTOR = float(RT_CFG.get("gpu_duration_factor", 1.5))
64
  MIX_PEAK = float(RT_CFG.get("mix_peak", 0.97))
65
  PITCH_ALGO = str(RT_CFG.get("pitch_algo", "rmvpe+"))
@@ -283,22 +281,10 @@ def _convert_voice(voice_id: str, mono: np.ndarray, sr: int) -> np.ndarray:
283
 
284
 
285
  # --------------------------------------------------------------------------------------
286
- # 动态 GPU 时长估计(@spaces.GPU 的 duration 必须是"与主函数同签名"的可调用对象)
 
 
287
  # --------------------------------------------------------------------------------------
288
- def estimate_duration(audio_path, voice_id, pitch, index_rate, protect,
289
- filter_radius, rms_mix_rate, remove_harmony, process_harmony):
290
- """返回要**声明**给 @spaces.GPU 的秒数(SDK 会再乘 GPU_DURATION_FACTOR 交给调度器)。"""
291
- seconds = 0.0
292
- try:
293
- seconds = float(librosa.get_duration(path=audio_path))
294
- except Exception: # noqa: BLE001
295
- pass
296
- # 三段分离 + 1~2 次 RVC 的经验系数:每 1 秒音频约 0.8 秒 GPU 时间,另加固定开销
297
- needed = 45.0 + seconds * 0.8
298
- needed = max(60.0, min(float(MAX_GPU_SECONDS), needed))
299
- return max(1, int(needed / GPU_DURATION_FACTOR))
300
-
301
-
302
  def reserved_seconds(declared: int) -> int:
303
  """声明值换算成调度器实际预占的秒数 —— 就是报错里 "requested" 那个数。"""
304
  return int(declared * GPU_DURATION_FACTOR)
@@ -307,7 +293,7 @@ def reserved_seconds(declared: int) -> int:
307
  # --------------------------------------------------------------------------------------
308
  # 主流程
309
  # --------------------------------------------------------------------------------------
310
- @spaces.GPU(duration=estimate_duration)
311
  def generate(audio_path, voice_id, pitch, index_rate, protect, filter_radius,
312
  rms_mix_rate, remove_harmony, process_harmony):
313
  missing = [k for k in ("vocal_split", "karaoke_split", "dereverb_split") if k not in SEPARATORS]
@@ -400,12 +386,9 @@ def generate(audio_path, voice_id, pitch, index_rate, protect, filter_radius,
400
  f"音色:{voice_label}(变调 {int(pitch):+d} 半音)",
401
  f"时长:{total / OUT_SR:.1f}s 音频,耗时 {elapsed:.1f}s",
402
  ]
403
- _declared = estimate_duration(
404
- audio_path, voice_id, pitch, index_rate, protect,
405
- filter_radius, rms_mix_rate, remove_harmony, process_harmony,
406
- )
407
  notes.append(
408
- f"ZeroGPU:声明 {_declared}s → 预占配额 {reserved_seconds(_declared)}s"
 
409
  )
410
  if remove_harmony:
411
  notes.append("已勾选「移除和声」:和声不参与流程与合成")
 
55
  VOICES_CFG = [v for v in (CFG.get("voices") or []) if v.get("repo_id")]
56
 
57
  OUT_SR = int(RT_CFG.get("output_sr", 44100))
58
+ GPU_DECLARED_SECONDS = int(RT_CFG.get("gpu_declared_seconds", 60))
59
  # ZeroGPU 的 SDK 会把声明值乘上硬件系数再交给调度器(spaces/zero/configs.json 里
60
  # RTX PRO 6000 Blackwell 是 1.5),这个系数同时也是函数真正能跑的时间。
 
 
61
  GPU_DURATION_FACTOR = float(RT_CFG.get("gpu_duration_factor", 1.5))
62
  MIX_PEAK = float(RT_CFG.get("mix_peak", 0.97))
63
  PITCH_ALGO = str(RT_CFG.get("pitch_algo", "rmvpe+"))
 
281
 
282
 
283
  # --------------------------------------------------------------------------------------
284
+ # ZeroGPU 时长
285
+ # 实测:204s 音频 → 实际 38~40s(约 0.19 s/s),且配额按**实际耗时**扣,
286
+ # 但调度门槛按**声明值**预检查 —— 所以声明值固定为一个小常数最省事。
287
  # --------------------------------------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  def reserved_seconds(declared: int) -> int:
289
  """声明值换算成调度器实际预占的秒数 —— 就是报错里 "requested" 那个数。"""
290
  return int(declared * GPU_DURATION_FACTOR)
 
293
  # --------------------------------------------------------------------------------------
294
  # 主流程
295
  # --------------------------------------------------------------------------------------
296
+ @spaces.GPU(duration=GPU_DECLARED_SECONDS)
297
  def generate(audio_path, voice_id, pitch, index_rate, protect, filter_radius,
298
  rms_mix_rate, remove_harmony, process_harmony):
299
  missing = [k for k in ("vocal_split", "karaoke_split", "dereverb_split") if k not in SEPARATORS]
 
386
  f"音色:{voice_label}(变调 {int(pitch):+d} 半音)",
387
  f"时长:{total / OUT_SR:.1f}s 音频,耗时 {elapsed:.1f}s",
388
  ]
 
 
 
 
389
  notes.append(
390
+ f"ZeroGPU:声明 {GPU_DECLARED_SECONDS}s → 预占配额 "
391
+ f"{reserved_seconds(GPU_DECLARED_SECONDS)}s"
392
  )
393
  if remove_harmony:
394
  notes.append("已勾选「移除和声」:和声不参与流程与合成")
config.yaml CHANGED
@@ -71,16 +71,18 @@ voices:
71
  # --------------------------------------------------------------------------
72
  runtime:
73
  output_sr: 44100 # 输出采样率(三个分离模型都是 44100)
74
- max_gpu_seconds: 180 # 单次请求里「函数最多能跑多少秒 GPU 时间」(= 会被预占的配额)。
75
- # ZeroGPU 不看实际耗时,而是先按声明值预占:剩余配额 < 声明值
76
- # 就直接拒绝(quota exceeded),所以这个值只能用「当天还剩多少」来换。
77
- # 免费号每天总额度仅 300s,这里 180 一次就占掉 180s —— 免费号基本
78
- # 只能跑一首歌;PRO 号每天 2400s 才够用。
 
 
 
 
79
  gpu_duration_factor: 1.5 # ZeroGPU 的硬件系数,来自 spaces/zero/configs.json:
80
  # RTX PRO 6000 Blackwell = 1.5 / H200 = 1.0
81
  # 我们声明 N 秒,调度器按 N×1.5 申请,函数也真能跑 N×1.5 秒。
82
- # app.py 已按此系数换算,所以上面的 max_gpu_seconds 就是
83
- # 最终会出现在「requested vs. left」里的那个数字。
84
  mix_peak: 0.97 # 合并轨峰值归一化目标,防削波
85
  pitch_algo: rmvpe+ # 音高提取算法:rmvpe+ / rmvpe / crepe / pm / harvest
86
  model_cache_dir: /tmp/pymss_models # 分离模型下载缓存目录(容器临时盘)
 
71
  # --------------------------------------------------------------------------
72
  runtime:
73
  output_sr: 44100 # 输出采样率(三个分离模型都是 44100)
74
+ gpu_declared_seconds: 60 # 固定声明给 @spaces.GPU 的秒数。
75
+ # 实测:204s 音频实际只跑 38~40s(无损/低品质一样),
76
+ # 约 0.19 秒 GPU 时间 / 秒音频。
77
+ # 两条规则要一起看:
78
+ # 1) 门槛按声明值预检查 —— 剩余配额 < 声明值×系数 就直接拒绝,
79
+ # 所以声明值固定小一点,更容易过门(60 → 预占 90s)。
80
+ # 2) 声明值同时是运行时限(声明 N → 约 N×系数 秒跑道)。
81
+ # 60 对应 90s 跑道,按实测速度够跑约 7 分钟以内的音频;
82
+ # 如果以后要处理更长的歌,把它按 0.19s/s 往上调。
83
  gpu_duration_factor: 1.5 # ZeroGPU 的硬件系数,来自 spaces/zero/configs.json:
84
  # RTX PRO 6000 Blackwell = 1.5 / H200 = 1.0
85
  # 我们声明 N 秒,调度器按 N×1.5 申请,函数也真能跑 N×1.5 秒。
 
 
86
  mix_peak: 0.97 # 合并轨峰值归一化目标,防削波
87
  pitch_algo: rmvpe+ # 音高提取算法:rmvpe+ / rmvpe / crepe / pm / harvest
88
  model_cache_dir: /tmp/pymss_models # 分离模型下载缓存目录(容器临时盘)