TGI 在 Kubernetes HPA 中基于自定义指标生成 RT 的弹性伸缩配置

文章导读
TGI(Text Generation Inference)服务在 Kubernetes 中做 HPA 弹性伸缩时,通常觉得“只按 CPU/内存不够稳”,因为生成式推理的负载特征取决于并发请求数和每个请求的生成长度,CPU 使用率往往滞后。如果想让伸缩更贴近真实服务质量,可以考虑把响应时间(RT)或排队等待时间作为自定义指标接入 HPA。这里说的“响应时间”,一般指从请求进入服务到首个 token
📋 目录
  1. A 先确认指标来源
  2. B 搭建指标链路
  3. C HPA 配置和 target 选择
  4. D 验证和注意边界
A A

TGI(Text Generation Inference)服务在 Kubernetes 中做 HPA 弹性伸缩时,通常觉得“只按 CPU/内存不够稳”,因为生成式推理的负载特征取决于并发请求数和每个请求的生成长度,CPU 使用率往往滞后。如果想让伸缩更贴近真实服务质量,可以考虑把响应时间(RT)或排队等待时间作为自定义指标接入 HPA。这里说的“响应时间”,一般指从请求进入服务到首个 token 返回的时间,或者完整请求的 P50/P90 延迟,具体用哪种需要先和业务方约定清楚。

用 TGI 的 RT 触发 HPA 是可行的,但前提是:TGI 能暴露相关指标、指标能被 Kubernetes 自定义指标 API 读取、HPA 配置了正确的 target 值。实现路径通常是 Prometheus 采集 TGI 指标,经过 Prometheus Adapter 转成自定义指标,再交给 HPA 使用。如果 TGI 本身不提供 RT 指标,可以在网关层自行埋点,用请求耗时作为指标源。整体配置不复杂,但要先确认指标口径和延迟类型,否则伸缩会偏离预期。

先确认指标来源

TGI 服务在较新版本中会通过 /metrics 暴露 Prometheus 格式指标,常见的有请求数量、生成 token 数、排队请求数等。但“响应时间”这类直方图指标不一定默认开启,或者指标名可能随版本变化。建议先进入 TGI Pod,执行:

kubectl exec -it deploy/text-generation-inference -- curl localhost:80/metrics | grep -i 'duration\|latency\|time'

如果输出里没有类似 tgi_request_duration_seconds 或 tgi_queue_duration_seconds 的汇总指标,就要考虑从 TGI 所在服务网格、API 网关或自己写一层代理来统计 RT。另外,单机延迟可能受 GPU 负载和模型大小影响,指标要按 Pod 维度聚合,还是按模型名维度聚合,需要根据部署方式决定。通常建议按 Deployment 名称和 Pod 名称聚合,这样 HPA 可以直接消费。

搭建指标链路

HPA 需要通过 custom.metrics.k8s.io 或 external.metrics.k8s.io API 获取指标。最常见的做法是部署 Prometheus + Prometheus Adapter。如果你已经在集群里用了 Prometheus,只需让 Adapter 通过 ServiceMonitor 或直接抓取 TGI 的 metrics 端点。没有现成 Adapter 时,可以用 KEDA,它把 Prometheus 查询封装成 ScaledObject,同时支持生成 HPA 对象。

这里给出 Prometheus Adapter 的配置片段,用 tgi_request_duration_seconds 的 P90 作为指标源:

TGI 在 Kubernetes HPA 中基于自定义指标生成 RT 的弹性伸缩配置
rules:
- seriesQuery: 'tgi_request_duration_seconds_bucket{namespace!="",pod!=""}'
  resources:
    overrides:
      namespace: {resource: "namespace"}
      pod: {resource: "pod"}
  name:
    matches: "^(.*)_duration_seconds_bucket$"
    as: "tgi_rt_p90"
  metricsQuery: |
    histogram_quantile(0.90,
      sum(rate(<<.Series>>{<<.LabelMatchers>>}[5m])) by (<<.GroupBy>>))

如果 TGI 指标名不同,把 seriesQuery 中的名字替换成实际暴露的名称。配置完成后,先验证 API 是否返回:

kubectl get `--raw` "/apis/custom.metrics.k8s.io/v1beta1/namespaces/tgi/pods/*/tgi_rt_p90" | jq .

这一步能确认指标名、命名空间和聚合方式是否正确,不要让 HPA 配置直接去猜。

HPA 配置和 target 选择

确认指标可用后,写 HPA。注意 type: Pods 只能针对每个 Pod 的指标,而 type: Object 可以直接引用一个对象指标(比如整个 Deployment 的平均 RT)。如果指标是按 Pod 聚合的,用下面的写法:

TGI 在 Kubernetes HPA 中基于自定义指标生成 RT 的弹性伸缩配置
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: tgi-rt-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: text-generation-inference
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Pods
    pods:
      metric:
        name: tgi_rt_p90
      target:
        type: AverageValue
        averageValue: 500m

这里的 averageValue: 500m 表示希望每个 Pod 的 RT P90 平均为 0.5 秒。如果实际值高于 0.5 秒,HPA 会扩容。选择 target 值不能拍脑袋,需要先采集一段时间 TGI 在不同并发下的延迟分布,结合业务能接受的延迟上限来定。一般建议先保守一些,比如把 target 设为上限的 70%~80%,避免扩容太激进。如果指标来源是外部 Prometheus 且带业务维度,可以用 type: Object 引用没有实例维度的指标,但需要 Adapter 配置里定义好 resource 映射。

验证和注意边界

配置完成后,不能只看 HPA 状态,要实际压一下。可以使用一个简单脚本持续发送并发请求,观察 HPA 的 kubectl describe hpa 输出,确认指标读数是否在变化、副本数是否按预期调整。压测时注意:RT 指标是延迟分布,短时间内抖动可能很大,HPA 默认的 `--horizontal-pod-autoscaler-sync-period` 为 15 秒,但 Adapter 的缓存和 Prometheus 的拉取间隔会引入延迟,整个伸缩链路可能有数分钟延迟。

风险边界主要有三处。第一,如果 TGI 的 RT 指标包含排队时间,当队列堆积时 RT 会快速上升,HPA 扩容可能很猛,需要配合 behavior 里的 scaleUp 策略限制扩容速率。第二,单个请求的 P90 如果受长尾影响大,建议同时观察 P50,避免把少数慢请求放大成扩容触发器。第三,如果模型加载需要时间,扩容后的 Pod 不能立即承接流量,这时应结合 HPA 的 minReplicas 和 PodDisruptionBudget 控制最小基数,或在网关层配置预热。监控方面,除了 HPA 本身,也要盯 Prometheus 中指标本身的采集延迟和缺失情况,指标断点会导致 HPA 停止扩容。

最后提醒一句:自定义指标伸缩不是越灵敏越好。如果 RT 波动频繁,扩容缩容反复横跳,反而会影响稳定性。先跑一段时间,记录实际伸缩行为,再决定是否需要加平滑窗口或使用 KEDA 的高级触发器。