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:

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 as credenciais de autenticação do 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 as credenciais de autenticação do 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 apresentação de alta prioridade têm sempre os recursos necessários, pausando ou desalojando tarefas de preparação de prioridade inferior 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 nominalQuota 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 baixa prioridade 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:

    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