透過多叢集 Ray Serve 和 GKE Inference Gateway 提供 LLM

本文說明如何設定 Kubernetes Gateway API 和 GKE Inference Gateway,管理 Google Kubernetes Engine (GKE) 上多個 Ray Serve 叢集的推論要求。這項設定可讓您集中管理多個團隊的流量、在不同區域分配工作負載以提高容量,以及根據要求主體內容實作模型感知型路徑。

使用 GKE Inference Gateway 和 Ray Serve 的優點

使用 GKE Inference Gateway 和 Ray Serve 可享有下列優點:

  • 路徑轉送:為每個 RayService 設定路徑前置字元,然後透過一個 Gateway 轉送至多個 RayService。
  • 模型感知型轉送:根據要求主體選擇要轉送的 RayService,例如從 OpenAI API JSON 要求中擷取要求的模型。
  • 控管:要求使用 API 金鑰才能使用服務,或使用 Apigee 進行驗證和 API 管理,強制執行使用者配額。
  • 多區域:使用 RayServices 在多個 GKE 叢集之間分配流量,透過多叢集閘道提高可用性或容量。
  • 關注點分離:使用可由不同團隊管理、遵循不同推出程序,以及在不同拓撲上執行的獨立 RayService。
  • 安全性:使用 Gateway 做為 SSL 終止器,協助保護使用者透過網際網路傳輸的流量。詳情請參閱「閘道安全性」。

如要設定轉送,您需要部署 Gateway、HTTPRoute 和 RayService。KubeRay 通常會為每個目標 Ray 叢集建立 Kubernetes Service。Ray Serve 會在叢集中分散要求負載,不需要建立 InferencePool 或 Endpoint Picker。

在 GKE 上使用 Ray Serve 進行模型感知式路由

以模型為依據的路由是由以主體為依據的路由擴充功能啟用。您可以根據使用者要求中指定的模型名稱,將流量導向至不同的 RayService,這樣一來,單一端點就能服務多個 Ray 叢集中託管的模型。使用者可輕鬆存取,應用程式開發人員則可控管每個 Ray 端點的設定。

如要設定模型感知路徑,請部署下列主要元件:

  • 以主體為基礎的路由器擴充功能,可從 JSON 酬載擷取模型名稱。這個路由器擴充功能是使用 Helm 部署。
  • GKE Gateway (第 7 層區域性內部應用程式負載平衡器),用於處理傳入流量。
  • HTTPRoute 規則,可使用路由器擴充功能填入的標頭,將流量導向正確的 Ray 服務。
  • 多個 Ray Serve 叢集,用於管理孤立模型的生命週期和自動調度資源。

事前準備

開始之前,請務必先完成下列工作:

  • 啟用 Google Kubernetes Engine API。
  • 啟用 Google Kubernetes Engine API
  • 如要使用 Google Cloud CLI 執行這項工作,請安裝初始化 gcloud CLI。如果您先前已安裝 gcloud CLI,請執行 gcloud components update 指令,取得最新版本。較舊的 gcloud CLI 版本可能不支援執行本文件中的指令。

準備環境

設定環境變數:

export CLUSTER=$(whoami)-ray-bbr
export PROJECT_ID=$(gcloud config get-value project)
export LOCATION=us-central1-b
export REGION=us-central1
export HUGGING_FACE_TOKEN=YOUR_HUGGING_FACE_TOKEN

YOUR_HUGGING_FACE_TOKEN 替換為您的 Hugging Face 存取權杖。

準備基礎架構

在本節中,您將設定啟用 Ray 和 Gateway 的 GKE 叢集,並使用 L4 GPU。

  1. 建立叢集,並啟用 Ray Operator 和 Gateway API:

    gcloud container clusters create ${CLUSTER} \
        --project ${PROJECT_ID} \
        --location ${LOCATION} \
        --cluster-version 1.35 \
        --gateway-api standard \
        --addons HttpLoadBalancing,RayOperator \
        --enable-ray-cluster-logging \
        --enable-ray-cluster-monitoring \
        --machine-type e2-standard-4
    
  2. 為模型工作負載建立 GPU 節點集區:

    gcloud container node-pools create gpu-pool \
        --cluster=${CLUSTER} \
        --location=${LOCATION} \
        --accelerator="type=nvidia-l4,count=1,gpu-driver-version=latest" \
        --machine-type=g2-standard-8 \
        --num-nodes=4
    
  3. 為區域性內部應用程式負載平衡器建立 Proxy 專用子網路,這是以主體為準的路由功能所需:

    gcloud compute networks subnets create bbr-proxy-only-subnet \
        --purpose=REGIONAL_MANAGED_PROXY \
        --role=ACTIVE \
        --region=${REGION} \
        --network=default \
        --range=192.168.10.0/24
    
  4. 部署 Hugging Face 密鑰:

    kubectl create secret generic hf-secret \
        --from-literal=hf_api_token=${HUGGING_FACE_TOKEN}
    

