loading

Loading

探索与发现

首页 AI 开发实战

推理服务扩缩容:GPU 利用率为什么不能当主指标

字数: (3194)
阅读: (4)
0
摘要:HPA 照抄 CPU/GPU 利用率阈值,在推理服务上基本会失灵。本文拆开说明为什么排队深度才是可靠信号,以及 vLLM、SGLang、Dynamo 各自动哪个指标、adapter 怎么接、阈值和预热窗口怎样兜底。

给推理服务配 HPA,第一版 YAML 基本都是照着微服务抄的:targetCPUUtilizationPercentage: 70,或者换成 GPU 利用率。上线后通常会出现两种情况——要么抖动到不敢看,要么压根不触发。这中间的错位不在阈值,在指标选错了对象。

GPU 利用率为什么不能当主信号

阿里云 ACK 的文档把原因写得很直白:LLM 推理和传统微服务有明显差异,单次推理耗时显著增加,资源瓶颈集中在 GPU 算力和显存容量,而当前 GPU 利用率和显存的统计方式有局限,难以准确反映节点负载,因此要以推理引擎自身暴露的性能指标(请求延迟、队列深度)作为扩缩容决策依据。

从工程角度拆一下,GPU 利用率(nvidia-smi 报的那个数)衡量的是一段时间内有计算任务在跑的采样比例,而不是这段时间产生了多少有用的 token。一个卡在解码阶段的步进循环里持续干活的 GPU,利用率可以稳稳贴着 100%,此时后面可能排着几十个请求,你也看不出任何余量;反过来,刚做完预热、正在等第一批请求的那几秒,利用率会跌下来,但其实一波流量马上就到。拿这个数做扩容信号,扩容动作总比实际压力晚半拍到一拍。

显存更不适合当触发条件。模型权重是常驻的,KV Cache 又比较容易以预分配的方式先占住,显存的基线占用几乎从一开始就是平坦的。到头之前它会一直安静,打满之后则直接 OOM 挂了,中间没有一段可供 HPA 平滑判断的过渡区间。它适合报警,不适合扩缩容。

这也是现有生产方案的一个共同结论。腾讯云开发者社区里那篇讲算力成本管控的文章,虽然把 GPU 利用率列为“最核心判定指标”,并给出低于 30% 持续 5 分钟缩容、高于 80% 持续 3 分钟扩容的示例区间,但它同时还要看并发请求数和平均响应时延。AWS 在 EKS 混合云那篇实践里则直接给出指标清单,明确写“Requests Waiting 是触发扩容的关键指标”。阿里云 ACK 的例子更干脆:vLLM 挂 vllm:num_requests_waiting,SGLang 挂 sglang:num_queue_reqs,NVIDIA Dynamo 挂 nv_llm_http_service_inflight_requests;阈值只是示例,文档要求按实际业务场景、结合资源成本和服务 SLO 再调。

外挂指标这条路怎么走

习惯走 Kubernetes 这条线的话,HPA 读不了 /metrics 上的文本,中间必须先有一个 metrics adapter。按阿里云那条链路,是先用 PodMonitor 抓取推理 Pod 的指标,把数据推到阿里云 Prometheus,Prometheus 侧把这些自定义指标按 seriesQuery 的写法转成 custom.metrics.k8s.io 的 API。中间的自定义指标接入 Ali 云的 Prometheus 是计费的,按集群规模、应用数量、数据量计。

那段 adapter 配置的要点不在语法,在 metricsQuery 里的聚合:sum({}) by ()。这一步等于约定“一个 Pod 副本的队列深度”是怎么算出来的。

