# Correr um LLM Local em Silício da Apple para Triagem de Segurança

> Configurar o MLX e o llama.cpp num Mac, o que a quantização realmente troca em memória, um padrão de instrução para triar uma linha de registo de ligação, e os limites reais de pedir a um pequeno modelo local que faça trabalho de segurança.

HisnLabs Security & Research Team · Published 2026-09-19
Canonical: https://hisnlabs.com/pt/blog/local-llm-apple-silicon-security

Um modelo que classifica uma ligação de rede como merecedora de atenção ou não, a correr no mesmo Mac que fez a ligação, muda três coisas ao mesmo tempo: o que sai da máquina, quanto custa correr, e se continua a funcionar quando a própria rede é aquilo sob suspeita. As três importam mais para uma ferramenta de segurança do que para a maioria dos outros usos de um modelo de linguagem, e é por isso que este artigo trata especificamente do caso no dispositivo, em vez de chamar uma API.

## Por que no dispositivo, especificamente para triagem

Enviar uma linha de registo de ligação para um modelo na nuvem, para classificação, significa transmitir, para cada decisão, qual a aplicação no seu Mac a falar com que anfitrião, em que porta, neste momento. Nada disso é um segredo da forma como uma palavra-passe é, mas é exatamente o tipo de metadados que uma configuração consciente da segurança tenta minimizar partilhar, e uma ferramenta cujo propósito inteiro é decidir o que o seu Mac tem permissão para enviar para outro lado tem uma razão óbvia para não depender ela própria de enviar algo para outro lado em cada decisão que toma. Correr o modelo localmente elimina essa dependência por completo: a linha de registo nunca sai do dispositivo, porque não há nenhuma chamada de rede no percurso da decisão.

A segunda razão é a continuidade. Um passo de triagem baseado na nuvem só está tão disponível quanto a sua ligação à internet e a API do fornecedor, ambas exatamente as coisas que podem estar degradadas ou deliberadamente cortadas durante um incidente real. Um modelo a correr em memória local continua a responder com a rede em baixo, o Wi-Fi desligado, ou um compromisso suspeito em curso na mesma ligação que a chamada à API teria usado.

## MLX: a própria estrutura de arrays da Apple

O MLX é uma estrutura de arrays construída pela equipa de investigação em aprendizagem automática da Apple especificamente para o silício da Apple, com APIs em Python, C++, C e Swift. A sua própria documentação descreve um modelo de memória unificada como a sua escolha de conceção definidora: os arrays no MLX vivem em memória partilhada, e as operações sobre eles podem correr em qualquer dispositivo suportado — CPU ou GPU — sem que os pesos do modelo tenham primeiro de ser copiados entre grupos de memória separados. Isso importa concretamente para um portátil: uma máquina com GPU dedicada tem de copiar os pesos de um modelo através de um barramento para a memória da GPU antes de conseguir computar seja o que for, o que custa tempo e duplica a pegada de memória; na arquitetura de memória unificada de um Mac, a CPU e a GPU já partilham a mesma memória física, pelo que não há nada para copiar.

O mlx-lm, o pacote complementar para correr modelos de linguagem sobre o MLX, instala-se com um único comando e traz um modelo predefinido já pronto a usar:

*Configuração do MLX*

```console
$ pip install mlx-lm
# runs the default model (mlx-community/Llama-3.2-3B-Instruct-4bit)
$ mlx_lm.generate --prompt "How tall is Mt Everest?"
# or name a specific quantized model from the mlx-community hub
$ mlx_lm.generate --model mlx-community/Mistral-7B-Instruct-v0.3-4bit --prompt "..."
# interactive chat session instead of a single prompt
$ mlx_lm.chat
# convert and quantize a model yourself
$ mlx_lm.convert --model mistralai/Mistral-7B-Instruct-v0.3 -q
```

## llama.cpp: a opção portátil

O llama.cpp é o mais antigo e mais amplamente portado dos dois, escrito em C/C++ sem necessidade de nenhum runtime Python em tempo de inferência. O seu próprio README declara diretamente a posição do projeto quanto a este hardware: o silício da Apple é tratado, nas palavras do projeto, como "um cidadão de primeira classe — otimizado através das estruturas ARM NEON, Accelerate e Metal", e a sua documentação de compilação confirma que, no macOS, o backend Metal da GPU vem ativado por defeito, com uma opção em tempo de compilação para o desativar caso queira especificamente inferência apenas em CPU.

