You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flask+Phusion Passenger多进程下共享joblib加载的Gensim模型问题

解决方案

1. 利用Phusion Passenger预加载机制(最优方案)

Phusion Passenger支持预加载应用——启动主进程时先加载模型,后续fork出的所有Worker子进程会通过Copy-On-Write(COW)共享父进程的模型内存(因为模型仅做只读操作,不会触发内存复制)。整个服务器只需加载一次模型,彻底解决多进程重复加载的问题。

配置步骤

  • 在CPanel的Passenger设置中开启**"Preload Application"**选项;
  • 也可手动在.htaccess中添加配置:
PassengerPreloadApplication on

调整Flask代码

确保模型仅在应用初始化时加载一次:

from flask import Flask, request, jsonify
import gensim

app = Flask(__name__)
engine = None

def load_model():
    global engine
    if engine is None:
        # 仅预加载阶段执行一次
        engine = gensim.Models.load("search_engine")

# Passenger预加载时触发初始化
@app.before_first_request
def init():
    load_model()

@app.route('/api', methods=['GET'])
def api():
    query = request.args.get("query", "")
    closest = engine.wv.most_similar(query)
    return jsonify(closest)

注意事项

  • 必须保证模型是只读的,任何修改操作都会触发COW复制内存,失去共享效果;
  • 更新模型需要重启Passenger应用。

2. 用内存映射文件存储Numpy矩阵

把模型核心的Numpy向量矩阵(engine.wv.vectors)保存为内存映射文件,每个进程直接映射该文件到内存,无需重复加载整个模型,绕过进程间对象传递的限制。

步骤1:提前导出模型核心数据

加载一次模型,将向量矩阵和词汇表单独存储:

import numpy as np
from gensim.models import Word2Vec

# 仅执行一次的导出操作
model = Word2Vec.load("search_engine")
# 保存向量矩阵为内存映射格式
np.save("word2vec_vectors.npy", model.wv.vectors)
# 保存词汇表(体积小,直接存文本)
with open("word2vocab.txt", "w") as f:
    for word in model.wv.index_to_key:
        f.write(word + "\n")

步骤2:Flask应用中加载映射文件

每个进程仅建立内存映射,不加载整个文件:

from flask import Flask, request, jsonify
import numpy as np
from gensim.models.keyedvectors import KeyedVectors

app = Flask(__name__)
wv = None

def load_vectors():
    global wv
    if wv is None:
        # 映射向量矩阵到内存(仅建立映射,无全量IO开销)
        vectors = np.load("word2vec_vectors.npy", mmap_mode="r")
        # 加载词汇表
        with open("word2vocab.txt", "r") as f:
            index_to_key = [line.strip() for line in f.readlines()]
        # 构建只读的KeyedVectors对象
        wv = KeyedVectors(vector_size=vectors.shape[1])
        wv.vectors = vectors
        wv.index_to_key = index_to_key
        wv.key_to_index = {word: idx for idx, word in enumerate(index_to_key)}

@app.before_first_request
def init():
    load_vectors()

@app.route('/api', methods=['GET'])
def api():
    query = request.args.get("query", "")
    closest = wv.most_similar(query)
    return jsonify(closest)

优势

  • 内存映射由操作系统管理,多进程共享同一块内存,不会重复占用资源;
  • 加载速度极快,仅建立映射无需读取全量文件。

3. 限制Passenger Worker进程数(临时缓解)

如果无法启用预加载,可通过限制Worker进程数量减少重复加载次数,在.htaccess中添加:

PassengerMaxPoolSize 2
PassengerMinInstances 1

这样最多生成2个Worker进程,模型最多加载2次,缓解IO和内存压力。


之前方案失败的原因

  • multiprocessing.Managers:BaseManager的DictProxy仅支持可序列化的简单对象,Gensim模型包含复杂内部结构(如Numpy数组视图),无法直接序列化存储,因此存入后值为None;
  • flask_caching + Redis:Redis不适合存储大体积Numpy数组,内存不足导致连接重置,且大数组的序列化/反序列化开销极高,不符合快速访问的需求。

内容的提问来源于stack exchange,提问作者Aurélien Pierre

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 04:50:14