3 pontos por GN⁺ 2023-11-13 | 1 comentários | Compartilhar no WhatsApp
  • O desenvolvimento de IA já não se basta apenas com a execução sequencial típica da CPU; é preciso entender o modelo de processamento massivamente paralelo da GPU para lidar corretamente com o desempenho de treinamento e inferência
  • Em CPUs de consumo, o normal é ter 2 a 16 núcleos, com força em tarefas de thread única e ramificações condicionais; já a GPU usa milhares de pequenos núcleos, sendo vantajosa para operações matriciais, processamento de imagens e deep learning
  • A AWS oferece ambientes de execução com GPU como P3/P4, P5/Inf1, G4 e Amazon SageMaker; a p3.2xlarge custa cerca de US$ 3,06 por hora, a p5.48xlarge US$ 98,32, e a g4dn.xlarge US$ 0,526
  • O CUDA da NVIDIA permite que o desenvolvedor lide diretamente com o fluxo de execução paralela que vai da alocação de memória da GPU à cópia de dados, execução de kernels e compilação
  • Exemplos de soma de arrays, geração de Mandelbrot e uma CNN para classificar gatos e cães mostram como dividir loops sequenciais em threads de GPU; no Mandelbrot, o tempo caiu de 4,07 segundos na CPU para 0,0046 segundo na GPU

Por que só conhecimento de CPU não basta

  • Muitos desenvolvedores aprenderam e resolveram problemas de uma forma centrada na CPU, mas a CPU funciona basicamente apoiada em uma arquitetura sequencial
  • CPUs tradicionais executam instruções de forma linear e otimizam um pequeno número de núcleos potentes para desempenho em thread única
  • Em situações que exigem processar várias tarefas ao mesmo tempo, o custo de tratar cada tarefa em sequência aumenta por causa do modelo de execução sequencial
  • Embora seja possível melhorar o desempenho com multithreading, a filosofia básica de projeto da CPU ainda está mais próxima da execução sequencial

Modelos de IA e processamento paralelo

  • Arquiteturas modernas de IA, como Transformers, usam processamento paralelo para aumentar o desempenho de treinamento
  • RNNs operam sequencialmente, mas Transformers como o GPT conseguem processar várias palavras ao mesmo tempo, melhorando a eficiência de treinamento e a capacidade do modelo
  • O treinamento paralelo viabiliza modelos maiores, e modelos maiores se tornam a base para gerar saídas melhores
  • O paralelismo se aplica não só ao processamento de linguagem natural, mas também ao reconhecimento de imagens
    • AlexNet é um exemplo de processamento simultâneo de várias partes de uma imagem para identificar padrões
  • Por seu projeto focado no desempenho em thread única, a CPU tem dificuldade para distribuir e executar com eficiência a grande quantidade de cálculos paralelos necessária em modelos complexos de IA

Como a GPU reduz gargalos

  • A GPU é projetada com uma estrutura que usa muitos núcleos pequenos e especializados, em vez dos núcleos grandes e potentes da CPU
  • O paralelismo da GPU aparece bem em workloads que repetem em grande escala o mesmo tipo de operação, como renderização gráfica e cálculos matemáticos complexos
  • Frameworks de deep learning como TensorFlow são otimizados para aproveitar o desempenho da GPU e acelerar treinamento e inferência de modelos
  • O treinamento de redes neurais envolve muitas operações matriciais, e a GPU é forte em paralelizar esse tipo de operação graças ao seu grande número de núcleos

