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

我有以下内容:

  • 一个使用PyTorch的机器学习模型,能够将数据向量化并在约3.5毫秒内做出预测(中位数 ≈ 平均数)
  • 一个使用FastAPI + uvicorn的HTTP API,能够在约2毫秒内处理简单请求

但当我将它们结合使用时,中位数响应时间几乎变成了200毫秒

导致这种性能下降的原因可能是什么?


请注意以下几点:

  • 我还尝试了单独使用aiohttp,以及aiohttp + gunicorn和Flask开发服务器进行服务,结果相同
  • 我尝试每秒发送2次、20次和100次请求,结果相同
  • 我意识到并行请求可能会导致延迟降低,但不会降低30倍!
  • CPU负载仅为约7%

这是我测量模型性能的方式(我单独测量了中位数时间,它几乎与平均时间相同):

def predict_all(predictor, data):    for i in range(len(data)):        predictor(data[i])data = load_random_data()predictor = load_predictor()%timeit predict_all(predictor, data)# 手动将总时间除以数据中的记录数

这是FastAPI版本:

from fastapi import FastAPIfrom starlette.requests import Requestfrom my_code import load_predictorapp = FastAPI()app.predictor = load_predictor()@app.post("/")async def root(request: Request):    predictor = request.app.predictor    data = await request.json()    return predictor(data)

HTTP性能测试:

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

编辑。

这是我尝试过的稍微修改的版本,带有和不带async:

@app.post("/")# async def root(request: Request, user_dict: dict):def root(request: Request, user_dict: dict):    predictor = request.app.predictor    start_time = time.time()    y = predictor(user_dict)    finish_time = time.time()    logging.info(f"user {user_dict['user_id']}: "                 "prediction made in {:.2f}ms".format((finish_time - start_time) * 1000))    return y

所以我只是添加了预测时间的日志记录。

异步版本的日志:

2021-02-03 11:14:31,822: user 12345678-1234-1234-1234-123456789123: prediction made in 2.87msINFO:     127.0.0.1:49284 - "POST / HTTP/1.1" 200 OK2021-02-03 11:14:56,329: user 12345678-1234-1234-1234-123456789123: prediction made in 3.93msINFO:     127.0.0.1:49286 - "POST / HTTP/1.1" 200 OK2021-02-03 11:14:56,345: user 12345678-1234-1234-1234-123456789123: prediction made in 15.06msINFO:     127.0.0.1:49287 - "POST / HTTP/1.1" 200 OK2021-02-03 11:14:56,351: user 12345678-1234-1234-1234-123456789123: prediction made in 4.78msINFO:     127.0.0.1:49288 - "POST / HTTP/1.1" 200 OK2021-02-03 11:14:56,358: user 12345678-1234-1234-1234-123456789123: prediction made in 6.85msINFO:     127.0.0.1:49289 - "POST / HTTP/1.1" 200 OK2021-02-03 11:14:56,363: user 12345678-1234-1234-1234-123456789123: prediction made in 3.71msINFO:     127.0.0.1:49290 - "POST / HTTP/1.1" 200 OK2021-02-03 11:14:56,369: user 12345678-1234-1234-1234-123456789123: prediction made in 5.49msINFO:     127.0.0.1:49291 - "POST / HTTP/1.1" 200 OK2021-02-03 11:14:56,374: user 12345678-1234-1234-1234-123456789123: prediction made in 5.00ms

所以预测速度很快,平均不到10毫秒,但整个请求需要200毫秒。

同步版本的日志:

2021-02-03 11:17:58,332: user 12345678-1234-1234-1234-123456789123: prediction made in 65.49ms2021-02-03 11:17:58,334: user 12345678-1234-1234-1234-123456789123: prediction made in 23.05msINFO:     127.0.0.1:49481 - "POST / HTTP/1.1" 200 OKINFO:     127.0.0.1:49482 - "POST / HTTP/1.1" 200 OK2021-02-03 11:17:58,338: user 12345678-1234-1234-1234-123456789123: prediction made in 72.39ms2021-02-03 11:17:58,341: user 12345678-1234-1234-1234-123456789123: prediction made in 78.66ms2021-02-03 11:17:58,341: user 12345678-1234-1234-1234-123456789123: prediction made in 85.74ms

现在预测需要很长时间!出于某种原因,相同的调用,但在同步上下文中执行时,开始需要大约30倍的时间。但整个请求所需的时间大致相同 – 160-200毫秒。


回答:

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

当您使用def而不是async def时,FastAPI默认会使用Starlette的run_in_threadpool,其底层也使用loop.run_in_executor

run_in_executor将在默认循环执行器中执行函数,它会在一个单独的线程中执行函数,如果您在进行高CPU密集型工作,您可能还想检查像ProcessPoolExecutorThreadPoolExecutor这样的选项。

这个简单的数学计算在处理协程时非常有帮助。

function   if function_takes ≥ 500ms       use `def`   else       use `async def`

使您的函数成为非协程应该会很有帮助。

@app.post("/")def root(request: Request):    predictor = request.app.predictor    data = await request.json()    return predictor(data)

Related Posts

如何对SVC进行超参数调优?

已关闭。此问题需要更加聚焦。目前不接受回答。 想要改进…

如何在初始训练后向模型添加训练数据?

我想在我的scikit-learn模型已经训练完成后再…

使用Google Cloud Function并行运行带有不同用户参数的相同训练作业

我正在寻找一种方法来并行运行带有不同用户参数的相同训练…

加载Keras模型,TypeError: ‘module’ object is not callable

我已经在StackOverflow上搜索并阅读了文档,…

在计算KNN填补方法中特定列中NaN值的”距离平均值”时

当我从头开始实现KNN填补方法来处理缺失数据时,我遇到…

使用巨大的S3 CSV文件或直接从预处理的关系型或NoSQL数据库获取数据的机器学习训练/测试工作

已关闭。此问题需要更多细节或更清晰的说明。目前不接受回…

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注