语音 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 Driver 和 NVIDIA 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 的四个主要步骤是:
- 为 Riva 服务器创建 Kubernetes deployment
- 创建 Kubernetes 服务以将 Riva 服务器公开为网络服务
- 自动缩放 Riva 部署:
- 使用 kube Prometheus 和 ServiceMonitor 向 Prometheus 公开 NVIDIA Triton 指标
- 为自定义度量部署 Prometheus 适配器
- 创建水平吊舱自动缩放器( HPA )
- 使用 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 deployment 为 Pods 和 ReplicaSets 提供声明性更新。下面的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 部署
为了自动更改 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.yaml
和value.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 以时间序列显示结果。
结论
本文提供了在 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 。