求助: Python flask 应用内存一直在增加

2023-09-25 16:45:15 +08:00
 davinci21s

2 个版本,都没有解决内存持续增加,由于内存一直增加,最后因为小鸡内存不足而被杀掉。

这是一个加载训练后的模型,通过网络传入预测参数,然后返回预测 json 结果。

可能同时发起(并发)几十个请求。

求大佬帮忙看看问题出在哪里,谢谢。

这是 chatgpt 4.0 给的版本

from flask import Flask, request, jsonify
import pickle
import os
import psutil
import pandas as pd

app = Flask(__name__)

class SingletonModel:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            print("Creating Singleton Instance")
            cls._instance = super(SingletonModel, cls).__new__(cls)
            modelName = "xgboost_model-k.pkl"
            with open(modelName, "rb") as pkl_file:
                loaded_data = pickle.load(pkl_file)
            cls._instance.model = loaded_data['model']
            cls._instance.scaler = loaded_data['scaler']
            cls._instance.label_encoder = loaded_data['label_encoder']
            cls._instance.feature_names = ['shortAvg','longAvg','volatility','diff']
        return cls._instance


resources = SingletonModel()
model = resources.model
scaler = resources.scaler
label_encoder = resources.label_encoder

@app.route('/predict', methods=['POST'])
def predict():
    global model, scaler, label_encoder

    data = request.json['input']

    df = pd.DataFrame([data], columns=resources.feature_names)

    scaled_data = scaler.transform(df)

    prediction = model.predict(scaled_data)

    label_prediction = label_encoder.inverse_transform(prediction)

    return jsonify([label_prediction[0]])

if __name__ == '__main__':
    app.run(port=6601,debug=True)

这是 Claude 给的版本


import asyncio
from flask import Flask, request, jsonify
import pickle
import pandas as pd

app = Flask(__name__)

# 模型相关全局变量
model = None
scaler = None 
label_encoder = None

async def load_model():

  global model, scaler, label_encoder,feature_names

  if not model:

    with open('xgboost_model-k.pkl', 'rb') as f:
      loaded_data = pickle.load(f)
      model = loaded_data['model']
      scaler = loaded_data['scaler'] 
      label_encoder = loaded_data['label_encoder']
      feature_names = ['shortAvg','longAvg','volatility','diff']

async def predict(data):

  await load_model()

  df = pd.DataFrame([data], columns=feature_names)

  scaled_data = scaler.transform(df)

  prediction = model.predict(scaled_data)

  label_prediction = label_encoder.inverse_transform(prediction)

  return label_prediction[0]


@app.route('/predict', methods=['POST'])
async def predict_handler():

  data = request.json['input']
  result = await asyncio.gather(predict(data))
  return jsonify(result)

if __name__ == '__main__':
    app.run(port=6601,debug=False)


2346 次点击
所在节点    Python
20 条回复
missz
2023-09-25 17:10:13 +08:00
我用 flask 启的 yolo5 的接口也是内存无限增长,用 memory_profiler 也看不出具体增长原因,现在是用个 shell 脚本超过一定内存就 kill 重启
jstony
2023-09-25 17:35:17 +08:00
换个版本,cpython 的底层还是 c ,就不能避免完全没有内存泄漏,而且一大堆库,质量良莠不齐。
davinci21s
2023-09-25 17:45:43 +08:00
@missz 无奈中


@jstony thanks ,我试试。
wynemo
2023-09-25 20:18:06 +08:00
用 uwsgi ,flask 自带的就是这样
Inzufu
2023-09-25 22:19:25 +08:00
跑大项目不建议用 py ,还是 nodejs 靠谱一点儿,这两个语言其实学起来差不多。
roycestevie6761
2023-09-25 22:28:42 +08:00
python 就这样的啦
among
2023-09-25 22:34:54 +08:00
uwsgi ,多进程模式,配置超过多少内存,就 fork 一个新的进程。
youngce
2023-09-25 22:35:02 +08:00
@inzufu 这玩意一眼就是算法推理服务,nodejs 加载模型文件跑算法推理,你这不是难为算法同学吗?

这代码里面一眼看去就是模型资源全局变量跑,十几个并发,要是没有 gpu 没有显存,就是需要大内存的。再就是推理接口一般也都是 batch 推理,并发可以利用 batch 来缓解压力。

简而言之,要么懂算法、要么懂 python 后端,两者都不精通,算法服务能跑起来已经谢天谢地,就不要苛责性能了
Inzufu
2023-09-25 22:43:25 +08:00
@youngce 不好意思,我确实没看代码,抱歉抱歉
Mystery0
2023-09-26 09:49:35 +08:00
蹲一个,我也遇到这个问题,照着别人的代码训练了一个模型然后用 flask 提供接口调模型预测数据
运行之后内存就会慢慢变大,现在的解决办法是隔段时间看一下内存,超过 6-700mb 就重启一下
davinci21s
2023-09-26 10:51:12 +08:00
@missz
@jstony
@wynemo
@inzufu
@roycestevie6761
@among
@Mystery0

感谢各位,谢谢楼上提醒,uwsgi 设置参数可以完美解决,我设置内存超过 200M 自动重启。
yagamil
2023-09-26 12:12:54 +08:00
model, scaler, label_encoder,feature_names
这几个变量用全局, 如果不同请求过来, 里面的模型一些参数会被其他进程的请求给修改掉么?
wxlpure
2023-09-26 12:52:29 +08:00
flask 不是同步框架吗?同步框架内用异步是啥效果?
davinci21s
2023-09-26 13:56:50 +08:00
@yagamil 不是很懂😂,chatgpt 帮我写的


@wxlpure Claude 帮我写的,这应该相当于队列吧。
julyclyde
2023-09-26 17:09:19 +08:00
@inzufu 如果只给出建议但不说为什么
那我认为这其实是个宗教式的回答
nonduality
2023-09-26 17:11:48 +08:00
对 Claude 写的不评价,对 ChatGPT 写的说点看法。

单例模式在这里应该是没用的,你可以把 print 的内容改为输出到日志( logging.info ),然后在日志里查看是否不断创建 SingletonModel 。

解决方案之一是利用 RPC:在后台启动一个常驻的数据处理服务,负责接收请求、数据处理和返回结果; Flask 负责把客户端请求转发到常驻服务,再把返回来的结果发给客户端。

需要注意的是,RPC 有多种执行模式,你要避免不断 fork 进程或 spawn 线程去处理数据,这样内存占用也有可能不断膨胀,而要直接调用数据处理的入口函数,这时候单例模式就能起作用。
davinci21s
2023-09-26 17:29:15 +08:00
@nonduality 感谢,可以对遇到相同问题的提供参考。
subjadeites
2023-09-26 18:32:58 +08:00
用 gunicorn 试试?
subjadeites
2023-09-26 18:33:46 +08:00
import gevent.monkey

gevent.monkey.patch_all()

import multiprocessing

debug = False
loglevel = 'info'
bind = '0.0.0.0:7000'
pidfile = 'logs/gunicorn.pid'
logfile = 'logs/debug.log'

# 启动的进程数
workers = multiprocessing.cpu_count() * 2
worker_class = 'gunicorn.workers.ggevent.GeventWorker'
preload_app = True

x_forwarded_for_header = 'X-FORWARDED-FOR'
zheng5200
2023-09-27 10:35:01 +08:00
gunicorn 起 flask 也有这个问题,也是通过--max-requests 解决的,╮(╯▽╰)╭

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/977017

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX