TL;DR
CPU基準のスケールではGPU推論は守れない。Prometheus AdapterGPU利用率/メモリ占有/リクエスト遅延 を Kubernetes の HPA 指標に公開し、KServe を RawDeployment で動かして HPA を素直に当てるのが実務で安定。Knative モードでも工夫はできるが、コントローラの競合に注意。


なぜ必要?

  • デフォルトHPAは CPU使用率 が指標 → GPU推論PodではCPUが暇でも GPUが飽和 して詰まる。
  • KServe は Prometheus に豊富なメトリクスを出す。GPUユーティリゼーション/メモリ、リクエストレイテンシ/スループット をスケール指標にできる。
  • マルチモデル(Triton/KServe multi-model)では、モデルごと/エンドポイントごとに Pod 数 × GPU 割当 を最適化することが安定運用のカギ。

構成の全体像(ASCII)

Clients ─▶ Istio/Ingress ─▶ KServe Service ─▶ Predictor Pod (GPU)
                                   │               ├─ app container (Triton/torchserve etc.)
                                   │               └─ queue-proxy (Knative) ※Knative時
                                   │
                                   └─ Metrics ▶ Prometheus ◀ dcgm-exporter/nvidia-dcgm
                                                        │
                                                Prometheus Adapter
                                                        │ (custom.metrics / external.metrics)
                                                        ▼
                                                     HPA (autoscaling/v2)

2つの運用パターン

A) RawDeployment モード(推奨)

KServe の predictor を RawDeployment として起動し、普通の Deployment/Service になる。HPA の制御がシンプルで、GPU系カスタム指標を素直に適用可能。

要点

  • KServe InferenceService で deploymentMode: RawDeployment を設定。
  • 生成される deploymentscaleTargetRef に指定した HPA を作る。
  • Knative のスケーラ(KPA)と競合しない。

InferenceService 例(抜粋)

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: triton-gpu
spec:
  predictor:
    deploymentMode: RawDeployment
    containers:
      - name: triton
        image: nvcr.io/nvidia/tritonserver:24.01-py3
        resources:
          limits:
            nvidia.com/gpu: 1

B) Knative モード(高度)

Knative(KPA or Knative-HPA)を利用。同時実行数(concurrency) でのスケールは得意だが、GPUカスタム指標での制御は工夫が要る。KPA と HPA の多重制御に注意。

要点

  • autoscaling.knative.dev/class: hpa.autoscaling.knative.dev で Knative-HPA クラスに切替え。
  • ただし Knative アノテーションで指定できる指標は限定的。GPU系は 別途HPA を作り、対象DeploymentがKnativeのRevision になっている点に注意(ローリング時に名前が変わる)。
  • 本番では RawDeployment を強く推奨。

指標の集め方

1) GPU 指標(dcgm-exporter)

  • nvidia-dcgm / dcgm-exporter を GPU ノード に DaemonSet で配置。
  • 代表的メトリクス:
    • DCGM_FI_DEV_GPU_UTIL(GPU使用率 %)
    • DCGM_FI_DEV_FB_USED(FBメモリ使用量 bytes)
    • 環境により nvidia_gpu_duty_cycle / nvidia_gpu_memory_used_bytes として露出する場合もあり

2) リクエスト指標(KServe/Knative)

  • 代表例:
    • queue_requests_per_second / request_count(スループット)
    • request_latencies(p50/p90/p99)
  • Istio や app 側(Prometheus SDK)で直接 histogram を出すのも有効。

Prometheus Adapter の設定

Prometheus のクエリ結果を Custom Metrics API / External Metrics API として公開。

Adapter ConfigMap(抜粋)

apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-adapter
  namespace: custom-metrics
  labels:
    app.kubernetes.io/name: prometheus-adapter
    app.kubernetes.io/component: metrics
    app.kubernetes.io/part-of: hpa
data:
  rules.yaml: |
    rules:
      # Pod単位のGPU使用率(Pods metric)
      - seriesQuery: 'DCGM_FI_DEV_GPU_UTIL{namespace!="",pod!=""}'
        resources:
          overrides:
            namespace: {resource: namespace}
            pod:       {resource: pod}
        name:
          matches: ".*"
          as: "gpu_utilization"
        metricsQuery: 'avg(DCGM_FI_DEV_GPU_UTIL{<<.LabelMatchers>>}) by (<<.GroupBy>>)'

      # Pod単位のGPUメモリ使用量(bytes)
      - seriesQuery: 'DCGM_FI_DEV_FB_USED{namespace!="",pod!=""}'
        resources:
          overrides:
            namespace: {resource: namespace}
            pod:       {resource: pod}
        name:
          matches: ".*"
          as: "gpu_memory_bytes"
        metricsQuery: 'avg(DCGM_FI_DEV_FB_USED{<<.LabelMatchers>>}) by (<<.GroupBy>>)'

      # 1分平均リクエスト数(External metric; サービス全体)
      - seriesQuery: 'request_count{namespace!=""}'
        resources:
          overrides:
            namespace: {resource: namespace}
        name:
          matches: ".*"
          as: "request_rate_per_min"
        metricsQuery: 'sum(rate(request_count{<<.LabelMatchers>>}[1m]))'

Tips: 環境によってメトリクス名が nvidia_gpu_duty_cycle 等の場合は seriesQuery/metricsQuery を置換。まずは kubectl -n monitoring port-forward svc/prometheus 9090 して 実際のメトリクス名 を確認。


