Versão original em inglês: Running a coding model locally with Ollama.

Rodar um modelo de programação capaz na sua própria GPU exige uma instalação e um punhado de comandos. A parte melhor é o que vem depois: o modelo não é uma caixa-preta. Você consegue ler quanta memória ele usa, quão rápido ele escreve e quanto texto ele consegue segurar de uma vez, e nenhum dos seus prompts jamais sai da máquina.

Inferência local: o modelo se muda para o seu laptop

Inferência local significa rodar o modelo na sua própria máquina em vez de chamar uma API hospedada. O Ollama é o caminho mais rápido até lá. Ele baixa modelos de pesos abertos (open-weight), serve-os por trás de uma API HTTP local em http://localhost:11434 e os roda na sua GPU. Pesos abertos significa que os pesos treinados, os números que o modelo aprendeu durante o treinamento, são publicados para você baixar e rodar, ao contrário de modelos apenas via API, como Claude ou GPT, cujos pesos nunca saem do fornecedor.

Uma família de modelos é uma linhagem: quem treinou os pesos. Quatro valem a pena conhecer pelo nome:

  • Llama (Meta)
  • Qwen (Alibaba)
  • Gemma (Google)
  • DeepSeek (DeepSeek)

Você escolhe uma família e um tamanho, e o Ollama baixa uma build comprimida para caber na memória da sua máquina.

Instale o Ollama e o modelo

Cinco comandos levam você do zero a um modelo respondendo na sua máquina:

  • Instale o Ollama com o Homebrew (no Linux ou Windows, use o instalador em ollama.com/download).
  • Inicie o servidor; ele escuta em http://localhost:11434.
  • Baixe (pull) um modelo de programação.
  • Liste o que você baixou, com o tamanho em disco.
  • Rode uma vez com um prompt para confirmar que ele responde localmente.
brew install ollama
brew services start ollama      # serve em http://localhost:11434
# nomes de modelo seguem família:tamanho: deepseek-coder-v2:16b é o modelo de programação da DeepSeek
# na sua build de 16 bilhões de parâmetros (o que é um parâmetro vem com a ficha técnica)
ollama pull deepseek-coder-v2:16b
# ollama list mostra o que você baixou, e o tamanho em disco
ollama list

O ollama list reporta ele em disco:

NAME                     ID              SIZE      MODIFIED
deepseek-coder-v2:16b    63fb193b3a9b    8.9 GB    2 minutes ago

O ollama run escreve o código e imprime um rodapé --verbose:

# --verbose acrescenta estatísticas de tempo depois da resposta
ollama run deepseek-coder-v2:16b --verbose "Write a one-line hello world in Python."
 
```python
print("Hello, World!")
```
 
total duration:       5.994714959s
load duration:        5.659750709s
prompt eval count:    18 token(s)
prompt eval duration: 70.487ms
prompt eval rate:     255.37 tokens/s
eval count:           28 token(s)
eval duration:        262.95ms
eval rate:            106.48 tokens/s

O rodapé são duas fases, leitura e depois escrita:

  • O modelo não vê letras. O texto é dividido em tokens, pequenos pedaços, mais ou menos partes de palavras. O prompt acima, mais o texto de moldura que o Ollama adiciona ao redor dele, se divide em 18 deles: o prompt eval count.
  • Antes de escrever qualquer coisa, o modelo lê todos os 18 tokens para montar o que você pediu. Essa fase levou 70.487ms, então a prompt eval rate, 18 tokens em 70.487 ms, é 255.37 tokens/s: a velocidade de leitura dele.
  • Só então a geração começa: a resposta de 28 tokens, escrita à eval rate de 106.48 tokens/s. Esse número é a throughput (vazão) do modelo.

Lendo a ficha técnica

O ollama show imprime a ficha técnica do modelo:

