25.4 C
Colombia
lunes, julio 7, 2025

Optimización de cargas de trabajo de IA con GPU NVIDA, Time Slicing y Karpenter (Parte 2)


Introducción: superar los desafíos de la administración de GPU

En la Parte 1 de esta serie de blogs, exploramos los desafíos de alojar modelos de lenguaje grandes (LLM) en cargas de trabajo basadas en CPU dentro de un clúster EKS. Discutimos las ineficiencias asociadas con el uso de CPU para tales tareas, principalmente debido a los grandes tamaños de los modelos y las velocidades de inferencia más lentas. La introducción de recursos de GPU ofreció un aumento significativo del rendimiento, pero también generó la necesidad de una gestión eficiente de estos recursos de alto costo.

En esta segunda parte, profundizaremos en cómo optimizar el uso de GPU para estas cargas de trabajo. Cubriremos las siguientes áreas clave:

  • Configuración del complemento del dispositivo NVIDIA: Esta sección explicará la importancia del complemento de dispositivo NVIDIA para Kubernetes y detallará su función en el descubrimiento, la asignación y el aislamiento de recursos.
  • División del tiempo: Analizaremos cómo la división del tiempo permite que múltiples procesos compartan recursos de GPU de manera efectiva, asegurando la máxima utilización.
  • Escalado automático de nodos con Karpenter: Esta sección describirá cómo Karpenter gestiona dinámicamente el escalado de nodos en función de la demanda en tiempo actual, optimizando la utilización de recursos y reduciendo costos.

Desafíos abordados

  1. Gestión eficiente de GPU: Garantizar que las GPU se utilicen por completo para justificar su alto costo.
  2. Manejo de concurrencia: Permitir que múltiples cargas de trabajo compartan recursos de GPU de manera efectiva.
  3. Escalado dinámico: Ajusta automáticamente la cantidad de nodos según las demandas de la carga de trabajo.

Sección 1: Introducción al complemento de dispositivo NVIDIA

El complemento de dispositivo NVIDIA para Kubernetes es un componente que simplifica la administración y el uso de las GPU NVIDIA en clústeres de Kubernetes. Permite a Kubernetes reconocer y asignar recursos de GPU a pods, lo que permite cargas de trabajo aceleradas por GPU.

Por qué necesitamos el complemento de dispositivo NVIDIA

  • Descubrimiento de recursos: Detecta automáticamente los recursos de GPU NVIDIA en cada nodo.
  • Asignación de recursos: gestiona la distribución de recursos de GPU a pods en función de sus solicitudes.
  • Aislamiento: Garantiza la utilización segura y eficiente de los recursos de GPU entre diferentes pods.

El complemento de dispositivo NVIDIA simplifica la administración de GPU en clústeres de Kubernetes. Automatiza la instalación del controlador nvidia, package de herramientas para contenedoresy CUDAlo que garantiza que los recursos de la GPU estén disponibles para las cargas de trabajo sin necesidad de configuración handbook.

  • Controlador NVIDIA: Requerido para nvidia-smi y operaciones básicas de GPU. Interfaz con el {hardware} de la GPU. La siguiente captura de pantalla muestra el resultado del comando nvidia-smi, que muestra información clave como la versión del controlador, la versión CUDA y la configuración detallada de la GPU, lo que confirma que la GPU está configurada correctamente y lista para usar.

  • Package de herramientas de contenedor NVIDIA: Requerido para usar GPU con contenedor. A continuación podemos ver la versión del package de herramientas del contenedor y el estado del servicio que se ejecuta en la instancia.
#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: Requerido para bibliotecas y aplicaciones aceleradas por GPU. A continuación se muestra el resultado del comando nvcc, que muestra la versión de CUDA instalada en el 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 

Configurar el complemento del dispositivo NVIDIA

Para garantizar que DaemonSet se ejecute exclusivamente en instancias basadas en GPU, etiquetamos el nodo con la clave “nvidia.com/gpu” y el valor “true”. Esto se logra usando Afinidad de nodo, Selector de nodos y Contaminaciones y tolerancias.

Profundicemos ahora en cada uno de estos componentes en detalle.

  • Afinidad de nodo: La afinidad de nodo permite programar pods en los nodos según las etiquetas de los nodos requerido durante la programación ignorado durante la ejecución: El programador no puede programar el Pod a menos que se cumpla la regla y la clave sea “nvidia.com/gpu”, el operador esté “en” y el valor sea “verdadero”.
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" 
  • Selector de nodos: norteEl selector de odos es la forma de recomendación más easy para las restricciones de selección de nodos. nvidia.com/gpu: “verdadero”
  • Contaminaciones y tolerancias: Se agregan tolerancias al Daemon Set para garantizar que se pueda programar en los nodos 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 

Después de implementar el etiquetado de nodos, la afinidad, el selector de nodos y las manchas/toleraciones, podemos asegurarnos de que Daemon Set se ejecute exclusivamente en instancias basadas en GPU. Podemos verificar la implementación del complemento del dispositivo NVIDIA usando el siguiente comando:

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

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

