亚马逊AWS官方博客

Amazon EKS 现在支持 EC2 Inf1 实例

Amazon Elastic Kubernetes Service (EKS) 已经迅速成为机器学习工作负载的的首要选择。它结合了开发人员敏捷性与 Kubernetes 的可扩展性,并可选择 AWS 上可用的多种 Amazon Elastic Compute Cloud (EC2) 实例类型,例如 C5P3G4 系列。

随着模型变得更加精密,越来越需要通过硬件加速以更高的吞吐量实现更快的预测。今天,我们非常兴奋地宣布,AWS 客户现在可将 Amazon EC2 Inf1 实例用在 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 实例的 EKS 集群上部署经 Neuron 优化的模型,以及如何使用 TensorFlow Serving 来进行预测。所述的模型是 BERT,这是用于自然语言处理任务的最新模型。这种巨大的模型包含数似亿个参数,因此极其适合用于硬件加速。

构建 EC2 Inf1 实例的 EKS 集群
首先,我们来构建包含两个 inf1.2xlarge 实例的集群。我可以通过 eksctl 轻松地完成此工作,这种命令行工具可预置和管理 EKS 集群。您可以在 EKS 文档中找到安装说明

此处显示的是我的集群的配置文件。Eksctl 检测到我正在启动 Inf1 实例类型的节点组,并将使用 EKS-optimized Accelerated AMI 启动您的工作线程节点。

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: cluster-inf1
  region: us-west-2
nodeGroups:
  - name: ng1-public
    instanceType: inf1.2xlarge
    minSize: 0
    maxSize: 3
    desiredCapacity: 2
    ssh:
      allow: true

然后,我使用 eksctl 创建该集群。此流程将需要大约 10 分钟。

$ eksctl create cluster -f inf1-cluster.yaml

Eksctl 自动在您的集群中安装 Neuron 设备插件。此插件可向 Kubernetes 计划程序推广 Neuron 设备,计划程序可由容器使用部署规范请求。我可以使用 kubectl 检查设备插件容器是否在两个 Inf1 实例上都运行良好。

$ kubectl get pods -n kube-system
NAME                                  READY STATUS  RESTARTS AGE
aws-node-tl5xv                        1/1   Running 0        14h
aws-node-wk6qm                        1/1   Running 0        14h
coredns-86d5cbb4bd-4fxrh              1/1   Running 0        14h
coredns-86d5cbb4bd-sts7g              1/1   Running 0        14h
kube-proxy-7px8d                      1/1   Running 0        14h
kube-proxy-zqvtc                      1/1   Running 0        14h
neuron-device-plugin-daemonset-888j4  1/1   Running 0        14h
neuron-device-plugin-daemonset-tq9kc  1/1   Running 0        14h

接下来,我在 Kubernetes 密钥中定义 AWS 凭据。通过它们我将可获取存储在 S3 中的 BERT 模型。请注意,两个密钥都需要采用 base64 编码格式。

apiVersion: v1 
kind: Secret 
metadata: 
  name: aws-s3-secret 
type: Opaque 
data: 
  AWS_ACCESS_KEY_ID: <base64-encoded value> 
  AWS_SECRET_ACCESS_KEY: <base64-encoded value>

最后,我将这些凭据存储在该集群上。

$ kubectl apply -f secret.yaml

集群已正确设置。现在,我们来构建存储启用了 Neuron 版的 TensorFlow Serving 的应用程序容器。

为 TensorFlow Serving 构建应用程序容器
Dockerfile 非常简单。我们从 Amazon Linux 2 基本映像开始。然后,我们安装 AWS CLI 以及可从 Neuron 存储库中获得的 TensorFlow Serving 包

FROM amazonlinux:2
RUN yum install -y awscli
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

我构建映像,创建 Amazon Elastic Container Registry 存储库,并将映像推送给它。

$ docker build . -f Dockerfile -t tensorflow-model-server-neuron
$ docker tag IMAGE_NAME 123456789012.dkr.ecr.us-west-2.amazonaws.com/inf1-demo
$ aws ecr create-repository --repository-name inf1-demo
$ docker push 123456789012.dkr.ecr.us-west-2.amazonaws.com/inf1-demo

我们的应用程序容器已就绪。现在,我们来定义将使用此容器进行 BERT 预测的 Kubernetes 服务。我使用的是通过 Neuron SDK 编译的模型。您可以使用可从 Neuron SDK 存储库中获得的说明编译自己的模型。

