すべてのプロダクト
Search
ドキュメントセンター

Container Service for Kubernetes:PD デカップリングされた推論サービスの自動スケーリングポリシーを設定する

最終更新日:Nov 09, 2025

大規模言語モデル (LLM) 推論のための Prefill-Decode (PD) デカップリングアーキテクチャでは、Prefill ステージと Decode ステージのリソース要件が大幅に異なります。CPU や GPU 使用率などの従来のメトリックは、自動スケーリングには効果がありません。このトピックでは、Dynamo フレームワークを例として使用し、Kubernetes Event-driven Autoscaling (KEDA) を使用して、NATS メッセージキュー内のメッセージバックログに基づいて Prefill ロールに独立した自動スケーリングポリシーを設定する方法を説明します。このメソッドにより、オンデマンドのリソース割り当てが可能になり、サービスコストとパフォーマンスが最適化されます。

前提条件

制限事項

  • このトピックの自動スケーリングソリューションは、PD デカップリングアーキテクチャの Prefill ロールにのみ適用されます。Decode ロールには、別の自動スケーリングポリシーが必要です。Decode ロールには GPU メモリ使用率を使用することをお勧めします。

  • このトピックの例は、Dynamo 推論フレームワークに基づいています。別のフレームワークを使用する場合は、NATS Stream 名や Consumer 名など、関連する構成を適宜調整する必要があります。

手順

RoleBasedGroup (RBG) は、PD デカップリングされた推論サービス内の各ロールの独立したスケーリングを可能にします。このトピックでは、Dynamo PD デカップリングフレームワークを例として、Kubernetes Event-driven Autoscaling (KEDA) を使用して Prefill ロールの個別の自動スケーリングポリシーを設定する方法について説明します。

Dynamo PD デカップリングアーキテクチャでは、保留中の推論リクエストはメッセージとして NATS メッセージキューの dynamo_prefill_queue ストリームにプッシュされます。Prefill インスタンスはコンシューマーとして機能し、このキューからメッセージをプルして処理します。キュー内の保留中のメッセージ数は、Prefill ロールのワークロードを正確に反映します。KEDA が提供する NATS JetStream Scaler は、このキューのメッセージバックログをモニターします。その後、自動スケーリングをトリガーして Prefill インスタンスの数を調整します。

この自動スケーリングポリシーを本番環境に適用する前に、テスト環境で徹底的なストレステストを実行してください。これにより、ワークロードに最適な lagThreshold (メッセージバックログのしきい値) と pollingInterval を決定できます。不適切な構成は、サービスパフォーマンスに影響を与えるスケールアウトの遅延や、リソースを浪費する過剰なスケールアウトを引き起こす可能性があります。

ステップ 1: RBG ロールの ScalingAdapter を作成する

