大模型服务自动扩缩容:基于Token吞吐与GPU利用率的HPA策略设计

📅 2026/7/17 16:17:10
大模型服务自动扩缩容:基于Token吞吐与GPU利用率的HPA策略设计
大模型服务自动扩缩容基于Token吞吐与GPU利用率的HPA策略设计一、模型服务扩缩容的指标困境大模型推理服务的扩缩容远比传统微服务复杂。传统服务通常以CPU使用率或QPS作为核心指标但GPU场景下这两个指标都存在明显缺陷。CPU使用率无法反映GPU的计算饱和度——模型在做矩阵运算时CPU可能很闲QPS则忽略了请求复杂度的差异一次处理512 token的请求和一次处理8192 token的请求成本完全不同。真正有效的指标需要同时覆盖两个维度请求处理速率和硬件资源利用率。Token吞吐量tokens per second衡量实际计算产出GPU利用率反映硬件使用效率。这两者结合才能准确描述模型服务的健康状态。二、KEDA Prometheus Scaler配置实践KEDAKubernetes Event-Driven Autoscaling提供了比原生HPA更灵活的扩缩容策略。对于GPU推理服务我们通过Prometheus Scaler来对接自定义指标。以下是基于Token吞吐量和GPU利用率的ScaledObject配置apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: llm-inference-scaler namespace: ai-serving spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: llm-inference-deployment pollingInterval: 15 cooldownPeriod: 300 minReplicaCount: 1 maxReplicaCount: 20 triggers: - type: prometheus metadata: serverAddress: http://prometheus.monitoring.svc:9090 metricName: token_throughput_utilization threshold: 0.8 query: | sum(rate(http_tokens_per_second{servicellm-inference}[2m])) / sum(kube_deployment_spec_replicas{deploymentllm-inference-deployment}) - type: prometheus metadata: serverAddress: http://prometheus.monitoring.svc:9090 metricName: gpu_utilization_percentage threshold: 85 query: | avg(DCGM_FI_DEV_GPU_UTIL{pod~llm-inference-deployment-.*}) - type: prometheus metadata: serverAddress: http://prometheus.monitoring.svc:9090 metricName: request_queue_depth threshold: 10 query: | avg(inference_request_queue_depth{pod~llm-inference-deployment-.*}) advanced: horizontalPodAutoscalerConfig: behavior: scaleDown: stabilizationWindowSeconds: 300 policies: - type: Percent value: 50 periodSeconds: 60 scaleUp: stabilizationWindowSeconds: 60 policies: - type: Pods value: 2 periodSeconds: 30多指标触发器的设计遵循任一触发即扩容的原则。实际压测数据显示Token吞吐量指标通常最先触发扩容——当单Pod处理能力接近饱和时Token生产速率会出现明显拐点。GPU利用率作为辅助指标用于防止资源浪费队列深度则提供最终兜底。三、冷启动延迟与扩容窗口的博弈模型服务的最大痛点是冷启动。一个70B参数的模型从镜像拉取到权重加载完成可能需要3-8分钟。这意味着在流量突增场景下新Pod启动期间老Pod已经在承受超额负载。解决冷启动问题的策略分层第一层模型预热池。维持一定数量的预热Pod权重已加载到GPU显存中处于待命状态。这类似于数据库连接池的思路——用空间换时间。# 模型预热管理器Python侧示例 import asyncio import torch from transformers import AutoModelForCausalLM, AutoTokenizer class ModelWarmPool: 模型预热池确保最小数量的Pod处于就绪状态 def __init__(self, model_path: str, min_warm_pods: int 2): self.model_path model_path self.min_warm_pods min_warm_pods self._ready_event asyncio.Event() async def preload_weights(self) - None: 在Pod启动时预加载模型权重到GPU torch.cuda.synchronize() model AutoModelForCausalLM.from_pretrained( self.model_path, torch_dtypetorch.float16, device_mapauto, trust_remote_codeTrue ) # 触发初次推理以完成KV Cache预热 tokenizer AutoTokenizer.from_pretrained(self.model_path) inputs tokenizer(warmup, return_tensorspt).to(cuda) with torch.no_grad(): _ model.generate(**inputs, max_new_tokens1) self._ready_event.set() # 向就绪探针暴露状态 await self._mark_ready() async def _mark_ready(self) - None: import os ready_file /tmp/model_ready os.makedirs(os.path.dirname(ready_file), exist_okTrue) with open(ready_file, w) as f: f.write(1)第二层渐进式引流。新Pod就绪后不立即接收全量流量而是通过Service Mesh逐步增加权重让模型在前几个请求中完成KV Cache的热身。第三层溢出队列。当所有Pod都在处理请求时新请求进入有界队列等待而非直接返回503。队列长度也是KEDA的触发指标之一。四、缩容时的优雅退出与请求排空缩容比扩容更容易出问题。粗暴地终止Pod会导致正在推理中的请求被中断已消耗的计算资源白白浪费。对于自回归生成任务一个请求可能持续数十秒甚至数分钟。优雅退出流程的核心实现// Go侧推理网关的优雅退出处理器 package gateway import ( context os/signal sync syscall time ) type GracefulShutdown struct { inflightRequests sync.WaitGroup draining atomic.Bool drainTimeout time.Duration } func (gs *GracefulShutdown) OnSIGTERM(ctx context.Context, server *http.Server) { sigCh : make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT) -sigCh gs.draining.Store(true) // Step 1: 标记节点为不可调度停止接收新请求 // 通过更新Endpoint的ready状态实现 // Step 2: 等待正在处理的请求完成 done : make(chan struct{}) go func() { gs.inflightRequests.Wait() close(done) }() select { case -done: // 所有请求正常完成 case -time.After(gs.drainTimeout): // 超时强制退出但记录未完成请求数量 log.Warn(drain timeout, forcing shutdown, remaining, gs.remainingCount()) } // Step 3: 释放GPU资源 gs.releaseGPUResources() // Step 4: 关闭HTTP服务器 shutdownCtx, cancel : context.WithTimeout(context.Background(), 5*time.Second) defer cancel() server.Shutdown(shutdownCtx) } func (gs *GracefulShutdown) WrapHandler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if gs.draining.Load() { // 已经处于排空状态将请求转发到其他Pod w.Header().Set(X-Draining, true) http.Error(w, node draining, http.StatusServiceUnavailable) return } gs.inflightRequests.Add(1) defer gs.inflightRequests.Done() next.ServeHTTP(w, r) }) } func (gs *GracefulShutdown) releaseGPUResources() { // 通知设备插件释放GPU // 避免GPU资源泄漏导致后续调度问题 }排空超时的设定需要权衡。对于推理服务建议排空超时设置在60-120秒之间——足以让大部分请求完成生成但不至于让Kubernetes等待过久导致Pod卡在Terminating状态。五、总结大模型推理服务的扩缩容策略核心在于指标选择和生命周期管理。Token吞吐量配合GPU利用率构成更准确的饱和度衡量体系KEDA的Prometheus Scaler提供了灵活的指标对接能力。冷启动问题需要通过权重预热、渐进式引流和溢出队列三层策略来缓解。缩容时的优雅退出尤其重要——推理请求的生命周期远比HTTP请求长粗暴终止的成本不可忽视。合理的排空超时和流量转发机制是保障服务稳定性的最后一道防线。