【阅读原文】戳:ACK Gateway with Inference Extension:优化多机分布式大模型推理服务实践
ACK Gateway with Inference Extension组件专为LLM推理场景设计,支持四层/七层流量路由,并提供基于模型服务器负载智能感知的负载均衡能力。此外,通过InferencePool和InferenceModel自定义资源(CRD),可以灵活定义推理服务的流量分发策略,包括模型灰度发布、LLM流量镜像等。
本文主要介绍在多机分布式部署的大模型推理服务场景下,基于ACK Gateway with Inference Extension智能路由优化推理服务性能的实践流程。
多机分布式推理背景
vllm是为大规模语言模型(LLM)设计的高性能推理系统,其目标是在保证高吞吐量和低延迟的同时,高效利用GPU及多机集群资源。由于大语言模型的参数量极大,单卡甚至单机往往难以一次性容纳所有参数,这就要求底层架构在并行计算和内存优化上做大量工作。为此,vllm采用了多种并行化技术,其中主要包括tensor parallel和pipeline parallel,再结合多机分布式部署来实现大模型的高效推理。
Tensor Parallel(张量并行)
张量并行主要针对单个层中的矩阵运算进行切分,将计算任务分散到多块GPU上。其主要思想和技术背景如下:
① 切分模型参数
大型神经网络中的全连接层和其他权重矩阵可能远超单卡内存容量。通过tensor parallel,将这些矩阵按照一定方式(例如按行、列或块)切分成若干子张量,每块GPU只负责一部分的存储和计算,可以有效降低单卡内存压力。
② 分布式矩阵运算
在前向和反向传播过程中,各GPU分别计算局部子矩阵的乘法、激活等操作,最后需要通过通信操作(如All-Reduce或者All-Gather)将各部分结果进行汇总与归约,确保最终输出的一致性。这要求具有高效的跨设备通信和同步策略,避免通信瓶颈对整体推理延迟的影响。
③ 自定义调度与优化
为了进一步提升效率,vllm在tensor parallel设计中通常会结合任务调度、流水线填充(overlap communication and computation)等技术,优化不同GPU之间的负载均衡,降低通信延时,实现高效并行计算。
Pipeline Parallel(流水线并行)
流水线并行则侧重于将整个模型分解成多个连续阶段,每个阶段在不同的GPU(甚至不同的机器上)上运行,从而实现在不同阶段间的并行重叠。其背景和关键点包括:
① 模型切分
对于深层模型,可以按照层次结构将网络划分为多个“阶段”。每个阶段包含若干连续层,部署在不同的设备上。当一个输入样本或微批次在第一阶段执行时,后续样本可以接踵而至进入前面的阶段,实现流水线式处理,充分利用各设备资源。
② 流水线并发
通过微批处理(micro-batching)对输入进行切分,不同的微批次在流水线不同阶段上并行执行,既避免各阶段长时间空闲,也充分重叠了计算与通信开销。需要精细的调度机制来解决流水线中可能出现的气泡(pipeline bubbles)或不平衡问题。
③ 通信与同步
由于各阶段之间存在前后依赖,必须在阶段间传递中间激活值、梯度等信息,这需要低延时通信机制。同时,调度系统也要确保每个设备能够及时获取输入数据,尽量减少因等待数据传输而带来的延迟。
大规模模型由于参数量巨大,单机资源往往无法满足存储与计算需求,因此需要跨多机集群部署。vllm在多机分布式部署时,通常将tensor parallel与pipeline parallel结合使用。
实践步骤
一、环境准备:多机分布式部署QwQ-32B模型推理服务
在本文中,我们将QwQ-32B模型进行多机分布式部署。QwQ-32B是阿里云最新发布的高效能大语言模型,拥有32亿参数,性能可媲美DeepSeek-R1 671B。该模型在数学、代码生成等核心指标上表现出色,能够以更低的资源消耗提供卓越的推理能力。
前提条件
1. 已创建包含GPU的Kubernetes集群,具体操作请参考创建包含GPU的Kubernetes集群[1]。
2. 至少准备总GPU数量不少于4、单节点GPU数量不少于2的GPU节点集群。本文示例使用了包含3个ecs.gn7i-c32g1.32xlarge(4*A10 GPU)GPU节点的集群。
3. 安装LeaderWorkerSet。
a. 登录容器服务管理控制台,在左侧导航栏选择集群列表。
b. 在集群列表页面,单击目标集群名称,然后在左侧导航栏,选择应用 > Helm。
c. 在Helm页面单击创建,然后在创建页面,将应用名设置为lws,在Chart区域的搜索栏搜索并选中lws,完成后单击下一步,在弹出的对话框中单击是,确认采用lws-system为Chart的默认的命名空间。
d. 在参数配置页面,确认Chart版本和参数信息后,单击确定。
步骤一:准备模型数据
1. 下载模型
GIT_LFS_SKIP_SMUDGE=1 git clone https://www.modelscope.cn/Qwen/QwQ-32B.git cd QwQ-32B git lfs pull
2. 上传模型至OSS
ossutil mkdir oss://<Your-Bucket-Name>/QwQ-32B ossutil cp -r ./QwQ-32B oss://<Your-Bucket-Name>/QwQ-32B
3. 为目标集群配置存储卷PV和PVC
具体操作请参考使用OSS静态存储卷[2]。以下为示例PV的配置信息:
配置项 | 说明 |
存储卷类型 | OSS |
名称 | llm-model |
访问证书 | 配置用于访问OSS的AccessKey ID和AccessKey Secret。 |
Bucket ID | 选择已创建的OSS Bucket。 |
OSS Path | 选择模型所在的路径,如/models/QwQ-32B |
以下为示例PVC的配置信息:
配置项 | 说明 |
存储声明类型 | OSS |
名称 | llm-model |
分配模式 | 选择已有存储卷。 |
已有存储卷 | 单击选择已有存储卷链接,选择已创建的存储卷PV。 |
单击选择已有存储卷链接,选择已创建的存储卷PV。
步骤二:部署推理服务
以下命令基于vLLM框架,多机分布式部署QwQ-32B模型的推理服务:
kubectl apply -f- <<EOF apiVersion: v1 data: hostfile-0: |- qwq-dist-v1.qwq-dist-v1-0.default qwq-dist-v1.qwq-dist-v1-0-0.default master.rayInit: |- #!/bin/bash ray_port=6379 ray_init_timeout=300 ray_cluster_size=$WORLD_SIZE master_command=$1 ray start --head --port=$ray_port for (( i=0; i < $ray_init_timeout; i+=5 )); do active_nodes=`python3 -c 'import ray; ray.init(); print(sum(node["Alive"] for node in ray.nodes()))'` if [ $active_nodes -eq $ray_cluster_size ]; then echo "All ray workers are active and the ray cluster is initialized successfully." $master_command exit 0 fi echo "Wait for all ray workers to be active. $active_nodes/$ray_cluster_size is active" sleep 5s; done echo "Waiting for all ray workers to be active timed out." exit 1 worker.rayInit: |- #!/bin/bash ray_port=6379 ray_init_timeout=300 ray_address=$MASTER_ADDR worker_command=$1 for (( i=0; i < $ray_init_timeout; i+=5 )); do ray start --address=$ray_address:$ray_port if [ $? -eq 0 ]; then echo "Worker: Ray runtime started with head address $ray_address:$ray_port" $worker_command exit 0 fi echo "Waiting until the ray worker is active..." sleep 5s; done echo "Ray worker starts timeout, head address: $ray_address:$ray_port" exit 1 kind: ConfigMap metadata: labels: app: distributed-serving chart: distributed-serving-0.1.0 createdBy: DistributedServing heritage: Helm release: qwq-dist-v1 name: qwq-dist-v1-cm --- apiVersion: leaderworkerset.x-k8s.io/v1 kind: LeaderWorkerSet metadata: labels: app: distributed-serving release: qwq-dist-v1 serviceName: qwq-dist servingName: qwq-dist servingType: distributed-serving servingVersion: v1 name: qwq-dist-v1-distributed-serving spec: leaderWorkerTemplate: leaderTemplate: metadata: annotations: prometheus.io/path: /metrics prometheus.io/port: "8000" prometheus.io/scrape: "true" labels: app: distributed-serving release: qwq-dist-v1 role: leader serviceName: qwq-dist servingName: qwq-dist servingType: distributed-serving servingVersion: v1 spec: containers: - command: - sh - -c - /vllm-workspace/ray_init.sh leader --ray_cluster_size=$(LWS_GROUP_SIZE); vllm serve /mnt/models/QwQ-32B --port 8000 --trust-remote-code --served-model-name qwq --max-model-len 16384 --gpu-memory-utilization 0.95 --tensor-parallel-size 2 --pipeline-parallel-size 2 --enforce-eager env: - name: MASTER_ADDR value: $(LWS_LEADER_ADDRESS) - name: WORLD_SIZE valueFrom: fieldRef: fieldPath: metadata.annotations['leaderworkerset.sigs.k8s.io/size'] - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_INDEX valueFrom: fieldRef: fieldPath: metadata.labels['leaderworkerset.sigs.k8s.io/worker-index'] - name: GROUP_INDEX valueFrom: fieldRef: fieldPath: metadata.labels['leaderworkerset.sigs.k8s.io/group-index'] - name: GPU_COUNT value: "2" - name: HOSTFILE value: /etc/hostfile - name: ROLE value: master image: kube-ai-registry.cn-shanghai.cr.aliyuncs.com/kube-ai/vllm:v0.7.2 imagePullPolicy: IfNotPresent name: distributed-serving-leader ports: - containerPort: 8000 name: restful protocol: TCP readinessProbe: initialDelaySeconds: 30 periodSeconds: 30 tcpSocket: port: 8000 resources: limits: nvidia.com/gpu: "2" volumeMounts: - mountPath: /dev/shm name: dshm - mountPath: /mnt/models name: llm-models - mountPath: /etc/hostfile name: qwq-dist-v1-cm subPathExpr: hostfile-$(GROUP_INDEX) volumes: - configMap: items: - key: hostfile-0 mode: 438 path: hostfile-0 name: qwq-dist-v1-cm name: qwq-dist-v1-cm - emptyDir: medium: Memory sizeLimit: 30Gi name: dshm - name: llm-model persistentVolumeClaim: claimName: llm-model restartPolicy: RecreateGroupOnPodRestart size: 2 workerTemplate: metadata: labels: app: distributed-serving release: qwq-dist-v1 role: worker serviceName: qwq-dist servingName: qwq-dist servingType: distributed-serving servingVersion: v1 spec: containers: - command: - sh - -c - /vllm-workspace/ray_init.sh worker --ray_address=$(LWS_LEADER_ADDRESS) env: - name: MASTER_ADDR value: $(LWS_LEADER_ADDRESS) - name: WORLD_SIZE valueFrom: fieldRef: fieldPath: metadata.annotations['leaderworkerset.sigs.k8s.io/size'] - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_INDEX valueFrom: fieldRef: fieldPath: metadata.labels['leaderworkerset.sigs.k8s.io/worker-index'] - name: GROUP_INDEX valueFrom: fieldRef: fieldPath: metadata.labels['leaderworkerset.sigs.k8s.io/group-index'] - name: GPU_COUNT value: "2" - name: HOSTFILE value: /etc/hostfile - name: ROLE value: worker image: kube-ai-registry.cn-shanghai.cr.aliyuncs.com/kube-ai/vllm:v0.7.2 imagePullPolicy: IfNotPresent name: distributed-serving-worker resources: limits: nvidia.com/gpu: "2" volumeMounts: - mountPath: /dev/shm name: dshm - mountPath: /mnt/models name: llm-models - mountPath: /etc/hostfile name: qwq-dist-v1-cm subPathExpr: hostfile-$(GROUP_INDEX) volumes: - configMap: items: - key: hostfile-0 mode: 438 path: hostfile-0 name: qwq-dist-v1-cm name: qwq-dist-v1-cm - emptyDir: medium: Memory sizeLimit: 30Gi name: dshm - name: llm-model persistentVolumeClaim: claimName: llm-model networkConfig: subdomainPolicy: Shared replicas: 3 startupPolicy: LeaderCreated --- apiVersion: v1 kind: Service metadata: labels: app: distributed-serving release: qwq-dist-v1 servingName: qwq-dist servingType: distributed-serving servingVersion: v1 spec: ports: - name: http-serving port: 8000 protocol: TCP targetPort: 8000 selector: app: distributed-serving release: qwq-dist-v1 role: leader type: ClusterIP EOF
示例YAML通过LeaderWorkderSet对QwQ-32B进行多机分布式部署,其中每个group包含一个leader pod和一个worker pod,二者各自包含2 GPU,组成ray集群。
vLLM启动时,通过设置pipeline-parallel-size为2,在两个pod间启用管道并行;通过设置tensor-parallel-size为2,在单个pod内使用两张GPU启用张量并行。
二、使用ACK Gatewaywith Inference Extension
智能路由优化多机分布式部署模型服务
ACK Gateway with AI Extension概述
ACK Gateway with AI Extension组件专为LLM推理场景设计,支持四层/七层流量路由,并提供基于模型服务器负载智能感知的负载均衡能力。此外,通过InferencePool和InferenceModel自定义资源(CRD),可以灵活定义推理服务的流量分发策略,包括模型灰度发布。
ACK Gateway with Inference Extension提供基于模型服务器负载智能感知的负载均衡能力,在tensor parallel 与 pipeline parallel结合的多机分布式部署环境下,可以通过leader模型服务器指标智能感知多机分布式系统负载,并依此进行细粒度路由,确保不同的分布式工作负载组负载均衡。
步骤一:开启ACK Gateway with Inference Extension组件
1. 在ACK集群的组件管理中开启ACK Gateway with Inference Extension组件,并启用“Gateway API推理扩展”选项。
2. 创建网关实例:使用kubectl连接到ACK集群,并使用以下指令来创建一个具有8080和8081端口的网关。其中8080端口将部署一个标准的HTTP路由、路由到后端推理服务,而8081端口则将基于推理服务扩展向后端推理服务进行路由。
kubectl apply -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: GatewayClass metadata: name: inference-gateway spec: controllerName: gateway.envoyproxy.io/gatewayclass-controller --- apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: inference-gateway spec: gatewayClassName: inference-gateway listeners: - name: http protocol: HTTP port: 8080 - name: llm-gw protocol: HTTP port: 8081 --- apiVersion: gateway.envoyproxy.io/v1alpha1 kind: ClientTrafficPolicy metadata: name: client-buffer-limit spec: connection: bufferLimit: 20Mi targetRefs: - group: gateway.networking.k8s.io kind: Gateway name: inference-gateway --- apiVersion: gateway.envoyproxy.io/v1alpha1 kind: BackendTrafficPolicy metadata: name: backend-timeout spec: timeout: http: requestTimeout: 24h targetRef: group: gateway.networking.k8s.io kind: Gateway name: inference-gateway --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: reasoning-backend spec: parentRefs: - name: inference-gateway sectionName: http rules: - backendRefs: - group: "" kind: Service name: qwq-dist-v1 port: 8000 weight: 1 matches: - path: type: PathPrefix value: / EOF
步骤二:在网关8081端口上启用推理扩展
通过InferencePool和InferenceModel资源来启用AI推理扩展能力。在推理扩展提供的CRD中:InferencePool资源通过标签选择器声明一组在集群中运行的LLM推理服务工作负载,而InferenceModel指定了InferencePool中具体模型的流量分发策略。
1. 创建InferencePool
InferencePool通过标签选择器声明一组推理服务工作负载:
kubectl apply -f- <<EOF apiVersion: inference.networking.x-k8s.io/v1alpha1 kind: InferencePool metadata: annotations: inference.networking.x-k8s.io/attach-to: | name: inference-gateway port: 8081 name: reasoning-pool spec: extensionRef: failureMode: FailClose group: "" kind: Service name: inference-gateway-ext-proc selector: app: distributed-serving release: qwq-dist-v1 role: leader targetPortNumber: 8000 EOF
2. 创建InferenceModel
InferenceModel定义了模型的流量分发策略,支持灰度发布。以下示例展示了一个基本用例:将模型名为qwq的请求全部转发给qwq-32b模型。
kubectl apply -f- <<EOF apiVersion: inference.networking.x-k8s.io/v1alpha1 kind: InferenceModel metadata: name: reasoning-model spec: criticality: Critical modelName: qwq poolRef: group: inference.networking.x-k8s.io kind: InferencePool name: reasoning-pool targetModels: - name: qwq weight: 100 EOF
语义说明:
• targetModels字段定义了目标模型及其权重比例。例如,上述配置表示100%的请求将路由到QwQ-32B模型。
• criticality字段用于标记模型的重要性,影响流量调度优先级。
执行以下命令验证智能路由是否生效:
GATEWAY_IP=$(kubectl get gateway/inference-gateway -o jsonpath='{.status.addresses[0].value}') curl -X POST ${GATEWAY_IP}/v1/chat/completions \ -H 'Content-Type: application/json' \ -d '{ "model": "qwq", "messages": [ { "role": "user", "content": "你是谁?" } ] }' -v
步骤三:压测推理服务、观测推理服务性能和负载均衡效果
1. 通过Prometheus实例默认的服务发现机制采集推理服务相关指标。具体操作,请参见默认服务发现[3]。
2. 通过Grafana大盘来观测基于vLLM部署的LLM推理服务:将vllm的Grafana JSON model导入到Grafana,创建LLM推理服务的可观测大盘。导入的JSON model请参见vllm官方网站[4]。具体导入操作,请参见如何导出和导入Grafana仪表盘[5]。
3. 执行以下命令创建压测工具
kubectl apply -f- <<EOF apiVersion: apps/v1 kind: Deployment metadata: name: vllm-benchmark labels: app: vllm-benchmark spec: replicas: 1 selector: matchLabels: app: vllm-benchmark template: metadata: labels: app: vllm-benchmark spec: volumes: - name: llm-model persistentVolumeClaim: claimName: llm-model containers: - name: vllm-benchmark image: kube-ai-registry.cn-shanghai.cr.aliyuncs.com/kube-ai/vllm-benchmark:v1 command: - "sh" - "-c" - "sleep inf" volumeMounts: - mountPath: /models/QwQ-32B name: llm-model EOF
4. 下载压测数据集
# 执行以下命令进入Benchmark Pod PODNAME=$(kubectl get po -o custom-columns=":metadata.name"|grep "vllm-benchmark") kubectl exec -it $PODNAME -- bash # 下载压测数据集 pip3 install modelscope modelscope download --dataset gliang1001/ShareGPT_V3_unfiltered_cleaned_split ShareGPT_V3_unfiltered_cleaned_split.json --local_dir /root/
5. 使用以下指令分别向网关的8080端口(采用网关默认服务路由)和8081端口(采用推理扩展的服务路由)发起两次压测。
• 压测网关默认服务路
python3 /root/vllm/benchmarks/benchmark_serving.py \ --backend vllm \ --model /models/QwQ-32B \ --served-model-name qwq \ --trust-remote-code \ --dataset-name random \ --dataset-path /root/ShareGPT_V3_unfiltered_cleaned_split.json \ --random-prefix-len 1000 \ --random-input-len 4000 \ --random-output-len 3000 \ --random-range-ratio 0.2 \ --num-prompts 300 \ --max-concurrency 30 \ --host 192.168.14.126 \ # ACK网关内网IP --port 8080 \ #请求8080端口,即网关默认路由 --endpoint /v1/completions \ --save-result \ 2>&1 | tee benchmark_serving.txt ============ Serving Benchmark Result ============ Successful requests: 300 Benchmark duration (s): 2362.70 Total input tokens: 1014085 Total generated tokens: 537817 Request throughput (req/s): 0.13 Output token throughput (tok/s): 227.63 Total Token throughput (tok/s): 656.83 ---------------Time to First Token---------------- Mean TTFT (ms): 10909.18 Median TTFT (ms): 2446.06 P99 TTFT (ms): 119308.83 -----Time per Output Token (excl. 1st token)------ Mean TPOT (ms): 114.18 Median TPOT (ms): 113.00 P99 TPOT (ms): 158.03 ---------------Inter-token Latency---------------- Mean ITL (ms): 113.69 Median ITL (ms): 103.10 P99 ITL (ms): 109.65 ==================================================
• 压测推理扩展的服务路由
python3 /root/vllm/benchmarks/benchmark_serving.py \ --backend vllm \ --model /models/QwQ-32B \ --served-model-name qwq \ --trust-remote-code \ --dataset-name random \ --dataset-path /root/ShareGPT_V3_unfiltered_cleaned_split.json \ --random-prefix-len 1000 \ --random-input-len 4000 \ --random-output-len 3000 \ --random-range-ratio 0.2 \ --num-prompts 300 \ --max-concurrency 30 \ --host 192.168.14.126 \ --port 8081 \ # 请求8081端口,使用网关推理扩展进行路由 --endpoint /v1/completions \ --save-result \ 2>&1 | tee gie_benchmark_serving.txt ============ Serving Benchmark Result ============ Successful requests: 300 Benchmark duration (s): 2325.10 Total input tokens: 1014085 Total generated tokens: 538158 Request throughput (req/s): 0.13 Output token throughput (tok/s): 231.46 Total Token throughput (tok/s): 667.60 ---------------Time to First Token---------------- Mean TTFT (ms): 7335.87 Median TTFT (ms): 2413.13 P99 TTFT (ms): 106624.40 -----Time per Output Token (excl. 1st token)------ Mean TPOT (ms): 117.26 Median TPOT (ms): 114.39 P99 TPOT (ms): 198.15 ---------------Inter-token Latency---------------- Mean ITL (ms): 115.99 Median ITL (ms): 103.84 P99 ITL (ms): 109.88 ==================================================
性能对比与总结
分别对普通网关(8080端口)和ACK Gateway with AI Extension(8081端口)进行了压测,关键指标结果如下:
指标 | 指标 | ACK Gateway with AI Extension智能路由 |
平均TTF T (ms) |
10909.18 | 7335.87 |
P99 TTFT (ms) | 119308.83 | 106624.40 |
输出吞吐量 (tok/s) | 656.83 | 667.60 |
同时可以观测到两次压测对应时间段内,vLLM服务器可观测大盘的可视化对比。
通过压测数据和大盘可以直观地发现基于ACK Gateway的推理扩展对推理服务进行路由和负载均衡时,QwQ-32B推理服务拥有更好的延迟、吞吐量和缓存利用率表现,生产性能更佳。平均TTFT缩短32.7%,KV Cache利用率在不同工作负载之间更加均匀。
注:测试结果与多种因素有关,上述结果仅供参考。
相关链接:
[1] 创建包含GPU的Kubernetes集群
[2] 使用OSS静态存储卷
[3] 默认服务发现
https://help.aliyun.com/zh/arms/prometheus-monitoring/default-pod-service-discovery
[4] vllm官方网站
https://docs.vllm.ai/en/latest/getting_started/examples/prometheus_grafana.html
[5] 如何导出和导入Grafana仪表
https://help.aliyun.com/zh/grafana/support/how-to-export-and-import-the-grafana-dashboard
我们是阿里巴巴云计算和大数据技术幕后的核心技术输出者。
获取关于我们的更多信息~