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

In diesem Dokument wird erläutert, wie Sie Anfragen für die Inferenz über mehrere Ray Serve-Cluster in Google Kubernetes Engine (GKE) hinweg verwalten. Dazu konfigurieren Sie die Kubernetes Gateway API und das GKE Inference Gateway. Mit dieser Konfiguration können Sie die Trafficverwaltung für mehrere Teams zentralisieren, Arbeitslasten zur Erhöhung der Kapazität auf mehrere Regionen verteilen und modellbasiertes Routing basierend auf dem Inhalt des Anfragetextes implementieren.

Vorteile der Verwendung von GKE Inference Gateway und Ray Serve

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

  • Pfadrouting: Konfigurieren Sie jeden RayService mit einem Pfadpräfix und stellen Sie ihn dann mit einem Gateway bereit, das Traffic an mehrere RayServices weiterleitet.
  • Modellbasiertes Routing: Wählen Sie basierend auf dem Anfragetext einen RayService aus, an den weitergeleitet werden soll. 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 Verwendung Ihres Dienstes anfordern oder Kontingente für Nutzer erzwingen, indem Sie Apigee für die Authentifizierung und API-Verwaltung verwenden.
  • Multiregional: Verteilen Sie Traffic auf mehrere GKE-Cluster mit RayServices, um mit Multi-Cluster-Gateways eine höhere Verfügbarkeit oder Kapazität zu erzielen.
  • Trennung von Zuständigkeiten: Verwenden Sie separate RayServices, die von separaten Teams verwaltet werden können, separate Roll-outs durchlaufen und auf verschiedenen Topologien ausgeführt werden können.
  • Sicherheit: Verwenden Sie das Gateway als SSL-Terminator, um den Traffic Ihrer Nutzer im Internet zu schützen. Weitere Informationen finden Sie unter Gateway-Sicherheit.

Zum Konfigurieren des Routings 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.

Modellbasiertes Routing für Ray Serve in GKE

Das modellbasierte Routing wird durch eine Erweiterung für das textkörperbasierte Routing aktiviert. Mit dem textkörperbasierten Routing können Sie Traffic basierend auf dem in der Anfrage des Nutzers genannten Modell an verschiedene RayServices weiterleiten. So haben Sie einen einzigen Endpunkt, der viele Modelle bereitstellen kann, die in mehreren Ray-Clustern gehostet werden. Ihre Nutzer haben einen vereinfachten Zugriff und Ihre App-Entwickler können jeden Ray-Endpunkt konfigurieren.

Zum Konfigurieren des modellbasierten Routings stellen Sie die folgenden Schlüsselkomponenten bereit:

  • Eine Erweiterung für den textkörperbasierten Router, um Modellnamen aus JSON-Nutzlasten zu extrahieren. Diese Routererweiterung wird mit Helm bereitgestellt.
  • Ein GKE-Gateway (regionaler interner Application Load Balancer der Ebene 7) zur Verarbeitung des eingehenden Traffics.
  • HTTPRoute-Regeln, um Traffic mithilfe von Headern, die von der Routererweiterung ausgefüllt werden, an den richtigen Ray-Dienst weiterzuleiten.
  • Mehrere Ray Serve-Cluster, um den Lebenszyklus und das Autoscaling von isolierten Modellen zu verwalten.

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 diesen Task verwenden möchten, installieren und dann initialisieren Sie die gcloud CLI. Wenn Sie die gcloud CLI bereits installiert haben, rufen Sie die neueste Version mit dem gcloud components update Befehl ab. Ältere gcloud CLI-Versionen unterstützen möglicherweise nicht die Ausführung der Befehle in diesem Dokument.

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 GKE-Cluster mit aktivierter Ray- und Gateway-Funktion 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 Ihre Modell-Arbeitslasten:

    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, der für das textkörperbasierte 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. Stellen Sie Ihr Hugging Face-Secret bereit:

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

Textkörperbasierten Router für modellbasiertes Routing bereitstellen

Die Erweiterung für den textkörperbasierten Router 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 folgenden 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 folgenden 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 folgenden 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. Stellen Sie die Modelle bereit:

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

Systemdiagnosen konfigurieren

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

  1. Erstellen Sie eine Datei mit dem Namen healthcheck-policy.yaml und dem folgenden 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 Systemdiagnoserichtlinie an:

    kubectl apply -f healthcheck-policy.yaml
    

Routing konfigurieren

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

  1. Erstellen Sie eine Datei mit dem Namen gateway.yaml und dem folgenden 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 und beide Ray-Cluster bereit sind, können Sie das Routing testen, indem Sie Anfragen mit verschiedenen Modellnamen im JSON-Text 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. Testen Sie das Routing zu 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?"}]
        }'
    

    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 textkörperbasierte Router extrahiert automatisch den Wert des Felds model und sorgt dafür, dass jede Anfrage den richtigen Backend-Dienst erreicht, der in der Datei gateway.yaml konfiguriert ist.

Bereinigen

Löschen Sie den Cluster:

gcloud container clusters delete ${CLUSTER}

Nächste Schritte