LLM mit Multi-Cluster-Ray Serve und GKE Inference Gateway bereitstellen

In diesem Dokument wird beschrieben, wie Sie Inferenzanfragen in mehreren Ray Serve-Clustern in Google Kubernetes Engine (GKE) verwalten, indem Sie die Kubernetes Gateway API und das GKE Inference Gateway konfigurieren. Mit dieser Konfiguration können Sie die Traffic-Verwaltung für mehrere Teams zentralisieren, Arbeitslasten zur Erhöhung der Kapazität auf Regionen verteilen und modellbewusstes Routing basierend auf dem Inhalt des Anfrage-Bodys implementieren.

Vorteile der Verwendung von GKE Inference Gateway und Ray Serve

Die Verwendung von GKE Inference Gateway und Ray Serve bietet folgende Vorteile:

  • Pfad-Routing: Konfigurieren Sie jeden RayService mit einem Pfadpräfix und stellen Sie sie dann mit einem Gateway bereit, das zu mehreren Ray-Diensten weiterleitet.
  • Modellbasiertes Routing: Wählen Sie einen RayService aus, an den weitergeleitet werden soll, basierend auf dem Anfragetext. Sie können beispielsweise das angeforderte Modell aus einer JSON-Anfrage der OpenAI API extrahieren.
  • Governance: Sie können API-Schlüssel für die Nutzung Ihres Dienstes erforderlich machen oder Kontingente für Nutzer mit Apigee für Authentifizierung und API-Verwaltung erzwingen.
  • Mehrere Regionen: Teilen Sie den Traffic mit Multi-Cluster-Gateways auf mehrere GKE-Cluster mit RayServices auf, um eine höhere Verfügbarkeit oder Kapazität zu erzielen.
  • Trennung von Belangen: Verwenden Sie separate RayServices, die von separaten Teams verwaltet werden können, separate Rollouts durchlaufen und in verschiedenen Topologien ausgeführt werden.
  • Sicherheit: Verwenden Sie Gateway als SSL-Terminator, um den Nutzer-Traffic im Internet zu schützen. Weitere Informationen finden Sie unter Gateway-Sicherheit.

Um das Routing zu konfigurieren, müssen Sie ein Gateway, eine HTTPRoute und einen RayService bereitstellen. Ein Kubernetes-Dienst für jeden Ziel-Ray-Cluster wird in der Regel von KubeRay erstellt. Ray Serve verteilt die Anfragelast im Cluster, ohne dass ein InferencePool oder Endpoint Picker erstellt werden muss.

Modellbezogenes Routing für Ray Serve in GKE

Modellbasiertes Routing wird durch eine Erweiterung für textkörperbasiertes Routing ermöglicht. Mit dem Body-basierten Routing können Sie Traffic nur basierend auf dem in der Nutzeranfrage genannten Modell an verschiedene RayServices weiterleiten. So können Sie einen einzelnen Endpunkt verwenden, über den viele Modelle bereitgestellt werden können, die in mehreren Ray-Clustern gehostet werden. Ihre Nutzer haben einen vereinfachten Zugriff und Ihre App-Entwickler können jeden Ray-Endpunkt konfigurieren.

Um modellbasiertes Routing zu konfigurieren, stellen Sie die folgenden Schlüsselkomponenten bereit:

  • Eine Body-basierte Router-Erweiterung zum Extrahieren von Modellnamen aus JSON-Nutzlasten. Diese Router-Erweiterung wird mit Helm bereitgestellt.
  • Ein GKE Gateway (regionaler interner Application Load Balancer auf Layer 7) zur Verarbeitung des eingehenden Traffics.
  • HTTPRoute-Regeln, um Traffic mithilfe von Headern, die von der Router-Erweiterung ausgefüllt werden, an den richtigen Ray-Dienst weiterzuleiten.
  • Mehrere Ray Serve-Cluster zum Verwalten des Lebenszyklus und des Autoscalings von isolierten Modellen.

Hinweis

Führen Sie die folgenden Aufgaben aus, bevor Sie beginnen:

  • Aktivieren Sie die Google Kubernetes Engine API.
  • Google Kubernetes Engine API aktivieren
  • Wenn Sie die Google Cloud CLI für diese Aufgabe verwenden möchten, müssen Sie die gcloud CLI installieren und dann initialisieren. Wenn Sie die gcloud CLI bereits installiert haben, rufen Sie die neueste Version mit dem Befehl gcloud components update ab. In früheren gcloud CLI-Versionen werden die Befehle in diesem Dokument möglicherweise nicht unterstützt.

