LLMs locais no macOS: Ollama, LM Studio, MLX, llama.cpp

Tradução automática

Este artigo foi traduzido automaticamente a partir da versão original em inglês.

Quem corre modelos locais num Mac com Apple Silicon precisa de dimensionar a carga de trabalho completa, e não apenas de escolher um modelo que se consiga descarregar. O Apple Silicon consegue executar prompts do dia a dia sem uma API remota, porque o CPU e o GPU partilham uma única memória unificada, mas os pesos do modelo partilham essa memória com a cache que guarda o contexto, com os buffers de trabalho do runtime e com o próprio macOS.

Quando o modelo, o contexto e a margem de memória cabem, escolha a ferramenta que se adequa à sua forma de trabalhar: Ollama para um serviço local gerido, LM Studio para explorar modelos no desktop, llama.cpp para controlo direto das flags do runtime, ou MLX-LM para Python nativo da Apple. Vai aprender a estimar a memória, para que serve cada ferramenta, como executar cada uma e como compará-las na mesma carga de trabalho.

Para a tabela de decisão resumida, veja Ferramentas de LLMs locais no macOS em 2026.

Quanta memória precisa um modelo local

Quatro termos aparecem ao longo deste artigo:

  • Quantização guarda os pesos do modelo em menos bits para reduzir a memória. Num nome de ficheiro como Llama-3.1-8B-Instruct-Q4_K_M.gguf, Q4 indica um esquema de 4 bits; Q4_K_M tem em média pouco menos de 5 bits por peso, depois de contar as escalas e alguns tensores de maior precisão.
  • GGUF é o formato de modelo num único ficheiro usado pelo llama.cpp e pelas ferramentas construídas sobre ele. O MLX-LM carrega modelos convertidos para MLX, por isso o mesmo modelo é distribuído em downloads separados de GGUF e de MLX.
  • KV cache guarda as chaves e os valores de atenção de cada token do contexto. Cresce com o tamanho do contexto e com o número de pedidos em paralelo.
  • Família de checkpoint significa um modelo base com um tamanho, como o Llama 3.1 8B Instruct, qualquer que seja o seu formato de ficheiro ou quantização.

O gráfico soma os pesos e a KV cache de Llama 3.1 8B em Q4_K_M, com contextos de 8K e 32K. Compara esses subtotais com o conjunto de trabalho recomendado da GPU indicado num Mac M1 com 16 GB; o espaço de trabalho do runtime e o macOS precisam de memória adicional.

Barras empilhadas: 4,6 GiB de pesos mais 1 GiB de KV cache com contexto de 8K e 4 GiB com 32K, deixando 5,1 GiB e 2,1 GiB abaixo do conjunto de trabalho recomendado da GPU de 10,7 GiB indicado num M1 com 16 GB; o espaço de trabalho e a memória do sistema operativo estão excluídosBarras empilhadas: 4,6 GiB de pesos mais 1 GiB de KV cache com contexto de 8K e 4 GiB com 32K, deixando 5,1 GiB e 2,1 GiB abaixo do conjunto de trabalho recomendado da GPU de 10,7 GiB indicado num M1 com 16 GB; o espaço de trabalho e a memória do sistema operativo estão excluídos

O tamanho bruto dos pesos quantizados é apenas a primeira parcela:

peak memory ≈ model weights
            + KV cache
            + runtime workspace
            + multimodal components
            + application and OS memory

O tamanho do contexto, o dtype da cache, os pedidos em paralelo e a arquitetura do modelo alteram o resultado. A KV cache é a parcela que controla mais diretamente, através do tamanho do contexto:

KV bytes per token = 2 × layers × KV heads × head dimension × bytes per value

