【发布时间】:2022-11-03 03:25:30
【问题描述】:
我编写了一个具有 python 绑定的机器学习推理库。在正常操作下,该库将使用 8 个线程进行推理,并将 100% 最大化所有 8 个线程。这是所需的行为,因为模型非常重,我需要针对低延迟进行优化(因此我需要使用所有 CPU 资源)。
如果我编写一个 python 脚本并调用这个库中的推理函数(在一个无限循环中),8 个线程会按预期被最大化(这是htop 命令的输出)。
现在这是我遇到问题的地方。我需要从我编写的 FastAPI 服务器中调用这个机器学习库。我在我的 docker 容器中使用以下命令来启动 FastAPI 服务器CMD uvicorn main:app --host 0.0.0.0 --port 8080。可以看出,我使用了uvicorn。
现在,事情变得有趣了。如果我在机器学习库中调用相同的推理函数,再次进入无限循环,但这次是从我的一个 FastAPI 端点中调用,则 CPU 使用率上限为每个线程约 65%,并且不会超过此值。
关于为什么 CPU 使用率被限制的任何想法?我想让它达到 100% 以充分利用 CPU。由于 CPU 使用率受到限制,我正在遭受性能损失。
【问题讨论】:
-
我的猜测是,当您在
uvicorn下运行时,还有其他线程正在运行,它们会占用推理线程的时间。当其他线程被引入环境时,由于GIL,这很容易发生。这只是一个猜测,因为我不知道您设置的所有细节。为了解决这个问题,从多线程模型切换到多进程模型通常是有意义的。在您的情况下,您可能只是生成一个单独的进程来运行推理线程以将它们与主运行时环境分离。 -
这是个好主意,我可以测试一下。但是,我编写的 ML 库是用 C++ 编写的,并且是线程安全的。因此,在pybind11 python绑定层(调用C++方法的地方),我释放了python GIL:
py::gil_scoped_release release; -
您需要提供minimal reproducible example 和调试详细信息。尝试制作测试模块比如用纯Python烧CPU,用纯C扩展,用pybind11 C扩展等。我的意思是一个简单的循环比如
a = 0; while True: a += 1 -
一个解决方案(一旦您开始在服务上获得更多负载,这通常是首选方式)是将 ML 部分移到它自己的进程中 - 而不是在 uvicorn/fastapi 进程层次结构中运行它。相反,使用队列 - 当它出现时将请求放入队列中,从您的 ML 工作人员的队列中弹出它们(这将是一组单独的进程),然后通过排队系统将结果返回给调用者(或通过 redis/a 数据库/等带外)。这使您可以根据需要自行扩展系统的两个部分。