亚马逊AWS官方博客

Amazon ECS 现在支持 EC2 Inf1 实例

随着机器学习和深度学习模型变得更加精密,越来越需要通过硬件加速以更高的吞吐量实现更快的预测。今天,我们非常兴奋地宣布,AWS 客户现在可将 Amazon EC2 Inf1 实例用在 Amazon ECS 上,以在云中获得高性能和最低的预测成本。现在,这些实例已经可以Amazon Elastic Kubernetes Service 使用几个星期。

EC2 Inf1 实例基础
Inf1 实例在 AWS re:Invent 2019 上发布。它们由 AWS Inferentia 提供支持,这是 AWS 重新制造的一种定制芯片,用于加速机器学习推理工作负载。

Inf1 实例可用于多个区域,采用 1、4 或 16 个 AWS Inferentia 芯片,网络带宽可高达 100 Gbps,EBS 带宽可高达 19 Gbps。AWS Inferentia 芯片中包含四个 NeuronCore。每个都可以部署高性能脉动阵列矩阵乘法引擎,大幅加快典型深度学习运算的速度,例如卷积和 Transformer。NeuronCore 还配有大型芯片上缓存,可帮助减少外部存储器访问,节省流程中的 I/O 时间。当 Inf1 实例上有多个 AWS Inferentia 芯片可用时,您可以跨芯片分割模型并将其完整存储在高速缓冲存储器中。或者,为了从单 Inf1 实例服务多模型分割,您可以跨多个模型分割 AWS Inferentia 芯片的 NeuronCore。

为 EC2 Inf1 实例编译模型
要在 Inf1 实例上运行机器学习模型,您需要使用 AWS Neuron SDK 将其编译到经过硬件优化的表示中。所有工具均可从 AWS Deep Learning AMI 中获得,您还可以将其安装在自己的实例上。您将可以在 Deep Learning AMI 文档以及 AWS Neuron SDK 存储库中适用于 TensorFlow、PyTorch 和 Apache MXNet 的教程中找到相关说明。

在下面的演示中,我将向您展示如何在 Inf1 实例的 ECS 集群上部署经 Neuron 优化的模型,以及如何使用 TensorFlow Serving 来进行预测。所述的模型是 BERT,这是用于自然语言处理任务的最新模型。这种巨大的模型包含数以亿个参数,因此极其适合用于硬件加速。

创建 Amazon ECS 集群
创建集群是最简单的事:只需要调用 CreateCluster API 即可。

$ aws ecs create-cluster --cluster-name ecs-inf1-demo

我立即可以在控制台中看到新集群。

新集群

需要满足几个先决条件才能将实例添加到此集群中:

  • ECS 实例的 AWS Identity and Access Management (IAM) 角色:如果您还没有该角色,可以在文档中查找说明。我的角色在此被命名为 ecsInstanceRole
  • 包含 ECS 代理和支持性 Inf1 实例的 Amazon 系统映像 (AMI)。您可以构建自己的系统映像,或使用适用于 Inferentia 的经 ECS 优化的 AMI。在 us-east-1 区域,它的 ID 是 ami-04450f16e0cd20356
  • 安全组,适用于 TensorFlow Serving 的开放网络端口(gRPC 为 8500,HTTP 为 8501)。我的标识符为 sg-0994f5c7ebbb48270
  • 如果您希望具有 ssh 访问权限,您的安全组也应开放端口 22,并且您应该传递 SSH 密钥对的名称。我的被称为 admin.

我们还需要创建一个小型用户数据文件,以便让实例加入我们的集群中。我们可以通过将集群名称存储在环境变量(环境变量本身写入 ECS 代理的配置文件中)来实现这一点。

#!/bin/bash
echo ECS_CLUSTER=ecs-inf1-demo >> /etc/ecs/ecs.config

现在我们一切准备就绪。我们来通过 RunInstances API 添加几个 Inf1 实例。为了最大限度降低成本,我们将请求使用 Spot 实例

