O blog da AWS

Entrega de conteúdo de vídeo com GPUs fracionadas em contêineres no Amazon EKS

Por Josh Hart e Evan Statton

A codificação e transcodificação de vídeo são cargas de trabalho essenciais para empresas de mídia e entretenimento. A entrega de conteúdo de vídeo de alta qualidade aos espectadores em vários dispositivos e redes precisa de uma infraestrutura de codificação eficiente e escalável. À medida que as resoluções de vídeo continuam aumentando para 4K e 8K, a aceleração da GPU é essencial para fluxos de trabalho de codificação em tempo real, nos quais tarefas de codificação paralela são necessárias. Embora a codificação em CPU seja possível, ela é mais adequada para tarefas de codificação sequencial em menor escala ou onde a velocidade de codificação é menos preocupante. A AWS oferece famílias de instâncias de GPU, como G4dn, G5 e G5g que são adequadas para essas cargas de trabalho de codificação em tempo real.

As GPUs modernas oferecem aos usuários milhares de unidades de sombreamento e a capacidade de processar bilhões de pixels por segundo. A execução de uma única tarefa de codificação na GPU geralmente deixa os recursos subutilizados, o que representa uma oportunidade de otimização. Ao executar vários processos simultaneamente em uma única GPU, os processos podem ser compactados em compartimentos e usar uma fração da GPU. Essa prática é conhecida como fracionamento.

Este post explora como criar um pipeline de codificação de vídeo na AWS que usa GPUs fracionadas em contêineres usando o Amazon Elastic Kubernetes Service (Amazon EKS). Ao dividir a GPU em frações, vários trabalhos de codificação podem compartilhar a GPU simultaneamente. Isso melhora o uso de recursos e reduz os custos. Este post também aborda o uso do Bottlerocket e do Karpenter para obter um escalonamento rápido da capacidade de codificação heterogênea. Com os recursos de cache de imagens do Bottlerocket, novas instâncias podem ser inicializadas rapidamente para lidar com picos de demanda. Ao combinar GPUs fracionadas, contêineres e Bottlerocket na AWS, as empresas de mídia podem alcançar o desempenho, a eficiência e a escala de que precisam para fornecer streams de vídeo de alta qualidade aos espectadores.

Os exemplos deste post estão usando as seguintes versões de software:

  • Amazon EKS versão 1.28
  • Karpenter versão 0.33.0
  • Bottlerocket versão 1.16.0

Para ver e implantar o exemplo completo, consulte o repositório do GitHub.

Configurando o corte temporal da GPU no Amazon EKS

O conceito de compartilhar ou dividir o tempo de uma GPU não é novo. Para obter o máximo de uso, vários processos podem ser executados na mesma GPU física. Ao usar o máximo possível da capacidade de GPU disponível, o custo por sessão de streaming diminui. Portanto, a densidade — o número de processos simultâneos de transcodificação ou codificação — é uma dimensão importante para um streaming de mídia econômico.

Com a popularidade do Kubernetes, os fornecedores de GPU investiram pesadamente no desenvolvimento de plug-ins para facilitar esse processo. Alguns dos benefícios de usar o Kubernetes em vez de executar processos diretamente na máquina virtual (VM) são:

  • Resiliência — Ao usar daemonsets e implantações do Kubernetes, você pode confiar no Kubernetes para reiniciar automaticamente qualquer tarefa travada ou com falha.
  • Segurança — As políticas de rede podem ser definidas para evitar a comunicação entre pods. Além disso, os namespaces do Kubernetes podem ser usados para fornecer isolamento adicional. Isso é útil em ambientes multilocatários (multi tenents) para fornecedores de software e provedores de software como serviço (SaaS).
  • Elasticidade — as implantações do Kubernetes permitem que você escale e amplie facilmente com base nas mudanças nos volumes de tráfego. O escalonamento automático orientado por eventos, como o KEDA, permite o provisionamento responsivo de recursos adicionais. Ferramentas como o Cluster Autoscaler e o Karpenter provisionam automaticamente a capacidade computacional com base no uso dos recursos.

