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

Alibaba Cloud Service Mesh:リクエストキュー、KVCache、および LoRA を認識したインテリジェントルーティングとトラフィック管理

最終更新日:Jun 10, 2025

このトピックでは、Kubernetes クラスタにデプロイされた LLM 推論サービスの負荷分散とトラフィック管理を強化するためにサービスメッシュ(ASM)を使用する方法について説明します。 LLM 推論トラフィックとワークロードの固有の特性により、従来の負荷分散方式では不十分な場合があります。このトピックでは、パフォーマンスを向上させ、推論トラフィックを把握するために、vLLM 推論サービスのサービスプールとルーティングを定義する手順を説明します。

ヒントを読む

始める前に、以下について理解していることを確認してください。

このトピックを読むことで、以下について学ぶことができます。

  • これらのスタックは、新しいリリースで定期的に更新されます。独自のカスタム ランタイム スタックを作成することもできます。

  • 従来の方法を使用してクラスター内の LLM 推論サービスを管理する際の課題。

  • ASM を使用してクラスター内の LLM 推論サービスを管理するための概念と実践的なステップ。

背景

大規模言語モデル (LLM)

大規模言語モデル (LLM) は、数十億のパラメーターを持つニューラルネットワークベースの言語モデルであり、GPT、Qwen、Llama などが代表的な例です。これらのモデルは、Webテキスト、専門文献、コードなど、多様で大規模な事前トレーニング データセットでトレーニングされ、主に補完や対話などのテキスト生成タスクに使用されます。

LLM を活用してアプリケーションを構築するには、次の方法があります。

  • OpenAI、Alibaba Cloud Model Studio、Moonshot などのプラットフォームから外部 LLM API サービスを利用する。

  • vLLM などのオープンソースまたは独自のモデルとフレームワークを使用して独自の LLM 推論サービスを構築し、Kubernetes クラスターにデプロイする。このアプローチは、推論サービスの制御や LLM 推論機能の高いカスタマイズが必要なシナリオに適しています。

vLLM

vLLM は、LLM 推論サービスを効率的かつユーザーフレンドリーに構築するために設計されたフレームワークです。 Qwen を含むさまざまな大規模言語モデルをサポートし、PagedAttention、動的バッチ推論 (Continuous Batching)、モデル量子化などの手法によって LLM 推論効率を最適化します。

背景

ASM はクラスター内の LLM 推論サービストラフィックの管理を容易にします。LLM 推論サービスをデプロイする際、InferencePool と InferenceModel のカスタムリソース定義 (CRD) を通じて、サービスを提供するワークロードとモデル名を宣言できます。その後、ASM は LLM 推論バックエンドを対象とする LLM 推論サービスに対して、負荷分散、トラフィックルーティング、および可観測性を提供します。

重要

現在、vLLM に基づく LLM 推論サービスのみがサポートされています。

従来の負荷分散

従来の負荷分散アルゴリズムは、HTTP リクエストをさまざまなワークロードに均等に分散します。ただし、LLM 推論サービスでは、各リクエストがバックエンドに課す負荷は予測できません。

推論プロセスは、プリフィルとデコードの 2 つのフェーズで構成されます。

  • プリフィルステージ: ユーザーの入力をエンコードします。

  • デコードステージ: 複数のステップが含まれ、各ステップで前の入力をデコードし、新しいトークン (LLM データ処理の基本単位) を生成します。各リクエストが生成するトークンの数は事前に不明であるため、リクエストを均等に分散すると、実際のワークロードが不均一になり、負荷の不均衡が生じる可能性があります。

LLM 負荷分散

ASM は、LLM バックエンド向けに調整された負荷分散アルゴリズムを提供します。これは、多次元メトリックを使用して推論サーバーの内部状態を評価し、複数のサーバーにワークロードを分散します。主なメトリックは次のとおりです。

  • リクエストキューの長さ: モデルサーバーのキューにある保留中のリクエストの数を示し、vllm: num_requests_waiting メトリックから導出されます。キューが短いほど、リクエストがタイムリーに処理される可能性が高くなります。

  • GPU キャッシュ使用率: 中間推論結果を格納するためにサーバーが使用するキャッシュ (KV キャッシュ) の割合を反映します。vllm: gpu_cache_usage_perc メトリックによって測定され、使用率が低いほど、新しいリクエストに使用できる GPU リソースが増えます。

このアプローチは、推論サービス全体で一貫した GPU 負荷を確保し、LLM リクエストの最初のトークンの応答レイテンシ (ttft) を削減し、スループットを向上させることで、従来のアルゴリズムよりも優れたパフォーマンスを発揮します。

従来の可観測性

LLM 推論サービスは通常、OpenAI のリクエスト API 形式を使用して対話します。モデル名や最大トークン数など、ほとんどのリクエストメタデータはリクエスト本文にあります。リクエストヘッダーとパスに基づく従来のルーティング機能と可観測性機能は、リクエスト本文を解析しないため、リクエストモデル名またはトークン数に基づくトラフィック割り当てまたは観測に対応できません。

