Amazon Web Services 한국 블로그

Amazon EKS, 기계 학습 추론용 EC2 Inf1 인스턴스 지원 시작

Amazon Elastic Kubernetes Service (EKS)는 기계 학습 워크로드를 위한 최고의 선택으로 빠르게 자리매김하고 있습니다. 이 솔루션은 개발자의 민첩성 및 Kubernetes의 확장성을 C5, P3G4 패밀리와 같이 AWS에서 사용할 수 있는 다양한 Amazon Elastic Compute Cloud(EC2) 인스턴스 유형과 결합합니다.

모델이 더욱 정교해짐에 따라 높은 처리량으로 빠른 예측을 제공하는 데 하드웨어 가속이 점점 더 요구되고 있습니다. 오늘, AWS는 고객이 이제 클라우드에서 고성능을 구현하고 최저 예측 비용을 실현하기 위해 Amazon Elastic Kubernetes Service에서 Amazon EC2 Inf1 인스턴스를 사용할 수 있습니다.

EC2 Inf1 인스턴스에 대한 기본 정보
Inf1 인스턴스는 AWS re:Invent 2019에서 출시되었습니다. 이 인스턴스는 AWS가 기계 학습 추론 워크로드를 가속화하기 위해 처음부터 빌드한 맞춤형 칩인 AWS Inferentia에 의해 지원됩니다.

Inf1 인스턴스는 최대 100Gbps의 네트워크 대역폭 및 최대 19Gbps의 EBS 대역폭으로 1, 4 또는 16개의 AWS Inferentia 칩과 함께 다양한 크기로 제공됩니다. AWS Inferentia 칩에는 네 개의 NeuronCore가 포함되어 있습니다. 각 코어는 고성능 시스톨릭 배열 행렬 곱셈 엔진을 구현하여 컨볼루션 및 트랜스포머와 같은 일반적인 딥 러닝 연산의 속도를 크게 높여줍니다. 또한 NeuronCore에는 대용량 온-칩(on-chip) 캐시가 장착되어 있어 외부 메모리 액세스를 줄임으로써 프로세스의 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입니다. BERT는 수억 개의 파라미터를 가진 거대한 모델로, 하드웨어 가속을 위한 훌륭한 후보입니다.

EC2 Inf1 인스턴스의 EKS 클러스터 구축
우선 두 개의 inf1.2xlarge 인스턴스로 클러스터를 빌드하겠습니다. EKS 클러스터를 프로비저닝하고 관리하는 명령줄 도구인 eksctl을 사용하여 이 작업을 쉽게 수행할 수 있습니다. 설치 지침은 EKS 설명서에서 찾아볼 수 있습니다.

다음은 클러스터의 구성 파일입니다. Eksctl은 Inf1 인스턴스 유형으로 노드 그룹을 실행하고 있음을 감지하고 EKS 최적화 가속 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 리포지토리에서 제공되는 명령을 사용하여 직접 컴파일할 수도 있습니다.

Kubernetes Service로 BERT 배포
배포 시 두 개의 컨테이너, 즉 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개의 토큰 시퀀스를 예상하고 의미론적으로 동등한지 확인하기 위해 비교하려는 두 문장의 단어를 인코딩합니다.

여기서는 예측 지연 시간 측정에만 관심이 있으므로 더미 데이터가 좋습니다. 128개의 0으로 구성된 시퀀스를 저장하는 100개의 예측 요청을 빌드합니다. 그리고 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가 진행되는 한, 이 소요 시간은 꽤 좋습니다!

Ran 100 inferences successfully. Latency average = 0.05920819044113159

실제로는 처리량을 늘리기 위해 예측 요청을 확실히 일괄 처리합니다. 또한 필요한 경우 여러 Inferentia 칩을 지원하는 더 큰 Inf1 인스턴스로 확장하고 낮은 비용으로 훨씬 더 뛰어난 예측 성능을 제공할 수 있습니다.

시작하기
Kubernetes 사용자는 오늘 미국 동부(버지니아 북부)미국 서부(오레곤) 리전에서 Amazon Elastic Kubernetes ServiceAmazon Elastic Compute Cloud(EC2) Inf1 인스턴스를 배포할 수 있습니다. Inf1 배포가 진행됨에 따라 더 많은 리전에서 Amazon Elastic Kubernetes Service와 함께 이 인스턴스를 사용할 수 있습니다.

이를 사용해 보고, 기존 AWS Support 담당자를 통해 또는 Amazon Elastic Kubernetes ServiceAWS 포럼이나 Github의 컨테이너 로드맵에서 피드백을 제공해 주십시오.

– Julien