MLX: rodar LLM nativo em M3/M4 Apple Silicon
- ⬜🎯 Speculative decoding: 2-3x speedup grátis(Local LLMs & Edge AI)
Recomendamos completar os pré-requisitos antes de seguir, mas nada te impede de continuar.
Em dezembro de 2023, Apple Machine Learning Research lançou silenciosamente o MLX — um framework de arrays inspirado em NumPy/JAX/PyTorch, mas desenhado do zero para Apple Silicon. Em 2026, com chips M3 Ultra (192GB unified memory, 800 GB/s bandwidth) e M4 Max, o MLX se tornou a maneira de rodar modelos gigantes em hardware de escritório: Llama 405B INT4 cabe em um Mac Studio que cabe na sua mesa.
O diferencial Apple Silicon: unified memory
Em arquiteturas tradicionais (PC + dGPU NVIDIA/AMD), CPU e GPU têm memórias separadas, conectadas por PCIe (até ~64 GB/s em PCIe 5.0). Carregar um modelo grande exige cópias entre RAM e VRAM. Em Apple Silicon, tudo é uma memória só — fisicamente compartilhada entre CPU, GPU, Neural Engine e AMX, com até 800 GB/s de bandwidth no M3 Ultra.
| Chip | CPU cores | GPU cores | Neural Engine | Memory | Bandwidth | Llama 70B INT4 tok/s |
|---|---|---|---|---|---|---|
| M2 Max | 12 (8P + 4E) | 38 | 16-core | 32-96 GB | 400 GB/s | ~12-15 |
| M2 Ultra | 24 (16P + 8E) | 76 | 32-core | 64-192 GB | 800 GB/s | ~18-22 |
| M3 Max | 16 (12P + 4E) | 40 | 16-core | 36-128 GB | 400 GB/s | ~14-18 |
| M3 Ultra | 32 (24P + 8E) | 80 | 32-core | 96-192 GB | 800 GB/s | ~22-28 |
| M4 Pro | 14 (10P + 4E) | 20 | 16-core | 24-64 GB | 273 GB/s | ~8-10 (45B INT4) |
| M4 Max | 16 (12P + 4E) | 40 | 16-core | 36-128 GB | 546 GB/s | ~16-20 |
Para comparar: RTX 4090 24GB tem 1008 GB/s de bandwidth mas só 24GB. Roda Llama 8B INT4 em 80-120 tok/s — mais rápido que qualquer Mac em modelos pequenos. Mas não roda Llama 70B+ sem multi-GPU. A unified memory do M3 Ultra é a única forma de rodar 70B-405B em hardware single-box até hoje.
MLX core: arrays lazy + automatic differentiation
# MLX core — NumPy-like com lazy evaluation
import mlx.core as mx
import mlx.nn as nn
# Arrays não são materializados até mx.eval()
a = mx.random.normal(shape=(1024, 1024))
b = mx.random.normal(shape=(1024, 1024))
c = a @ b # lazy: grafo construído
d = mx.exp(c) + a # lazy: fusão potencial
# Nenhum cálculo ainda
mx.eval(d) # materializa — agora computa
print(d.shape) # (1024, 1024)
# Diferenciação automática
def loss_fn(W, x, y):
return mx.mean((x @ W - y) ** 2)
grad_fn = mx.grad(loss_fn)
W = mx.random.normal((10, 1))
x = mx.random.normal((100, 10))
y = mx.random.normal((100, 1))
dW = grad_fn(W, x, y) # gradiente vs primeiro arg
# Composability: gradients of gradients
hessian_diag = mx.grad(mx.grad(loss_fn))(W, x, y)mlx-lm: LLMs de produção em uma linha
# Instalação
pip install mlx-lm
# Download e geração — modelo já quantizado em INT4
python -m mlx_lm.generate \
--model mlx-community/Meta-Llama-3.1-70B-Instruct-4bit \
--prompt "Explique PagedAttention em 3 bullets." \
--max-tokens 500 \
--temp 0.5
# Conversão própria de HF → MLX INT4
python -m mlx_lm.convert \
--hf-path meta-llama/Meta-Llama-3.1-8B-Instruct \
--mlx-path ./Llama-3.1-8B-Instruct-mlx-q4 \
--quantize \
-q --q-bits 4 --q-group-size 64
# Servidor compatível com OpenAI API
python -m mlx_lm.server \
--model mlx-community/Qwen2.5-72B-Instruct-4bit \
--port 8080 \
--temp 0.5
# curl http://localhost:8080/v1/chat/completions \
# -d '{"model":"mlx","messages":[{"role":"user","content":"oi"}]}'# API Python idiomática
from mlx_lm import load, generate, stream_generate
model, tokenizer = load("mlx-community/Meta-Llama-3.1-8B-Instruct-4bit")
prompt = tokenizer.apply_chat_template(
[{"role": "user", "content": "O que é unified memory em M3 Ultra?"}],
tokenize=False,
add_generation_prompt=True,
)
# Generation simples
response = generate(model, tokenizer, prompt=prompt, max_tokens=500, temp=0.5)
print(response)
# Streaming
for tok in stream_generate(model, tokenizer, prompt=prompt, max_tokens=500):
print(tok.text, end="", flush=True)
# Speculative decoding nativo
from mlx_lm import generate
draft_model, _ = load("mlx-community/Llama-3.2-1B-Instruct-4bit")
response = generate(
model, tokenizer, prompt=prompt, max_tokens=500,
draft_model=draft_model, num_draft_tokens=5,
)Fine-tune LoRA local — 70B em um Mac Studio
# mlx_lm.lora — QLoRA-style em hardware Apple
python -m mlx_lm.lora \
--model mlx-community/Meta-Llama-3.1-70B-Instruct-4bit \
--train \
--data ./data_jsonl \ # JSONL com {"prompt":..., "completion":...}
--iters 1000 \
--batch-size 2 \
--lora-layers 16 \ # quantos layers receber LoRA (top N)
--learning-rate 1e-5 \
--adapter-path ./ffv-tutor-lora \
--save-every 200
# Fundir LoRA com base
python -m mlx_lm.fuse \
--model mlx-community/Meta-Llama-3.1-70B-Instruct-4bit \
--adapter-path ./ffv-tutor-lora \
--save-path ./ffv-tutor-70b-fused-mlx
# Servir o modelo fundido
python -m mlx_lm.server --model ./ffv-tutor-70b-fused-mlxEm M3 Ultra 192GB: Llama 70B INT4 (~35GB) + LoRA adapters (~500MB) + ativações + KV cache ≈ 50GB total. Sobra muita RAM para batch maior. 1000 iters de fine-tune em dataset de 10k exemplos: 4-8 horas. Mesma carga em 4× A100 80GB: 1-2h, mas custa ~$15-30 na AWS — o Mac Studio paga em alguns ciclos.
O que a memória unificada permite que uma placa dedicada de consumo não permite?
mlx-vlm: visão multimodal
# Modelos multimodais (VLMs) no MLX
from mlx_vlm import load, generate
model, processor = load("mlx-community/Qwen2.5-VL-7B-Instruct-4bit")
response = generate(
model, processor,
image="https://example.com/diagram.png",
prompt="Descreva o diagrama de arquitetura.",
max_tokens=500,
temp=0.3,
)
# Suportados:
# - LLaVA family (1.5, 1.6, NeXT)
# - Qwen2-VL, Qwen2.5-VL
# - Llama 3.2 Vision (11B, 90B)
# - Phi-3-vision, Phi-3.5-vision
# - InternVL2
# - Idefics3Comparativo de performance real
| Modelo | M3 Ultra 192GB | M4 Max 128GB | RTX 4090 24GB | A100 80GB |
|---|---|---|---|---|
| Llama 3.1 8B INT4 | ~70 tok/s | ~50 tok/s | ~120 tok/s (vLLM) | ~180 tok/s |
| Llama 3.1 70B INT4 | ~24 tok/s | ~18 tok/s (cabe apertado) | Não cabe (split) | ~60 tok/s |
| Llama 3.1 405B INT4 | ~5-7 tok/s | Não cabe | Não cabe | Não cabe (single GPU) |
| Qwen 2.5 72B INT4 | ~22 tok/s | ~17 tok/s | Não cabe | ~55 tok/s |
| Phi-4 14B INT4 | ~55 tok/s | ~45 tok/s | ~95 tok/s | ~150 tok/s |
| Whisper Large v3 | Real-time 5× | Real-time 4× | Real-time 10× | Real-time 15× |
Perguntas frequentes
❓ MLX roda em iPhone/iPad?
❓ Como faço o MLX usar Neural Engine (ANE)?
❓ Onde achar modelos prontos para MLX?
❓ MLX tem equivalente ao DeepSpeed/FSDP?
Referências
Fixando
Por que a largura de banda de memória é o número mais relevante desses chips para inferência?
Qual é a limitação apontada dessa plataforma?
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…