A conta de guardanapo pra treino LoRA é conhecida: pesos + ativações + estados do otimizador. Num Mac de memória unificada essa conta mente, porque não existe uma fronteira entre RAM e VRAM onde você bate e recebe um erro claro. Você só fica lento. E aí passa uma hora achando que o problema é o batch size.
Então rodamos a mesma configuração de LoRA em cinco tamanhos de modelo até achar onde ela para de caber de verdade — não onde a fórmula diz que deveria parar.
O protocolo
Mesma coisa em todos: LoRA rank 16, alpha 32, adaptadores nas projeções de atenção, sequência de 512 tokens, 12 mil pares de treino. Só o modelo muda. A cada rodada medimos pico de memória, tokens por segundo e quanto o sistema paginou pra disco.
mlx_lm.lora \
--model "$MODEL" \
--train \
--data ./data \
--batch-size 2 \
--num-layers 16 \
--iters 600 \
--max-seq-length 512O detalhe que importa é medir o swap, não só a memória. memory_pressure e a coluna de compressed pages dizem o que o gráfico de RAM esconde:
# Páginas comprimidas e swap usado durante o treino, a cada 5s
while true; do
vm_stat | awk '/Pages occupied by compressor|Swapins/ {print $NF}' | tr '\n' ' '
echo
sleep 5
doneOnde ele para
Pico de memória durante o treino LoRA
GB · batch 2 · seq 512 · limite físico 16GB
ver como tabela
| GB | pico (GB) |
|---|---|
| 1B 4-bit | 4.1 |
| 3B 4-bit | 9.8 |
| 4B 4-bit | 13.6 |
| 7B 4-bit | 21.4 |
| 3B bf16 | 17.9 |
O 4B em 4-bit dá 13,6GB de pico. Cabe nos 16, no papel. Na prática o sistema operacional, o navegador e o próprio Python já ocupavam ~3GB, então o treino passou a rodada inteira paginando. Não travou — ficou 6x mais lento, que é pior, porque parece que está funcionando.
Throughput conta a mesma história
Tokens por segundo conforme o modelo cresce
tok/s durante o treino
ver como tabela
| tok/s durante o treino | tok/s |
|---|---|
| 1B | 412 |
| 3B | 168 |
| 4B | 27 |
| 7B | 4 |
A queda de 3B pra 4B é de 6x — muito além do que o dobro de parâmetros justificaria. Aquele degrau é o swap, não a computação. Entre 4B e 7B o treino deixa de ser um treino e vira uma demonstração de paciência.
| modelo | pico | tok/s | 600 iters | swapou? |
|---|---|---|---|---|
| 1B 4-bit | 4.1 GB | 412 | 8 min | não |
| 3B 4-bit | 9.8 GB | 168 | 22 min | não |
| 4B 4-bit | 13.6 GB | 27 | 2 h 15 | sim |
| 7B 4-bit | 21.4 GB | 4 | não terminou | muito |
| 3B bf16 | 17.9 GB | 11 | não terminou | muito |
A conta que funciona
Depois das rodadas, a estimativa que bateu com a realidade em 16GB de memória unificada:
Com parâmetros, bytes por peso, o rank, as camadas adaptadas, o comprimento da sequência e o batch. O termo que todo mundo esquece é o — e num Mac ele não é pequeno.
Regra prática: modelo em 4-bit ocupando menos de 60% da RAM total. Em 16GB isso dá 3B com folga, 4B se você fechar tudo e não respirar.
O que dá pra fazer com 3B
O 3B não é um consolo. Nas tarefas em que a gente realmente usa fine-tuning — formato de saída, vocabulário de domínio, tom — o 3B ajustado bateu o 7B genérico na nossa avaliação interna. O que ele não faz é raciocínio de múltiplos passos, e nenhum LoRA conserta isso.
Se o seu caso precisa de 7B treinado, alugue uma GPU por uma hora. Sai mais barato que a tarde que você vai perder tentando fazer caber.