Otimizando cargas de trabalho de IA com GPUs NVIDA, Time Slicing e Karpenter (Parte 2)


Introdução: Superando desafios de gerenciamento de GPU

Na Parte 1 desta série de blogs, exploramos os desafios de hospedar grandes modelos de linguagem (LLMs) em cargas de trabalho baseadas em CPU em um cluster EKS. Discutimos as ineficiências associadas ao uso de CPUs para tais tarefas, principalmente devido aos grandes tamanhos dos modelos e às velocidades de inferência mais lentas. A introdução de recursos de GPU ofereceu um aumento significativo de desempenho, mas também trouxe a necessidade de um gerenciamento eficiente desses recursos de alto custo.

Nesta segunda parte, nos aprofundaremos em como otimizar o uso da GPU para essas cargas de trabalho. Cobriremos as seguintes áreas principais:

  • Configuração do plug-in de dispositivo NVIDIA: esta seção explicará a importância do plug-in de dispositivo NVIDIA para Kubernetes, detalhando sua função na descoberta, alocação e isolamento de recursos.
  • Fatiação do tempo: Discutiremos como a divisão de tempo permite que vários processos compartilhem recursos de GPU de maneira eficaz, garantindo a utilização máxima.
  • Escalonamento automático de nós com Karpenter: Esta seção descreverá como o Karpenter gerencia dinamicamente o dimensionamento de nós com base na demanda em tempo actual, otimizando a utilização de recursos e reduzindo custos.

Desafios abordados

  1. Gerenciamento eficiente de GPU: Garantir que as GPUs sejam totalmente utilizadas para justificar seu alto custo.
  2. Tratamento de simultaneidade: permitindo que várias cargas de trabalho compartilhem recursos de GPU de maneira eficaz.
  3. Dimensionamento Dinâmico: ajusta automaticamente o número de nós com base nas demandas da carga de trabalho.

Seção 1: Introdução ao plug-in de dispositivo NVIDIA

O plugin de dispositivo NVIDIA para Kubernetes é um componente que simplifica o gerenciamento e o uso de GPUs NVIDIA em clusters Kubernetes. Ele permite que o Kubernetes reconheça e aloque recursos de GPU para pods, permitindo cargas de trabalho aceleradas por GPU.

Por que precisamos do plug-in de dispositivo NVIDIA

  • Descoberta de recursos: detecta automaticamente recursos de GPU NVIDIA em cada nó.
  • Alocação de Recursos: gerencia a distribuição de recursos de GPU para pods com base em suas solicitações.
  • Isolamento: Garante a utilização segura e eficiente dos recursos da GPU entre diferentes pods.

O plugin de dispositivo NVIDIA simplifica o gerenciamento de GPU em clusters Kubernetes. Automatiza a instalação do Controlador NVIDIA, equipment de ferramentas de contêinere CUDAgarantindo que os recursos da GPU estejam disponíveis para cargas de trabalho sem exigir configuração handbook.

  • Controlador NVIDIA: necessário para operações básicas de GPU e nvidia-smi. Interface com o {hardware} da GPU. A captura de tela abaixo mostra a saída do comando nvidia-smi, que mostra informações importantes como versão do driver, versão CUDA e configuração detalhada da GPU, confirmando se a GPU está configurada corretamente e pronta para uso

Otimizando cargas de trabalho de IA com GPUs NVIDA, Time Slicing e Karpenter (Parte 2)

  • Equipment de ferramentas de contêiner NVIDIA: necessário para usar GPUs com containerd. Abaixo podemos ver a versão do equipment de ferramentas do contêiner e o standing do serviço em execução na instância
#Put in Model 
rpm -qa | grep -i nvidia-container-toolkit 
nvidia-container-toolkit-base-1.15.0-1.x86_64 
nvidia-container-toolkit-1.15.0-1.x86_64 
  • CUDA: necessário para aplicativos e bibliotecas acelerados por GPU. Abaixo está a saída do comando nvcc, mostrando a versão do CUDA instalada no sistema:
/usr/native/cuda/bin/nvcc --model 
nvcc: NVIDIA (R) Cuda compiler driver 
Copyright (c) 2005-2023 NVIDIA Company 
Constructed on Tue_Aug_15_22:02:13_PDT_2023 
Cuda compilation instruments, launch 12.2, V12.2.140 
Construct cuda_12.2.r12.2/compiler.33191640_0 

Configurando o plug-in de dispositivo NVIDIA

Para garantir que o DaemonSet seja executado exclusivamente em instâncias baseadas em GPU, rotulamos o nó com a chave “nvidia.com/gpu” e o valor “true”. Isto é conseguido usando Afinidade do nó, Seletor de nó e Máculas e tolerâncias.

Vamos agora nos aprofundar em cada um desses componentes.

  • Afinidade do nó: A afinidade do nó permite agendar o pod nos nós com base nos rótulos dos nós obrigatórioDuringSchedulingIgnoredDuringExecution: o agendador não pode agendar o pod a menos que a regra seja atendida e a chave seja “nvidia.com/gpu” e o operador esteja “in” e os valores sejam “true”.
affinity: 
    nodeAffinity: 
        requiredDuringSchedulingIgnoredDuringExecution: 
            nodeSelectorTerms: 
                - matchExpressions: 
                    - key: function.node.kubernetes.io/pci-10de.current 
                      operator: In 
                      values: 
                        - "true" 
                - matchExpressions: 
                    - key: function.node.kubernetes.io/cpu-mannequin.vendor_id 
                      operator: In 
                      values: 
                      - NVIDIA 
                - matchExpressions: 
                    - key: nvidia.com/gpu 
                      operator: In 
                      values: 
                    - "true" 
  • Seletor de nó: NO seletor de ode é o formulário de recomendação mais simples para restrições de seleção de nós nvidia.com/gpu: “verdadeiro”
  • Máculas e tolerâncias: Tolerâncias são adicionadas ao Daemon Set para garantir que ele possa ser agendado nos nós de GPU contaminados (nvidia.com/gpu=true:Noschedule).
kubectl taint node ip-10-20-23-199.us-west-1.compute.inner nvidia.com/gpu=true:Noschedule 
kubectl describe node ip-10-20-23-199.us-west-1.compute.inner | grep -i taint 
Taints: nvidia.com/gpu=true:NoSchedule 

tolerations: 
  - impact: NoSchedule 
    key: nvidia.com/gpu 
    operator: Exists 

Depois de implementar a rotulagem do nó, afinidade, seletor de nó e taints/tolerações, podemos garantir que o Daemon Set seja executado exclusivamente em instâncias baseadas em GPU. Podemos verificar a implantação do plugin do dispositivo NVIDIA usando o seguinte comando:

kubectl get ds -n kube-system 
NAME                                      DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE  NODE SELECTOR                                     AGE 

nvidia-system-plugin                      1         1         1       1            1          nvidia.com/gpu=true                               75d 
nvidia-system-plugin-mps-management-daemon   0         0         0       0            0          nvidia.com/gpu=true,nvidia.com/mps.succesful=true   75d 

Mas o desafio aqui é que as GPUs são muito caras e precisam garantir a utilização máxima das GPUs e nos deixar explorar mais sobre a simultaneidade de GPU.

Simultaneidade de GPU:

Refere-se à capacidade de executar múltiplas tarefas ou threads simultaneamente em uma GPU

  • Processo único: em uma configuração de processo único, apenas um aplicativo ou contêiner usa a GPU por vez. Essa abordagem é simples, mas pode levar à subutilização dos recursos da GPU se o aplicativo não carregar totalmente a GPU.
  • Serviço Multiprocesso (MPS): O Serviço Multiprocesso (MPS) da NVIDIA permite que vários aplicativos CUDA compartilhem uma única GPU simultaneamente, melhorando a utilização da GPU e reduzindo a sobrecarga da alternância de contexto.
  • Fatiamento de tempo: O fatiamento de tempo envolve dividir o tempo da GPU entre diferentes processos, em outras palavras, vários processos se revezam nas GPUs (Spherical Robin context Switching)
  • GPU de múltiplas instâncias (MIG): MIG é um recurso disponível nas GPUs NVIDIA A100 que permite que uma única GPU seja particionada em várias instâncias menores e isoladas, cada uma se comportando como uma GPU separada.
  • Virtualização: a virtualização de GPU permite que uma única GPU física seja compartilhada entre várias máquinas virtuais (VMs) ou contêineres, fornecendo a cada uma delas uma GPU digital.