ollama show deepseek-coder-v2:16b
  Model
    architecture        deepseek2
    parameters          15.7B
    context length      163840
    embedding length    2048
    quantization        Q4_0
 
  Capabilities
    completion
    insert
 
  Parameters
    stop    "User:"
    stop    "Assistant:"
 
  License
    DEEPSEEK LICENSE AGREEMENT
    Version 1.0, 23 October 2023
  • parameters (15.7B): quantos pesos o modelo tem.
    • Escrever um token significa passar pesos por contas matemáticas: quanto mais pesos participam, mais devagar cada token sai.
    • Em um modelo denso (dense), todo peso participa de todo token.
    • O DeepSeek-Coder-V2 é, em vez disso, um modelo mixture-of-experts (mistura de especialistas): os pesos são agrupados em pequenas sub-redes especialistas, e um roteador escolhe os poucos especialistas relevantes para cada token, do jeito que uma clínica mantém muitos especialistas no corpo clínico mas te encaminha só para os dois que o seu caso precisa.
    • Apenas cerca de 2.4B dos seus 15.7B pesos trabalham em cada token, então ele escreve como um modelo pequeno (os 106.48 tokens/s acima) enquanto carrega o conhecimento de um modelo grande.
    • Todos os 15.7B ainda precisam ficar na memória.
  • context length (163840): a janela, o máximo de tokens que o modelo consegue segurar de uma vez.
    • Prompt e saída compartilham ela: tudo o que o modelo lê e tudo o que ele escreve precisa caber dentro.
    • Em ~160K, esta aqui foi feita para contexto longo.
    • Essa janela é a maior alavanca sobre a memória.
  • embedding length (2048): os 2048 números que descrevem cada token.
    • Eles vêm de 16 attention heads (cabeças de atenção), leitoras paralelas do código.
    • Cada cabeça contribui com 128 números: 16 × 128 = 2048.
  • quantization (Q4_0): a precisão com que os pesos são armazenados, o botão de tamanho-versus-qualidade.
    • Cada peso normalmente é um número de 16 bits, a precisão em que o modelo foi treinado e distribuído.
    • Q4 mantém apenas 4 bits dele. Quatro bits só conseguem cair em 16 valores possíveis, então cada peso é arredondado para o mais próximo deles.
    • Menos bits por peso deixa o modelo cerca de 4× menor, por um pouco de precisão perdida.

O bloco Capabilities é o que o modelo sabe fazer:

  • completion: gerar texto.
  • insert: fill-in-the-middle, o truque de preencher o meio do código que caracteriza um modelo de programação.
  • Sem tools: function calling (chamada de função), em que o modelo responde com uma chamada estruturada para o seu código executar em vez de prosa, não está no repertório deste modelo, então a API de tools do Ollama não vai conseguir dirigi-lo.
  • Para ganhar uma capacidade, escolha um modelo que já a liste (qwen2.5-coder, por exemplo, adiciona tools). As capacidades são definidas pelo modelo e pelo seu template, o texto de moldura que o Ollama coloca ao redor do seu prompt antes de o modelo vê-lo. Listar uma capacidade e entregá-la são coisas diferentes; só uma sonda decide qual dos dois um flag representa.

Entendendo o embedding length

Cada uma das 16 cabeças lê um token em relação ao resto do código buscando uma relação diferente. Pegue o total na última linha aqui:

def checkout(cart, user):
    total = 0
    for item in cart:
        total += item.price
    if user.is_member:
        total *= 0.9
    return total

Cabeças diferentes ligam esse total a tokens anteriores diferentes ao mesmo tempo:

  • uma cabeça → total = 0, onde ele foi declarado
  • uma cabeça → total += item.price, onde o preço de cada item é somado
  • uma cabeça → total *= 0.9, seu valor mais recente, o desconto de membro
  • uma cabeça → def checkout, marcando-o como o valor de retorno da função

Cada cabeça registra sua leitura como 128 números, um ponto em 128 dimensões, do mesmo jeito que (x, y) é um ponto em 2 dimensões e (r, g, b) uma cor em 3. Empilhe todas as 16 cabeças e você tem a descrição completa do token, de 2048 números:

token "total"           128 números por cabeça
  head  1   [ 0.12  -0.03   0.88   …   0.05 ]
  head  2   [-0.44   0.21   0.01   …  -0.10 ]
  head  3   [ 0.07   0.55  -0.32   …   0.19 ]

  head 16   [ 0.31  -0.09   0.42   …  -0.02 ]
  ──────────────────────────────────────────────
  16 cabeças × 128 = 2048 números

Os valores acima são ilustrativos. O Ollama reporta memória e throughput, não as ativações internas do modelo, então um dump real por cabeça precisa de um framework como o PyTorch.

Observabilidade do modelo

O ollama ps e o endpoint /api/ps te dizem como o modelo está rodando agora. O ollama run da instalação o carregou na janela padrão, a janela que o Ollama usa quando você não define nada, então é esse o estado em exibição:

ollama ps
NAME                     ID              SIZE     PROCESSOR    CONTEXT    UNTIL              
deepseek-coder-v2:16b    63fb193b3a9b    13 GB    100% GPU     32768      4 minutes from now

PROCESSOR é a coluna que importa:

  • Um modelo roda mantendo seus pesos na memória, e há dois reservatórios para isso:
    • a VRAM da própria GPU, rápida para esse tipo de conta
    • a RAM comum do sistema, que a CPU alcança e é bem mais lenta
  • 100% GPU significa que toda camada cabe na VRAM, então a GPU faz todo o trabalho, aos 106.48 tokens/s que a rodada de instalação mediu.
  • Quando um modelo é maior que a VRAM livre, o Ollama carrega o que cabe na GPU e joga o resto para a RAM do sistema; o ollama ps então mostra uma divisão como 60% GPU / 40% CPU.
  • Essa fatia de CPU é a parte do modelo rodando no caminho lento, e a throughput despenca, de dezenas de tokens por segundo para um só dígito.

/api/ps é o mesmo relatório por HTTP, com contagens exatas de bytes:

curl -s localhost:11434/api/ps | jq -r '
  .models[] |
  "\(.name)\n  VRAM:    \(.size_vram/1e9*10|round/10) GB of \(.size/1e9*10|round/10) GB (\(.size_vram/.size*100|round)% on GPU)\n  Context: \(.context_length) tokens\n  Quant:   \(.details.quantization_level), \(.details.parameter_size) params"
'
deepseek-coder-v2:16b
  VRAM:    13.8 GB of 13.8 GB (100% on GPU)
  Context: 32768 tokens
  Quant:   Q4_0, 15.7B params

Mesmo na janela padrão, o valor de VRAM supera os 8.9 GB que o ollama list reportou em disco lá na instalação:

  • O disco guarda apenas os pesos quantizados.
  • A VRAM guarda os pesos mais um cache por token que cresce com a janela carregada.

A janela de contexto é a maior alavanca sobre a memória

Uma janela de contexto maior não é de graça:

  • Para evitar reprocessar todo o contexto a cada novo token, o modelo faz cache de um pequeno pedaço de dado para cada token que já viu: o KV cache.
  • Esse cache cresce com a janela, e você consegue ver ele se mexer.

O 163840 do ollama show é um teto, não um tamanho fixo:

  • Você escolhe a janela real por rodada com a opção num_ctx, até esse teto, e o modelo carrega com o valor que você escolher.
  • É o mesmo deepseek-coder-v2:16b o tempo todo, só a janela muda.

O ollama run não tem uma flag de linha de comando para a janela, então o num_ctx é definido de outro jeito:

  • sessão interativa: rode o modelo, depois /set parameter num_ctx 32768
  • env do servidor: OLLAMA_CONTEXT_LENGTH=32768 ollama serve
  • API: options.num_ctx na requisição

Carregue com 32K (K = 1024 tokens), ou 32 × 1024 = 32768 tokens. Leia a pegada de memória logo em seguida:

# num_ctx=32768 carrega com uma janela de 32K; resposta descartada, só queremos o load
curl -s localhost:11434/api/generate \
  -d '{"model":"deepseek-coder-v2:16b","options":{"num_ctx":32768},"prompt":"hi"}' \
  >/dev/null
