Spaces:
Running on Zero
Running on Zero
Commit ·
8b7ec64
1
Parent(s): 388ff02
ZeroGPU 声明时长固定为 60s(预占 90s),按实测 0.19s/s 更新文档
Browse files- README.md +6 -4
- app.py +7 -24
- config.yaml +9 -7
README.md
CHANGED
|
@@ -88,7 +88,8 @@ voices:
|
|
| 88 |
| 字段 | 默认 | 说明 |
|
| 89 |
| --- | --- | --- |
|
| 90 |
| `output_sr` | 44100 | 输出采样率(三个分离模型都是 44100) |
|
| 91 |
-
| `
|
|
|
|
| 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
|
| 104 |
-
- **
|
| 105 |
-
- **
|
|
|
|
| 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 |
-
|
| 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 |
-
#
|
|
|
|
|
|
|
| 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=
|
| 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:声明 {
|
|
|
|
| 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 |
-
|
| 75 |
-
#
|
| 76 |
-
#
|
| 77 |
-
#
|
| 78 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 # 分离模型下载缓存目录(容器临时盘)
|