Pesquisa de sites

Do Python ao Silício: A Pilha de Computação de IA Explicada


O que realmente acontece quando você executa um modelo de IA no PyTorch?

Muitos profissionais de IA nunca precisam de pensar sobre isto. Eles projetam e treinam modelos usando frameworks de alto nível e sabem que os cálculos acontecem numa GPU, mas raramente precisam de pensar em como isto funciona internamente.

Isto acontece porque estes frameworks, como o PyTorch, fornecem interfaces fáceis de usar para interagir com a GPU. Mas, para tornar isto possível, existem camadas e camadas de software para traduzir este código em instruções que um chip físico consegue entender. Estas camadas são chamadas de “AI compute stack”, e cada fornecedor de hardware tem as suas próprias versões - CUDA para a NVIDIA e ROCm para a AMD.

Neste artigo, vamos desvendar as camadas de abstração dentro destas pilhas de computação. Vamos construir cada camada a partir da GPU física até aos frameworks de alto nível como o PyTorch, destacando os componentes correspondentes dos ecossistemas da NVIDIA e da AMD.

Camada 0: O Hardware (A Fundação)

Na base da pilha, temos o componente físico: a própria GPU (por exemplo, uma NVIDIA H200 ou AMD Instinct MI300X).

Estes componentes são valiosos porque são muito eficientes a realizar multiplicação de matrizes. Esta é a operação matemática que alimenta muitos modelos de IA, como os LLMs. Para calcular uma única multiplicação de matrizes, um computador precisa de realizar milhares de milhões de multiplicações simples (como 2 x 3), chamadas de “multiplicações escalares”.

As pessoas costumavam realizar estas operações em CPUs (Unidades Centrais de Processamento), que muitas vezes são referidas como o 'cérebro' de um computador. Mas em 2012, um modelo chamado AlexNet demonstrou que um tipo diferente de componentes de hardware, chamados GPUs (Unidades de Processamento Gráfico), são na verdade mais adequados para esta tarefa de multiplicação de matrizes.

CPUs são inteligentes, mas sequenciais. Foram concebidas para realizar tarefas complexas uma de cada vez muito rapidamente. GPUs são “simples”, mas massivas. Possuem milhares de “núcleos” que podem processar operações simples em paralelo.

Uma GPU pode executar muitas operações simples (caixas verdes) em paralelo, levando a ganhos de eficiência extremamente elevados em comparação com os CPUs, que têm de realizar estas operações em sequência

Estas «operações simples» que as GPUs conseguem executar de forma eficiente incluem multiplicações escalares, a mesma coisa que impulsiona a multiplicação de matrizes (e, portanto, os modelos de IA). Por isso, as GPUs são muito mais eficientes na formação e execução de modelos de IA do que os CPUs.

A limitação é que a GPU tem uma barreira linguística. Ela não entende linguagens de programação como Python, C++ ou mesmo Assembly. Ela apenas entende código de máquina específico (o Conjunto de Instruções ou ISA) concebido exatamente para esse chip.

Para colmatar esta lacuna, precisamos de software.

Camada 1: O Driver do Núcleo

Primeiro, um aviso sobre jargão: Em IA, ouvirá a palavra “kernel” com frequência. Esta camada, o Kernel Driver, refere-se ao Kernel Driver do sistema operativo. Isto é diferente de um “GPU Compute Kernel”, que discutiremos na Camada 4.

O Driver do Kernel é um módulo dentro do kernel do seu sistema que atua como a ponte entre a sua GPU física e o seu computador. É a primeira camada desta pilha que se encontra dentro dos ecossistemas CUDA (pronunciado “coo-dah”) e ROCm (pronunciado “rock-um”).

Fisicamente, a sua GPU liga-se à placa-mãe através de PCIe. O Kernel Driver é o componente de software que envia dados de um lado para o outro através dessa ligação física, permitindo que o sistema operativo do seu computador (como MacOS, Windows 11, Ubuntu, etc.) comunique com o firmware da GPU. O Kernel Driver opera em “modo kernel” com os privilégios mais elevados, que são necessários para gerir a memória da GPU, o consumo de energia e a temperatura através desta ligação.

