Implemente uma aplicação Ray Serve com um modelo Stable Diffusion no Google Kubernetes Engine (GKE)

Este guia fornece um exemplo de como implementar e publicar um modelo Stable Diffusion no Google Kubernetes Engine (GKE) através do Ray Serve e do suplemento Ray Operator como exemplo de implementação.

Acerca do Ray e do Ray Serve

O Ray é uma framework de computação escalável de código aberto para aplicações de IA/AA. O Ray Serve é uma biblioteca de publicação de modelos para o Ray usada para dimensionar e publicar modelos num ambiente distribuído. Para mais informações, consulte o artigo Ray Serve na documentação do Ray.

Pode usar um recurso RayCluster ou RayService para implementar as suas aplicações Ray Serve. Deve usar um recurso RayService em produção pelos seguintes motivos:

  • Atualizações no local para aplicações RayService
  • Atualização sem período de inatividade para recursos RayCluster
  • Aplicações Ray Serve altamente disponíveis

Prepare o seu ambiente

Para preparar o seu ambiente, siga estes passos:

  1. Inicie uma sessão do Cloud Shell a partir da Google Cloud consola, clicando em Ícone de ativação do Cloud Shell Ativar Cloud Shell na Google Cloud consola. Esta ação inicia uma sessão no painel inferior da Google Cloud consola.

  2. Defina variáveis de ambiente:

    export PROJECT_ID=PROJECT_ID
    export CLUSTER_NAME=rayserve-cluster
    export COMPUTE_REGION=us-central1
    export COMPUTE_ZONE=us-central1-c
    export CLUSTER_VERSION=CLUSTER_VERSION
    export TUTORIAL_HOME=`pwd`
    

    Substitua o seguinte:

    • PROJECT_ID: o seu Google Cloud ID do projeto.
    • CLUSTER_VERSION: a versão do GKE a usar. Tem de ser 1.30.1 ou posterior.
  3. Clone o repositório do GitHub:

    git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples
    
  4. Mude para o diretório de trabalho:

    cd kubernetes-engine-samples/ai-ml/gke-ray/rayserve/stable-diffusion
    
  5. Crie um ambiente virtual do Python:

    venv

    python -m venv myenv && \
    source myenv/bin/activate
    

    Conda

    1. Instale o Conda.

    2. Execute os seguintes comandos:

      conda create -c conda-forge python=3.9.19 -n myenv && \
      conda activate myenv
      

    Quando implementa uma aplicação Serve com serve run, o Ray espera que a versão do Python do cliente local corresponda à versão usada no cluster do Ray. A imagem rayproject/ray:2.37.0 usa o Python 3.9. Se estiver a usar uma versão diferente do cliente, selecione a imagem do raio adequada.

  6. Instale as dependências necessárias para executar a aplicação Serve:

    pip install ray[serve]==2.37.0
    pip install torch
    pip install requests
    

Crie um cluster com um node pool de GPU

Crie um cluster do GKE Autopilot ou Standard com um node pool de GPU:

Piloto automático

Crie um cluster do Autopilot:

gcloud container clusters create-auto ${CLUSTER_NAME}  \
    --enable-ray-operator \
    --cluster-version=${CLUSTER_VERSION} \
    --location=${COMPUTE_REGION}

Standard

  1. Criar um cluster padrão:

    gcloud container clusters create ${CLUSTER_NAME} \
        --addons=RayOperator \
        --cluster-version=${CLUSTER_VERSION}  \
        --machine-type=c3d-standard-8 \
        --location=${COMPUTE_ZONE} \
        --num-nodes=1
    
  2. Crie um node pool de GPU:

    gcloud container node-pools create gpu-pool \
        --cluster=${CLUSTER_NAME} \
        --machine-type=g2-standard-8 \
        --location=${COMPUTE_ZONE} \
        --num-nodes=1 \
        --accelerator type=nvidia-l4,count=1,gpu-driver-version=latest
    

Implemente um recurso RayCluster

