对话式人工智能/自然语言处理

使用 Kubernetes 自动缩放 NVIDIA Riva 部署,用于生产中的语音 AI

语音 AI 应用,从呼叫中心到虚拟助理,严重依赖 自动语音识别 ( ASR )和文本转语音( TTS )。 ASR 可以处理音频信号并将音频转录为文本。语音合成或 TTS 可以实时从文本中生成高质量、自然的声音。语音 AI 的挑战是实现高精度并满足实时交互的延迟要求。

NVIDIA Riva 是一个 GPU 加速 SDK ,用于构建语音 AI 应用程序,使用最先进的模型实现高精度,同时提供高吞吐量。 Riva 提供世界级的语音识别和文本到语音技能,以与人类进行多种语言的交互。 Riva 可以部署在内部 、云中、边缘或嵌入式平台上,您可以扩展 Riva 服务器,以低延迟处理数百或数千个实时流。这篇文章一步一步地指导您如何使用 Kubernetes 进行自动缩放和 Traefik 进行负载平衡来大规模部署 Riva 服务器。

Riva 可以针对不同应用程序(如聊天机器人、虚拟助手和转录服务)的数据集进行定制。预训练的模型可以通过 NVIDIA TAO Toolkit 使用迁移学习进行微调,使您的开发比从头开始预训练模型更快。 NVIDIA GPU Cloud 中提供了语音识别和语音合成的预训练模型。

非英语(西班牙语、德语和俄语) ASR 模型在多种开源数据集(如 Mozilla Common Voice )以及私有数据集上进行训练。 Riva 开发了自动语音识别模型,以提供开箱即用的准确度,并作为适应行业、行话、方言甚至嘈杂环境的良好起点。

为了让用户享受逼真的对话,语音应用程序必须提供类似人类的表情。使用 FastPitch 模型, Riva 帮助开发人员定制文本到语音的管道,并创建富有表现力的类人声音。例如,在推断期间,开发人员可以使用 SSML tags 改变语音音调和速度。最新的最先进的模型,如 Riva 中的 Fastpitch ,可以帮助文本到语音管道的运行速度比市场上其他竞争选项快几倍。

硬件和软件先决条件

要扩展 Riva 部署以以低延迟处理数百到数千个实时流,可以使用 Kubernetes 。为 Riva 部署准备 Kubernetes 集群需要满足几个先决条件。

首先,确保 Kubernetes 节点有 GPU 可用。在 GPU 上运行 Riva 的深度神经网络对于确保实时流的低延迟和高带宽要求至关重要。 NVIDIA Volta 或更高版本 GPU 支持 Riva ,建议每个 GPU 至少具有 16 GB 的 VRAM 。 Riva 也可以部署在具有适当 GPU 资源的公共云计算实例上,例如 AWS EC2 实例。

接下来,允许从 Kubernetes 集群中运行的工作负载访问 GPU 。最简单的方法是使用 NVIDIA GPU Operator ,它将自动设置和管理工作节点上的 NVIDIA 软件组件。

如果您愿意,也可以自己安装和管理 GPU 软件组件。在这种情况下,您需要在每个节点上安装 NVIDIA GPU DriverNVIDIA Container Toolkit ,以及 Kubernetes 的 NVIDIA 设备插件。这使 Kubernetes 能够发现哪些节点具有 GPU ,并使其可用于在这些节点上运行的容器。您还可以选择安装 GPU Feature Discovery plugin ,它将为每个节点生成描述可用 GPU 功能的标签。

NVIDIA GPU Operator 的公开版本是免费的,仅包括社区支持。 NVIDIA AI Enterprise 推荐用于企业客户。对于活动订阅,您可以使用经过充分验证且企业支持的 GPU Operator 版本。有关更多信息,请参阅 NVIDIA AI Enterprise 文档。

为了支持自动缩放, Prometheus 必须安装在 Kubernetes 集群中,以收集度量并将其报告给控制平面。还要确保在 Kubernetes 集群中启用 Kubernetes API Aggregation Layer ,这允许自动缩放器访问 Prometheus 等外部 API 。

要在下一节中使用 Helm 图表,请使用以下脚本安装 Helm :

$ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
$ chmod 700 get_helm.sh
$ ./get_helm.sh

然后使用 kube 普罗米修斯堆栈 Helm chart 安装普罗米修斯监视堆栈:

$ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
$ helm repo update
$ helm install <name> --set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false
prometheus-community/kube-prometheus-stack

Autoscale Riva deployment