Diferenças de papel entre CPU e GPU

  • CPU

    • A CPU é projetada com foco em processamento sequencial, sendo forte em tarefas que executam linearmente um único fluxo de instruções
    • É adequada para computação de propósito geral, tarefas de sistema e processamento de algoritmos complexos com ramificações condicionais
    • CPUs de consumo geralmente têm uma quantidade relativamente pequena de núcleos, na faixa de 2 a 16 núcleos
    • Cada núcleo pode processar de forma independente seu próprio conjunto de instruções
  • GPU

    • A GPU é projetada com uma arquitetura paralela, eficiente para processar muitas subtarefas ao mesmo tempo
    • É vantajosa para renderização gráfica, cálculos matemáticos complexos e execução de algoritmos paralelizáveis
    • Divide uma tarefa em unidades paralelas menores para processar várias operações simultaneamente
    • Os núcleos de GPU frequentemente chegam aos milhares e são organizados em streaming multiprocessors (SMs) ou estruturas semelhantes
    • É adequada para tarefas que lidam com muitos dados ao mesmo tempo, como processamento de imagem e vídeo, deep learning e simulações científicas

Ambientes de GPU disponíveis na AWS

  • A AWS oferece várias instâncias com GPU que podem ser usadas em tarefas como machine learning
  • Instâncias de GPU de uso geral

    • P3 e P4 são instâncias de GPU de uso geral, adequadas a diversos workloads
    • Podem ser usadas para treinamento e inferência de machine learning, processamento de imagens e codificação de vídeo
    • A p3.2xlarge custa US$ 3,06 por hora e oferece 1 GPU NVIDIA Tesla V100 com 16 GB de memória de GPU
  • Instâncias otimizadas para inferência

    • Inferência é o processo de inserir dados em tempo real em um modelo de IA treinado para fazer previsões ou resolver tarefas
    • P5 e Inf1 são voltadas para inferência de machine learning quando baixa latência e eficiência de custo são importantes
    • A p5.48xlarge custa US$ 98,32 por hora e oferece 8 GPUs NVIDIA H100 com 80 GB de memória cada, totalizando 640 GB de memória de vídeo
  • Instâncias otimizadas para gráficos

    • G4 instances são projetadas para processar tarefas intensivas em gráficos
    • Desenvolvedores de videogames podem usar instâncias G4 para renderizar gráficos 3D de jogos
    • A g4dn.xlarge custa US$ 0,526 por hora e usa 1 GPU NVIDIA T4 com 16 GB de memória
  • Serviço gerenciado de machine learning

    • Amazon SageMaker é um serviço gerenciado para machine learning que oferece acesso a instâncias baseadas em GPU, como P3, P4 e P5
    • O SageMaker é adequado para organizações que querem começar com machine learning sem gerenciar diretamente a infraestrutura subjacente
    • A documentação de preços do Amazon SageMaker é fornecida separadamente

Uso básico do NVIDIA CUDA

  • CUDA é uma plataforma de computação paralela e um modelo de programação desenvolvidos pela NVIDIA, permitindo executar aplicações rapidamente usando aceleradores de GPU
  • Os exemplos mostram o fluxo de desenvolvimento em CUDA: alocação de memória na GPU, cópia de dados, execução de kernel e recuperação dos resultados
  • Fluxo de instalação

    • Baixe o base installer e o driver installer em CUDA
    • Adicione as seguintes variáveis de ambiente ao .bashrc na pasta home
      • export PATH="/usr/local/cuda-12.3/bin:$PATH"
      • export LD_LIBRARY_PATH="/usr/local/cuda-12.3/lib64:$LD_LIBRARY_PATH"
    • Execute os seguintes comandos
      • sudo apt-get install cuda-toolkit
      • sudo apt-get install nvidia-gds
    • Reinicie o sistema para aplicar as mudanças
  • Comandos úteis de verificação

    • lspci | grep VGA: identifica e lista as GPUs do sistema
    • nvidia-smi: fornece informações detalhadas como uso, temperatura e consumo de memória das GPUs NVIDIA
    • sudo lshw -C display: fornece informações sobre controladores de display, como placas gráficas
    • inxi -G: mostra informações do subsistema gráfico, incluindo GPU e display
    • sudo hwinfo --gfxcard: usado para verificar informações detalhadas sobre a placa gráfica do sistema