Para implementar um recurso RayCluster:

  1. Reveja o seguinte manifesto:

    apiVersion: ray.io/v1
    kind: RayCluster
    metadata:
      name: stable-diffusion-cluster
    spec:
      rayVersion: '2.37.0'
      headGroupSpec:
        rayStartParams:
          dashboard-host: '0.0.0.0'
        template:
          metadata:
          spec:
            containers:
            - name: ray-head
              image: rayproject/ray:2.37.0
              ports:
              - containerPort: 6379
                name: gcs
              - containerPort: 8265
                name: dashboard
              - containerPort: 10001
                name: client
              - containerPort: 8000
                name: serve
              resources:
                limits:
                  cpu: "2"
                  ephemeral-storage: "15Gi"
                  memory: "8Gi"
                requests:
                  cpu: "2"
                  ephemeral-storage: "15Gi"
                  memory: "8Gi"
            nodeSelector:
              cloud.google.com/machine-family: c3d
      workerGroupSpecs:
      - replicas: 1
        minReplicas: 1
        maxReplicas: 4
        groupName: gpu-group
        rayStartParams: {}
        template:
          spec:
            containers:
            - name: ray-worker
              image: rayproject/ray:2.37.0-gpu
              resources:
                limits:
                  cpu: 4
                  memory: "16Gi"
                  nvidia.com/gpu: 1
                requests:
                  cpu: 3
                  memory: "16Gi"
                  nvidia.com/gpu: 1
            nodeSelector:
              cloud.google.com/gke-accelerator: nvidia-l4

    Este manifesto descreve um recurso RayCluster.

  2. Aplique o manifesto ao cluster:

    kubectl apply -f ray-cluster.yaml
    
  3. Verifique se o recurso RayCluster está pronto:

    kubectl get raycluster
    

    O resultado é semelhante ao seguinte:

    NAME                       DESIRED WORKERS   AVAILABLE WORKERS   CPUS   MEMORY   GPUS   STATUS   AGE
    stable-diffusion-cluster   2                 2                   6      20Gi     0      ready    33s
    

    Neste resultado, ready na coluna STATUS indica que o recurso RayCluster está pronto.

Estabeleça ligação ao recurso RayCluster

Para se ligar ao recurso RayCluster:

  1. Verifique se o GKE criou o serviço RayCluster:

    kubectl get svc stable-diffusion-cluster-head-svc
    

    O resultado é semelhante ao seguinte:

    NAME                             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                AGE
    pytorch-mnist-cluster-head-svc   ClusterIP   34.118.238.247   <none>        10001/TCP,8265/TCP,6379/TCP,8080/TCP   109s
    
  2. Estabeleça sessões de encaminhamento de portas para a cabeça do Ray:

    kubectl port-forward svc/stable-diffusion-cluster-head-svc 8265:8265 2>&1 >/dev/null &
    kubectl port-forward svc/stable-diffusion-cluster-head-svc 10001:10001 2>&1 >/dev/null &
    
  3. Verifique se o cliente Ray consegue estabelecer ligação ao cluster Ray através de localhost:

    ray list nodes --address http://localhost:8265
    

    O resultado é semelhante ao seguinte:

    ======== List: 2024-06-19 15:15:15.707336 ========
    Stats:
    ------------------------------
    Total: 3
    
    Table:
    ------------------------------
        NODE_ID                                                   NODE_IP     IS_HEAD_NODE    STATE    NODE_NAME    RESOURCES_TOTAL                 LABELS
    0  1d07447d7d124db641052a3443ed882f913510dbe866719ac36667d2  10.28.1.21  False           ALIVE    10.28.1.21   CPU: 2.0                        ray.io/node_id: 1d07447d7d124db641052a3443ed882f913510dbe866719ac36667d2
    # Several lines of output omitted
    

Execute uma aplicação Ray Serve

Para executar uma aplicação Ray Serve:

  1. Execute a aplicação Stable Diffusion Ray Serve:

    serve run stable_diffusion:entrypoint --working-dir=. --runtime-env-json='{"pip": ["torch", "torchvision", "diffusers==0.12.1", "huggingface_hub==0.25.2", "transformers", "fastapi==0.113.0"], "excludes": ["myenv"]}' --address ray://localhost:10001
    
    

    O resultado é semelhante ao seguinte:

    2024-06-19 18:20:58,444 INFO scripts.py:499 -- Running import path: 'stable_diffusion:entrypoint'.
    2024-06-19 18:20:59,730 INFO packaging.py:530 -- Creating a file package for local directory '.'.
    2024-06-19 18:21:04,833 INFO handle.py:126 -- Created DeploymentHandle 'hyil6u9f' for Deployment(name='StableDiffusionV2', app='default').
    2024-06-19 18:21:04,834 INFO handle.py:126 -- Created DeploymentHandle 'xo25rl4k' for Deployment(name='StableDiffusionV2', app='default').
    2024-06-19 18:21:04,836 INFO handle.py:126 -- Created DeploymentHandle '57x9u4fp' for Deployment(name='APIIngress', app='default').
    2024-06-19 18:21:04,836 INFO handle.py:126 -- Created DeploymentHandle 'xr6kt85t' for Deployment(name='StableDiffusionV2', app='default').
    2024-06-19 18:21:04,836 INFO handle.py:126 -- Created DeploymentHandle 'g54qagbz' for Deployment(name='APIIngress', app='default').
    2024-06-19 18:21:19,139 INFO handle.py:126 -- Created DeploymentHandle 'iwuz00mv' for Deployment(name='APIIngress', app='default').
    2024-06-19 18:21:19,139 INFO api.py:583 -- Deployed app 'default' successfully.
    
  2. Estabeleça uma sessão de encaminhamento de porta para a porta do Ray Serve (8000):

    kubectl port-forward svc/stable-diffusion-cluster-head-svc 8000:8000 2>&1 >/dev/null &
    
  3. Execute o script Python:

    python generate_image.py
    

    O script gera uma imagem para um ficheiro denominado output.png. A imagem é semelhante à seguinte:

    Uma praia ao pôr do sol. Imagem gerada pelo Stable Diffusion.

Implemente um RayService

O recurso personalizado RayService gere o ciclo de vida de um recurso RayCluster e de uma aplicação Ray Serve.

Para mais informações sobre o RayService, consulte os artigos Implemente aplicações Ray Serve e Guia de produção na documentação do Ray.

Para implementar um recurso RayService, siga estes passos:

  1. Reveja o seguinte manifesto:

    apiVersion: ray.io/v1
    kind: RayService
    metadata:
      name: stable-diffusion
    spec:
      serveConfigV2: |
        applications:
          - name: stable_diffusion
            import_path: ai-ml.gke-ray.rayserve.stable-diffusion.stable_diffusion:entrypoint
            runtime_env:
              working_dir: "https://github.com/GoogleCloudPlatform/kubernetes-engine-samples/archive/main.zip"
              pip: ["diffusers==0.12.1", "torch", "torchvision", "huggingface_hub==0.25.2", "transformers"]
      rayClusterConfig:
        rayVersion: '2.37.0'
        headGroupSpec:
          rayStartParams:
            dashboard-host: '0.0.0.0'
          template:
            spec:
              containers:
              - name: ray-head
                image:  rayproject/ray:2.37.0
                ports:
                - containerPort: 6379
                  name: gcs
                - containerPort: 8265
                  name: dashboard
                - containerPort: 10001
                  name: client
                - containerPort: 8000
                  name: serve
                resources:
                  limits:
                    cpu: "2"
                    ephemeral-storage: "15Gi"
                    memory: "8Gi"
                  requests:
                    cpu: "2"
                    ephemeral-storage: "15Gi"
                    memory: "8Gi"
              nodeSelector:
                cloud.google.com/machine-family: c3d
        workerGroupSpecs:
        - replicas: 1
          minReplicas: 1
          maxReplicas: 4
          groupName: gpu-group
          rayStartParams: {}
          template:
            spec:
              containers:
              - name: ray-worker
                image: rayproject/ray:2.37.0-gpu
                resources:
                  limits:
                    cpu: 4
                    memory: "16Gi"
                    nvidia.com/gpu: 1
                  requests:
                    cpu: 3
                    memory: "16Gi"
                    nvidia.com/gpu: 1
              nodeSelector:
                cloud.google.com/gke-accelerator: nvidia-l4

    Este manifesto descreve um recurso personalizado RayService.

  2. Aplique o manifesto ao cluster:

    kubectl apply -f ray-service.yaml
    
  3. Verifique se o serviço está pronto:

    kubectl get svc stable-diffusion-serve-svc
    

    O resultado é semelhante ao seguinte:

    NAME                         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
    
    stable-diffusion-serve-svc   ClusterIP   34.118.236.0   <none>        8000/TCP   31m
    
  4. Configure o encaminhamento de portas para o serviço Ray Serve:

    kubectl port-forward svc/stable-diffusion-serve-svc 8000:8000 2>&1 >/dev/null &
    
  5. Execute o script Python da secção anterior:

    python generate_image.py
    

    O script gera uma imagem semelhante à imagem gerada na secção anterior.