推論トラフィックの可観測性

ASM は、リクエストのアクセスログと監視メトリックにおける LLM 推論機能を強化します。

  • アクセスログに、リクエストのモデル名、入力トークン数、出力トークン数のフィールドを含めることができるようになりました。

  • 監視メトリックに、モデル名のカスタムディメンションが追加されました。

  • ワークロードのトークン消費を監視するための新しいメトリックが利用可能になりました。

前提条件

  • GPU ノードプールを持つ ACK マネージドクラスターを作成したか、GPU 計算能力を持つ推奨ゾーンで ACS クラスターを選択したことを確認します。詳細については、「ACK マネージドクラスターの作成」および「ACS クラスターを作成する」をご参照ください。

    ACK マネージドクラスターに ACK Virtual Node コンポーネントをインストールして、ACS GPU 計算機能を利用できます。詳細については、「ACK での ACS GPU 計算能力」をご参照ください。

  • v1.24 以降の ASM インスタンスにクラスターが追加されています。詳細については、「ASM インスタンスにクラスターを追加する」をご参照ください。

  • イングレスゲートウェイが作成され、ポート 8080 で HTTP サービスが有効になっています。詳細については、「イングレスゲートウェイを作成する」をご参照ください。

  • (オプション) Sidecar がデフォルトの名前空間に挿入されています。詳細については、「Sidecar プロキシの自動挿入を有効にする」をご参照ください。

    説明

    可観測性の実際の操作を確認する必要がない場合は、Sidecar の挿入をスキップできます。

ベストプラクティス

次の例は、vLLM に基づいて Llama2 large モデルをデプロイすることで、ASM を使用してクラスタ内の LLM 推論サービストラフィックを管理する方法を示しています。

