Download app.py from Mukesh-98/MuseForge: direct link, hf CLI and curl.
- Browser
- Download file 9.66 kB
-
https://huggingface.co/spaces/Mukesh-98/MuseForge/resolve/main/app.py
- Command line
-
hf download hf://spaces/Mukesh-98/MuseForge/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Mukesh-98/MuseForge/resolve/main/app.py
9.66 kB
| """MuseForge HeartMuLa ZeroGPU Test — standalone real model check. | |
| Hugging Face ZeroGPU -> HeartMuLa 3B -> HeartCodec -> real Hindi MP3. | |
| Flow: | |
| 1. Space starts on CPU: checks disk, downloads the 3 public model repos | |
| only when their marker files are missing, then builds the heartlib | |
| pipeline with lazy_load=True (tokenizer + config only). NO GPU | |
| allocation and NO model weights in VRAM at startup. | |
| 2. Clicking "Generate MP3" calls the ZeroGPU-decorated function -> ZeroGPU | |
| allocates a GPU -> heartlib lazily loads HeartMuLa weights onto it, | |
| generates up to the requested audio length, decodes with HeartCodec | |
| and saves an MP3. heartlib's own _unload() (del + gc + empty_cache) | |
| frees VRAM after the forward pass and after codec decoding, before | |
| the function returns. | |
| Model repos (all public, Apache-2.0): | |
| - HeartMuLa/HeartMuLaGen -> ckpt/gen_config.json + ckpt/tokenizer.json | |
| - HeartMuLa/HeartMuLa-oss-3B -> ckpt/HeartMuLa-oss-3B/ | |
| - HeartMuLa/HeartCodec-oss-20260123 -> ckpt/HeartCodec-oss/ | |
| (the bare "HeartCodec-oss" repo is gated/not downloadable — 401 on the | |
| Hub API; the official heartlib README recommends the -20260123 codec, | |
| which is public. Swap HEARTMULA_REPO to the happy-new-year variant by | |
| changing one constant if desired.) | |
| """ | |
| import gc | |
| import os | |
| import shutil | |
| import threading | |
| import time | |
| import traceback | |
| from pathlib import Path | |
| import spaces | |
| import gradio as gr | |
| import torch | |
| try: | |
| from heartlib.pipelines.music_generation import HeartMuLaGenPipeline | |
| except ModuleNotFoundError: | |
| # heartlib pins transformers==4.57.0 (needs huggingface-hub<1.0), which | |
| # conflicts with the Space runtime's gradio 6.24.0 (needs huggingface-hub | |
| # >=1.16). Install heartlib with --no-deps so pip does not enforce its | |
| # incompatible pins; all real runtime deps are pinned in requirements.txt. | |
| import subprocess | |
| import sys | |
| print("[startup] installing heartlib (--no-deps, pinned commit)...", flush=True) | |
| subprocess.check_call( | |
| [ | |
| sys.executable, | |
| "-m", | |
| "pip", | |
| "install", | |
| "--no-deps", | |
| "heartlib @ git+https://github.com/HeartMuLa/heartlib.git@3783bdb8441f2c298b1e64c8651173aac200361c", | |
| ] | |
| ) | |
| from heartlib.pipelines.music_generation import HeartMuLaGenPipeline | |
| APP_DIR = Path("/home/user/app") | |
| CKPT_DIR = APP_DIR / "ckpt" | |
| VERIFY_DIR = APP_DIR / "verify" | |
| HEARTMULA_REPO = "HeartMuLa/HeartMuLa-oss-3B" | |
| CODEC_REPO = "HeartMuLa/HeartCodec-oss-20260123" | |
| GEN_CONFIG_REPO = "HeartMuLa/HeartMuLaGen" | |
| MODEL_VERSION = "3B" | |
| DEFAULT_LYRICS = """[verse] | |
| रात के सन्नाटे में | |
| एक नई सुबह की आहट है | |
| गिरकर भी मैं उठता हूँ | |
| दिल में अब भी चाहत है | |
| [chorus] | |
| मैं फिर उठूँगा | |
| मैं फिर चलूँगा | |
| हज़ार बार गिरकर | |
| फिर संभलूँगा | |
| [verse] | |
| रास्ते चाहे मुश्किल हों | |
| आँधियाँ चाहे तेज़ चलें | |
| मेरे कदम नहीं रुकेंगे | |
| जब तक सपने साथ चलें | |
| [chorus] | |
| मैं फिर उठूँगा | |
| मैं फिर चलूँगा | |
| हज़ार बार गिरकर | |
| फिर संभलूँगा""" | |
| DEFAULT_TAGS = "hindi, cinematic, emotional, powerful male vocal, warm vocal, piano, strings, atmospheric, melodic" | |
| GEN_LOCK = threading.Lock() | |
| PIPE = None | |
| # ---------------------------------------------------------------- startup (CPU) | |
| def _download_if_missing(repo_id: str, local_dir: Path, marker: Path) -> None: | |
| if marker.exists(): | |
| print(f"[startup] {repo_id}: already present ({marker.name})", flush=True) | |
| return | |
| from huggingface_hub import snapshot_download | |
| print(f"[startup] downloading {repo_id} -> {local_dir} ...", flush=True) | |
| snapshot_download(repo_id=repo_id, local_dir=str(local_dir)) | |
| if not marker.exists(): | |
| raise RuntimeError(f"{repo_id} download finished but {marker} is missing") | |
| def _ensure_models() -> None: | |
| total, used, free = shutil.disk_usage(APP_DIR) | |
| print(f"[startup] disk: {free / 1024**3:.1f} GB free (models need ~22.4 GB)", flush=True) | |
| if free < 24 * 1024**3: | |
| raise RuntimeError( | |
| f"Insufficient disk: {free / 1024**3:.1f} GB free, need >= 24 GB" | |
| ) | |
| _download_if_missing(GEN_CONFIG_REPO, CKPT_DIR, CKPT_DIR / "gen_config.json") | |
| _download_if_missing( | |
| HEARTMULA_REPO, | |
| CKPT_DIR / "HeartMuLa-oss-3B", | |
| CKPT_DIR / "HeartMuLa-oss-3B" / "model.safetensors.index.json", | |
| ) | |
| _download_if_missing( | |
| CODEC_REPO, | |
| CKPT_DIR / "HeartCodec-oss", | |
| CKPT_DIR / "HeartCodec-oss" / "model.safetensors.index.json", | |
| ) | |
| print(f"[startup] PyTorch {torch.__version__} (CUDA emulation outside the ZeroGPU decorator)", flush=True) | |
| _ensure_models() | |
| print("[startup] creating heartlib pipeline with lazy_load=True (no GPU used yet)...", flush=True) | |
| PIPE = HeartMuLaGenPipeline.from_pretrained( | |
| str(CKPT_DIR), | |
| device=torch.device("cuda"), | |
| dtype={"mula": torch.bfloat16, "codec": torch.float32}, | |
| version=MODEL_VERSION, | |
| lazy_load=True, | |
| ) | |
| print("[startup] pipeline ready. HeartMuLa/HeartCodec weights load on first generation.", flush=True) | |
| # ---------------------------------------------------------------- GPU function | |
| def _allocation_duration(lyrics: str, tags: str, duration_sec: float) -> int: | |
| """Declared ZeroGPU runtime ceiling (quota is billed on actual runtime). | |
| The spaces SDK multiplies this value by its duration_factor (1.5 on | |
| zero-a10g -> RTX PRO 6000 Blackwell) before requesting ZeroGPU, so cap | |
| at 196 so the SDK-equivalent request is 196 * 1.5 = 294s, below the | |
| 300s free quota (300s requests are rejected). | |
| """ | |
| return min(196, 120 + int(duration_sec) * 2) | |
| def generate_music(lyrics: str, tags: str, duration_sec: float): | |
| """Runs inside a real ZeroGPU allocation. Returns (mp3_path, info_text).""" | |
| started = time.time() | |
| current_stage = "unknown" | |
| def stage(name: str) -> None: | |
| print(f"[generation] {name} (+{time.time() - started:.1f}s)", flush=True) | |
| try: | |
| props = torch.cuda.get_device_properties(0) | |
| info = [ | |
| f"GPU: {props.name}", | |
| f"VRAM total: {props.total_memory / 1024**3:.2f} GB", | |
| f"CUDA: {torch.version.cuda}", | |
| f"PyTorch: {torch.__version__}", | |
| ] | |
| VERIFY_DIR.mkdir(parents=True, exist_ok=True) | |
| save_path = str(VERIFY_DIR / "hindi_heartmula_test.mp3") | |
| if os.path.exists(save_path): | |
| os.remove(save_path) | |
| max_ms = int(max(10, min(int(duration_sec), 240)) * 1000) | |
| current_stage = "loading model" | |
| stage("Loading model (HeartMuLa weights -> VRAM)") | |
| t0 = time.time() | |
| _ = PIPE.mula # heartlib lazy weight load | |
| info.append(f"model load: {time.time() - t0:.1f}s") | |
| current_stage = "generating" | |
| stage("Generating (HeartMuLa forward)") | |
| t0 = time.time() | |
| PIPE( | |
| {"lyrics": lyrics, "tags": tags}, | |
| max_audio_length_ms=max_ms, | |
| topk=50, | |
| temperature=1.0, | |
| cfg_scale=1.5, | |
| save_path=save_path, | |
| ) | |
| info.append(f"generation + codec decode: {time.time() - t0:.1f}s") | |
| current_stage = "processing audio" | |
| stage("Processing audio (MP3 saved)") | |
| if not os.path.isfile(save_path) or os.path.getsize(save_path) < 10_000: | |
| raise RuntimeError("MP3 output missing or too small (silence/corrupt audio?)") | |
| info.append(f"peak VRAM used: {torch.cuda.max_memory_allocated() / 1024**3:.2f} GB") | |
| info.append(f"output: {save_path}") | |
| info.append(f"total time: {time.time() - started:.1f}s") | |
| info.append("STATUS: SUCCESS") | |
| stage("Completed") | |
| return save_path, "\n".join(info) | |
| except Exception as exc: | |
| traceback.print_exc() | |
| try: | |
| mem = ( | |
| f"\nCUDA allocated: {torch.cuda.memory_allocated() / 1024**3:.2f} GB, " | |
| f"reserved: {torch.cuda.memory_reserved() / 1024**3:.2f} GB" | |
| ) | |
| except Exception: | |
| mem = "" | |
| return "", ( | |
| "STATUS: FAILED\n" | |
| f"stage: {current_stage}\n" | |
| f"error type: {type(exc).__name__}\n" | |
| f"error: {exc}{mem}" | |
| ) | |
| finally: | |
| gc.collect() | |
| torch.cuda.empty_cache() | |
| def generate_clicked(lyrics: str, tags: str, duration_sec: float): | |
| with GEN_LOCK: # one generation at a time, ever | |
| return generate_music(lyrics or DEFAULT_LYRICS, tags or DEFAULT_TAGS, int(duration_sec or 40)) | |
| # ---------------------------------------------------------------- Gradio UI | |
| with gr.Blocks(title="MuseForge HeartMuLa ZeroGPU Test") as demo: | |
| gr.Markdown("# MuseForge HeartMuLa ZeroGPU Test") | |
| lyrics = gr.Textbox(label="Lyrics", value=DEFAULT_LYRICS, lines=14) | |
| tags = gr.Textbox(label="Music tags", value=DEFAULT_TAGS, lines=2) | |
| duration = gr.Slider(label="Duration (seconds)", minimum=10, maximum=120, value=40, step=5) | |
| generate_btn = gr.Button("Generate MP3", variant="primary") | |
| audio = gr.Audio(label="Generated MP3", type="filepath") | |
| info = gr.Textbox(label="GPU status / result", lines=14) | |
| generate_btn.click(generate_clicked, inputs=[lyrics, tags, duration], outputs=[audio, info]) | |
| demo.launch() | |