大模型推理服务的弹性伸缩架构——基于 GPU 指标的 HPA 策略
一、问题背景与核心矛盾
大模型推理服务与传统的无状态 Web 服务在资源需求上存在本质差异。传统服务以 CPU 和内存为核心指标,通过 Kubernetes HPA(Horizontal Pod Autoscaler)即可实现较为准确的弹性伸缩。然而,大模型推理服务的核心瓶颈在于 GPU 显存占用和计算利用率,CPU 使用率与服务质量之间并不存在线性映射关系。
在实际生产环境中,曾经出现这样一个典型场景:某对话服务的 GPU 利用率已经接近 95%,但 CPU 使用率仅为 30%,按照默认的 CPU-based HPA 策略,系统并不会触发扩容,导致请求排队时间从 200ms 飙升至 5s 以上。这说明基于 CPU 的伸缩策略在面对 GPU 密集型工作负载时存在根本性缺陷。
核心矛盾可以归纳为以下三点:
- GPU 利用率与 CPU 利用率不耦合,CPU 指标无法反映 GPU 瓶颈。
- 模型加载时间较长(数十秒到分钟级),冷启动延迟严重影响弹性响应速度。
- GPU 资源成本高昂,过度扩容会造成显著的资源浪费。
二、GPU 指标的采集与暴露
要实现基于 GPU 指标的 HPA,第一步是在 Kubernetes 集群中采集并暴露 GPU 相关指标。在技术选型层面,DCGM(NVIDIA Data Center GPU Manager)是目前较为成熟的方案。
/** * GPU指标采集器——通过 DCGM Exporter 的 Prometheus 接口拉取 GPU 指标, * 并将其转换为 Kubernetes Metrics API 可消费的格式。 * * 为什么用 Pull 模式而非 Push 模式:避免 GPU 节点上的采集 Agent 成为性能瓶颈, * 同时保持指标采集链路与现有 Prometheus 体系的一致性。 */ @Service public class GpuMetricsCollector { private static final Logger log = LoggerFactory.getLogger(GpuMetricsCollector.class); private final RestTemplate restTemplate; private final String dcgmExporterUrl; public GpuMetricsCollector( @Value("${monitor.dcgm.exporter.url}") String dcgmExporterUrl, RestTemplateBuilder builder) { this.dcgmExporterUrl = dcgmExporterUrl; // 为什么设置较长超时:GPU 指标采集涉及硬件查询,响应时间可能超过默认的 1s this.restTemplate = builder .connectTimeout(Duration.ofSeconds(5)) .readTimeout(Duration.ofSeconds(10)) .build(); } /** * 采集指定 GPU 设备的利用率指标。 * * @param gpuDevice GPU 设备编号(如 nvidia0) * @return GPU 利用率(0.0~1.0),采集失败返回 -1 表示不可用 */ public double collectGpuUtilization(String gpuDevice) { try { String query = String.format( "DCGM_FI_DEV_GPU_UTIL{device=\"%s\"}", gpuDevice); String url = dcgmExporterUrl + "/api/v1/query?query=" + URLEncoder.encode(query, StandardCharsets.UTF_8); ResponseEntity<PrometheusResponse> response = restTemplate.getForEntity( url, PrometheusResponse.class); if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) { return parseGpuUtilization(response.getBody()); } } catch (RestClientException e) { // 为什么记录 WARN 而非 ERROR:单次采集失败不影响整体伸缩决策, // 多次失败后会通过指标缺失自动触发告警 log.warn("GPU指标采集失败, device={}, 原因={}", gpuDevice, e.getMessage()); } return -1.0; } private double parseGpuUtilization(PrometheusResponse response) { if (response.getData() == null || response.getData().getResult() == null || response.getData().getResult().isEmpty()) { return -1.0; } try { String value = response.getData().getResult().get(0).getValue()[1]; return Double.parseDouble(value); } catch (NumberFormatException e) { log.warn("GPU利用率数值解析异常, rawValue={}", response.getData().getResult().get(0).getValue()[1]); return -1.0; } } }DCGM Exporter 将 GPU 指标暴露为 Prometheus 格式,再通过 Prometheus Adapter 转换为 Kubernetes Custom Metrics API。Prometheus Adapter 的配置决定了哪些指标可供 HPA 使用。
三、HPA 策略设计
基于 GPU 指标的 HPA 不能简单地照搬 CPU-based HPA 的配置模式,需要针对业务场景进行差异化设计。
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: llm-inference-hpa namespace: ai-inference spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: llm-inference-service minReplicas: 2 maxReplicas: 10 metrics: # GPU利用率指标 —— 在70%时扩容,避免GPU算力瓶颈导致排队 - type: Pods pods: metric: name: gpu_utilization_avg target: type: AverageValue averageValue: "70" # 请求队列长度指标 —— 兜底策略,防止GPU指标滞后导致的响应延迟 - type: Pods pods: metric: name: inference_request_queue_depth target: type: AverageValue averageValue: "5" behavior: scaleUp: stabilizationWindowSeconds: 60 policies: - type: Pods value: 2 periodSeconds: 60 scaleDown: stabilizationWindowSeconds: 300 policies: - type: Pods value: 1 periodSeconds: 120上述配置中包含两个关键设计:
- GPU 利用率作为主指标,目标值设为 70% 而非更高,预留 30% 的缓冲空间以吸收突发流量。
- 请求队列深度作为辅助指标,在 GPU 指标尚未反映压力时提前触发扩容(例如模型切换导致单请求耗时增加的情况)。
缩容策略设计上,stabilizationWindowSeconds设为 300s(5分钟),避免因流量短暂回落后立即缩容而导致反复的模型加载。
四、冷启动优化与模型预热
大模型推理服务的一个显著痛点是冷启动时间。容器启动后需要加载模型权重到 GPU 显存,对于 7B 参数级别的模型,加载时间通常在 30~90 秒之间。
为解决这一问题,引入模型预热机制(Model Pre-warming):
/** * 模型预热调度器——在 HPA 扩容完成后自动触发模型加载, * 确保新 Pod 在接收流量前已完成模型初始化。 * * 为什么在 PostConstruct 中执行而非构造函数: * Spring 依赖注入完成后才能获取 Service 引用,构造函数阶段依赖尚未就绪。 */ @Component public class ModelPreWarmScheduler { private static final Logger log = LoggerFactory.getLogger(ModelPreWarmScheduler.class); private final InferenceService inferenceService; private final PodMetadataProvider podMetadataProvider; public ModelPreWarmScheduler( InferenceService inferenceService, PodMetadataProvider podMetadataProvider) { this.inferenceService = inferenceService; this.podMetadataProvider = podMetadataProvider; } @EventListener(ContextRefreshedEvent.class) public void onApplicationReady() { String podName = podMetadataProvider.getCurrentPodName(); log.info("容器启动完成, pod={}, 开始模型预热加载", podName); try { // 为什么设置 180s 超时:7B 模型加载一般在 90s 内完成, // 保留 2 倍 margin 应对峰值负载下的 GPU 驱动排队 boolean success = inferenceService.loadModelWithTimeout( "qwen-7b-chat", Duration.ofSeconds(180)); if (success) { log.info("模型预热完成, pod={}, 准备接收流量", podName); } else { log.error("模型预热超时, pod={}, 容器将被驱逐重建", podName); // 为什么主动退出而非等待重试: // 加载超时说明 GPU 驱动或显存存在问题,继续运行只会接收流量后返回错误 System.exit(1); } } catch (Exception e) { log.error("模型预热异常, pod={}, 原因={}", podName, e.getMessage(), e); System.exit(1); } } }此外,配合 Kubernetes 的 Readiness Probe,只有模型加载完成后才将 Pod 标记为就绪状态。
五、架构总览
graph TD A[Prometheus] -->|Pull Metrics| B[DCGM Exporter] B -->|Query GPU Stats| C[NVIDIA GPU Driver] D[Prometheus Adapter] -->|Custom Metrics API| E[Kubernetes API Server] A -->|Remote Write| D E -->|Metrics Query| F[HPA Controller] F -->|Scale Decision| G[llm-inference Deployment] G -->|Create Pod| H[New Inference Pod] H -->|Readiness Gate| I[Model PreWarmScheduler] I -->|Load Model| C I -->|Ready Signal| J[Service Endpoint] J -->|Route Traffic| K[Ingress / Gateway] style F fill:#f9f,stroke:#333,stroke-width:2px style I fill:#bbf,stroke:#333,stroke-width:2px六、总结
大模型推理服务的弹性伸缩是一项需要综合考虑 GPU 特性、模型加载时间、成本控制与服务质量保障的系统工程。基于 GPU 指标的多维度 HPA 策略配合模型预热机制,能够在保障服务可用性的同时,将 GPU 资源利用率维持在合理区间。
在实际落地过程中,还需要根据业务流量特征持续调优 HPA 的扩缩容参数,建议配合混沌工程手段验证极端场景下的弹性能力。同时,对于成本敏感的场景,可以考虑配合 Spot 实例或竞价实例来降低 GPU 使用成本。
GPU 显存利用率的监控与优化
GPU 利用率指标并不完全等于显存利用率。一个常见误区是 GPU 利用率 70% 就认为还有 30% 的扩容空间,但实际上可能是 GPU 计算单元闲置但显存已满——新的模型加载或新请求的 KV Cache 分配被显存不足阻断。DCGM 提供的DCGM_FI_DEV_FB_USED(Frame Buffer Used)和DCGM_FI_DEV_FB_FREE(Frame Buffer Free)指标是显存监控的关键。我们在 HPA 中额外增加了显存使用率指标作为扩容信号:当显存使用率超过 85% 且 GPU 利用率未达到扩容阈值时,触发扩容而非缩容——因为显存已饱和意味着模型无法处理更多并发请求。
HPA 与 VPA 的配合策略
对于推理服务的 Batching 场景,HPA 的横向扩容和 VPA(Vertical Pod Autoscaler)的纵向扩容可以互补。HPA 增加 Pod 数量提升并发吞吐,VPA 增加单 Pod 的 GPU 显存配额提升批处理上限(更大的 KV Cache 空间可容纳更大的 Batch Size)。两者的配合策略是:优先使用 HPA 扩容到 maxReplicas,若仍无法满足吞吐需求(GPU 利用率持续 >85% 且显存已满),则触发 VPA 提升单 Pod 的 GPU 规格。