手順 1:サンプル推論サービスをデプロイする

  1. 提供されたコンテンツを使用して、vllm-service.yaml という名前のファイルを作成します。

    説明

    このトピックで説明されているイメージには、16 GiB を超えるビデオメモリを搭載した GPU が必要です。 16 GiB のビデオメモリを搭載した T4 カードタイプでは、このアプリケーションを起動するための十分なリソースが提供されません。 ACK クラスタには A10 カードタイプ、ACS クラスタには第 8 世代 GPU B を使用することをお勧めします。モデルの詳細については、submit a ticket を送信して、サポートを受けてください。

    LLM イメージのサイズが大きいため、ACR に事前に保存し、プルに内部ネットワークアドレスを使用することをお勧めします。パブリックネットワークから直接プルすると、クラスタ EIP 帯域幅の構成によっては、待機時間が長くなる可能性があります。

    ACS クラスタ

    apiVersion: v1
    kind: Service
    metadata:
      name: vllm-llama2-7b-pool
    spec:
      selector:
        app: vllm-llama2-7b-pool
      ports:
      - protocol: TCP
        port: 8000
        targetPort: 8000
      type: ClusterIP
    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: chat-template
    data:
      llama-2-chat.jinja: |
        {% if messages[0]['role'] == 'system' %}
          {% set system_message = '<<SYS>>\n' + messages[0]['content'] | trim + '\n<</SYS>>\n\n' %}  // メッセージがシステムメッセージの場合、システムメッセージ変数を設定します
          {% set messages = messages[1:] %} // システムメッセージをメッセージリストから削除します
        {% else %}
            {% set system_message = '' %} // メッセージがシステムメッセージでない場合、システムメッセージ変数を空に設定します
        {% endif %}
    
        {% for message in messages %} // メッセージリストを反復処理します
            {% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %} // ユーザーとアシスタントのロールが交互になっていることを確認します
                {{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }} // ロールが交互になっていない場合は、例外を発生させます
            {% endif %}
    
            {% if loop.index0 == 0 %} // 最初のメッセージの場合
                {% set content = system_message + message['content'] %} // システムメッセージをコンテンツに追加します
            {% else %}
                {% set content = message['content'] %} // それ以外の場合は、メッセージコンテンツをコンテンツに設定します
            {% endif %}
            {% if message['role'] == 'user' %} // メッセージがユーザーメッセージの場合
                {{ bos_token + '[INST] ' + content | trim + ' [/INST]' }} // BOS トークンと [INST] タグを追加します
            {% elif message['role'] == 'assistant' %} // メッセージがアシスタントメッセージの場合
                {{ ' ' + content | trim + ' ' + eos_token }} // EOS トークンを追加します
            {% endif %}
        {% endfor %}
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: vllm-llama2-7b-pool
      namespace: default
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: vllm-llama2-7b-pool
      template:
        metadata:
          annotations:
            prometheus.io/path: /metrics
            prometheus.io/port: '8000'
            prometheus.io/scrape: 'true'
          labels:
            app: vllm-llama2-7b-pool
            alibabacloud.com/compute-class: gpu  # GPU 計算能力を指定します
            alibabacloud.com/compute-qos: default
            alibabacloud.com/gpu-model-series: "example-model" # GPU モデルを example-model として指定します。実際の状況に応じて入力してください。
        spec:
          containers:
            - name: lora
              image: "registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/llama2-with-lora:v0.2"
              imagePullPolicy: IfNotPresent
              command: ["python3", "-m", "vllm.entrypoints.openai.api_server"]
              args:
              - "--model"
              - "/model/llama2"
              - "--tensor-parallel-size"
              - "1"
              - "--port"
              - "8000"
              - '--gpu_memory_utilization'
              - '0.8'
              - "--enable-lora"
              - "--max-loras"
              - "4"
              - "--max-cpu-loras"
              - "12"
              - "--lora-modules"
              - 'sql-lora=/adapters/yard1/llama-2-7b-sql-lora-test_0'
              - 'sql-lora-1=/adapters/yard1/llama-2-7b-sql-lora-test_1'
              - 'sql-lora-2=/adapters/yard1/llama-2-7b-sql-lora-test_2'
              - 'sql-lora-3=/adapters/yard1/llama-2-7b-sql-lora-test_3'
              - 'sql-lora-4=/adapters/yard1/llama-2-7b-sql-lora-test_4'
              - 'tweet-summary=/adapters/vineetsharma/qlora-adapter-Llama-2-7b-hf-TweetSumm_0'
              - 'tweet-summary-1=/adapters/vineetsharma/qlora-adapter-Llama-2-7b-hf-TweetSumm_1'
              - 'tweet-summary-2=/adapters/vineetsharma/qlora-adapter-Llama-2-7b-hf-TweetSumm_2'
              - 'tweet-summary-3=/adapters/vineetsharma/qlora-adapter-Llama-2-7b-hf-TweetSumm_3'
              - 'tweet-summary-4=/adapters/vineetsharma/qlora-adapter-Llama-2-7b-hf-TweetSumm_4'
              - '--chat-template'
              - '/etc/vllm/llama-2-chat.jinja'
              env:
                - name: PORT
                  value: "8000"
              ports:
                - containerPort: 8000
                  name: http
                  protocol: TCP
              livenessProbe:  // liveness プローブの設定
                failureThreshold: 2400
                httpGet:
                  path: /health
                  port: http
                  scheme: HTTP
                initialDelaySeconds: 5
                periodSeconds: 5
                successThreshold: 1
                timeoutSeconds: 1
              readinessProbe: // readiness プローブの設定
                failureThreshold: 6000
                httpGet:
                  path: /health
                  port: http
                  scheme: HTTP
                initialDelaySeconds: 5
                periodSeconds: 5
                successThreshold: 1
                timeoutSeconds: 1
              resources: // リソース制限とリクエストの設定
                limits:
                  cpu: 16
                  memory: 64Gi
                  nvidia.com/gpu: 1
                requests:
                  cpu: 8
                  memory: 30Gi
                  nvidia.com/gpu: 1
              volumeMounts: // ボリュームマウントの設定
                - mountPath: /data
                  name: data
                - mountPath: /dev/shm
                  name: shm
                - mountPath: /etc/vllm
                  name: chat-template
          restartPolicy: Always
          schedulerName: default-scheduler
          terminationGracePeriodSeconds: 30
          volumes: // ボリュームの設定
            - name: data
              emptyDir: {}
            - name: shm
              emptyDir:
                medium: Memory
            - name: chat-template
              configMap:
                name: chat-template

    ACK クラスタ

    apiVersion: v1
    kind: Service
    metadata:
      name: vllm-llama2-7b-pool
    spec:
      selector:
        app: vllm-llama2-7b-pool
      ports:
      - protocol: TCP
        port: 8000
        targetPort: 8000
      type: ClusterIP
    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: chat-template
    data:
      llama-2-chat.jinja: |
        {% if messages[0]['role'] == 'system' %}
          {% set system_message = '<<SYS>>\n' + messages[0]['content'] | trim + '\n<</SYS>>\n\n' %} // メッセージがシステムメッセージの場合、システムメッセージ変数を設定します
          {% set messages = messages[1:] %} // システムメッセージをメッセージリストから削除します
        {% else %}
            {% set system_message = '' %} // メッセージがシステムメッセージでない場合、システムメッセージ変数を空に設定します
        {% endif %}
    
        {% for message in messages %} // メッセージリストを反復処理します
            {% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %} // ユーザーとアシスタントのロールが交互になっていることを確認します
                {{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }} // ロールが交互になっていない場合は、例外を発生させます
            {% endif %}
    
            {% if loop.index0 == 0 %} // 最初のメッセージの場合
                {% set content = system_message + message['content'] %} // システムメッセージをコンテンツに追加します
            {% else %}
                {% set content = message['content'] %} // それ以外の場合は、メッセージコンテンツをコンテンツに設定します
            {% endif %}
            {% if message['role'] == 'user' %} // メッセージがユーザーメッセージの場合
                {{ bos_token + '[INST] ' + content | trim + ' [/INST]' }} // BOS トークンと [INST] タグを追加します
            {% elif message['role'] == 'assistant' %} // メッセージがアシスタントメッセージの場合
                {{ ' ' + content | trim + ' ' + eos_token }} // EOS トークンを追加します
            {% endif %}
        {% endfor %}
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: vllm-llama2-7b-pool
      namespace: default
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: vllm-llama2-7b-pool
      template:
        metadata:
          annotations:
            prometheus.io/path: /metrics
            prometheus.io/port: '8000'
            prometheus.io/scrape: 'true'
          labels:
            app: vllm-llama2-7b-pool
        spec:
          containers:
            - name: lora
              image: "registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/llama2-with-lora:v0.2"
              imagePullPolicy: IfNotPresent
              command: ["python3", "-m", "vllm.entrypoints.openai.api_server"]
              args:
              - "--model"
              - "/model/llama2"
              - "--tensor-parallel-size"
              - "1"
              - "--port"
              - "8000"
              - '--gpu_memory_utilization'
              - '0.8'
              - "--enable-lora"
              - "--max-loras"
              - "4"
              - "--max-cpu-loras"
              - "12"
              - "--lora-modules"
              - 'sql-lora=/adapters/yard1/llama-2-7b-sql-lora-test_0'
              - 'sql-lora-1=/adapters/yard1/llama-2-7b-sql-lora-test_1'
              - 'sql-lora-2=/adapters/yard1/llama-2-7b-sql-lora-test_2'
              - 'sql-lora-3=/adapters/yard1/llama-2-7b-sql-lora-test_3'
              - 'sql-lora-4=/adapters/yard1/llama-2-7b-sql-lora-test_4'
              - 'tweet-summary=/adapters/vineetsharma/qlora-adapter-Llama-2-7b-hf-TweetSumm_0'
              - 'tweet-summary-1=/adapters/vineetsharma/qlora-adapter-Llama-2-7b-hf-TweetSumm_1'
              - 'tweet-summary-2=/adapters/vineetsharma/qlora-adapter-Llama-2-7b-hf-TweetSumm_2'
              - 'tweet-summary-3=/adapters/vineetsharma/qlora-adapter-Llama-2-7b-hf-TweetSumm_3'
              - 'tweet-summary-4=/adapters/vineetsharma/qlora-adapter-Llama-2-7b-hf-TweetSumm_4'
              - '--chat-template'
              - '/etc/vllm/llama-2-chat.jinja'
              env:
                - name: PORT
                  value: "8000"
              ports:
                - containerPort: 8000
                  name: http
                  protocol: TCP
              livenessProbe: // liveness プローブの設定
                failureThreshold: 2400
                httpGet:
                  path: /health
                  port: http
                  scheme: HTTP
                initialDelaySeconds: 5
                periodSeconds: 5
                successThreshold: 1
                timeoutSeconds: 1
              readinessProbe: // readiness プローブの設定
                failureThreshold: 6000
                httpGet:
                  path: /health
                  port: http
                  scheme: HTTP
                initialDelaySeconds: 5
                periodSeconds: 5
                successThreshold: 1
                timeoutSeconds: 1
              resources: // リソース制限とリクエストの設定
                limits:
                  nvidia.com/gpu: 1
                requests:
                  nvidia.com/gpu: 1
              volumeMounts: // ボリュームマウントの設定
                - mountPath: /data
                  name: data
                - mountPath: /dev/shm
                  name: shm
                - mountPath: /etc/vllm
                  name: chat-template
          restartPolicy: Always
          schedulerName: default-scheduler
          terminationGracePeriodSeconds: 30
          volumes: // ボリュームの設定
            - name: data
              emptyDir: {}
            - name: shm
              emptyDir:
                medium: Memory
            - name: chat-template
              configMap:
                name: chat-template
  2. データプレーン上のクラスタの kubeconfig ファイルを使用して、LLM 推論サービスをデプロイします。

    kubectl apply -f vllm-service.yaml

