PTEN

A Única Configuração do llama.cpp que Deixou Minha RTX 3090 10x Mais Rápida (Todo Guia Erra Nisso)

9 de junho de 2026 · também no Medium

Benchmarks reais, o truque do cache q4_0, e como fiz um LLM local escrever código no padrão do meu time, numa única GPU de 24GB.

LLM local numa RTX 3090

Meu LLM local rodava a 100 tokens por segundo. Todo guia que eu lia dizia que minha configuração estava correta. Aí mudei uma única flag, o tipo de detalhe enterrado numa issue do GitHub com quatro votos, e a avaliação de prompt saltou para mais de 1.100 tokens por segundo. Mesma GPU, mesmo modelo, mesmo contexto. Uma diferença de 10x vindo de uma configuração de cache que quase todo tutorial erra.

Sou dev fullstack, e nos últimos meses construí um assistente de código com IA totalmente local numa única RTX 3090: sem chave de API, sem mensalidade, sem mandar o código da empresa pro servidor de terceiros. Ele escreve código no padrão exato do meu time, e é rápido o suficiente pra eu esquecer que está rodando na minha própria mesa.

Aqui vai o relato: os benchmarks reais, os erros que me custaram horas, a configuração que muda tudo em GPUs Ampere, e onde os modelos locais ainda perdem.

TL;DR

  • Numa RTX 3090 com Flash Attention, use --cache-type-k q4_0, e não q8_0. O cache K em q8_0 força um caminho de kernel mais lento e derruba a avaliação de prompt em uns 10x.
  • Um Qwen3.6 27B denso é um daily driver estável. O 35B MoE, mais chamativo, entrava em loop em conversas com várias trocas de mensagem.
  • ~1.150 t/s de avaliação de prompt e ~60 t/s de geração é o suficiente pra substituir um assistente de código na nuvem no trabalho do dia a dia.
  • Velocidade e tamanho de modelo não eram o gargalo real. Modelos locais inventam APIs que não existem com total confiança, e resolvi isso com regras, não com um modelo maior.

Por que rodar local?

Dois motivos. Primeiro, custo: assistentes de código na nuvem são ótimos até você usar bastante o dia inteiro e a conta (ou o limite de créditos) lembrar que são cobrados por uso. Segundo, e mais importante pra mim, trabalho numa base de código de produção de verdade, e não me sinto confortável mandando o código da empresa pra uma API de terceiros a cada autocomplete. Rodar local significa que o código nunca sai da minha máquina.

A pegadinha que todo mundo avisa é qualidade e velocidade. “Você precisa de um H100.” “Modelo local é brinquedo.” Isso está desatualizado. Numa única placa de consumo de 24GB, com as configurações certas, um modelo 27B moderno é genuinamente útil, desde que você evite algumas armadilhas.

A stack

Nada exótico:

  • GPU: RTX 3090, 24GB de VRAM (Ampere)
  • Inferência: servidor llama.cpp
  • Integração com o editor: a extensão Continue no VS Code
  • Modelo: Qwen3.6 27B (denso) em GGUF, Q5_K_M pra qualidade, Q4_K_M quando preciso de contexto máximo

O llama.cpp serve um endpoint compatível com OpenAI, e o Continue aponta pra http://localhost:11435/v1. Essa é toda a arquitetura. A parte interessante é o ajuste fino.

A configuração que todo guia erra: cache KV na Ampere

Quando você roda um modelo no llama.cpp, o cache KV guarda as chaves e valores de atenção de cada token no contexto. Dá pra quantizar esse cache pra economizar VRAM, e o conselho comum é usar q8_0 no cache K porque tem qualidade maior e custa quase nada.

Numa RTX 3090 (Ampere) com Flash Attention ligado, esse conselho é uma armadilha.

# O que todo guia me mandou fazer:
--cache-type-k q8_0 --cache-type-v q8_0   # ~100 t/s de avaliação de prompt 💀
# O que realmente funciona na Ampere:
--cache-type-k q4_0 --cache-type-v q4_0   # ~1.150 t/s de avaliação de prompt 🚀

O Flash Attention tem um caminho de kernel rápido na Ampere, mas ele só entra em ação pra tipos de cache específicos. Um cache K em q8_0 empurra o Flash Attention pra um caminho alternativo, e a avaliação de prompt desaba em uns 10x. Para trabalho de código, a diferença de qualidade entre cache q4_0 e q8_0 é praticamente imperceptível. A diferença de velocidade é toda a experiência: é a diferença entre o editor parecer instantâneo e o editor parecer travado.

Perdi horas com isso. Culpei o tamanho do contexto, o quant do modelo, minha GPU, meus drivers. O conserto foram quatro caracteres: trocar q8_0 por q4_0.

Se você levar uma coisa deste post, leve essa linha.

O modelo que parecia melhor no papel e perdeu na prática

Eu realmente queria que o novo modelo 35B MoE (mixture-of-experts) fosse meu daily driver. Em benchmark ele é mais forte, e modelos MoE são rápidos pro tamanho porque só uma fração dos parâmetros ativa por token.

No uso real com várias trocas de mensagem, ele desandava. Na segunda ou terceira mensagem de uma conversa já começava a entrar em loop, repetindo pedaços da própria resposta anterior, se perdendo, nunca fechando o raciocínio. Tentei desligar o orçamento de raciocínio, mexer na amostragem, nada resolveu. Pra codar interativamente, indo e voltando ajustando, um modelo que desestabiliza depois de duas trocas é inutilizável, não importa quão boa seja a primeira resposta.

