O Instituto de Computação (IC) adquiriu recentemente um servidor de alto desempenho, equipado com suporte para até 10 GPUs, que serão utilizadas de forma compartilhada. Esse recurso está disponível para atividades de teste e validação de soluções em IA voltadas para graduação, extensão e pós-graduação.

Como Solicitar Acesso

O acesso às GPUs será disponibilizado mediante solicitação do docente responsável, que deverá solicitar via suporte@ic.unicamp.br para atribuição da permissão ao aluno interessado. A equipe do IC habilitará o acesso no usuário institucional do aluno, permitindo a conexão ao servidor via SSH. O endereço é gpus.lab.ic.unicamp.br

Instruções de Acesso e Uso

Para se conectar ao servidor e utilizar as GPUs, siga as instruções abaixo:

  1. Conectar via SSH
    • Utilize seu usuário e senha institucional para acessar o servidor.
  2. Monitoramento de Processos nas GPUs
    • Execute o comando nvidia-smi para visualizar todos os processos ativos nas GPUs.
  3. Especificar uma GPU para Uso Para rodar um processo em uma GPU específica, utilize os seguintes comandos:
    • GPU A5500 24GB (slot 0):
      • export CUDA_VISIBLE_DEVICES=0
    • GPU GTX 980 4GB (slot 1):
      • export CUDA_VISIBLE_DEVICES=1
    • GPU GTX 980 4GB (slot 2):
      • export CUDA_VISIBLE_DEVICES=2
    • GPU A5500 24GB (slot 3):
      • export CUDA_VISIBLE_DEVICES=3
    • GPU GTX 980 4GB (slot 4):
      • export CUDA_VISIBLE_DEVICES=4

Instalação de Bibliotecas Python para Rodar

Para instalar bibliotecas Python com o pip, recomenda-se o uso do seguinte comando, que deve incluir a flag --user:

pip install <biblioteca> --user

Apenas bibliotecas que requerem chamadas de sistema privilegiadas necessitam de permissões de root. A maioria das bibliotecas que utilizam GPUs não exige permissões administrativas.

Uso de Docker com GPUs

O ambiente também suporta o uso de Docker. Exemplos de execução:

  • Para utilizar todas as GPUs disponíveis:
    docker run -it --rm --gpus all nvidia/cuda:11.0.3-base-ubuntu20.04 nvidia-smi
  • Para utilizar apenas a GPU 0:
    docker run -it --rm --gpus device=0 nvidia/cuda:11.0.3-base-ubuntu20.04 nvidia-smi
  • Para utilizar apenas a GPU 1:
    docker run -it --rm --gpus device=1 nvidia/cuda:11.0.3-base-ubuntu20.04 nvidia-smi
  • Para utilizar apenas a GPU 2:
    docker run -it --rm --gpus device=2 nvidia/cuda:11.0.3-base-ubuntu20.04 nvidia-smi

Acesso ao Ollama

O ambiente também conta com o Ollama, que permite a execução de modelos. Para verificar os modelos disponíveis e executar um deles:

  • Liste os modelos disponíveis:
    ollama list

    Exemplos de modelos disponíveis:

    • codellama:13b
    • gemma:7b
    • llama2:13b
    • qwen:4b
  • Para rodar um modelo, utilize o comando:
    ollama run qwen:4b

Memória das GPUs

Você pode definir em qual GPU seu processo deve ser iniciado, utilizando as variaves de ambiente CUDA_VISIBLE_DEVICES ou a partir do seu container docker –gpus device=X. E com base ao comando nvidia-smi você ve a utilização de cada GPU.

Porem existem outras estrategias para alocação de memoria e GPU, GPU vs CPU RAM.

O conceito de “swap” entre disco e GPU é diferente do swap de memória convencional entre RAM e disco. No caso da GPU, a memória de vídeo (VRAM) é bastante limitada em comparação à RAM do sistema, e quando a VRAM é insuficiente, não há um mecanismo nativo de swap entre disco e VRAM como existe entre disco e RAM. Porém, é possível utilizar algumas técnicas para lidar com grandes quantidades de dados na GPU e, em alguns casos, transferir partes dos dados entre a memória principal (RAM) e a GPU, simulando um comportamento parecido com o swap.

Aqui estão algumas maneiras de realizar algo semelhante ao “swap” em Python, em contextos onde os dados são maiores que a VRAM disponível:

1. Uso de Bibliotecas Otimizadas para Transferência entre RAM e GPU.

CuPy e PyTorch são bibliotecas que facilitam a transferência de dados entre a RAM e a GPU de forma eficiente. Você pode mover partes dos dados para a GPU quando necessário e liberar a memória na GPU quando não forem mais necessários.

Exemplo em PyTorch:

import torch

# Crie um tensor grande na RAM (CPU)
data_cpu = torch.randn(1000000, 1000)

# Mova partes do tensor para a GPU conforme necessário
for i in range(0, data_cpu.size(0), 100000):
data_chunk_gpu = data_cpu[i:i+100000].cuda() # Transferindo para a GPU

# Realize operações na GPU
result_gpu = data_chunk_gpu * 2

# Libere a memória da GPU quando terminar
del data_chunk_gpu
torch.cuda.empty_cache() # Libera memória não utilizada da GPU

No exemplo acima, pequenos blocos de dados são transferidos para a GPU para evitar sobrecarregar a VRAM, e a memória é liberada após o uso.

2. Utilização de Disco para Dados Grandes.

Para conjuntos de dados que não podem ser mantidos completamente em RAM, você pode utilizar soluções como Dask ou Zarr, que permitem o carregamento de dados grandes em pedaços diretamente do disco. Esses pedaços podem então ser transferidos para a GPU conforme necessário.

Exemplo usando Dask para carregamento de grandes arquivos:

import dask.array as da
import cupy as cp

# Carregar dados grandes em blocos diretamente do disco
data = da.from_zarr('large_data.zarr') # Dados no disco

# Transferir blocos para a GPU e processar
for chunk in data.to_delayed():
chunk_gpu = cp.asarray(chunk.compute()) # Computar e mover para GPU

# Processar no GPU
result = chunk_gpu * 2

# Libera a memória da GPU após o processamento
del chunk_gpu
cp._default_memory_pool.free_all_blocks()

3. Uso de Memória Compartilhada com Managed Memory (NVIDIA CUDA)

Com GPUs compatíveis com CUDA, você pode usar a chamada “Unified Memory” que permite o uso da memória principal do sistema de forma unificada com a GPU. Assim, o CUDA gerencia a troca entre memória principal e a VRAM automaticamente.

Para isso, você precisa escrever código em CUDA, mas em Python pode ser feito com bibliotecas como PyCUDA:

Exemplo com PyCUDA:

import pycuda.autoinit
import pycuda.driver as cuda
import numpy as np
import pycuda.gpuarray as gpuarray

# Cria um array na CPU
data_cpu = np.random.randn(1000000).astype(np.float32)

# Aloca memória unificada entre CPU e GPU
data_unified = cuda.managed_zeros_like(data_cpu, mem_flags=cuda.mem_attach_flags.GLOBAL)

# Mover dados para a memória unificada
data_unified[:] = data_cpu

# O CUDA gerenciará automaticamente a transferência entre CPU e GPU

Essa técnica só funciona se o hardware e drivers suportarem memória unificada.