写完之后值得回头验证两件事。一是指标到底有没有被采到:kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/*/vllm:num_requests_waiting" 取一下,看到的要是个像数量的数,别是空数组。二是聚合口径对不对:num_requests_waiting 是每个 Pod 的排队数,配额加 by (pod) 才是每个副本的平均值;如果不分组直接 sum 全加起来,再设个绝对值阈值,那么队列翻倍时它到阈值的时间点会随副本数变化,扩容倍数全靠运气。这一点直接决定 HPA 的解算式是什么样。

如果连自己数排队数都信不过,可以换个玩法:阿里云文档那边是单独设阈值。averageValue当取 5 就是“单Pod平均排队 5 个以上扩容”,但它在 average 后没有做粒度区分。AWS 中文博客列的`custom_metric`想法更明确,直接把多个数算成 0/1 拨弄开关指标给KEDA读。适用于和 custom.metrics.k8s.io适配的方式谈不拢的场景。

不过GKE、AKS都有 HPA 读自定义的集群适配做法,主要缺的是各引擎里原生没法暴露 num_requests_waiting 的框架。如果引擎版本较老、又用监控测这2个指标,建议先就这块做个简单的基线或者代理服务。

如果栈是 Knative,它的做法类似而更集中:Serving 给每个 Pod 注入 queue-proxy,由它把请求信息报给 Autoscaler,可以按 concurrency、rps、cpu、memory 里选;同时优先级类可以让推理 Pod 的启动少受普通业务的干扰。选择某个方案,前面在指标采集链路上没有投入改造成本,看你自己。

阈值和不稳定的关系

代理设置的示例没必要抄数字:averageValue取一个数就只管 5。这跟成本相关,而不同的部署在不同的业务量。不过用户面是在微服务习惯了“扩容秒级、最少换多少资源”之后走到这一步,必然会遇到同一类的困惑:averageValue: 5 在高SLA负载下就非常偏敏感、导致排队来不及扩。稳妥起见在指标设成sglang 之类的语义正常量。

有个细节要提前看懂。队列数字来自pod指标,如果新增的副本预热极慢(大模型加载权重、填充 Graph、编译),扩出来的临时负载来不及干活。所以在 HPA 的反应窗口几秒对任务完全无法改变卡的情况,这一层其实一直在。文档让人根据结合 SLO 去按成本分尺寸和设定副本范围是诚实的——那样人少因为只提到了扩,实际是阈值到达时间是否浪费。

推理是幂等的

写扩缩容细节容易忽略一段现实:缩容时把 pod 喊停,请求要是还在流。设 terminationGracePeriodSeconds允许多活好一会儿后,别的不是 GPU 的用户来了,活也接着在你上一个 pod 那里开跑;服务没做完已送达给客户端就没啥用,客户端重发到别的pod也是等。这个在实际占大头的资源利用率在负载突然涨时远没有它看起来重要:扩出来的新副本在预热中。 补一个手段跟效果对比:

指标来源代表量做法
vLLMvllm:num_requests_waiting按pod均值的averageValue
SGLangsglang:num_queue_reqs同上
NVIDIA Dynamonv_llm_http_service_inflight_requests同上
Knative queue-proxyconcurrency原生KPA,不需要多加解析层

还有一点,AWS 列了 KV Cache Usage。我认为它值可以拿来看显存压力,但不做onlineslow down。它的特点跟 GPU 显存一样,在压力早期太平静,按这个扩容是相当敏感和滞后。官方文档没有强推荐,扩时把它当成附加信息处理即可。

围绕各集群实际验证过的那个对指标不要一步定得太粗:pending队列变多、原因在排队前就看哪几个分量的贡献:num_requests_waiting可能仅仅因为前缀缓存刚好全都失效不命中,变长。可以按 model 和 backend 分几个副本、指标分开设 HPA。

一开始就把它做对没有太大指望:从带队列深度、限制副本范围的做法开始试跑一周,再按压测数据和真实流量分布把扩和缩的动作决策做成两层阈值、按窗口做稳定性判断。指标对了,HPA 才有意义。

转载请注明出处: 皮哈皮阿

本文的链接地址: https://blog.phpat.com/ai-dev/656.html

手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

您可能对以下文章感兴趣
1 / 1
预览图