Press "Enter" to skip to content

使用TF Serving在Kubernetes上部署🤗 ViT

在上一篇文章中,我们展示了如何使用🤗 Transformers在本地使用TensorFlow Serving部署Vision Transformer (ViT)模型。我们涵盖了嵌入预处理和后处理操作在Vision Transformer模型中的应用、处理gRPC请求等主题!

尽管本地部署是构建有用项目的出发点,但在实际项目中,您需要执行能够为许多用户提供服务的部署。在本文中,您将学习如何使用Docker和Kubernetes扩展之前文章中的本地部署。因此,我们假设您对Docker和Kubernetes有一定的了解。

本文在之前的文章基础上构建,因此强烈建议您先阅读之前的文章。您可以在此存储库中找到本文中讨论的所有代码。

扩展本类部署的基本工作流程包括以下步骤:

  • 容器化应用逻辑:应用逻辑涉及处理请求并返回预测结果的模型。对于容器化,Docker是业界标准。

  • 部署Docker容器:您有多种选项。最常用的选项是将Docker容器部署到Kubernetes集群上。Kubernetes提供了许多友好的部署特性(例如自动缩放和安全性)。您可以使用Minikube等解决方案在本地管理Kubernetes集群,或者使用Elastic Kubernetes Service (EKS)等Serverless解决方案。

您可能想知道在Sagemaker、Vertex AI等提供了机器学习部署特定功能的时代,为什么要使用这样明确的设置。这是一个合理的思考。

上述工作流程在业界被广泛采用,并且许多组织从中受益。它已经经过多年的实战测试。它还可以让您在抽象非平凡的部分的同时更加精细地控制部署。

本文使用Google Kubernetes Engine (GKE)来提供和管理Kubernetes集群。我们假设您已经有一个启用计费的GCP项目(如果您使用GKE)。另外,请注意,您需要配置gcloud实用程序以在GKE上执行部署。但本文中讨论的概念同样适用于您决定使用Minikube的情况。

注意:本文中显示的代码片段可以在Unix终端上执行,只要您已经配置了gcloud实用程序以及Docker和kubectl。更多说明请参见相关存储库。

服务模型可以处理原始图像输入数据,并能进行预处理和后处理。

在本节中,您将看到如何使用基础TensorFlow Serving镜像将该模型容器化。TensorFlow Serving以SavedModel格式消耗模型。回想一下,您如何在之前的文章中获取这样一个SavedModel。我们假设您已经将SavedModel压缩为tar.gz格式。您可以在这里获取它以防万一。然后,SavedModel应该放置在<MODEL_NAME>/<VERSION>/<SavedModel>的特殊目录结构中。这样,TensorFlow Serving可以同时管理多个不同版本模型的部署。

准备Docker镜像

下面的shell脚本将SavedModel放置在父目录models下的hf-vit/1中。在准备Docker镜像时,您将复制其中的所有内容。这个示例中只有一个模型,但这是一种更具普适性的方法。

$ MODEL_TAR=model.tar.gz
$ MODEL_NAME=hf-vit
$ MODEL_VERSION=1
$ MODEL_PATH=models/$MODEL_NAME/$MODEL_VERSION

$ mkdir -p $MODEL_PATH
$ tar -xvf $MODEL_TAR --directory $MODEL_PATH

下面,我们展示了在我们的情况下models目录的结构:

$ find /models
/models
/models/hf-vit
/models/hf-vit/1
/models/hf-vit/1/keras_metadata.pb
/models/hf-vit/1/variables
/models/hf-vit/1/variables/variables.index
/models/hf-vit/1/variables/variables.data-00000-of-00001
/models/hf-vit/1/assets
/models/hf-vit/1/saved_model.pb

自定义的TensorFlow Serving镜像应该基于基础镜像构建。有多种方法可以实现这一点,但您可以通过在Docker容器中运行来完成,如官方文件所示。我们首先以后台模式运行tensorflow/serving镜像,然后将整个models目录复制到正在运行的容器中,如下所示。

$ docker run -d --name serving_base tensorflow/serving
$ docker cp models/ serving_base:/models/

我们使用了TensorFlow Serving的官方Docker镜像作为基础,但您也可以使用您自己从源代码构建的镜像。