Para o Llama 3.1 8B (32 camadas, 8 KV heads, dimensão de head 128) com uma cache de 16 bits, isso dá 2 × 32 × 8 × 128 × 2 = 131 072 bytes, ou 128 KiB por token. Um contexto de 8K precisa de 1 GiB de cache por pedido, e um contexto de 32K precisa de 4 GiB. Os pesos Q4_K_M são um ficheiro de 4,9 GB (4,6 GiB). Antes da área de trabalho, o modelo precisa de cerca de 5,6 GiB com contexto de 8K e de cerca de 8,6 GiB com contexto de 32K.

O Metal indica um tamanho recomendado para o conjunto de trabalho da GPU, recommendedMaxWorkingSetSize, que estima quanta memória da GPU pode ser atribuída sem afetar o desempenho do runtime. É uma recomendação de desempenho, não um limite imposto à atribuição. Num relatório de 2023 sobre um M1 com 16 GB, o llama.cpp imprimiu 10 923 MB, cerca de 10,7 GiB ou dois terços da RAM. Face a essa recomendação, o subtotal de 8K deixa 5,1 GiB para o espaço de trabalho e a margem; o de 32K deixa 2,1 GiB. Verifique o valor no seu próprio Mac e meça a carga completa antes de decidir se cabe. Um Mac com 8 GB tem menos margem, e só estes pesos já precisam de 4,6 GiB.

Planeie também o espaço em disco. Cada modelo de 7B ou 8B a 4 bits é um download de 4–5 GB, e uma pequena biblioteca de modelos nos dois formatos ocupa 10–20 GB.

Use o Monitor de Atividade ou as métricas do próprio runtime durante os testes. A Apple define a pressão de memória com base na memória livre, na taxa de swap, na memória wired e nos ficheiros em cache. Deixe margem suficiente para evitar swap prolongado; um modelo que carrega mas leva o sistema a pressão de memória não serve para uso interativo.

Cada secção de ferramenta abaixo mostra onde definir o tamanho do contexto, porque essa definição é a que mais altera a KV cache.

As quatro ferramentas resolvem problemas diferentes de fluxo de trabalho

FerramentaInterface principalFormato do modeloLicençaEscolha-a quando
OllamaCLI e API HTTP localPacotes de modelos geridos, sobretudo GGUFMITUma aplicação precisa de um serviço local gerido e simples
LM StudioInterface desktop, CLI, SDKs, APIs locaisGGUF e MLXAplicação proprietária, gratuita para trabalho; SDKs MITUma pessoa precisa de descobrir, comparar, inspecionar e servir modelos visualmente
llama.cppCLI, biblioteca C/C++, servidor localGGUFMITPrecisa de flags diretas, ferramentas de conversão e quantização, ou inferência embebida
MLX-LMPython e CLIPesos em formato MLXMITEstá a escrever Python para Apple Silicon ou a fazer fine-tuning de um modelo no mesmo Mac

Esta é uma tabela de responsabilidades, não uma classificação de velocidade. Três ferramentas partilham uma biblioteca de tensores. O Ollama executa modelos GGUF com o seu próprio motor, construído sobre GGML, a biblioteca em que assenta o llama.cpp. A versão preliminar MLX do Ollama de março de 2026 também executa modelos selecionados em Macs com mais de 32 GB de memória. O LM Studio executa modelos GGUF com llama.cpp e modelos MLX com MLX. Partilhar GGML não significa usar motores de inferência idênticos. Mesmo com o mesmo ficheiro GGUF, as predefinições, como o comprimento do contexto e o tamanho do lote, as implementações dos motores e as versões do backend podem afetar a velocidade.

O diagrama mostra os formatos de modelos e as bibliotecas partilhadas usadas por cada ferramenta, incluindo o percurso condicional de MLX do Ollama.