使用自动缩放和负载平衡为语音 AI 推理部署 Riva 的四个主要步骤是:

  1. 为 Riva 服务器创建 Kubernetes deployment
  2. 创建 Kubernetes 服务以将 Riva 服务器公开为网络服务
  3. 自动缩放 Riva 部署:
    • 使用 kube Prometheus 和 ServiceMonitor 向 Prometheus 公开 NVIDIA Triton 指标
    • 为自定义度量部署 Prometheus 适配器
    • 创建水平吊舱自动缩放器( HPA )
  4. 使用 Traefik 在所有 Pod 之间分发客户端请求

Riva 部署的舵图

您可以使用 Helm 图表进行此部署,因为它很容易在不同环境中进行修改和部署。图表被组织为riva目录中的文件集合,如下所示。在riva目录中, Helm 需要一个与以下文件匹配的结构:

chart.yaml:这里是您打包的图表的所有信息。例如,您的版本号和依赖项。

values.yaml:您可以在这里定义要注入到模板目录中的所有值。

charts:这是存储 Riva 图表所依赖的其他图表的目录。 Riva 分别依赖于 Traefik 和 Prometheus 适配器图表进行入口和自动缩放。

templates:这是放置与图表一起部署的实际清单的目录。

对于 Riva 服务器,您需要deployment.yaml , service.yaml , [EZX205] ,ingressroute.yaml,它们都在templates目录中。这些都将从values.yaml中获得某些配置值。按照惯例, Helm 图表还包括部分文件(通常称为_helpers.tpl . )中的助手模板