*Compilação e execução do llama.cpp*

```console
$ git clone https://github.com/ggml-org/llama.cpp
$ cd llama.cpp
$ cmake -B build
$ cmake --build build --config Release
# Metal is on by default on macOS; add -DGGML_METAL=OFF to the first
# command above if you need to force CPU-only inference
$ llama cli -hf ggml-org/Qwen3.5-0.8B-GGUF
# or run it as a local server instead of a one-shot command
$ llama serve -hf ggml-org/Qwen3.5-0.8B-GGUF
```

O llama.cpp trabalha a partir de ficheiros GGUF, um formato de modelo num único ficheiro que junta os pesos quantizados e tudo o que é necessário para os correr; o comando acima obtém um diretamente de um repositório do Hugging Face pelo nome. O MLX, por contraste, mantém-se mais próximo de um fluxo de trabalho nativo em Python, convertendo e quantizando modelos para o seu próprio formato antecipadamente. Nenhum é estritamente melhor: o llama.cpp é a escolha se quiser um único binário compilado sem dependência de Python, o MLX é a escolha se já estiver a construir o resto do seu pipeline em Python e quiser acesso de primeira classe ao modelo de memória unificada da Apple a partir daí.

## Quantização: o que realmente se troca por um modelo mais pequeno

A quantização guarda cada peso do modelo em menos bits do que os 16 ou 32 em que o modelo foi treinado, encolhendo tanto o ficheiro em disco como a memória necessária para o correr, a algum custo na qualidade da saída. A própria documentação de quantização do llama.cpp lista valores exatos de bits por peso para cada esquema que suporta, o que é uma forma mais precisa de pensar sobre a troca do que os habituais atalhos de "4 bits" ou "8 bits":

| Família de esquema | Esquemas de exemplo | Bits por peso |
| --- | --- | --- |
| Precisão total (referência) | F16 | 16,0 |
| Quase sem perdas | Q8_0 | 8,50 |
| Meio-termo de maior qualidade | Q5_K_S / Q5_K_M | 5,57 - 5,70 |
| Qualidade e tamanho equilibrados | Q4_K_S / Q4_K_M | 4,67 - 4,89 |
| Mais pequeno, mais perdas | Q3_K_S / Q3_K_M / Q3_K_L | 3,64 - 4,30 |
| Compressão extrema | IQ2_XXS ... IQ2_M | 2,00 - 2,93 |

*Da documentação da ferramenta de quantização do llama.cpp.*

Multiplicar o número de parâmetros de um modelo pelo seu valor de bits por peso dá uma estimativa aproximada de memória apenas para os pesos: um modelo de 7 mil milhões de parâmetros a Q4_K_M, com 4,89 bits por peso, precisa de aproximadamente 7 000 000 000 × 4,89 ÷ 8 bytes, ou cerca de 4,3 GB, antes de contar a janela de contexto e as ativações intermédias, que acrescentam mais consoante a quantidade de texto que lhe der. Este é um cálculo a partir do valor de bits por peso citado, não um número que qualquer um dos projetos publique diretamente, e o uso real de memória será maior assim que um pedido real e o seu contexto forem carregados. A orientação prática que resulta da tabela é simples: para uma tarefa como classificar uma linha de registo de ligação de cada vez, em que a entrada é curta e a saída necessária é um pequeno veredito estruturado, um modelo da classe Q4_K_M é muitas vezes a troca certa — visivelmente mais pequeno e mais rápido do que Q8_0 ou F16, sem descer ao nível de compressão extrema, onde a qualidade da saída se degrada de forma mais acentuada.

Nenhum dos projetos publica um requisito de memória por configuração de Mac, por isso a abordagem prática é trabalhar a partir da estimativa acima, deixando uma margem real: o próprio macOS, o seu navegador e o que mais estiver a correr também precisam de memória unificada, e um modelo que cabe à justa, sem mais nada aberto, vai trocar (swap) ou parar assim que mudar para outra aplicação. Num Mac com uma quantidade modesta de memória unificada, isso aconselha a manter-se mais próximo do extremo mais pequeno da tabela acima — alguns milhares de milhões de parâmetros a Q4_K_M em vez de um modelo muito maior à mesma quantização — e a reservar as opções maiores e de maior precisão para máquinas com memória de sobra. Para uma tarefa de classificação restrita como a de que trata este artigo, um modelo mais pequeno a quem se faz uma pergunta precisa tende a ser tanto mais rápido como, na prática, mais consistente do que um modelo maior a quem se dá uma pergunta vaga.