手順 2:ASM ゲートウェイルールを構成する

ASM ゲートウェイでポート 8080 のリスニングを有効にするゲートウェイルールをデプロイします。

  1. 次の内容で gateway.yaml という名前のファイルを作成します。

    apiVersion: networking.istio.io/v1
    kind: Gateway
    metadata:
      name: llm-inference-gateway
      namespace: default
    spec:
      selector:
        istio: ingressgateway
      servers:
        - hosts:
            - '*'
          port:
            name: http-service
            number: 8080
            protocol: HTTP
  2. ゲートウェイルールを作成します。

    kubectl apply -f gateway.yaml

手順 3:LLM 推論サービスのルーティングと負荷分散を構成する

説明

従来の負荷分散と LLM 負荷分散のパフォーマンスを比較するには、「(オプション) 可観測性ダッシュボードを使用して従来の負荷分散とパフォーマンスを比較する」の手順を完了してから、さらに操作を進めてください。

  1. ASM の kubeconfig ファイルを使用して、LLM 推論サービスのルーティングを有効にします。

    kubectl patch asmmeshconfig default --type=merge --patch='{"spec":{"gatewayAPIInferenceExtension":{"enabled":true}}}'
  2. InferencePool リソースをデプロイします。

    InferencePool リソースは、ラベルセレクターを使用して、クラスタ内の一連の LLM 推論サービスのワークロードを定義します。 ASM は、作成された InferencePool に基づいて vLLM 負荷分散を適用します。

    1. 次の内容で inferencepool.yaml という名前のファイルを作成します。

      apiVersion: inference.networking.x-k8s.io/v1alpha1
      kind: InferencePool
      metadata:
        name: vllm-llama2-7b-pool
      spec:
        targetPortNumber: 8000
        selector:
          app: vllm-llama2-7b-pool

      次の表は、いくつかの設定項目について説明しています。

      設定項目

      説明

      .spec.targetPortNumber

      推論サービスを提供する Pod によって公開されるポート。

      .spec.selector

      推論サービスを提供する Pod ラベル。ラベルキーは app でなければならず、値は対応するサービス名でなければなりません。

    2. データプレーン上のクラスタの kubeconfig ファイルを使用して、InferencePool リソースを作成します。

      kubectl apply -f inferencepool.yaml
  3. InferenceModel リソースをデプロイします。

    InferenceModel は、InferencePool リソース内の特定のモデルのトラフィック分散ポリシーを指定します。

    1. 次の内容を使用して inferencemodel.yaml という名前のファイルを作成します。

      apiVersion: inference.networking.x-k8s.io/v1alpha1
      kind: InferenceModel
      metadata:
        name: inferencemodel-sample
      spec:
        modelName: tweet-summary
        poolRef:
          group: inference.networking.x-k8s.io
          kind: InferencePool
          name: vllm-llama2-7b-pool
        targetModels:
        - name: tweet-summary
          weight: 100

      次の表は、いくつかの設定項目について説明しています。

      設定項目

      説明

      .spec.modelName

      リクエスト内の model パラメーターと照合するために使用されます。

      .spec.targetModels

      トラフィックルーティングルールを構成します。上記の例では、リクエストヘッダーに model: tweet-summary が含まれるトラフィックは 100%、tweet-summary モデルを実行している Pod に送信されます。

    2. InferenceModel リソースを作成します。

      kubectl apply -f inferencemodel.yaml
  4. LLMRoute リソースを作成します。

    InferencePool リソースを参照することで、ポート 8080 で受信したすべてのリクエストをサンプル LLM 推論サービスに転送するようにゲートウェイのルーティングルールを設定します。

    1. 次の内容で llmroute.yaml という名前のファイルを作成します。

      apiVersion: istio.alibabacloud.com/v1
      kind: LLMRoute
      metadata:  
        name: test-llm-route
      spec:
        gateways: 
        - llm-inference-gateway
        host: test.com
        rules:
        - backendRefs:
          - backendRef:
              group: inference.networking.x-k8s.io
              kind: InferencePool
              name: vllm-llama2-7b-pool
    2. LLMRoute リソースをデプロイします。

      kubectl apply -f llmroute.yaml

