import funcy import gc import os import pathlib import re import tempfile import time import modal from modal import enter import numpy as np from suno_utils.utils.display import suppress_logging from contextlib import redirect_stderr from suno_utils.utils.s3 import read_from_s3, upload_s3_files, list_s3_dir from suno_utils.utils.text import read_jsonl, write_jsonl from suno_utils.tasks.data_loader import load_audio_mp from suno_utils.diffusion import generation as diffusion_gen from suno_utils.worker.settings import s3_client N_MAX_REPLICAS = 1 MOUNT_PATH = "/suno/models" 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("api-callback-token"), modal.Secret.from_name("datadog-metrics"), ] base_image = ( modal.Image.debian_slim() .apt_install( "curl", "ffmpeg", "sox", "unzip", "libsox-fmt-mp3", "zlib1g-dev", "git" ) .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.4.0") .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", "rpyc", "biopython>=1.81", # TODO: don't love this depdendency, for hoot "pynvml", # for torch cuda utilization "torchsde", ) .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==2.5.9.post1 --no-build-isolation" ) .pip_install( "torch==2.5.0.dev20240728+cu121", "torchaudio==2.4.0.dev20240728+cu121", index_url="https://download.pytorch.org/whl/nightly/cu121", ) ) stub = modal.App("batch-upsample-gpu", image=base_image) # ## Defining the prediction function # # * Container lifecycle hook: this lets us load the model only once in each container # # Instead of a using `@stub.function()` in the global scope, # we put the method on a class, and define an `__enter__` method on that class. # Modal reuses containers for successive calls to the same function, so # we want to take advantage of this and avoid setting up the same model # for every function call. # Notes: # relative speeds: 5.5s on a5000, 7.5s on A10G, 18.5s on T4 # n_cpu on T4/A10G: 24 # audio load: 75s for 1k files on 24 cores (70h audio) ~3-4k realtime @stub.cls( gpu="A10G", timeout=2 * 60 * 60, concurrency_limit=N_MAX_REPLICAS, secrets=[modal.Secret.from_name("aws-bucket")], # cloud="oci", memory=150000, # youtube 5k needs about 100gb retries=3, ) class Worker: def __init__( self, encode_s3_dir, min_duration_s, max_duration_s, ): """ Args: encode_s3_dir: S3 directory to store upsampled audio files min_duration_s: Minimum duration of audio to consider max_duration_s: Maximum duration of audio to consider """ self.encode_s3_dir = encode_s3_dir self.min_duration_s = min_duration_s self.max_duration_s = max_duration_s @enter() def set_up_models(self): start_time = time.time() print("Start loading models") tokenizer_path = diffusion_gen.get_model_if_needed( diffusion_gen.TOKENIZER_FILEPATH, cache_dir=MOUNT_PATH ) semantic_model_path = diffusion_gen.get_model_if_needed( diffusion_gen.SEMANTIC_MODEL_FILEPATH, cache_dir=MOUNT_PATH ) semantic_clusters_path = diffusion_gen.get_model_if_needed( diffusion_gen.SEMANTIC_CLUSTERS_FILEPATH, cache_dir=MOUNT_PATH ) codec_path = diffusion_gen.get_model_if_needed( diffusion_gen.CODEC_FILEPATH, cache_dir=MOUNT_PATH ) dit_model_path = diffusion_gen.get_model_if_needed( diffusion_gen.DIT_MODEL_FILEPATH, cache_dir=MOUNT_PATH ) dit_config_path = diffusion_gen.get_model_if_needed( diffusion_gen.DIT_CONFIG_FILEPATH, cache_dir=MOUNT_PATH ) diffusion_gen.preload_models( tokenizer_filepath=tokenizer_path, semantic_model_filepath=semantic_model_path, semantic_clusters_filepath=semantic_clusters_path, codec_filepath=codec_path, dit_model_filepath=dit_model_path, dit_config_filepath=dit_config_path, compile=True, # can't compile with flash v2) ) print( f"Finish loading models. Took {round(time.time() - start_time, 2)} seconds" ) @staticmethod def download_models(dir_path=MOUNT_PATH): """Download diffusion models.""" print("Start downloading models") _ = diffusion_gen.get_model_if_needed( diffusion_gen.TOKENIZER_FILEPATH, cache_dir=dir_path ) _ = diffusion_gen.get_model_if_needed( diffusion_gen.SEMANTIC_MODEL_FILEPATH, cache_dir=dir_path ) _ = diffusion_gen.get_model_if_needed( diffusion_gen.SEMANTIC_CLUSTERS_FILEPATH, cache_dir=dir_path ) _ = diffusion_gen.get_model_if_needed( diffusion_gen.CODEC_FILEPATH, cache_dir=dir_path ) _ = diffusion_gen.get_model_if_needed( diffusion_gen.DIT_MODEL_FILEPATH, cache_dir=dir_path ) _ = diffusion_gen.get_model_if_needed( diffusion_gen.DIT_CONFIG_FILEPATH, cache_dir=dir_path ) print("Finish downloading models") @modal.method() def upsample(self, work_item): pass if __name__ == "__main__": encode_s3_dir = os.path.join(base_s3_dir, output_name) print("Downloading data...") metas = get_metas(os.path.join(base_s3_dir, "metas.jsonl")) print(len(metas), "data items loaded") work_items = list(funcy.chunks(chunksize, metas)) work_items = list(zip(range(len(work_items)), work_items)) print(len(work_items), "work items") worker = Worker( encode_s3_dir, min_duration_s, max_duration_s, ) print("Testing inference...") t0 = time.time() for work_item in work_items[:1]: _ = worker.embed.remote(work_item) print(f"{int(round(time.time()-t0))}s for test") print("Running batch inference...") t0 = time.time() _ = list(worker.embed.map(work_items[1:])) print(round((time.time() - t0) / 60 / 60), "h total for batch embed")