curl -s localhost:11434/api/ps \
  | jq -r '.models[] | "\(.context_length) ctx  \(.size_vram/1e9*10|round/10) GB VRAM  \(.size_vram/.size*100|round)% GPU"'
32768 ctx  13.8 GB VRAM  100% GPU

Depois o mesmo modelo em 128K, quatro vezes a janela:

# mesmo modelo, num_ctx=131072 recarrega com uma janela de 128K; resposta descartada, só queremos o load
curl -s localhost:11434/api/generate \
  -d '{"model":"deepseek-coder-v2:16b","options":{"num_ctx":131072},"prompt":"hi"}' \
  >/dev/null
curl -s localhost:11434/api/ps \
  | jq -r '.models[] | "\(.context_length) ctx  \(.size_vram/1e9*10|round/10) GB VRAM  \(.size_vram/.size*100|round)% GPU"'
131072 ctx  28.3 GB VRAM  100% GPU

Mesmos pesos, mesmo modelo:

  • Quatro vezes a janela mais ou menos dobra a memória, 13.8 GB → 28.3 GB.
  • Esses ~14 GB a mais são o KV cache crescendo, a única coisa que a janela mexe.
  • Nesta máquina ainda coube na VRAM a 100% GPU; em uma menor, a rodada de 128K vazaria para a CPU e se arrastaria.
  • A janela, não os pesos, é o que decide se um modelo cabe.

Memória é só metade do custo de uma janela grande:

  • A qualidade também deriva: modelos de contexto longo são amplamente relatados como ficando menos confiáveis conforme o contexto enche, bem antes de a janela acabar.
  • Um contexto grande só vale a pena pagar quando você realmente for usá-lo.

As famílias, lado a lado

Nomear famílias é uma coisa; rodá-las mostra o trade-off. Uma build de programação de cada família na mesma máquina, mesmo prompt --verbose:

Os modelos limitam suas janelas de contexto em tetos diferentes, então a memória é medida em 8K, a única janela que os quatro suportam, para manter os números comparáveis:

modeloparamsctx máxVRAM @ 8Ktokens/scapacidades
codellama:7b7B dense16K6.2 GB61.7completion
qwen2.5-coder:7b7.6B dense32K4.9 GB47.9completion, tools, insert
codegemma:7b9B dense8K7.4 GB40.6completion
deepseek-coder-v2:16b15.7B MoE (~2.4B ativos)160K10.3 GB106.5completion, insert

Maior não é mais lento, e tamanho não é o único eixo:

  • O DeepSeek é o maior aqui com 15.7B e mesmo assim o mais rápido de longe, mais que o dobro de qualquer um dos três modelos densos de 7-9B, porque seu design mixture-of-experts ativa só ~2.4B de parâmetros por token.
  • Entre os modelos densos, mais parâmetros custam um pouco de velocidade: o codellama de 7B lidera com 61.7, o codegemma de 9B fica atrás com 40.6, com o qwen no meio; toda essa diferença é menor que o salto para o modelo MoE.
  • A memória não acompanha a velocidade. Todos os 15.7B pesos do DeepSeek ficam na VRAM mesmo que só ~2.4B disparem por token, então numa janela compartilhada de 8K ele é o mais pesado (10.3 GB); entre os densos, o qwen é o mais leve (4.9 GB) apesar de ficar no meio do pelotão em parâmetros.
  • O teto da janela é um eixo à parte: o DeepSeek estica até 160K e o qwen até 32K, mas o codegemma para em 8K e o codellama em 16K, um limite real antes de você alimentar um arquivo longo.
  • A capacidade é, de novo, separada: só o qwen2.5-coder lista tools; as duas builds code* fazem só completion, e o DeepSeek adiciona insert mas não tools.
  • Tamanho, velocidade, memória, janela e capacidade se movem de forma independente; você escolhe o canto que serve para o trabalho.

Mais um eixo: instruct versus reasoning.

  • Todos os quatro modelos acima são modelos instruct, eles respondem na hora.
  • Um modelo reasoning como o deepseek-r1 pensa em aberto primeiro, gastando tokens numa cadeia de raciocínio visível antes da resposta.
  • Ele é mais lento, e mais forte em problemas passo a passo.
  • Baixe um (ollama pull deepseek-r1:8b) e você assiste ele raciocinar antes de responder.

