目录
介绍
Docker等容器技术可简化依赖项管理并提高软件的可移植性。在本系列文章中,我们探讨了Docker在机器学习(ML)场景中的使用。
本系列假设您熟悉AI/ML、容器化,尤其是Docker。
在本系列的前一篇文章中,我们使用保存在Docker卷上的模型运行了NLP推理模型。在本教程中,我们将使用Fast API和Gunicorn将上述模型作为服务公开。欢迎您下载本文中使用的代码。
为什么是快速API?
目前,Flask是Python Rest API服务之王。然而,Fast API正在迅速获得动力。虽然Flask及其所有插件在灵活性方面难以匹敌,但Fast API在大多数常见场景中使用起来更快、更简单。此外,它还带有对异步通信和OpenAPI的本机支持。
部署快速API
与Flask不同,Fast API甚至不包括一个简单的开发服务器。为它推荐的第三方选项是Uvicorn。Uvicorn本身缺乏许多您期望从生产服务器获得的功能,例如扩展正在运行的进程的数量或自动重启失败的工作程序。为了处理此类场景,Fast API通常与Gunicorn管理的Uvicorn workers一起使用。虽然这种安排看起来有些复杂,但实现起来却非常容易。这是我们将在本文中使用的。
容器定义
我们的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配置时解释这一点。
在最后两个语句中,我们记录了为TCP协议公开端口8000并添加一个命令来使用Gunicorn启动我们的服务。请注意10分钟超时。这似乎有点矫枉过正,但这只是为了确保有足够的时间在第一次执行API方法时将模型下载到Docker卷。
请注意,我们在这里只请求一个worker(参数--workers=1)。您可以增加此值以处理多个并发请求。但是,请记住,每个worker都会将单独的模型副本加载到内存中。不幸的是,由于在模型上运行推理是一项受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浏览器中输入以下本地地址来完成:
http://localhost:8000/docs
要运行语义分析,我们只需单击选定的方法,然后单击“试用”按钮:
然后,我们可以直接在请求正文字段中输入我们的文本,然后单击下面的执行按钮:
请求完成后,我们可以向下滚动到结果:
任务完成!随意尝试其余的方法。请记住,第一次使用新方法时,它会下载自己的模型。在这种情况下,这可能需要一段时间才能得到响应。
限制
现在我们有一个带有Transformers NLP模型的Rest API服务。请注意,并非总是应该做可以做的事情。Transformers模型非常笨重、缓慢,而且一般来说,它们比临时Web请求更适合批处理。
当通过Rest API公开这些模型时,您需要考虑超时并确保每个worker有足够的RAM将他们自己的模型加载到内存中。
此外,我们的API服务并非旨在直接暴露于互联网流量。在目前的形式中,没有诸如身份验证、授权或CORS之类的安全措施,它应该保留在防火墙后面以进行可信的系统到系统通信。
如果需要,FastAPI本身支持OAuth2。但是,我们希望模型中的服务尽可能精简,并添加额外的层来处理安全性或任何其他业务逻辑。如果有兴趣,请查看本文以了解如何将大型(和慢速)Transformers模型可靠地公开到Internet的示例。
总结
在本文中,我们使用Fast API通过Web浏览器在我们的NLP模型上运行推理。我们的代码变得越来越复杂,所以很快我们就需要调试它。在接下来的文章中,我们将这样做。我们将使用Visual Studio Code来编辑和调试在Docker容器内运行的代码。敬请关注!
https://www.codeproject.com/Articles/5302894/Exposing-Dockerized-AI-Models-via-RESTful-API