注意:TensorFlow Serving可以利用诸如AVX512等指令集的硬件优化来加速深度学习模型的推断。因此,如果您知道模型将在哪种硬件上部署,通常有利于获取一个经过优化的TensorFlow Serving镜像并在整个过程中使用它。

现在,正在运行的容器已经具有了所需的文件和适当的目录结构,我们需要创建一个新的Docker镜像,包含这些更改。可以使用下面的docker commit命令来完成,这样您将获得一个名为$NEW_IMAGE的新Docker镜像。需要注意的一点是,您需要设置MODEL_NAME环境变量为模型的名称,在本例中为hf-vit。这将告诉TensorFlow Serving要部署哪个模型。

$ NEW_IMAGE=tfserving:$MODEL_NAME

$ docker commit \ 
    --change "ENV MODEL_NAME $MODEL_NAME" \ 
    serving_base $NEW_IMAGE

在本地运行Docker镜像

最后,您可以在本地运行新构建的Docker镜像,以查看其是否正常工作。下面是docker run命令的输出结果。由于输出较长,我们对其进行了裁剪,以便重点关注重要部分。另外值得注意的是,它打开了85008501端口,分别用于gRPC和HTTP/REST端点。

$ docker run -p 8500:8500 -p 8501:8501 -t $NEW_IMAGE &


---------OUTPUT---------
(Re-)adding model: hf-vit
Successfully reserved resources to load servable {name: hf-vit version: 1}
Approving load for servable version {name: hf-vit version: 1}
Loading servable version {name: hf-vit version: 1}
Reading SavedModel from: /models/hf-vit/1
Reading SavedModel debug info (if present) from: /models/hf-vit/1
Successfully loaded servable version {name: hf-vit version: 1}
Running gRPC ModelServer at 0.0.0.0:8500 ...
Exporting HTTP/REST API at:localhost:8501 ...

推送Docker镜像

这里的最后一步是将Docker镜像推送到镜像仓库。您将使用Google Container Registry (GCR)来完成此操作。以下代码可以帮助您完成此操作:

$ GCP_PROJECT_ID=<GCP_PROJECT_ID>
$ GCP_IMAGE=gcr.io/$GCP_PROJECT_ID/$NEW_IMAGE

$ gcloud auth configure-docker
$ docker tag $NEW_IMAGE $GCP_IMAGE
$ docker push $GCP_IMAGE

由于我们使用的是GCR,您需要在Docker镜像标签(注意其他格式)前加上gcr.io/<GCP_PROJECT_ID>。准备好并将Docker镜像推送到GCR后,您现在可以继续在Kubernetes集群上部署它。

在Kubernetes集群上部署需要以下步骤:

  • 使用Google Kubernetes Engine (GKE)预配Kubernetes集群,本文中使用GKE进行演示,但您也可以使用其他平台和工具,如EKS或Minikube。

  • 连接到Kubernetes集群以进行部署。

  • 编写YAML清单文件。

  • 使用实用工具kubectl执行部署。

让我们逐步介绍这些步骤。

在GKE上预配Kubernetes集群

您可以使用如下的shell脚本来完成此操作(在此处可用):

$ GKE_CLUSTER_NAME=tfs-cluster
$ GKE_CLUSTER_ZONE=us-central1-a
$ NUM_NODES=2
$ MACHINE_TYPE=n1-standard-8

$ gcloud container clusters create $GKE_CLUSTER_NAME \
    --zone=$GKE_CLUSTER_ZONE \
    --machine-type=$MACHINE_TYPE \
    --num-nodes=$NUM_NODES

GCP提供了多种可配置部署的机器类型。我们鼓励您参考文档以了解更多相关信息。

一旦群集被配置好,您需要连接到群集进行部署。由于这里使用了GKE,您还需要进行身份验证。您可以使用如下的shell脚本来完成这两个操作:

$ GCP_PROJECT_ID=<GCP_PROJECT_ID>

$ export USE_GKE_GCLOUD_AUTH_PLUGIN=True

$ gcloud container clusters get-credentials $GKE_CLUSTER_NAME \
    --zone $GKE_CLUSTER_ZONE \
    --project $GCP_PROJECT_ID

gcloud container clusters get-credentials命令同时处理了连接到群集和身份验证的操作。完成这些操作后,您就可以准备编写清单文件了。

编写Kubernetes清单文件