É necessário um plug-in de dispositivo para expor os recursos da GPU ao Kubernetes. A principal tarefa do plug-in do dispositivo é tornar os detalhes dos recursos de GPU disponíveis visíveis para o Kubernetes. Vários plug-ins estão disponíveis para alocar frações de GPU no Kubernetes. Neste post, o plug-in de dispositivo NVIDIA para Kubernetes é usado porque fornece um mecanismo leve para expor os recursos da GPU. A partir da versão 12, este plugin suporta o corte de tempo. Estão disponíveis embalagens (wrappers) adicionais para o plug-in do dispositivo, como o NVIDIA GPU Operator for Kubernetes, que fornecem mais recursos de gerenciamento e monitoramento, se necessário.

Para configurar o plug-in do dispositivo NVIDIA com corte de tempo, as etapas a seguir devem ser seguidas.

Remova qualquer plug-in de dispositivo NVIDIA existente do cluster:

kubectl delete daemonset nvidia-device-plugin-daemonset -n kube-system
Apache Configuration

Em seguida, crie um ConfigMap para definir em quantas “fatias” dividir a GPU. O número de fatias necessárias pode ser calculado analisando o uso da GPU para uma única tarefa. Por exemplo, se sua carga de trabalho usa no máximo 10% da GPU disponível, você pode dividir a GPU em 10 fatias. Isso é mostrado no exemplo de configuração a seguir:

apiVersion: v1
kind: ConfigMap
metadata:
  name: time-slicing-config-all
data:
  any: |-
    version: v1
    flags:
      migStrategy: none
    sharing:
      timeSlicing:
        resources:
        - name: nvidia.com/gpu
          replicas: 10

kubectl create -n kube-system -f time-slicing-config-all.yaml
Apache Configuration

Por fim, implante a versão mais recente do plug-in, usando o ConfigMap criado:

helm repo add nvdp https://nvidia.github.io/k8s-device-plugin
helm repo update
helm upgrade -i nvdp nvdp/nvidia-device-plugin \
    --version=0.14.1 \
    --namespace kube-system \
    --create-namespace \
    --set config.name=time-slicing-config-all
Apache Configuration

Se os nós no cluster forem inspecionados, eles mostrarão um limite de recursos de GPU atualizado, apesar de terem apenas uma GPU física:

Capacity:
  cpu:                8
  ephemeral-storage:  104845292Ki
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             32386544Ki
  nvidia.com/gpu:     10
  pods:               29
  
Apache Configuration

Como o objetivo é agrupar o máximo possível de tarefas em uma única GPU, é provável que a configuração Max Pods seja a próxima a ser alterada. Na máquina usada neste post (g4dn.2xlarge), o máximo padrão de pods é 29. Para fins de teste, isso é aumentado para 110 pods. 110 é o máximo recomendado para nós menores que 32 vCPUs. Para aumentar isso, as etapas a seguir precisam ser seguidas.
Passe a sinalização max-pods para o kubelet no script de bootstrap do node:

/etc/eks/bootstrap.sh my-cluster –use-max-pods false –kubelet-extra-args ‘–max-pods=110’

Ao usar o Karpenter para escalonamento automático, a definição do recurso NodePool passa essa configuração para novos nós:

kubelet:
    maxPods: 110 
Apache Configuration

O número de pods agora é limitado pelo máximo de interfaces de rede elásticas (ENIs) e endereços IP por interface. Consulte a documentação da ENI para ver os limites de cada tipo de instância. A fórmula é Número de ENIs * (Número de IPv4 por ENI — 1) + 2). Para aumentar o máximo de pods por nó além disso, a delegação de prefixo deve ser usada. Isso é configurado usando o seguinte comando:

kubectl set env daemonset aws-node -n kube-system ENABLE_PREFIX_DELEGATION=true
Apache Configuration

Para obter mais detalhes sobre a delegação de prefixos, consulte Amazon VPC CNI aumenta os limites de pods por nó.

Densidade de sessão do tipo de instância do Amazon EC2

A próxima decisão é qual tipo de instância usar. As instâncias de GPU geralmente têm alta demanda devido ao seu uso em cargas de trabalho de mídia e aprendizado de máquina (ML). É uma prática recomendada diversificar o maior número possível de tipos de instância em todas as zonas de disponibilidade (AZs) em uma região da AWS.

No momento em que este artigo foi escrito, as três famílias de instâncias com GPU NVIDIA da geração atual mais usadas para cargas de trabalho de mídia são G4dn, G5 e G5g. O último usa uma arquitetura de CPU ARM64 com processadores AWS Graviton 2.

Os exemplos usados neste post usam 1080p25 (resolução de 1080 e 25 quadros por segundo) como perfil de taxa de quadros. Se você estiver usando uma resolução ou taxa de quadros diferente, os resultados podem variar. Para testar isso, o ffmpeg foi executado no contêiner usando a codificação de hardware h264 com CUDA usando os seguintes argumentos:

ffmpeg -nostdin -y -re -vsync 0 -c:v h264_cuvid -hwaccel cuda -i <input_file> -c:v h264_nvenc -preset p1 -profile:v baseline -b:v 5M -an -f rtp -payload_type 98 rtp://192.168.58.252:5000?pkt_size=1316
Apache Configuration

As principais opções usadas neste exemplo são as seguintes, e talvez você queira alterá-las com base em seus requisitos:

  • `-re`: Leia a entrada na taxa de quadros nativa. Isso é particularmente útil para cenários de streaming em tempo real.
  • `-c:v h264_cuvid`: Use NVIDIA CUVID para decodificação.
  • `-hwaccel cuda`: especifique CUDA como a API de aceleração de hardware.
  • `-c:v h264_nvenc`: Use NVIDIA NVENC para codificação de vídeo.
  • `-preset p1`: Defina a predefinição de codificação como “p1” (talvez você queira ajustá-la com base em seus requisitos).
  • `-profile:v baseline`: Defina o perfil H.264 como linha de base.
  • `-b:v 5M`: Defina a taxa de bits do vídeo para 5 Mbps.

Para ver a definição completa de implantação, consulte o repositório do GitHub. Todas as instâncias estavam usando a versão 535 do driver NVIDIA e a versão 12.2 da CUDA. Em seguida, a saída foi monitorada em uma instância remota usando o seguinte comando:

ffmpeg -protocol_whitelist file,crypto,udp,rtp -i input.sdp -f null –
Apache Configuration

Sessões simultânea

Média de FPS g4dn.2xlarge

Média de FPS g5g.2xlarge

Média de FPS g5.2xlarge

26

25

25

25

27

25

25

25

28

25

25

25

29

23

24

25

30

23

24

25

31

23

23

25

32

22

23

24

33

22

21

23

35

21

20

22

40

19

19

19

50

12

12

15

As células destacadas em verde indicam o máximo de sessões simultâneas nas quais a taxa de quadros desejada foi alcançada de forma consistente.

G4dn.2xlarge

A GPU T4 na instância g4dn tem um único codificador, o que significa que o codificador atinge consistentemente a capacidade em cerca de 28 trabalhos simultâneos. Em um 2xl, ainda há VRAM, CPU e memória sobressalentes disponíveis nessa densidade. Essa capacidade ociosa pode ser usada para codificar sessões adicionais na CPU, executar os pods de aplicativos ou a instância pode ser reduzida para um tamanho de instância menor. Além de monitorar o FPS, o stream pode ser monitorado manualmente usando ffplay ou VLC. Observe que, embora sessões adicionais possam ser executadas além dos números anteriores, as quedas na taxa de quadros se tornam mais comuns.

Eventualmente, a GPU fica saturada e as exceções de memória CUDA são lançadas, fazendo com que o contêiner trave e reinicie. A seguinte qualidade de transmissão foi observada ao assistir manualmente a transmissão por meio do VLC:

  • 25 a 28 sessões — alta qualidade, quedas mínimas na taxa de quadros, experiência de visualização ideal
  • >=30 sessões — algumas quedas perceptíveis na taxa de quadros e na resolução.
  • >=50 sessões — interrupções frequentes e artefatos pesados, a maioria impossíveis de assistir (nessa densidade, CPU, memória e rede podem se tornar gargalos)

G5g.2xlarge – A instância baseada em Graviton tem um
desempenho quase idêntico ao G4dn. Isso é esperado, pois a GPU T4g na instância G5g tem especificações semelhantes às da GPU T4. A principal diferença é que o G5g usa processadores Graviton 2 baseados em ARM em vez de x86. Isso significa que as instâncias G5g têm uma relação preço/desempenho aproximadamente 25% melhor do que as G4dn equivalentes. Ao implantar o ffmpeg em um ambiente em contêineres, isso significa que imagens de contêiner com várias arquiteturas podem ser criadas para atingir as arquiteturas x86 e ARM. Usar a codificação de hardware com h264 e CUDA funciona bem usando bibliotecas de compilação cruzada para ARM.

