通过 RESTful API 暴露 Docker 化 AI 模型





5.00/5 (2投票s)
在本文中,我们将修改我们的代码,通过 Rest API 服务公开相同的逻辑。
引言
Docker 等容器技术可以简化依赖管理并提高软件的可移植性。在本系列文章中,我们将探讨 Docker 在机器学习 (ML) 场景中的使用。
本系列假定您熟悉 AI/ML、容器化(一般)以及 Docker(具体)。
在本系列上一篇文章中,我们使用存储在 Docker 卷上的模型运行了一个 NLP 推理模型。在本文中,我们将使用 Fast API 和 Gunicorn 将上述模型公开为一项服务。欢迎下载本文使用的代码。
为什么选择 Fast API?
目前,Flask 是 Python Rest API 服务的王者。然而,Fast API 正在迅速崛起。虽然 Flask 及其所有插件在灵活性方面难以超越,但在大多数常见场景中,Fast API 的使用速度更快、更简单。此外,它还内置了对异步通信和OpenAPI 的原生支持。
部署 Fast API
与 Flask 不同,Fast API 甚至不包含一个简单的开发服务器。推荐的第三方选项是 Uvicorn。Uvicorn 本身缺乏您期望从生产服务器获得的功能,例如扩展运行进程数量或自动重启失败的工作进程。为了处理这些场景,Fast API 通常与由 Gunicorn 管理的 Uvicorn 工作进程一起使用。虽然这种组合看起来有些复杂,但实现起来却非常简单。这就是我们将在本文中使用的方法。
容器定义
我们的 Dockerfile 将与上一篇文章中的非常相似。下面代码中加粗的三个语句是唯一不同的地方。
FROM pytorch/pytorch:1.6.0-cuda10.1-cudnn7-runtime ENV DEBIAN_FRONTEND=noninteractive ARG USERNAME=mluser ARG USERID=1000 RUN useradd --system --create-home --shell /bin/bash --uid ${USERID:-1000} $USERNAME \ && mkdir /home/$USERNAME/.cache && chown -R $USERNAME /home/$USERNAME/.cache COPY requirements.txt /tmp/requirements.txt RUN pip install -r /tmp/requirements.txt \ && rm /tmp/requirements.txt USER $USERNAME COPY --chown=$USERNAME ./app /home/$USERNAME/app WORKDIR /home/$USERNAME/app ENV DEBIAN_FRONTEND=dialog ENV LC_ALL=C.UTF-8 ENV LANG=C.UTF-8 EXPOSE 8000/tcp CMD ["gunicorn", "--worker-class", "uvicorn.workers.UvicornWorker", "--preload", "main:app", "--bind", "0.0.0.0:8000", "--timeout", "600", "--workers=1", "--log-level=debug", "--error-logfile=-", "--access-logfile=-"]
在 RUN useradd
语句中,我们为 USERID
添加了一个看似多余的默认值。当讨论 docker-compose
配置时,我们将对此进行解释。
在最后两个语句中,我们记录了暴露端口 8000 以用于 TCP 协议,并添加了一个使用 Gunicorn 启动服务的命令。注意 10 分钟的超时时间。这可能看起来有些多余,但只是为了确保在首次执行 API 方法时有足够的时间将模型下载到 Docker 卷中。
请注意,我们这里只请求了一个工作进程(参数 --workers=1
)。您可以增加此值以处理多个并发请求。但是,请记住,每个工作进程都会将单独的模型副本加载到内存中。不幸的是,由于在模型上运行推理是一项 CPU 密集型任务,因此无法在一个工作进程中从并行异步处理中获益。
除了之前使用的库之外,我们的 requirements.txt 文件还需要一些新库。
transformers==4.3.2 fastapi==0.63.0 gunicorn==20.0.4 uvicorn[standard]==0.13.4
API 应用
在继续之前,请下载本文的源代码。模型相关的代码包含在 app/nlp_service.py 脚本中,API 代码包含在 app/main.py 脚本中。
现在,要通过 Rest API 公开我们的问答模型,我们只需要在 app/main.py 中包含以下代码:
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
from nlp_service import NLPService
class QuestionAnswerRequest(BaseModel):
text: str
question: str
class QuestionAnswerResponse(BaseModel):
answer: str
start: int
end: int
score: float
def create_app():
app = FastAPI(title="NLP Service API", version="1.0")
return app
nlp_service = NLPService()
app = create_app()
@app.post('/question/', response_model=QuestionAnswerResponse)
async def question_answer(request: QuestionAnswerRequest):
response = nlp_service.run_question_answering(request.text, request.question)
return QuestionAnswerResponse(
answer = response['answer'],
start = response['start'],
end = response['end'],
score = response['score']
)
虽然这不像“一行代码”那么简单,但应该很容易理解。我们导入所需的库,定义请求和响应类,初始化 FastAPI 应用程序,最后公开一个 API 方法 question_answer
。让我们尝试一下。
构建容器镜像
与上一篇文章一样,我们将使用 docker-compose
。这次使用以下 docker-compose.yml:
version: '3.7' volumes: mluser_cache: name: mluser_cache services: mld08_transformers_api: build: context: '.' dockerfile: 'Dockerfile' args: USERID: ${USERID} image: 'mld08_transformers_api' volumes: - mluser_cache:/home/mluser/.cache ports: - '8000:8000' user: '${USERID}:${GROUPID}'
与上一篇文章相比,加粗的部分是新增的。新的构建 args USERID
值在构建期间使用,它对应于末尾的新用户部分,该部分将在我们的容器运行时使用。这次我们使用环境变量来设置 USERID
和 GROUPID
,因为我们希望使用 docker-compose up
命令运行我们的服务,该命令比简单的 run
更适合长时间运行的服务。信不信由你,docker-compose up
没有直接传递用户上下文的参数。
端口部分确保在容器运行时,容器和主机之间的端口映射正确。
将所有部分就位后,我们终于可以构建我们的镜像了。
$ export USERID=$(id -u) $ export GROUPID=$(id -g) $ docker-compose build
和以前一样,在 Windows 上运行时可以跳过 USERID
和 GROUPID
的值——将使用 Dockerfile 中定义的默认值。这正是我们之前需要更改 Dockerfile 中的以下行的原因。
RUN useradd --system --create-home --shell /bin/bash --uid ${USERID:-1000}
使用我们当前的 docker-compose.yml,当没有定义 USERID
变量时,将把空的 USERID
值传递给构建过程。通过 --uid ${USERID:-1000}
,我们确保将使用默认值 1000。
运行容器
当定义了我们的环境变量并且镜像成功构建后,我们可以启动它。
$ docker-compose up
片刻之后,我们应该会看到确认我们的 Gunicorn 服务器已准备好接受请求。
测试 API
现在我们可以检查我们的服务了。因为 Fast API 支持 OpenAPI,我们可以在 Web 浏览器中通过输入以下本地地址来完成此操作:
https://:8000/docs
要运行语义分析,我们只需单击选定的方法,然后单击 Try it out 按钮。
然后,我们可以直接在 Request body 字段中输入我们的文本,然后单击下面的 Execute 按钮。
请求完成后,我们可以向下滚动查看结果。
任务完成!您可以随意尝试其余方法。请记住,首次使用新方法时,它将下载自己的模型。在这种情况下,可能需要一段时间才能获得响应。
限制
现在我们有了一个带有 Transformers NLP 模型的 Rest API 服务。请注意,并非所有可以做的事情都应该做。Transformers 模型非常庞大、缓慢,总的来说,它们比即时 Web 请求更适合批量处理。
通过 Rest API 公开这些模型时,您需要考虑超时问题,并确保每个工作进程有足够的 RAM 将其自己的模型加载到内存中。
此外,我们的 API 服务并非设计为直接暴露给 Internet 流量。目前的形式,没有安全措施(如身份验证、授权或CORS),它应该保留在防火墙后面,用于受信任的系统间通信。
如果需要,FastAPI 本身就支持 OAuth2。但是,我们倾向于将带有我们模型的服务保持尽可能精简,并增加额外的层来处理安全或其他业务逻辑。如果您感兴趣,可以参考这篇文章,了解如何可靠地将大型(且缓慢)的 Transformers 模型暴露给 Internet 的示例。
摘要
在本文中,我们使用 Fast API 通过 Web 浏览器对 NLP 模型进行了推理。然而,我们的代码变得越来越复杂,所以很快我们就需要对其进行调试。在下一篇文章中,我们将进行 exactly 这一点。我们将使用 Visual Studio Code 来编辑和调试在 Docker 容器内运行的代码。敬请期待!