Ollama, llama.cpp e LM Studio usam GGML para ficheiros GGUF, mas podem usar motores de inferência diferentes; LM Studio e MLX-LM usam pesos em formato MLX; a versão preliminar MLX do Ollama de março de 2026 abrange modelos selecionados em Macs com mais de 32 GB; GGML e MLX suportam MetalOllama, llama.cpp e LM Studio usam GGML para ficheiros GGUF, mas podem usar motores de inferência diferentes; LM Studio e MLX-LM usam pesos em formato MLX; a versão preliminar MLX do Ollama de março de 2026 abrange modelos selecionados em Macs com mais de 32 GB; GGML e MLX suportam Metal

Se ainda não sabe qual é o seu fluxo de trabalho, use a última coluna da tabela. Comece pelo Ollama se só quer ter algo a funcionar hoje.

Há um caso fora das quatro ferramentas: o KoboldCpp, um fork do llama.cpp com interface web, foi feito para ficção longa e role-play. Tem licença AGPL, o que importa se o distribuir dentro de um produto.

Uma API para as quatro ferramentas

As quatro ferramentas podem servir uma API compatível com a OpenAI em localhost. O código de aplicação que usa o cliente openai pode mudar de ferramenta alterando base_url, e o mesmo cliente pode fazer o benchmark das quatro.

FerramentaComando do servidorbase_url por omissão
Ollamaollama serve (a app macOS executa-o)http://localhost:11434/v1
LM Studiolms server starthttp://localhost:1234/v1
llama.cppllama-server -hf <repo>:<quant>http://localhost:8080/v1
MLX-LMmlx_lm.server --model <repo>http://localhost:8080/v1

O cliente exige um valor de chave API. Use local como valor de substituição quando a autenticação do servidor está desativada; use o seu token configurado quando está ativada. llama-server e mlx_lm.server usam ambos a porta 8080 por predefinição, por isso inicie um deles com --port 8081 para executar ambos.

Os exemplos Python que importam openai precisam do pacote openai. O benchmark abaixo inclui um comando que executa o script guardado com essa dependência.

from openai import OpenAI

# Swap base_url for :1234 (LM Studio) or :8080 (llama.cpp, MLX-LM).
client = OpenAI(base_url="http://localhost:11434/v1", api_key="local")

A secção de benchmark abaixo usa este cliente para medir o tempo das quatro ferramentas com um único script.

Ollama: serviço local gerido

O Ollama gere os downloads de modelos, os templates, o ciclo de vida dos processos e uma API em localhost. É útil quando o código da aplicação deve usar um serviço local estável, em vez de ser ele a controlar as flags de inferência.

MODEL=llama3.2
ollama pull "$MODEL"
ollama show "$MODEL"   # architecture, parameters, context length, quantization
ollama run "$MODEL" "Explain unified memory."

curl http://localhost:11434/api/chat \
    -H 'Content-Type: application/json' \
    -d '{
      "model": "'"$MODEL"'",
      "messages": [{"role": "user", "content": "Explain unified memory."}],
      "options": {"num_ctx": 8192},
      "stream": false
    }'

A opção num_ctx define o tamanho do contexto de um pedido. Para o definir em todo o servidor, inicie-o com OLLAMA_CONTEXT_LENGTH=8192 ollama serve ou use o controlo deslizante nas definições da app. Sem nenhuma das duas, o Ollama escolhe um valor por omissão com base na memória disponível: 4K tokens quando há menos de 24 GiB de memória de GPU disponíveis. ollama ps mostra o contexto que cada modelo carregado usa.

Um nome curto de biblioteca como llama3.2 não identifica um ficheiro fixo; o mesmo nome pode apontar para outra versão depois de uma atualização. Registe o ID que ollama list apresenta, que é o início do digest do modelo, em cada avaliação.

O Ollama troca alguma visibilidade de baixo nível pela conveniência no ciclo de vida. Passe para o llama.cpp ou para outro runtime quando precisar de controlar diretamente um ficheiro GGUF, um template de chat, uma definição da cache ou uma funcionalidade nova do backend.

LM Studio: exploração no desktop e APIs locais