Ambos os projetos estão em desenvolvimento ativo, e a comparação honesta é menos "qual é melhor" do que "qual se encaixa no seu pipeline." O llama.cpp compila para um único binário sem necessidade de runtime Python em tempo de inferência, o que importa se quiser incorporá-lo dentro de outra peça de software sem enviar também um interpretador Python. O MLX assume que já está a trabalhar em Python (ou em Swift, para o qual também disponibiliza bindings) e recompensa isso com uma integração mais estreita na própria pilha de aprendizagem automática da Apple e no modelo de memória unificada descrito acima. Uma ferramenta de segurança construída como uma aplicação macOS autónoma, que é a situação em que o próprio FireAI se encontra, fica mais próxima do extremo do llama.cpp desse espetro; um caderno de investigação a explorar qual o padrão de instrução que funciona melhor fica mais próximo do extremo do MLX.

## Um padrão de instrução para triar uma ligação

Quanto mais estreita a pergunta que se faz a um pequeno modelo local, mais fiavelmente ele responde. Para uma única ligação, isso significa dar-lhe exatamente os campos que um revisor humano observaria — nada mais, nada inferido — e pedir um veredito estruturado, em vez de texto corrido livre:

*modelo de instrução de triagem (ilustrativo, não testado contra nenhum conjunto de dados)*

```text
System: You review one outbound network connection at a time. You are
given only the fields listed below. Do not assume anything not stated.
Respond with exactly two lines: a verdict (allow, ask, or block) and a
one-sentence reason a non-expert could understand.

Connection:
  app: UpdaterHelper.app
  code_signature: unsigned
  destination: 91.203.xxx.xxx:4444
  protocol: TCP
  threat_feed_hit: none
  first_seen: yes (no prior rule for this app)

Verdict:
```

Os campos que importam são os que uma verificação de assinatura de código e um feed de ameaças conseguem de facto produzir sem adivinhar: se o binário está assinado e por quem, para onde está a tentar ligar-se e em que porta, se esse destino aparece num feed de ameaças, e se é a primeira vez que esta aplicação tenta ligar-se. Pedir uma saída fixa de duas linhas, em vez de uma explicação aberta, torna o resultado mais fácil de registar, mais fácil de comparar entre milhares de ligações, e muito mais difícil de o modelo encher com linguagem evasiva que soa autorizada sem dizer nada verificável.

Um modelo pequeno vai, ocasionalmente, ignorar o formato pedido de qualquer forma — três linhas em vez de duas, uma ressalva extra, uma palavra de veredito que não é nenhuma das três que pediu. Trate isso como um problema de engenharia, não de modelação: valide a saída contra a forma exata que espera, e se não corresponder, ou volte a perguntar ou recorra ao veredito mais seguro (ask, ou seja, mostrar a um humano), em vez de tentar analisar uma resposta mais solta. Um passo de triagem que falha em segurança perante uma saída malformada é muito mais útil do que um que ocasionalmente produz uma linha de aparência confiante mas impossível de analisar, e a descarta em silêncio.

Corra este padrão ao longo de um dia inteiro de tentativas de ligação, em vez de uma de cada vez, e a forma da carga de trabalho muda: a maioria das ligações vêm de aplicações com uma regra já existente e nunca chegam a alcançar o modelo, um número menor são genuinamente vistas pela primeira vez e recebem um veredito, e apenas uma fração desses veredictos é outra coisa que não uma permissão de rotina. O verdadeiro trabalho do modelo, nesse ponto, não é ser um perito em segurança; é reduzir uma longa lista de ligações vistas pela primeira vez ao pequeno subconjunto que uma pessoa realmente precisa de ver, o que é um objetivo muito mais alcançável para um modelo de poucos milhares de milhões de parâmetros do que seria um juízo de segurança aberto.

## Onde isto corre mal

