TL;DR
CPU基準のスケールではGPU推論は守れない。Prometheus Adapter
で GPU利用率/メモリ占有/リクエスト遅延 を 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
を設定。 - 生成される
deployment
をscaleTargetRef
に指定した 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
- GPU:
- K8sイベント で HPA の決定を追跡:
kubectl describe hpa triton-gpu-hpa
- 実リソース確認:
nvidia-smi dmon
(ノード)、kubectl top pods --use-protocol-buffers
(metrics-server)
チューニングの考え方(現場のプレイブック)
- SLO を先に決める:p99 ≤ 150ms / RPS 200 / エラー率 < 0.5% … など。
- ボトルネック特定:GPU利用率>90%で遅延↑ならGPU不足。利用率<50%で遅延↑ならI/O/前処理/メモリ不足。
- 閾値設計:
- GPU利用率: 65–75% で拡張、55–60% で縮小。
- p99 レイテンシ: SLO×0.9 を閾値に。
- リクエストレート: 1GPUあたり処理能力から逆算。
- スケール速度:
behavior.scaleUp
は速め(period短/増分大)、scaleDown
は慎重(stabilization長/減少率低)。 - 多指標の併用: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