部署 BERT 作为 Kubernetes 服务
该部署管理两个容器:Neuron 运行时容器,以及我的应用程序容器。Neuron 运行时作为伴生容器映像运行,用于与 AWS Inferentia 芯片交互。启动时,后者使用适当的安全凭据配置 AWS CLI。然后,它从 S3 获取 BERT 模型。最后,它启动 TensorFlow Serving,加载 BERT 模型并等待预测请求。为此,HTTP 和 grpc 端口打开。下面是完整清单。

kind: Service
apiVersion: v1
metadata:
  name: eks-neuron-test
  labels:
    app: eks-neuron-test
spec:
  ports:
  - name: http-tf-serving
    port: 8500
    targetPort: 8500
  - name: grpc-tf-serving
    port: 9000
    targetPort: 9000
  selector:
    app: eks-neuron-test
    role: master
  type: ClusterIP
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: eks-neuron-test
  labels:
    app: eks-neuron-test
    role: master
spec:
  replicas: 2
  selector:
    matchLabels:
      app: eks-neuron-test
      role: master
  template:
    metadata:
      labels:
        app: eks-neuron-test
        role: master
    spec:
      volumes:
        - name: sock
          emptyDir: {}
      containers:
      - name: eks-neuron-test
        image: 123456789012.dkr.ecr.us-west-2.amazonaws.com/inf1-demo:latest
        command: ["/bin/sh","-c"]
        args:
          - "mkdir ~/.aws/ && \
           echo '[eks-test-profile]' > ~/.aws/credentials && \
           echo AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID >> ~/.aws/credentials && \
           echo AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY >> ~/.aws/credentials; \
           /usr/bin/aws --profile eks-test-profile s3 sync s3://jsimon-inf1-demo/bert /tmp/bert && \
           /usr/local/bin/tensorflow_model_server_neuron --port=9000 --rest_api_port=8500 --model_name=bert_mrpc_hc_gelus_b4_l24_0926_02 --model_base_path=/tmp/bert/"
        ports:
        - containerPort: 8500
        - containerPort: 9000
        imagePullPolicy: Always
        env:
        - name: AWS_ACCESS_KEY_ID
          valueFrom:
            secretKeyRef:
              key: AWS_ACCESS_KEY_ID
              name: aws-s3-secret
        - name: AWS_SECRET_ACCESS_KEY
          valueFrom:
            secretKeyRef:
              key: AWS_SECRET_ACCESS_KEY
              name: aws-s3-secret
        - name: NEURON_RTD_ADDRESS
          value: unix:/sock/neuron.sock

        resources:
          limits:
            cpu: 4
            memory: 4Gi
          requests:
            cpu: "1"
            memory: 1Gi
        volumeMounts:
          - name: sock
            mountPath: /sock

      - name: neuron-rtd
        image: 790709498068.dkr.ecr.us-west-2.amazonaws.com/neuron-rtd:1.0.6905.0
        securityContext:
          capabilities:
            add:
            - SYS_ADMIN
            - IPC_LOCK

        volumeMounts:
          - name: sock
            mountPath: /sock
        resources:
          limits:
            hugepages-2Mi: 256Mi
            aws.amazon.com/neuron: 1
          requests:
            memory: 1024Mi

我使用 kubectl 创建该服务。

$ kubectl create -f bert_service.yml

几秒钟后,Pod 开始运行。

$ kubectl get pods
NAME                           READY STATUS  RESTARTS AGE
eks-neuron-test-5d59b55986-7kdml 2/2   Running 0        14h
eks-neuron-test-5d59b55986-gljlq 2/2   Running 0        14h

最后,我将服务端口 9000 重定向到本地端口 9000,使我的预测客户端从本地建立连接。

$ kubectl port-forward svc/eks-neuron-test 9000:9000 &

现在,预测所需的一切都准备就绪,让我们调用该模型。

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

在此,我只对衡量预测延迟感兴趣,因此模拟数据就已足够。我构建了 100 个预测请求,存储包含 128 个零的序列。我通过 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('localhost:9000')
    stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)
    request = predict_pb2.PredictRequest()
    request.model_spec.name = 'bert_mrpc_hc_gelus_b4_l24_0926_02'
    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)))

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

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

在现实中,我们肯定会批量处理预测请求以提高吞吐量。如果需要,我们还可以扩展到支持多个 Inferentia 芯片的更大 Inf1 实例,以较低成本获得更高的预测性能。

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

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

– Julien