方案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:修改Gunicorn配置,只使用1个工作进程
如果仍然内存不足:尝试方案3使用更轻量的模型
考虑增加交换空间:使用方案4增加系统交换空间
最后考虑监控方案:使用方案6监控内存使用并自动重启
主要问题是FunASR模型非常大,单个模型就可能占用2-3GB内存,多个工作进程会迅速耗尽内存。通过限制为单个工作进程,可以解决内存不足的问题。