G5.2xlarge
As instâncias G5 usam a GPU A10G mais recente. Isso adiciona 8 GB adicionais de VRAM e dobra a largura de banda da memória em comparação com o T4, até 600 GBs, graças ao PCIe Gen4. Isso significa que ele pode produzir vídeo com menor latência e maior resolução. No entanto, ele ainda tem um codificador. Ao executar trabalhos de renderização simultâneos, o gargalo é a capacidade do codificador. A maior largura de banda de memória permite algumas sessões simultâneas extras, mas a densidade que pode ser alcançada é semelhante. Isso significa que é possível atingir a mesma densidade com uma taxa de quadros ou resolução um pouco maior.

O custo por sessão de cada instância é mostrado na tabela a seguir (com base nos preços sob demanda na região Leste dos EUA):

Tipo de instância

Custo por hora ($) Máximo de sessões em 1080p25

Custo por sessão por hora ($)

G4dn 0.752 28 0.027
G5 1.212 31 0.039
G5g 0.556 28 0.02

Ao combinar diferentes famílias e tamanhos de instâncias e implantar em todas as AZs em uma região ou em várias regiões, você pode melhorar sua resiliência e escalabilidade. Isso também permite que você desbloqueie o desconto spot máximo escolhendo um modelo “price-capacity-optimized”, caso seu aplicativo seja capaz de lidar com interrupções de instâncias spot.

Escalonamento automático de nós horizontais

Como as cargas de trabalho de streaming de mídia variam de acordo com os hábitos de visualização, é importante ter uma capacidade de renderização elasticamente escalável. Quanto mais responsivamente a capacidade computacional adicional puder ser provisionada, melhor será a experiência do usuário. Isso também otimiza o custo ao reduzir a necessidade de provisionamento para picos. Observe que esta seção explora o escalonamento dos recursos computacionais subjacentes, não o escalonamento automático das cargas de trabalho em si. O último é abordado na documentação do Horizontal Pod Autoscaler.

As imagens de contêiner que precisam de drivers ou estruturas de vídeo geralmente são grandes, variando de 500 MiB a 3 GiB ou mais. A utilização dessas imagens de contêineres grandes pela rede pode ser demorada. Isso prejudica a capacidade de escalar de forma responsiva a mudanças repentinas na atividade.

Há algumas ferramentas que podem ser usadas para tornar o dimensionamento mais responsivo:

  • Karpenter – O Karpenter permite escalar usando tipos de instância heterogêneas. Isso significa que grupos de instâncias G4dn, G5 e G5g podem ser usados, com o Karpenter escolhendo a mais econômica para colocar os pods pendentes.
    • Como o tipo de recurso usado pelo plug-in do dispositivo se apresenta como um recurso de GPU padrão, o Karpenter pode escalar com base nesse recurso.
    • No momento em que escrevo, o Karpenter não oferece suporte ao dimensionamento com base em recursos personalizados. Inicialmente, os nós são iniciados com o recurso padrão de uma GPU até que o nó seja devidamente rotulado pelo plug-in do dispositivo. Durante picos de escalabilidade, os nós podem ser provisionados em excesso até que o Karpenter reconcilie a carga de trabalho.
  • Bottlerocket — O Bottlerocket é um sistema operacional de contêiner mínimo que contém apenas o software necessário para executar imagens de contêiner. Devido a essa imagem menor, os nós do Bottlerocket podem iniciar mais rápido do que as distribuições Linux de uso geral em alguns cenários. Consulte a tabela a seguir para uma comparação disso:

Estágio

Tempo decorrido do Linux de uso geral g4dn.xlarge (s) Tempo decorrido do Bottlerocket g4dn.xlarge (s)

Tempo decorrido do Bottlerocket g5g.x large (s)

Instance Launch 0 0 0
Kubelet Starting 33.36 17.5 16.54
Kubelet Started 37.36 21.25 19.85
Node Ready 51.71 34.19 32.38

