import json import time from typing import Any import pathlib from uuid import uuid4 import modal from torchaudio.pipelines import HDEMUCS_HIGH_MUSDB_PLUS from openai import OpenAI from suno_utils.audio import Audio from suno_utils.gpt import chirp_v2 from suno_utils.worker.loader import S3Loader from suno_utils.worker.modal_base import MODAL_MOUNTS from suno_utils.worker.schema import QueueItem from suno_utils.worker.utils import recursive_ls_dir base_image = ( modal.Image.debian_slim() .apt_install("curl", "ffmpeg", "sox", "unzip", "libsox-fmt-mp3") .run_commands( [ 'curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"', "unzip -q awscliv2.zip", "./aws/install", ] ) .pip_install( "torch==2.1.0.dev20230831+cu118", "torchaudio==2.2.0.dev20230831+cu118", index_url="https://download.pytorch.org/whl/nightly/cu118", ) .pip_install_private_repos( "github.com/suno-ai/glockenspiel.git@f05e2f251#subdirectory=descript-audio-codec&egg=descript-audio-codec", git_user="mcamac", secrets=[modal.Secret.from_name("victor-modal-github-token")], ) .pip_install( "boto3", "transformers", "tokenizers", "encodec", "ctc_segmentation", "psutil", "redis", "gradio", "pydantic", "nnAudio", ) .pip_install_from_pyproject( str(pathlib.Path(__file__).parent.parent.parent / "pyproject.toml"), ) .run_commands( "FLASH_ATTENTION_SKIP_CUDA_BUILD=TRUE pip install flash-attn --no-build-isolation", ) ) MOUNT_PATH = "/suno/models" MODEL_VERSION = "2.0.0.0" class ChirpV2Worker(S3Loader): gpu_id: int def __init__(self, gpu_id, bg_image=None): super().__init__(bg_image=bg_image) self.gpu_id = gpu_id self.bg_image = bg_image def preload(self): start_time = time.time() chirp_v2.preload_models( cache_dir=MOUNT_PATH, local_whisper="/suno/models/whisper", load_codec_device="cuda" ) finish_time = time.time() print(f"Preloading took {finish_time - start_time}s") @staticmethod def download_models(dir_path=MOUNT_PATH): """Use AWS CLI to download models if they don't exist.""" chirp_v2.preload_models( fetch_only=True, cache_dir=dir_path, load_semantic=False, load_codec_device="cuda" ) recursive_ls_dir(dir_path) HDEMUCS_HIGH_MUSDB_PLUS.get_model() import whisper whisper.load_model("small") whisper.load_model("small.en") def process_item(self, item: QueueItem) -> tuple[list[Audio], list[Any]]: history_audio = self._load_audio_prompt(item, MODEL_VERSION) if item.prompt_audio else None options = item.metadata.get("options", {}) or {} n_batch = 2 chaos = options.get("chaos", 1) chaos = float(chaos) text = item.prompt_text or "" text_tags = item.metadata.get("tags", None) if text_tags == "random": text_tags = None detected_lang = chirp_v2._get_text_lang(text) cfg_coef = 1.25 # cfg_coef = float(options.get("lyrics_strength", 1.2)) # condition on non-english, higher cfg text if (detected_lang is not None and not detected_lang == "en") and text: cfg_coef = 1.7 print(f"detected non-english lyrics, increase cfg, lang is {detected_lang}") # condition on genre, rap elif text_tags and ("rap" in text_tags or "hip-hop" in text_tags): cfg_coef = 1.4 text_neg_tags = None if "instrumental" not in (text_tags or "") and len(text.split()) > 5: text_neg_tags = "instrumental noise" # cfg_coef_tags = float(options.get("tags_strength", 1.75)) cfg = chirp_v2.GenerationConfig( text=text, text_tags=text_tags or None, text_neg_tags=text_neg_tags, n_batch=n_batch, max_gen_duration_s=80, cfg_coef=cfg_coef, cfg_coef_tags=1.9, temp_semantic=0.9, temp_coarse=0.85, min_text_offset=128, top_k_semantic=1000, top_k_coarse=1000, top_p_semantic=None, top_p_coarse=None, rep_penality=0, ) MAX_HISTORY_DURATION_S = 50 MAX_CONTINUATION_DURATION_S = 60 if isinstance(history_audio, Audio): history_audio = history_audio.get_segment( from_s=max(0, history_audio.duration_s - MAX_HISTORY_DURATION_S) ) cfg = cfg.modify(max_gen_duration_s=MAX_CONTINUATION_DURATION_S) elif history_audio is not None: # NPZ # history_arrs are passed through GenerationConfig cfg = cfg.modify( max_gen_duration_s=MAX_CONTINUATION_DURATION_S, history_arr=history_audio, cfg_coef_tags=0, ) history_audio = None _, raw_arrays, audios = chirp_v2.generate_audio( cfg, chaos_level=chaos, history_audio=history_audio, return_raw_arrays=True, max_history_duration_s=MAX_HISTORY_DURATION_S, ) return audios, raw_arrays aws_secret = modal.Secret.from_name("studio-aws") SECRETS = [ aws_secret, modal.Secret.from_dict( { "SUNO_ASSETS_PATH": "/suno/models/assets", "XDG_CACHE_HOME": "/suno/models/", } ), modal.Secret.from_name("openai-secret"), ] def download_model_wrapper_a4(): ChirpV2Worker.download_models() image = base_image.run_function(download_model_wrapper_a4, secrets=SECRETS) ### CHANGE ME ENV ENV_NAME = "dev" STUB_NAME = f"chirp-v2-{ENV_NAME}" stub = modal.Stub(STUB_NAME, image=image) #### CONCURRENCY_LIMITS = { "priority": 80, "staging": 600, "prod": 600, "dev": 5, } @stub.cls( cpu=2.0, # memory=16384, gpu=modal.gpu.A10G(count=1), secrets=SECRETS + [ modal.Secret.from_name("api-callback-token"), ], timeout=300, container_idle_timeout=400, mounts=MODAL_MOUNTS, memory=60000, retries=modal.Retries( max_retries=3, backoff_coefficient=2.0, initial_delay=10.0, ), keep_warm=1, concurrency_limit=CONCURRENCY_LIMITS.get(ENV_NAME, 80), ) class ChirpV2Stub: def __enter__(self): import torch num_gpus = torch.cuda.device_count() print(f"Found {num_gpus} GPUs.") recursive_ls_dir("/suno/models") self.worker = ChirpV2Worker(0, bg_image="/suno/models/assets/wave-bg-2.png") self.worker.preload() self.openai_client = OpenAI() @modal.method() def generate(self, queue_item: str): import json from suno_utils.worker.schema import QueueItem print(queue_item) start_time = time.time() item = QueueItem(**json.loads(queue_item)) assert item.ids item_id = item.id print(item.metadata) is_square = True ids = [] f = modal.Function.lookup( f"sdxl-{'dev' if ENV_NAME == 'dev' else 'prod'}", "StableDiffusion.generate_image_item", ) fn_calls = [ f.spawn( item.copy(deep=True, update={"id": item_id, "ids": None}).json(), ) for item_id in item.ids ] try: audios, raw_arrays = self.worker.process_item(item) items = [] for i, audio in enumerate(audios): new_id = item.ids[i] ids.append(new_id) new_item = item.copy(deep=True, update={"id": new_id, "ids": None}) # item.prompt_text = trimmed[i] items.append(new_item) self.worker._write_audio_only( new_item, audio, ) self.worker._write_npz(new_item, raw_arrays[i], STUB_NAME, MODEL_VERSION) # image_urls = [fn_call.get(timeout=None) for fn_call in fn_calls] # print("images located", image_urls) for i, item in enumerate(items): item.notify_progress( { "id": item.id, "model": "chirp-v2-xxl-alpha", "n_audios": 1, "ok": 1, "gen_duration": time.time() - start_time, "ids": [item.id], "durations": [audios[i].duration_s], }, ) video_time = time.time() f = modal.Function.lookup("videos-v2-prod", "DummyV0Stub.write_video") # Start the video jobs. for i, item in enumerate(items): f.spawn(item.json(), f"image_{item.id}.png", is_square) print("Spawning videos", time.time() - video_time) return item_id except Exception as e: import traceback finish_time = time.time() item.notify_progress( { "type": "error", "id": item_id, "model": "chirp-v2-xxl-alpha", "n_audios": len(ids), "ok": 0, "gen_duration": finish_time - start_time, }, ) traceback.print_exc() raise e @stub.local_entrypoint() def main(): uid = str(uuid4()) inputs = [ json.dumps( dict( id=uid, prompt_text="""Hello world""", metadata={"tags": "pop"}, ids=[str(uuid4()), str(uuid4())], ), ), json.dumps( dict( id=uid, prompt_text="""你好""", metadata={"tags": "pop"}, ids=[str(uuid4()), str(uuid4())], ), ), ] model = ChirpV2Stub() print(uid) for input in inputs: model.generate.remote( input, )