Python 服务于ML模型的FastAPI应用程序是否有阻塞代码?
我们有一个装有烧瓶的ML模型。使用Gatling()对烧瓶应用进行负载测试,导致性能非常低。它无法每秒处理大量请求。因此,我们转向了FastAPI 用uvicorn或gunicorn在Docker容器中本地提供它效果很好。但是,我们注意到应用程序在几分钟内没有响应: 在这幅图中,您可以看到应用程序以“批处理”方式响应。在Kubernetes集群中服务我们的应用程序会导致容器重新启动,因为负责的容器不会成功完成就绪性/活动性探测 这个问题我们已经提过了。然而,我认为我们不会在那里得到答案。我们认为这可能是因为我们编写的代码阻塞了主线程,因此我们的FastAPI应用程序在几分钟内不会响应 应用程序终结点的代码段:Python 服务于ML模型的FastAPI应用程序是否有阻塞代码?,python,async-await,gunicorn,fastapi,uvicorn,Python,Async Await,Gunicorn,Fastapi,Uvicorn,我们有一个装有烧瓶的ML模型。使用Gatling()对烧瓶应用进行负载测试,导致性能非常低。它无法每秒处理大量请求。因此,我们转向了FastAPI 用uvicorn或gunicorn在Docker容器中本地提供它效果很好。但是,我们注意到应用程序在几分钟内没有响应: 在这幅图中,您可以看到应用程序以“批处理”方式响应。在Kubernetes集群中服务我们的应用程序会导致容器重新启动,因为负责的容器不会成功完成就绪性/活动性探测 这个问题我们已经提过了。然而,我认为我们不会在那里得到答案。我们认为
async def verify_client(token: str):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM], audience=AUDIENCE)
except JWTError:
raise credentials_exception
@app.post("/score", response_model=cluster_api_models.Response_Model)
async def score(request: cluster_api_models.Request_Model, token: str = Depends(oauth2_scheme)):
logger.info("Token: {0}".format(token))
await verify_client(token)
result = await do_score(request)
return result
wait do_score(request)
包含所有预处理和预测代码。它使用gensim fasttext模型创建文档向量,并使用scikit learn K-Means模型do_score()
是用async def do_score(请求)
定义的。从一开始,我们认为这足以使我们的应用程序异步。然而,它看起来不像。它仍在按顺序处理,而且几分钟内没有响应。该方法还包括一个嵌套的for循环O(n²)。。。不确定这是否也会导致代码阻塞
我希望提供的信息足以开始。如果你需要更多关于代码的信息,请告诉我。我需要更改一些代码的变量名。提前非常感谢 当然,如果您的应用程序不是完全异步的,那么会有一些东西阻止您的应用程序,异步只是一个花哨的关键字 即使您使用
async def
定义了一个函数,如果它在下面做了一些阻塞,它也会阻塞应用程序的整个执行。你不相信吗?测试一下
@app.get(“/dummy”)
异步def dummy():
时间。睡眠(5)
让我们向它发送3个并发请求
for _ in {1..3}; do curl http://127.0.0.1:8000/dummy &; done
这将需要+15秒
让我们深入探讨一下,我说过async def只是声明协同路由的一种奇特语法,为什么?看
async def
函数始终是协程,即使它们不包含wait
表达式
为什么这很重要?
当您定义一个协同程序时,使用wait
语法,您将事件循环说成继续进行,它会这样做,它会切换到另一个协同程序并运行它
有什么区别?
基本上,协同程序不等待结果,它只是继续进行。但是当你定义一个普通函数时,它当然会等待它的执行
既然我们都知道它会阻塞,你能做什么?
您可能希望使用像芹菜这样的作业/任务队列库。这里的正确答案是使用非异步路由定义。FastAPI说明,任何包含同步代码的路由都应该放在这些类型的路由中,这样它就可以将代码放在自己的内部线程池中,从而创建一个伪异步路由
@app.post(“/score”,response\u model=cluster\u api\u models.response\u model)
def分数(请求:集群\ api \模型。请求\模型,令牌:str=Depends(oauth2 \方案)):
logger.info(“令牌:{0}”.format(令牌))
验证\u客户端(令牌)
结果=do_分数(请求)
返回结果
这里有一个指向我引用的fastapi文档的链接
非常感谢您的快速回答。我们认为FastAPI将负责创建作业/任务队列。我想我们完全错了。因此,FastAPI应用程序的每个端点(活动性/就绪性检查)和每个函数都需要芹菜来处理?更新:我刚刚阅读了FastAPI中的背景任务。它建议将其用于小任务或需要访问在同一FastAPI应用程序中创建的变量的用例。因为我们使用的是一个相当大的文本模型,芹菜可能不是一个选项。将更新,什么对我们有效。在我们的情况下,我们有大约需要3-5分钟的模型和20秒的小任务,但我们通过设置优先级来为所有任务使用芹菜,这是我不建议
背景任务的原因。这里面有很多问题。下面是最常见的方法,您如何确保芹菜(我真的不反对这个选项),可以访问FastAPI应用程序中加载的模型,还是在每个请求中加载您的模型?通常执行方法do_score(request)
的时间约为500毫秒。您能定义正确的答案吗?是什么使我的答案不正确?我已经在FastAPI社区呆了很长时间,我知道一些极端情况,有一些大问题,比如,从长远来看,会导致内存泄漏,在线程池中运行一个需要约500毫秒的函数是不好的。如果您的函数需要1s>ns>5s。
那么您可以将其用于>5s
您应该进行其他操作。感谢链接这些问题,我没有发现任何内存泄漏。因此,我可能应该说,公认的答案忽略了更简单的解决方案(非异步路由defs),它是通过FastAPI记录和支持的。我想我的答案确实定义了正确答案的含义。它遗漏了什么?