Ao usar uma construção de contêiner de várias arquiteturas, vários tipos de instância do Amazon Elastic Compute Cloud (Amazon EC2) podem ser direcionados usando a mesma configuração do NodePool no Karpenter. Isso permite um escalonamento econômico dos recursos. O exemplo de carga de trabalho foi criado usando o seguinte comando:

docker buildx build --platform "linux/amd64,linux/arm64" --tag ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/ffmpeg:1.0 --push  . -f Dockerfile
Apache Configuration

Isso permite um NodePool definido no Karpenter da seguinte forma:

apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: default
spec:
  template:
    spec:
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["on-demand", "spot"]
        - key: "node.kubernetes.io/instance-type"
          operator: In
          values: ["g5g.2xlarge", "g4dn.2xlarge", "g5.2xlarge"]
      nodeClassRef:
        name: default
      kubelet:
        maxPods: 110
Apache Configuration

Neste NodePool, todos os três tipos de instância estão disponíveis para o Karpenter usar. O Karpenter pode escolher o mais eficiente, independentemente da arquitetura do processador, pois estamos usando a imagem de várias arquiteturas criada anteriormente na implantação. O tipo de capacidade usa instâncias spot para reduzir custos. Se a carga de trabalho não tolerar interrupções, o spot poderá ser removido e somente instâncias sob demanda serão provisionadas.

Isso funcionaria com qualquer sistema operacional compatível. Para fazer com que o Karpenter use Amazon Machine Images (AMIs) baseadas em BottleRocket, a EC2NodeClass correspondente é definida da seguinte forma:

apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
  name: default
spec:
  amiFamily: Bottlerocket
...
Apache Configuration

Isso seleciona automaticamente a AMI mais recente na família especificada, neste caso, Bottlerocket. Para ver o exemplo completo e obter mais detalhes sobre essa configuração, consulte o exemplo no repositório do GitHub.

Conclusão

Ao aproveitar GPUs fracionárias, orquestração de contêineres, tipos de sistemas operacionais e instâncias específicas, as empresas de mídia podem obter uma relação preço-desempenho até 95% melhor. As técnicas abordadas nesta postagem mostram como a infraestrutura da AWS pode ser personalizada para oferecer codificação de vídeo de alta densidade em grande escala. Com decisões de arquitetura ponderadas, as organizações podem preparar seus fluxos de trabalho para o futuro e oferecer experiências de visualização excepcionais à medida que o vídeo continua evoluindo para resoluções e taxas de bits mais altas.

Para começar a otimizar suas cargas de trabalho, experimente diferentes tipos de instância e opções de sistema operacional, como o Bottlerocket. Monitore o desempenho e a economia de custos à medida que você expande a capacidade de codificação. Use a flexibilidade e as ferramentas específicas da AWS para preparar seu pipeline de vídeo para o futuro hoje mesmo.

Este texto foi traduzido para o Português, e o texto original em inglês encontra-se aqui 

Biografia dos autores

Josh Hart é arquiteto sênior de soluções na Amazon Web Services. Ele trabalha com clientes ISV no Reino Unido para ajudá-los a criar e modernizar seus aplicativos SaaS na AWS.
Evan Statton é tecnólogo corporativo na equipe de mídia e entretenimento, jogos e esportes da AWS. Ele passou os últimos 20 anos inventando o futuro da tecnologia de mídia com algumas das maiores empresas de mídia do mundo. Evan trabalha em estreita colaboração com arquitetos de soluções e equipes de serviços em todo o mundo, cujo trabalho afeta os clientes em todos os aspectos da mídia e do entretenimento

 

Biografia da tradutor

Daniel Abib é arquiteto de soluções sênior na AWS, com mais de 25 anos trabalhando com gerenciamento de projetos, arquiteturas de soluções escaláveis, desenvolvimento de sistemas e CI/CD, microsserviços, arquitetura Serverless & Containers e segurança. Ele trabalha apoiando clientes corporativos, ajudando-os em sua jornada para a nuvem.
https://www.linkedin.com/in/danielabib/

Biografia do revisor

Marcelo Moras é Arquiteto de Soluções na AWS atendendo clientes Enterprise com foco em mercado financeiro. Com mais de 15 anos de experiência atuando com infraestrutura, administração de sistemas, redes, containers e segurança.