Os conetores brancos e vermelhos que vê acima são slots PCIe. É aqui que liga a sua GPU na motherboard do seu computador, e a ligação através da qual o Kernel Driver comunica com a GPU (fonte

Crucialmente, o Driver do Kernel é a única camada que comunica diretamente com a GPU. Todas as camadas acima desta têm de passar as suas mensagens através do Driver do Kernel.

Camada 2: O Tempo de Execução do Sistema

Subindo um nível, chegamos ao Tempo de Execução do Sistema. Pense nesta camada como o Controlador de Tráfego Aéreo para o sistema combinado CPU + GPU, tratado como uma única unidade computacional unificada.

O Runtime do Sistema atua como um agendador de tarefas. Ele cria "pacotes" de comandos, organiza-os em filas e envia-os para a GPU. Também espera que a GPU sinalize de volta, "Ei, terminei esse trabalho".

Precisamos da Camada 2 porque a Camada 1, o Driver do Kernel, preocupa-se apenas com a comunicação direta com a GPU. Não faz sentido que cada programa individual que queira usar a GPU tente comunicar-se diretamente com ela. O Runtime do Sistema atua como o controlador central que orquestra todo o trabalho que precisa ser feito na GPU, para que o Driver do Kernel possa concentrar-se em comunicar com a GPU para executar uma tarefa individual.

O Runtime do Sistema ROCm chama-se ROCr. Ele implementa um standard aberto chamado “Heterogeneous System Architecture” (HSA). Este standard define como sistemas de unidades computacionais heterogéneas (como uma combinação de CPU e GPU) devem funcionar em conjunto como uma única unidade.

A CUDA não tem um equivalente exato, mas o mais próximo é a CUDA Driver API, que é uma API proprietária que desempenha uma função semelhante (embora também possa ser considerada parte da camada seguinte, uma vez que se pode programar diretamente contra ela).

Camada 3: A Interface de Programação

A Camada 3 é onde os engenheiros de software que estão a escrever código para uma GPU começam realmente a interagir com a stack, o que fazem através da Interface de Programação para o seu ecossistema.

Programar diretamente uma GPU em código máquina é impraticável para os humanos. Para resolver isto, os fornecedores disponibilizam extensões C++ que permitem aos programadores escrever código convencional que pode depois ser compilado para código máquina da GPU.*

Existem duas destas extensões de C++:

  • NVIDIA utiliza CUDA C++, este é o padrão da indústria e tem sido o standard há mais de uma década
  • AMD usa HIP (Interface de Computação Heterogénea para Portabilidade)

Tecnicamente, o equivalente ao HIP é a combinação de ambos CUDA C++ e a API de Runtime CUDA em conjunto, comumente referida apenas como “CUDA”. Tanto o HIP como o CUDA são basicamente apenas C++, mas fornecem palavras-chave e funções adicionais para capacidades críticas relacionadas com a programação em GPU, como:

  1. Alocação: Reservar memória na GPU
  2. Movimento: Transferência de dados da CPU para a GPU
  3. Execução: Lançamento de cálculos na GPU

Quando executa um programa escrito em HIP ou CUDA (a interface de programação), estes comandos relacionados com GPU são entregues ao System Runtime (Camada 2)**, que se encarrega de garantir que sejam executados.

As linhas ficam um pouco difusas entre os dois ecossistemas nas camadas 2 e 3 - eles não são diretamente equivalentes nessas camadas. Por exemplo, o ROCr é pura infraestrutura e não pode ser programado diretamente pelos desenvolvedores, enquanto a API do CUDA Driver expõe funcionalidades com as quais você pode programar usando CUDA C++. No entanto, esses detalhes não são importantes para a maioria dos desenvolvedores.

O que é importante é compreender (1) que ROCm e CUDA (o ecossistema) são arquitetonicamente diferentes, e, mais importante, (2) que quando as pessoas dizem “CUDA”, podem ou estar a referir-se à interface de programação do CUDA C++ juntamente com a API de Runtime do CUDA, ou a todo o ecossistema CUDA.

Uma Nota sobre Compatibilidade

A AMD fez uma escolha estratégica com o HIP, que foi lançado anos depois do CUDA. A AMD desenhou o HIP para se parecer quase exatamente com o CUDA. Isto permite aos desenvolvedores portar código CUDA para HIP com alterações mínimas. A AMD fornece até uma ferramenta chamada hipify para automatizar esta tradução. O HIP foi concebido para ser portátil, o que significa que pode correr tanto em GPUs AMD como em GPUs NVIDIA.

O CUDA nem sempre pode ser portado para HIP porque as bibliotecas do ROCm não são tão maduras, pelo que a conversão do seu CUDA para HIP depende do seu caso de uso específico. A diferença está a diminuir, mas a NVIDIA continua a ter uma vantagem em áreas de nicho (como a Computação Quântica). No entanto, para aplicações de IA convencionais e computação científica padrão, o ecossistema da AMD amadureceu significativamente e atingiu a paridade para muitas cargas de trabalho.

* Nota de rodapé: “compilado para código máquina” é uma simplificação. Na realidade, os programas CUDA C++ e HIP são divididos quando compilados. As partes do código para a CPU são compiladas pelo compilador C++ normal do sistema, e as partes para a GPU são compiladas pelo compilador da GPU e armazenadas no programa como um bloco de dados no programa.

Quando o programa é executado, ele corre na CPU. Quando se encontra uma secção de código para a GPU, a CPU delega a execução dessa secção à GPU, enviando o bloco de código pré-compilado para o runtime da GPU, para que este trate da execução do código na GPU.

** Nota de rodapé: Para maior completude, a AMD utiliza uma camada intermédia chamada ROCclr (ROCm Common Language Runtime) entre o HIP e o ROCr. Esta camada de abstração permite à AMD suportar múltiplas interfaces de programação (HIP e OpenCL) com o mesmo runtime subjacente. Para simplificar, tratamos isto como parte da ligação HIP → ROCr.

Camada 4: Bibliotecas de Matemática

Se estiver a criar uma aplicação de IA, provavelmente não está a escrever CUDA C++ ou HIP puro para multiplicar matrizes. Isso seria como escrever o seu próprio motor de base de dados apenas para armazenar o endereço de e-mail de um utilizador — ineficiente e sujeito a erros.

Em vez disso, você utiliza bibliotecas de matemática - a última camada na pilha que está dentro dos ecossistemas CUDA e ROCm.

Estas são colecções de "kernels de cálculo" otimizados (pequenos programas que correm na GPU) escritos pelos próprios fabricantes de hardware. Estão altamente otimizados para extrair o máximo desempenho do hardware.

A maioria destas bibliotecas espelha-se mutuamente através dos ecossistemas:

Tipo de OperaçãoNVIDIA (CUDA)AMD (ROCm)
Álgebra Linear BásicacuBLAS, cuSPARSErocBLAS, rocSPARSE
Aprendizagem ProfundacuDNNMIOpen
ResolutorescuSOLVERrocSOLVER

Quando um investigador fala em “otimizar kernels”, geralmente está a falar de reescrever o código dentro dessas bibliotecas para que funcione mais rapidamente em arquiteturas de hardware específicas.

Camada 5: Estruturas de IA

Finalmente, chegamos ao topo da pilha: Os Frameworks de IA.

É aqui que vivem PyTorch, TensorFlow e JAX, e onde a maioria dos desenvolvedores de IA trabalha. Estes frameworks abstraem toda a complexidade que acabámos de discutir, proporcionando uma forma intuitiva e 'pythónica' de construir modelos sem se preocupar com a alocação de memória ou largura de banda PCIe. Estes frameworks têm diferentes versões dos seus backends, uma para CUDA e outra para ROCm.

Mas essa simplicidade não vem à custa da velocidade. Quando executa uma função no PyTorch, ela atua principalmente como um wrapper que chama essas bibliotecas altamente otimizadas de Nível 4. Assim, ao usar um framework, obtém a flexibilidade e simplicidade do Python com o desempenho de kernels de GPU otimizados.

Estas frameworks permitem-lhe escrever código legível desta forma:

import torch

# 1. define the device (the GPU)
device = torch.device('cuda')

# 2. Create two random matrices on the GPU
A = torch.randn(1024, 1024, device=device)
B = torch.randn(1024, 1024, device=device)

# 3. Perform the calculation (Matrix Multiplication)
C = torch.matmul(A, B)

print(f"Calculation complete. Result shape: {C.shape}")

Em vez de tentar escrever esses programas em instruções de GPU de baixo nível e confusas:

// Address      // Hex Encoding        // Disassembly (Operation)
/*0000*/        0x00005a0000057ab9     IMAD.MOV.U32 R1, RZ, RZ, c[0x0][0x28] 
/*0010*/        0x0000980000000a00     IMAD.MOV.U32 R0, RZ, RZ, c[0x0][0x20] 
/*0020*/        0x0000020000027a02     MOV R2, c[0x0][0x0] 
/*0030*/        0x3800000000025910     IMAD.WIDE.U32 R2, R2, R0, c[0x0][0x0]
/*0040*/        0x0000580000000a00     IMAD.MOV.U32 R0, RZ, RZ, c[0x0][0x24]
/*0050*/        0x0000000002037202     IADD3 R3, R2, c[0x0][0x0], RZ 
/*0060*/        0x0001940003027981     LDG.E.SYS R2, [R2] 
/*0070*/        0x0000020000007200     IADD3 R0, R0, 0x1, RZ 
/*0080*/        0x000000000000794d     STG.E.SYS [R2], R0 
/*0090*/        0x0000000000007943     EXIT

Resumo: O Rasto Completo

Para visualizar como tudo isto se liga, vamos seguir um único comando. Imagine que está a usar PyTorch numa máquina AMD e executa uma multiplicação de matrizes: torch.matmul().

  1. Python (Camada 5): Você executa o comando. O backend do PyTorch é ativado.
  2. Biblioteca de Matemática (Camada 4): O PyTorch chama rocBLAS, solicitando um kernel de multiplicação de matrizes.
  3. Interface de Programação (Camada 3): rocBLAS chama a API HIP para executar esse kernel.
  4. Tempo de Execução do Sistema (Camada 2): O Runtime coloca o trabalho na fila e agenda a sua execução.
  5. Driver do Kernel (Camada 1): O driver recebe o pacote e envia-o através do bus PCIe.
  6. Hardware (Camada 0): O silício da GPU recebe o código de máquina e executa os cálculos.

Toda esta pilha - desde o Python até ao Silício - é uma conquista notável de engenharia. Ela preenche a lacuna entre a lógica humana e o poder computacional bruto, permitindo a explosão da IA que vemos hoje.

A pilha completa de computação de IA, desde o hardware (verde), passando pelos ecossistemas CUDA e ROCm (azul), até aos frameworks de IA (amarelo)

Obrigado por aprender com a comunidade Linux-Console.net.