Pero el desafío aquí es que las GPU son muy caras y es necesario garantizar su máxima utilización y permitirnos explorar más sobre la simultaneidad de GPU.

Simultaneidad de GPU:

Se refiere a la capacidad de ejecutar múltiples tareas o subprocesos simultáneamente en una GPU.

  • Proceso único: en una configuración de proceso único, solo una aplicación o contenedor usa la GPU a la vez. Este enfoque es sencillo, pero puede provocar una infrautilización de los recursos de la GPU si la aplicación no carga completamente la GPU.
  • Servicio multiproceso (MPS): el servicio multiproceso (MPS) de NVIDIA permite que varias aplicaciones CUDA compartan una única GPU al mismo tiempo, lo que mejora la utilización de la GPU y scale back la sobrecarga del cambio de contexto.
  • División de tiempo: la división de tiempo implica dividir el tiempo de la GPU entre diferentes procesos; en otras palabras, múltiples procesos se turnan en la GPU (cambio de contexto Spherical Robin)
  • GPU de instancias múltiples (MIG): MIG es una función disponible en las GPU NVIDIA A100 que permite dividir una sola GPU en varias instancias aisladas más pequeñas, cada una de las cuales se comporta como una GPU independiente.
  • Virtualización: la virtualización de GPU permite compartir una única GPU física entre múltiples máquinas virtuales (VM) o contenedores, proporcionando a cada una una GPU digital.

Sección 2: Implementación de la división del tiempo para GPU

La división de tiempo en el contexto de las GPU NVIDIA y Kubernetes se refiere a compartir una GPU física entre varios contenedores o pods en un clúster de Kubernetes. La tecnología implica dividir el tiempo de procesamiento de la GPU en intervalos más pequeños y asignar esos intervalos a diferentes contenedores o pods.

  • Asignación de intervalos de tiempo: el programador de GPU asigna intervalos de tiempo a cada vGPU configurada en la GPU física.
  • Preferencia y cambio de contexto: al closing del intervalo de tiempo de una vGPU, el programador de la GPU se adelanta a su ejecución, guarda su contexto y cambia al contexto de la siguiente vGPU.
  • Cambio de contexto: el programador de GPU garantiza un cambio de contexto fluido entre vGPU, minimizando la sobrecarga y garantizando un uso eficiente de los recursos de la GPU.
  • Finalización de tareas: los procesos dentro de los contenedores completan sus tareas aceleradas por GPU dentro de los intervalos de tiempo asignados.
  • Gestión y seguimiento de recursos
  • Liberación de recursos: a medida que se completan las tareas, los recursos de GPU se devuelven a Kubernetes para su reasignación a otros pods o contenedores.

Por qué necesitamos dividir el tiempo

  • Rentabilidad: Garantiza que las GPU de alto costo no se subutilicen.
  • concurrencia: permite que varias aplicaciones utilicen la GPU simultáneamente.

Ejemplo de configuración para división de tiempo

Apliquemos la configuración de división de tiempo usando el mapa de configuración como se muestra a continuación. Aquí réplicas: 3 especifica la cantidad de réplicas de los recursos de GPU, lo que significa que el recurso de GPU se puede dividir en 3 instancias compartidas

apiVersion: v1 
type: ConfigMap 
metadata: 
  identify: nvidia-machine-plugin 
  namespace: kube-system 
knowledge: 
  any: |- 
    model: v1 
    flags: 
      migStrategy: none 
    sharing: 
      timeSlicing: 
        sources: 
        - identify: nvidia.com/gpu 
          replicas: 3 
#We are able to confirm the GPU sources accessible in your nodes utilizing the next command:     
kubectl get nodes -o json | jq -r '.objects[] | 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 accessible. 
#We are able to request GPU sources of their pod specs by setting useful resource limits 
sources: 
      limits: 
        cpu: "1" 
        reminiscence: 2G 
        nvidia.com/gpu: "1" 
      requests: 
        cpu: "1" 
        reminiscence: 2G 
        nvidia.com/gpu: "1" 

En nuestro caso podemos alojar 3 pods en un solo nodo ip-10-20-23-199.us-west-1. calcular. Interno y debido al tiempo de división, estos 3 pods pueden usar 3 GPU virtuales como se muestra a continuación

Las GPU se han compartido virtualmente entre los pods y podemos ver los PIDS asignados para cada uno de los procesos a continuación.

Ahora optimizamos la GPU a nivel de pod, centrémonos en optimizar los recursos de GPU a nivel de nodo. Podemos lograr esto utilizando una solución de escalado automático de clúster llamada carpintero. Esto es particularmente importante ya que es posible que los laboratorios de aprendizaje no siempre tengan una carga o actividad de usuario constante, y las GPU son extremadamente caras. Aprovechando carpinteropodemos escalar dinámicamente los nodos de GPU hacia arriba o hacia abajo según la demanda, lo que garantiza la rentabilidad y la utilización óptima de los recursos.

Sección 3: Escalado automático de nodos con Karpenter

