Isso começou como uma pergunta idiota feita depois de já ter visto o Qwen3 4B rodando a 35 tokens/s via Vulkan na mesma máquina: se isso já funciona, até onde vai o limite real?
A resposta levou duas sessões de testes, cinco tentativas falhas, um erro de protocolo, dois esgotamentos de contexto, um timeout de cliente e, só então, uma resposta completa. No dia seguinte, mais três testes confirmaram exatamente o que tinha dado errado e por quê. Esse é o relato inteiro, sem cortar as partes em que o negócio quebrou.
O hardware: nada disso é novo
Xeon E5-2690 v3, 12 núcleos / 24 threads, lançado em 2014. 31,8GB de RAM DDR4 REG ECC em quad-channel. E uma AMD Radeon RX 580 2048SP de 8GB GDDR5, lançada em 2017 — a placa que todo mundo associa a mineração de criptomoeda, não a inferência de modelos de linguagem.
| Componente | Especificação |
|---|---|
| CPU | Intel Xeon E5-2690 v3 — 12C/24T, 3,05GHz turbo |
| RAM | 31,8GB DDR4 REG ECC quad-channel |
| GPU | AMD RX 580 2048SP — 8GB GDDR5 |
| Storage | NVMe (modelos) + HDD (swap/sistema) |
| Driver AMD | 31.0.21925.1001 |
| Backend | Vulkan — sem CUDA, sem ROCm, sem DirectML |
Stack de software: llama.cpp (build b9049), Vulkan SDK 1.4.350.0, OpenWebUI v0.9.6 e SearXNG via Docker para web search. Nada exótico, tudo open source, tudo rodando local.
O modelo era o Qwen3.5-35B-A3B-Uncensored Q6_K: 34,66 bilhões de parâmetros totais, arquitetura Mixture of Experts com 256 experts, dos quais apenas 8 são ativados por token — 3,1% do modelo "aceso" a cada passo. Esse detalhe é o motivo pelo qual a história inteira é possível. Em um modelo denso de 35B, os 35 bilhões de parâmetros entram em jogo para cada token. No MoE, o roteador escolhe 8 experts relevantes e ignora os outros 248 naquele instante. Isso não reduz o tamanho do arquivo no disco (ainda são 26,55GB), mas reduz brutalmente o que precisa estar disponível com baixa latência ao mesmo tempo. (Logs brutos completos dessa rodada na Seção 33 do laboratório.)
O fitting automático: 1,15 segundo decidindo onde colocar 26GB
Aqui está a parte que, na minha opinião, é mais interessante que o benchmark final. Eu não passei nenhuma flag manual de camadas (-ngl, --override-tensor ou qualquer split rígido). O comando de inicialização foi literalmente isso:
.\llama-server.exe -m "E:\models\Qwen3.5-35B-A3B-...-Q6_K.gguf" --host 0.0.0.0 --port 8081
E o llama.cpp resolveu o problema sozinho. A situação inicial era inviável: o modelo completo precisava de ~32.961 MiB de VRAM e havia 7.366 MiB livres. Um déficit de mais de 26GB. O algoritmo de fitting fez, em sequência, e em pouco mais de um segundo:
- Reduziu o contexto de 262.144 tokens (o máximo de treino do modelo) para 4.096 tokens, liberando ~5.347 MiB.
- Moveu todos os 256 experts MoE para a RAM, mapeados via mmap — 25.613 MiB saindo da equação da GPU.
- Realocou as camadas densas residuais de volta para a GPU, de trás para frente (back-to-front), até ocupar 3.048 MiB.
- Preencheu o resto front-to-back com overflow fracionado no gate layer, terminando com 41 camadas na GPU (36 delas "overflowing"), uso final de 6.255 MiB e apenas 1.111 MiB livres.
O resultado: 41 camadas densas + a output layer ficaram na VRAM da RX 580 (5.154 MiB), e os 256 experts MoE foram para CPU_Mapped, ocupando 26.784 MiB de RAM via mmap. Em cima disso ainda rodava KV cache (80 MiB), buffer recorrente (251 MiB), compute buffer (770 MiB) e mais alguns buffers menores — tudo somando entre 6,2 e 7,2GB de uso efetivo de VRAM, ou seja, 77–90% dos 8GB físicos.
O sistema acabou usando quatro níveis de memória simultaneamente:
- VRAM GDDR5 da RX 580 (~400 GB/s) para as camadas densas.
- RAM DDR4 ECC quad-channel (~51 GB/s) para os experts MoE via mmap.
- SSD NVMe (1,7–3,5 GB/s) como origem do arquivo .gguf.
- HDD via swap do Windows (~120–180 MB/s) quando a RAM passava de 97% de uso.
Esse último nível é o vilão da história, e chega a aparecer de novo mais adiante.
Os números da primeira sessão
Com flash attention habilitado automaticamente, fused gated delta net (autoregressive e chunked) ativos, e 4 slots paralelos configurados, a geração ficou estável em torno de 5,6 tokens/segundo, com prompt eval em ~34–40 tok/s.
| Sessão | Prompt Eval | Geração | Tokens totais | Tempo total |
|---|---|---|---|---|
| 1 | 34,13 tok/s | 5,57 tok/s | 1.377 | ~107s |
| 2 | ~40,00 tok/s | 5,64 tok/s | 2.929 | ~533s |
A temperatura nunca passou de 80°C, com a placa operando entre 44–64°C na maior parte do tempo e só subindo de fato quando o web search estava ativo (70–75°C). O throttling térmico da RX 580 fica em torno de 90°C — sobrou margem o tempo inteiro. Em nenhum momento a placa crashou ou resetou, mesmo sob 12h+ de stress acumulado entre as duas sessões.
A pergunta que quebrou cinco vezes antes de funcionar
O prompt de teste foi sempre o mesmo: peça para explicar atenção em transformers e por que MoE é mais eficiente que modelos densos, em português. Documentei cada tentativa porque cada falha ensinou algo diferente:
Teste 1 — thinking ON + web search ON + geração de imagem ON. Resultado: erro de protocolo. O OpenWebUI injetou um prefill de resposta incompatível com a flag enable_thinking, e o servidor cancelou a chamada depois de já ter gastado um minuto pensando e recuperado 10 fontes do SearXNG.
Teste 2 — thinking ON + web search ON, 30 pesquisas consecutivas. O contexto esgotou: n_tokens = 3285, truncated = 1. As 30 buscas injetaram tanto texto no histórico que sobrou pouquíssimo espaço para o raciocínio interno do modelo, que tentou alocar seu próprio buffer e estourou os 4.096 tokens definidos pelo fitting automático.
Teste 3 — mesma configuração, 25 pesquisas em uma rodada acumulada. Esgotamento de novo, agora batendo exatamente no teto: n_tokens = 4095, truncated = 1. O OpenWebUI reenviou o histórico anterior, e o prompt cresceu até o limite físico.
Teste 4 — thinking ON, web search OFF. A temperatura caiu para 51°C estáveis, mas o raciocínio interno levou tempo demais na CPU Xeon de 2014, e a interface desistiu por timeout antes do servidor terminar de gerar qualquer coisa.
Teste 5 — thinking ON, web search OFF, prompt reduzido a 45 tokens. Sucesso completo. O modelo pensou por 4 minutos sob o Xeon, rascunhou a matemática internamente e entregou uma resposta técnica e estruturada em português sobre atenção scaled dot-product, multi-head e a eficiência do roteamento esparso do MoE.
A conclusão da primeira sessão ficou clara: a GPU e a CPU nunca foram o problema. Em nenhuma das cinco tentativas houve instabilidade física, reset ou crash. Todas as falhas foram de calibração de software — thinking mode e contexto de 4.096 tokens não combinam quando o histórico (especialmente com web search) cresce demais.
Para contexto: outro projeto da comunidade, do Matheus Fertunani, rodou o mesmo Qwen3.5 35B em Q8 usando CPU pura com 192GB de RAM em Linux, atingindo 7–8 tokens/s. Esse setup, com uma GPU de menos de R$400 no mercado de segunda mão somada a 32GB de RAM ECC, chegou a 5,64 tokens/s. A diferença de hardware profissional para hardware reaproveitado é grande, mas o resultado prático fica surpreendentemente próximo.
Capítulo 2: provando a hipótese, no dia seguinte
A hipótese da primeira sessão era simples — "o problema nunca foi o hardware, foi thinking + web search esgotando os 4.096 tokens de contexto." No dia seguinte, três testes foram desenhados especificamente para confirmar isso. (Documentação completa desses três testes na Seção 34.)
O curl resolve o que o navegador não resolvia
Primeiro, era preciso isolar se o timeout do Teste 4 vinha do servidor ou da camada AJAX do cliente OpenWebUI. A resposta veio batendo o endpoint /v1/chat/completions diretamente via curl, com timeout de 600 segundos e sem nenhuma interface no meio:
curl.exe -X POST http://localhost:8081/v1/chat/completions -H "Content-Type: application/json" --max-time 600 -d "@E:\teste.json"
Resultado: truncated = 0. Resposta completa entregue — 1.955 tokens, 266,42 segundos de tempo total, 255,97 segundos de eval a 6,57 tok/s (já usando Q4_K_M, que entrou no lugar do Q6_K para esse segundo bloco de testes). O hardware sempre conseguiu terminar o trabalho; era o navegador que desistia da conexão TCP enquanto o servidor continuava gerando em segundo plano.
Durante esse mesmo teste, o OpenWebUI foi conectado em paralelo disparando a mesma pergunta por outro canal. O agendador do llama.cpp processou as duas tarefas concorrentemente sem travar nada: uma gerando a 5,96 tok/s e outra a 5,08 tok/s, simultaneamente, com a GPU em 63°C e o uso de RAM do sistema em 91% — seguro, sem travar o Windows.
--ctx-size 8192 e os "pensamentos" capturados do modelo
O segundo teste simplesmente subiu o contexto manualmente:
.\llama-server.exe -m "...Q4_K_M.gguf" --host 0.0.0.0 --port 8081 --ctx-size 8192
Com esse buffer dobrado, o modelo recebeu o prompt "explique por que MoE permite rodar modelos grandes em hardware com pouca VRAM" e processou por 9 minutos inteiros de raciocínio em background, sem nenhum corte. A parte curiosa veio da interceptação direta dos blocos <think> gerados nas três rodadas de teste — o que dá uma visão rara de como o modelo "argumenta" internamente antes de responder.
No bloco capturado durante o esgotamento de contexto (Teste com Q4_K_M ainda em 4.096 tokens), o modelo literalmente calculou a memória necessária para 35B de parâmetros em diferentes quantizações — FP32 em 140GB, FP16 em 70GB, INT8 em 35GB, INT4 em 17,5GB — concluiu que nenhuma dessas contas fechava com 8GB de VRAM, e então corrigiu a própria premissa: se o modelo está rodando mesmo assim, só pode ser por offloading agressivo dos experts inativos para a RAM, mantendo na GPU apenas o roteador e os blocos compartilhados. Esse raciocínio descreveu, com bastante precisão, a própria arquitetura de mmap que estava sustentando ele em tempo real — sem ter qualquer acesso aos metadados do sistema de arquivos do laboratório. Esse bloco específico não chegou a ser entregue, porque o contexto de 4.096 tokens estourou antes da resposta final ser formatada.
Já com --ctx-size 8192 ativo, o mesmo tipo de raciocínio — mais focado, menos exploratório, claramente comprimindo o rascunho até caber em "direto e conciso" como pedido — terminou em sucesso absoluto, zero cortes.
| Métrica | Sessão Q6_K | Sessão Q4_K_M (ctx 4096) | Sessão Q4_K_M (ctx 8192) |
|---|---|---|---|
| Duração do raciocínio | ~4 min | ~11 min | ~9 min |
Tokens do bloco <think>
|
~2.000 | ~3.500 | ~3.000 |
| Questionou a premissa do prompt? | Não | Sim, com cálculo de memória | Sim, reajuste técnico |
| Resultado | Sucesso | Estourou contexto | Sucesso absoluto |
Q4_K_M venceu na prática
Trocar Q6_K (28,51GB) por Q4_K_M (21,17GB) liberou cerca de 7GB na carga física de RAM, o que reduziu a dependência do swap em HDD — o ponto mais lento de toda a cadeia de memória. O resultado prático: geração subindo para 6,42–6,65 tok/s, pico de temperatura caindo para 74°C (10°C mais frio que a sessão anterior) e zero atividade de swap visível durante a inferência.
O que sobrou disso tudo
Algumas conclusões que valem para qualquer pessoa tentando algo parecido com hardware velho:
O thermal throttling nunca foi um risco real nesse setup — mesmo sob horas de stress acumulado, a RX 580 ficou sempre 10–16°C abaixo do limite de 90°C. O fitting automático do llama.cpp via cálculo de grafos resolveu a distribuição entre GPU e RAM melhor do que qualquer tentativa manual de split por flags rígidas teria feito. O thinking mode do Qwen3.5 consome sozinho entre 2.000 e 3.500 tokens antes de produzir qualquer resposta visível, então qualquer contexto abaixo de 8.192 tokens vira um gargalo quase garantido se você também ligar web search ou histórico de conversa. E o timeout do cliente (navegador/interface) é, na prática, mais limitante que a capacidade real do hardware — bater direto no endpoint via curl revelou isso de forma inequívoca.
No fim, o veredito é direto: uma GPU de 2017 com 8GB de VRAM e uma CPU de datacenter de 2014 rodam um modelo MoE de 35B de parâmetros de forma estável, sem crash, sem throttling e sem custo adicional além da eletricidade. Não é prático para uso diário — 5,6 a 6,6 tokens/s e um contexto efetivo limitado não competem com qualquer GPU moderna —, mas como prova de conceito sobre até onde sparsity de MoE e fitting automático de memória conseguem levar hardware obsoleto, a resposta é: bem mais longe do que o mercado de hardware sugere.
Hardware: Xeon E5-2690 v3 + RX 580 2048SP 8GB + 32GB DDR4 ECC. Stack: llama.cpp (Vulkan) + OpenWebUI + SearXNG. Todos os números vêm de logs reais de duas sessões de teste, sem nenhuma camada de marketing por cima.



