Quando sua GPU fica sem VRAM: o que fazer
A VRAM da GPU é o gargalo de cargas de IA e gráficos. Veja como memória unificada, offloading de VRAM e swap em NVMe funcionam por dentro.

O erro mais comum em machine learning não é um traceback do Python nem um erro de incompatibilidade de shape. É CUDA out of memory. Seu modelo é grande demais, seu batch size é alto demais, ou suas ativações intermediárias não cabem na VRAM da sua GPU. Uma RTX 4090 tem 24 GB. Um modelo de 70B parâmetros em meia precisão precisa de ~140 GB. A conta não fecha, e jogar dinheiro em GPUs maiores só adia o problema — uma H100 com 80 GB ainda não consegue carregar os maiores modelos em uma única placa.
Mas e se você pudesse estender a memória da GPU de forma transparente usando a RAM do sistema ou até um armazenamento NVMe? Ferramentas como o Greenboost da NVIDIA e projetos semelhantes fazem exatamente isso — usando a hierarquia de memória (VRAM → RAM do sistema → SSD) para rodar cargas que não deveriam caber no seu hardware. Os trade-offs de performance são reais, mas surpreendentemente administráveis para muitos casos de uso. Para entender como isso funciona, é preciso entender a própria hierarquia de memória da GPU.
Por que a memória da GPU é diferente da memória da CPU
CPU e GPU atendem a padrões de acesso fundamentalmente diferentes. Cargas de CPU são sensíveis à latência — uma única thread precisa de um dado e fica bloqueada até ele chegar. As caches da CPU são projetadas para minimizar a latência em padrões de acesso aleatórios.
Cargas de GPU são sensíveis a throughput — milhares de threads precisam dos seus dados, e a GPU pode alternar entre threads para esconder a latência. A memória da GPU (HBM em GPUs de datacenter, GDDR em placas de consumo) é projetada para largura de banda: entregar enormes volumes de dados por segundo, mesmo que cada acesso individual demore mais do que um hit na cache da CPU.
Memory bandwidth comparison (approximate):
RTX 4090 GDDR6X: 1,000 GB/s
H100 HBM3: 3,350 GB/s
DDR5 System RAM: 50 GB/s
PCIe 5.0 x16: 64 GB/s (theoretical max)
NVMe SSD: 7 GB/s
The bandwidth cliff between VRAM and system RAM is ~20x.
Between VRAM and NVMe it's ~140x.
This is why naive offloading to system RAM kills performance —
you're trying to feed a 1,000 GB/s appetite through a 50 GB/s straw.
Essa diferença de largura de banda é o motivo de não funcionar, de forma ingênua, simplesmente tornar a memória da GPU 'virtual' — paginar dados para a RAM do sistema como a CPU faz com o disco. Uma CPU tolera page faults com talvez uma lentidão de 10x. Uma GPU que acessa a RAM do sistema em vez da VRAM sofre uma redução de 20x na largura de banda, o que, para cargas limitadas por banda (a maioria da inferência de ML), significa uma lentidão de 20x.
Como o offloading de VRAM realmente funciona
O truque não é tratar a RAM do sistema como uma VRAM lenta. É fazer prefetch dos dados da RAM do sistema para a VRAM antes que a GPU precise deles, escondendo a latência por trás da computação. Essa é a ideia-chave por trás de toda técnica prática de extensão de VRAM.
Durante a inferência de uma rede neural, a computação é sequencial através das camadas. Enquanto a GPU processa a camada 5, ela sabe que a camada 6 vem a seguir. Um sistema de offloading inteligente pode começar a transferir os pesos da camada 6 da RAM do sistema para a VRAM enquanto a camada 5 está sendo calculada. Se a computação demorar mais que a transferência (o que costuma acontecer com camadas grandes), a transferência fica totalmente escondida — a GPU nunca para.
# Conceptual overlap of compute and transfer
# (simplified pseudocode)
def inference_with_offloading(model, input_data):
# Only 2 layers fit in VRAM at a time
# Rest are in system RAM
for i, layer in enumerate(model.layers):
# Start async transfer of NEXT layer while computing current
if i + 1 < len(model.layers):
async_transfer_to_gpu(model.layers[i + 1])
# Compute on current layer (GPU is busy, transfer happens in parallel)
output = layer.forward(input_data)
# Evict current layer from VRAM (it's done)
transfer_to_ram(layer)
# Wait for next layer transfer to complete (usually already done)
sync_transfer()
input_data = output
return output
Essa abordagem em pipeline funciona bem para inferência porque o grafo de computação é previsível — você sabe exatamente quais pesos serão necessários em seguida. Treinamento é mais difícil, porque os passes backward precisam das ativações do forward, criando padrões de movimentação de dados mais complexos.
As abordagens na prática
Memória unificada da CUDA
A CUDA Unified Memory da NVIDIA cria um único espaço de endereçamento que abrange a VRAM da GPU e a RAM do sistema. O runtime da CUDA migra páginas automaticamente entre elas com base nos padrões de acesso. Quando a GPU acessa uma página na RAM do sistema, ela dispara um page fault e a página é migrada para a VRAM.
A vantagem: é transparente para a aplicação. Seu código CUDA não precisa gerenciar o posicionamento dos dados. A desvantagem: page faults são caros, e as heurísticas de migração do runtime nem sempre batem com o padrão de acesso da aplicação. Para cargas previsíveis, como inferência de redes neurais, o gerenciamento explícito supera a migração automática.
Offloading camada por camada
Ferramentas como o Hugging Face Accelerate, o DeepSpeed ZeRO-Inference e a flag --mmap do llama.cpp implementam offloading explícito de camadas. Elas mantêm só as camadas ativas na VRAM e transmitem o restante da RAM do sistema ou do disco. O modelo não precisa caber inteiro na VRAM — ele só precisa caber uma ou duas camadas por vez.
É assim que rodar modelos de 70B em hardware de consumo funciona na prática. Um modelo de 70B quantizado em 4 bits precisa de ~40 GB no total, mas qualquer camada individual precisa de apenas ~1-2 GB. Com 24 GB de VRAM e prefetch, você consegue rodar o modelo com impacto modesto na performance — talvez 30-50% mais lento do que se coubesse inteiro na VRAM.
NVMe como memória estendida
A abordagem mais agressiva usa SSDs NVMe como um terceiro nível de memória da GPU. A largura de banda é péssima comparada à VRAM (~7 GB/s vs ~1.000 GB/s), mas a capacidade é essencialmente ilimitada. Um drive NVMe de 4 TB custa US$ 200 e pode armazenar dezenas de modelos grandes ao mesmo tempo.
Projetos como o Greenboost da NVIDIA implementam isso de forma transparente — o sistema de memória da GPU se estende até o NVMe, com prefetch inteligente para minimizar o impacto da limitação de banda. Em cargas de inferência em que a GPU passa um tempo significativo computando (e não só movendo dados), a latência do NVMe pode ser totalmente escondida pela sobreposição com a computação.
A performance depende muito da carga. Operações limitadas por computação (multiplicações de matrizes grandes) escondem bem a latência de transferência. Operações limitadas por memória (mecanismos de atenção com contexto grande) não escondem. Na prática, o offloading em NVMe funciona melhor para inferência em lote de modelos grandes com batch sizes pequenos — exatamente o caso de uso da inferência local de LLMs.
A vantagem da memória unificada da Apple
A arquitetura de memória unificada do Apple Silicon segue um caminho totalmente diferente: elimina a distinção entre VRAM e RAM. A CPU e a GPU compartilham o mesmo pool de memória física. Não há 'offloading' porque não há separação — a GPU acessa a mesma memória que a CPU usa.
Isso não elimina o problema de largura de banda — a banda de memória da linha M (~400 GB/s no M4 Max) é menor que a de uma GPU dedicada —, mas elimina o gargalo do PCIe que deixa o offloading em GPUs discretas lento. Um Mac com 128 GB de memória unificada consegue carregar um modelo de 70B inteiro em memória acessível à GPU, sem nenhum overhead de offloading.
O trade-off: menor throughput de pico em cargas que caberiam inteiras na VRAM de uma GPU dedicada, mas performance dramaticamente melhor em cargas que não cabem. Para inferência de modelos grandes, em que o modelo excede a VRAM da GPU discreta, o Apple Silicon costuma ser mais rápido que uma GPU discreta com offloading, mesmo tendo menos poder de computação bruto.
O que isso significa para desenvolvedores
Se você está construindo aplicações que usam GPU — inferência de ML, gráficos, computação científica —, a limitação de VRAM afeta suas decisões de arquitetura de formas bem concretas.
- Conheça o tamanho do seu working set. Faça profiling do uso de memória da GPU. Não a alocação de pico — o working set em qualquer momento. Se seu pico é de 48 GB, mas nenhuma operação precisa de mais de 8 GB de dados ativos, o offloading vai funcionar bem. Se uma única operação realmente precisa de 48 GB simultaneamente, você precisa de uma GPU maior.
- Escolha sua estratégia de offloading com base no padrão de acesso. Acesso sequencial (inferência camada por camada) funciona muito bem com prefetch. Acesso aleatório (atenção sobre contextos grandes) não funciona. Saiba qual padrão sua carga segue.
- Quantização costuma ser mais barata que offloading. Reduzir seu modelo de FP16 para INT4 corta a memória em 4x com impacto modesto na qualidade. Offloading para a RAM do sistema adiciona latência, sem perda de qualidade, mas com economia de memória limitada. Faça quantização primeiro, offloading depois.
- O batch size é seu botão de ajuste. Lotes maiores precisam de mais memória, mas diluem melhor o overhead. Lotes menores precisam de menos memória, mas processam menos itens por segundo. Quando você está perto do limite de VRAM, reduzir o batch size é a solução mais simples.
- Monitore a fragmentação de memória. A alocação de memória da CUDA pode fragmentar a VRAM com o tempo, especialmente com entradas de tamanho variável. Você pode ter 8 GB livres no total, mas nenhum bloco contíguo de 2 GB. O
torch.cuda.memory_stats()do PyTorch mostra a fragmentação. Otorch.cuda.empty_cache()pode ajudar, mas não é uma solução milagrosa.
A memória da GPU sempre será o gargalo de cargas de computação em larga escala. Os modelos crescem mais rápido que a VRAM. Mas as ferramentas para lidar com esse gargalo — memória unificada, offloading inteligente, cache em múltiplos níveis — estão ficando boas o suficiente para que 'não cabe na VRAM' deixe de ser uma barreira intransponível. É um trade-off de performance e, cada vez mais, um trade-off administrável.


