Disponibilizar um LLM com o Ray Serve de vários clusters e o GKE Inference Gateway

Este documento explica como gerenciar solicitações de inferência em vários clusters do Ray Serve no Google Kubernetes Engine (GKE) configurando a API Gateway do Kubernetes e o GKE Inference Gateway. Essa configuração permite centralizar o gerenciamento de tráfego para várias equipes, distribuir cargas de trabalho entre regiões para maior capacidade e implementar o roteamento com reconhecimento de modelo com base no conteúdo do corpo da solicitação.

Benefícios de usar o GKE Inference Gateway e o Ray Serve

O uso do GKE Inference Gateway e do Ray Serve oferece os seguintes benefícios:

  • Roteamento de caminho: configure cada RayService com um prefixo de caminho e, em seguida, disponibilize os com um roteamento de gateway para vários serviços do Ray.
  • Roteamento com reconhecimento de modelo: escolha um RayService para rotear com base no corpo da solicitação. Por exemplo, extraindo o modelo solicitado de uma solicitação JSON da API OpenAI.
  • Governança: exija chaves de API para usar seu serviço ou aplique cotas para usuários usando a Apigee para autenticação e gerenciamento de API.
  • Várias regiões: divida o tráfego em vários clusters do GKE com RayServices para alcançar maior disponibilidade ou capacidade com gateways de vários clusters.
  • Separação de interesses: use RayServices separados, que podem ser administrados por equipes separadas, seguir implementações separadas e ser executados em topologias diferentes.
  • Segurança: use o gateway para atuar como o terminador SSL para ajudar a proteger o tráfego de usuários na Internet. Para mais informações, consulte Segurança do gateway.

Para configurar o roteamento, implante um gateway, HTTPRoute e RayService. Um serviço do Kubernetes para cada cluster do Ray de destino é normalmente criado pelo KubeRay. O Ray Serve distribui a carga de solicitações no cluster, sem a necessidade de criar um InferencePool ou um seletor de endpoints.

Roteamento com reconhecimento de modelo para Ray Serve no GKE

O roteamento com reconhecimento de modelo é ativado por uma extensão de roteamento baseada no corpo. O roteamento baseado no corpo permite direcionar o tráfego para diferentes RayServices com base apenas no modelo nomeado na solicitação do usuário, o que permite ter um único endpoint que pode disponibilizar muitos modelos hospedados em vários clusters do Ray. Seus usuários têm acesso simplificado, e os desenvolvedores de apps têm controle sobre a configuração de cada endpoint do Ray.

Para configurar o roteamento com reconhecimento de modelo, implante os seguintes componentes principais:

  • Uma extensão de roteador baseada no corpo para extrair nomes de modelos de payloads JSON. Essa extensão de roteador é implantada usando o Helm.
  • Um gateway do GKE (balanceador de carga de aplicativo interno regional de camada 7) para processar o tráfego de entrada.
  • Regras HTTPRoute para direcionar o tráfego ao serviço do Ray correto usando cabeçalhos preenchidos pela extensão do roteador.
  • Vários clusters do Ray Serve para gerenciar o ciclo de vida e o escalonamento automático de modelos isolados.

Antes de começar

Antes de começar, verifique se você realizou as tarefas a seguir:

  • Ativar a API Google Kubernetes Engine.
  • Ativar a API Google Kubernetes Engine
  • Se você quiser usar a Google Cloud CLI para essa tarefa, instale e, em seguida, inicialize a CLI gcloud. Se você instalou a CLI gcloud anteriormente, instale a versão mais recente executando o comando gcloud components update. Talvez as versões anteriores da CLI gcloud não sejam compatíveis com a execução dos comandos neste documento.

Preparar o ambiente

Configure as variáveis de ambiente:

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

Substitua YOUR_HUGGING_FACE_TOKEN pelo seu token de acesso do Hugging Face.

Preparar sua infraestrutura

Nesta seção, você configura um cluster do GKE ativado para o Ray e o gateway com GPUs L4.

  1. Crie um cluster com o operador Ray e a API Gateway ativados:

    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. Crie um pool de nós de GPU para as cargas de trabalho do modelo:

    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. Crie uma sub-rede somente proxy para o balanceador de carga de aplicativo interno regional, que é exigido pelo roteamento baseado no corpo:

    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. Implante seu secret do Hugging Face:

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

Implantar o roteador baseado no corpo para roteamento com reconhecimento de modelo

A extensão do roteador baseado no corpo intercepta solicitações, analisa o corpo JSON e extrai o campo do modelo em um cabeçalho X-Gateway-Model-Name.

  1. Crie um arquivo chamado helm-values.yaml com o conteúdo a seguir:

    bbr:
      plugins:
        - type: "body-field-to-header"
          name: "openai-model-extractor"
          json:
            field_name: "model"
            header_name: "X-Gateway-Model-Name"
    
  2. Instale o roteador baseado no corpo usando o 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
    

Implantar RayServices

Para implantar seus modelos, aplique os manifestos RayService. Cada manifesto define um cluster do Ray que executa um LLM específico.

  1. Crie um arquivo chamado gemma-2b-it.yaml com o conteúdo a seguir:

    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. Crie um arquivo chamado qwen2.5-3b.yaml com o conteúdo a seguir:

    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. Implante os modelos:

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

Configurar verificações de integridade

Para garantir que o balanceador de carga monitore com precisão a integridade do worker do Ray, aplique o recurso HealthCheckPolicy.

  1. Crie um arquivo chamado healthcheck-policy.yaml com o conteúdo a seguir:

    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. Aplique a política de verificação de integridade:

    kubectl apply -f healthcheck-policy.yaml
    

Configurar o roteamento

Para configurar o roteamento, aplique os manifestos Gateway e HTTPRoute. O HTTPRoute contém regras que correspondem ao cabeçalho X-Gateway-Model-Name (preenchido pelo roteador baseado no corpo) para rotear o tráfego para o serviço do Ray apropriado.

  1. Crie um arquivo chamado gateway.yaml com o conteúdo a seguir:

    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. Aplique o gateway e a rota:

    kubectl apply -f gateway.yaml
    

Teste a implantação

Depois que o gateway for provisionado e os dois clusters do Ray estiverem prontos, você poderá testar o roteamento enviando solicitações com nomes de modelos diferentes no corpo JSON.

  1. Acessar o endereço IP do gateway:

    kubectl get gateways ray-multi-model-gateway
    
  2. Inicie um shell em uma rede que possa acessar o endereço do gateway. Você pode usar o curl em um dos pods do cluster do Ray:

    POD_NAME=$(kubectl get pods -l ray.io/node-type=head -o jsonpath='{.items[0].metadata.name}')
    kubectl exec -it $POD_NAME -- bash
    
  3. Envie solicitações testando o roteamento para 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."}]
        }'
    

    Substitua GATEWAY_IP_ADDRESS pelo endereço IP da etapa anterior.

    O resultado será assim:

    {"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. Teste o roteamento para 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?"}]
        }'
    

    O resultado será assim:

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

O roteador baseado no corpo extrai automaticamente o valor do campo model e garante que cada solicitação chegue ao serviço de back-end correto configurado no arquivo gateway.yaml.

Limpar

Exclua o cluster:

gcloud container clusters delete ${CLUSTER}

A seguir