部署以主體為依據的路由器,進行模型感知路由

以主體為基礎的路由器擴充功能會攔截要求、剖析 JSON 主體,並將模型欄位擷取至 X-Gateway-Model-Name 標頭。

  1. 建立名為 helm-values.yaml 的檔案,並加入以下內容:

    bbr:
      plugins:
        - type: "body-field-to-header"
          name: "openai-model-extractor"
          json:
            field_name: "model"
            header_name: "X-Gateway-Model-Name"
    
  2. 使用 Helm 安裝以主體為依據的路由器:

    helm install body-based-router \
        oci://registry.k8s.io/gateway-api-inference-extension/charts/body-based-routing \
        --version v1.4.0 \
        --set provider.name=gke \
        --set inferenceGateway.name=ray-multi-model-gateway \
        --values helm-values.yaml
    

部署 RayService

如要部署模型,請套用 RayService 資訊清單。每個資訊清單都會定義執行特定 LLM 的 Ray 叢集。

  1. 建立名為 gemma-2b-it.yaml 的檔案,並加入以下內容:

    apiVersion: ray.io/v1
    kind: RayService
    metadata:
      name: gemma-2b-it
    spec:
      serveConfigV2: |
        applications:
        - name: llm_app
          route_prefix: "/"
          import_path: ray.serve.llm:build_openai_app
          args:
            llm_configs:
                - model_loading_config:
                    model_id: gemma-2b-it
                    model_source: google/gemma-2b-it
                  accelerator_type: L4
                  log_engine_metrics: true
                  deployment_config:
                    autoscaling_config:
                        min_replicas: 2
                        max_replicas: 2
                    health_check_period_s: 600
                    health_check_timeout_s: 300
      rayClusterConfig:
        headGroupSpec:
          rayStartParams:
            dashboard-host: "0.0.0.0"
            num-cpus: "0"
          template:
            spec:
              containers:
                - name: ray-head
                  image: rayproject/ray-llm:2.54.0-py311-cu128
                  resources:
                    limits:
                      memory: "8Gi"
                      ephemeral-storage: "32Gi"
                    requests:
                      cpu: "2"
                      memory: "8Gi"
                      ephemeral-storage: "32Gi"
                  ports:
                    - containerPort: 6379
                      name: gcs-server
                    - containerPort: 8265
                      name: dashboard
                    - containerPort: 10001
                      name: client
                    - containerPort: 8000
                      name: serve
                  env:
                    - name: RAY_SERVE_THROUGHPUT_OPTIMIZED
                      value: "1"
                    - name: RAY_SERVE_ENABLE_HA_PROXY
                      value: "1"
                    - name: HUGGING_FACE_HUB_TOKEN
                      valueFrom:
                        secretKeyRef:
                          name: hf-secret
                          key: hf_api_token
        rayVersion: 2.54.0
        workerGroupSpecs:
          - replicas: 2
            minReplicas: 2
            maxReplicas: 2
            groupName: gpu-group
            rayStartParams: {}
            template:
              spec:
                containers:
                  - name: llm
                    image: rayproject/ray-llm:2.54.0-py311-cu128
                    env:
                      - name: RAY_SERVE_THROUGHPUT_OPTIMIZED
                        value: "1"
                      - name: RAY_SERVE_ENABLE_HA_PROXY
                        value: "1"
                      - name: HUGGING_FACE_HUB_TOKEN
                        valueFrom:
                          secretKeyRef:
                            name: hf-secret
                            key: hf_api_token
                    resources:
                      limits:
                        nvidia.com/gpu: "1"
                        ephemeral-storage: "24Gi"
                      requests:
                        cpu: "6"
                        memory: "24Gi"
                        nvidia.com/gpu: "1"
                        ephemeral-storage: "24Gi"
                nodeSelector:
                  cloud.google.com/gke-accelerator: nvidia-l4
    
  2. 建立名為 qwen2.5-3b.yaml 的檔案,並加入以下內容:

    apiVersion: ray.io/v1
    kind: RayService
    metadata:
      name: qwen-25-3b
    spec:
      serveConfigV2: |
        applications:
        - name: llm_app
          route_prefix: "/"
          import_path: ray.serve.llm:build_openai_app
          args:
            llm_configs:
                - model_loading_config:
                    model_id: qwen-2.5-3b
                    model_source: Qwen/Qwen2.5-3B
                  accelerator_type: L4
                  log_engine_metrics: true
                  deployment_config:
                    autoscaling_config:
                        min_replicas: 2
                        max_replicas: 2
                    health_check_period_s: 600
                    health_check_timeout_s: 300
      rayClusterConfig:
        headGroupSpec:
          rayStartParams:
            dashboard-host: "0.0.0.0"
            num-cpus: "0"
          template:
            spec:
              containers:
                - name: ray-head
                  image: rayproject/ray-llm:2.54.0-py311-cu128
                  resources:
                    limits:
                      memory: "8Gi"
                      ephemeral-storage: "32Gi"
                    requests:
                      cpu: "2"
                      memory: "8Gi"
                      ephemeral-storage: "32Gi"
                  ports:
                    - containerPort: 6379
                      name: gcs-server
                    - containerPort: 8265
                      name: dashboard
                    - containerPort: 10001
                      name: client
                    - containerPort: 8000
                      name: serve
                  env:
                    - name: RAY_SERVE_THROUGHPUT_OPTIMIZED
                      value: "1"
                    - name: RAY_SERVE_ENABLE_HA_PROXY
                      value: "1"
                    - name: HUGGING_FACE_HUB_TOKEN
                      valueFrom:
                        secretKeyRef:
                          name: hf-secret
                          key: hf_api_token
        rayVersion: 2.54.0
        workerGroupSpecs:
          - replicas: 2
            minReplicas: 2
            maxReplicas: 2
            groupName: gpu-group
            rayStartParams: {}
            template:
              spec:
                containers:
                  - name: llm
                    image: rayproject/ray-llm:2.54.0-py311-cu128
                    env:
                      - name: RAY_SERVE_THROUGHPUT_OPTIMIZED
                        value: "1"
                      - name: RAY_SERVE_ENABLE_HA_PROXY
                        value: "1"
                      - name: HUGGING_FACE_HUB_TOKEN
                        valueFrom:
                          secretKeyRef:
                            name: hf-secret
                            key: hf_api_token
                    resources:
                      limits:
                        nvidia.com/gpu: "1"
                        ephemeral-storage: "24Gi"
                      requests:
                        cpu: "6"
                        memory: "24Gi"
                        nvidia.com/gpu: "1"
                        ephemeral-storage: "24Gi"
                nodeSelector:
                  cloud.google.com/gke-accelerator: nvidia-l4
    
  3. 部署模型:

    kubectl apply -f gemma-2b-it.yaml
    kubectl apply -f qwen2.5-3b.yaml
    