Seção 2: Implementando Time Slicing para GPUs

A divisão de tempo no contexto de GPUs NVIDIA e Kubernetes refere-se ao compartilhamento de uma GPU física entre vários contêineres ou pods em um cluster Kubernetes. A tecnologia envolve particionar o tempo de processamento da GPU em intervalos menores e alocar esses intervalos para diferentes contêineres ou pods.

  • Alocação de intervalo de tempo: o agendador de GPU aloca intervalos de tempo para cada vGPU configurado na GPU física.
  • Preempção e troca de contexto: no closing do intervalo de tempo de uma vGPU, o agendador da GPU interrompe sua execução, salva seu contexto e alterna para o contexto da próxima vGPU.
  • Troca de contexto: o agendador de GPU garante uma troca de contexto suave entre vGPUs, minimizando a sobrecarga e garantindo o uso eficiente dos recursos da GPU.
  • Conclusão de tarefas: os processos dentro dos contêineres concluem suas tarefas aceleradas por GPU dentro dos intervalos de tempo alocados.
  • Gerenciamento e monitoramento de recursos
  • Liberação de recursos: conforme as tarefas são concluídas, os recursos da GPU são liberados de volta ao Kubernetes para realocação para outros pods ou contêineres

Por que precisamos de redução de tempo

  • Eficiência de custos: garante que GPUs de alto custo não sejam subutilizadas.
  • Simultaneidade: permite que vários aplicativos usem a GPU simultaneamente.

Exemplo de configuração para divisão de tempo

Vamos aplicar a configuração de divisão de tempo usando o mapa de configuração conforme mostrado abaixo. Aqui réplicas: 3 especifica o número de réplicas para recursos de GPU, o que significa que o recurso de GPU pode ser dividido em 3 instâncias de compartilhamento

apiVersion: v1 
form: ConfigMap 
metadata: 
  identify: nvidia-system-plugin 
  namespace: kube-system 
information: 
  any: |- 
    model: v1 
    flags: 
      migStrategy: none 
    sharing: 
      timeSlicing: 
        assets: 
        - identify: nvidia.com/gpu 
          replicas: 3 
#We will confirm the GPU assets obtainable in your nodes utilizing the next command:     
kubectl get nodes -o json | jq -r '.gadgets() | choose(.standing.capability."nvidia.com/gpu" != null) 
| {identify: .metadata.identify, capability: .standing.capability}' 

  "identify": "ip-10-20-23-199.us-west-1.compute.inner", 
  "capability": { 
    "cpu": "4", 
    "ephemeral-storage": "104845292Ki", 
    "hugepages-1Gi": "0", 
    "hugepages-2Mi": "0", 
    "reminiscence": "16069060Ki", 
    "nvidia.com/gpu": "3", 
    "pods": "110" 
  } 

#The above output reveals that the node ip-10-20-23-199.us-west-1. compute.inner has 3 digital GPUs obtainable. 
#We will request GPU assets of their pod specs by setting useful resource limits 
assets: 
      limits: 
        cpu: "1" 
        reminiscence: 2G 
        nvidia.com/gpu: "1" 
      requests: 
        cpu: "1" 
        reminiscence: 2G 
        nvidia.com/gpu: "1" 

No nosso caso, podemos hospedar 3 pods em um único nó ip-10-20-23-199.us-west-1. calcular. Internos e devido à divisão do tempo, esses 3 pods podem usar 3 GPUs virtuais conforme abaixo

As GPUs foram compartilhadas virtualmente entre os pods e podemos ver os PIDS atribuídos para cada um dos processos abaixo.