HPA マニフェスト例(autoscaling/v2)

Pod指標(GPU利用率)でスケール

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: triton-gpu-hpa
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: triton-gpu-predictor-default # RawDeploymentで生成されるDeployment名に合わせる
  minReplicas: 1
  maxReplicas: 5
  metrics:
  - type: Pods
    pods:
      metric:
        name: gpu_utilization
      target:
        type: AverageValue
        averageValue: "70"   # 70% を目安に(整数/数値として扱われる)

外部指標(リクエストレート+p99レイテンシ上限)でスケール

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: triton-gpu-hpa-ext
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: triton-gpu-predictor-default
  minReplicas: 1
  maxReplicas: 10
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
        - type: Pods
          value: 2
          periodSeconds: 15
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 30
          periodSeconds: 60
  metrics:
  - type: External
    external:
      metric:
        name: request_rate_per_min
        selector:
          matchLabels:
            service: "triton-gpu"
      target:
        type: Value
        value: "200" # 1分あたり200reqでスケールアップ

運用メモ: p99 レイテンシを使う場合は、Adapterで p99 を返すクエリ(histogram_quantile(0.99, rate(...[1m])))を公開し、閾値(ms)で type: AverageValue/Value を指定。


監視と可視化

  • Grafana ダッシュボード
    • GPU: gpu_utilization, gpu_memory_bytes(Podごと/ノードごと)
    • App: request_rate, latency_p50/p90/p99, error_rate
  • K8sイベント で HPA の決定を追跡:kubectl describe hpa triton-gpu-hpa
  • 実リソース確認nvidia-smi dmon(ノード)、kubectl top pods --use-protocol-buffers(metrics-server)

チューニングの考え方(現場のプレイブック)

  1. SLO を先に決める:p99 ≤ 150ms / RPS 200 / エラー率 < 0.5% … など。
  2. ボトルネック特定:GPU利用率>90%で遅延↑ならGPU不足。利用率<50%で遅延↑ならI/O/前処理/メモリ不足。
  3. 閾値設計
    • GPU利用率: 65–75% で拡張、55–60% で縮小。
    • p99 レイテンシ: SLO×0.9 を閾値に。
    • リクエストレート: 1GPUあたり処理能力から逆算。
  4. スケール速度behavior.scaleUp は速め(period短/増分大)、scaleDown は慎重(stabilization長/減少率低)。
  5. 多指標の併用:GPU利用率と p99 の OR 条件(どちらか超えたら拡張)を別HPAで模擬、またはアダプタ側で max() を返すクエリを作る。

マルチモデル運用のコツ(Triton/KServe)

  • モデルごとに別Deployment(RawDeployment)で HPA を分離。依存関係/負荷特性が違うモデルを同居させない。
  • 同一Pod内に複数モデルを載せる場合、モデル別キュー/動的バッチング を調整し、RPS→GPU使用率 の変換が一定になるよう整える。
  • GPUメモリの上限 で頭打ちになるケースが多い→ gpu_memory_bytes もスケール指標に追加し、FB使用率が 90% 超で拡張。

よくある落とし穴 → 対策

  • CPU指標のまま:GPU詰まりでスケールしない → Adapter導入+HPAをGPU/レイテンシ指標へ置換。
  • KnativeとHPAがケンカ:KPAがscale-to-zero、HPAが増減で競合 → RawDeploymentで解消、やむを得ずKnativeならクラス/閾値を一本化。
  • メトリクス名の不一致:環境で DCGM_* / nvidia_* が違う → Prometheus で name を確認しルールを合わせる。
  • リビジョン名が変わる(Knative)→ HPAの scaleTargetRef が外れる → RawDeployment推奨。もしくは自動化でHPA更新。
  • スケールが遅いbehavior 未設定 → scaleUp を攻め、scaleDown は保守的に。

検証チェックリスト(5分)


最小構成のデプロイ順(コマンド雛形)

# 1) GPUノードに dcgm-exporter
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/dcgm-exporter/master/dcgm-exporter.yaml

# 2) Prometheus / ServiceMonitor(全体の監視基盤)
#   例: kube-prometheus-stack を Helm で導入

# 3) Prometheus Adapter(上記の rules.yaml をConfigMapに)
#   例: helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
#       helm install prom-adapter prometheus-community/prometheus-adapter -n custom-metrics -f values.yaml

# 4) KServe InferenceService (RawDeployment) を適用
kubectl apply -f inference_rawdeployment.yaml

# 5) HPA(gpu_utilization / request_rate)を適用
kubectl apply -f hpa-gpu.yaml
kubectl apply -f hpa-req.yaml

# 6) 監視と負荷試験
kubectl describe hpa triton-gpu-hpa
hey -z 60s -q 50 -c 50 https://.../v1/models/xxx:predict

仕上げのヒント

  • SLOファースト:指標設計は「何を守りたいか」(p99/エラー率)から逆算。
  • 単純さは正義:まずは RawDeployment + 単一指標 で正しく動かす → その後、複合指標で洗練。
  • ダッシュボード駆動:HPAの挙動とSLOを 同じ画面 に置く。判断が速くなる。

参考キーワード

  • KServe HPA custom metrics / Knative HPA class / Prometheus Adapter / dcgm-exporter / Triton Inference Server / histogram_quantile / RawDeployment

投稿者 kojiro777

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です