設定健康狀態檢查

為確保負載平衡器能準確監控 Ray 工作人員的健康狀態,您必須套用 HealthCheckPolicy 資源。

  1. 建立名為 healthcheck-policy.yaml 的檔案,並加入以下內容:

    apiVersion: networking.gke.io/v1
    kind: HealthCheckPolicy
    metadata:
      name: gemma-serve-healthcheck
      namespace: default
    spec:
      default:
        checkIntervalSec: 5
        timeoutSec: 5
        healthyThreshold: 2
        unhealthyThreshold: 2
        config:
          type: HTTP
          httpHealthCheck:
            port: 8000
            requestPath: /-/healthz
      targetRef:
        group: ""
        kind: Service
        name: gemma-2b-it-serve-svc
    ---
    apiVersion: networking.gke.io/v1
    kind: HealthCheckPolicy
    metadata:
      name: qwen-serve-healthcheck
      namespace: default
    spec:
      default:
        checkIntervalSec: 5
        timeoutSec: 5
        healthyThreshold: 2
        unhealthyThreshold: 2
        config:
          type: HTTP
          httpHealthCheck:
            port: 8000
            requestPath: /-/healthz
      targetRef:
        group: ""
        kind: Service
        name: qwen-25-3b-serve-svc
    
  2. 套用健康狀態檢查政策:

    kubectl apply -f healthcheck-policy.yaml
    

