Funasr 内部python 使用Gunicorn启动内存不足

吴书松
吴书松
发布于 2025-09-25 / 6 阅读
0
0

Funasr 内部python 使用Gunicorn启动内存不足

方案1:限制工作进程数和内存使用

python

# gunicorn_config.py - 优化内存使用的配置
import os
import multiprocessing

# 服务器Socket绑定
bind = "0.0.0.0:5000"

# 只使用1个工作进程(避免内存不足)
workers = 1

# 使用gevent异步工作模式,减少内存占用
worker_class = "gevent"
worker_connections = 10

# 超时设置
timeout = 600

# 守护进程模式
daemon = False

# 访问日志和错误日志
accesslog = "./logs/access.log"
errorlog = "./logs/error.log"
loglevel = "info"

# 进程ID文件
pidfile = "./tmp/gunicorn.pid"

# 预加载应用(重要:确保模型只加载一次)
preload_app = True

# 限制内存使用
max_requests = 50  # 处理50个请求后重启工作进程
max_requests_jitter = 5

def when_ready(server):
    server.log.info("服务器启动中,模型加载可能需要较长时间...")

def pre_fork(server, worker):
    os.makedirs("./logs", exist_ok=True)
    os.makedirs("./tmp", exist_ok=True)

方案2:使用单个工作进程和线程池

python

# app_single_worker.py - 优化内存使用的应用
from flask import Flask, request, jsonify
from funasr import AutoModel
import os
import logging
import threading
from concurrent.futures import ThreadPoolExecutor

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

app = Flask(__name__)

# 设置模型缓存目录
os.environ['MODELSCOPE_CACHE'] = './models'
os.environ['MODELSCOPE_HUB_CACHE'] = './models'

# 全局变量
model = None
executor = ThreadPoolExecutor(max_workers=2)  # 限制并发线程数

def load_model():
    """加载语音识别模型"""
    global model
    try:
        logger.info("开始加载语音识别模型...")
        
        # 禁用tqdm进度条
        try:
            from tqdm import tqdm
            tqdm.__init__ = lambda self, *args, **kwargs: None
        except ImportError:
            pass
        
        model = AutoModel(
            model="iic/speech_paraformer-large-vad-punc-spk_asr_nat-zh-cn",
            vad_model="fsmn-vad", 
            punc_model="ct-punc-c",
            model_revision="v2.0.4",
            disable_update=True
        )
        logger.info("语音识别模型加载成功")
    except Exception as e:
        logger.error(f"模型加载失败: {e}")
        raise

# 在应用启动时立即加载模型
try:
    load_model()
    logger.info("应用启动完成")
except Exception as e:
    logger.error(f"应用启动失败: {e}")
    exit(1)

def process_audio(save_path):
    """处理音频的线程函数"""
    try:
        result = model.generate(input=save_path)
        return {"success": True, "result": result}
    except Exception as e:
        return {"success": False, "error": str(e)}

@app.route('/asr', methods=['POST'])
def recognize():
    """语音识别接口"""
    if model is None:
        return jsonify({"error": "模型未加载"}), 500
    
    if 'audio' not in request.files:
        return jsonify({"error": "未提供音频文件"}), 400
    
    audio_file = request.files['audio']
    if audio_file.filename == '':
        return jsonify({"error": "未选择文件"}), 400

    # 保存临时文件
    save_path = f"/tmp/{audio_file.filename}"
    audio_file.save(save_path)

    try:
        # 使用线程池处理音频,避免阻塞
        future = executor.submit(process_audio, save_path)
        result = future.result(timeout=300)  # 5分钟超时
        
        # 清理临时文件
        os.remove(save_path)
        
        if result["success"]:
            return jsonify(result)
        else:
            return jsonify({"error": result["error"]}), 500
            
    except Exception as e:
        logger.error(f"处理音频时出错: {e}")
        if os.path.exists(save_path):
            os.remove(save_path)
        return jsonify({"error": str(e)}), 500

@app.route('/health', methods=['GET'])
def health_check():
    """健康检查接口"""
    return jsonify({
        "status": "healthy",
        "service": "funasr-asr",
        "model_loaded": model is not None,
        "active_threads": executor._work_queue.qsize()
    })

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=False, threaded=True)

方案3:使用更轻量的模型

python

# app_lightweight.py - 使用轻量级模型
from flask import Flask, request, jsonify
from funasr import AutoModel
import os
import logging

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

app = Flask(__name__)

# 设置模型缓存目录
os.environ['MODELSCOPE_CACHE'] = './models'
os.environ['MODELSCOPE_HUB_CACHE'] = './models'

# 全局变量存储模型
model = None