Kubernetes清单文件使用YAML格式编写。虽然可以使用单个清单文件来执行部署,但是创建单独的清单文件通常有助于委派关注点的分离。通常使用三个清单文件来实现这一点:

  • deployment.yaml通过提供Docker镜像的名称、运行Docker镜像时的额外参数、开放的对外访问端口以及资源限制来定义Deployment的期望状态。

  • service.yaml定义了外部客户端与Kubernetes集群中的Pod之间的连接。

  • hpa.yaml定义了扩容和缩容由Deployment组成的Pod数量的规则,例如CPU利用率的百分比。

您可以在此处找到与本文相关的清单文件。下面,我们以图示的方式呈现这些清单文件的使用方式。

使用TF Serving在Kubernetes上部署🤗 ViT 四海 第1张

接下来,我们将逐个介绍这些清单文件的重要部分。

deployment.yaml :

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: tfs-server
  name: tfs-server
...
spec:
  containers:
  - image: gcr.io/$GCP_PROJECT_ID/tfserving-hf-vit:latest
    name: tfs-k8s
    imagePullPolicy: Always
    args: ["--tensorflow_inter_op_parallelism=2", 
           "--tensorflow_intra_op_parallelism=8"] 
    ports:
    - containerPort: 8500
      name: grpc
    - containerPort: 8501
      name: restapi
    resources:
      limits:
        cpu: 800m
      requests:
        cpu: 800m
...

您可以根据需要自定义名称,如tfs-servertfs-k8s。在containers下,您指定了部署将使用的Docker镜像URI。通过设置容器的resources的允许范围,可以监视当前的资源利用情况。它可以让Horizontal Pod Autoscaler(稍后讨论)决定扩容或缩容容器的数量。requests.cpu是操作者设置的使容器正常工作所需的最小CPU资源量。这里的800m表示整个CPU资源的80%。因此,HPA监视所有Pod的requests.cpu总和的平均CPU利用率,以进行扩容决策。

除了Kubernetes特定的配置,您还可以在args中指定TensorFlow Serving的特定选项。在本例中,有两个选项:

  • tensorflow_inter_op_parallelism,设置并行运行执行独立操作的线程数。推荐值为2。

  • tensorflow_intra_op_parallelism,设置并行运行执行单个操作的线程数。推荐值为部署CPU的物理核心数。

您可以在这里和这里了解更多关于这些选项(和其他选项)以及在部署中对它们进行调优的提示。

service.yaml

apiVersion: v1
kind: Service
metadata:
  labels:
    app: tfs-server
  name: tfs-server
spec:
  ports:
  - port: 8500
    protocol: TCP
    targetPort: 8500
    name: tf-serving-grpc
  - port: 8501
    protocol: TCP
    targetPort: 8501
    name: tf-serving-restapi
  selector:
    app: tfs-server
  type: LoadBalancer

我们将服务类型设置为“LoadBalancer”,以便将端点外部暴露给Kubernetes集群。它选择“tfs-server”部署以通过指定的端口与外部客户端建立连接。我们为gRPC和HTTP/REST连接分别打开了两个端口:’8500’和’8501’。

hpa.yaml

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
 name: tfs-server

spec:
 scaleTargetRef:
   apiVersion: apps/v1
   kind: Deployment
   name: tfs-server
 minReplicas: 1
 maxReplicas: 3
 targetCPUUtilizationPercentage: 80

HPA代表Horizontal Pod Autoscaler。它设置了决定何时扩展目标部署中的Pod数量的标准。您可以在这里了解Kubernetes内部使用的自动扩展算法。

在这里,您指定了Kubernetes应该如何处理自动扩展。特别是,您定义了应该执行自动扩展的副本数量范围 – minReplicasmaxReplicas以及目标CPU利用率。对于自动扩展来说,targetCPUUtilizationPercentage是一个重要的指标。以下线程恰当地总结了它的含义(摘自这里):

CPU利用率是部署中所有Pod在最后一分钟内的平均CPU使用率除以此部署的请求CPU。如果Pod的CPU利用率的平均值高于您定义的目标值,则会调整您的副本。

回想一下,在部署清单中指定了resources。通过指定resources,Kubernetes控制平面开始监视指标,因此targetCPUUtilization起作用。否则,HPA不知道部署的当前状态。

您可以根据自己的要求进行实验和设置这些要求。但是,请注意,自动扩展将取决于您在GCP上可用的配额,因为GKE内部使用Google Compute Engine来管理这些资源。