carpintero es una gestión del ciclo de vida de nodos de código abierto para Kubernetes. Automatiza el aprovisionamiento y desaprovisionamiento de nodos en función de las necesidades de programación de los pods, lo que permite un escalado eficiente y una optimización de costos.

  • Aprovisionamiento dinámico de nodos: escala automáticamente los nodos según la demanda.
  • Optimiza la utilización de recursos: hace coincidir la capacidad del nodo con las necesidades de la carga de trabajo.
  • Cut back Costos Operativos: Minimiza gastos de recursos innecesarios.
  • Mejora la eficiencia del clúster: mejora el rendimiento common y la capacidad de respuesta.

Por qué utilizar Karpenter para el escalado dinámico

  • Escalado dinámico: ajusta automáticamente el recuento de nodos según las demandas de la carga de trabajo.
  • Optimización de costos: Garantiza que los recursos solo se aprovisionen cuando sean necesarios, lo que scale back los gastos.
  • Gestión eficiente de recursos: Realiza un seguimiento de los pods que no se pueden programar debido a la falta de recursos, revisa sus requisitos, aprovisiona los nodos para acomodarlos, programa los pods y desmantela los nodos cuando son redundantes.

Instalación de 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.sources.requests.cpu=1    
--set controller.sources.requests.reminiscence=1Gi    --set controller.sources.limits.cpu=1    
--set controller.sources.limits.reminiscence=1Gi 

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

Configuración de Karpenter con NodePools y NodeClasses:

Karpenter se puede configurar con Grupos de nodos y Clases de nodo para automatizar el aprovisionamiento y el escalado de nodos en función de las necesidades específicas de sus cargas de trabajo

  • Grupo de nodos de Karpenter: Nodepool es un recurso personalizado que outline un conjunto de nodos con especificaciones y restricciones compartidas en un clúster de Kubernetes. Karpenter utiliza NodePools para administrar y escalar dinámicamente los recursos de los nodos según los requisitos de las cargas de trabajo en ejecución.
apiVersion: karpenter.sh/v1beta1 
type: 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 
        type: EC2NodeClass 
        identify: g4-nodeclass 
  limits: 
    cpu: 1000 
  disruption: 
    expireAfter: 120m 
    consolidationPolicy: WhenUnderutilized 
  • Clases de nodo son configuraciones que definen las características y parámetros de los nodos que Karpenter puede aprovisionar en un clúster de Kubernetes. Una NodeClass especifica los detalles de la infraestructura subyacente para los nodos, como tipos de instancias, configuraciones de plantillas de lanzamiento y configuraciones específicas del proveedor de la nube.

Nota: La sección userData contiene secuencias de comandos para iniciar la instancia EC2, incluida la extracción de una imagen de Docker GPU de TensorFlow y la configuración de la instancia para unirse al clúster de Kubernetes.

apiVersion: karpenter.k8s.aws/v1beta1 
type: EC2NodeClass 
metadata: 
  identify: g4-nodeclass 
spec: 
  amiFamily: AL2 
  launchTemplate: 
    identify: "ack_nodegroup_template_new" 
    model: "7"  
  function: "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: 
    Identify: Learninglab-Staging-Auto-GPU-Node 
  userData: | 
        MIME-Model: 1.0 
        Content material-Kind: multipart/combined; boundary="//" 
        --// 
        Content material-Kind: 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-Kind: textual content/x-shellscript; charset="us-ascii" 
        B64_CLUSTER_CA=" " 
        API_SERVER_URL="" 
        /and many others/eks/bootstrap.sh nextgen-learninglab-eks --kubelet-additional-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-Kind: textual content/x-shellscript; charset="us-ascii" 
        KUBELET_CONFIG=/and many others/kubernetes/kubelet/kubelet-config.json 
        echo "$(jq ".podPidsLimit=32768" $KUBELET_CONFIG)" > $KUBELET_CONFIG 
        --// 
        Content material-Kind: textual content/x-shellscript; charset="us-ascii" 
        systemctl cease kubelet 
        systemctl daemon-reload 
        systemctl begin kubelet
        --//--

En este escenario, cada nodo (por ejemplo, ip-10-20-23-199.us-west-1.compute.inner) puede acomodar hasta tres cápsulas. Si la implementación se escala para agregar otro pod, los recursos serán insuficientes, lo que provocará que el nuevo pod permanezca en un estado pendiente.

Karpenter monitorea estos módulos no programables y evalúa sus requisitos de recursos para actuar en consecuencia. Habrá una reclamación de nodos que reclama el nodo del grupo de nodos y, por lo tanto, Karpenter aprovisiona un nodo según el requisito.

Conclusión: gestión eficiente de recursos de GPU en Kubernetes

Con la creciente demanda de cargas de trabajo aceleradas por GPU en Kubernetes, gestionar los recursos de GPU de forma eficaz es esencial. la combinación de Complemento de dispositivo NVIDIA, corte de tiempoy carpintero proporciona un enfoque poderoso para administrar, optimizar y escalar los recursos de GPU en un clúster de Kubernetes, brindando un alto rendimiento con una utilización eficiente de los recursos. Esta solución se ha implementado para albergar laboratorios de aprendizaje piloto habilitados para GPU en desarrollador.cisco.com/studyingproporcionando experiencias de aprendizaje impulsadas por GPU.

Compartir:

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles