Otimize a utilização de recursos do GKE para cargas de trabalho mistas de inferência e preparação de IA/AA


Este tutorial mostra como partilhar eficientemente recursos de acelerador entre cargas de trabalho de serviço de inferência e de preparação num único cluster do Google Kubernetes Engine (GKE). Ao distribuir as suas cargas de trabalho mistas por um único cluster, melhora a utilização de recursos, simplifica a gestão de clusters, reduz os problemas decorrentes de limitações da quantidade de aceleradores e melhora a rentabilidade geral.

Neste tutorial, cria uma implementação de serviço de alta prioridade usando o modelo de linguagem grande (LLM) Gemma 2 para inferência e a framework de serviço Hugging Face TGI (interface de geração de texto), juntamente com uma tarefa de ajuste fino de LLM de baixa prioridade. Ambas as cargas de trabalho são executadas num único cluster que usa GPUs NVIDIA L4. Usa o Kueue, um sistema de filas de tarefas nativo do Kubernetes de código aberto, para gerir e agendar as suas cargas de trabalho. O Kueue permite-lhe dar prioridade a tarefas de publicação e antecipar tarefas de preparação com prioridade inferior para otimizar a utilização de recursos. À medida que as exigências de publicação diminuem, reatribui os aceleradores libertados para retomar as tarefas de preparação. Usa o Kueue e as classes de prioridade para gerir as quotas de recursos ao longo do processo.

Este tutorial destina-se a engenheiros de aprendizagem automática (ML), administradores e operadores de plataformas, e especialistas em dados e IA que pretendam preparar e alojar um modelo de aprendizagem automática (ML) num cluster do GKE, e que também pretendam reduzir os custos e a sobrecarga de gestão, especialmente quando lidam com um número limitado de aceleradores. Para saber mais sobre as funções comuns e exemplos de tarefas que referimos no conteúdo, consulte o artigo Funções e tarefas comuns do utilizador do GKE. Google Cloud

Antes de ler esta página, certifique-se de que conhece o seguinte:

Objetivos

No final deste guia, deve conseguir realizar os seguintes passos:

  • Configure uma implementação de publicação de alta prioridade.
  • Configurar tarefas de preparação de prioridade mais baixa.
  • Implemente estratégias de antecipação para responder à variação da procura.
  • Faça a gestão da atribuição de recursos entre tarefas de preparação e publicação com o Kueue.

Antes de começar

  • Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  • In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  • Verify that billing is enabled for your Google Cloud project.

  • Enable the required APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

  • In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  • Verify that billing is enabled for your Google Cloud project.

  • Enable the required APIs.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the APIs

  • Make sure that you have the following role or roles on the project: roles/container.admin, roles/iam.serviceAccountAdmin

    Check for the roles

    1. In the Google Cloud console, go to the IAM page.

      Go to IAM
    2. Select the project.
    3. In the Principal column, find all rows that identify you or a group that you're included in. To learn which groups you're included in, contact your administrator.

    4. For all rows that specify or include you, check the Role column to see whether the list of roles includes the required roles.

    Grant the roles

    1. In the Google Cloud console, go to the IAM page.

      Aceder ao IAM
    2. Selecione o projeto.
    3. Clique em Conceder acesso.
    4. No campo Novos responsáveis, introduza o identificador do utilizador. Normalmente, este é o endereço de email de uma Conta Google.

    5. Na lista Selecionar uma função, selecione uma função.
    6. Para conceder funções adicionais, clique em Adicionar outra função e adicione cada função adicional.
    7. Clique em Guardar.
    8. Prepare o ambiente

      Nesta secção, aprovisiona os recursos de que precisa para implementar o TGI e o modelo para as suas cargas de trabalho de inferência e preparação.

      Aceda ao modelo

      Para aceder aos modelos Gemma para implementação no GKE, primeiro tem de assinar o contrato de consentimento de licença e, em seguida, gerar um token de acesso do Hugging Face.

      1. Assine o contrato de consentimento de licença. Aceda à página de consentimento do modelo, valide o consentimento através da sua conta do Hugging Face e aceite os termos do modelo.
      2. Gere uma chave de acesso. Para aceder ao modelo através do Hugging Face, precisa de um token do Hugging Face. Siga estes passos para gerar um novo token se ainda não tiver um:

        1. Clique em O seu perfil > Definições > Tokens de acesso.
        2. Selecione Novo token.
        3. Especifique um nome à sua escolha e uma função de, pelo menos, Read.
        4. Selecione Gerar um token.
        5. Copie o token gerado para a área de transferência.

      Inicie o Cloud Shell

      Neste tutorial, vai usar o Cloud Shell para gerir recursos alojados no Google Cloud. O Cloud Shell vem pré-instalado com o software de que precisa para este tutorial, incluindo o kubectl, a CLI gcloud e o Terraform.

      Para configurar o seu ambiente com o Cloud Shell, siga estes passos:

      1. Na Google Cloud consola, inicie uma sessão do Cloud Shell 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 consola. Google Cloud

      2. Defina as variáveis de ambiente predefinidas:

        gcloud config set project PROJECT_ID
        export PROJECT_ID=$(gcloud config get project)
        

        Substitua PROJECT_ID pelo seu Google Cloud ID do projeto.

      3. Clone o exemplo de código do GitHub. No Cloud Shell, execute os seguintes comandos:

        git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples/
        cd kubernetes-engine-samples/ai-ml/mix-train-and-inference
        export EXAMPLE_HOME=$(pwd)
        

      Crie um cluster do GKE

      Pode usar um cluster do Autopilot ou Standard para as suas cargas de trabalho mistas. Recomendamos que use um cluster do Autopilot para uma experiência do Kubernetes totalmente gerida. Para escolher o modo de funcionamento do GKE mais adequado às suas cargas de trabalho, consulte o artigo Escolha um modo de funcionamento do GKE.

      Piloto automático

      1. Defina as variáveis de ambiente predefinidas no Cloud Shell:

        export HF_TOKEN=HF_TOKEN
        export REGION=REGION
        export CLUSTER_NAME="llm-cluster"
        export PROJECT_NUMBER=$(gcloud projects list \
            --filter="$(gcloud config get-value project)" \
            --format="value(PROJECT_NUMBER)")
        export MODEL_BUCKET="model-bucket-$PROJECT_ID"
        

        Substitua os seguintes valores:

        • HF_TOKEN: o token do Hugging Face que gerou anteriormente.
        • REGION: uma região que suporta o tipo de acelerador que quer usar, por exemplo, us-central1 para a GPU L4.

        Pode ajustar a variável MODEL_BUCKET, que representa o contentor do Cloud Storage onde armazena as ponderações do modelo com aprendizagem.

      2. Crie um cluster do Autopilot:

        gcloud container clusters create-auto ${CLUSTER_NAME} \
            --project=${PROJECT_ID} \
            --location=${REGION} \
            --release-channel=rapid
        
      3. Crie o contentor do Cloud Storage para a tarefa de ajuste preciso:

        gcloud storage buckets create gs://${MODEL_BUCKET} \
            --location ${REGION} \
            --uniform-bucket-level-access
        
      4. Para conceder acesso ao contentor do Cloud Storage, execute este comando:

        gcloud storage buckets add-iam-policy-binding "gs://$MODEL_BUCKET" \
            --role=roles/storage.objectAdmin \
            --member=principal://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$PROJECT_ID.svc.id.goog/subject/ns/llm/sa/default \
            --condition=None
        
      5. Para obter credenciais de autenticação para o cluster, execute este comando:

        gcloud container clusters get-credentials llm-cluster \
            --location=$REGION \
            --project=$PROJECT_ID
        
      6. Crie um espaço de nomes para as suas implementações. No Cloud Shell, execute o seguinte comando:

        kubectl create ns llm
        

      Standard

      1. Defina as variáveis de ambiente predefinidas no Cloud Shell:

        export HF_TOKEN=HF_TOKEN
        export REGION=REGION
        export CLUSTER_NAME="llm-cluster"
        export GPU_POOL_MACHINE_TYPE="g2-standard-24"
        export GPU_POOL_ACCELERATOR_TYPE="nvidia-l4"
        export PROJECT_NUMBER=$(gcloud projects list \
            --filter="$(gcloud config get-value project)" \
            --format="value(PROJECT_NUMBER)")
        export MODEL_BUCKET="model-bucket-$PROJECT_ID"
        

        Substitua os seguintes valores:

        • HF_TOKEN: o token do Hugging Face que gerou anteriormente.
        • REGION: a região que suporta o tipo de acelerador que quer usar, por exemplo, us-central1 para a GPU L4.

        Pode ajustar estas variáveis:

        • GPU_POOL_MACHINE_TYPE: a série de máquinas do node pool que quer usar na região selecionada. Este valor depende do tipo de acelerador que selecionou. Para saber mais, consulte o artigo Limitações da utilização de GPUs no GKE. Por exemplo, este tutorial usa o g2-standard-24 com duas GPUs anexadas por nó. Para ver a lista mais atualizada de GPUs disponíveis, consulte o artigo GPUs para cargas de trabalho de computação.
        • GPU_POOL_ACCELERATOR_TYPE: o tipo de acelerador suportado na região selecionada. Por exemplo, este tutorial usa nvidia-l4. Para ver a lista mais recente de GPUs disponíveis, consulte o artigo GPUs para cargas de trabalho de computação.
        • MODEL_BUCKET: o contentor do Cloud Storage onde armazena os pesos do modelo preparado.
      2. Criar um cluster padrão:

        gcloud container clusters create ${CLUSTER_NAME} \
            --project=${PROJECT_ID} \
            --location=${REGION} \
            --workload-pool=${PROJECT_ID}.svc.id.goog \
            --release-channel=rapid \
            --machine-type=e2-standard-4 \
            --addons GcsFuseCsiDriver \
            --num-nodes=1
        
      3. Crie o node pool de GPU para cargas de trabalho de inferência e ajuste preciso:

        gcloud container node-pools create gpupool \
            --accelerator type=${GPU_POOL_ACCELERATOR_TYPE},count=2,gpu-driver-version=latest \
            --project=${PROJECT_ID} \
            --location=${REGION} \
            --node-locations=${REGION}-a \
            --cluster=${CLUSTER_NAME} \
            --machine-type=${GPU_POOL_MACHINE_TYPE} \
            --num-nodes=3
        
      4. Crie o contentor do Cloud Storage para a tarefa de ajuste preciso:

        gcloud storage buckets create gs://${MODEL_BUCKET} \
            --location ${REGION} \
            --uniform-bucket-level-access
        
      5. Para conceder acesso ao contentor do Cloud Storage, execute este comando:

        gcloud storage buckets add-iam-policy-binding "gs://$MODEL_BUCKET" \
            --role=roles/storage.objectAdmin \
            --member=principal://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$PROJECT_ID.svc.id.goog/subject/ns/llm/sa/default \
            --condition=None
        
      6. Para obter credenciais de autenticação para o cluster, execute este comando:

        gcloud container clusters get-credentials llm-cluster \
            --location=$REGION \
            --project=$PROJECT_ID
        
      7. Crie um espaço de nomes para as suas implementações. No Cloud Shell, execute o seguinte comando:

        kubectl create ns llm
        

      Crie um segredo do Kubernetes para as credenciais do Hugging Face

      Para criar um segredo do Kubernetes que contenha o token do Hugging Face, execute o seguinte comando:

      kubectl create secret generic hf-secret \
          --from-literal=hf_api_token=$HF_TOKEN \
          --dry-run=client -o yaml | kubectl apply --namespace=llm --filename=-
      

      Configure o Kueue

      Neste tutorial, o Kueue é o gestor de recursos central, o que permite a partilha eficiente de GPUs entre as cargas de trabalho de preparação e publicação. O Kueue consegue isto definindo requisitos de recursos ("sabores"), dando prioridade às cargas de trabalho através de filas (com tarefas de publicação com prioridade sobre o treino) e atribuindo dinamicamente recursos com base na procura e na prioridade. Este tutorial usa o tipo de recurso Workload para agrupar as cargas de trabalho de inferência e ajuste fino, respetivamente.

      A funcionalidade de preemptção do Kueue garante que as cargas de trabalho de fornecimento de alta prioridade têm sempre os recursos necessários, pausando ou desalojando tarefas de preparação de prioridade mais baixa quando os recursos são escassos.

      Para controlar a implementação do servidor de inferência com o Kueue, ative a integração do pod e configure o managedJobsNamespaceSelector para excluir os espaços de nomes kube-system e kueue-system.

      1. No diretório /kueue, veja o código em kustomization.yaml. Este manifesto instala o gestor de recursos Kueue com configurações personalizadas.

        apiVersion: kustomize.config.k8s.io/v1beta1
        kind: Kustomization
        resources:
        - https://github.com/kubernetes-sigs/kueue/releases/download/v0.12.3/manifests.yaml
        patches:
        - path: patch.yaml
          target:
            version: v1
            kind: ConfigMap
            name: kueue-manager-config
        
      2. No diretório /kueue, veja o código em patch.yaml. Este ConfigMap personaliza o Kueue para excluir a gestão de pods nos espaços de nomes kube-system e kueue-system.

        apiVersion: v1
        kind: ConfigMap
        metadata:
          name: kueue-manager-config
        data:
          controller_manager_config.yaml: |
            apiVersion: config.kueue.x-k8s.io/v1beta1
            kind: Configuration
            health:
              healthProbeBindAddress: :8081
            metrics:
              bindAddress: :8080
            # enableClusterQueueResources: true
            webhook:
              port: 9443
            leaderElection:
              leaderElect: true
              resourceName: c1f6bfd2.kueue.x-k8s.io
            controller:
              groupKindConcurrency:
                Job.batch: 5
                Pod: 5
                Workload.kueue.x-k8s.io: 5
                LocalQueue.kueue.x-k8s.io: 1
                ClusterQueue.kueue.x-k8s.io: 1
                ResourceFlavor.kueue.x-k8s.io: 1
            clientConnection:
              qps: 50
              burst: 100
            #pprofBindAddress: :8083
            #waitForPodsReady:
            #  enable: false
            #  timeout: 5m
            #  blockAdmission: false
            #  requeuingStrategy:
            #    timestamp: Eviction
            #    backoffLimitCount: null # null indicates infinite requeuing
            #    backoffBaseSeconds: 60
            #    backoffMaxSeconds: 3600
            #manageJobsWithoutQueueName: true
            managedJobsNamespaceSelector:
              matchExpressions:
                - key: kubernetes.io/metadata.name
                  operator: NotIn
                  values: [ kube-system, kueue-system ]
            #internalCertManagement:
            #  enable: false
            #  webhookServiceName: ""
            #  webhookSecretName: ""
            integrations:
              frameworks:
              - "batch/job"
              - "kubeflow.org/mpijob"
              - "ray.io/rayjob"
              - "ray.io/raycluster"
              - "jobset.x-k8s.io/jobset"
              - "kubeflow.org/paddlejob"
              - "kubeflow.org/pytorchjob"
              - "kubeflow.org/tfjob"
              - "kubeflow.org/xgboostjob"
              - "kubeflow.org/jaxjob"
              - "workload.codeflare.dev/appwrapper"
              - "pod"
            #  - "deployment" # requires enabling pod integration
            #  - "statefulset" # requires enabling pod integration
            #  - "leaderworkerset.x-k8s.io/leaderworkerset" # requires enabling pod integration
            #  externalFrameworks:
            #  - "Foo.v1.example.com"
            #fairSharing:
            #  enable: true
            #  preemptionStrategies: [LessThanOrEqualToFinalShare, LessThanInitialShare]
            #admissionFairSharing:
            #  usageHalfLifeTime: "168h" # 7 days
            #  usageSamplingInterval: "5m"
            #  resourceWeights: # optional, defaults to 1 for all resources if not specified
            #    cpu: 0    # if you want to completely ignore cpu usage
            #    memory: 0 # ignore completely memory usage
            #    example.com/gpu: 100 # and you care only about GPUs usage
            #resources:
            #  excludeResourcePrefixes: []
            #  transformations:
            #  - input: nvidia.com/mig-4g.5gb
            #    strategy: Replace | Retain
            #    outputs:
            #      example.com/accelerator-memory: 5Gi
            #      example.com/accelerator-gpc: 4
            #objectRetentionPolicies:
            #  workloads:
            #    afterFinished: null # null indicates infinite retention, 0s means no retention at all
            #    afterDeactivatedByKueue: null # null indicates infinite retention, 0s means no retention at all
        
      3. No Cloud Shell, execute o seguinte comando para instalar o Kueue:

        cd ${EXAMPLE_HOME}
        kubectl kustomize kueue |kubectl apply --server-side --filename=-
        

        Aguarde até que os pods do Kueue estejam prontos:

        watch kubectl --namespace=kueue-system get pods
        

        O resultado deve ser semelhante ao seguinte:

        NAME                                        READY   STATUS    RESTARTS   AGE
        kueue-controller-manager-bdc956fc4-vhcmx    1/1     Running   0          3m15s
        
      4. No diretório /workloads, veja os ficheiros flavors.yaml, cluster-queue.yaml e local-queue.yaml. Estes manifestos especificam como o Kueue gere as quotas de recursos:

        ResourceFlavor

        Este manifesto define um ResourceFlavor predefinido no Kueue para a gestão de recursos.

        apiVersion: kueue.x-k8s.io/v1beta1
        kind: ResourceFlavor
        metadata:
          name: default-flavor
        

        ClusterQueue

        Este manifesto configura uma Kueue ClusterQueue com limites de recursos para CPU, memória e GPU.

        Este tutorial usa nós com duas GPUs Nvidia L4 anexadas, com o tipo de nó correspondente g2-standard-24, que oferece 24 vCPUs e 96 GB de RAM. O código de exemplo mostra como limitar a utilização de recursos da sua carga de trabalho a um máximo de seis GPUs.

        O campo preemption na configuração ClusterQueue faz referência às PriorityClasses para determinar que pods podem ser antecipados quando os recursos são escassos.

        apiVersion: kueue.x-k8s.io/v1beta1
        kind: ClusterQueue
        metadata:
          name: "cluster-queue"
        spec:
          namespaceSelector: {} # match all.
          preemption:
            reclaimWithinCohort: LowerPriority
            withinClusterQueue: LowerPriority
          resourceGroups:
          - coveredResources: [ "cpu", "memory", "nvidia.com/gpu", "ephemeral-storage" ]
            flavors:
            - name: default-flavor
              resources:
              - name: "cpu"
                nominalQuota: 72
              - name: "memory"
                nominalQuota: 288Gi
              - name: "nvidia.com/gpu"
                nominalQuota: 6
              - name: "ephemeral-storage"
                nominalQuota: 200Gi
        

        LocalQueue

        Este manifesto cria uma LocalQueue do Kueue denominada lq no espaço de nomes llm.

        apiVersion: kueue.x-k8s.io/v1beta1
        kind: LocalQueue
        metadata:
          namespace: llm # LocalQueue under llm namespace 
          name: lq
        spec:
          clusterQueue: cluster-queue # Point to the ClusterQueue
        
      5. Veja os ficheiros default-priorityclass.yaml, low-priorityclass.yaml e high-priorityclass.yaml. Estes manifestos definem os objetos PriorityClass para o agendamento do Kubernetes.

        Prioridade predefinida

        apiVersion: scheduling.k8s.io/v1
        kind: PriorityClass
        metadata:
          name: default-priority-nonpreempting
        value: 10
        preemptionPolicy: Never
        globalDefault: true
        description: "This priority class will not cause other pods to be preempted."
        

        Prioridade baixa

        apiVersion: scheduling.k8s.io/v1
        kind: PriorityClass
        metadata:
          name: low-priority-preempting
        value: 20
        preemptionPolicy: PreemptLowerPriority
        globalDefault: false
        description: "This priority class will cause pods with lower priority to be preempted."
        

        Prioridade alta

        apiVersion: scheduling.k8s.io/v1
        kind: PriorityClass
        metadata:
          name: high-priority-preempting
        value: 30
        preemptionPolicy: PreemptLowerPriority
        globalDefault: false
        description: "This high priority class will cause other pods to be preempted."
        
      6. Crie os objetos Kueue e Kubernetes executando estes comandos para aplicar os manifestos correspondentes.

        cd ${EXAMPLE_HOME}/workloads
        kubectl apply --filename=flavors.yaml
        kubectl apply --filename=default-priorityclass.yaml
        kubectl apply --filename=high-priorityclass.yaml
        kubectl apply --filename=low-priorityclass.yaml
        kubectl apply --filename=cluster-queue.yaml
        kubectl apply --filename=local-queue.yaml --namespace=llm
        

      Implemente o servidor de inferência TGI

      Nesta secção, implementa o contentor TGI para publicar o modelo Gemma 2.

      1. No diretório /workloads, veja o ficheiro tgi-gemma-2-9b-it-hp.yaml. Este manifesto define uma implementação do Kubernetes para implementar o tempo de execução de publicação do TGI e o modelo gemma-2-9B-it. Uma implementação é um objeto da API Kubernetes que lhe permite executar várias réplicas de pods distribuídas entre os nós num cluster.

        A implementação dá prioridade às tarefas de inferência e usa duas GPUs para o modelo. Usa o paralelismo de tensores, definindo a variável de ambiente NUM_SHARD, para ajustar o modelo à memória da GPU.

        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: tgi-gemma-deployment
          labels:
            app: gemma-server
        spec:
          replicas: 1
          selector:
            matchLabels:
              app: gemma-server
          template:
            metadata:
              labels:
                app: gemma-server
                ai.gke.io/model: gemma-2-9b-it
                ai.gke.io/inference-server: text-generation-inference
                examples.ai.gke.io/source: user-guide
                kueue.x-k8s.io/queue-name: lq
            spec:
              priorityClassName: high-priority-preempting
              containers:
              - name: inference-server
                image: us-docker.pkg.dev/deeplearning-platform-release/gcr.io/huggingface-text-generation-inference-cu121.2-1.ubuntu2204.py310
                resources:
                  requests:
                    cpu: "4"
                    memory: "30Gi"
                    ephemeral-storage: "30Gi"
                    nvidia.com/gpu: "2"
                  limits:
                    cpu: "4"
                    memory: "30Gi"
                    ephemeral-storage: "30Gi"
                    nvidia.com/gpu: "2"
                env:
                - name: AIP_HTTP_PORT
                  value: '8000'
                - name: NUM_SHARD
                  value: '2'
                - name: MODEL_ID
                  value: google/gemma-2-9b-it
                - name: HUGGING_FACE_HUB_TOKEN
                  valueFrom:
                    secretKeyRef:
                      name: hf-secret
                      key: hf_api_token
                volumeMounts:
                - mountPath: /dev/shm
                  name: dshm
              volumes:
              - name: dshm
                emptyDir:
                  medium: Memory
              nodeSelector:
                cloud.google.com/gke-accelerator: "nvidia-l4"
        ---
        apiVersion: v1
        kind: Service
        metadata:
          name: llm-service
        spec:
          selector:
            app: gemma-server
          type: ClusterIP
          ports:
          - protocol: TCP
            port: 8000
            targetPort: 8000
        
      2. Aplique o manifesto executando o seguinte comando:

        kubectl apply --filename=tgi-gemma-2-9b-it-hp.yaml --namespace=llm
        

        A operação de implementação demora alguns minutos a concluir.

      3. Para verificar se o GKE criou a implementação com êxito, execute o seguinte comando:

        kubectl --namespace=llm get deployment
        

        O resultado deve ser semelhante ao seguinte:

        NAME                   READY   UP-TO-DATE   AVAILABLE   AGE
        tgi-gemma-deployment   1/1     1            1           5m13s
        

      Valide a gestão de quotas do Kueue

      Nesta secção, confirma que o Kueue está a aplicar corretamente a quota de GPU para a sua implementação.

      1. Para verificar se o Kueue tem conhecimento da sua implementação, execute este comando para obter o estado dos objetos de carga de trabalho:

        kubectl --namespace=llm get workloads
        

        O resultado deve ser semelhante ao seguinte:

        NAME                                              QUEUE   RESERVED IN     ADMITTED   FINISHED   AGE
        pod-tgi-gemma-deployment-6bf9ffdc9b-zcfrh-84f19   lq      cluster-queue   True                  8m23s
        
      2. Para testar a substituição dos limites de quota, dimensione a implementação para quatro réplicas:

        kubectl scale --replicas=4 deployment/tgi-gemma-deployment --namespace=llm
        
      3. Execute o seguinte comando para ver o número de réplicas que o GKE implementa:

        kubectl get workloads --namespace=llm
        

        O resultado deve ser semelhante ao seguinte:

        NAME                                              QUEUE   RESERVED IN     ADMITTED   FINISHED   AGE
        pod-tgi-gemma-deployment-6cb95cc7f5-5thgr-3f7d4   lq      cluster-queue   True                  14s
        pod-tgi-gemma-deployment-6cb95cc7f5-cbxg2-d9fe7   lq      cluster-queue   True                  5m41s
        pod-tgi-gemma-deployment-6cb95cc7f5-tznkl-80f6b   lq                                            13s
        pod-tgi-gemma-deployment-6cb95cc7f5-wd4q9-e4302   lq      cluster-queue   True                  13s
        

        A saída mostra que apenas três pods são admitidos devido à quota de recursos que o Kueue aplica.

      4. Execute o seguinte comando para apresentar os pods no espaço de nomes llm:

        kubectl get pod --namespace=llm
        

        O resultado deve ser semelhante ao seguinte:

        NAME                                    READY   STATUS            RESTARTS   AGE
        tgi-gemma-deployment-7649884d64-6j256   1/1     Running           0          4m45s
        tgi-gemma-deployment-7649884d64-drpvc   0/1     SchedulingGated   0          7s
        tgi-gemma-deployment-7649884d64-thdkq   0/1     Pending           0          7s
        tgi-gemma-deployment-7649884d64-znvpb   0/1     Pending           0          7s
        
      5. Agora, reduza a implementação para 1. Este passo é necessário antes de implementar a tarefa de ajuste fino. Caso contrário, não é admitida porque a tarefa de inferência tem prioridade.

        kubectl scale --replicas=1 deployment/tgi-gemma-deployment --namespace=llm
        

      Explicação do comportamento

      O exemplo de escalabilidade resulta em apenas três réplicas (apesar da escalabilidade para quatro) devido ao limite de quota da GPU que definiu na configuração ClusterQueue. A secção spec.resourceGroups de ClusterQueue define uma quota nominal de "6" para nvidia.com/gpu. A implementação especifica que cada pod requer "2" GPUs. Por conseguinte, a ClusterQueue só pode acomodar um máximo de três réplicas da implementação de cada vez (uma vez que 3 réplicas * 2 GPUs por réplica = 6 GPUs, que é a quota total).

      Quando tenta dimensionar para quatro réplicas, o Kueue reconhece que esta ação excederia a quota de GPUs e impede o agendamento da quarta réplica. Isto é indicado pelo estado SchedulingGated do quarto agrupamento. Este comportamento demonstra a aplicação da quota de recursos do Kueue.

      Implemente a tarefa de preparação

      Nesta secção, implementa uma tarefa de ajuste fino de prioridade inferior para um modelo Gemma 2 que requer quatro GPUs em dois pods. Um controlador de tarefas no Kubernetes cria um ou mais pods e garante que executam com êxito uma tarefa específica.

      Esta tarefa vai usar a quota de GPU restante na ClusterQueue. A tarefa usa uma imagem pré-criada e guarda pontos de verificação para permitir o reinício a partir de resultados intermédios.

      A tarefa de ajuste fino usa o conjunto de dados b-mc2/sql-create-context. Pode encontrar a origem da tarefa de ajuste na repositório.

      1. Veja o ficheiro fine-tune-l4.yaml. Este manifesto define a tarefa de ajuste preciso.

        apiVersion: v1
        kind: Service
        metadata:
          name: headless-svc-l4
        spec:
          clusterIP: None # clusterIP must be None to create a headless service
          selector:
            job-name: finetune-gemma-l4 # must match Job name
        ---
        apiVersion: batch/v1
        kind: Job
        metadata:
          name: finetune-gemma-l4
          labels:
            kueue.x-k8s.io/queue-name: lq
        spec:
          backoffLimit: 4
          completions: 2
          parallelism: 2
          completionMode: Indexed
          suspend: true # Set to true to allow Kueue to control the Job when it starts
          template:
            metadata:
              labels:
                app: finetune-job
              annotations:
                gke-gcsfuse/volumes: "true"
                gke-gcsfuse/memory-limit: "35Gi"
            spec:
              priorityClassName: low-priority-preempting
              containers:
              - name: gpu-job
                imagePullPolicy: Always
                image: us-docker.pkg.dev/google-samples/containers/gke/gemma-fine-tuning:v1.0.0
                ports:
                - containerPort: 29500
                resources:
                  requests:
                    nvidia.com/gpu: "2"
                  limits:
                    nvidia.com/gpu: "2"
                command:
                - bash
                - -c
                - |
                  accelerate launch \
                  --config_file fsdp_config.yaml \
                  --debug \
                  --main_process_ip finetune-gemma-l4-0.headless-svc-l4 \
                  --main_process_port 29500 \
                  --machine_rank ${JOB_COMPLETION_INDEX} \
                  --num_processes 4 \
                  --num_machines 2 \
                  fine_tune.py
                env:
                - name: "EXPERIMENT"
                  value: "finetune-experiment"
                - name: MODEL_NAME
                  value: "google/gemma-2-2b"
                - name: NEW_MODEL
                  value: "gemma-ft"
                - name: MODEL_PATH
                  value: "/model-data/model-gemma2/experiment"
                - name: DATASET_NAME
                  value: "b-mc2/sql-create-context"
                - name: DATASET_LIMIT
                  value: "5000"
                - name: EPOCHS
                  value: "1"
                - name: GRADIENT_ACCUMULATION_STEPS
                  value: "2"
                - name: CHECKPOINT_SAVE_STEPS
                  value: "10"
                - name: HF_TOKEN
                  valueFrom:
                    secretKeyRef:
                      name: hf-secret
                      key: hf_api_token
                volumeMounts:
                - mountPath: /dev/shm
                  name: dshm
                - name: gcs-fuse-csi-ephemeral
                  mountPath: /model-data
                  readOnly: false
              nodeSelector:
                cloud.google.com/gke-accelerator: nvidia-l4
              restartPolicy: OnFailure
              serviceAccountName: default
              subdomain: headless-svc-l4
              terminationGracePeriodSeconds: 60
              volumes:
              - name: dshm
                emptyDir:
                  medium: Memory
              - name: gcs-fuse-csi-ephemeral
                csi:
                  driver: gcsfuse.csi.storage.gke.io
                  volumeAttributes:
                    bucketName: <MODEL_BUCKET>
                    mountOptions: "implicit-dirs"
                    gcsfuseLoggingSeverity: warning
        
      2. Aplique o manifesto para criar a tarefa de ajuste fino:

        cd ${EXAMPLE_HOME}/workloads
        
        sed -e "s/<MODEL_BUCKET>/$MODEL_BUCKET/g" \
            -e "s/<PROJECT_ID>/$PROJECT_ID/g" \
            -e "s/<REGION>/$REGION/g" \
            fine-tune-l4.yaml |kubectl apply --filename=- --namespace=llm
        
      3. Verifique se as implementações estão em execução. Para verificar o estado dos objetos Workload, execute o seguinte comando:

        kubectl get workloads --namespace=llm
        

        O resultado deve ser semelhante ao seguinte:

        NAME                                              QUEUE   RESERVED IN     ADMITTED   FINISHED   AGE
        job-finetune-gemma-l4-3316f                       lq      cluster-queue   True                  29m
        pod-tgi-gemma-deployment-6cb95cc7f5-cbxg2-d9fe7   lq      cluster-queue   True                  68m
        

        Em seguida, veja os pods no espaço de nomes llm executando este comando:

        kubectl get pod --namespace=llm
        

        O resultado deve ser semelhante ao seguinte:

        NAME                                    READY   STATUS    RESTARTS   AGE
        finetune-gemma-l4-0-vcxpz               2/2     Running   0          31m
        finetune-gemma-l4-1-9ppt9               2/2     Running   0          31m
        tgi-gemma-deployment-6cb95cc7f5-cbxg2   1/1     Running   0          70m
        

        O resultado mostra que o Kueue admite a execução do seu trabalho de ajuste preciso e dos pods do servidor de inferência, reservando os recursos corretos com base nos limites de quota especificados.

      4. Veja os registos de saída para verificar se a tarefa de ajuste fino guarda pontos de verificação no contentor do Cloud Storage. A tarefa de ajuste preciso demora cerca de 10 minutos antes de começar a guardar o primeiro ponto de verificação.

        kubectl logs --namespace=llm --follow --selector=app=finetune-job
        

        O resultado do primeiro ponto de verificação guardado tem um aspeto semelhante ao seguinte:

        {"name": "finetune", "thread": 133763559483200, "threadName": "MainThread", "processName": "MainProcess", "process": 33, "message": "Fine tuning started", "timestamp": 1731002351.0016131, "level": "INFO", "runtime": 451579.89835739136}
        …
        {"name": "accelerate.utils.fsdp_utils", "thread": 136658669348672, "threadName": "MainThread", "processName": "MainProcess", "process": 32, "message": "Saving model to /model-data/model-gemma2/experiment/checkpoint-10/pytorch_model_fsdp_0", "timestamp": 1731002386.1763802, "level": "INFO", "runtime": 486753.8924217224}
        

      Teste a preemptividade e a atribuição dinâmica do Kueue na sua carga de trabalho mista

      Nesta secção, simula um cenário em que a carga do servidor de inferência aumenta, o que requer o aumento da escala. Este cenário demonstra como o Kueue dá prioridade ao servidor de inferência de alta prioridade suspendendo e antecipando a tarefa de ajuste preciso de prioridade inferior quando os recursos estão limitados.

      1. Execute o seguinte comando para dimensionar as réplicas do servidor de inferência para duas:

        kubectl scale --replicas=2 deployment/tgi-gemma-deployment --namespace=llm
        
      2. Verifique o estado dos objetos Workload:

        kubectl get workloads --namespace=llm
        

        O resultado tem um aspeto semelhante ao seguinte:

        NAME                                              QUEUE   RESERVED IN     ADMITTED   FINISHED   AGE
        job-finetune-gemma-l4-3316f                       lq                      False                 32m
        pod-tgi-gemma-deployment-6cb95cc7f5-cbxg2-d9fe7   lq      cluster-queue   True                  70m
        pod-tgi-gemma-deployment-6cb95cc7f5-p49sh-167de   lq      cluster-queue   True                  14s
        

        O resultado mostra que a tarefa de ajuste fino já não é admitida porque as réplicas do servidor de inferência aumentadas estão a usar a quota de GPU disponível.

      3. Verifique o estado da tarefa de ajuste preciso:

        kubectl get job --namespace=llm
        

        O resultado tem um aspeto semelhante ao seguinte, o que indica que o estado da tarefa de ajuste preciso está agora suspenso:

        NAME                STATUS      COMPLETIONS   DURATION   AGE
        finetune-gemma-l4   Suspended   0/2                      33m
        
      4. Execute o seguinte comando para inspecionar os seus pods:

        kubectl get pod --namespace=llm
        

        O resultado é semelhante ao seguinte, o que indica que o Kueue terminou os pods de tarefas de ajuste fino para libertar recursos para a implementação do servidor de inferência de prioridade mais elevada.

        NAME                                    READY   STATUS              RESTARTS   AGE
        tgi-gemma-deployment-6cb95cc7f5-cbxg2   1/1     Running             0          72m
        tgi-gemma-deployment-6cb95cc7f5-p49sh   0/1     ContainerCreating   0          91s
        
      5. Em seguida, teste o cenário em que a carga do servidor de inferência diminui e os respetivos pods são reduzidos. Execute o seguinte comando:

        kubectl scale --replicas=1 deployment/tgi-gemma-deployment --namespace=llm
        

        Execute o seguinte comando para apresentar os objetos Workload:

        kubectl get workloads --namespace=llm
        

        O resultado é semelhante ao seguinte, o que indica que uma das implementações do servidor de inferência foi terminada e que a tarefa de ajuste fino foi novamente admitida.

        NAME                                              QUEUE   RESERVED IN     ADMITTED   FINISHED   AGE
        job-finetune-gemma-l4-3316f                       lq      cluster-queue   True                  37m
        pod-tgi-gemma-deployment-6cb95cc7f5-cbxg2-d9fe7   lq      cluster-queue   True                  75m
        
      6. Execute este comando para apresentar as tarefas:

        kubectl get job --namespace=llm
        

        O resultado tem um aspeto semelhante ao seguinte, o que indica que o trabalho de ajuste fino está a ser executado novamente, retomando a partir do ponto de verificação mais recente disponível.

        NAME                STATUS    COMPLETIONS   DURATION   AGE
        finetune-gemma-l4   Running   0/2           2m11s      38m
        

      Limpar

      Para evitar incorrer em custos na sua conta do Google Cloud pelos recursos usados neste tutorial, elimine o projeto que contém os recursos ou mantenha o projeto e elimine os recursos individuais.

      Elimine os recursos implementados

      Para evitar incorrer em custos na sua Google Cloud conta pelos recursos que criou neste guia, execute os seguintes comandos:

      gcloud storage rm --recursive gs://${MODEL_BUCKET}
      gcloud container clusters delete ${CLUSTER_NAME} --location ${REGION}
      

      O que se segue?