手順 4:確認する

次のコマンドを複数回実行してテストを実行します。

curl -H "host: test.com" ${ASM Gateway IP}:8080/v1/completions -H 'Content-Type: application/json' -d '{
"model": "tweet-summary",
"prompt": "Write as if you were a critic: San Francisco",
"max_tokens": 100,
"temperature": 0
}' -v

予期される出力:

{"id":"cmpl-2fc9a351-d866-422b-b561-874a30843a6b","object":"text_completion","created":1736933141,"model":"tweet-summary","choices":[{"index":0,"text":", I'm a newbie to this forum. Write a summary of the article.\nWrite a summary of the article.\nWrite a summary of the article. Write a summary of the article. Write a summary of the article. Write a summary of the article. Write a summary of the article. Write a summary of the article. Write a summary of the article. Write a summary of the article. Write a summary of the article. Write a summary","logprobs":null,"finish_reason":"length","stop_reason":null,"prompt_logprobs":null}],"usage":{"prompt_tokens":2,"total_tokens":102,"completion_tokens":100,"prompt_tokens_details":null}}

(オプション) 手順 5:LLM サービスの可観測性メトリックとダッシュボードを構成する

InferencePool リソースと InferenceMode リソースを使用してクラスタ内の LLM 推論サービスを宣言し、ルーティングポリシーを設定した後、ログとモニタリングメトリックを通じて LLM 推論サービスの可観測性を表示できます。

  1. ASM コンソールで LLM トラフィック可観測性機能を有効にして、モニタリングメトリックを収集します。

    1. 追加のログフィールド、メトリック、およびメトリックディメンションを組み込むことで、LLM 推論リクエストの可観測性を向上させます。詳細な構成手順については、「トラフィック観測:ASM を使用した LLM トラフィックの効率的な管理」をご参照ください。

    2. 構成が完了すると、model ディメンションが ASM モニタリングメトリックに組み込まれます。これらのメトリックは、可観測性モニタリングフレームワーク内で Prometheus を使用するか、サービスメッシュモニタリング用にセルフホスト型 Prometheus を統合することで収集できます。

    3. ASM は、すべての要求の入力トークン数を表す asm_llm_proxy_prompt_tokens と出力トークン数を表す asm_llm_proxy_completion_tokens という 2 つの新しいメトリックを導入しています。これらのメトリックは、Prometheus に次のルールを追加することで組み込むことができます。詳細については、「その他の Prometheus サービスディスカバリ構成」をご参照ください。

      scrape_configs:
      - job_name: asm-envoy-stats-llm // ジョブ名
        scrape_interval: 30s // スクレイピング間隔
        scrape_timeout: 30s // スクレイピングタイムアウト
        metrics_path: /stats/prometheus // メトリックパス
        scheme: http // スキーム
        kubernetes_sd_configs:
        - role: pod // Kubernetes サービスディスカバリのロール
        relabel_configs:
        - source_labels:
          - __meta_kubernetes_pod_container_port_name
          action: keep
          regex: .*-envoy-prom
        - source_labels:
          - __address__
          - __meta_kubernetes_pod_annotation_prometheus_io_port
          action: replace
          regex: ([^:]+)(?::\d+)?;(\d+)
          replacement: $1:15090
          target_label: __address__
        - action: labelmap
          regex: __meta_kubernetes_pod_label_(.+)
        - source_labels:
          - __meta_kubernetes_namespace
          action: replace
          target_label: namespace
        - source_labels:
          - __meta_kubernetes_pod_name
          action: replace
          target_label: pod_name
        metric_relabel_configs:
        - action: keep
          source_labels:
          - __name__
          regex: asm_llm_.*
  2. vLLM サービスのモニタリングメトリックを収集します。

    1. LLM 推論リクエストのモニタリングメトリックには、主に外部 LLM 推論リクエストのスループットが含まれます。 vLLM サービス pod に Prometheus コレクターのアノテーションを追加して、vLLM サービスによって公開されたメトリックを収集し、その内部状態を監視します。

      ...
      annotations:
        prometheus.io/path: /metrics # メトリックが公開される HTTP パス。
        prometheus.io/port: "8000" # メトリックが公開されるポート。vLLM サーバーのリスニングポートです。
        prometheus.io/scrape: "true" # 現在の pod のメトリックをスクレイピングするかどうか。
      ...
    2. Prometheus のデフォルトのサービスディスカバリメカニズムを使用して、vLLM サービスに関連するメトリックを取得します。詳細な手順については、「デフォルトのサービスディスカバリ」をご参照ください。

      vLLM サービスからの主要なメトリックは、vLLM ワークロードの内部状態に関する洞察を提供します。

      メトリック名

      説明

      vllm:gpu_cache_usage_perc

      vLLM の GPU キャッシュ使用率です。vLLM が起動すると、KV キャッシュのために可能な限り多くの GPU ビデオメモリを事前に占有します。 vLLM サーバーの場合、キャッシュ使用率が低いほど、GPU が新しいリクエストにリソースを割り当てるためのスペースが増えます。

      vllm: リクエストキュー時間 (秒) の合計

      待機状態キューでの所要時間です。LLM 推論リクエストが vLLM サーバーに到着した後、すぐに処理されずに、vLLM スケジューラがプリフィルとデコードをスケジュールするのを待つ必要がある場合があります。

      vllm:num_requests_running

      vllm:num_requests_waiting

      vllm:num_requests_swapped

      推論を実行中、待機中、およびメモリにスワップされたリクエストの数です。 vLLM サービスの現在のリクエスト負荷を評価するために使用できます。

      vllm:avg_generation_throughput_toks_per_s

      vllm:avg_prompt_throughput_toks_per_s

      プリフィルステージで消費され、デコードステージで生成される 1 秒あたりのトークン数。

      vllm: トークン初回受信時間 (秒) バケット

      リクエストが vLLM サービスに送信されてから最初のトークンが応答されるまでの待機時間レベル。 このメトリックは通常、クライアントがリクエストコンテンツを出力してから最初の応答を取得するまでにかかる時間を表し、LLM ユーザーエクスペリエンスに影響を与える重要なメトリックです。

  3. Grafana ダッシュボードをデプロイして、LLM 推論サービスの可観測性を視覚化します。

    vLLM でデプロイされた LLM 推論サービスを Grafana ダッシュボードで監視します。

    • 開始するには、次の手順に従います。

    • vLLM モニタリング メトリックを使用して、LLM 推論サービスのワークロードの内部状態を評価します。

    Grafana コンソールでデータソース (Prometheus インスタンス) を作成できます。ASM とvLLM の監視メトリクスが Prometheus インスタンスによって収集されていることを確認してください。

    LLM 推論サービスの可観測性ダッシュボードを作成するには、以下に提供される内容を Grafana にインポートしてください。

    JSON コンテンツを表示するには展開します

    {
      "annotations": {
        "list": [
          {
            "builtIn": 1,
            "datasource": {
              "type": "grafana",
              "uid": "-- Grafana --"
            },
            "enable": true,
            "hide": true,
            "iconColor": "rgba(0, 211, 255, 1)",
            "name": "Annotations & Alerts", // アノテーションとアラート
            "target": {
              "limit": 100,
              "matchAny": false,
              "tags": [],
              "type": "dashboard"
            },
            "type": "dashboard"
          }
        ]
      },
      "description": "Monitoring vLLM Inference Server", // vLLM 推論サーバーの監視
      "editable": true,
      "fiscalYearStartMonth": 0,
      "graphTooltip": 0,
      "id": 49,
      "links": [],
      "liveNow": false,
      "panels": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${DS_PROMETHEUS}"
          },
          "description": "",
          "fieldConfig": {
            "defaults": {
              "color": {
                "mode": "palette-classic"
              },
              "custom": {
                "axisCenteredZero": false,
                "axisColorMode": "text",
                "axisLabel": "",
                "axisPlacement": "auto",
                "barAlignment": 0,
                "drawStyle": "line",
                "fillOpacity": 0,
                "gradientMode": "none",
                "hideFrom": {
                  "legend": false,
                  "tooltip": false,
                  "viz": false
                },
                "lineInterpolation": "linear", // 線形
                "lineWidth": 1,
                "pointSize": 5,
                "scaleDistribution": {
                  "type": "linear" // 線形
                },
                "showPoints": "auto",
                "spanNulls": false,
                "stacking": {
                  "group": "A",
                  "mode": "none"
                },
                "thresholdsStyle": {
                  "mode": "off"
                }
              },
              "mappings": [], // マッピング
              "thresholds": { // しきい値
                "mode": "absolute",
                "steps": [
                  {
                    "color": "green"
                  },
                  {
                    "color": "red",
                    "value": 80
                  }
                ]
              }
            },
            "overrides": [] // オーバーライド
          },
          "gridPos": {
            "h": 8,
            "w": 12,
            "x": 0,
            "y": 0
          },
          "id": 23,
          "options": {
            "legend": { // 凡例
              "calcs": [],
              "displayMode": "list", // リスト
              "placement": "bottom",
              "showLegend": true
            },
            "tooltip": {
              "mode": "single",
              "sort": "none"
            }
          },
          "targets": [
            {
              "datasource": {
                "type": "prometheus",
                "uid": "${DS_PROMETHEUS}"
              },
              "editorMode": "builder",
              "exemplar": false,
              "expr": "sum by(model) (rate(istio_requests_total{model!=\"unknown\"}[$__rate_interval]))", // model でグループ化された unknown 以外の istio_requests_total のレートの合計
              "instant": false,
              "interval": "",
              "legendFormat": "__auto",
              "range": true,
              "refId": "A"
            }
          ],
          "title": "Request Rate", // リクエストレート
          "type": "timeseries"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "prom-cec64713b1aab44d0b49236b6f54cd671"
          },
          "description": "",
          "fieldConfig": {
            "defaults": {
              "color": {
                "mode": "palette-classic"
              },
              "custom": {
                "axisCenteredZero": false,
                "axisColorMode": "text",
                "axisLabel": "",
                "axisPlacement": "auto",
                "barAlignment": 0,
                "drawStyle": "line", // 回線
                "fillOpacity": 0,
                "gradientMode": "none",
                "hideFrom": {
                  "legend": false, // 凡例
                  "tooltip": false,
                  "viz": false
                },
                "lineInterpolation": "linear", // 線形
                "lineWidth": 1,
                "pointSize": 5,
                "scaleDistribution": {
                  "type": "linear" // 線形
                },
                "showPoints": "auto",
                "spanNulls": false,
                "stacking": {
                  "group": "A",
                  "mode": "none"
                },
                "thresholdsStyle": { // しきい値
                  "mode": "off"
                }
              },
              "mappings": [], // マッピング
              "thresholds": { // しきい値
                "mode": "absolute",
                "steps": [
                  {
                    "color": "green"
                  },
                  {
                    "color": "red",
                    "value": 80
                  }
                ]
              }
            },
            "overrides": [] // オーバーライド
          },
          "gridPos": {
            "h": 8,
            "w": 12,
            "x": 12,
            "y": 0
          },
          "id": 20,
          "options": {
            "legend": { // 凡例
              "calcs": [],
              "displayMode": "list", // リスト
              "placement": "bottom",
              "showLegend": true
            },
            "tooltip": {
              "mode": "single",
              "sort": "none"
            }
          },
          "targets": [
            {
              "datasource": {
                "type": "prometheus",
                "uid": "prom-cec64713b1aab44d0b49236b6f54cd671"
              },
              "editorMode": "code",
              "expr": "sum by(llmproxy_model) (rate(asm_llm_proxy_completion_tokens{}[$__rate_interval]))", // llmproxy_model でグループ化された asm_llm_proxy_completion_tokens のレートの合計
              "instant": false,
              "legendFormat": "generate tokens (from proxy)", // プロキシからの生成トークン
              "range": true,
              "refId": "A"
            },
            {
              "datasource": {
                "type": "prometheus",
                "uid": "prom-cec64713b1aab44d0b49236b6f54cd671"
              },
              "editorMode": "code",
              "expr": "sum by(llmproxy_model) (rate(asm_llm_proxy_prompt_tokens{}[$__rate_interval]))", // llmproxy_model でグループ化された asm_llm_proxy_prompt_tokens のレートの合計
              "hide": false,
              "instant": false,
              "legendFormat": "prompt tokens (from proxy)", // プロキシからのプロンプトトークン
              "range": true,
              "refId": "B"
            }
          ],
          "title": "Tokens Rate", // トークンレート
          "type": "timeseries"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${DS_PROMETHEUS}"
          },
          "fieldConfig": {
            "defaults": {
              "color": {
                "mode": "thresholds" // しきい値
              },
              "mappings": [], // マッピング
              "min": -1,
              "thresholds": { // しきい値
                "mode": "absolute",
                "steps": [
                  {
                    "color": "green",
                    "value": null
                  },
                  {
                    "color": "red",
                    "value": 80
                  }
                ]
              },
              "unit": "percentunit"
            },
            "overrides": [] // オーバーライド
          },
          "gridPos": {
            "h": 8,
            "w": 12,
            "x": 0,
            "y": 8
          },
          "id": 17,
          "options": {
            "colorMode": "value", // 値
            "graphMode": "area", // エリア
            "justifyMode": "auto",
            "orientation": "auto",
            "reduceOptions": {
              "calcs": [
                "mean" // 平均
              ],
              "fields": "",
              "values": false
            },
            "textMode": "auto"
          },
          "pluginVersion": "10.0.9",
          "targets": [
            {
              "datasource": {
                "type": "prometheus",
                "uid": "${DS_PROMETHEUS}"
              },
              "editorMode": "builder",
              "expr": "avg(vllm:gpu_cache_usage_perc)", // vllm:gpu_cache_usage_perc の平均
              "hide": false,
              "instant": false,
              "range": true,
              "refId": "A"
            }
          ],
          "title": "Average gpu cache usage", // GPU キャッシュの平均使用率
          "type": "stat"
        },

    次のようなダッシュボードが表示されます。

    image

