PyTorch型号+;HTTP API=执行速度非常慢

PyTorch型号+;HTTP API=执行速度非常慢,http,machine-learning,pytorch,fastapi,Http,Machine Learning,Pytorch,Fastapi,我有: ML模型(PyTorch),对数据进行矢量化,并在~3.5ms内进行预测(中位数≈ 平均数) HTTP API(FastAPI+uvicorn),以~2ms的速度为简单请求提供服务 但当我将它们结合起来时,平均响应时间几乎变成200ms 这种退化的原因是什么? 请注意: 我还单独尝试了aiohttp、aiohttp+gunicorn和Flask development server,以获得相同的结果 我试着每秒发送2个、20个和100个请求,结果是一样的 我确实意识到并行请求可以

我有:

  • ML模型(PyTorch),对数据进行矢量化,并在~3.5ms内进行预测(中位数≈ 平均数)
  • HTTP API(FastAPI+uvicorn),以~2ms的速度为简单请求提供服务
但当我将它们结合起来时,平均响应时间几乎变成200ms

这种退化的原因是什么?


请注意:

  • 我还单独尝试了aiohttp、aiohttp+gunicorn和Flask development server,以获得相同的结果
  • 我试着每秒发送2个、20个和100个请求,结果是一样的
  • 我确实意识到并行请求可以减少延迟,但不会减少30倍
  • CPU负载仅为~7%

以下是我测量模型性能的方法(我分别测量了平均时间,几乎与平均时间相同):

def predict_all(预测器、数据):
对于范围内的i(len(数据)):
预测器(数据[i])
数据=加载随机数据()
预测器=负载\预测器()
%timeit PREDICTION_all(预测器、数据)
#手动将总时间除以数据中的记录数
以下是FastAPI版本:

从fastapi导入fastapi
从starlette.requests导入请求
从我的\u代码导入加载\u预测器
app=FastAPI()
app.predictor=load_predictor()
@app.post(“/”)
异步定义根目录(请求:请求):
预测器=request.app.predictor
data=wait request.json()
返回预测(数据)
HTTP性能测试:

wrk2 -t2 -c50 -d30s -R100 --latency -s post.lua http://localhost:8000/
编辑。

这里有一个稍微修改过的版本,我尝试了使用和不使用
async

