【阅读原文】戳:基于Knative的LLM推理场景弹性伸缩方案
1. 背景
在人工智能商业化的时代,模型推理比训练会被更加广泛地使用,这是因为技术商业化的本质在于其广泛应用的价值。可以预见,模型推理将成为未来的主要战场。这一点已经从现实中得到了印证——微软已经在Azure的60多个数据中心部署了数十万台GPU,用于支持ChatGPT的推理服务。
然而,大模型推理面临着巨大的挑战,主要集中在规模化部署的情况下,平衡成本和性能。其中,成本是最为关键的因素。随着大模型规模的不断扩大,其所需的计算资源也在不断增加。而由于GPU资源的稀缺性和高昂的价格,每次模型推理的成本也水涨船高。而最终用户只愿意为价值买单,而不是为推理的高成本买单。因此,降低每次推理的成本成为基础设施团队的首要任务。
在此基础上,性能则是核心竞争力,尤其是在面向消费者(ToC)的领域。更快速的推理速度和更优的推理效果是增强用户粘性和体验的关键。因此,在优化推理服务时,不仅要降低成本,还要持续提升性能和效率,以应对市场和用户不断变化的需求。
由于GPU的稀缺性,多数人在大语言模型推理时往往需要为外部服务峰值预留固定数量的GPU实例。这样的做法能够保证服务的可靠性,但也带来了明显的资源浪费问题。在线推理场景下,流量的波峰波谷特征非常明显,例如聊天机器人在中午和夜间的流量较低,这导致了大量GPU资源的闲置。而GPU与传统CPU最大的区别在于其弹性伸缩的确定性不足,资源不会轻易释放,进一步加剧了浪费。
然而,我们从大模型推理服务商的分享中得到了启发—如果能在低流量时解放GPU资源,进行一些模型训练和离线推理,就能有效提高资源的利用率。这种方法被称为“反向弹性伸缩”。因此,今天我们讨论的不是计算节点的传统弹性伸缩,而是在固定节点的情况下,通过工作负载的弹性伸缩和资源抢占来最大化资源利用率。但由于LLM本身的特性,实现自动弹性伸缩同时满足低延迟的要求并非易事,需要首先确保以下三个前提:
1. 科学合理的自动化弹性扩容机制
2. 扩容时刻的计算资源保障
3. 扩容过程对于用户体验SLA的保障
科学合理的自动化弹性扩容机制
在Kubernetes中,提供了HPA(Horizontal Pod Autoscaler)和CronHPA两种机制来按需扩展实例数量,从而减少资源浪费。但是,传统HPA存在“弹性滞后”的问题,且对常见的流量突发的场景能力支持不足。而CronHPA虽然能够按时间定时扩缩容,但它要求业务有非常明确的周期性,并且需要手动调整策略,增加了运维成本。
且针对LLM推理的合适弹性伸缩指标相对较少。传统的水平自动伸缩(HPA)默认支持的指标是CPU和内存,难以适用于GPU场景。即使现有的GPU exporter已经能够提供部分GPU指标信息,但这些指标往往不足以准确反映GPU的实际负载情况。
扩容时刻的资源保障
因为本文讨论的是在固定节点的情况下,通过工作负载的弹性伸缩和资源抢占来最大化资源利用率。因此,扩容时刻的资源保障主要是通过抢占其他低优先级的任务(比如一些训练任务、离线推理任务等)来实现的。
且在弹性伸缩的过程中,考虑到不同卡的推理性能不同,用户可能有自定义优先级配置来充分利用不同的异构资源。这种对不同异构资源按优先级调度和抢占的能力,是目前kubernetes原生调度器无法支持的。
扩容过程对于用户体验SLA的保障
与传统在线服务相比,大模型推理的最大区别在于从启动到就绪所需的时间更长。这主要是因为LLM的推理镜像和模型都占据来非常大的存储空间。比如当前流行的推理框架vLLM,其版本v0.6.3的镜像大小约为5GB。LLM的模型规模通常也很大,在实际生产环境中,主流模型的参数规模通常在7B到14B之间。以常见的Qwen2.5-7B-Instruct为例,其模型大小约为15GB。这使得模型从网络存储加载到GPU的过程中耗时尤为严重。
为了应对这些问题,在ACK场景下,我们提出了一些推理场景下的弹性伸缩方案。
2. 方案介绍
2.1 整体架构
针对以上问题,我们将ACK多个企业级能力进行协同,基于Knative+ResourcePolicy+Fluid提出了以下部署方案。采用Knative支持的基于并发的弹性伸缩策略(KPA),能够灵敏感知负载变化,并灵活应对突发流量。在弹性过程中,通过ResoucePolicy自定义弹性资源优先级调度,同时在剩余空闲资源不足时,通过优先级抢占来保证高优先级的在线推理业务的资源。关于扩容过程对于用户体验SLA的保障,我们通过Fluid提高弹性效率,提前预下载模型保存在网络存储中,并基于Fluid来进行数据访问加速。在增强系统弹性效率的基础上,在满足推理场景自动弹性需求的同时,保证了首token的响应时间。
2.2 基于推理请求的弹性伸缩-Knative
一个科学合理的自动化弹性策略主要包括两个部分,一是合理的弹性指标,二是健壮的弹性伸缩机制。
合理的弹性指标是制定弹性策略的基础。传统思维惯用GPU利用率(GPU Utilization)作为弹性伸缩指标。然因GPU的架构及计算方式与CPU不同,GPU利用率常常无法真实反映GPU的计算负载,仅能用作判断机器空闲状态的依据。那么,LLM推理中合理的弹性指标为何?这需要通过丰富的测试加以确定,目前业界尚无统一答案。有些方法利用推理框架本身提供的指标,如vLLM/NVIDIA NIM的num_requests_waiting、TGI的batch_size、queue_size等。何为最优指标,尚待讨论,但根据我们的测试效果,Knative serving的并发量(concurrency)的表现还不错。且Knavtive通过sidecar的形式自动透出concurrency和rps指标,不需要业务容器透出,适用场景更为广泛。
Knative是一款基于Kubernetes的开源Serverless容器框架,支持基于请求的自动弹性、在没有流量时将实例数量自动缩容至0、版本管理与灰度发布等能力。其KPA支持通过并发数(concurrency)和每秒请求数(rps)作为弹性伸缩指标。其中,concurrency适用于单次请求资源消耗大且处理时间长的业务,而rps则适合较短处理时间的业务。从LLM推理来看,其耗时通常在数百毫秒至数秒之间,适合将concurrency作为弹性指标。
除去指标与LLM推理的契合度,Knative KPA相较于HPA在应对突发流量方面具备更灵活的机制:
• 快速扩容:在应对突发流量时,设计了Stable和Panic模式,以区分并处理正常与突发流量场景。在短暂的Panic窗口期内,如果并发请求量超出当前处理能力的阈值(默认200%),系统会立即扩容,不受扩缩容时间窗口限制。而HPA为避免过于频繁的扩缩容,默认在5分钟内没有扩缩容动作才会触发。
• 缓存流量:Knative的Activator和Queue-proxy具备缓存流量的能力,其优势在于:
- 防止存量实例因处理过多请求而性能下降。
- 延迟请求调度,在短暂的高流量时期不会将所有流量分配给现有实例,待新实例创建后可分摊流量。
2.3 自定义弹性资源优先级调度-ResourcePolicy
自定义弹性资源优先级调度是ACK Pro调度器提供的一种高级调度策略。在包含异构GPU资源的集群中,用户可以通过自定义资源策略(ResourcePolicy)来设定LLM应用在扩容过程中Pod调度到不同类型GPU节点资源的顺序和抢占策略。同时,在缩容过程中,Pod将按照原调度顺序的逆序进行回收。此功能有助于精细化地管理用户对不同异构资源的调度。ResourcePolicy支持设置抢占策略,当每个调度单元无法成功调度时,调度器会尝试进行资源抢占。因此,当推理流量处于低谷时,系统可以将资源分配给更多的训练或离线推理任务。当在线推理流量增加时,可以通过ResourcePolicy将任务调度到指定类型的资源池,若资源不足,还可通过抢占机制来确保资源的有效使用。
2.4 模型数据加速-Fluid
在云环境中,我们通常使用网络存储(如NAS、OSS)来存储预下载的模型。然而,在ACK集群中访问这些远程网络存储时,高延迟与带宽受限的问题时常出现。特别是在AI模型推理服务启动时,发布或更新AI模型推理服务会导致大量服务实例同时启动,这些实例需要并发读取存储系统的相同模型文件,并将其加载到GPU显存中,直接从网络存储拉取的速度因此变慢。
Fluid的设计思路是将存储系统的功能进行分层,并将其划分为数据存储和数据访问两大能力。同时,将部分数据访问能力向上迁移至计算集群中。在大数据(AI)应用场景中,Fluid对“计算任务访问数据的过程”进行了抽象,提出了弹性数据集(Dataset)的概念,并将数据分布式缓存技术与云原生的自动弹性(Autoscaling)、可迁移性(Portability)和可调度性(Scheduling)能力相结合。通过数据分流(Data Offloading)来降低中央存储的压力;通过分层本地性缓存(Tiered Locality Cache)和亲和性调度(Cache Locality Scheduling)来提升数据访问性能;在计算资源高并发访问数据时,通过自动扩容缓存集群来提供弹性IO吞吐能力,从而提升数据使用的访问效率。
为了充分利用Fluid的数据加速效果,我们需要根据业务的性能需求和预算合理配置Fluid:包括选择合适的ECS机型、缓存介质和缓存系统参数。虽然听起来复杂,但Fluid提供了详细的最佳实践配置文档,参照其进行操作即可。
3. 快速实践
3.1 环境准备
• 推理框架: vllm:0.4.1
• LLM: Qwen-7B-Chat-Int8
• 已创建ACK集群并包含多个GPU节点,示例主要使用
- 3台 ecs.gn7i-c32g1.8xlarge(A10): 模拟主流推理机器
- 1台 ecs.gn6i-c4g1.xlarge(T4):流量低谷时,资源降配的备用机器
- 3台 ecs.g8i.24xlarge:用于存放fluid数据缓存
• 安装ack-knative:部署Knative_容器服务Kubernetes版ACK(ACK)-阿里云帮助中心
• 安装ack-fluid: 已安装云原生AI套件并部署ack-fluid组件。具体操作,请参见安装云原生AI套件。
3.2 准备模型数据
参照文档准备模型数据,本文以Qwen-7B-Chat-Int8模型为例,将模型上传到NAS/OSS中。
并在集群中配置名为qwen-7b-chat-int8的存储卷PV和存储声明PVC。
3.3 基于Fluid加速模型加载
Fluid支持在Kubernetes环境下对PV存储卷中的数据进行缓存,以提升后续数据访问过程的效率,可以提升模型加载的速度。这里推荐使用Jindo runtime的方式。
1. 配置dataset调度到准备好的缓存ECS机型上(ecs.g8i.24xlarge)
apiVersion: data.fluid.io/v1alpha1 kind: Dataset metadata: name: qwen-7b-chat-int8-dataset spec: mounts: - mountPoint: pvc://qwen-7b-chat-int8 #前面准备的pvc name name: data path: / accessModes: - ReadOnlyMany # 配置缓存系统Worker Pod调度到指定ECS机型的node上 nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: node.kubernetes.io/instance-type operator: In values: - "ecs.g8i.24xlarge" --- apiVersion: data.fluid.io/v1alpha1 kind: JindoRuntime metadata: name: qwen-7b-chat-int8-dataset spec: replicas: 2 tieredstore: levels: # 配置存储介质为内存 - mediumtype: MEM volumeType: emptyDir path: /dev/shm quota: 20Gi high: "0.9" low: "0.8"
预期输出:
$ kubectl get datasets.data.fluid.io NAME UFS TOTAL SIZE CACHED CACHE CAPACITY CACHED PERCENTAGE PHASE AGE qwen-7b-chat-int8-dataset 17.01GiB 0.00B 40.00GiB 0.0% Bound 5m46s $ kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE qwen-7b-chat-int8 Bound qwen-7b-int8 20Gi RWX nas <unset> 7d1h qwen-7b-chat-int8-dataset Bound default-qwen-7b-chat-int8-dataset 100Pi ROX fluid <unset> 4m11s
1. 创建DataLoad执行缓存预热。
apiVersion: data.fluid.io/v1alpha1 kind: DataLoad metadata: name: dataset-warmup spec: dataset: name: qwen-7b-chat-int8-dataset namespace: default loadMetadata: true target: - path: / replicas: 1
预期输出:
$ kubectl get dataloads NAME DATASET PHASE AGE DURATION dataset-warmup qwen-7b-chat-int8-dataset Complete 12m 47s $ kubectl get dataset NAME UFS TOTAL SIZE CACHED CACHE CAPACITY CACHED PERCENTAGE PHASE AGE qwen-7b-chat-int8-dataset 17.01GiB 17.01GiB 40.00GiB 100.0% Bound 31m
Note:在使用fluid加速model存储卷数据访问时,必须在对应的node上创建出对应的Jindo FUSE Pod。而业务Pod在使用dataset的情况下,必须等待Jindo FUSE Pod就绪。为了避免此过程带来的业务Pod启动耗时,可以提前拉起。
3.4 优先级调度和抢占配置
在弹性伸缩的过程中,考虑到不同卡的推理性能不同,用户可能有自定义优先级配置来保证不同的AI业务调度到不同的资源池。并且对部分实时性要求的高的业务,配置抢占能力。
• 优先级调度:配置ResourcePolicy优先调度到A10节点;A10节点不足时调度到T4节点
• 抢占:
- 配置preemptPolicy为BeforeNextUnit,调度器将在每个Unit调度失败时尝试抢占
- 推理Pod的PriorityClass > 训练Pod的PriorityClass,确保抢占成功
apiVersion: scheduling.alibabacloud.com/v1alpha1 kind: ResourcePolicy metadata: name: qwen namespace: default spec: selector: release: qwen # 此处要与后续创建的Pod的label相关联。 strategy: prefer # 尝试抢占训练pod preemptPolicy: BeforeNextUnit units: - resource: ecs nodeSelector: aliyun.accelerator/nvidia_name: NVIDIA-A10 - resource: ecs nodeSelector: aliyun.accelerator/nvidia_name: Tesla-T4 # - resource: eci --- apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: infrence-with-high-priority value: 1000000 globalDefault: false description: "此优先级类应仅用于推理服务Pod。"
3.5 配置KPA弹性伸缩
Knative中提供了开箱即用、基于流量请求的自动扩缩容KPA(Knative Pod Autoscaler)功能。Knative Serving会为每个Pod注入一个名为queue-proxy的QUEUE代理容器,该容器负责向Autoscaler报告业务容器的关于请求的指标。Autoscaler接收到这些指标之后,会根据并发请求数及相应的算法,调整Deployment的Pod数量,从而实现自动扩缩容。kpa默认支持的指标包括:"concurrency"、"rps"、"cpu"、"memory"。在LLM、文生图场景下,单次推理的耗时较长,推荐使用并发数(concurrency)来作为弹性指标。
apiVersion: serving.knative.dev/v1 kind: Service metadata: labels: release: qwen name: qwen namespace: default spec: template: metadata: annotations: autoscaling.knative.dev/class: kpa.autoscaling.knative.dev autoscaling.knative.dev/metric: "concurrency" # 配置并发数为弹性伸缩指标 autoscaling.knative.dev/target: "2" # 设置当前最大并发请求数为2 autoscaling.knative.dev/min-scale: "1" # 设置最小副本数 autoscaling.knative.dev/max-scale: "3" # 设置最大副本数 labels: release: qwen spec: containers: - command: - sh - -c - python3 -m vllm.entrypoints.openai.api_server --port 8080 --trust-remote-code --served-model-name qwen --model /mnt/models/Qwen-7B-Chat-Int8 --gpu-memory-utilization 0.95 --quantization gptq --max-model-len=6144 image: kube-ai-registry.cn-shanghai.cr.aliyuncs.com/kube-ai/vllm:0.4.1 imagePullPolicy: IfNotPresent name: vllm-container priorityClassName: infrence-with-high-priority # 设置优先级,用于调度抢占 readinessProbe: tcpSocket: port: 8080 initialDelaySeconds: 5 periodSeconds: 5 resources: limits: cpu: "32" memory: 64Gi nvidia.com/gpu: "1" requests: cpu: "16" memory: 64Gi nvidia.com/gpu: "1" volumeMounts: - mountPath: /mnt/models/Qwen-7B-Chat-Int8 name: qwen-7b-chat-int8 volumes: - name: qwen-7b-chat-int8 persistentVolumeClaim: claimName: qwen-7b-chat-int8-dataset # 使用dataset创建的pvc
参数说明:
预期输出:
$ kubectl get ksvc NAME URL LATESTCREATED LATESTREADY READY REASON qwen http://qwen.default.example.com qwen-00001 qwen-00001 True
3.6 配置资源降配
大部分推理业务具有一定的时间周期,在流量低谷时如果持续保有高规格的GPU实例可能会导致GPU资源的大量浪费。但如果缩容至零以降低成本,导致应用重新启动时会经历较长的冷启动时间。ACK Knative提供的保留实例并进行资源降配的功能,保留一个低规格的GPU实例,可以平衡好使用成本和启动时长的问题。
比如,我们日常使用ecs.gn7i-c32g1.8xlarge(¥21.253元/时)的实例处理流量高峰,在流量低谷期使用ecs.gn6i-c4g1.xlarge(¥8.896/时), 在流量低谷期间可以节省60%左右。空下来的高规GPU实例可以用于模型训练等任务中。
apiVersion: serving.knative.dev/v1 kind: Service metadata: labels: release: qwen name: qwen namespace: default spec: template: metadata: annotations: autoscaling.knative.dev/class: kpa.autoscaling.knative.dev autoscaling.knative.dev/metric: "concurrency" # 配置并发数为弹性伸缩指标 autoscaling.knative.dev/target: "2" # 设置当前最大并发请求数为2 autoscaling.knative.dev/min-scale: "0" autoscaling.knative.dev/max-scale: "3" knative.aliyun.com/reserve-instance: enable # 开启保留实例功能 knative.aliyun.com/reserve-instance-type: ecs # 配置保留实例类型 knative.aliyun.com/reserve-instance-ecs-use-specs: ecs.gn6i-c4g1.xlarge # 配置保留实例规格,支持配置多个规格 knative.aliyun.com/reserve-instance-cpu-resource-request: "2" # 配置 knative.aliyun.com/reserve-instance-cpu-resource-limit: "2" knative.aliyun.com/reserve-instance-memory-resource-request: "8Gi" knative.aliyun.com/reserve-instance-memory-resource-limit: "8Gi" labels: release: qwen spec: containers: - command: - sh - -c - python3 -m vllm.entrypoints.openai.api_server --port 8080 --trust-remote-code --served-model-name qwen --model /mnt/models/Qwen-7B-Chat-Int8 --gpu-memory-utilization 0.95 --quantization gptq --max-model-len=6144 image: kube-ai-registry.cn-shanghai.cr.aliyuncs.com/kube-ai/vllm:0.4.1 imagePullPolicy: IfNotPresent name: vllm-container priorityClassName: infrence-with-high-priority # 设置优先级,用于调度抢占 readinessProbe: tcpSocket: port: 8080 initialDelaySeconds: 5 periodSeconds: 5 resources: limits: cpu: "16" memory: 60Gi nvidia.com/gpu: "1" requests: cpu: "8" memory: 36Gi nvidia.com/gpu: "1" volumeMounts: - mountPath: /mnt/models/Qwen-7B-Chat-Int8 name: qwen-7b-chat-int8 volumes: - name: qwen-7b-chat-int8 persistentVolumeClaim: claimName: qwen-7b-chat-int8-dataset
预期输出:
$ kubectl get po -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES qwen-00001-deployment-reserve-67664799b7-s7kmr 2/2 Running 0 51s 10.0.6.250 ap-southeast-1.10.0.6.236 <none> <none>
4. 性能测试
在生产场景下,用户往往对弹性伸缩的效率、以及弹性伸缩过程中的用户响应时延比较关心。这里我们以缩容到零的场景作为baseline, 基于上述实验环境,测试了多种场景下的性能表现。
依据测试结果,我们有以下建议:
1. Fluid进行模型数据加速在弹性场景下,能明显提高推理服务容器的冷启动时间,进而缩短用用户响应时延,这里主要以TTFT为例。不管是在缩容到0、保留实例还是资源将配的保留实例的部署方式,Fluid加速都能有效的降低Max、P99、P90的TTFT时间。
2. 保留实例+Fluid: 适合对用户响应时延要求高,比如在线推理业务,在保留示例承载10qps请求时,Max_TTFT基本控制在1.2s, P99_TTFT在0.6s左右。
3. 资源降配的保留实例+Fluid: 适合用户对用户响应时延和成本有权衡的场景,最大时延在资源降配后更高;但在异构资源的集群中,可以明显的节省用户成本。
4. 缩容到0+Fluid加速:适合用户响应时延不敏感的业务类型,比如离线推理。虽然在扩容阶段个别请求延迟较高,但是从P99_TTFT和Avg_Output_Tokens来看,批处理的速度并不低。
5. 小结
Knative的基于请求弹性配置与大语言模型(LLM)的推理场景高度契合。此外,它的资源降配特性可以显著帮助用户降低成本。而ResourcePolicy则通过自定义优先级和抢占,增强了对异构计算资源精细化使用,并大幅提高了利用率。对于推理过程中的弹性场景,模型加载时间是导致冷启动延迟的主要因素。使用Fluid可以显著提升LLM应用的弹性效率:一个17GB的模型加载时间能够从90秒缩短至约20秒,在缩容至0的情况下,从0开始进行弹性扩展的最大TTFT从94秒缩短到21秒。而在保留一个A10实例的情况下,最大TTFT从2秒缩短至1.2秒。
欢迎有兴趣的加入阿里云 Knative 钉钉交流群(群号:23302777)。
参考:
[1] Fluid数据缓存优化策略最佳实践_容器服务 Kubernetes 版 ACK(ACK)-阿里云帮助中心
[2] 部署Knative_容器服务Kubernetes版ACK(ACK)-阿里云帮助中心
[3] 自定义弹性资源优先级调度_容器服务Kubernetes版 ACK(ACK)-阿里云帮助中心
我们是阿里巴巴云计算和大数据技术幕后的核心技术输出者。
获取关于我们的更多信息~