KEDA が RBG 内の特定のロールのレプリカ数を独立して制御できるようにするには、RBG の作成時にターゲットロールの ScalingAdapter を有効にします。この操作により、RoleBasedGroupScalingAdapter リソースが自動的に作成され、ロールにバインドされます。

  1. rbg.yaml ファイルを作成し、73-74 行目で scalingAdapter: enable: true を設定して、RBG の `prefill` ロールの ScalingAdapter を有効にすることができます。

    サンプル YAML コードを展開して表示します。

    apiVersion: workloads.x-k8s.io/v1alpha1
    kind: RoleBasedGroup
    metadata:
      name: dynamo-pd
      namespace: default
    spec:
      roles:
        - name: processor
          replicas: 1
          template:
            spec:
              containers:
                - command:
                    - sh
                    - -c
                    - cd /workspace/examples/llm; dynamo serve graphs.pd_disagg:Frontend -f ./configs/qwen3.yaml
                  env:
                    - name: DYNAMO_NAME
                      value: dynamo
                    - name: DYNAMO_NAMESPACE
                      value: default
                    - name: ETCD_ENDPOINTS
                      value: http://etcd:2379
                    - name: NATS_SERVER
                      value: nats://nats:4222
                    - name: DYNAMO_RP_TIMEOUT
                      value: "60"
                  image: # Dynamo ランタイムイメージのアドレス
                  name: processor
                  ports:
                    - containerPort: 8000
                      name: health
                      protocol: TCP
                    - containerPort: 9345
                      name: request
                      protocol: TCP
                    - containerPort: 443
                      name: api
                      protocol: TCP
                    - containerPort: 9347
                      name: metrics
                      protocol: TCP
                  readinessProbe:
                    initialDelaySeconds: 30
                    periodSeconds: 30
                    tcpSocket:
                      port: 8000
                  resources:
                    limits:
                      cpu: "8"
                      memory: 12Gi
                    requests:
                      cpu: "8"
                      memory: 12Gi
                  volumeMounts:
                    - mountPath: /models/Qwen3-32B/
                      name: model
                    - mountPath: /workspace/examples/llm/configs/qwen3.yaml
                      name: dynamo-configs
                      subPath: qwen3.yaml
                    - mountPath: /workspace/examples/llm/graphs/pd_disagg.py
                      name: dynamo-configs
                      subPath: pd_disagg.py
              volumes:
                - name: model
                  persistentVolumeClaim:
                    claimName: llm-model
                - configMap:
                    name: dynamo-configs
                  name: dynamo-configs
        - name: prefill
          replicas: 2
          scalingAdapter:
            enable: true
          template:
            spec:
              containers:
                - command:
                    - sh
                    - -c
                    - cd /workspace/examples/llm; dynamo serve components.prefill_worker:PrefillWorker -f ./configs/qwen3.yaml
                  env:
                    - name: DYNAMO_NAME
                      value: dynamo
                    - name: DYNAMO_NAMESPACE
                      value: default
                    - name: ETCD_ENDPOINTS
                      value: http://etcd:2379
                    - name: NATS_SERVER
                      value: nats://nats:4222
                    - name: DYNAMO_RP_TIMEOUT
                      value: "60"
                  image: # Dynamo ランタイムイメージのアドレス
                  name: prefill-worker
                  resources:
                    limits:
                      cpu: "12"
                      memory: 50Gi
                      nvidia.com/gpu: "2"
                    requests:
                      cpu: "12"
                      memory: 50Gi
                      nvidia.com/gpu: "2"
                  volumeMounts:
                    - mountPath: /models/Qwen3-32B/
                      name: model
                    - mountPath: /workspace/examples/llm/configs/qwen3.yaml
                      name: dynamo-configs
                      subPath: qwen3.yaml
              volumes:
                - name: model
                  persistentVolumeClaim:
                    claimName: llm-model
                - configMap:
                    name: dynamo-configs
                  name: dynamo-configs
        - name: decoder
          replicas: 1
          template:
            spec:
              containers:
                - command:
                    - sh
                    - -c
                    - cd /workspace/examples/llm; dynamo serve components.worker:VllmWorker -f ./configs/qwen3.yaml --service-name VllmWorker
                  env:
                    - name: DYNAMO_NAME
                      value: dynamo
                    - name: DYNAMO_NAMESPACE
                      value: default
                    - name: ETCD_ENDPOINTS
                      value: http://etcd:2379
                    - name: NATS_SERVER
                      value: nats://nats:4222
                    - name: DYNAMO_RP_TIMEOUT
                      value: "60"
                  image: # Dynamo ランタイムイメージのアドレス
                  name: vllm-worker
                  resources:
                    limits:
                      cpu: "12"
                      memory: 50Gi
                      nvidia.com/gpu: "2"
                    requests:
                      cpu: "12"
                      memory: 50Gi
                      nvidia.com/gpu: "2"
                  volumeMounts:
                    - mountPath: /models/Qwen3-32B/
                      name: model
                    - mountPath: /workspace/examples/llm/configs/qwen3.yaml
                      name: dynamo-configs
                      subPath: qwen3.yaml
              volumes:
                - name: model
                  persistentVolumeClaim:
                    claimName: llm-model
                - configMap:
                    name: dynamo-configs
                  name: dynamo-configs
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: dynamo-service
    spec:
      type: ClusterIP
      ports:
        - port: 8000
          protocol: TCP
          targetPort: 8000
      selector:
        rolebasedgroup.workloads.x-k8s.io/name: dynamo-pd
        rolebasedgroup.workloads.x-k8s.io/role: processor
  2. 次のコマンドを実行してリソースを作成できます。

    kubectl apply -f ./rbg.yaml
  3. RBG を作成すると、システムは ScalingAdapter が有効になっているロールに対して RoleBasedGroupScalingAdapter カスタムリソースを自動的に作成します。その後、システムはこのリソースをロールにバインドします。RoleBasedGroupScalingAdapter は、バインドされたロールの Scale サブリソース実装を提供します。

    • 次のコマンドを実行して、指定されたロールに対して自動的に作成された RoleBasedGroupScalingAdapter を表示できます。

      kubectl get rolebasedgroupscalingadapter

      出力例:

      NAME                  PHASE   REPLICAS
      dynamo-pd-prefill     Bound   2
    • 次のコマンドを実行して、dynamo-pd-prefill ScalingAdapter のステータスを確認できます。

      kubectl describe rolebasedgroupscalingadapter dynamo-pd-prefill

      出力例では、Status.PhaseBound である必要があります。これは、ScalingAdapter が RBG の prefill ロールに正常にバインドされたことを示します。

      Name:         dynamo-pd-prefill
      Namespace:    default
      Labels:       <none>
      Annotations:  <none>
      API Version:  workloads.x-k8s.io/v1alpha1
      Kind:         RoleBasedGroupScalingAdapter
      Metadata:
        Creation Timestamp:  2025-07-25T06:10:37Z
        Generation:          2
        Owner References:
          API Version:           workloads.x-k8s.io/v1alpha1
          Block Owner Deletion:  true
          Kind:                  RoleBasedGroup
          Name:                  dynamo-pd
          UID:                   5dd61668-79f3-4197-a5db-b778ce460270
        Resource Version:        1157485
        UID:                     edbb8373-2b9c-4ad1-8b6b-d5dfff71e769
      Spec:
        Replicas:  2
        Scale Target Ref:
          Name:  dynamo-pd
          Role:  prefill
      Status:
        Phase:     Bound
        Replicas:  2
        Selector:  rolebasedgroup.workloads.x-k8s.io/name=dynamo-pd,rolebasedgroup.workloads.x-k8s.io/role=prefill
      Events:
        Type    Reason           Age   From                          Message
        ----    ------           ----  ----                          -------
        Normal  SuccessfulBound  25s   RoleBasedGroupScalingAdapter  rbg [dynamo-pd] のスケールターゲットロール [prefill] の検出に成功しました

