翼度科技»论坛 编程开发 python 查看内容

解读FastAPI异步化为transformers模型打造高性能接口

4

主题

4

帖子

12

积分

新手上路

Rank: 1

积分
12
背景

最近公司需要用到一个Bert模型,使用这个模型对一个短文本做实时的encode(也就是实现文本转换成向量)。
因为模型是基于python的transformers和sentence_transfromers。也就是只能使用python来做。
整体的数据流都是通过java来调用,而python这端只需要提供文本转向量的接口即可。
因为之前就比较喜欢使用fastapi,而且fastapi也比flask快得多。因此将fastapi结合sentence_transfromers是再正常不过的了。

过程


简单版本

需要注意的是,这个代码是cpu密集型的,非常吃cpu的计算。
要想实现这样的一个功能,其实非常简单,创建一个python文件叫
  1. nlp_api.py
复制代码
,填写代码如下:
  1. # 导入包
  2. import numpy as np
  3. import pandas as pd
  4. import torch as t
  5. from tqdm import tqdm
  6. from fastapi import FastAPI
  7. from fastapi.middleware.cors import CORSMiddleware
  8. from sentence_transformers import SentenceTransformer as SBert
  9. import uvicorn
  10. from asgiref.sync import sync_to_async
  11. import asyncio

  12. # 加载模型
  13. model = SBert("/home/dataai/文档/huzheng/模型部分/预训练模型/paraphrase-multilingual-MiniLM-L12-v2")

  14. # 启动app
  15. app = FastAPI()

  16. # 让app可以跨域
  17. origins = ["*"]
  18. app.add_middleware(
  19.     CORSMiddleware,
  20.     allow_origins=origins,
  21.     allow_credentials=True,
  22.     allow_methods=["*"],
  23.     allow_headers=["*"],
  24. )


  25. @app.get("/")
  26. async def main():
  27.     return {"message": "Hello World"}
  28.    
  29. # 实现功能
  30. @app.get('/get_vector_simple')
  31. def sentenc2vector_simple(sentence):
  32.     """
  33.     这里是提供文本转向量的接口,
  34.     :param sentence: 文本字符串
  35.     :return: 文本向量
  36.     """
  37.     encode1 = model.encode(sentence)
  38.     encode1 = encode1.flatten().tolist()
  39.     return {'vector': encode1}

  40. if __name__ == '__main__':
  41.     uvicorn.run(app='nlp_api:app', host="0.0.0.0",
  42.                 port=8000, reload=True, debug=True)
复制代码
运行这个代码也是非常简单,直接python运行即可:
  1. python nlp_api.py
复制代码

上面代码其实已经实现了这个功能。但是要对这个接口 做压测。这里使用的工具是
  1. wrk
复制代码
。我们设置了100个
  1. thread
复制代码
、1000个
  1. connection
复制代码
、时间是100秒。最终得到的结果是:
在1.67分钟内,接受了2529个请求;平均下来是每秒接受25.27个请求。这个时候我其实比较满意了。但是技术不满意,说这个太低了。
我使用
  1. htop
复制代码
查看了服务器的运行情况,发现cpu基本上都是吃满状态。负荷非常高。

改进

既然要考虑到每秒请求这么多的情况下,我用异步试一试。然后把上面的接口 做了异步处理。只要加上这个代码就行。
  1. async def encode2list(encode):
  2.     return encode.flatten().tolist()

  3. @app.get('/get_vector_async')
  4. async def sentenc2vector_async(sentence):
  5.     """
  6.     异步版本
  7.     这里是提供文本转向量的接口,
  8.     :param sentence: 文本字符串
  9.     :return: 文本向量
  10.     """
  11.     encode1 = await sync_to_async(model.encode)(sentence)
  12.     encode1 = await encode2list(encode1)
  13.     return {'vector': encode1}
复制代码
这个时候,就创建了一个异步接口。然后我又使用
  1. wrk
复制代码
。设置了100个
  1. thread
复制代码
、1000个
  1. connection
复制代码
、时间是100秒。测试这个接口,最终得到的结果是:
在1.67分钟内,接受了7691个请求,平均下来说每秒接受76.84个请求。
我把这个给技术,技术那边也基本是满意了。这样算下来,我平均一个句子转向量的时间大概需要13ms。这个其实已经非常高了。

对比

下图就是一个接口对比:

  • 最上面的框是同步接口效率展示
  • 最下面的框是异步接口效率展示

在这次cpu密集型中,异步接口的效率是同步接口效率的3倍。我后来又测试了几次,基本上都是在3倍以上。
在两种不同的接口下,我使用
  1. htop
复制代码
查看了cpu的运行情况:

  • 同步接口被请求时,cpu负载情况:
  • 异步接口被请求时,cpu负载情况:
可以看出来,相同的任务下,cpu的负载没有那么高,但是效率反而还提高了。

总结

这个模型400MB,底层基于python,使用了pytorch、transformers、Fastapi等包,实现了文本转向量功能,并且这个接口的效率可以达到每秒处理76条。折合每条的文本转向量的时间只需要13ms左右,我还是很开心的。起码不用去搞c++、TensorRT之类的东西。python yyds!!
但是我还没搞懂为什么在cpu密集型的这种任务下,异步接口效率比同步接口效率高这么多,而且还降低了cpu的使用率。
这条路走通,起码代表Fastapi一点也不差!!!我后面如果要开别的接口,可能都会用这种方式试一试。
numpy这种,以及Sbert模型其实都不能异步操作的,但是我使用了
  1. asgiref.sync
复制代码
,这个可以将非异步的转换成异步。非常方便。
作为Fastapi拥鳖,我还是很开心将他用在生产环境中。希望可以接受住考验!
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
参考资料:

来源:https://www.jb51.net/python/3234736yt.htm
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
来自手机

举报 回复 使用道具