O LM Studio é útil quando a descoberta de modelos, a configuração de carregamento, a inspeção do chat e a avaliação humana lado a lado pertencem ao mesmo fluxo de trabalho. Expõe SDKs nativos de Python e TypeScript, endpoints compatíveis com a OpenAI, structured output, tool use e um daemon headless, por isso é mais do que uma interface gráfica de desktop. A sua vista de chat também aceita ficheiros .pdf, .docx e .txt anexados como contexto, o que cobre perguntas simples sobre documentos sem escrever código.

O SDK Python atual liga-se a um modelo que já descarregou. Inicie o servidor local do LM Studio, guarde este código como lmstudio_demo.py e execute uv run --with lmstudio python lmstudio_demo.py. Se a autenticação estiver ativada, defina LM_API_TOKEN; o SDK lê-o automaticamente.

import lmstudio as lms

MODEL_KEY = "ibm/granite-4-micro"

with lms.Client() as client:
    model = client.llm.model(MODEL_KEY)
    response = model.respond("Write one sentence about local inference.")
    print(response)

Inicie a API local a partir do separador Developer ou da CLI. lms load define o tamanho do contexto ao carregar um modelo, e --estimate-only apresenta a estimativa de memória sem carregar nada:

lms server start
lms load ibm/granite-4-micro --context-length 8192 --estimate-only
lms load ibm/granite-4-micro --context-length 8192

O LM Studio pode servir em localhost ou numa rede local e suporta tokens de API nas suas definições de autenticação da API. Mantenha-o em loopback, a menos que o acesso remoto seja deliberado. Se o expuser numa LAN, exija a opção documentada de token de API, aplique uma regra de firewall no host e reveja o acesso de ferramentas e integrações.

llama.cpp: execução direta de GGUF

O llama.cpp é a ferramenta de referência para ficheiros GGUF quando quer definir as flags do runtime por si. Suporta o Apple Metal, além de CPU e de outros backends de hardware.

brew install llama.cpp

# Download through the Hugging Face integration and select a quantization.
MODEL_REPO=ggml-org/gemma-3-1b-it-GGUF
QUANT=Q4_K_M
llama-cli -hf "$MODEL_REPO:$QUANT"

# The same model with explicit settings: one turn, then exit.
llama-cli -hf "$MODEL_REPO:$QUANT" \
    -c 8192 -n 256 --temp 0.7 -st \
    -p "Write a Python function that sorts a list."

# Or start an OpenAI-compatible local server with an 8K context.
llama-server -hf "$MODEL_REPO:$QUANT" -c 8192

-c (--ctx-size) define o tamanho do contexto e, com ele, o tamanho da KV cache. -n limita o número de tokens gerados, --temp define a temperatura de amostragem, -st executa um único turno e termina, e -p fornece o prompt.

A opção -hf também descarrega um projetor multimodal correspondente quando o repositório tem um. O suporte de modelos, os templates e as flags da CLI mudam depressa, por isso fixe uma build conhecida e guarde o comando de arranque junto do registo da avaliação.

Escolha o llama.cpp quando o objetivo for o controlo direto, e não porque “mais baixo nível” signifique automaticamente mais rápido. Uma ferramenta gerida pode escolher bons valores por omissão; flags diretas também podem piorar o desempenho.

MLX-LM: trabalho em Python nativo da Apple

O MLX-LM assenta no framework de arrays MLX da Apple. Suporta geração, chat, conversão, quantização e fine-tuning eficiente em parâmetros para modelos compatíveis.

MODEL=mlx-community/Llama-3.2-3B-Instruct-4bit
uv run --with mlx-lm mlx_lm.generate \
    --model "$MODEL" \
    --max-kv-size 8192 \
    --prompt "Explain Metal acceleration in one paragraph."

O MLX-LM não tem uma definição fixa de contexto: a cache cresce com o prompt e com a saída. --max-kv-size limita a cache a esse número de tokens. Acima do limite, o MLX-LM descarta os tokens mais antigos e mantém apenas alguns tokens do início.

