treinar_lora_mlx.sh 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. #!/usr/bin/env bash
  2. # Treina um LoRA do Llama-3.1-8B-Instruct com os atendimentos exportados (estágio 2),
  3. # usando MLX no Apple Silicon. Ajustado para 16 GB de RAM (M4): modelo 4-bit, batch 1.
  4. #
  5. # Pré-requisito: dataset exportado (backend: node scripts/exportarDatasetTreinamento.js)
  6. # Uso: ./treinar_lora_mlx.sh [epocas] (default 3)
  7. set -euo pipefail
  8. cd "$(dirname "$0")"
  9. MODEL="mlx-community/Meta-Llama-3.1-8B-Instruct-4bit"
  10. DATA_DIR="data"
  11. ADAPTER_DIR="adapters"
  12. EPOCAS="${1:-3}"
  13. if [[ ! -f "$DATA_DIR/train.jsonl" ]]; then
  14. echo "ERRO: $DATA_DIR/train.jsonl não existe. Rode antes: cd .. && node scripts/exportarDatasetTreinamento.js" >&2
  15. exit 1
  16. fi
  17. if [[ ! -d .venv ]]; then
  18. python3 -m venv .venv
  19. fi
  20. source .venv/bin/activate
  21. pip -q install --upgrade mlx-lm
  22. echo "Filtrando amostras por tamanho real em tokens (evita picos de memória na validação)"
  23. python3 filtrar_por_tokens.py --data-dir "$DATA_DIR"
  24. N_TRAIN=$(wc -l < "$DATA_DIR/train.jsonl" | tr -d ' ')
  25. ITERS_TOTAL=$(( N_TRAIN * EPOCAS ))
  26. if (( ITERS_TOTAL < 100 )); then ITERS_TOTAL=100; fi
  27. # retomada: interrupções por falta de memória (rodando junto com outros processos
  28. # pesados) não custam o progresso já feito. O mlx_lm.lora sempre reinicia a
  29. # contagem de iteração em 1 a cada chamada (mesmo com --resume-adapter-file), então
  30. # guardamos o total acumulado em $ADAPTER_DIR/.progresso.
  31. #
  32. # Importante: quando o processo é morto de fora (harness/SO por falta de memória),
  33. # a árvore de processos inteira morre de uma vez — o bash NUNCA chega a rodar
  34. # código depois do `python3 ...`, então atualizar o .progresso só no final não
  35. # funciona. Por isso o progresso é escrito ao vivo, linha a linha, toda vez que o
  36. # mlx_lm reporta ter salvo um checkpoint (--save-every), não só quando o treino
  37. # termina. Os arquivos numerados (0000100_adapters.safetensors etc.) podem colidir
  38. # entre retomadas, já que o contador local sempre reinicia em 1 — isso é só
  39. # histórico/debug; o que importa pra retomar corretamente é o par
  40. # adapters.safetensors (sempre a versão mais recente) + .progresso (iteração
  41. # global correspondente).
  42. PROGRESSO_FILE="$ADAPTER_DIR/.progresso"
  43. ITERS_FEITAS=0
  44. [[ -f "$PROGRESSO_FILE" ]] && ITERS_FEITAS=$(cat "$PROGRESSO_FILE")
  45. RESUME_FLAGS=()
  46. if (( ITERS_FEITAS > 0 )) && [[ -f "$ADAPTER_DIR/adapters.safetensors" ]]; then
  47. RESUME_FLAGS=(--resume-adapter-file "$ADAPTER_DIR/adapters.safetensors")
  48. echo "Retomando de $ITERS_FEITAS/$ITERS_TOTAL iterações já feitas"
  49. fi
  50. ITERS=$(( ITERS_TOTAL - ITERS_FEITAS ))
  51. if (( ITERS < 1 )); then
  52. echo "Já foram feitas $ITERS_FEITAS/$ITERS_TOTAL iterações — nada a treinar. Apague $ADAPTER_DIR/ pra recomeçar do zero." >&2
  53. exit 0
  54. fi
  55. echo "Treinando: $N_TRAIN amostras, $EPOCAS épocas => $ITERS_TOTAL iterações no total ($ITERS_FEITAS já feitas, $ITERS restantes, batch 1)"
  56. set +e
  57. PYTHONUNBUFFERED=1 python3 run_lora_seguro.py \
  58. --model "$MODEL" \
  59. --train \
  60. --data "$DATA_DIR" \
  61. --adapter-path "$ADAPTER_DIR" \
  62. "${RESUME_FLAGS[@]}" \
  63. --batch-size 1 \
  64. --num-layers 16 \
  65. --iters "$ITERS" \
  66. --max-seq-length 2048 \
  67. --grad-checkpoint \
  68. --save-every 100 \
  69. --steps-per-eval 100 \
  70. --val-batches 10 2>&1 | while IFS= read -r linha; do
  71. echo "$linha"
  72. if [[ "$linha" == *"Saved adapter weights"* ]]; then
  73. local_iter=$(grep -oE '^Iter [0-9]+' <<<"$linha" | grep -oE '[0-9]+')
  74. [[ -n "$local_iter" ]] && echo "$(( ITERS_FEITAS + local_iter ))" > "$PROGRESSO_FILE"
  75. fi
  76. done
  77. TREINO_EXIT=${PIPESTATUS[0]}
  78. set -e
  79. if (( TREINO_EXIT == 0 )); then
  80. echo "$ITERS_TOTAL" > "$PROGRESSO_FILE"
  81. else
  82. echo "Treino interrompido (exit $TREINO_EXIT). Progresso salvo: $(cat "$PROGRESSO_FILE" 2>/dev/null || echo "$ITERS_FEITAS")/$ITERS_TOTAL. Rode o script de novo pra continuar." >&2
  83. exit "$TREINO_EXIT"
  84. fi
  85. echo
  86. echo "Adapter salvo em $ADAPTER_DIR/. Teste rápido:"
  87. echo " mlx_lm.generate --model $MODEL --adapter-path $ADAPTER_DIR --prompt 'Boa tarde, minha internet caiu'"
  88. echo "Para publicar no Ollama: ./publicar_no_ollama.sh"