ステップ 2: メッセージキューをモニターする KEDA ScaledObject を作成する

ScaledObject リソースを作成し、スケーリングルールを定義して、前のステップで作成した RoleBasedGroupScalingAdapter に関連付けることができます。

  1. 次の内容で scaledobject.yaml ファイルを作成できます。この構成では、dynamo-pd-prefill ScalingAdapter をスケールターゲットとして指定し、NATS メッセージキューのバックログに基づいてトリガーを設定します。

    以下のスケーリングポリシーのパラメーター設定は、デモ目的のみです。ビジネスシナリオに基づいて構成を調整する必要があります。
    apiVersion: keda.sh/v1alpha1
    kind: ScaledObject
    metadata:
      name: dynamo-prefill-scaledobject
    spec:
      pollingInterval: 30 # デモ目的。デフォルト: 30 秒。
      minReplicaCount: 1 # デモ目的。デフォルト: 0。
      maxReplicaCount: 6 # デモ目的。デフォルト: 100。
      scaleTargetRef:
        apiVersion: workloads.x-k8s.io/v1alpha1
        kind: RoleBasedGroupScalingAdapter
        name: dynamo-pd-prefill # RoleBasedGroup の Prefill ロールをスケールターゲットとして指定します。
      triggers:
      - type: nats-jetstream
        metadata:
          natsServerMonitoringEndpoint: "nats.default.svc.cluster.local:8222" # NATS サービスエンドポイント。
          account: "$G" # Nats アカウントが設定されていない場合のデフォルト値。
          stream: "dynamo_prefill_queue" # Dynamo の PrefillQueue の名前。
          consumer: "worker-group" # Dynamo のコンシューマーの永続化名。
          lagThreshold: "5" # 指定された Nats キュー内の保留メッセージ数に対するスケーリングのしきい値。
          useHttps: "false" # HTTPS プロトコルを使用するかどうかを指定します。
  2. 次のコマンドを実行してリソースを作成できます。

    kubectl apply -f ./scaledobject.yaml
  3. 次のコマンドを実行して、KEDA ScaledObject リソースのステータスを確認できます。

    kubectl describe so dynamo-prefill-scaledobject

    出力例を展開して表示します。

    Name:         dynamo-prefill-scaledobject
    Namespace:    default
    Labels:       scaledobject.keda.sh/name=dynamo-prefill-scaledobject
    Annotations:  <none>
    API Version:  keda.sh/v1alpha1
    Kind:         ScaledObject
    Metadata:
      ...
    Spec:
      Cooldown Period:    300
      Max Replica Count:  6
      Min Replica Count:  1
      Polling Interval:   30
      Scale Target Ref:
        API Version:  workloads.x-k8s.io/v1alpha1
        Kind:         RoleBasedGroupScalingAdapter
        Name:         dynamo-pd-prefill
      Triggers:
        Metadata:
          Account:                          $G
          Consumer:                         worker-group
          Lag Threshold:                    5
          Nats Server Monitoring Endpoint:  nats.default.svc.cluster.local:8222
          Stream:                           dynamo_prefill_queue
          Use Https:                        false
        Type:                               nats-jetstream
    Status:
      Conditions:
        Message:  ScaledObject は正しく定義されており、スケーリングの準備ができています
        Reason:   ScaledObjectReady
        Status:   True
        Type:     Ready
        Message:  トリガーがアクティブでないため、スケーリングは実行されません
        Reason:   ScalerNotActive
        Status:   False
        Type:     Active
        Status:   Unknown
        Type:     Fallback
      External Metric Names:
        s0-nats-jetstream-dynamo_prefill_queue
      Hpa Name:                keda-hpa-dynamo-prefill-scaledobject
      Original Replica Count:  1
      Scale Target GVKR:
        Group:            workloads.x-k8s.io
        Kind:             RoleBasedGroupScalingAdapter
        Resource:         rolebasedgroupscalingadapters
        Version:          v1alpha1
      Scale Target Kind:  workloads.x-k8s.io/v1alpha1.RoleBasedGroupScalingAdapter
    Events:
      Type    Reason              Age   From           Message
      ----    ------              ----  ----           -------
      Normal  KEDAScalersStarted  3s    keda-operator  スケーラーの監視を開始しました
      Normal  ScaledObjectReady   3s    keda-operator  ScaledObject はスケーリングの準備ができています

    サンプル出力では、Status.ConditionsReady ステータスが True と表示されます。

    KEDA は Horizontal Pod Autoscaler (HPA) リソースも自動的に作成します。HPA 名は Status.HpaName フィールドに記録されます。次のコマンドを実行して HPA を表示できます。

    kubectl get hpa keda-hpa-dynamo-prefill-scaledobject

