V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
davinci21s
V2EX  ›  Python

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

  •  
  •   davinci21s · 2023-09-25 16:45:15 +08:00 · 2366 次点击
    这是一个创建于 457 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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)
    
    
    
    第 1 条附言  ·  2023-09-26 10:49:34 +08:00

    使用常规uWSGI也无法解决内存持续增加问题,但是,

    配置一个参数可以完美的解决。

    --max-worker-lifetime 参数,它允许你设置 worker 进程的最大生命周期(以秒为单位), 到达这个时间限制后,worker 进程将被优雅地重启。

    --reload-on-rss 如果一个 worker 使用超过限定 的内存,它将被重启。

    img

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


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

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

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

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


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

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

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

    需要注意的是,RPC 有多种执行模式,你要避免不断 fork 进程或 spawn 线程去处理数据,这样内存占用也有可能不断膨胀,而要直接调用数据处理的入口函数,这时候单例模式就能起作用。
    davinci21s
        17
    davinci21s  
    OP
       2023-09-26 17:29:15 +08:00
    @nonduality 感谢,可以对遇到相同问题的提供参考。
    subjadeites
        18
    subjadeites  
       2023-09-26 18:32:58 +08:00 via Android
    用 gunicorn 试试?
    subjadeites
        19
    subjadeites  
       2023-09-26 18:33:46 +08:00 via Android
    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
        20
    zheng5200  
       2023-09-27 10:35:01 +08:00 via iPhone
    gunicorn 起 flask 也有这个问题,也是通过--max-requests 解决的,╮(╯▽╰)╭
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3036 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 35ms · UTC 14:30 · PVG 22:30 · LAX 06:30 · JFK 09:30
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.