O laboratório de IA de $900: rodando modelos 70B em hardware consumer
Montei um setup de $900 com RTX 3060 Ti e rodei modelos de 8B a 70B parâmetros. Os dados revelaram um fenômeno que ninguém documentou: o GPU Offloading Cliff.
Indice
Eu queria rodar um modelo de 70 bilhões de parâmetros na minha própria máquina. Sem cloud, sem API, sem mandar meus dados pra ninguém. O problema? Todo benchmark que eu encontrava usava hardware de $10.000 ou mais. A100, H100, placas que custam mais do que eu ganho em meses.
Então fiz o que qualquer pessoa teimosa faria: montei um setup de $900 e testei eu mesmo. Uma RTX 3060 Ti de $300, um i7 usado, 32GB de RAM e um SSD. Setup de estudante. Setup de quem mora em país onde A100 não é opção.
E funciona? Funciona. Mas não do jeito que eu esperava. Achei que bastava jogar mais camadas na GPU e a velocidade ia subindo proporcionalmente. Errado. Os benchmarks revelaram um fenômeno que a literatura acadêmica não documentou, otimizações que não fazem diferença nenhuma quando você acha que deveriam fazer, e uma única técnica que realmente muda o jogo.
Esse post é o registro completo dos experimentos: dados reais, tabelas, configs de terminal, e zero hype. Se você já pensou em rodar LLMs localmente e não sabe se vale a pena, esses números vão te dar a resposta.
O setup: o que $900 compra em 2026
Antes de mostrar os números, preciso ser transparente sobre o hardware. Não é um PC gamer de última geração. É o tipo de máquina que um desenvolvedor ou estudante monta com orçamento apertado.
| Componente | Especificação | Custo (USD) |
|---|---|---|
| GPU | NVIDIA GeForce RTX 3060 Ti | ~$300 |
| VRAM | 8 GB GDDR6X | (inclusa) |
| CPU | Intel Core i7-9700F @ 3.00GHz | ~$200 |
| RAM | 32 GB DDR4 2666 MHz (2x16GB) | ~$70 |
| Storage | NVMe SSD 512GB | ~$50 |
| Placa-mãe + Fonte + Gabinete | ATX padrão | ~$280 |
| Total | ~$900 |
Software: Ubuntu 24.04, NVIDIA Driver 580, CUDA 13.0, llama.cpp v8763. Tudo open-source, tudo gratuito.
Os modelos testados
Testei quatro modelos cobrindo de 8B a 70B parâmetros, todos quantizados para caber na memória disponível:
| Modelo | Parâmetros | Quantização | Tamanho (GiB) | Camadas | Cabe na VRAM? |
|---|---|---|---|---|---|
| Llama 3.1 8B Instruct | 8.03B | Q4_K_M | 4.58 | 33 | 100% |
| Mistral Nemo 12B Instruct | 12.25B | Q4_K_M | 6.96 | 41 | 100% |
| Qwen 2.5 14B Instruct | 14.77B | Q4_K_M | 8.37 | 40 | ~100%* |
| Llama 3.1 70B Instruct | 70.55B | Q2_K | 24.56 | 80 | 25% |
O 70B é o caso mais interessante. Com quantização Q2_K (2 bits), ele comprime de ~140GB (FP16) para ~25GB. Não cabe inteiro na GPU (8GB), mas cabe na RAM do sistema (32GB).
Se você não é da área: quantização é como comprimir uma foto. Você reduz o tamanho do arquivo, mas perde um pouco de detalhe. Q2_K significa que cada peso do modelo usa apenas 2 bits em vez de 16. O modelo fica 7x menor, com alguma perda de qualidade nas respostas.
O llama.cpp permite distribuir as camadas do modelo entre GPU e CPU usando o parâmetro ngl (number of GPU layers). Pense assim: o modelo tem 80 camadas, e você escolhe quantas delas rodam na GPU (rápida) vs na CPU (lenta). E é exatamente aqui que a história fica interessante.
O GPU Offloading Cliff: o fenômeno que ninguém documentou
A ideia por trás do GPU offloading parece simples: quanto mais camadas do modelo você coloca na GPU, mais rápido ele roda. Faz sentido intuitivamente, certo? A literatura acadêmica também trata assim: uma relação gradual, mais ou menos proporcional.
Eu esperava ver exatamente isso nos meus benchmarks. Não foi o que aconteceu.
Meus dados mostram que essa suposição está errada.
Rodei benchmarks sintéticos com o llama-bench (ferramenta nativa do llama.cpp), variando o ngl de 0 (tudo na CPU) até o máximo que a VRAM permite. Os resultados por modelo:
Llama 3.1 8B Q4_K_M (4.58 GiB, 33 camadas)
| ngl | GPU % | pp512 (tok/s) | tg128 (tok/s) | Speedup |
|---|---|---|---|---|
| 0 | 0% | 744.99 ± 11.16 | 7.00 ± 0.02 | 1.0x |
| 16 | 48% | 1127.81 ± 12.03 | 12.77 ± 0.01 | 1.8x |
| 33 | 100% | 2525.68 ± 24.73 | 76.25 ± 0.02 | 10.9x |
Mistral Nemo 12B Q4_K_M (6.96 GiB, 41 camadas)
| ngl | GPU % | pp512 (tok/s) | tg128 (tok/s) | Speedup |
|---|---|---|---|---|
| 0 | 0% | 508.86 ± 6.56 | 4.59 ± 0.01 | 1.0x |
| 20 | 49% | 769.65 ± 7.90 | 8.66 ± 0.01 | 1.9x |
| 41 | 100% | 1690.74 ± 10.34 | 50.85 ± 0.01 | 11.1x |
Qwen 2.5 14B Q4_K_M (8.37 GiB, 40 camadas)
| ngl | GPU % | pp256 (tok/s) | tg64 (tok/s) | Speedup |
|---|---|---|---|---|
| 0 | 0% | 258.66 ± 4.32 | 3.73 ± 0.02 | 1.0x |
| 20 | 50% | 386.39 ± 5.07 | 6.21 ± 0.03 | 1.7x |
| 40 | 100% | 759.44 ± 9.41 | 14.84 ± 0.06 | 4.0x |
Llama 3.1 70B Q2_K (24.56 GiB, 80 camadas)
| ngl | GPU % | pp128 (tok/s) | tg32 (tok/s) | Speedup |
|---|---|---|---|---|
| 0 | 0% | 42.99 | 1.20 | 1.0x |
| 10 | 12.5% | 49.85 | 1.37 | 1.14x |
| 15 | 18.7% | 52.86 | 1.47 | 1.22x |
| 20 | 25% | 56.15 | 1.55 | 1.29x |
Agora olhe para esses números com atenção. O padrão é claro:
Speedup (geração)
12x | * 8B (10.9x)
| * 12B (11.1x)
10x |
|
8x |
|
6x |
| * 14B (4.0x)
4x |
|
2x | *** (todos os modelos ~1.3-1.9x)
| ****
1x |***
+--+------+------+------+------+------+------+------+--
0% 12% 25% 37% 50% 62% 75% 100%
% de camadas na GPU
Veja o que acontece: existe uma transição de fase por volta de 50% de camadas na GPU. Abaixo desse ponto, o overhead de transferência de dados entre CPU e GPU a cada fronteira de camada domina o tempo de execução. O speedup é marginal: 1.0x a 1.9x, independente do modelo. Acima de 50%, a computação se torna o bottleneck (e a GPU é excelente nisso), e o ganho explode para 4.0x a 11.1x.
Analogia: imagine uma esteira de fábrica onde cada peça precisa ser transportada entre dois prédios. Se mais da metade das máquinas está no prédio A, o tempo de transporte domina e não importa quão rápidas as máquinas são. Mas quando você concentra a maioria no mesmo prédio, a velocidade das máquinas finalmente importa.
Eu chamo isso de GPU Offloading Cliff porque é exatamente o que os dados mostram: não é uma rampa, é um penhasco. E a implicação prática é direta. Se você não consegue colocar pelo menos metade das camadas do modelo na GPU, o ganho de ter uma GPU é quase irrelevante. Para o 70B no meu setup, o máximo que consigo é 25% das camadas (ngl=20). Estou no lado errado do penhasco. E confesso que demorei pra aceitar isso.
Por que isso importa
A literatura acadêmica (FlexGen, PowerInfer) trata a relação entre GPU layers e performance como um tradeoff gradual. Os schedulers de offloading assumem que cada layer adicional na GPU dá um ganho proporcional. Meus dados mostram que essa suposição está errada. O ganho é não-linear com transição de fase. E isso tem implicações concretas para quem decide quanto investir em VRAM: se sua GPU não tem VRAM suficiente para passar do threshold de 50%, o investimento tem retorno marginal.
| Modelo | Abaixo de 50% GPU | Acima de 50% GPU | Cliff Ratio |
|---|---|---|---|
| Llama 8B | 1.8x (48%) | 10.9x (100%) | 6.0x |
| Mistral 12B | 1.9x (49%) | 11.1x (100%) | 5.8x |
| Qwen 14B | 1.7x (50%) | 4.0x (100%) | 2.4x |
| Llama 70B | 1.3x (25%) | N/A (não cabe) | N/A |
Zonas de viabilidade: quando rodar local faz sentido?
Com os benchmarks em mãos, criei um framework prático para decidir quando inferência local é viável. Três zonas, definidas pela velocidade de geração:
| Zona | Velocidade | % GPU Layers | Cenário | Exemplos |
|---|---|---|---|---|
| A: Interativo | >10 tok/s | >80% | Chat, IDE copilot, APIs real-time | 8B Q4 ngl=33, 12B Q4 ngl=41 |
| B: Batch | 1-10 tok/s | 25-80% | Sumarização, pipelines offline, análise | 14B Q4 ngl=40, 70B Q2 + speculative |
| C: Impraticável | Abaixo de 1 tok/s | Abaixo de 15% | Custo de espera excede custo de API | 70B Q2 ngl=0 |
Zona A é onde o usuário percebe streaming quase instantâneo. O 8B a 76 tok/s e o 12B a 51 tok/s vivem aqui confortavelmente. Dá pra usar como copilot de código, chatbot, API de produção.
Zona B é viável para processamento automatizado. Uma resposta de 200 tokens leva de 30 a 130 segundos. Não serve pra chat interativo, mas funciona bem pra pipelines de sumarização, extração de entidades, tradução em lote.
Zona C é economicamente irracional. O tempo que o desenvolvedor gasta esperando custa mais do que pagar uma API cloud.
Cenários reais com o 70B (ngl=20, Zona B)
Para validar as zonas, rodei sete tarefas de NLP do mundo real usando a API HTTP do llama-server (compatível com OpenAI):
| Cenário | tok/s | TTFT (s) | Latência (s) | Tokens (in/out) | Zona |
|---|---|---|---|---|---|
| Sumarização de texto | 1.54 | 1.67 | 131.5 | 484 / 200 | B |
| Geração de código | 1.61 | 1.17 | 24.1 | 158 / 37 | B |
| Tradução (EN→PT) | 1.56 | 1.34 | 85.2 | 223 / 131 | B |
| Extração de entidades (JSON) | 1.57 | 1.31 | 79.7 | 218 / 123 | B |
| Raciocínio lógico | 1.58 | 1.23 | 56.5 | 184 / 88 | B |
| Classificação de sentimento | 1.55 | 1.26 | 33.7 | 206 / 51 | B |
| QA com contexto longo | 1.60 | 1.50 | 23.5 | 387 / 35 | B |
Dois insights saltam dos dados:
-
A velocidade de geração é notavelmente consistente entre tarefas (1.54 a 1.62 tok/s). O bottleneck é puramente computacional, não depende do tipo de tarefa.
-
O TTFT (time-to-first-token) é desacoplado da geração. O primeiro token chega em 1.17 a 1.67 segundos, muito rápido em relação à velocidade de geração. Para tarefas com output curto (classificação: 51 tokens, QA: 35 tokens), o TTFT domina a latência total. Isso torna essas tarefas mais viáveis do que o tok/s sozinho sugeriria.
A caçada por performance: o que funciona e o que não funciona
Com o 70B preso na Zona B a 1.57 tok/s, eu não queria aceitar. Tinha que ter um jeito de melhorar isso com software. Parti pra testar toda otimização disponível, uma por uma, com a esperança de encontrar algum ganho significativo. Spoiler: a maioria decepcionou.
Flash Attention
Teoria: computação de atenção IO-aware que reduz o bottleneck de bandwidth de memória. Performance grátis, sem impacto na qualidade.
Resultado: +0.6% (1.57 → 1.58 tok/s). Irrelevante.
KV Cache Quantization
Teoria: comprimir o cache de key-value de FP16 para Q4_0 ou Q8_0, liberando VRAM para mais camadas na GPU.
Resultado com Q8_0: 0% de ganho. Com Q4_0 tentando subir para ngl=25 ou ngl=30: OOM (out of memory). A VRAM de 8GB simplesmente não comporta.
Mais GPU layers (ngl > 20)
Resultado: impossível. ngl=20 é o máximo absoluto para o 70B Q2_K em 8GB de VRAM, mesmo com KV cache quantizado.
O stack completo de otimizações
| Otimização | ngl | tg (tok/s) | vs Baseline | Nota |
|---|---|---|---|---|
| Baseline | 20 | 1.57 | 1.0x | referência |
| + Flash Attention | 20 | 1.58 | 1.01x | negligível |
| + FA + KV Cache Q8_0 | 20 | 1.57 | 1.00x | zero ganho |
| + FA + KV Q4_0 + ngl=25 | 25 | OOM | N/A | não cabe |
| + FA + KV Q4_0 + ngl=30 | 30 | OOM | N/A | não cabe |
| Speculative Decoding | 15+draft | ~3.45 | 2.2x | único ganho real |
Achado crítico: em hardware com VRAM severamente limitada (menos de 25% das camadas na GPU), otimizações de software que atacam eficiência de compute (flash attention) ou compressão de cache (KV quantization) não produzem melhoria mensurável. O bottleneck é a bandwidth de memória entre CPU e GPU nas fronteiras de camada, não o compute da GPU nem o tamanho do KV cache.
Speculative decoding: a única saída
Depois de tanta frustração com otimizações que não deram resultado, essa foi a técnica que mudou tudo.
Speculative decoding funciona com uma ideia elegante: um modelo pequeno e rápido (o “draft”) gera vários tokens candidatos, e o modelo grande (o “target”) verifica todos de uma vez num único forward pass em batch. É como ter um estagiário escrevendo rascunhos rápidos e o sênior apenas revisando, em vez do sênior escrever cada palavra do zero. Tokens aceitos pelo sênior são produzidos a custo marginal quase zero.
No meu setup, usei o Llama 3.2 1B Q4_K_M (771 MB) como draft model. Ele cabe inteiro na GPU e roda a 317 tok/s. O target (70B) verifica os candidatos em batch, e o resultado efetivo é 3.45 tok/s. Um ganho de 2.2x sobre o baseline.
Mas aqui vem o plot twist: para caber o draft model na GPU, preciso sacrificar 5 camadas do target (de ngl=20 para ngl=15). Essa perda isolada custa ~6% de performance (1.57 → 1.47 tok/s). Mas o ganho do speculative compensa com folga.
# Config otimizada com speculative decoding
llama-cli -m llama-70b-q2_k.gguf \
-md llama-3.2-1b-q4_0.gguf \
-ngl 15 -ngld 99 -fa -ctk q4_0 -ctv q4_0 \
-c 2048 --mlock --draft-max 8
O trade-off de alocação de VRAM
Esse é o achado que mais me surpreendeu. Em hardware com VRAM limitada, speculative decoding cria um dilema de alocação: dedicar VRAM ao modelo principal (mais GPU layers) ou ao draft model (ganhos do speculative)?
| Configuração | Target ngl | Draft | tok/s | Efeito |
|---|---|---|---|---|
| Sem speculative | 20 | nenhum | 1.57 | baseline |
| Speculative (ngl=20) | 20 | 1B (GPU) | OOM | falha |
| Speculative (ngl=15) | 15 | 1B (GPU) | 3.45 | +2.2x |
A resposta é contraintuitiva: a alocação ótima de VRAM não é “maximizar layers do modelo principal”. É reservar VRAM para um draft model rápido, aceitando menos layers do target. Sacrificar 5 layers do 70B (perda de ~6%) para alocar o draft model inteiro dá ganho líquido de 2.2x.
Esse trade-off tem implicações diretas para schedulers de offloading automático em frameworks de inferência. Nenhum paper publicado que eu conheça analisou essa decisão de alocação entre target e draft model em hardware consumer.
Quanto custa de verdade: local vs cloud
TCO do hardware
| Componente | Custo (USD) | Amortização (3 anos) | Mensal |
|---|---|---|---|
| Sistema completo | $900 | 36 meses | $25/mês |
| Eletricidade (~200W) | $0.12/kWh | contínuo | ~$17/mês |
| Total mensal | $42/mês |
Custo por token: local vs cloud
| Provedor | Modelo | Custo por 1M tokens de output |
|---|---|---|
| OpenAI GPT-4o | 200B+ (est.) | $15.00 |
| Anthropic Claude Sonnet | N/A | $15.00 |
| Anthropic Claude Opus | N/A | $75.00 |
| Local RTX 3060 Ti | Llama 70B Q2 | ~$0.02 |
| Local RTX 3060 Ti | Llama 8B Q4 | ~$0.001 |
A diferença é brutal: local custa 750x menos que GPT-4o por token de output. Mas velocidade importa. A 1.57 tok/s (70B), o sistema gera ~4.9M tokens por mês rodando 24/7.
Break-even: quando local compensa?
| Padrão de uso | Tokens/mês | Custo cloud (GPT-4o) | Custo local | Compensa? |
|---|---|---|---|---|
| Leve (50 req/dia, 200 tok) | 300K | $4.50 | $42 | Não |
| Médio (500 req/dia) | 3M | $45.00 | $42 | Sim, mês 1 |
| Pesado (2000 req/dia) | 12M | $180.00 | $42 | Sim, mês 1 |
| Pipeline batch (24/7) | 4.9M | $73.50 | $42 | Sim, mês 1 |
Para uso leve, cloud é mais econômico. A partir de ~500 requisições por dia, local já compensa no primeiro mês. E tem fatores que não aparecem na planilha:
| Fator | Local | Cloud API |
|---|---|---|
| Privacidade de dados | Total: nada sai do dispositivo | Dados enviados a terceiros |
| Disponibilidade | 24/7, sem rate limits | Rate limits, outages |
| Consistência de latência | Determinística | Variável (rede, fila) |
| Customização | Controle total (fine-tuning, system prompts) | Limitado |
| Dependência de internet | Nenhuma | Obrigatória |
Quando o 70B compensa sobre o 8B?
Uma pergunta natural: se o 8B roda a 76 tok/s (Zona A) e o 70B a 1.57 tok/s (Zona B), por que usar o 70B?
A resposta está na qualidade das respostas, não na velocidade:
Raciocínio complexo: modelos 70B superam os 8B substancialmente em raciocínio multi-step, resolução de problemas matemáticos e instruções complexas.
Qualidade multilíngue: traduções e geração em múltiplos idiomas são significativamente melhores no 70B.
Output estruturado: extração de entidades e geração de JSON são mais confiáveis com modelos maiores.
O framework de decisão é direto: use 8B para tarefas interativas sensíveis a velocidade, 70B para tarefas batch sensíveis a qualidade.
O que aprendi: lições para quem quer montar o próprio lab
Depois de dezenas de benchmarks, configs testadas e OOMs enfrentados, estas são as lições que eu gostaria de ter recebido antes de começar:
1. VRAM é o recurso mais precioso. Não é clock speed, não é CUDA cores, não é bandwidth de memória. É quanto VRAM sua GPU tem. Ela determina quantas camadas do modelo cabem na GPU, e isso determina de qual lado do cliff você está.
2. Se menos de 50% das camadas cabem na GPU, o ganho é marginal. Não compre uma GPU de 6GB achando que vai ter “algum ganho”. O cliff effect garante que abaixo de 50%, o retorno é mínimo (1.0x a 1.9x). Se puder, invista nos 8GB extras que te colocam do outro lado do penhasco.
3. Otimizações de software não resolvem limitação de hardware. Flash attention, KV cache quantization: são técnicas válidas, mas inúteis quando o bottleneck é bandwidth de memória entre CPU e GPU. Não perca tempo otimizando compute quando o problema é transferência de dados.
4. Speculative decoding é a exceção. É a única técnica que muda o paradigma de inferência (de geração sequencial para verificação em batch) e, por isso, é a única que dá ganho real em hardware limitado.
5. 32GB de RAM é o mínimo para modelos 70B. O modelo Q2_K ocupa ~25GB. Com overhead do sistema operacional e do KV cache, 32GB fica apertado. 64GB daria mais margem.
6. Quantização Q2_K funciona, mas tem custo de qualidade. Comprimir de 16 bits para 2 bits inevitavelmente perde informação. Para tarefas onde precisão máxima importa (matemática, código complexo), considere modelos menores com quantização mais alta (14B Q4) em vez de modelos maiores com quantização agressiva (70B Q2).
Conclusão: o lab de $900 é real
Rodei um modelo de 70 bilhões de parâmetros numa máquina de $900. Não é rápido o suficiente pra chat interativo (1.57 tok/s baseline, 3.45 tok/s com speculative decoding), mas é perfeitamente viável para processamento batch, pipelines de NLP, e qualquer cenário onde qualidade importa mais que latência.
O achado mais importante não foi a velocidade em si. Foi o GPU Offloading Cliff: a descoberta de que a relação entre camadas na GPU e performance não é gradual, e sim uma transição de fase. Abaixo de 50%, ganho marginal. Acima de 50%, o speedup explode. Esse fenômeno tem implicações diretas para decisões de compra de hardware e para o design de schedulers de offloading automático.
Para um estudante no Brasil, na Índia ou na Nigéria, onde uma A100 custa mais do que muita gente ganha em um ano, $900 é a diferença entre ter acesso a modelos frontier ou não. E o que os dados mostram é que, com as técnicas certas (speculative decoding, quantização agressiva, zonas de viabilidade bem definidas), esse acesso é real. Limitado, mas real.
O hardware consumer não substitui o datacenter. Mas democratiza o acesso ao que antes era exclusivo de quem tinha $10.000+ pra gastar em GPU. E num mundo onde IA está se tornando infraestrutura básica, garantir que qualquer pessoa possa experimentar, aprender e construir com modelos frontier, mesmo que devagar, é mais do que uma questão técnica. É uma questão de acesso.
E pra mim, isso vale cada tok/s.
Referências
- Frantar, E., et al. “GPTQ: Accurate Post-Training Quantization for Generative Pre-Trained Transformers.” ICLR 2023.
- Lin, J., et al. “AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration.” MLSys 2024.
- Sheng, Y., et al. “FlexGen: High-Throughput Generative Inference of Large Language Models with a Single GPU.” ICML 2023.
- Song, Y., et al. “PowerInfer: Fast Large Language Model Serving with a Consumer-grade GPU.” SOSP 2024.
- Leviathan, Y., et al. “Fast Inference from Transformers via Speculative Decoding.” ICML 2023.
- Chen, C., et al. “Accelerating Large Language Model Decoding with Speculative Sampling.” 2023.
- Dao, T. “FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning.” 2023.
- Dettmers, T., et al. “QLoRA: Efficient Finetuning of Quantized LLMs.” NeurIPS 2023.
- Kwon, W., et al. “Efficient Memory Management for Large Language Model Serving with PagedAttention.” SOSP 2023.
- Alizadeh, K., et al. “LLM in a Flash: Efficient Large Language Model Inference with Limited Memory.” Apple ML Research, 2023.