程序员鸭梨头像
关注

大模型推理服务的弹性伸缩架构——基于 GPU 指标的 HPA 策略

大模型推理服务的弹性伸缩架构——基于 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 规格。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/alex_goden/article/details/162829694

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--