Skip to content

unload model after x minutes of inactivity? #7

Description

@jozsefszalma

Hey Tim,

as far as I can tell the model hangs around in GPU memory indefinitely, which might be undesirable for certain use-cases.
I could submit a PR that introduces a timeout env variable (defaulting to 15min if not set), something like this (not yet tested):

import os
import uvicorn
from fastapi import FastAPI, Body, Depends
from pydantic import BaseModel
from fastapi.responses import FileResponse
from melo.api import TTS
from dotenv import load_dotenv
import tempfile
import threading
import time

load_dotenv()
DEFAULT_SPEED = float(os.getenv("DEFAULT_SPEED"))
DEFAULT_LANGUAGE = os.getenv("DEFAULT_LANGUAGE")
DEFAULT_SPEAKER_ID = os.getenv("DEFAULT_SPEAKER_ID")
device = "auto"  # Will automatically use GPU if available

# Idle timeout for unloading the model (in seconds), defaulting to 15 minutes
MODEL_IDLE_TIMEOUT = int(os.getenv("MODEL_IDLE_TIMEOUT", 15 * 60))

class TextModel(BaseModel):
    text: str
    speed: float = DEFAULT_SPEED
    language: str = DEFAULT_LANGUAGE
    speaker_id: str = DEFAULT_SPEAKER_ID


app = FastAPI()


class ModelManager:
    def __init__(self):
        self.model = None
        self.last_used = time.time()
        self.lock = threading.Lock()
        self._start_cleanup_thread()

    def _start_cleanup_thread(self):
        # Start a background thread that will periodically check and unload the model if idle
        def cleanup():
            while True:
                with self.lock:
                    if self.model and (time.time() - self.last_used) > MODEL_IDLE_TIMEOUT:
                        print("Unloading model due to inactivity...")
                        self.model = None
                time.sleep(60)  # Check every minute

        thread = threading.Thread(target=cleanup, daemon=True)
        thread.start()

    def get_model(self, language):
        with self.lock:
            if not self.model:
                print("Loading TTS model...")
                self.model = TTS(language=language, device=device)
            self.last_used = time.time()
            return self.model


# Instantiate the model manager
model_manager = ModelManager()

def get_tts_model(body: TextModel):
    return model_manager.get_model(body.language)


@app.post("/convert/tts")
async def create_upload_file(
    body: TextModel = Body(...), model: TTS = Depends(get_tts_model)
):
    speaker_ids = model.hps.data.spk2id

    # Use a temporary file
    with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp:
        output_path = tmp.name
        model.tts_to_file(
            body.text, speaker_ids[body.speaker_id], output_path, speed=body.speed
        )

        # Return the audio file, ensure the file is not deleted until after the response is sent
        response = FileResponse(
            output_path, media_type="audio/mpeg", filename=os.path.basename(output_path)
        )

    return response


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8080)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions