Uso de GPUs

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

Especificações do nó GPU

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

job_gpu_simples.sh
#!/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

treinamento.py
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

job_pytorch.sh
#!/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

tf_treino.py
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

job_tensorflow.sh
#!/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

vetor_add.cu
#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

  1. Tamanho de batch adequado

    • Muito pequeno: subutiliza a GPU

    • Muito grande: pode estourar memória

  2. Data loading eficiente

  3. Comunicação CPU-GPU minimizada

    • Transfira dados em lotes

    • Mantenha dados na GPU o máximo possível

  4. 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:

job_mixed_precision.sh
#!/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

  1. Fora de memória (OOM)

    • Reduza batch size

    • Use gradient accumulation

    • Libere memória com torch.cuda.empty_cache()

  2. GPU subutilizada

    • Aumente batch size

    • Verifique bottleneck de I/O

    • Aumente número de CPUs para data loading

  3. 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