Agora que otimizamos a GPU no nível do pod, vamos agora nos concentrar na otimização dos recursos da GPU no nível do nó. Podemos conseguir isso usando uma solução de escalonamento automático de cluster chamada carpinteiro. Isso é particularmente importante porque os laboratórios de aprendizagem nem sempre têm carga ou atividade do usuário constante, e as GPUs são extremamente caras. Ao aproveitar carpinteiropodemos aumentar ou diminuir dinamicamente os nós da GPU com base na demanda, garantindo economia e utilização supreme de recursos.

Seção 3: Escalonamento automático de nós com Karpenter

carpinteiro é um gerenciamento de ciclo de vida de nó de código aberto para Kubernetes. Ele automatiza o provisionamento e desprovisionamento de nós com base nas necessidades de agendamento dos pods, permitindo escalonamento eficiente e otimização de custos

  • Provisionamento dinâmico de nós: dimensiona nós automaticamente com base na demanda.
  • Otimiza a utilização de recursos: combina a capacidade do nó com as necessidades da carga de trabalho.
  • Reduz custos operacionais: Minimiza despesas desnecessárias com recursos.
  • Melhora a eficiência do cluster: melhora o desempenho geral e a capacidade de resposta.

Por que usar o Karpenter para dimensionamento dinâmico

  • Dimensionamento Dinâmico: ajusta automaticamente a contagem de nós com base nas demandas da carga de trabalho.
  • Otimização de custos: Garante que os recursos sejam provisionados apenas quando necessários, reduzindo despesas.
  • Gestão eficiente de recursos: Rastreia pods que não podem ser agendados devido à falta de recursos, analisa seus requisitos, provisiona nós para acomodá-los, agenda os pods e desativa nós quando redundantes.

Instalando o Karpenter:

 #Set up Karpenter utilizing HELM:
helm improve --set up karpenter oci://public.ecr.aws/karpenter/karpenter --model "${KARPENTER_VERSION}" 
--namespace "${KARPENTER_NAMESPACE}" --create-namespace   --set "settings.clusterName=${CLUSTER_NAME}"    
--set "settings.interruptionQueue=${CLUSTER_NAME}"    --set controller.assets.requests.cpu=1    
--set controller.assets.requests.reminiscence=1Gi    --set controller.assets.limits.cpu=1    
--set controller.assets.limits.reminiscence=1Gi 

#Confirm Karpenter Set up: 
kubectl get pod -n kube-system | grep -i karpenter 
karpenter-7df6c54cc-rsv8s             1/1     Working   2 (10d in the past)   53d 
karpenter-7df6c54cc-zrl9n             1/1     Working   0             53d 

Configurando Karpenter com NodePools e NodeClasses:

Karpenter pode ser configurado com NodePools e NodeClasses para automatizar o provisionamento e o escalonamento de nós com base nas necessidades específicas de suas cargas de trabalho

  • NodePool do Karpenter: Nodepool é um recurso personalizado que outline um conjunto de nós com especificações e restrições compartilhadas em um cluster Kubernetes. Karpenter usa NodePools para gerenciar e dimensionar dinamicamente os recursos do nó com base nos requisitos de execução de cargas de trabalho
apiVersion: karpenter.sh/v1beta1 
form: NodePool 
metadata: 
  identify: g4-nodepool 
spec: 
  template: 
    metadata: 
      labels: 
        nvidia.com/gpu: "true" 
    spec: 
      taints: 
        - impact: NoSchedule 
          key: nvidia.com/gpu 
          worth: "true" 
      necessities: 
        - key: kubernetes.io/arch 
          operator: In 
          values: ("amd64") 
        - key: kubernetes.io/os 
          operator: In 
          values: ("linux") 
        - key: karpenter.sh/capability-sort 
          operator: In 
          values: ("on-demand") 
        - key: node.kubernetes.io/occasion-sort 
          operator: In 
          values: ("g4dn.xlarge" ) 
      nodeClassRef: 
        apiVersion: karpenter.k8s.aws/v1beta1 
        form: EC2NodeClass 
        identify: g4-nodeclass 
  limits: 
    cpu: 1000 
  disruption: 
    expireAfter: 120m 
    consolidationPolicy: WhenUnderutilized 
  • NodeClasses são configurações que definem as características e parâmetros dos nós que o Karpenter pode provisionar em um cluster Kubernetes. Uma NodeClass especifica os detalhes da infraestrutura subjacente para nós, como tipos de instância, configurações de modelo de execução e configurações específicas do provedor de nuvem.