O 27B denso, mais sem graça, nunca fez isso. Benchmark mede resposta de tiro único, mas o trabalho do dia a dia é de várias trocas, e estabilidade ganha de capacidade de pico sempre.

Dois modelos, dois trabalhos

24GB é suficiente pra rodar um 27B com folga, mas você troca qualidade por tamanho de contexto, porque o cache KV disputa VRAM com os pesos. Então eu rodo duas configurações e troco no Continue selecionando uma porta diferente:

Modo            Quant     aval. prompt   geração    VRAM       contexto
Qualidade       Q5_K_M    ~1.150 t/s     ~60 t/s    ~21.5 GB   49k
Contexto máx.   Q4_K_M    ~1.150 t/s     ~60 t/s    ~22 GB     131k

O modo Qualidade (Q5) é meu uso diário: código mais afiado, contexto de sobra pro trabalho normal. O modo Contexto máximo (Q4) é pra quando preciso jogar um arquivo grande ou vários arquivos de uma vez, e topo trocar um pouco de precisão pelos 131k de espaço.

Os dois usam os mesmos não negociáveis: cache q4_0, Flash Attention ligado, e decodificação especulativa MTP (--spec-type draft-mtp) pra ganhar velocidade extra de geração.

27B Q5 sob carga numa única RTX 3090

Modo Qualidade sob carga: 27B Q5 numa única RTX 3090, ~21.5 GB usados, GPU fixada em 96%.

Fazendo ele escrever código como meu time

Um modelo rápido que escreve código genérico de tutorial ainda dá trabalho extra, porque você gasta o tempo que economizou reescrevendo pra bater com sua base de código. A maioria dos posts sobre “rodar um LLM local” pula essa parte, e é aí que está a produtividade de verdade.

O Continue carrega regras automaticamente de .continue/rules/*.md. Destilei as convenções do meu time em alguns arquivos de regra com escopo:

  • Comportamento (sempre aplicado): ser direto, responder no meu idioma mas escrever código em inglês, seguir nosso formato de commit, e não inventar APIs que não existem.
  • Frontend (com escopo pra arquivos Vue/SCSS): nossos componentes de Design System, tokens de CSS, nomenclatura de arquivo/diretório, a ordem das seções dentro de um single-file component, nossos padrões de Vuex.
  • Backend (com escopo pra arquivos PHP): nossa arquitetura de bundles, o padrão de service Manager, onde mora a lógica de negócio versus queries, convenções de autorização.

Com isso configurado, o modelo parou de produzir código correto-mas-estranho e passou a produzir código que passa na nossa revisão de primeira. As regras ficam versionadas no repositório, então são compartilhadas e evoluem junto com a base de código.

Onde os modelos locais ainda perdem

Pedi pro modelo adicionar um método numa classe de serviço. Ele produziu código limpo, idiomático, no padrão exato do meu time: checagem de autorização, buscar o entity manager, persistir, dar flush. Parecia perfeito. Tinha um problema: chamava um setter num campo que a entidade não tem. O código quebraria em tempo de execução.

O modelo não mentiu. Ele fez pattern-matching. Viu o pedido, viu as convenções ao redor, e inventou com total confiança uma API que deveria existir pela lógica dos padrões, mas não existe. Assistente de código local, e honestamente os de nuvem também, são fluentes, não fundamentados. Eles modelam a aparência do código, não o que seu código realmente é.

Meu conserto não foi um modelo maior. Foi uma regra de comportamento: antes de chamar um método ou campo, confirmar que ele existe, não inventar getter, setter ou método. Isso não elimina o problema, mas reduz bastante, e me treinou a revisar a saída do jeito que eu revisaria o PR de um colega confiante: confiar na estrutura, verificar os detalhes.

Rode você mesmo

Este é o script inteiro de inicialização do modo qualidade. Ajuste os caminhos e você está rodando em dois minutos:

#!/bin/bash
# Qwen3.6 27B Q5_K_M, daily driver de qualidade. Porta 11435.
# cache q4_0/q4_0 é obrigatório: é o caminho rápido de Flash Attention na Ampere.
cd ~/llama.cpp && ./build/bin/llama-server \
  -m ~/models/Qwen3.6-27B-Q5_K_M.gguf \
  -ngl 99 \
  -c 49152 \
  -fa on \
  --cache-type-k q4_0 \
  --cache-type-v q4_0 \
  --temp 0.6 --top-k 20 --top-p 0.95 \
  --spec-type draft-mtp \
  --n-predict -1 \
  --parallel 1 \
  --host 0.0.0.0 --port 11435

Aponte o Continue pra http://localhost:11435/v1, jogue as convenções do seu time em .continue/rules/, e você tem um assistente de código privado que não custa nada por token e nunca liga pra casa.

O que eu diria pro eu do passado

  • O gargalo nunca foi a GPU. Foi uma flag de cache.
  • Escolha o modelo estável, não o de benchmark mais alto.
  • Gaste sua primeira hora escrevendo regras, não caçando um modelo maior.
  • Já rodo IA local pro trabalho de dev de verdade numa única 3090. Funciona hoje, não algum dia.

Esse é o relato completo. Se te salvou da armadilha do q8_0, cumpriu seu papel.

← Todos os artigos