Paralelizando soma de arrays com CUDA

  • Soma de arrays é um problema adequado para explicar paralelização em GPU
  • Os arrays de exemplo são A = [1,2,3,4,5,6], B = [7,8,9,10,11,12], e o resultado é C = [8,10,12,14,16,18]
  • A abordagem com CPU percorre os elementos do array um a um e realiza a soma
  • À medida que a quantidade de dados cresce, o tempo do método sequencial aumenta, enquanto a GPU pode executar simultaneamente operações como 1+7, 2+8 e 3+9
  • O exemplo em CUDA usa um arquivo de kernel .cu
    • __global__ indica uma função de kernel chamada na GPU
    • vectorAdd recebe três ponteiros inteiros, a, b e c, e realiza a soma de vetores
    • threadIdx.x obtém o índice da thread atual
    • Cada thread armazena em c[i] a soma do elemento correspondente
  • A função main segue a ordem de alocação de memória na GPU, cópia de dados, execução do kernel e cópia do resultado
    • cudaMalloc aloca na GPU memória para cudaA, cudaB e cudaC
    • cudaMemcpy copia a e b do host para a GPU
    • vectorAdd <<<1, sizeof(a) / sizeof(a[0])>>> executa o kernel
    • O vetor de resultado cudaC é copiado da GPU para o host
  • Para compilar e executar, usa-se o comando nvcc
  • O código completo é disponibilizado

Uso de GPU na geração de imagens em Python

  • A geração do conjunto de Mandelbrot é uma tarefa que cria padrões visuais complexos com base no comportamento dos números em uma equação específica, e é intensiva em recursos
  • O exemplo em Python baseado em CPU percorre cada pixel e calcula o valor de Mandelbrot, levando 4,07 segundos para gerar uma imagem de 1024×1536
  • A versão acelerada por GPU usa a biblioteca Numba
    • O decorador @jit realiza compilação Just-In-Time, convertendo código Python em código de máquina
    • cuda.jit cria mandel_gpu, e device=True é definido para que ele execute na GPU
    • mandel_kernel é executado em uma GPU CUDA e divide a tarefa de geração do Mandelbrot entre threads da GPU
  • create_fractal_gpu realiza alocação de memória na GPU, configuração de threads e blocos, execução do kernel da GPU, sincronização e cópia do resultado
    • Usa threadsperblock = (16, 16)
    • cuda.synchronize() aguarda a conclusão do trabalho da GPU
    • d_image.copy_to_host(image) copia o resultado para o lado da CPU
  • O tempo de execução na GPU é de 0,0046 segundo, muito mais rápido que o código baseado em CPU
  • O código completo é disponibilizado

Treinando uma rede neural para classificar gatos e cães com GPU

  • Para mostrar como GPUs são usadas em IA, é usado um exemplo de rede neural que distingue gatos e cães
  • Os itens de preparação são CUDA e TensorFlow
    • O TensorFlow pode ser instalado com pip install tensorflow[and-cuda]
    • O dataset usado é o Kaggle Dogs vs. Cats
    • Após o download, organize as imagens de gatos e cães em subpastas diferentes dentro da pasta de treinamento
  • O modelo usa uma rede neural convolucional (CNN)
    • pandas e numpy são usados para manipulação de dados
    • Sequential é usado para empilhar linearmente as camadas da rede neural
    • Convolution2D, MaxPooling2D, Dense e Flatten são camadas que compõem a CNN
    • ImageDataGenerator é usado para aumento de dados em tempo real durante o treinamento
  • Os dados de treinamento são carregados com ImageDataGenerator
    • Nos dados de treinamento, são aplicados rescale=1./255, shear_range=0.2, zoom_range=0.2 e horizontal_flip=True
    • As imagens de entrada são configuradas com tamanho (64, 64), batch size 32 e modo de classificação binária
  • A estrutura da CNN é composta por convolução, max pooling, flattening, camadas Dense e saída sigmoid
  • O modelo é compilado com o otimizador adam, a loss binary_crossentropy e a métrica accuracy
  • O treinamento é executado com epochs=25 e validation_steps=2000, e salvo em um arquivo .h5 com classifier.save('trained_model.h5')
  • O código de inferência carrega trained_model.h5, converte a imagem para (64, 64) e imprime dog se o valor previsto for 0,5 ou maior; caso contrário, imprime cat
  • O código completo é disponibilizado