@app.post(“/”)
#异步定义根(请求:请求,用户\u dict:dict):
def root(请求:请求,用户指令:指令):
预测器=request.app.predictor
开始时间=time.time()
y=预测器(用户指令)
完成时间=time.time()
logging.info(f“user{user_dict['user_id']}”:
“以{.2f}ms进行预测”。格式((完成时间-开始时间)*1000)
返回y
所以我只是增加了预测时间的记录

异步版本的日志:

2021-02-03 11:14:31,822: user 12345678-1234-1234-1234-123456789123: prediction made in 2.87ms
INFO:     127.0.0.1:49284 - "POST / HTTP/1.1" 200 OK
2021-02-03 11:14:56,329: user 12345678-1234-1234-1234-123456789123: prediction made in 3.93ms
INFO:     127.0.0.1:49286 - "POST / HTTP/1.1" 200 OK
2021-02-03 11:14:56,345: user 12345678-1234-1234-1234-123456789123: prediction made in 15.06ms
INFO:     127.0.0.1:49287 - "POST / HTTP/1.1" 200 OK
2021-02-03 11:14:56,351: user 12345678-1234-1234-1234-123456789123: prediction made in 4.78ms
INFO:     127.0.0.1:49288 - "POST / HTTP/1.1" 200 OK
2021-02-03 11:14:56,358: user 12345678-1234-1234-1234-123456789123: prediction made in 6.85ms
INFO:     127.0.0.1:49289 - "POST / HTTP/1.1" 200 OK
2021-02-03 11:14:56,363: user 12345678-1234-1234-1234-123456789123: prediction made in 3.71ms
INFO:     127.0.0.1:49290 - "POST / HTTP/1.1" 200 OK
2021-02-03 11:14:56,369: user 12345678-1234-1234-1234-123456789123: prediction made in 5.49ms
INFO:     127.0.0.1:49291 - "POST / HTTP/1.1" 200 OK
2021-02-03 11:14:56,374: user 12345678-1234-1234-1234-123456789123: prediction made in 5.00ms
2021-02-03 11:17:58,332: user 12345678-1234-1234-1234-123456789123: prediction made in 65.49ms
2021-02-03 11:17:58,334: user 12345678-1234-1234-1234-123456789123: prediction made in 23.05ms
INFO:     127.0.0.1:49481 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:49482 - "POST / HTTP/1.1" 200 OK
2021-02-03 11:17:58,338: user 12345678-1234-1234-1234-123456789123: prediction made in 72.39ms
2021-02-03 11:17:58,341: user 12345678-1234-1234-1234-123456789123: prediction made in 78.66ms
2021-02-03 11:17:58,341: user 12345678-1234-1234-1234-123456789123: prediction made in 85.74ms
所以预测速度很快,平均不到10毫秒,但整个请求需要200毫秒

同步版本的日志:

2021-02-03 11:14:31,822: user 12345678-1234-1234-1234-123456789123: prediction made in 2.87ms
INFO:     127.0.0.1:49284 - "POST / HTTP/1.1" 200 OK
2021-02-03 11:14:56,329: user 12345678-1234-1234-1234-123456789123: prediction made in 3.93ms
INFO:     127.0.0.1:49286 - "POST / HTTP/1.1" 200 OK
2021-02-03 11:14:56,345: user 12345678-1234-1234-1234-123456789123: prediction made in 15.06ms
INFO:     127.0.0.1:49287 - "POST / HTTP/1.1" 200 OK
2021-02-03 11:14:56,351: user 12345678-1234-1234-1234-123456789123: prediction made in 4.78ms
INFO:     127.0.0.1:49288 - "POST / HTTP/1.1" 200 OK
2021-02-03 11:14:56,358: user 12345678-1234-1234-1234-123456789123: prediction made in 6.85ms
INFO:     127.0.0.1:49289 - "POST / HTTP/1.1" 200 OK
2021-02-03 11:14:56,363: user 12345678-1234-1234-1234-123456789123: prediction made in 3.71ms
INFO:     127.0.0.1:49290 - "POST / HTTP/1.1" 200 OK
2021-02-03 11:14:56,369: user 12345678-1234-1234-1234-123456789123: prediction made in 5.49ms
INFO:     127.0.0.1:49291 - "POST / HTTP/1.1" 200 OK
2021-02-03 11:14:56,374: user 12345678-1234-1234-1234-123456789123: prediction made in 5.00ms
2021-02-03 11:17:58,332: user 12345678-1234-1234-1234-123456789123: prediction made in 65.49ms
2021-02-03 11:17:58,334: user 12345678-1234-1234-1234-123456789123: prediction made in 23.05ms
INFO:     127.0.0.1:49481 - "POST / HTTP/1.1" 200 OK
INFO:     127.0.0.1:49482 - "POST / HTTP/1.1" 200 OK
2021-02-03 11:17:58,338: user 12345678-1234-1234-1234-123456789123: prediction made in 72.39ms
2021-02-03 11:17:58,341: user 12345678-1234-1234-1234-123456789123: prediction made in 78.66ms
2021-02-03 11:17:58,341: user 12345678-1234-1234-1234-123456789123: prediction made in 85.74ms

现在预测需要很长时间!无论出于何种原因,完全相同的调用,但在同步上下文中进行,开始需要约30倍的时间。但整个请求所需的时间大致相同—160-200ms。

在进行高强度计算且与其他端点相比可能需要更长时间的端点中,请使用非协同路由处理程序

当您使用
def
而不是
async def
时,默认情况下,FastAPI将使用Starlette提供的
run_in_threadpool
,并在下方使用
loop.run_in_executor

run_in_executor
将在默认循环执行器中执行函数,它在单独的线程中执行函数,您可能还需要检查选项,如您正在执行高CPU密集型工作

这个简单的数学在处理协同程序时非常有用

function
   if function_takes ≥ 500ms
       use `def`
   else
       use `async def`
使您的函数非协同程序应该会有好处

@app.post(“/”)
def根目录(请求:请求):
预测器=request.app.predictor
data=wait request.json()
返回预测(数据)

谢谢您的回答!我尝试了同步和异步,并添加了中间测量值-请参阅编辑后的文章。这很有趣,操作系统调度程序和线程的开销不应该增加x30延迟~3.5ms用于预测是很好的,在您的情况下使用协同程序更好。但我仍然不明白为什么非协程端点速度慢x30。请注意,同步和异步端点的延迟大致相同,这是一个预测器。这是不同的。它看起来像是极端的上下文切换(尽管2RP无论如何都不应该这样做),或者使用CPU时间来做与预测完全无关的事情。奇怪的是,经过多次实验和微调,我们终于使用一台异步服务器(没有gunicorn workers)和
ProcessPoolExecutor
实现了良好的性能。尽管如此,我仍然无法解释为什么多个员工不工作,而其他流程可以。