Um modelo local é um substituto direto para uma API na nuvem

O Ollama serve um segundo caminho de API, /v1:

  • Ele fala o formato chat-completions da OpenAI, o formato de requisição e resposta que a API na nuvem da OpenAI popularizou.
  • Qualquer código ou ferramenta que já chama um modelo hospedado pode apontar para o Ollama mudando uma única coisa, a URL base:
curl -s localhost:11434/v1/chat/completions \
  -d '{"model":"deepseek-coder-v2:16b","messages":[{"role":"user","content":"Say hello in one short line."}]}' \
  | jq '{object, model, choices: [.choices[0].message], usage}'
{
  "object": "chat.completion",
  "model": "deepseek-coder-v2:16b",
  "choices": [ { "role": "assistant", "content": " Hello!" } ],
  "usage": { "prompt_tokens": 15, "completion_tokens": 3, "total_tokens": 18 }
}

Mesmo formato de requisição, mesmo formato de resposta, de um servidor na sua máquina:

  • A resposta traz a mesma contabilidade de usage que uma API na nuvem retorna.
  • Troque https://api.openai.com/v1 por http://localhost:11434/v1 e o código existente roda contra o modelo local.

Pi, apontado para os modelos locais

Pi, o agente de programação de Claude Code vs Pi, sai de fábrica conhecendo só provedores hospedados, Anthropic e Google. Um arquivo JSON, ~/.pi/agent/models.json, ensina a ele os locais. Ele adiciona o Ollama como um provedor customizado: um nome, a URL base /v1 acima e uma entrada por modelo, com ids exatamente como o ollama list os imprime (deepseek-coder-v2:16b, qwen2.5-coder:7b e qwen3:8b). O apiKey é um placeholder; o Ollama o ignora, mas o Pi exige um valor:

{
  "providers": {
    "ollama": {
      "baseUrl": "http://localhost:11434/v1",
      "api": "openai-completions",
      "apiKey": "ollama",
      "models": [
        { "id": "deepseek-coder-v2:16b", "contextWindow": 163840, "maxTokens": 8192 },
        { "id": "qwen2.5-coder:7b", "contextWindow": 32768, "maxTokens": 8192 },
        { "id": "qwen3:8b", "contextWindow": 40960, "maxTokens": 8192 }
      ]
    }
  }
}

contextWindow e maxTokens importam: o Pi não pergunta ao Ollama os limites de um modelo, ele acredita nesses metadados (e assume 128K quando a entrada os omite). Declare cada janela a partir da ficha técnica do ollama show do modelo, ou o Pi vai alegremente superalimentar um modelo de 32K. O pi --list-models ollama confirma a conexão:

provider  model                  context  max-out  thinking  images
ollama    deepseek-coder-v2:16b  163.8K   8.2K     no        no
ollama    qwen2.5-coder:7b       32.8K    8.2K     no        no
ollama    qwen3:8b               41.0K    8.2K     no        no

Um flag de capacidade é uma alegação, não uma prova

Inferência local sem uma API externa só compensa se o modelo consegue fazer o trabalho para o qual você o contratou, e para trabalho de agente o trabalho é a chamada de ferramenta (tool call).

Três dos modelos instalados competem por ele, e seus blocos Capabilities do ollama show discordam:

modelocapacidadestools?
deepseek-coder-v2:16bcompletion, insertnão
qwen2.5-coder:7bcompletion, tools, insertsim
qwen3:8bcompletion, tools, thinkingsim

Se um modelo com o flag consegue de fato fazer uma chamada de ferramenta é outra pergunta, e uma única sonda a responde: ofereça ao modelo uma única ferramenta, write, e peça um arquivo. A mesma requisição vai para os três modelos; só o campo model muda:

curl -s localhost:11434/api/chat -d '{
  "model": "deepseek-coder-v2:16b",
  "stream": false,
  "messages": [{"role":"user","content":"Create a file named hello.txt containing exactly the word: hello"}],
  "tools": [{"type":"function","function":{"name":"write","description":"Write a file","parameters":{"type":"object","properties":{"path":{"type":"string"},"content":{"type":"string"}},"required":["path","content"]}}}]
}'

A requisição tem duas partes móveis:

  • messages carrega o pedido, um único turno de usuário: crie um arquivo chamado hello.txt contendo exatamente a palavra hello.

    "messages": [
      {
        "role": "user",
        "content": "Create a file named hello.txt containing exactly the word: hello"
      }
    ]
  • tools entrega ao modelo a única função que ele pode chamar para dar conta disso: write, que recebe um objeto com path e content, ambos strings, ambos obrigatórios. O bloco parameters é um JSON Schema descrevendo os argumentos da função; o modelo o lê para dar forma a uma chamada válida.

    "tools": [
      {
        "type": "function",
        "function": {
          "name": "write",
          "description": "Write a file",
          "parameters": {
            "type": "object",
            "properties": {
              "path": { "type": "string" },
              "content": { "type": "string" }
            },
            "required": ["path", "content"]
          }
        }
      }
    ]

Um modelo que merece seu flag lê o pedido, o casa com a ferramenta e responde com um campo tool_calls estruturado carregando write e {"path": "hello.txt", "content": "hello"}.

Uma nota sutil: um arquivo em disco não é esperado, porque o /api/chat é texto entra, texto sai; executar a chamada é trabalho de quem chama. A sonda avalia exatamente uma coisa: se tool_calls volta preenchido. Todo o resto é só texto.

Três modelos, três veredictos

deepseek-coder-v2: sem flag, e a ausência é imposta

O deepseek-coder-v2 não lista tools, então a expectativa é que ele nem chegue a responder, e a sonda confirma isso na API:

{"error":"registry.ollama.ai/library/deepseek-coder-v2:16b does not support tools"}

O Ollama rejeita a requisição antes de o modelo sequer ver o prompt.

qwen2.5-coder: um falso positivo

O qwen2.5-coder:7b tem o flag, então a mesma requisição passa, e falha de um jeito mais interessante:

curl -s localhost:11434/api/chat -d '{
  "model": "qwen2.5-coder:7b",
  "stream": false,
  "messages": [{"role":"user","content":"Create a file named hello.txt containing exactly the word: hello"}],
  "tools": [{"type":"function","function":{"name":"write","description":"Write a file","parameters":{"type":"object","properties":{"path":{"type":"string"},"content":{"type":"string"}},"required":["path","content"]}}}]
}' | jq -r '"tool_calls: \(.message.tool_calls)\ncontent:\n\(.message.content)"'
tool_calls: null
content:
{
  "name": "write",
  "arguments": {
    "path": "hello.txt",
    "content": "hello"
  }
}

A resposta, lida contra a expectativa:

  • tool_calls: null: o único campo que a sonda avalia está vazio; o Ollama não interpretou chamada nenhuma.
  • content carrega a chamada mesmo assim: um payload write bem formado, com exatamente os argumentos certos, entregue como prosa.

O modelo entendeu a tarefa, escolheu a ferramenta certa e montou argumentos válidos, e ainda assim falhou na única coisa que conta: um campo tool_calls preenchido. O parser do Ollama procura exatamente o formato que o próprio chat template do modelo exige:

<tool_call>
{"name": "write", "arguments": {"path": "hello.txt", "content": "hello"}}
</tool_call>

Mas o modelo escreveu o JSON e pulou as tags. Sem tags, sem parse: tool_calls fica null, e a chamada cai no content como prosa. A culpa é do modelo? Vamos testar a mesma requisição com o qwen3:8b:

curl -s localhost:11434/api/chat -d '{
  "model": "qwen3:8b",
  "stream": false,
  "messages": [{"role":"user","content":"Create a file named hello.txt containing exactly the word: hello"}],
  "tools": [{"type":"function","function":{"name":"write","description":"Write a file","parameters":{"type":"object","properties":{"path":{"type":"string"},"content":{"type":"string"}},"required":["path","content"]}}}]
}' | jq -r '"tool_calls: \(.message.tool_calls)\ncontent:\n\(.message.content)"'
tool_calls: [{"id":"call_6epdfuul","function":{"index":0,"name":"write","arguments":{"path":"hello.txt","content":"hello"}}}]
content:

A requisição está bem formada e o parsing do Ollama funciona; o pipeline produz uma chamada estruturada sempre que o modelo faz a parte dele. Essa é a parte intrigante: o mesmo prompt, delegado a dois modelos da mesma família, ambos hasteando o flag tools, e só um entrega de verdade. A mesma requisição entra, e só os pesos diferem.

O flag do qwen2.5-coder anuncia o padrão; o modelo entrega um dialeto. Para dirigir um agente de prateleira, isso é um falso positivo.

qwen3: o flag, entregue

O qwen3:8b (ollama pull qwen3:8b), da mesma classe de tamanho que o qwen2.5-coder:7b e uma geração mais novo, responde à mesma sonda com uma chamada estruturada:

{
  "content": "",
  "tool_calls": [{"id": "call_9lsm4tio", "function": {"index": 0, "name": "write", "arguments": {"content": "hello", "path": "hello.txt"}}}]
}

A ficha técnica diz o que o chat template suporta; só uma sonda diz o que o modelo faz. Antes de construir qualquer coisa sobre um flag de capacidade, faça o modelo provar uma vez.

Ajustando a pegada de memória

Quando uma janela grande empurra o KV cache para além da sua VRAM, você tem botões no nível do servidor, distintos da quantização de pesos do próprio modelo. Inicie o Ollama com:

OLLAMA_FLASH_ATTENTION=1 OLLAMA_KV_CACHE_TYPE=q8_0 ollama serve

OLLAMA_KV_CACHE_TYPE=q8_0 armazena o KV cache em 8 bits em vez de 16:

  • Isso mais ou menos corta pela metade a memória dele por um custo mínimo de qualidade, então uma janela que vazava para a CPU pode voltar a caber na GPU.
  • Isso quantiza o KV cache (uma alavanca de runtime), não os pesos (Q4_0, embutidos no modelo).
  • Uma ressalva: esses são definidos no processo do servidor, então não se aplicam quando o Ollama roda sob o brew services. Pare esse e rode ollama serve você mesmo para usá-los.

Quando rodar você mesmo vale a pena

A inferência local conquista seu lugar por razões concretas, mais ou menos nesta ordem:

  • Custo em volume. Um modelo hospedado cobra por token; um local é um custo fixo de hardware e eletricidade. Passado um certo volume de chamadas, rodar o seu próprio é simplesmente mais barato.
  • Latência e controle. Sem salto de rede, sem rate limits, sem cota por chamada. O modelo responde tão rápido quanto sua GPU permite, tão frequentemente quanto você quiser.
  • Experimentação livre. Itere em ferramentas internas sem uma API medida no meio, sem conta por um prompt ruim, sem cota para cuidar.
  • Telemetria completa. O rodapé --verbose, o ollama ps e o /api/ps reportam tokens, memória e a janela carregada direto do runtime, não resumidos por trás de um dashboard de fornecedor.
  • Localidade dos dados. Um prompt para um modelo hospedado sai da sua máquina; um local nunca sai. O raio de impacto de um vazamento encolhe para esta única máquina, o que importa mais para dados sensíveis ou regulados.

É esse o ciclo inteiro: um brew install, um ollama pull, e um modelo de programação está escrevendo na sua própria GPU. A partir daí, o --verbose, o ollama ps e o /api/ps reportam sua velocidade, memória e janela a cada passo, e todo prompt fica na máquina. Nada nele é uma caixa-preta.

A nuvem ainda vence quando você precisa de qualidade de fronteira que nenhum modelo na sua GPU consegue igualar. A divisão honesta:

  • vá de local para custo, controle e experimentação
  • vá de um modelo de fronteira hospedado quando a tarefa exige o teto dele