設定轉送

如要設定轉送,請套用 GatewayHTTPRoute 資訊清單。 HTTPRoute 包含與 X-Gateway-Model-Name 標頭 (由以主體為基礎的路由器填入) 相符的規則,可將流量轉送至適當的 Ray 服務。

  1. 建立名為 gateway.yaml 的檔案,並加入以下內容:

    apiVersion: gateway.networking.k8s.io/v1
    kind: Gateway
    metadata:
      name: ray-multi-model-gateway
      namespace: default
    spec:
      gatewayClassName: gke-l7-rilb
      listeners:
      - allowedRoutes:
          namespaces:
            from: Same
        name: http
        port: 80
        protocol: HTTP
    ---
    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: ray-multi-model-route
    spec:
      parentRefs:
      - name: ray-multi-model-gateway
      rules:
      - matches:
        - headers:
          - type: Exact
            name: X-Gateway-Model-Name
            value: gemma-2b-it  # Must match model named in JSON request!
          path:
            type: PathPrefix
            value: /
        backendRefs:
        - name: gemma-2b-it-serve-svc  # Ray service name plus "-serve-svc".
          kind: Service
          port: 8000
    
      - matches:
        - headers:
          - type: Exact
            name: X-Gateway-Model-Name
            value: qwen-2.5-3b  # Matches another extracted model name
          path:
            type: PathPrefix
            value: /
        backendRefs:
        - name: qwen-25-3b-serve-svc  # Target Ray Service.
          kind: Service
          port: 8000
    
  2. 套用閘道和路徑:

    kubectl apply -f gateway.yaml
    

測試部署作業

佈建 Gateway 並準備好兩個 Ray 叢集後,您可以在 JSON 內文中傳送含有不同模型名稱的要求,測試路由功能。

  1. 取得閘道 IP 位址:

    kubectl get gateways ray-multi-model-gateway
    
  2. 在可連上閘道位址的網路中啟動殼層。 您可以在其中一個 Ray 叢集 Pod 上使用 curl:

    POD_NAME=$(kubectl get pods -l ray.io/node-type=head -o jsonpath='{.items[0].metadata.name}')
    kubectl exec -it $POD_NAME -- bash
    
  3. 測試將要求傳送至 Gemma 的路徑:

    curl http://GATEWAY_IP_ADDRESS/v1/chat/completions \
        --header 'Content-Type: application/json' \
        --data '{
        "model": "gemma-2b-it",
        "messages": [{"role": "user", "content": "Tell me about GKE."}]
        }'
    

    GATEWAY_IP_ADDRESS 替換為上一步的 IP 位址。

    輸出結果會與下列內容相似:

    {"id":"chatcmpl-594f7cab-f991-4522-9829-acdbb65d9f67","object":"chat.completion","created":1776379509,"model":"gemma-2b-it","choices":[{"index":0,"message":{"role":"assistant","content":"**Google Kubernetes Engine (GKE)** is a fully managed container orchestration service for Kubernetes [...]
    
  4. 測試轉送至 Qwen:

    curl http://GATEWAY_IP_ADDRESS/v1/chat/completions \
        --header 'Content-Type: application/json' \
        --data '{
        "model": "qwen-2.5-3b",
        "messages": [{"role": "user", "content": "How does Ray Serve work?"}]
        }'
    

    輸出結果會與下列內容相似:

    {"id":"chatcmpl-dfe3f3b7-45fc-481c-b53e-2fc09c033cdb","object":"chat.completion","created":1776380249,"model":"qwen-2.5-3b","choices":[{"index":0,"message":{"role":"assistant","content":"Ray Serve facilitates the hosting and deployment of scalable microservices. [...]
    

以主體為準的路由器會自動擷取 model 欄位的值,並確保每項要求都會傳送至 gateway.yaml 檔案中設定的正確後端服務。

清除所用資源

刪除叢集:

gcloud container clusters delete ${CLUSTER}

後續步驟