- Um pequeno modelo local pode produzir uma razão confiante, bem escrita e inteiramente errada. Nada em correr localmente muda esse risco; só muda onde o erro acontece, não se pode acontecer.
- As janelas de contexto são finitas, e um registo longo e ruidoso não cabe. Resumir ou pré-filtrar antes de o modelo ver os dados introduz o seu próprio ponto de falha — pode perder a única linha que importava antes de o modelo sequer ter oportunidade de a ver.
- O modelo só vê o que a linha de registo contém. Não consegue ver dentro de tráfego cifrado, não consegue verificar se um feed de ameaças está atualizado, e não consegue conhecer a sua intenção — uma ligação de uma ferramenta que acabou de instalar de propósito parece idêntica a uma de uma ferramenta de que nunca ouviu falar.
- Um veredito não é uma ação. Nada aqui deve bloquear, apagar ou permitir silenciosamente uma ligação por si só; uma pessoa continua a precisar de ver a razão e confirmá-la, e de poder reverter a decisão se o modelo se enganou.

Esse último ponto não é uma limitação específica de um pequeno modelo quantizado a correr num portátil — é verdade para toda a decisão de segurança automatizada, local ou na nuvem, modelo pequeno ou grande. O valor de o correr localmente está no que remove da equação (uma dependência de rede, uma fatura recorrente, um terceiro a receber os seus metadados de ligação), não numa alegação de que remove a necessidade de um humano no ciclo.

## Onde o FireAI encaixa neste padrão

O FireAI, a firewall da HisnLabs para Mac, inclui algo construído sobre a mesma ideia, mas com um âmbito mais estreito do que um modelo de chat de uso geral: um modelo local opcional, uma transferência adicional de cerca de 1,5 GB, que corre inteiramente no Mac e revê ligações de aplicações que ainda não têm nenhuma regra. Exige macOS 14 ou posterior, aplica feeds de ameaças públicos localmente em vez de os verificar contra um serviço remoto, e mostra a razão da sua decisão no mesmo pedido de permissão que usa para perguntar se deve permitir ou negar a ligação — cada uma dessas decisões torna-se numa regra visível e editável, e qualquer uma delas pode ser desfeita. Vale a pena ser preciso sobre o que isto é e não é: não é o modelo de chat aberto, de poucos milhares de milhões de parâmetros, que este artigo tem vindo a descrever, e não é um antivírus nem uma VPN — faz uma única tarefa restrita de classificação, localmente, e deixa a decisão final a quem lê o pedido.

## O papel do FireAI e da HisnLabs

FireAI’s own connection reviewer is this exact bet, made narrower still: an optional, roughly 1.5 GB on-device model, macOS 14 and up (on an Intel Mac, through the free Ollama app), reviewing one thing (should this unknown app reach this destination) with a visible reason and an undo button — and, like the triage pattern in this article, it still needs a person to confirm anything consequential.

O FireAI é o produto da HisnLabs: uma firewall com IA que corre diretamente no seu Mac. Mostra, em linguagem clara, cada ligação que as suas aplicações fazem e deixa-o decidir o que sai do seu Mac — a IA funciona localmente, pelo que o seu tráfego nunca é enviado para nós nem para mais ninguém. A equipa de investigação em segurança da HisnLabs é quem mantém essas decisões fiáveis: cataloga que domínios são simples telemetria e quais são um serviço real, acompanha o país e a rede por detrás de uma ligação e treina o modelo local (a funcionalidade FireAI Pilot) com padrões de tráfego reais, sem que nada disso saia do seu Mac.

Pode ler as decisões técnicas por detrás dele, ou experimentar o FireAI durante 17 dias, em [FireAI, da HisnLabs](https://hisnlabs.com/fireai/pt/download).

## Sources

- [ml-explore/mlx: an array framework for Apple silicon](https://github.com/ml-explore/mlx)
- [ml-explore/mlx-lm: run and serve LLMs with MLX](https://github.com/ml-explore/mlx-lm)
- [ggml-org/llama.cpp](https://github.com/ggml-org/llama.cpp)
- [llama.cpp: build documentation](https://github.com/ggml-org/llama.cpp/blob/master/docs/build.md)
- [llama.cpp: quantize tool documentation (bits per weight by scheme)](https://github.com/ggml-org/llama.cpp/blob/master/tools/quantize/README.md)
