import modal import tempfile import time import json from suno_utils.worker.settings import s3_client from suno_utils.worker.schema import QueueItem from suno_utils.worker.modal_base import get_modal_base_image from suno_utils.audio import Audio ############## CHANGE THESE ############## DEPLOYMENT_TYPE = "dev" # dev, prod #### APP_NAME = f"whisper-{DEPLOYMENT_TYPE}" assert APP_NAME.endswith(DEPLOYMENT_TYPE) MODEL_DIR = "/model" MODEL_NAME = "openai/whisper-large-v3-turbo" MODEL_REVISION = "41f01f3fe87f28c78e2fbf8b568835947dd65ed9" image = ( get_modal_base_image() .env({"HF_HUB_ENABLE_HF_TRANSFER": "1", "HF_HUB_CACHE": MODEL_DIR}) .pip_install( "torch==2.5.1", "transformers==4.47.1", "hf-transfer==0.1.8", "huggingface_hub==0.27.0", "librosa==0.10.2", "soundfile==0.12.1", "accelerate==1.2.1", "datasets==3.2.0", ) .add_local_python_source("suno_utils", copy=False) ) model_cache = modal.Volume.from_name("hf-hub-cache", create_if_missing=True) app = modal.App( APP_NAME, image=image, volumes={MODEL_DIR: model_cache}, ) @app.function() def download_model(): from huggingface_hub import snapshot_download from transformers.utils import move_cache snapshot_download( MODEL_NAME, ignore_patterns=["*.pt", "*.bin"], # Using safetensors revision=MODEL_REVISION, ) move_cache() SECRETS = [ modal.Secret.from_name("studio-aws"), modal.Secret.from_name("redis-test"), modal.Secret.from_name("huggingface-secret-suno"), modal.Secret.from_name("api-callback-token"), ] @app.cls( gpu="A10G", min_containers=0 if DEPLOYMENT_TYPE == "dev" else 2, max_containers=5 if DEPLOYMENT_TYPE == "dev" else 50, secrets=SECRETS, timeout=200, scaledown_window=360, ) @modal.concurrent(max_inputs=4) class WhisperStub: @modal.enter() def load_model(self): import torch from transformers import ( AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline, ) self.processor = AutoProcessor.from_pretrained(MODEL_NAME) self.model = AutoModelForSpeechSeq2Seq.from_pretrained( MODEL_NAME, torch_dtype=torch.float16, low_cpu_mem_usage=True, use_safetensors=True, ).to("cuda") # Create a pipeline for preprocessing and transcribing speech data self.pipeline = pipeline( "automatic-speech-recognition", model=self.model, tokenizer=self.processor.tokenizer, feature_extractor=self.processor.feature_extractor, torch_dtype=torch.float16, device="cuda", ) @modal.method() def transcribe(self, queue_item: str): item = QueueItem(**json.loads(queue_item)) print(f"WhisperStub: transcribe: {queue_item}") s3_file_name = item.id start = time.monotonic_ns() try: # Create a temporary directory to store the downloaded file with tempfile.TemporaryDirectory() as temp_dir: if not s3_file_name.endswith(".mp3"): s3_file_name += ".mp3" # Create full local path local_path = f"{temp_dir}/{s3_file_name}" # Download the file to the temporary directory with open(local_path, "wb") as file: s3_path = "studio/uploads/" + s3_file_name s3_client.download_fileobj("suno-data-uploads", s3_path, file) print(f"Transcribing audio samples {local_path}") # Load audio data from the local file path audio = Audio.from_file(local_path) audio_chunks = [] for i in range(0, int(audio.duration_s // 30) + 1): start_time = i * 30 end_time = (i + 1) * 30 if audio.duration_s < start_time: break audio_chunks.append(audio.get_segment(start_time, end_time)) audio_chunk_inputs = [chunk.resample(16000).mono().array_float for chunk in audio_chunks] # Pass numpy array to the pipeline transcriptions = self.pipeline(audio_chunk_inputs) if not transcriptions: return "" end = time.monotonic_ns() total_text = "" for transcription in transcriptions: total_text += transcription["text"] print(f"Transcribed samples in {round((end - start) / 1e9, 2)}s. Text: {total_text}") return total_text except Exception as e: print(f"WhisperStub: transcribe: {queue_item} failed: {e}") return "" @app.local_entrypoint() def main(): model = WhisperStub() model.transcribe.spawn( json.dumps( { "id": "ae137e1f-7562-48f6-a11c-b125e645282c", "metadata": {}, "title": "Test Walking down", } ) ) # this is an instrumental song model.transcribe.spawn( json.dumps( { "id": "ea06bf18-d820-4b42-9b5b-54f6cf7efedb", "metadata": {}, "title": "Test Instrumental", } ) ) print("Done") time.sleep(100)