$ aws ec2 run-instances \
--image-id ami-04450f16e0cd20356 \
--count 2 \
--instance-type inf1.xlarge \
--instance-market-options '{"MarketType":"spot"}' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=ecs-inf1-demo}]' \
--key-name admin \
--security-group-ids sg-0994f5c7ebbb48270 \
--iam-instance-profile Name=ecsInstanceRole \
--user-data file://user-data.txt

这两个实例都会立即显示在 EC2 控制台中。

Inf1 实例

几分钟后,它们就可以在群集上运行任务了。

Inf1 实例

我们的基础设施已经准备就绪。现在,让我们构建一个存储 BERT 模型的容器。

为 Inf1 实例构建容器
Dockerfile 非常简单:

  • Amazon Linux 2 映像开始,我们为 TensorFlow Serving 开放端口 8500 和 8501。
  • 然后,我们将 Neuron SDK 存储库添加到存储库列表中,并安装支持 AWS Inferentia 的 TensorFlow Serving 版本。
  • 最后,将 BERT 模型复制到容器内,并在启动时加载该模型。

下面是完整的文件。

FROM amazonlinux:2
EXPOSE 8500 8501
RUN echo $'[neuron] \n\
name=Neuron YUM Repository \n\
baseurl=https://yum.repos.neuron.amazonaws.com \n\
enabled=1' > /etc/yum.repos.d/neuron.repo
RUN rpm --import https://yum.repos.neuron.amazonaws.com/GPG-PUB-KEY-AMAZON-AWS-NEURON.PUB
RUN yum install -y tensorflow-model-server-neuron
COPY bert /bert
CMD ["/bin/sh", "-c", "/usr/local/bin/tensorflow_model_server_neuron --port=8500 --rest_api_port=8501 --model_name=bert --model_base_path=/bert/"]

然后,构建并将容器推送到在 Amazon Elastic Container Registry 中托管的存储库中。一切照常。

$ docker build -t neuron-tensorflow-inference .

$ aws ecr create-repository --repository-name ecs-inf1-demo

$ aws ecr get-login-password | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com

$ docker tag neuron-tensorflow-inference 123456789012.dkr.ecr.us-east-1.amazonaws.com/ecs-inf1-demo:latest

$ docker push

现在,我们需要创建一个任务定义 ,以便在我们的集群上运行此容器。

为 Inf1 实例创建任务定义
如果您还没有执行角色,首先应创建一个执行角色,即允许 ECS 代理代表您执行 API 调用的角色。您可以在文档中找到更多信息。我的角色被称为 ecsTaskExecutionRole

完整的任务定义如下所示。正如您所看到的,它包含两个容器:

  • 我构建的 BERT 容器,
  • 被称为 neuron-rtd 的伴生容器,该容器可使 BERT 容器访问 Inf1 实例上的 NeuronCores。使用 AWS_NEURON_VISIBLE_DEVICES 环境变量,您可以控制哪些环境可以被容器使用。您可以使用它将容器锁定在一个或多个特定的 NeuronCores 上。
{
  "family": "ecs-neuron",
  "executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
  "containerDefinitions": [
    {
      "entryPoint": [
        "sh",
        "-c"
      ],
      "portMappings": [
        {
          "hostPort": 8500,
          "protocol": "tcp",
          "containerPort": 8500
        },
        {
          "hostPort": 8501,
          "protocol": "tcp",
          "containerPort": 8501
        },
        {
          "hostPort": 0,
          "protocol": "tcp",
          "containerPort": 80
        }
      ],
      "command": [
        "tensorflow_model_server_neuron --port=8500 --rest_api_port=8501 --model_name=bert --model_base_path=/bert"
      ],
      "cpu": 0,
      "environment": [
        {
          "name": "NEURON_RTD_ADDRESS",
          "value": "unix:/sock/neuron-rtd.sock"
        }
      ],
      "mountPoints": [
        {
          "containerPath": "/sock",
          "sourceVolume": "sock"
        }
      ],
      "memoryReservation": 1000,
      "image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/ecs-inf1-demo:latest",
      "essential": true,
      "name": "bert"
    },
    {
      "entryPoint": [
        "sh",
        "-c"
      ],
      "portMappings": [],
      "command": [
        "neuron-rtd -g unix:/sock/neuron-rtd.sock"
      ],
      "cpu": 0,
      "environment": [
        {
          "name": "AWS_NEURON_VISIBLE_DEVICES",
          "value": "ALL"
        }
      ],
      "mountPoints": [
        {
          "containerPath": "/sock",
          "sourceVolume": "sock"
        }
      ],
      "memoryReservation": 1000,
      "image": "790709498068.dkr.ecr.us-east-1.amazonaws.com/neuron-rtd:latest",
      "essential": true,
      "linuxParameters": { "capabilities": { "add": ["SYS_ADMIN", "IPC_LOCK"] } },
      "name": "neuron-rtd"
    }
  ],
  "volumes": [
    {
      "name": "sock",
      "host": {
        "sourcePath": "/tmp/sock"
      }
    }
  ]
}