def load_lightweight_model():
    """加载轻量级语音识别模型"""
    global model
    try:
        logger.info("开始加载轻量级语音识别模型...")
        
        # 使用更小的模型
        model = AutoModel(
            model="damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-pytorch",  # 更小的模型
            # 不加载VAD和标点模型以减少内存占用
            # vad_model="fsmn-vad",
            # punc_model="ct-punc-c",
            disable_update=True
        )
        logger.info("轻量级语音识别模型加载成功")
    except Exception as e:
        logger.error(f"轻量级模型加载失败: {e}")
        # 尝试加载更基础的模型
        try:
            model = AutoModel(
                model="damo/speech_paraformer_asr_nat-zh-cn-16k-common-vocab8404-pytorch",
                disable_update=True
            )
            logger.info("基础语音识别模型加载成功")
        except Exception as e2:
            logger.error(f"所有模型加载尝试均失败: {e2}")
            raise

# 在应用启动时立即加载模型
try:
    load_lightweight_model()
    logger.info("应用启动完成")
except Exception as e:
    logger.error(f"应用启动失败: {e}")
    exit(1)

# ... 其余代码保持不变 ...

方案4:增加系统交换空间

如果服务器内存不足,可以增加交换空间:

bash

# 检查当前交换空间
sudo swapon --show

# 创建交换文件(8GB)
sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

# 永久生效,添加到/etc/fstab
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

# 调整swappiness(更积极使用交换空间)
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

方案5:使用Docker内存限制

如果使用Docker,可以设置内存限制:

dockerfile

# Dockerfile
FROM python:3.8-slim

# 安装系统依赖
RUN apt-get update && apt-get install -y \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY app.py .

EXPOSE 5000

# 设置内存限制环境变量
ENV PYTHONUNBUFFERED=1
ENV MODEL_DIR=/app/models

# 使用单个工作进程启动
CMD ["gunicorn", "-w", "1", "-t", "600", "-b", "0.0.0.0:5000", "app:app"]

运行Docker时设置内存限制:

bash

docker run -d \
  --name funasr-app \
  --memory=4g \
  --memory-swap=6g \
  -p 5000:5000 \
  funasr-app

方案6:监控和自动重启脚本

python

# monitor_app.py - 监控应用内存使用
import psutil
import os
import time
import subprocess
import signal

class AppMonitor:
    def __init__(self):
        self.process = None
        self.max_memory_mb = 3000  # 3GB内存限制
        self.check_interval = 30  # 30秒检查一次
        
    def start_app(self):
        """启动应用"""
        self.process = subprocess.Popen([
            "gunicorn", "-c", "gunicorn_config_single.py", "app:app"
        ])
        print(f"应用已启动,PID: {self.process.pid}")
        
    def monitor(self):
        """监控应用内存使用"""
        while True:
            if self.process and self.process.poll() is None:
                try:
                    # 获取进程内存使用
                    process = psutil.Process(self.process.pid)
                    memory_mb = process.memory_info().rss / 1024 / 1024
                    
                    print(f"应用内存使用: {memory_mb:.2f} MB")
                    
                    # 如果内存使用超过限制,重启应用
                    if memory_mb > self.max_memory_mb:
                        print(f"内存使用超过限制({self.max_memory_mb}MB),重启应用...")
                        self.restart_app()
                        
                except psutil.NoSuchProcess:
                    print("应用进程不存在,重新启动...")
                    self.start_app()
            else:
                print("应用未运行,启动应用...")
                self.start_app()
                
            time.sleep(self.check_interval)
            
    def restart_app(self):
        """重启应用"""
        if self.process:
            self.process.terminate()
            self.process.wait(timeout=30)
        self.start_app()
        
    def stop(self):
        """停止监控和应用"""
        if self.process:
            self.process.terminate()
            self.process.wait()

if __name__ == "__main__":
    monitor = AppMonitor()
    try:
        monitor.monitor()
    except KeyboardInterrupt:
        print("停止监控...")
        monitor.stop()

推荐的Gunicorn配置(单工作进程)

python

# gunicorn_config_single.py - 单工作进程配置
import os

bind = "0.0.0.0:5000"
workers = 1  # 关键:只使用一个工作进程
worker_class = "sync"
timeout = 600
preload_app = True  # 预加载模型
max_requests = 20   # 处理20个请求后重启,防止内存泄漏
max_requests_jitter = 5

# 日志配置
accesslog = "./logs/access.log"
errorlog = "./logs/error.log"
loglevel = "info"

os.makedirs("./logs", exist_ok=True)
os.makedirs("./tmp", exist_ok=True)

推荐解决步骤:

  1. 首先使用方案1:修改Gunicorn配置,只使用1个工作进程

  2. 如果仍然内存不足:尝试方案3使用更轻量的模型

  3. 考虑增加交换空间:使用方案4增加系统交换空间

  4. 最后考虑监控方案:使用方案6监控内存使用并自动重启

主要问题是FunASR模型非常大,单个模型就可能占用2-3GB内存,多个工作进程会迅速耗尽内存。通过限制为单个工作进程,可以解决内存不足的问题。


评论