O Python expõe diretamente o modelo e o tokenizer. Guarde isto como mlx_demo.py e execute uv run --with mlx-lm python mlx_demo.py:

from mlx_lm import generate, load

MODEL = "mlx-community/Llama-3.2-3B-Instruct-4bit"

model, tokenizer = load(MODEL)
messages = [{"role": "user", "content": "Give one local-LLM benchmark rule."}]
prompt = tokenizer.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=True,
)
print(generate(model, tokenizer, prompt=prompt, max_tokens=80))

O servidor HTTP do MLX-LM, mlx_lm.server, está documentado como um servidor de desenvolvimento com verificações de segurança básicas, não como um serviço de produção. Use-o para experimentação local ou coloque à frente dele um proxy autenticado ou a sua própria API.

Comparar ferramentas na mesma carga de trabalho

Teste a mesma família de checkpoint e uma quantização comparável, quando os formatos o permitirem. Um ficheiro GGUF Q4_K_M e um modelo MLX de 4 bits do mesmo checkpoint são comparáveis, não idênticos.

Use um pequeno conjunto de prompts com:

  • um prompt interativo curto
  • um prompt longo perto do contexto pretendido
  • structured output ou tool calls, se a aplicação precisar deles
  • um comprimento de geração representativo
  • um pedido repetido para distinguir o carregamento a frio da inferência a quente

Registe:

MétricaPorque importa
Resultado da tarefaUm modelo rápido e errado não serve
Tempo até ao primeiro tokenCapacidade de resposta interativa
Tokens de saída por segundoDébito de geração
Pico de memória e pressãoSe a máquina continua utilizável
Tempo de carregamento a frioExperiência no desktop e a pedido
Comportamento energético e térmicoUso prolongado em portátil
Compatibilidade de API/schemaSe a ferramenta se adequa à aplicação

Cada ferramenta apresenta os números de velocidade por si:

  • Ollama: ollama run "$MODEL" --verbose apresenta a duração do carregamento, a taxa de avaliação do prompt e a taxa de avaliação (tokens de saída por segundo) depois de cada resposta.
  • llama.cpp: llama-bench -hf "$MODEL_REPO:$QUANT" -p 512 -n 128 mede o processamento de um prompt de 512 tokens e a geração de 128 tokens, em tokens por segundo.
  • MLX-LM: mlx_lm.generate apresenta os tokens por segundo do prompt e da geração e o pico de memória depois de cada execução.
  • LM Studio: a vista de chat mostra os tokens por segundo e o tempo até ao primeiro token por baixo de cada resposta, e a REST API nativa devolve os mesmos valores no campo stats.

Para comparar a latência observada pelo cliente, cronometre um pedido em streaming aos endpoints compatíveis com OpenAI indicados em «Uma API para as quatro ferramentas». O script mede a duração total do pedido e o tempo até ao primeiro texto visível, e depois imprime a resposta para inspeção. O texto visível pode surgir depois do raciocínio ou da saída de ferramentas, por isso este tempo não é necessariamente o tempo do runtime até ao primeiro token gerado.

Inicie o servidor escolhido com um modelo descarregado, guarde o script como benchmark.py e execute uv run --with openai python benchmark.py. Altere BASE_URL e MODEL por ferramenta. Defina LOCAL_API_KEY com o seu token configurado se a autenticação do servidor estiver ativada; caso contrário, o script usa local como valor de substituição.

import os
import time

from openai import OpenAI

BASE_URL = "http://localhost:11434/v1"
MODEL = "llama3.2"

client = OpenAI(base_url=BASE_URL, api_key=os.environ.get("LOCAL_API_KEY", "local"))

