Uso de GPUs
Nesta seção:
O GridUnesp possui um nó dedicado com GPUs de alto desempenho para aceleração de aplicações científicas, especialmente em áreas como aprendizado de máquina, simulações moleculares e processamento de imagens.
Infraestrutura de GPU
Nó GPU: gpunode001
Componente |
Especificação |
|---|---|
CPU |
AMD EPYC 9224 (48 cores, 96 threads) |
Memória RAM |
1.5 TB |
GPUs |
4 × NVIDIA L40S |
Memória por GPU |
48 GB GDDR6 |
Total de memória GPU |
192 GB |
Arquitetura |
Ada Lovelace |
CUDA Cores |
18.176 por GPU |
Tensor Cores |
568 por GPU (4ª geração) |
Partição GPU
Os jobs que utilizam GPU devem ser submetidos à partição especial gpu:
#SBATCH --partition=gpu
#SBATCH --gres=gpu:N # N = número de GPUs desejado (1-4)
Limites da partição gpu:
Tempo máximo: 24 horas
GPUs por job: 1 a 4
Memória: Até 1.5 TB (compartilhada com CPU)
Script Básico para GPU
#!/bin/bash
#SBATCH -J gpu_test
#SBATCH --partition=gpu
#SBATCH --nodes=1
#SBATCH --ntasks=1
#SBATCH --gres=gpu:1 # Solicita 1 GPU
#SBATCH --cpus-per-task=8 # 8 CPUs para alimentar a GPU
#SBATCH --time=06:00:00
#SBATCH --mem=32G
#SBATCH --output=gpu_%j.out
export INPUT="dados_entrada/"
export OUTPUT="resultados_gpu/"
# Carregar módulos necessários
module load cuda/12.9
job-nanny ./programa_cuda
Exemplo com PyTorch
Criação do ambiente Conda
# Carregar módulo Conda
module load miniconda/24.4.0-libmamba
# Criar ambiente com Python e PyTorch
conda create -n pytorch_env python=3.9
source activate pytorch_env
# Instalar PyTorch com suporte CUDA
conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia
Código Python de exemplo
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import time
# Verificar GPUs disponíveis
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
num_gpus = torch.cuda.device_count()
print(f"GPUs disponíveis: {num_gpus}")
print(f"Usando dispositivo: {device}")
# Definir modelo simples
class SimpleNN(nn.Module):
def __init__(self):
super(SimpleNN, self).__init__()
self.fc1 = nn.Linear(28*28, 512)
self.fc2 = nn.Linear(512, 256)
self.fc3 = nn.Linear(256, 10)
self.relu = nn.ReLU()
def forward(self, x):
x = x.view(-1, 28*28)
x = self.relu(self.fc1(x))
x = self.relu(self.fc2(x))
x = self.fc3(x)
return x
# Preparar dados
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
train_dataset = datasets.MNIST('./data', train=True, download=True, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
# Inicializar modelo
model = SimpleNN().to(device)
# Se houver múltiplas GPUs, usar DataParallel
if num_gpus > 1:
model = nn.DataParallel(model)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# Treinar
model.train()
start_time = time.time()
for epoch in range(5):
running_loss = 0.0
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
running_loss += loss.item()
if batch_idx % 100 == 99:
print(f'Epoch {epoch+1}, Batch {batch_idx+1}, Loss: {running_loss/100:.4f}')
running_loss = 0.0
elapsed = time.time() - start_time
print(f"Treinamento concluído em {elapsed:.2f} segundos")
# Salvar modelo
torch.save(model.state_dict(), 'modelo_final.pth')
Script de submissão para PyTorch
#!/bin/bash
#SBATCH -J pytorch_gpu
#SBATCH --partition=gpu
#SBATCH --nodes=1
#SBATCH --ntasks=1
#SBATCH --gres=gpu:2 # Usar 2 GPUs
#SBATCH --cpus-per-task=16 # 16 CPUs para alimentar as GPUs
#SBATCH --time=12:00:00
#SBATCH --mem=64G
#SBATCH --output=pytorch_%j.out
export INPUT="treinamento.py"
export OUTPUT="modelo_final.pth data/"
module load miniconda/24.4.0-libmamba
source activate pytorch_env
job-nanny python treinamento.py
Exemplo com TensorFlow
Criação do ambiente
module load miniconda/24.4.0-libmamba
conda create -n tf_env python=3.9
source activate tf_env
pip install tensorflow[and-cuda]
Código Python
import tensorflow as tf
import time
# Verificar GPUs
gpus = tf.config.list_physical_devices('GPU')
print(f"GPUs disponíveis: {len(gpus)}")
if gpus:
for gpu in gpus:
print(f"GPU: {gpu.name}")
# Configurar crescimento de memória (opcional)
for gpu in gpus:
tf.config.experimental.set_memory_growth(gpu, True)
# Carregar dados MNIST
mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
# Definir modelo
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=(28, 28)),
tf.keras.layers.Dense(512, activation='relu'),
tf.keras.layers.Dense(256, activation='relu'),
tf.keras.layers.Dense(10, activation='softmax')
])
# Compilar
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# Treinar
start_time = time.time()
history = model.fit(x_train, y_train, epochs=5, validation_data=(x_test, y_test))
elapsed = time.time() - start_time
print(f"Treinamento concluído em {elapsed:.2f} segundos")
model.save('modelo_tf_final.h5')
Script de submissão
#!/bin/bash
#SBATCH -J tf_gpu
#SBATCH --partition=gpu
#SBATCH --nodes=1
#SBATCH --ntasks=1
#SBATCH --gres=gpu:1
#SBATCH --cpus-per-task=8
#SBATCH --time=08:00:00
#SBATCH --mem=32G
export INPUT="tf_treino.py"
export OUTPUT="modelo_tf_final.h5"
module load miniconda/24.4.0-libmamba
source activate tf_env
job-nanny python tf_treino.py
Exemplo com CUDA (C/C++)
Código CUDA simples
#include <stdio.h>
#include <cuda_runtime.h>
// Kernel CUDA
__global__ void add_vectors(float *a, float *b, float *c, int n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
c[i] = a[i] + b[i];
}
}
int main() {
int n = 1000000;
size_t size = n * sizeof(float);
// Alocar memória no host
float *h_a = (float*)malloc(size);
float *h_b = (float*)malloc(size);
float *h_c = (float*)malloc(size);
// Inicializar dados
for (int i = 0; i < n; i++) {
h_a[i] = i * 1.0f;
h_b[i] = i * 2.0f;
}
// Alocar memória no device
float *d_a, *d_b, *d_c;
cudaMalloc(&d_a, size);
cudaMalloc(&d_b, size);
cudaMalloc(&d_c, size);
// Copiar dados para device
cudaMemcpy(d_a, h_a, size, cudaMemcpyHostToDevice);
cudaMemcpy(d_b, h_b, size, cudaMemcpyHostToDevice);
// Configurar e lançar kernel
int threads = 256;
int blocks = (n + threads - 1) / threads;
add_vectors<<<blocks, threads>>>(d_a, d_b, d_c, n);
cudaDeviceSynchronize();
// Copiar resultado de volta
cudaMemcpy(h_c, d_c, size, cudaMemcpyDeviceToHost);
// Verificar resultado
printf("c[0] = %f\n", h_c[0]);
printf("c[%d] = %f\n", n-1, h_c[n-1]);
// Liberar memória
cudaFree(d_a);
cudaFree(d_b);
cudaFree(d_c);
free(h_a);
free(h_b);
free(h_c);
return 0;
}
Compilação e execução
# Script de submissão
cat > job_cuda.sh << 'EOF'
#!/bin/bash
#SBATCH -J cuda_test
#SBATCH --partition=gpu
#SBATCH --gres=gpu:1
#SBATCH --time=00:30:00
export INPUT="vetor_add.cu"
export OUTPUT="*"
# Compilar
module load cuda/12.9
nvcc -o vetor_add vetor_add.cu
job-nanny ./vetor_add
EOF
sbatch job_cuda.sh
Monitoramento de GPUs
Durante a execução do job, você pode monitorar o uso das GPUs:
# Após o job iniciar, submeta um novo job
#!/bin/bash
#SBATCH -J check_gpu
#SBATCH --partition=gpu
#SBATCH --gres=gpu:1
#SBATCH --time=00:30:00
nvidia-smi
Exemplo de saída nvidia-smi:
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 525.60.11 Driver Version: 525.60.11 CUDA Version: 12.9 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|===============================+======================+======================|
| 0 NVIDIA L40S Off | 00000000:00:00.0 Off | 0 |
| 30% 65°C P0 250W / 300W| 10240MiB / 49152MiB | 85% Default |
+-------------------------------+----------------------+----------------------+
Dentro do script, você pode monitorar:
# No script de submissão
nvidia-smi --query-gpu=timestamp,name,pci.bus_id,driver_version,pstate,pcie.link.gen.max,pcie.link.gen.current,temperature.gpu,utilization.gpu,utilization.memory,memory.total,memory.free,memory.used --format=csv
Otimização para GPU
Tamanho de batch adequado
Muito pequeno: subutiliza a GPU
Muito grande: pode estourar memória
Data loading eficiente
Use
DataLoadercomnum_workersadequado (veja Código Python de exemplo)Pré-processe dados em paralelo com CPU
Comunicação CPU-GPU minimizada
Transfira dados em lotes
Mantenha dados na GPU o máximo possível
Precisão mista
# PyTorch from torch.cuda.amp import autocast, GradScaler scaler = GradScaler() with autocast(): output = model(data) loss = criterion(output, target)
Script com precisão mista:
#!/bin/bash
#SBATCH -J mixed_gpu
#SBATCH --partition=gpu
#SBATCH --gres=gpu:1
#SBATCH --cpus-per-task=8
#SBATCH --time=12:00:00
export INPUT="treinamento_mixed.py"
export OUTPUT="modelo_mixed.pth"
module load miniconda/24.4.0-libmamba
source activate pytorch_env
# Configurar para precisão mista
export CUDA_VISIBLE_DEVICES=0
export PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:128
job-nanny python treinamento_mixed.py
Problemas Comuns
Fora de memória (OOM)
Reduza batch size
Use gradient accumulation
Libere memória com
torch.cuda.empty_cache()
GPU subutilizada
Aumente batch size
Verifique bottleneck de I/O
Aumente número de CPUs para data loading
Erro de versão CUDA
Verifique compatibilidade PyTorch/TensorFlow com CUDA
Use módulos compatíveis
module load cuda/12.4 # Compatível com PyTorch 2.4 ou versões mais recentes
module load cuda/12.9 # Compatível com PyTorch 2.7 ou versões mais recentes
Ver também
Instalando Aplicações - Instalação de pacotes para GPU
Containers - Uso de containers com GPU
Processando Simulações - Conceitos básicos de submissão
Otimizando o Desempenho - Otimização geral