← todos os papers
llm-local / fine-tuning / hardware4 fev 2026

O teto real de 16GB: onde LoRA para de caber no M5

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.

rodada.sh
mlx_lm.lora \
  --model "$MODEL" \
  --train \
  --data ./data \
  --batch-size 2 \
  --num-layers 16 \
  --iters 600 \
  --max-seq-length 512

O 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
done

Onde ele para

Pico de memória durante o treino LoRA

GB · batch 2 · seq 512 · limite físico 16GB

1B 4-bit
4.1
3B 4-bit
9.8
4B 4-bit
13.6
7B 4-bit
21.4
3B bf16
17.9
ver como tabela
GBpico (GB)
1B 4-bit4.1
3B 4-bit9.8
4B 4-bit13.6
7B 4-bit21.4
3B bf1617.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 treinotok/s
1B412
3B168
4B27
7B4

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.

modelopicotok/s600 itersswapou?
1B 4-bit4.1 GB4128 minnão
3B 4-bit9.8 GB16822 minnão
4B 4-bit13.6 GB272 h 15sim
7B 4-bit21.4 GB4não terminoumuito
3B bf1617.9 GB11não terminoumuito

A conta que funciona

Depois das rodadas, a estimativa que bateu com a realidade em 16GB de memória unificada:

MpicoPb+2rdL4LoRA + otimizador+shLB2ativac¸o˜es+MSOM_{\text{pico}} \approx P \cdot b + \underbrace{2 \cdot r \cdot d \cdot L \cdot 4}_{\text{LoRA + otimizador}} + \underbrace{s \cdot h \cdot L \cdot B \cdot 2}_{\text{ativações}} + M_{\text{SO}}

Com PP parâmetros, bb bytes por peso, rr o rank, LL as camadas adaptadas, ss o comprimento da sequência e BB o batch. O termo que todo mundo esquece é o MSOM_{\text{SO}} — 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.