Spaces:
Running on Zero
Running on Zero
Upload 3 files
Browse files- README.md +27 -10
- app.py +95 -47
- requirements.txt +10 -8
README.md
CHANGED
|
@@ -1,16 +1,33 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
|
| 8 |
-
python_version: '3.12'
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
-
hf_oauth: true
|
| 12 |
-
hf_oauth_scopes:
|
| 13 |
-
- inference-api
|
| 14 |
---
|
| 15 |
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: ZeroGPU Strip Bench
|
| 3 |
+
emoji: ⏱️
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
sdk: gradio
|
| 7 |
+
python_version: "3.10"
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
|
|
|
|
|
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# ZeroGPU strip-pass benchmark (throwaway test app)
|
| 13 |
+
|
| 14 |
+
Standalone benchmark to measure real timing of the vocal-isolation pass — the
|
| 15 |
+
same model the live Voice Remover's Step 1 uses
|
| 16 |
+
(`mel_band_roformer_vocals_becruily`) — on this Space's ZeroGPU hardware.
|
| 17 |
+
|
| 18 |
+
Not connected to the production site, its password gate, or its backend.
|
| 19 |
+
Purpose is purely to get real load-time / inference-time numbers before
|
| 20 |
+
deciding whether ZeroGPU is worth building into the actual product.
|
| 21 |
+
|
| 22 |
+
## Use
|
| 23 |
+
|
| 24 |
+
Upload a song, click "Run strip pass". Reports model-load time, separation
|
| 25 |
+
time, total, the GPU name, and an estimate of how many runs the 2-min/day free
|
| 26 |
+
IP-based quota buys per visitor (excludes any per-region splits on top).
|
| 27 |
+
|
| 28 |
+
## Deploy
|
| 29 |
+
|
| 30 |
+
Space hardware must be set to ZeroGPU and SDK to Gradio (already required for
|
| 31 |
+
`spaces.GPU` to work at all — Docker/Static Spaces can't schedule onto
|
| 32 |
+
ZeroGPU). Upload `app.py`, `requirements.txt`, and this `README.md` to the
|
| 33 |
+
Space's file root.
|
app.py
CHANGED
|
@@ -1,52 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
-
import
|
|
|
|
|
|
|
|
|
|
| 3 |
import spaces
|
|
|
|
| 4 |
import torch
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
)
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
-
demo.launch()
|
|
|
|
| 1 |
+
"""ZeroGPU strip-pass benchmark — throwaway test app.
|
| 2 |
+
|
| 3 |
+
Standalone Gradio app, NOT part of the production site. Runs the exact same
|
| 4 |
+
vocal-isolation model the live Voice Remover's Step 1 uses
|
| 5 |
+
(mel_band_roformer_vocals_becruily) and times it on this Space's ZeroGPU
|
| 6 |
+
hardware, so we can decide with real numbers whether ZeroGPU is viable as a
|
| 7 |
+
backend before touching anything production-facing.
|
| 8 |
+
|
| 9 |
+
Deploy: create/point a ZeroGPU-hardware Space at this folder (sdk: gradio,
|
| 10 |
+
see README.md). Upload a song, click "Run strip pass".
|
| 11 |
+
"""
|
| 12 |
import os
|
| 13 |
+
import time
|
| 14 |
+
import traceback
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
|
| 17 |
import spaces
|
| 18 |
+
import gradio as gr
|
| 19 |
import torch
|
| 20 |
+
import torchaudio
|
| 21 |
+
from audio_separator.separator import Separator
|
| 22 |
+
|
| 23 |
+
MODELS_DIR = Path(os.environ.get("MODELS_DIR", "/tmp/models"))
|
| 24 |
+
MODELS_DIR.mkdir(parents=True, exist_ok=True)
|
| 25 |
+
|
| 26 |
+
# Same model the live site's Step 1 ("Isolate vocals — becruily") uses.
|
| 27 |
+
VOCAL_MODEL = "mel_band_roformer_vocals_becruily.ckpt"
|
| 28 |
+
|
| 29 |
+
# Fetch the checkpoint once at Space startup — CPU-only, no GPU needed — so the
|
| 30 |
+
# timed run below measures disk->GPU load, not an internet download.
|
| 31 |
+
print(f"[startup] fetching {VOCAL_MODEL} ...", flush=True)
|
| 32 |
+
_warm = Separator(output_dir="/tmp", model_file_dir=str(MODELS_DIR))
|
| 33 |
+
_warm.load_model(model_filename=VOCAL_MODEL)
|
| 34 |
+
del _warm
|
| 35 |
+
print("[startup] model cached on disk.", flush=True)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _normalize(src: str) -> str:
|
| 39 |
+
"""Approximates production's _normalize_for_sep (stereo / 32-bit float /
|
| 40 |
+
min 30s) so the timed input shape matches what RoFormer sees live."""
|
| 41 |
+
wf, sr = torchaudio.load(src)
|
| 42 |
+
if wf.shape[0] == 1:
|
| 43 |
+
wf = wf.repeat(2, 1)
|
| 44 |
+
elif wf.shape[0] > 2:
|
| 45 |
+
wf = wf[:2]
|
| 46 |
+
min_samples = sr * 30
|
| 47 |
+
if wf.shape[1] < min_samples:
|
| 48 |
+
pad = torch.zeros(wf.shape[0], min_samples - wf.shape[1], dtype=wf.dtype)
|
| 49 |
+
wf = torch.cat([wf, pad], dim=1)
|
| 50 |
+
out = "/tmp/_bench_input.wav"
|
| 51 |
+
torchaudio.save(out, wf, sr, encoding="PCM_F", bits_per_sample=32)
|
| 52 |
+
return out
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
@spaces.GPU(duration=110)
|
| 56 |
+
def run_benchmark(song_path):
|
| 57 |
+
if song_path is None:
|
| 58 |
+
return "Upload a song first."
|
| 59 |
+
|
| 60 |
+
try:
|
| 61 |
+
gpu_name = torch.cuda.get_device_name(0) if torch.cuda.is_available() else "no GPU visible"
|
| 62 |
+
|
| 63 |
+
t0 = time.perf_counter()
|
| 64 |
+
sep = Separator(output_dir="/tmp", output_format="WAV", model_file_dir=str(MODELS_DIR))
|
| 65 |
+
sep.load_model(model_filename=VOCAL_MODEL)
|
| 66 |
+
t1 = time.perf_counter()
|
| 67 |
+
|
| 68 |
+
sep_input = _normalize(song_path)
|
| 69 |
+
out_names = sep.separate(sep_input)
|
| 70 |
+
t2 = time.perf_counter()
|
| 71 |
+
|
| 72 |
+
load_s, sep_s, total_s = t1 - t0, t2 - t1, t2 - t0
|
| 73 |
+
runs_per_day = max(1, int(120 // total_s)) if total_s > 0 else "?"
|
| 74 |
+
|
| 75 |
+
return (
|
| 76 |
+
f"GPU: {gpu_name}\n\n"
|
| 77 |
+
f"Model load + move-to-device: {load_s:.1f}s\n"
|
| 78 |
+
f"Separation (inference): {sep_s:.1f}s\n"
|
| 79 |
+
f"Total: {total_s:.1f}s\n\n"
|
| 80 |
+
f"Output stems: {out_names}\n\n"
|
| 81 |
+
f"2 min/day free quota ÷ this total ≈ {runs_per_day} run(s)/day per visitor "
|
| 82 |
+
f"(before counting any region splits on top of this strip pass)."
|
| 83 |
+
)
|
| 84 |
+
except Exception:
|
| 85 |
+
return "Error:\n" + traceback.format_exc()
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
with gr.Blocks(title="ZeroGPU strip-pass benchmark") as demo:
|
| 89 |
+
gr.Markdown(
|
| 90 |
+
"### ZeroGPU strip-pass benchmark\n"
|
| 91 |
+
"Throwaway test app — not the production site. Upload a song and run "
|
| 92 |
+
"the same vocal-isolation model the live site's Step 1 uses, timed on "
|
| 93 |
+
"this Space's GPU, to get real numbers on whether ZeroGPU is viable."
|
| 94 |
)
|
| 95 |
+
audio_in = gr.Audio(label="Song", type="filepath")
|
| 96 |
+
run_btn = gr.Button("Run strip pass", variant="primary")
|
| 97 |
+
result = gr.Textbox(label="Timing", lines=9)
|
| 98 |
+
run_btn.click(fn=run_benchmark, inputs=audio_in, outputs=result)
|
| 99 |
|
| 100 |
+
demo.launch()
|
requirements.txt
CHANGED
|
@@ -1,8 +1,10 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
|
|
|
|
|
|
|
|
| 1 |
+
torch==2.5.1
|
| 2 |
+
torchaudio==2.5.1
|
| 3 |
+
numpy>=2.0,<2.1
|
| 4 |
+
numba>=0.60
|
| 5 |
+
audio-separator==0.44.2
|
| 6 |
+
pyyaml==6.0.3
|
| 7 |
+
soundfile==0.13.1
|
| 8 |
+
librosa==0.11.0
|
| 9 |
+
scipy==1.15.3
|
| 10 |
+
certifi
|