ステップ 3: (オプション) ストレステストとスケーリング効果の検証

  1. ストレステスト用のサービスインスタンスを作成し、ベンチマークツールを使用してサービスをテストできます。

    ベンチマークツールとその使用方法の詳細については、「vLLM Benchmark」をご参照ください。
    1. benchmark.yaml ファイルを作成できます。

      サンプル YAML コードを展開して表示します。

      apiVersion: apps/v1
      kind: StatefulSet
      metadata:
        labels:
          app: llm-benchmark
        name: llm-benchmark
      spec:
        selector:
          matchLabels:
            app: llm-benchmark
        template:
          metadata:
            labels:
              app: llm-benchmark
          spec:
            hostNetwork: true
            dnsPolicy: ClusterFirstWithHostNet
            containers:
            - command:
              - sh
              - -c
              - sleep inf
              image: # 推論サービスのデプロイに使用される Dynamo コンテナイメージ。
              imagePullPolicy: IfNotPresent
              name: llm-benchmark
              resources:
                limits:
                  cpu: "8"
                  memory: 40Gi
                requests:
                  cpu: "8"
                  memory: 40Gi
              volumeMounts:
              - mountPath: /models/Qwen3-32B
                name: llm-model
            volumes:
            - name: llm-model
              persistentVolumeClaim:
                claimName: llm-model
    2. 次のコマンドを実行して、ストレステスト用のサービスインスタンスを作成できます。

      kubectl create -f benchmark.yaml
    3. インスタンスが実行されたら、インスタンスで次のコマンドを実行してストレステストを実行できます。

      python3 $VLLM_ROOT_DIR/benchmarks/benchmark_serving.py \
              --backend openai-chat \
              --model /models/Qwen3-32B/ \
              --served-model-name qwen \
              --trust-remote-code \
              --dataset-name random \
              --random-input-len 1500 \
              --random-output-len 100 \
              --num-prompts 320 \
              --max-concurrency 32 \
              --host dynamo-service \
              --port 8000 \
              --endpoint /v1/chat/completions 
  2. ストレステスト中に、新しいターミナルを開いて次のコマンドを実行し、HPA スケーリングイベントを監視できます。

    kubectl describe hpa keda-hpa-dynamo-prefill-scaledobject

    出力例では、Events フィールドに SuccessfulRescale イベントが記録されています。これは、KEDA が NATS キューのバックログに基づいてスケールアウトを正常にトリガーしたことを示します。

    Name:                               keda-hpa-dynamo-prefill-scaledobject
    Namespace:                          default
    Reference:                          RoleBasedGroupScalingAdapter/dynamo-pd-prefill
    Min replicas:                       1
    Max replicas:                       6
    RoleBasedGroupScalingAdapter pods:  6 current / 6 desired
    Events:
      Type     Reason             Age                   From                       Message
      ----     ------             ----                  ----                       -------
      Normal  SuccessfulRescale  2m1s  horizontal-pod-autoscaler  新しいサイズ: 4; 理由: 外部メトリック s0-nats-jetstream-dynamo_prefill_queue(&LabelSelector{MatchLabels:map[string]string{scaledobject.keda.sh/name: dynamo-prefill-scaledobject,},MatchExpressions:[]LabelSelectorRequirement{},}) がターゲットを超えました
      Normal  SuccessfulRescale  106s  horizontal-pod-autoscaler  新しいサイズ: 6; 理由: 外部メトリック s0-nats-jetstream-dynamo_prefill_queue(&LabelSelector{MatchLabels:map[string]string{scaledobject.keda.sh/name: dynamo-prefill-scaledobject,},MatchExpressions:[]LabelSelectorRequirement{},}) がターゲットを超えました
  3. 同時に、RoleBasedGroupScalingAdapter のレプリカ数の変化を監視できます。

    kubectl describe rolebasedgroupscalingadapter dynamo-pd-prefill

    出力例では、Spec.ReplicasStatus.Replicas の値が初期値からスケールアウトされた値 (たとえば 6) に増加します。

    Name:         dynamo-pd-prefill
    Namespace:    default
    API Version:  workloads.x-k8s.io/v1alpha1
    Kind:         RoleBasedGroupScalingAdapter
    Metadata:
      Owner References:
        API Version:           workloads.x-k8s.io/v1alpha1
        Block Owner Deletion:  true
        Kind:                  RoleBasedGroup
        Name:                  dynamo-pd
    Spec:
      Replicas:  6
      Scale Target Ref:
        Name:  dynamo-pd
        Role:  prefill
    Status:
      Last Scale Time:  2025-08-04T02:08:10Z
      Phase:            Bound
      Replicas:         6
      Selector:         rolebasedgroup.workloads.x-k8s.io/name=dynamo-pd,rolebasedgroup.workloads.x-k8s.io/role=prefill
    Events:
      Type    Reason           Age    From                          Message
      ----    ------           ----   ----                          -------
      Normal  SuccessfulBound  6m9s   RoleBasedGroupScalingAdapter  rbg [dynamo-pd] のスケールターゲットロール [prefill] の検出に成功しました
      Normal  SuccessfulScale  4m40s  RoleBasedGroupScalingAdapter  rbg [dynamo-pd] のスケールターゲットロール [prefill] のレプリカを 1 から 4 にスケールすることに成功しました
      Normal  SuccessfulScale  4m25s  RoleBasedGroupScalingAdapter  rbg [dynamo-pd] のスケールターゲットロール [prefill] のレプリカを 4 から 6 にスケールすることに成功しました