import os import time import modal import torch import pathlib import funcy import pandas as pd import numpy as np import tempfile from suno_utils.gpt.generation import GenerationConfig from suno_utils.gpt.engine import Engine from suno_utils.gpt.generation_engine import make_request from suno_utils.worker.settings import s3_client from suno_utils.worker.modal_base import MODAL_MOUNTS from suno_utils.diffusion import generation as diffusion_gen from suno_utils.gpt import chirp_v2_5 as chirp_v2 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", ] ) .dockerfile_commands( [ "COPY --from=datadog/serverless-init:1.2.1 /datadog-init /app/datadog-init", 'ENTRYPOINT ["/app/datadog-init"]', ] ) .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", "funcy", "fasttext", ) .pip_install_from_pyproject( str( "/home/christian/code/glockenspiel/suno_utils/pyproject.toml", ), # force_build=True, ) .pip_install("torch==2.4.0") # this is cause flash-attn can't work with 2.5 yet .run_commands( "FLASH_ATTENTION_SKIP_CUDA_BUILD=TRUE pip install flash-attn==2.6.3 --no-build-isolation", ) .pip_install( "torch==2.5.1", "torchaudio==2.5.1", "transformers==4.30.0", "numpy==1.26.4", ) ) class GenerateWorker: def __init__(self, gpt_ckpt: str, dit_model_filepath: str, output_path: str): self.output_path = output_path start_time = time.time() print("Start loading models") gpt_ckpt_path = chirp_v2._get_model_if_needed(gpt_ckpt, cache_dir=MOUNT_PATH) tokenizer_path = chirp_v2._get_model_if_needed( chirp_v2.TOKENIZER_PATH, cache_dir=MOUNT_PATH ) print(tokenizer_path) N_BATCH = 2 engine = Engine( gpt_ckpt_path, tokenizer_path=tokenizer_path, max_sequences=4 * N_BATCH, compile=False, ) cfg = engine.model.config self.engine = engine self.cfg = cfg # load gpt model num_gpus = torch.cuda.device_count() cuda_device = torch.cuda.current_device() print(f"Found {num_gpus} GPUs. Using GPU {cuda_device}.") 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( dit_model_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, compile=True, # can't compile with flash v2) ) # diff_model_fp = "/app/suno/checkpoints/2025-01-19_05-10-13_s1983/step_3000_infer.pt" # best # diff_model_fp = "/app/suno/checkpoints/2024-12-16_15-54-26_s3031/last_ckpt_infer.pt" # diff_model_fp = "s3://suno-data/georg/tmp/2b_prefix_ft.pt" # codec_filepath = "s3://suno-data/minz/models/dac_vae_fixed_25hz.pth" # codec_filepath = "s3://suno-data/christian/25hz_vae_peaq_kl_0.005.pth" # tokenizer_filepath = "s3://suno-data/georg/models/tokenizers/tokenizer_60k.json" # semantic_model_filepath = "s3://suno-data/georg/models/semantic/mert_25.pt" # semantic_clusters_filepath = ( # "s3://suno-data/georg/models/semantic/mert_25_2x4k.npy" # ) if "dac_vae_fixed" in codec_path or "dac_vae_tuned" in codec_path: patch_size = 1 codec_scale_factor = 0.4 elif "convnext_vae_tuned" in codec_path: patch_size = 1 codec_scale_factor = 1.0 else: patch_size = 1 codec_scale_factor = 2.5 print( f"Finish loading models. Took {round(time.time() - start_time, 2)} seconds" ) @staticmethod def download_models(gpt_ckpt, dit_model_filepath, 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(dit_model_filepath, cache_dir=dir_path) _ = chirp_v2._get_model_if_needed(gpt_ckpt, cache_dir=dir_path) _ = chirp_v2._get_model_if_needed(chirp_v2.TOKENIZER_PATH, cache_dir=dir_path) print("Finish downloading models") def generate(self, work_item): for item in work_item: n_skip_semantic = 1 gconf = GenerationConfig( text=item["text"], text_tags=item["tags"], cfg_coef=1.0, cfg_coef_tags=1.0, cfg_coef_max_steps=None, cfg_coef_tags_max_steps=None, n_repeat_tags=1, n_skip_semantic=1, # text_start_control_tags="{duration:210}", temp_semantic=0.92, n_batch=1, min_text_offset=0, eos_pad_duration_s=0, max_gen_duration_s=int(8 * 60 / n_skip_semantic), random_seed=np.random.randint(0, 1000000), ) requests = [ make_request( f"{i}", gconf, self.engine.model.config, self.engine.tokenizer ) for i in range(1) ] jobs = self.engine.run_request(requests, tqdm_enabled=True) out_gpt = [] for n, job in enumerate(jobs): stream = self.engine.token_generator(job) arr = torch.stack(list(stream))[:, 1] if arr[-1] == 4000: arr = arr[:-1] print(f"{round(arr.shape[-1]/25*n_skip_semantic)}s for track {n}") # do stuff incase skip arr2 = ( torch.zeros(arr.shape[0] * n_skip_semantic, dtype=arr.dtype) + self.cfg.semantic_pad_token ) arr2[::n_skip_semantic] = arr # add out_gpt.append(arr2) # perform upsampling with multiple seeds diffusion_inference_config = diffusion_gen.DiffusionGenerationConfig( audio=out_gpt[0], lyrics=item["text"], tags=item["tags"], text_cfg_coef=1.5, ctx_cfg_coef=1.0, steps=16, seed=np.random.randint(0, 1000000), ) upsampled_audio = diffusion_gen.generate(diffusion_inference_config) # we want to save out # 3. mp3 audio file with tempfile.TemporaryDirectory() as td: base_filename = f"{item['id']}" # save audio to s3 upsampled_audio_path = os.path.join(td, f"{base_filename}.mp3") upsampled_audio.write_hq_mp3(upsampled_audio_path) s3_filepath = os.path.join(self.output_path, f"{base_filename}.mp3") print(f"Uploading to {s3_filepath}") s3_client.upload_file( upsampled_audio_path, "suno-data", s3_filepath, ExtraArgs={ "ContentType": "audio/mp3", }, ) # base model 6b GPT_CKPT = ( "s3://suno-data/christian/checkpoints/gpt/2025-02-04_21-04-31-last_ckpt_infer.pt" ) # 6b v2 finetune # GPT_CKPT = ( # "s3://suno-data/christian/checkpoints/gpt/2025-02-10_16-52-41-step_9000_infer.pt" # ) # GPT_CKPT = "s3://suno-data/christian/checkpoints/gpt/2025-02-13_11-27-29-step_10_000.pt" # prod diffusion model DIT_MODEL_FILEPATH = "s3://suno-data/tony/tmp/diff/dit_v3_dpo_t10_3k_5e6_b100_t25.pt" # DIT_MODEL_FILEPATH = "s3://suno-data/georg/tmp/2b_prefix_ft.pt" def download_model_wrapper_d(): # this print is necessary to have modal rerun this when MODEL changes # Modal tracks referenced global variables # Change the name of the function to force a rerun print("Downloading model for history encoder") GenerateWorker.download_models(GPT_CKPT, DIT_MODEL_FILEPATH) image = base_image.run_function(download_model_wrapper_d, secrets=SECRETS) APP_NAME = f"batch-generate-eval-gpu" app = modal.App(APP_NAME, image=image, secrets=SECRETS) N_MAX_REPLICAS = 8 @app.cls( gpu=modal.gpu.H100(count=1), cpu=4, secrets=SECRETS, timeout=2 * 60 * 60, container_idle_timeout=240, mounts=MODAL_MOUNTS, memory=15000, concurrency_limit=N_MAX_REPLICAS, ) class GenerateStub: def __init__(self, gpt_ckpt: str, dit_ckpt: str, output_path: str): import torch num_gpus = torch.cuda.device_count() print(f"Found {num_gpus} GPUs.") self.worker = GenerateWorker(gpt_ckpt, dit_ckpt, output_path) @modal.method() def generate(self, work_item: list[dict]): return self.worker.generate(work_item) @app.local_entrypoint() def main(): # checkpoints in s3://suno-data/christian/checkpoints # prompts in s3://suno-data/christian/prompts # outputs in s3://suno-data/christian/outputs ckpt_name = GPT_CKPT.split("/")[-1].replace(".pt", "") print(ckpt_name) # load the prompts to evaluate prompts_path = "s3://suno-data/christian/prompts/suno_bench_prompts_v1.csv" # prompts_path = "s3://suno-data/christian/prompts/02_singing_prompts.csv" # load the prompts to evaluate prompts = pd.read_csv(prompts_path) print(len(prompts)) chunksize = 16 # num of prompts per worker work_items = list(funcy.chunks(chunksize, prompts.to_dict(orient="records"))) print(len(work_items), "work items") worker = GenerateStub( GPT_CKPT, DIT_MODEL_FILEPATH, f"christian/outputs/{ckpt_name}" ) # print("Testing inference...") # t0 = time.time() # for work_item in work_items[:1]: # print(work_item) # _ = worker.generate.remote(work_item) # print(f"{int(round(time.time()-t0))}s for test") print("Running batch inference...") t0 = time.time() _ = list(worker.generate.map(work_items)) print(round((time.time() - t0) / 60 / 60), "h total for batch generation")