Observação: a seção userData contém scripts para inicializar a instância do EC2, incluindo extrair uma imagem do Docker de GPU do TensorFlow e configurar a instância para ingressar no cluster Kubernetes.

apiVersion: karpenter.k8s.aws/v1beta1 
form: EC2NodeClass 
metadata: 
  identify: g4-nodeclass 
spec: 
  amiFamily: AL2 
  launchTemplate: 
    identify: "ack_nodegroup_template_new" 
    model: "7"  
  position: "KarpenterNodeRole" 
  subnetSelectorTerms: 
    - tags: 
        karpenter.sh/discovery: "nextgen-learninglab" 
  securityGroupSelectorTerms: 
    - tags: 
        karpenter.sh/discovery: "nextgen-learninglab"     
  blockDeviceMappings: 
    - deviceName: /dev/xvda 
      ebs: 
        volumeSize: 100Gi 
        volumeType: gp3 
        iops: 10000 
        encrypted: true 
        deleteOnTermination: true 
        throughput: 125 
  tags: 
    Title: Learninglab-Staging-Auto-GPU-Node 
  userData: | 
        MIME-Model: 1.0 
        Content material-Sort: multipart/combined; boundary="//" 
        --// 
        Content material-Sort: textual content/x-shellscript; charset="us-ascii" 
        set -ex 
        sudo ctr -n=k8s.io picture pull docker.io/tensorflow/tensorflow:2.12.0-gpu 
        --// 
        Content material-Sort: textual content/x-shellscript; charset="us-ascii" 
        B64_CLUSTER_CA=" " 
        API_SERVER_URL="" 
        /and so on/eks/bootstrap.sh nextgen-learninglab-eks --kubelet-further-args '--node-labels=eks.amazonaws.com/capacityType=ON_DEMAND 
--pod-max-pids=32768 --max-pods=110' -- b64-cluster-ca $B64_CLUSTER_CA --apiserver-endpoint $API_SERVER_URL --use-max-pods false 
         --// 
        Content material-Sort: textual content/x-shellscript; charset="us-ascii" 
        KUBELET_CONFIG=/and so on/kubernetes/kubelet/kubelet-config.json 
        echo "$(jq ".podPidsLimit=32768" $KUBELET_CONFIG)" > $KUBELET_CONFIG 
        --// 
        Content material-Sort: textual content/x-shellscript; charset="us-ascii" 
        systemctl cease kubelet 
        systemctl daemon-reload 
        systemctl begin kubelet
        --//--

Neste cenário, cada nó (por exemplo, ip-10-20-23-199.us-west-1.compute.inner) pode acomodar até três pods. Se a implantação for dimensionada para adicionar outro pod, os recursos serão insuficientes, fazendo com que o novo pod permaneça em estado pendente.

O Karpenter monitora esses pods não programáveis ​​e avalia seus requisitos de recursos para agir de acordo. Haverá nodeclaim que reivindica o nó do nodepool e o Karpenter provisiona assim um nó com base no requisito.

Conclusão: gerenciamento eficiente de recursos de GPU no Kubernetes

Com a crescente demanda por cargas de trabalho aceleradas por GPU no Kubernetes, o gerenciamento eficaz dos recursos de GPU é essencial. A combinação de Plug-in de dispositivo NVIDIA, divisão do tempoe carpinteiro fornece uma abordagem poderosa para gerenciar, otimizar e dimensionar recursos de GPU em um cluster Kubernetes, oferecendo alto desempenho com utilização eficiente de recursos. Esta solução foi implementada para hospedar laboratórios de aprendizagem piloto habilitados para GPU em desenvolvedor.cisco.com/studyingproporcionando experiências de aprendizagem baseadas em GPU.

Compartilhar:

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *