用AWS Lambda和Ephemeral Storage部署ML模型

144 阅读6分钟
image.png

你是一名机器学习工程师,想用一种简单且有可能扩展的方式来部署你的大型机器学习模型?在这篇文章中,我将向你介绍一个相对简单的解决方案,它利用了Lambda最近增加的Ephemeral Storage的功能。

请放心,你不必浏览AWS控制台并手动点击生成所有资源。相反,我们将利用AWS命令行界面(CLI)和AWS云开发套件(CDK)的少量基础设施即代码。后者使我们有可能定义我们的服务和它们的关系,而不需要手动制作复杂的CloudFormation模板。

基础设施看起来很简陋:

image.png

从历史上看,Lambda并不是ML-Ops的首选,因为它在存储大型模型方面的限制。然而,这个特殊的限制后来得到了缓解,因为Lambda现在允许高达10GB的短暂存储*(/tmp*内存),我们可以利用它来下载和缓存我们的模型。此外,我们可以通过附加一个作为API的Function URL,轻松地暴露Lambda。

该部署包括以下步骤:

  1. ML模型:创建机器学习模型并将其上传到S3桶中。
  2. Lambda函数:创建用于推理的lambda函数代码
  3. Docker镜像:Docker化推理代码并将图像上传到弹性容器注册处(ECR)。
  4. 基础设施:用CDK创建资源

让我们动手做一个深度学习的玩具例子。我们将使用Flair构建和部署一个命名实体识别器(NER)。Flair提供了一个简单的接口,可以使用Huggingface的最先进的模型来解决大量的NLP任务。

先决条件

你需要安装以下工具和框架:

  • Python≥3.8
  • Docker
  • 具有12位数ID的AWS账户(例如:012345678901)
  • AWS CLI,用于AWS服务的命令行界面
  • AWS CDK CLI,AWS云开发工具包(CDK)的命令行接口

设置环境

克隆资源库,安装依赖项:

git clone https://github.com/as-ideas/deep-lambda.gitcd deep-lambdapip install -r requirements.txt

仓库的结构如下:

deep-lambda/|-- app.py |-- tagger.py|-- infrastructure/|-- ... 

我们只需要两个Python文件,包含深度学习代码的tagger.py和定义AWS lambda函数的app.py 。我们基础设施的CDK代码定义在infrastructure/lambda_stack.py中*。*

1.ML模型

首先,让我们使用Flair编写一个简单的NER标记器:

deep-lambda/tagger.py

运行上述代码,从Huggingface下载一个预先训练好的NER模型,将其保存到*/tmp/my_ner_tagger.pt*,并输出以下结果:

python tagger.pySpan [1,2]: "George Washington"   [− Labels: PER (0.9985)]Span [5]: "Washington"   [− Labels: LOC (0.9706)]

够简单了。从现在开始,让我们假设保存的模型是一些我们想要部署的自定义NER模型(在这里你可以阅读如何用Flair对预训练的模型进行微调)。

为了部署我们的模型,我们需要它驻留在AWS云中。因此,我们通过命令行界面将模型上传到S3桶(用你自己的区域代替):

aws s3api create-bucket --bucket deep-lambda --region eu-central-1 --create-bucket-configuration LocationConstraint=eu-central-1aws s3 cp /tmp/my_ner_tagger.pt s3://deep-lambda-2/ --region eu-central-1

2.Lambda函数

为了通过提供AWS Lambda Function Stack来启动代码,需要定义一个lambda handler,作为该函数的一个入口。lambda处理程序负责接收一个事件并生成相应的响应。我们的标签逻辑是由处理程序调用的,这就需要事先加载模型。我们通过在模块层面下载和缓存模型来解决这个问题,利用lambda在*/tmp*上的短暂存储:

deep-lambda/app.py

注意,模型被下载到*/tmp*,默认情况下,它被挂载到短暂存储中,所以有可能消耗大到几GB的模型。

3.Docker镜像