start = time.perf_counter()
first = None
parts = []
stream = client.chat.completions.create(
    model=MODEL,
    messages=[{"role": "user", "content": "Explain unified memory in 200 words."}],
    max_tokens=300,
    stream=True,
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        if first is None:
            first = time.perf_counter()
        parts.append(chunk.choices[0].delta.content)
end = time.perf_counter()

print(f"request duration: {end - start:.2f} s")
if first is None:
    print("time to first visible text: unavailable (no visible text)")
else:
    print(f"time to first visible text: {first - start:.2f} s")
print("response:", "".join(parts))

Os fragmentos de texto em streaming não são contagens de tokens. Para obter tokens de saída por segundo, use os contadores do runtime indicados acima e registe se incluem raciocínio ou saída de ferramentas. Por exemplo, o LM Studio apresenta separadamente os tokens de saída totais e os de raciocínio. Um stream sem texto visível indica essa ausência em vez de atribuir uma latência de texto; inspecione o raciocínio, os tool calls ou o estado de conclusão através da interface do runtime.

Execute cada pedido duas vezes: o primeiro pode incluir o carregamento do modelo e o segundo pode reutilizar os pesos carregados e o estado do prompt em cache. Registe as versões do runtime e as definições de cache de prompts para que os pedidos repetidos continuem comparáveis.

Não compare o modelo de 4 bits de uma ferramenta com o modelo de precisão total de outra e atribua a diferença ao runtime.

Checklist de segurança e privacidade

Separe inferência local de funcionamento offline. Os prompts podem ficar na máquina enquanto a aplicação ainda acede à rede para downloads de modelos, atualizações ou funcionalidades opcionais. O Ollama documenta separadamente a execução local, os downloads de modelos e as funcionalidades opcionais na cloud nas suas FAQ. Trate o funcionamento offline como um requisito do fluxo de trabalho a verificar em cada aplicação: descarregue primeiro os artefactos, desligue a rede e teste o fluxo de trabalho completo.

O diagrama mostra quatro percursos de rede a verificar durante a inferência local: que parte do Mac cada um alcança, em que sentido circulam os dados e o que verificar em cada um. As funcionalidades na nuvem e o acesso por LAN dependem da configuração da aplicação.

A aplicação do seu Mac envia dados para funcionalidades opcionais na cloud e recebe atualizações; outras máquinas da LAN só chegam ao servidor local se este escutar para além de 127.0.0.1; as transferências de modelos chegam da biblioteca do Ollama ou do Hugging Face; cada caminho indica a sua verificaçãoA aplicação do seu Mac envia dados para funcionalidades opcionais na cloud e recebe atualizações; outras máquinas da LAN só chegam ao servidor local se este escutar para além de 127.0.0.1; as transferências de modelos chegam da biblioteca do Ollama ou do Hugging Face; cada caminho indica a sua verificação

  1. Associe as APIs ao loopback, a menos que o acesso por rede seja necessário.
  2. Se o acesso por LAN for necessário, use uma firewall no host e a autenticação documentada da ferramenta ou um reverse proxy autenticado à frente do serviço. O suporte a tokens de API depende da ferramenta: o LM Studio documenta tokens de API e o llama-server aceita --api-key. O Ollama escuta em 127.0.0.1:11434 por omissão, e OLLAMA_HOST altera o endereço de associação. A sua API local não exige autenticação, por isso coloque um proxy autenticado à frente dela para uso em LAN.
  3. Trate os ficheiros de modelo como artefactos de terceiros; registe a origem, a revisão, a licença e o hash.
  4. Os ficheiros GGUF não contêm código executável. Os modelos em formato Hugging Face podem incluir código Python; não passe --trust-remote-code ao MLX-LM nem ao Transformers para um repositório que não reviu.
  5. Confirme se funcionalidades opcionais de documentos, ferramentas, atualizações ou analítica fazem chamadas de rede.
  6. Não assuma que a geração local torna seguros os documentos obtidos, os logs ou os efeitos secundários das ferramentas.

Referências