Escopo de uso da GPU

  • Na era da IA, é difícil ignorar os recursos da GPU, e desenvolvedores precisam entender melhor suas capacidades
  • Ao migrar de algoritmos sequenciais para algoritmos paralelizados, a GPU se torna uma ferramenta para acelerar cálculos complexos
  • A capacidade de processamento paralelo da GPU é especialmente vantajosa para lidar com grandes datasets e arquiteturas complexas de redes neurais em tarefas de IA e machine learning
  • A GPU é usada além do machine learning tradicional, também em pesquisa científica, simulações e tarefas intensivas em dados
  • A capacidade de processamento paralelo é aplicada à solução de problemas em várias áreas, como descoberta de novos medicamentos, modelagem climática e simulações financeiras

1 comentários

 
GN⁺ 2023-11-13
Opiniões no Hacker News
  • O código desse texto está errado. Nenhum kernel CUDA é chamado: https://github.com/RijulTP/GPUToolkit/blob/f17fec12e008d0d37...
    90% do tempo gasto para “calcular” o conjunto de Mandelbrot com código compilado por JIT é usado na compilação da função, não no cálculo em si
    Se você quer aprender CUDA de verdade, implementar multiplicação de matrizes é um bom exercício, e estes tutoriais valem como referência: https://cnugteren.github.io/tutorial/pages/page1.html e https://siboehm.com/articles/22/CUDA-MMM

    • Também existe SAXPY, chamado de “Hello World” do código matemático paralelo em CUDA. SAXPY é a sigla de “Single-Precision A·X Plus Y”, uma função BLAS padrão e uma operação muito simples que combina multiplicação por escalar e soma de vetores
      Ela recebe os vetores de ponto flutuante de 32 bits X e Y e o escalar A, multiplica cada X[i] por A e soma o resultado a Y[i]: https://developer.nvidia.com/blog/six-ways-saxpy/
    • Depois de receber a crítica, corrigiu o código e também atualizou o blog
  • O texto afirma que “todo desenvolvedor deveria saber”, mas, na prática, parece mais um texto sobre como GPUs são usadas em IA. A maioria dos desenvolvedores não é desenvolvedor de IA, nem interage diretamente com IA ou usa GPUs diretamente
    Além disso, quase não aborda gráficos 3D, que são a principal razão pela qual GPUs passaram a existir

    • Conhecer o básico de áreas adjacentes ajuda. Especialmente em uma área de aplicação ampla como aprendizado de máquina; talvez você precise usar aprendizado de máquina no projeto do mês que vem, e isso também ajuda ao colaborar com colegas responsáveis por essa parte
      Com conhecimento básico, também fica mais fácil entender as histórias de “IA” que vendem para os gerentes
      A atitude de “não preciso de áreas adjacentes” era algo que eu via com frequência na escola. Em administração de sistemas, meus colegas diziam que não precisavam saber programação, mas scripting era necessário; em uma escola de desenvolvimento de software, diziam que não precisavam saber redes, mas alguns anos depois DevOps apareceu amplamente nas vagas
      Se o texto tem cerca de 1.500 palavras, mesmo lendo como estudo leva algo como 12 minutos, e passar umas 2 horas executando os exemplos de código não é um grande investimento. Claro, isso pressupõe que o texto seja uma boa introdução
    • Quando saí de uma empresa tradicional de embarcados para uma startup, lembro de um colega me zoar de forma amigável porque eu não sabia enviar uma requisição JSON com curl. Ainda sou desenvolvedor embarcado, mas desde então aprendi bastante sobre backend, frontend e infraestrutura, e parece bem provável que, nos próximos anos, aconteça algo parecido no setor inteiro em torno de IA
    • Até o exemplo de renderização do conjunto de Mandelbrot só mostra aceleração de 10 vezes, e esse é um caso clássico de cálculo quase totalmente limitado pela quantidade de operações de ponto flutuante. Pessoalmente, achei o texto péssimo
    • Há muitas suposições imprecisas. Concordo que a maioria dos desenvolvedores não é desenvolvedor de IA, e o autor do texto original parece um pouco desconectado do conjunto geral de desenvolvedores, ou então assume o todo com base no mundo ao seu redor
    • Toda vez que vejo um texto afirmando que “todo desenvolvedor deveria saber”, a afirmação quase sempre é falsa. Pode até haver textos com informações que todo mundo realmente deveria saber, mas a maioria do que encontro é clickbait
  • Acho que o motivo de Python dominar em IA é que a relação Python-C se parece com a relação CPU-GPU
    GPUs têm desempenho muito alto, mas são difíceis de programar diretamente, então as pessoas lidam com GPUs por meio de chamadas a APIs de alto nível como PyTorch
    C também tem bom desempenho, mas é difícil de programar, então Python é usado como uma camada de abstração sobre C
    Não está claro se as pessoas realmente precisam entender GPUs com tanta profundidade. Isso vale ainda mais se você não está entrando a fundo em treinamento ou operação de IA; e, se a Lei de Moore acabar e multithreading se tornar a principal forma de ganhar velocidade, é bem provável que surjam novas linguagens alinhadas ao paradigma de programação paralela. Mojo parece ser o ponto de partida disso

    • Eu me perguntava se havia espaço para uma nova linguagem que maximizasse desempenho de forma invisível, independentemente do hardware em que estivesse rodando
      Algo projetado para, desde cálculos iterativos simples, usar inteligentemente todos os núcleos da CPU em paralelo por trás de cada instrução e passar para a GPU o que fosse possível
      Fico curioso se já houve tentativas assim, ou se isso sequer é possível
    • É difícil dizer que a Lei de Moore já acabou, e multithreading também não é a resposta. Mas a primeira frase está correta
    • Programar GPU não é tão difícil assim. CUDA é bastante intuitivo para muitas tarefas, e em muitos casos dá para obter um aumento de 100 vezes na velocidade de processamento com menos de 100 linhas de código
    • Usando linguagens mais modernas, é bem fácil obter desempenho no nível de C mantendo uma expressividade ao estilo Python. Na verdade, acho que C, por ter pouca abstração, faz código lento porém simples parecer mais atraente
    • C é um estilo de vida. Pessoas que usam quase exclusivamente C têm dificuldade de aceitar o conceito de “espaços em branco significativos” do Python
  • A explicação de que “quando a CPU encontra várias tarefas, ela aloca recursos para processar uma tarefa de cada vez” é simplista demais. Chego até a pensar que seria bom se CPUs ainda fossem simples assim
    É justo que o texto se concentre no modelo de programação, mas, do ponto de vista de desempenho, dizer que “a CPU executa instruções sequencialmente” é basicamente errado. Pipelines executam instruções em paralelo, há SIMD, e vários núcleos também podem trabalhar juntos no mesmo problema

    • O texto parece ter errado o foco. CPUs com AVX-512 também têm grande paralelismo de dados, e CPUs igualmente conseguem executar muitas instruções ao mesmo tempo
      A grande diferença é que CPUs gastam muito silício e energia com tratamento de fluxo de controle para executar uma única thread com eficiência, enquanto GPUs usam esses recursos em mais unidades de cálculo e executam inúmeras threads para esconder latências de fluxo de controle e de memória
    • CPUs também executam várias instruções SIMD simultaneamente
  • Dizer que CPUs são boas para código serial e GPUs são boas para código paralelo é correto até certo ponto, mas é uma aproximação bem grosseira. Assumindo um orçamento de energia parecido, na faixa de centenas de watts, uma CPU tem cerca de 100 “núcleos” que executam tarefas independentes uma a uma, incluindo hyper-threading, e esconde a latência de memória com previsão de desvios e pipelining
    Uma GPU tem cerca de 100 “unidades de computação”, e cada unidade intercala a execução de cerca de 80 tarefas independentes, escondendo a latência de memória ao executar a próxima instrução de outra tarefa
    A terminologia é bem confusa, e é bastante provável que a CPU tenha uma unidade vetorial de 256 bits de largura, enquanto a GPU tenha uma unidade vetorial de 2048 bits de largura, mas, olhando de um pouco mais longe, as duas arquiteturas parecem bem parecidas

    • GPUs têm cerca de 10 vezes mais largura de banda de memória que CPUs, e essa diferença se torna importante em LLMs. Isso porque, se o processamento em lote estiver otimizado, para gerar um token de saída é preciso, na prática, ler toda a memória, e essa memória é usada para pesos ou para o cache KV
    • Sempre achei curioso haver tão pouco movimento para combinar alguns núcleos de baixa latência com muitos núcleos de alta vazão. Bastaria cercar um núcleo P da Intel com vários núcleos E e colocar nesses núcleos E muitos núcleos de iGPU ou unidades AVX-512
      O nome poderia ser Xeon Chi
  • Como a maioria das linguagens de programação foi projetada para processamento sequencial, como uma CPU, enquanto Erlang/Elixir foi projetado para paralelismo, como uma GPU, fico curioso se Nx / Axon vai ganhar tração: https://github.com/elixir-nx/

    • Erlang foi projetado não para processamento paralelo com muita computação, mas para sistemas distribuídos com alta concorrência
    • Tenho muita curiosidade para saber quão bem Elixir e Nx funcionariam em tarefas intensivas em computação em clusters de computação de alto desempenho. Estruturalmente, não é muito diferente de MPI, muito usado nessa área, e poderia ser bem mais acessível, como o numpy e o ecossistema científico de Python
    • Estou analisando se a combinação de Elixir com Nx/Axon se encaixaria bem em arquiteturas como NVIDIA Grace Hopper, que misturam CPU e GPU
    • Fico curioso se isso roda em GPU. Acho que, no futuro, serão necessários ambos. Programação sequencial ainda é a melhor abstração para a maioria das tarefas que não exigem execução massivamente paralela
  • Preciso de um guia de compra. Quero saber qual é o mínimo que se deve gastar e qual é a melhor opção em cada faixa de orçamento. O problema é que essas informações mudam de tempos em tempos, e não sei se existe algum material mantido sempre atualizado

  • Então voltamos de novo aos textos clickbait do tipo “o que todo desenvolvedor precisa saber”?

    • Acho que esse formato de texto provavelmente será substituído pelo ChatGPT, mas, se for bem escrito, pode ter bastante valor na prática
      Gosto quando um texto encara a complexidade de frente e, como conheço em alguma medida tanto os métodos quantitativos quanto os detalhes qualitativos de áreas como hardware de computadores, fico feliz quando um artigo explica direito os detalhes de um campo
      Por exemplo, é outra questão se todo programador precisa saber “What every programmer should know about memory”, mas um bom programador deveria ter uma noção de como o computador realmente funciona. O ponto central a tirar daquele texto, a localidade, costuma surgir naturalmente em código bom: rápido, fácil de acompanhar e bem adequado ao problema
    • Parece que sim. As afirmações deste texto devem ser vistas com ressalvas
  • É um bom texto, mas as instâncias AWS P5, junto com P4d e P4de, são claramente voltadas para treinamento, não para inferência. Os tipos de instância mais adequados para inferência são G4dn e G5, que usam GPUs T4 e A10G, respectivamente

    • O texto original deixou a G5 de fora
  • Sou praticamente iniciante em programação em GPU, mas li este texto com interesse. É surpreendente ver que a área avançou a ponto de ser tão fácil treinar uma rede neural simples de “cachorro ou gato”