我们的Lambda将需要访问一些依赖项(例如Flair),我们将把这些依赖项放入docker镜像中。

一个简单的Docker文件可以从Python lambda基础镜像中构建:

FROM public.ecr.aws/lambda/python:3.8 as baseFROM baseCOPY requirements.txt .RUN  pip install -r requirements.txt --target "${LAMBDA_TASK_ROOT}"COPY app.py "${LAMBDA_TASK_ROOT}"COPY tagger.py "${LAMBDA_TASK_ROOT}"ENV PYTHONPATH="${LAMBDA_TASK_ROOT}"CMD ["app.lambda_handler"]

这是很标准的--我们只需要确保所有的文件都被正确地复制到Lambda原生位置Lambda_TASK_ROOT,也就是给定的工作目录。该路径通常在图像中解析为*/var/task*。

让我们构建并标记docker镜像(用你自己的AWS ID替换12位数字的代码):

docker build -t deep-lambda .docker tag deep-lambda:latest 012345678901.dkr.ecr.eu-central-1.amazonaws.com/deep-lambda:latest

我们现在通过命令行界面将镜像上传到弹性容器注册表(ECR):

aws ecr create-repository --repository-name deep-lambdaaws ecr get-login-password --region eu-central-1 | docker login --username AWS --password-stdin 012345678901.dkr.ecr.eu-central-1.amazonaws.comdocker push 012345678901.dkr.ecr.eu-central-1.amazonaws.com/deep-lambda:latest

这就是了。我们成功地将我们的模型和代码都上传到了AWS生态系统中。现在,是时候通过CDK提供云基础设施了

4.基础设施

让我们写一个简单的CDK栈,定义访问模型桶的lambda函数:

为Lambda提供足够的内存大小以及足够大的ephemeral_storage_size 很重要*。* 此外,我们需要将PYTORCH_TRANSFORMERS_CACHE目录指向*/tmp*目录,以允许Transformers库将模型标记器缓存到瞬时存储中。

现在我们已经准备好部署我们的函数了:

cd infrastructurepip install -r requirements.txtcdk synthcdk deploy deep-lambda-stack

就这样,lambda应该已经启动并运行了。让我们在在线控制台中快速检查一下它: image.png

在右下方你可以找到函数的URL。让我们用它来调用我们的Lambda,通过curl的简单请求:

curl -X POST -H "Content-Type: text/plain" -d "I went to New York last easter." https://rrpj3itxliq4754rbwscjg7p3i0geucq.lambda-url.eu-central-1.on.aws/Span[3:5]: "New York" → LOC (0.9988)

很好!现在你可以向端点发出更多的请求。重要的是要记住,由于冷启动过程,最初的请求可能需要更长的时间,因为lambda初始化并从桶中检索模型。然而,随后的请求应该迅速执行。

部署一个模型就像替换S3桶中的当前模型和重启lambda函数一样简单,这可以通过控制台或CLI完成。更新代码需要你使用前面的AWS命令推送一个新的镜像到ECR,然后将更新的镜像部署到Lambda函数上。

限制和使解决方案可扩展

如果你想快速部署你的模型进行展示或测试,所提出的解决方案是相当有用的,但它可能不够强大,无法用于生产系统。我将在下面讨论最大的限制:

局限性: 手动部署,解决方案: 添加CI/CD管道

考虑添加一个AWS Codepipeline,在代码更改时自动触发,将新的图像推送到ECR,并重新部署你的Lambda函数。

限制性: 安全性,解决方案: 添加API网关

如果你需要控制你的API暴露,即限制请求的最大数量或阻止某些IP地址,你可能想连接一个API网关 到你的Lambda。

限制: 可扩展性,解决方案: 添加队列

如果你想处理大量的数据,不建议向Lambda函数拓宽冗长的请求。一个更稳健的解决方案是将该函数连接到一个命令队列(SQS),并将结果存储在另一个队列或S3桶中。这种方法具有高度的可扩展性,并且可以直接监控,是大多数ML用例的理想选择。