您可以通过修改文件templates/*.yaml中的字段values.yaml来更改部署的配置。

riva/
     chart.yaml
     values.yaml
     charts/
     templates/
            deployment.yaml
            service.yaml
            hpa.yaml
            ingressroute.yaml
            _helpers.tpl

下面的chart.yaml文件需要指定图表的所有信息,例如 Riva 版本以及 Traefik Helm chart 和 Prometheus Community Kubernetes Helm Charts 的存储库。

apiVersion: v1
appVersion: 1.9.0-beta
description: Riva Speech Services
name: riva
version: 1.9.0-beta

dependencies:  
    - name: traefik    
      version: "~10.6.2"    
      repository: "https://helm.traefik.io/traefik"    
      tags:      
         - loadBalancing
    - name: prometheus-adapter   
      version: "~3.0.0"    
      repository: "https://prometheus-community.github.io/helm-charts"    
      tags:     
         - autoscaling

为 Riva 服务器创建 Kubernetes 部署

Kubernetes deploymentPodsReplicaSets 提供声明性更新。下面的deployment.yaml 为 GPU 服务器创建了一组复制的 Kubernetes Pod 。您可以指定开始部署时要使用多少 Kubernetes Pod ,如.spec.replicas字段所示。.spec.containers字段告诉每个 Kubernetes Pod 在 Riva 设备上运行一个 Riva 服务器 docker 容器。

.spec.selector字段定义了部署如何找到要管理的 Pod 。端口号50001是为 Riva 服务器指定的,以接收 GRPC 从客户端发出的推断请求,而端口8002是为 NVIDIA Triton 指标指定的。deployment.yaml 中的字段可以根据您的部署在values.yaml 中进行修改。

kind: Deployment
metadata:
  […]
spec:
  replicas: {{ .Values.autoscaling.minReplicas }}
  selector:
  […]
  template:
    metadata:
      labels:
        app: {{ template "riva-server.name" . }}

spec:
      volumes:
      […]
      containers:
        - name: riva-speech-api
          image: {{ $server_image }}              
         imagePullPolicy: {{ .Values.riva.pullPolicy }}              
         resources:            
            limits:              
              nvidia.com/gpu: {{ .Values.riva.numGpus }}
command: ["/opt/riva/bin/start-riva"]          
args:            
   {{- range $service, $enabled := .Values.riva.speechServices }}            
    - "--{{$service}}_service={{$enabled}}"            
   {{- end }}     

env:            
    - name: TRTIS_MODEL_STORE                  
      value: "/data/models“

ports:
    - containerPort: 50051
      name: speech-grpc
    - containerPort: 8000
      name: http-triton
    - containerPort: 8001
      name: grpc-triton
     - containerPort: 8002
       name: metrics-triton 

volumeMounts:            
    - mountPath: /data/             
      name: workdir
…… 

为 Riva 服务器创建 Kubernetes 服务

Kubernetes service 是将在 Pods 集合上运行的应用程序公开为网络服务的抽象方法。下面提供的service.yaml将 Riva 服务器公开为网络服务,因此 Riva 服务器已准备好接收来自客户端的推断请求。由于 NVIDIA Triton Inference Server 是 Riva 服务器的固有组件,因此 NVIDIA Triton metrics 可以从端口号8002收集,方法与您使用 NVIDIA Triton 推理服务器相同。service.yaml中的字段可以根据您的部署在values.yaml中进行修改。

创建服务时,您可以通过设置clusterIP: None . (如果您想指定给定的 IP 地址,请参阅 Kubernetes service documentation )自动创建外部负载平衡器。这使 Kubernetes 能够自动分配一个外部可访问的 IP 地址以将流量发送到 Kubernete 集群节点上的正确端口。

apiVersion: v1
kind: Service
metadata:
  name: {{ template "riva-server.fullname" . }}
  labels:
    app: {{ template "riva-server.name" . }}
spec:
  selector:
     app: {{ template "riva-server.name" . }}
   
  clusterIP: None

   ports:
    - port: 50051         
      targetPort: speech-grpc
      name: riva-speech
    - port: 8000      
      targetPort: http      
      name: triton-http    
    - port: 8001      
      targetPort: grpc      
      name: triton-grpc    
    - port: 8002      
      targetPort: metrics      
      name: metrics

自动缩放 Riva 部署

Graphic showing a Kubernetes deployment example with three replicated Pods, each Pod having a Riva server running on a GPU.
图 1 。 Kubernetes 部署的一个示例是三个复制的 Pod ,每个 Pod 都有一个在 GPU 上运行的 Riva 服务器。使用 ServiceMonitor 和 kube Prometheus 向 Prometheus 公开 NVIDIA Triton 指标。

为了自动更改 Kubernetes Pods 上 Riva 服务器的数量,您需要一个 ServiceMonitor 来监视 Prometheus 发现 Riva Service 的目标。您还需要 kube Prometheus 来部署 Prometheu 斯并将 Prometheus 链接到度量端点。在下面的yaml文件中, ServiceMonitor 设置为每 15 秒监视一次 Riva 服务。

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:  
     name: {{ template "riva-server-metrics-monitor.fullname" . }}  
     namespace: {{ .Release.Namespace }}  
     labels:    
        app: {{ template "riva-server-metrics-monitor.name" . }}    
        chart: {{ template "riva-server.chart" . }}   
        release: {{ .Release.Name }}    
spec:  
    selector:    
        matchLabels:      
             app: {{ template "riva-server.name" . }} 
    endpoints:  
    - port: metrics    
      interval: 15s

您可以使用 Prometheus 从端口号8002处的所有 Kubernetes Pod 中抓取 NVIDIA Triton 度量,然后使用 PromQL ( PrometheusQuery 语言)基于 NVIDIA Triton 度量定义一个新的自定义度量,告诉 Prometheu 适配器如何收集此自定义度量,如下所示:

prometheus-adapter:  
   prometheus:    
      url: http://example-metrics-kube-prome-prometheus.default.svc.cluster.local    
      port: 9090    
   rules:
      custom:
          - seriesQuery: 'nv_inference_queue_duration_us{namespace="default",pod!=""}'
            resources:
               overrides:
                   namespace:
                       resource: "namespace"
                   pod:
                       resource: "pod"
            name:
                matches: "nv_inference_queue_duration_us"
                as: "avg_time_queue_ms"
            metricsQuery: 'avg(delta(nv_inference_queue_duration_us{<<.LabelMatchers>>}[30s])/(1+delta(nv_inference_request_success{<<.LabelMatchers>>}[30s]))/1000) by (<<.GroupBy>>)’

您可以使用两个 NVIDIA Triton 度量来定义自定义度量avg_time_queue_ms,这意味着在过去 30 秒内每个推断请求的平均队列时间, HPA 根据它决定是否更改副本编号。

  • nv_inference_request_success[30]是过去 30 秒内成功的推理请求数。
  • nv_inference_queue_duration_us是以微秒为单位的累积推断排队持续时间。

现在您有了一个正在运行的 Prometheus 副本来监控您的应用程序,您需要部署 Prometheu 适配器,它知道如何与 Kubernetes 和 Prometheus 通信,充当两者之间的转换器。如果在chart.yaml中包含prometheus-adapter部分, Prometheus 适配器将自动部署。

普罗米修斯适配器帮助您利用普罗米修斯收集的指标,并使用它们做出缩放决策。这些度量由 API 服务公开, HPA 可以轻松使用。 HPA 可以使用自定义度量,根据推断请求的数量自动缩放(上下) Kubernetes Pod 的副本数量。如果自定义度量的值高于所需的值, HPA 可以增加副本的数量以减少队列时间。

hpa.yamlvalue.yaml中,您需要指定用于部署的副本的最大和最小数量。在value.yaml中,您可以将pods用于metrics.type,以获取自动缩放目标控制的所有 Pod 中给定度量的平均值。可以根据您对响应时间的要求将目标或期望的度量值设置为任意数字。

HPA 使用的相关度量标准的选择取决于您组织中的服务级别要求,例如,您可能还希望牺牲响应时间以支持整体设备利用率,以最小化应用程序部署的成本。在本示例中,选择对推断请求的响应时间 作为 HPA 的主要度量。

hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:  
     name: riva-hpa  
     namespace: {{ .Release.Namespace }} 
     labels:    
        app: {{ template "riva-server.name" . }}    
        chart: {{ template "riva-server.chart" . }}    
        ……
spec:  
    scaleTargetRef:    
        apiVersion: apps/v1    
        kind: Deployment    
        name: {{ template "riva-server.fullname" . }}  
    minReplicas: {{ .Values.autoscaling.minReplicas }} 
    maxReplicas: {{ .Values.autoscaling.maxReplicas }}  
    metrics: {{ toYaml .Values.autoscaling.metrics | nindent 2}}


values.yaml
autoscaling:  
    minReplicas: 1  
    maxReplicas: 3  
    metrics:    
         - type: Pods      
           pods:        
               metric:          
                    name: avg_time_queue_ms        
               target:         
                   type: AverageValue          
                   averageValue: 500m

使用 Traefik 实现 Riva 部署的负载平衡

您还需要一个负载平衡器,以帮助根据负载压力在所有 Riva 服务器之间均匀分布推断请求。一般来说,有两种类型的负载平衡器:第 4 层和第 7 层。Layer4负载平衡器位于网络的Transport层,不知道实际服务(例如 gRPC 中的 HTTP )。Layer7负载平衡器位于Application层,通常将特定service的请求代理到端点。

本示例使用 Traefik 入口控制器和负载平衡器(即第 7 层)来接收推断请求,然后将其分发到所有 Riva 服务器。在 Kubernetes 集群中注册IngressRoute类型,并在 Traefik 中为 GRPC 使用h2c协议,如下面的ingressroute.yaml所示。

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:  
     name: {{ template "riva-server-ingressroute.name" . }}  
     namespace: {{ .Release.Namespace }}  
     labels:    
        app: {{ template "riva-server.name" . }}    
        chart: {{ template "riva-server.chart" . }}   
        release: {{ .Release.Name }}    
        heritage: {{ .Release.Service }}
spec:  
    entryPoints:    
         - riva-grpc  
    routes:    
        - match: PathPrefix(`/`)      
          kind: Rule      
          services:        
              - name: {{ template "riva-server.fullname" . }}          
                port: 50051          
                scheme: h2c

尝试 Riva 部署

最后,您可以将所有内容放在一起,使 Riva 客户端能够向 Riva 服务器发送推理请求。对于 Riva 服务器,可以使用 Helm 图表安装所有内容:

$ helm install <name>  .
$ kubectl get svc
NAME                  TYPE           CLUSTER-IP      EXTERNAL-IP       PORT(S)                              AGE
riva-traefik    LoadBalancer      X.X.X.X             <pending>     50051:32067/TCP,80:30082/TCP   69s

您可以通过运行 NGC Riva Client image 中提供的 Riva 流式 ASR 客户端来测试 Riva client 。要求 Riva 客户端向 Riva 服务器发送流式推理请求,使 HPA 能够自动缩放(增加或减少) Kubernetes Pod 的数量。

$ riva_streaming_asr_client --riva_uri=<IP address of load balancer>:50051 --audio_file /work/wav/en-US_sample.wav --num_parallel_requests=32 --num_iterations=1024

您还可以从 Prometheus 查询 NVIDIA Triton 指标,并通过导航到其指标端点 localhost:9090/metrics 以时间序列显示结果。

Screenshot of graphic user interface for Prometheus showing the custom metric in time series.
图 2 : Prometheus 图形用户界面显示基于用户查询的时间序列中的自定义度量

结论

本文提供了在 Kubernetes 环境中自动缩放 NVIDIA Riva 部署的分步说明和代码。它还向您展示了如何使用 Traefik 平衡工作负载。查看所有步骤和结果.

要了解更多信息,请观看 Autoscaling Riva Deployment with Kubernetes for Conversational AI in Production GTC 2022 课程。有关使用 Kubernetes 和 NGINX Plus (作为另一个负载平衡器选项)大规模部署 AI 推理工作负载的更多信息,请参阅 Deploying NVIDIA Triton at Scale with MIG and Kubernetes

 

Tags