Objetivo da Aula
Ao concluir esta aula, você será capaz de:
Configurar hyperparameters no job de fine-tuning (n_epochs, batch_size, learning_rate_multiplier)
Implementar polling de status com client.fine_tuning.jobs.retrieve() em loop
Usar o modo --dry-run como salvaguarda antes de executar de verdade
Completar os 2 TODOs do starter Python
Interpretar status do job: validating_files → queued → running → succeeded/failed
Por que isso importa
Fine-tuning custa dinheiro e tempo. Um job mal configurado (n_epochs=20 quando 3 seriam suficientes) pode custar 6x mais e produzir modelo com overfitting. Polling sem timeout pode fazer um script rodar indefinidamente se o job travar.
Conceitos Fundamentais
Hiperparâmetros de Fine-Tuning
Fundamento: Hiperparâmetros e Ciclo do Job
n_epochs: quantas vezes o modelo vê o dataset completo. Mais épocas = mais custo de treino e risco de overfitting (decorar em vez de generalizar). learning_rate_multiplier: escala a taxa de aprendizado base — maior = ajuste mais agressivo por epoch, maior risco de perder qualidade do modelo base. batch_size: quantos exemplos processa antes de ajustar pesos — maior batch tende a generalizar melhor mas usa mais memória. O job é assíncrono: vai de validating_files → queued → running → succeeded (ou failed). Monitore o gap entre erro de treino e de validação: se treino cai mas validação sobe, o modelo está overfitting — pare mais cedo ou reduza n_epochs.
Parâmetro | Padrão | Quando ajustar |
n_epochs | "auto" (1-4) | Aumente para dataset pequeno (<100 exemplos) |
batch_size | "auto" | Deixe “auto” sempre — OpenAI otimiza |
learning_rate_multiplier | 1.0 | Diminua (0.1) se modelo muito diferente do base |
"auto" é quase sempre a melhor escolha para batch_size.TODO 1: criar_job_finetuning(file_id, modelo_base)
def criar_job_finetuning(file_id: str, modelo_base: str = "gpt-4o-mini-2024-07-18") -> str:
if DRY_RUN:
print(f" [DRY-RUN] Criaria job:")
print(f" modelo={modelo_base}")
print(f" file={file_id}")
print(f" n_epochs=3, batch_size=auto, lr=1.0")
print(f" suffix=suporte-ia-v1")
return "ftjob-SIMULADO456"
from openai import OpenAI
client = OpenAI()
response = client.fine_tuning.jobs.create(
training_file=file_id,
model=modelo_base,
hyperparameters={
'n_epochs': 3,
'batch_size': 'auto',
'learning_rate_multiplier': 1.0,
},
suffix='suporte-ia-v1', # o modelo será nomeado ft:gpt-4o-mini:org:suporte-ia-v1:HASH
)
print(f" [JOB] job_id: {response.id} | status: {response.status}")
return response.idTODO 2: monitorar_job(job_id) com Polling
def monitorar_job(job_id: str) -> dict:
if DRY_RUN:
print(f" [DRY-RUN] Monitoraria job {job_id}")
return {"status": "simulado", "modelo": "ft:gpt-4o-mini:org:suporte-ia:SIMULADO"}
from openai import OpenAI
client = OpenAI()
TIMEOUT_MINUTOS = 60
POLL_INTERVALO = 30 # segundos
inicio = time.time()
print(f" [POLL] Monitorando job {job_id}...")
while True:
# 1. Verificar timeout
elapsed = (time.time() - inicio) / 60
if elapsed > TIMEOUT_MINUTOS:
return {"status": "timeout", "erro": f"Job não concluiu em {TIMEOUT_MINUTOS} minutos"}
# 2. Recuperar status atual
job = client.fine_tuning.jobs.retrieve(job_id)
print(f" [POLL] Status: {job.status} ({elapsed:.1f}min)")
# 3. Verificar estado terminal
if job.status == "succeeded":
return {"status": "succeeded", "modelo": job.fine_tuned_model}
if job.status == "failed":
erro = str(job.error) if job.error else "Erro desconhecido"
return {"status": "failed", "erro": erro}
if job.status == "cancelled":
return {"status": "cancelled", "erro": "Job foi cancelado"}
# 4. Aguardar para próximo poll (estados não-terminais: validating_files, queued, running)
print(f" [POLL] Aguardando {POLL_INTERVALO}s...")
time.sleep(POLL_INTERVALO)Aprofundamento Técnico
Ciclo de Vida do Job
upload → file-XXXX (processamento assíncrono)
criar_job → ftjob-XXXX
└── validating_files (5-10 min)
└── queued (espera em fila)
└── running (treinamento ativo)
├── succeeded → fine_tuned_model = "ft:gpt-4o-mini:org:nome:HASH"
└── failed → error.message com detalheEstimativa de Custo por n_epochs
Dataset: 1000 exemplos × 300 tokens médios = 300K tokens
n_epochs=1: 300K tokens × $0.008/1K = $2.40
n_epochs=3: 900K tokens × $0.008/1K = $7.20 ← recomendado
n_epochs=5: 1.5M tokens × $0.008/1K = $12.00Mais épocas não é sempre melhor — verifique métricas de validação.
suffix e Identificação do Modelo
suffix="suporte-ia-v1" → modelo nomeado:
"ft:gpt-4o-mini-2024-07-18:sua-org:suporte-ia-v1:abcd1234"
Sem suffix:
"ft:gpt-4o-mini-2024-07-18:sua-org:abcd1234" ← difícil identificarUse sufixos descritivos: v1, prod, experimento-rag, etc.
Exemplos Anotados
Exemplo 1: Dry-run completo
$ python starter.py # dry-run por padrão
=== FINE-TUNING OPENAI API (DRY-RUN) ===
[UPLOAD] dataset_exemplo.jsonl → file-SIMULADO123
[DRY-RUN] Criaria job:
modelo=gpt-4o-mini-2024-07-18
file=file-SIMULADO123
n_epochs=3, batch_size=auto, lr=1.0
suffix=suporte-ia-v1
[DRY-RUN] Monitoraria job ftjob-SIMULADO456
Resultado: {'status': 'simulado', 'modelo': 'ft:gpt-4o-mini:org:suporte-ia:SIMULADO'}
Concluído. Use --live para executar de verdade.Padrões e Armadilhas
Padrões
Padrão 1: Sempre dry-run primeiro
python starter.py # dry-run: sem custos, sem API calls reais
python starter.py --live # execução real: cobra dinheiroPadrão 2: Cancelar job mal iniciado
client.fine_tuning.jobs.cancel(job_id) # cancela se job ainda não está "running"Armadilhas
⚠️ Armadilha 1: Poll sem sleep → rate limit
# ERRADO: loop sem sleep
while True:
job = client.fine_tuning.jobs.retrieve(job_id) # 429 em segundos
# CORRETO: aguardar entre polls
time.sleep(30)⚠️ Armadilha 2: n_epochs alto com dataset grande 1000 exemplos × 5 épocas × 300 tokens = 1.5M tokens = $12. Fine-tuning de 3 épocas quase sempre é suficiente.
⚠️ Armadilha 3: Arquivo inválido não detectado até validating_files Valide o JSONL antes de fazer upload. Cada linha deve ter {"messages": [...]} com roles corretos.
Se não for realizar o laboratório, pule para o próximo capítulo.
Ponte para o Lab
TODO 1 — criar_job_finetuning(file_id, modelo_base): adicione hyperparameters={'n_epochs': 3, 'batch_size': 'auto', 'learning_rate_multiplier': 1.0} e suffix='suporte-ia-v1'.
TODO 2 — monitorar_job(job_id): loop com client.fine_tuning.jobs.retrieve(job_id), time.sleep(30), verificar job.status in ('succeeded', 'failed', 'cancelled'), timeout após 60 minutos.
Agora você está pronto para o lab.