Umgebung vorbereiten

Richten Sie Umgebungsvariablen ein:

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

Ersetzen Sie YOUR_HUGGING_FACE_TOKEN durch Ihr Hugging Face-Zugriffstoken.

Infrastruktur vorbereiten

In diesem Abschnitt richten Sie einen Ray-fähigen, Gateway-fähigen GKE-Cluster mit L4-GPUs ein.

  1. Erstellen Sie einen Cluster mit aktiviertem Ray-Operator und aktivierter 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. Erstellen Sie einen GPU-Knotenpool für die Arbeitslasten Ihres Modells:

    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. Erstellen Sie ein Nur-Proxy-Subnetz für den regionalen internen Application Load Balancer, das für Body-basiertes Routing erforderlich ist:

    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. So stellen Sie Ihren geheimen Hugging Face-Schlüssel bereit:

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

Textkörperbasierten Router für modellbasiertes Routing bereitstellen

Die Body-basierte Router-Erweiterung fängt Anfragen ab, parst den JSON-Text und extrahiert das Modellfeld in einen X-Gateway-Model-Name-Header.

  1. Erstellen Sie eine Datei mit dem Namen helm-values.yaml und dem folgendem Inhalt:

    bbr:
      plugins:
        - type: "body-field-to-header"
          name: "openai-model-extractor"
          json:
            field_name: "model"
            header_name: "X-Gateway-Model-Name"
    
  2. Installieren Sie den textkörperbasierten Router mit 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
    

RayServices bereitstellen

Um Ihre Modelle bereitzustellen, müssen Sie die RayService-Manifeste anwenden. Jedes Manifest definiert einen Ray-Cluster, in dem ein bestimmtes LLM ausgeführt wird.

  1. Erstellen Sie eine Datei mit dem Namen gemma-2b-it.yaml und dem folgendem Inhalt:

    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. Erstellen Sie eine Datei mit dem Namen qwen2.5-3b.yaml und dem folgendem Inhalt:

    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. Modelle bereitstellen:

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

Systemdiagnosen konfigurieren

Damit der Load-Balancer den Zustand von Ray-Workern genau überwachen kann, müssen Sie die HealthCheckPolicy-Ressource anwenden.

  1. Erstellen Sie eine Datei mit dem Namen healthcheck-policy.yaml und dem folgendem Inhalt:

    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. Wenden Sie die Richtlinie für Systemdiagnosen an:

    kubectl apply -f healthcheck-policy.yaml
    

Routing konfigurieren

Zum Konfigurieren des Routings müssen Sie die Manifeste Gateway und HTTPRoute anwenden. Die HTTPRoute enthält Regeln, die mit dem X-Gateway-Model-Name-Header (der vom Body-basierten Router ausgefüllt wird) übereinstimmen, um Traffic an den entsprechenden Ray-Dienst weiterzuleiten.

  1. Erstellen Sie eine Datei mit dem Namen gateway.yaml und dem folgendem Inhalt:

    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. Wenden Sie das Gateway und die Route an:

    kubectl apply -f gateway.yaml
    

Deployment testen

Nachdem das Gateway bereitgestellt wurde und beide Ray-Cluster bereit sind, können Sie das Routing testen, indem Sie Anfragen mit verschiedenen Modellnamen im JSON-Textkörper senden.

  1. Rufen Sie die IP-Adresse des Gateways ab:

    kubectl get gateways ray-multi-model-gateway
    
  2. Starten Sie eine Shell in einem Netzwerk, das die Gateway-Adresse erreichen kann. Sie können „curl“ in einem der Ray-Cluster-Pods verwenden:

    POD_NAME=$(kubectl get pods -l ray.io/node-type=head -o jsonpath='{.items[0].metadata.name}')
    kubectl exec -it $POD_NAME -- bash
    
  3. Senden Sie Anfragen, indem Sie das Routing zu Gemma testen:

    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."}]
        }'
    

    Ersetzen Sie GATEWAY_IP_ADDRESS durch die IP-Adresse aus dem vorherigen Schritt.

    Die Ausgabe sieht etwa so aus:

    {"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. Routing zu Qwen testen:

    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?"}]
        }'
    

    Die Ausgabe sieht etwa so aus:

    {"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. [...]
    

Der Body-basierte Router extrahiert automatisch den Wert des Felds model und sorgt dafür, dass jede Anfrage den richtigen Back-End-Dienst erreicht, der in der Datei gateway.yaml konfiguriert ist.

Bereinigen

Löschen Sie den Cluster:

gcloud container clusters delete ${CLUSTER}

Nächste Schritte