执行部署

一旦清单准备好,您可以使用kubectl apply命令将它们应用到当前连接的Kubernetes集群。

$ kubectl apply -f deployment.yaml
$ kubectl apply -f service.yaml
$ kubectl apply -f hpa.yaml

使用kubectl来应用每个清单以执行部署是可以的,但如果有许多不同的清单,这可能会变得更加困难。这就是Kustomize这样的实用工具可以发挥作用的地方。您只需要像这样定义另一个名为kustomization.yaml的规范:

commonLabels:
  app: tfs-server
resources:
- deployment.yaml
- hpa.yaml
- service.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

然后,只需一行命令就可以执行实际的部署:

$ kustomize build . | kubectl apply -f -

完整的说明可以在这里找到。部署完成后,我们可以像这样获取端点IP:

$ kubectl rollout status deployment/tfs-server
$ kubectl get svc tfs-server --watch

---------OUTPUT---------
NAME        TYPE          CLUSTER-IP   EXTERNAL-IP  PORT(S)                        AGE
tfs-server  LoadBalancer  xxxxxxxxxx   xxxxxxxxxx   8500:30869/TCP,8501:31469/TCP  xxx

当外部IP可用时,请记录下来。

这就是在Kubernetes上部署模型所需的所有步骤!Kubernetes优雅地提供了复杂部分的抽象,如自动缩放和集群管理,同时让您专注于部署模型时应关注的关键方面。这些包括资源利用、安全性(我们在这里没有涵盖)、性能指标如延迟等。

假设您获得了端点的外部IP,您可以使用以下列表来进行测试:

import tensorflow as tf 
import json
import base64

image_path = tf.keras.utils.get_file(
    "image.jpg", "http://images.cocodataset.org/val2017/000000039769.jpg"
)
bytes_inputs = tf.io.read_file(image_path)
b64str = base64.urlsafe_b64encode(bytes_inputs.numpy()).decode("utf-8")
data = json.dumps(
    {"signature_name": "serving_default", "instances": [b64str]}
)

json_response = requests.post(
    "http://<ENDPOINT-IP>:8501/v1/models/hf-vit:predict", 
    headers={"content-type": "application/json"}, 
    data=data
)
print(json.loads(json_response.text))

---------OUTPUT---------
{'predictions': [{'label': 'Egyptian cat', 'confidence': 0.896659195}]}

如果您有兴趣了解在更多流量下此部署的性能,我们建议您查阅这篇文章。请参阅相应的存储库,了解如何使用Locust运行负载测试并可视化结果。

TensorFlow Serving提供了各种选项,以根据您的应用程序使用情况定制部署。在下面,我们简要讨论其中一些。

enable_batching启用批量推断功能,它会在一定时间窗口内收集传入请求,将它们合并为批量,执行批量推断,并将每个请求的结果返回给相应的客户端。TensorFlow Serving提供了丰富的可配置选项(如max_batch_sizenum_batch_threads),以满足您的部署需求。您可以在此处了解更多信息。批处理对于那些不需要立即从模型中获取预测的应用程序特别有益。在这些情况下,您通常会将多个样本进行批量预测,然后将这些批次发送进行预测。幸运的是,当我们启用其批处理功能时,TensorFlow Serving可以自动配置所有这些。

enable_model_warmup使用虚拟输入数据预热一些懒加载的TensorFlow组件。这样,您可以确保一切都被适当加载,并且在实际服务时间中不会出现延迟。

在本文和相关存储库中,您了解了如何在Kubernetes集群上部署🤗 Transformers的Vision Transformer模型。如果您是第一次这样做,这些步骤可能看起来有点令人生畏,但一旦掌握,它们将很快成为您工具箱中的重要组成部分。如果您已经熟悉这个工作流程,我们希望本文对您仍然有所帮助。

我们为相同的Vision Transformer模型的经过ONNX优化的版本应用了相同的部署工作流程。有关更多详细信息,请查看此链接。如果您在部署中使用x86 CPU,ONNX优化模型尤其有益。

在下一篇文章中,我们将向您展示如何使用更少的代码在Vertex AI上执行这些部署,就像model.deploy(autoscaling_config=...)一样简单!我们希望您和我们一样兴奋。

感谢Google的ML Developer Relations Program团队为我们提供GCP积分以进行实验。

Leave a Reply

Your email address will not be published. Required fields are marked *