(オプション) 可観測性ダッシュボードを使用して従来の負荷分散とパフォーマンスを比較する

可観測性ダッシュボードを使用すると、LLM 推論サービスの負荷分散のパフォーマンスと、キャッシュ使用率、リクエスト キュー時間、トークン スループット、ttft などのメトリックを含む従来の負荷分散アルゴリズムを直接比較できます。

説明

手順 3 が完了したら、次のコマンドを実行して、作成したリソースを消去できます。

kubectl delete inferencemodel --all
kubectl delete inferencepool --all
kubectl delete llmroute --all

完了していない場合は、手順 1 と 2 を完了した後、手順 3 に進む前に、作成した仮想サービスをすべてクリアしてください。

  1. 次のコマンドを実行して、サンプル LLM 推論サービスにルーティングと従来の負荷分散を提供する仮想サービスを作成します。

    kubectl apply -f- <<EOF
    apiVersion: networking.istio.io/v1
    kind: VirtualService
    metadata:
      name: llm-vs
      namespace: default
    spec:
      gateways:
        - default/llm-inference-gateway
      hosts:
        - '*'
      http:
        - name: any-host
          route:
            - destination:
                host: vllm-llama2-7b-pool.default.svc.cluster.local
                port:
                  number: 8000
    EOF
  2. llmperf ツールを使用して、LLM 推論サービスのストレステストを実行します。

  3. Grafana ダッシュボードを使用して、2 つのルーティングと負荷分散ポリシーを分析します。

    この比較は、LLM 推論サービスの負荷分散が、より優れたレイテンシ、スループット、およびキャッシュ使用率を提供することを示しています。

    image