最后,我调用 RegisterTaskDefinition API 让 ECS 后端知道它。

$ aws ecs register-task-definition --cli-input-json file://inf1-task-definition.json

我们现在已经准备好运行我们的容器,并用它进行预测。

在 Inf1 实例上运行容器
由于这是一种预测服务,我想确保它始终可在集群上使用。我没有简单地运行任务,而是创建了一个 ECS 服务,以确保正在运行所需数量的容器副本,并在发生任何故障时重新启动容器副本。

$ aws ecs create-service --cluster ecs-inf1-demo \
--service-name bert-inf1 \
--task-definition ecs-neuron:1 \
--desired-count 1

一分钟后,我看到两个任务容器都在集群上运行。

正在运行的容器

在 ECS 和 Inf1 上使用 BERT 进行预测
BERT 的内部工作超出了本博文介绍的范围。这种特别的模型预计使用包含 128 个令牌的序列,对我们要进行语义等价比较的两个句子中的词进行编码。

在此,我只对衡量预测延迟感兴趣,因此模拟数据就已足够。我构建了 100 个预测请求,存储包含 128 个零的序列。使用 BERT 容器的 IP 地址,我通过 grpc 将它们发送到 TensorFlow Serving 终端节点,并计算平均预测时间。

下面是完整代码。

import numpy as np
import grpc
import tensorflow as tf
from tensorflow_serving.apis import predict_pb2
from tensorflow_serving.apis import prediction_service_pb2_grpc
import time

if __name__ == '__main__':
    channel = grpc.insecure_channel('18.234.61.31:8500')
    stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)
    request = predict_pb2.PredictRequest()
    request.model_spec.name = 'bert'
    i = np.zeros([1, 128], dtype=np.int32)
    request.inputs['input_ids'].CopyFrom(tf.contrib.util.make_tensor_proto(i, shape=i.shape))
    request.inputs['input_mask'].CopyFrom(tf.contrib.util.make_tensor_proto(i, shape=i.shape))
    request.inputs['segment_ids'].CopyFrom(tf.contrib.util.make_tensor_proto(i, shape=i.shape))

    latencies = []
    for i in range(100):
        start = time.time()
        result = stub.Predict(request)
        latencies.append(time.time() - start)
        print("Inference successful: {}".format(i))
    print ("Ran {} inferences successfully.Latency average = {}".format(len(latencies), np.average(latencies)))

为方便起见,我在基于 Deep Learning AMI 的 EC2 实例上运行此代码。它预安装了一个适用于 TensorFlow 和 TensorFlow Serving 的 Conda 环境,让我无需安装任何依赖项。

$ source activate tensorflow_p36
$ python predict.py

预测平均用时 56.5ms。只要 BERT 运行,这就相当完美!

成功运行了 100 次推理。平均延迟 = 0.05647835493087769

开始使用
现在,您可以在美国东部(弗吉尼亚北部)美国西部(俄勒冈)区域将 Amazon Elastic Compute Cloud (EC2) Inf1 实例部署在 Amazon ECS 上。随着 Inf1 部署进展,您将可以在更多区域中将它们与 Amazon ECS 结合使用。

试一下,然后请通过您平常的 AWS Support 联系人在 Amazon ECSAWS 论坛 或 Github 上的容器路线图上向我们发送反馈。

– Julien